blob: a35ccd36c87738f6302559315c7b61aa98170e66 [file] [log] [blame]
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001#!/usr/bin/env vpython3
2
kjellander@webrtc.org595749f2012-05-31 20:19:05 +00003# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS. All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000010"""Script for constraining traffic on the local machine."""
11
12import logging
13import optparse
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000014import socket
15import sys
16
17import config
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000018import network_emulator
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000019
20_DEFAULT_LOG_LEVEL = logging.INFO
21
22# Default port range to apply network constraints on.
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000023_DEFAULT_PORT_RANGE = (32768, 65535)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000024
kjellander@webrtc.org3a6ff412013-09-03 13:01:31 +000025# The numbers below are gathered from Google stats from the presets of the Apple
26# developer tool called Network Link Conditioner.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000027_PRESETS = [
28 config.ConnectionConfig(1, 'Generic, Bad', 95, 95, 250, 2, 100),
29 config.ConnectionConfig(2, 'Generic, Average', 375, 375, 145, 0.1, 100),
30 config.ConnectionConfig(3, 'Generic, Good', 1000, 1000, 35, 0, 100),
31 config.ConnectionConfig(4, '3G, Average Case', 780, 330, 100, 0, 100),
32 config.ConnectionConfig(5, '3G, Good', 850, 420, 90, 0, 100),
33 config.ConnectionConfig(6, '3G, Lossy Network', 780, 330, 100, 1, 100),
34 config.ConnectionConfig(7, 'Cable Modem', 6000, 1000, 2, 0, 10),
35 config.ConnectionConfig(8, 'DSL', 2000, 256, 5, 0, 10),
36 config.ConnectionConfig(9, 'Edge, Average Case', 240, 200, 400, 0, 100),
37 config.ConnectionConfig(10, 'Edge, Good', 250, 200, 350, 0, 100),
38 config.ConnectionConfig(11, 'Edge, Lossy Network', 240, 200, 400, 1, 100),
39 config.ConnectionConfig(12, 'Wifi, Average Case', 40000, 33000, 1, 0, 100),
40 config.ConnectionConfig(13, 'Wifi, Good', 45000, 40000, 1, 0, 100),
41 config.ConnectionConfig(14, 'Wifi, Lossy', 40000, 33000, 1, 0, 100),
Mirko Bonadei8cc66952020-10-30 10:13:45 +010042]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000043_PRESETS_DICT = dict((p.num, p) for p in _PRESETS)
44
45_DEFAULT_PRESET_ID = 2
46_DEFAULT_PRESET = _PRESETS_DICT[_DEFAULT_PRESET_ID]
47
48
49class NonStrippingEpilogOptionParser(optparse.OptionParser):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010050 """Custom parser to let us show the epilog without weird line breaking."""
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000051
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010052 def format_epilog(self, formatter):
53 return self.epilog
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000054
55
kjellanderc88b5d52017-04-05 06:42:43 -070056def _GetExternalIp():
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010057 """Finds out the machine's external IP by connecting to google.com."""
58 external_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
59 external_socket.connect(('google.com', 80))
60 return external_socket.getsockname()[0]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000061
62
kjellanderc88b5d52017-04-05 06:42:43 -070063def _ParseArgs():
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010064 """Define and parse the command-line arguments."""
65 presets_string = '\n'.join(str(p) for p in _PRESETS)
66 parser = NonStrippingEpilogOptionParser(epilog=(
67 '\nAvailable presets:\n'
68 ' Bandwidth (kbps) Packet\n'
69 'ID Name Receive Send Queue Delay loss \n'
70 '-- ---- --------- -------- ----- ------- ------\n'
71 '%s\n' % presets_string))
72 parser.add_option('-p',
73 '--preset',
74 type='int',
75 default=_DEFAULT_PRESET_ID,
76 help=('ConnectionConfig configuration, specified by ID. '
77 'Default: %default'))
78 parser.add_option('-r',
79 '--receive-bw',
80 type='int',
81 default=_DEFAULT_PRESET.receive_bw_kbps,
82 help=('Receive bandwidth in kilobit/s. Default: %default'))
83 parser.add_option('-s',
84 '--send-bw',
85 type='int',
86 default=_DEFAULT_PRESET.send_bw_kbps,
87 help=('Send bandwidth in kilobit/s. Default: %default'))
88 parser.add_option('-d',
89 '--delay',
90 type='int',
91 default=_DEFAULT_PRESET.delay_ms,
92 help=('Delay in ms. Default: %default'))
93 parser.add_option('-l',
94 '--packet-loss',
95 type='float',
96 default=_DEFAULT_PRESET.packet_loss_percent,
97 help=('Packet loss in %. Default: %default'))
98 parser.add_option('-q',
99 '--queue',
100 type='int',
101 default=_DEFAULT_PRESET.queue_slots,
102 help=('Queue size as number of slots. Default: %default'))
103 parser.add_option('--port-range',
104 default='%s,%s' % _DEFAULT_PORT_RANGE,
105 help=('Range of ports for constrained network. Specify as '
106 'two comma separated integers. Default: %default'))
107 parser.add_option('--target-ip',
108 default=None,
109 help=('The interface IP address to apply the rules for. '
110 'Default: the external facing interface IP address.'))
111 parser.add_option('-v',
112 '--verbose',
113 action='store_true',
114 default=False,
115 help=('Turn on verbose output. Will print all \'ipfw\' '
116 'commands that are executed.'))
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000117
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100118 options = parser.parse_args()[0]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000119
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100120 # Find preset by ID, if specified.
121 if options.preset and options.preset not in _PRESETS_DICT:
122 parser.error('Invalid preset: %s' % options.preset)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000123
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100124 # Simple validation of the IP address, if supplied.
125 if options.target_ip:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000126 try:
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100127 socket.inet_aton(options.target_ip)
128 except socket.error:
129 parser.error('Invalid IP address specified: %s' % options.target_ip)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000130
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100131 # Convert port range into the desired tuple format.
132 try:
133 if isinstance(options.port_range, str):
134 options.port_range = tuple(
135 int(port) for port in options.port_range.split(','))
136 if len(options.port_range) != 2:
137 parser.error('Invalid port range specified, please specify two '
138 'integers separated by a comma.')
139 except ValueError:
140 parser.error('Invalid port range specified.')
141
142 _InitLogging(options.verbose)
143 return options
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000144
145
kjellanderc88b5d52017-04-05 06:42:43 -0700146def _InitLogging(verbose):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100147 """Setup logging."""
148 log_level = _DEFAULT_LOG_LEVEL
149 if verbose:
150 log_level = logging.DEBUG
151 logging.basicConfig(level=log_level, format='%(message)s')
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000152
153
kjellanderc88b5d52017-04-05 06:42:43 -0700154def main():
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100155 options = _ParseArgs()
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000156
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100157 # Build a configuration object. Override any preset configuration settings if
158 # a value of a setting was also given as a flag.
159 connection_config = _PRESETS_DICT[options.preset]
160 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
161 connection_config.receive_bw_kbps = options.receive_bw
162 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
163 connection_config.send_bw_kbps = options.send_bw
164 if options.delay is not _DEFAULT_PRESET.delay_ms:
165 connection_config.delay_ms = options.delay
166 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
167 connection_config.packet_loss_percent = options.packet_loss
168 if options.queue is not _DEFAULT_PRESET.queue_slots:
169 connection_config.queue_slots = options.queue
170 emulator = network_emulator.NetworkEmulator(connection_config,
171 options.port_range)
172 try:
173 emulator.CheckPermissions()
174 except network_emulator.NetworkEmulatorError as e:
175 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
176 return -1
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000177
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100178 if not options.target_ip:
179 external_ip = _GetExternalIp()
180 else:
181 external_ip = options.target_ip
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000182
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100183 logging.info('Constraining traffic to/from IP: %s', external_ip)
184 try:
185 emulator.Emulate(external_ip)
186 logging.info(
187 'Started network emulation with the following configuration:\n'
188 ' Receive bandwidth: %s kbps (%s kB/s)\n'
189 ' Send bandwidth : %s kbps (%s kB/s)\n'
190 ' Delay : %s ms\n'
191 ' Packet loss : %s %%\n'
192 ' Queue slots : %s', connection_config.receive_bw_kbps,
193 connection_config.receive_bw_kbps / 8, connection_config.send_bw_kbps,
194 connection_config.send_bw_kbps / 8, connection_config.delay_ms,
195 connection_config.packet_loss_percent, connection_config.queue_slots)
196 logging.info('Affected traffic: IP traffic on ports %s-%s',
197 options.port_range[0], options.port_range[1])
198 input('Press Enter to abort Network Emulation...')
199 logging.info('Flushing all Dummynet rules...')
200 network_emulator.Cleanup()
201 logging.info('Completed Network Emulation.')
202 return 0
203 except network_emulator.NetworkEmulatorError as e:
204 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
205 return -2
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100206
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000207
208if __name__ == '__main__':
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100209 sys.exit(main())