blob: 51224c80b10897c735f99954210f5092a57fd026 [file] [log] [blame]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +00001#!/usr/bin/env python
2# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS. All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +00009"""Script for constraining traffic on the local machine."""
10
11import logging
12import optparse
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000013import socket
14import sys
15
16import config
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000017import network_emulator
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000018
19_DEFAULT_LOG_LEVEL = logging.INFO
20
21# Default port range to apply network constraints on.
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000022_DEFAULT_PORT_RANGE = (32768, 65535)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000023
kjellander@webrtc.org3a6ff412013-09-03 13:01:31 +000024# The numbers below are gathered from Google stats from the presets of the Apple
25# developer tool called Network Link Conditioner.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000026_PRESETS = [
27 config.ConnectionConfig(1, 'Generic, Bad', 95, 95, 250, 2, 100),
28 config.ConnectionConfig(2, 'Generic, Average', 375, 375, 145, 0.1, 100),
29 config.ConnectionConfig(3, 'Generic, Good', 1000, 1000, 35, 0, 100),
30 config.ConnectionConfig(4, '3G, Average Case', 780, 330, 100, 0, 100),
31 config.ConnectionConfig(5, '3G, Good', 850, 420, 90, 0, 100),
32 config.ConnectionConfig(6, '3G, Lossy Network', 780, 330, 100, 1, 100),
33 config.ConnectionConfig(7, 'Cable Modem', 6000, 1000, 2, 0, 10),
34 config.ConnectionConfig(8, 'DSL', 2000, 256, 5, 0, 10),
35 config.ConnectionConfig(9, 'Edge, Average Case', 240, 200, 400, 0, 100),
36 config.ConnectionConfig(10, 'Edge, Good', 250, 200, 350, 0, 100),
37 config.ConnectionConfig(11, 'Edge, Lossy Network', 240, 200, 400, 1, 100),
38 config.ConnectionConfig(12, 'Wifi, Average Case', 40000, 33000, 1, 0, 100),
39 config.ConnectionConfig(13, 'Wifi, Good', 45000, 40000, 1, 0, 100),
40 config.ConnectionConfig(14, 'Wifi, Lossy', 40000, 33000, 1, 0, 100),
Mirko Bonadei8cc66952020-10-30 10:13:45 +010041]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000042_PRESETS_DICT = dict((p.num, p) for p in _PRESETS)
43
44_DEFAULT_PRESET_ID = 2
45_DEFAULT_PRESET = _PRESETS_DICT[_DEFAULT_PRESET_ID]
46
47
48class NonStrippingEpilogOptionParser(optparse.OptionParser):
Mirko Bonadei8cc66952020-10-30 10:13:45 +010049 """Custom parser to let us show the epilog without weird line breaking."""
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000050
Mirko Bonadei8cc66952020-10-30 10:13:45 +010051 def format_epilog(self, formatter):
52 return self.epilog
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000053
54
kjellanderc88b5d52017-04-05 06:42:43 -070055def _GetExternalIp():
Mirko Bonadei8cc66952020-10-30 10:13:45 +010056 """Finds out the machine's external IP by connecting to google.com."""
57 external_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
58 external_socket.connect(('google.com', 80))
59 return external_socket.getsockname()[0]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000060
61
kjellanderc88b5d52017-04-05 06:42:43 -070062def _ParseArgs():
Mirko Bonadei8cc66952020-10-30 10:13:45 +010063 """Define and parse the command-line arguments."""
64 presets_string = '\n'.join(str(p) for p in _PRESETS)
65 parser = NonStrippingEpilogOptionParser(epilog=(
66 '\nAvailable presets:\n'
67 ' Bandwidth (kbps) Packet\n'
68 'ID Name Receive Send Queue Delay loss \n'
69 '-- ---- --------- -------- ----- ------- ------\n'
70 '%s\n' % presets_string))
71 parser.add_option('-p',
72 '--preset',
73 type='int',
74 default=_DEFAULT_PRESET_ID,
75 help=('ConnectionConfig configuration, specified by ID. '
76 'Default: %default'))
77 parser.add_option(
78 '-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(
99 '-q',
100 '--queue',
101 type='int',
102 default=_DEFAULT_PRESET.queue_slots,
103 help=('Queue size as number of slots. Default: %default'))
104 parser.add_option(
105 '--port-range',
106 default='%s,%s' % _DEFAULT_PORT_RANGE,
107 help=('Range of ports for constrained network. Specify as '
108 'two comma separated integers. Default: %default'))
109 parser.add_option(
110 '--target-ip',
111 default=None,
112 help=('The interface IP address to apply the rules for. '
113 'Default: the external facing interface IP address.'))
114 parser.add_option('-v',
115 '--verbose',
116 action='store_true',
117 default=False,
118 help=('Turn on verbose output. Will print all \'ipfw\' '
119 'commands that are executed.'))
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000120
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100121 options = parser.parse_args()[0]
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000122
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100123 # Find preset by ID, if specified.
124 if options.preset and not _PRESETS_DICT.has_key(options.preset):
125 parser.error('Invalid preset: %s' % options.preset)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000126
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100127 # Simple validation of the IP address, if supplied.
128 if options.target_ip:
129 try:
130 socket.inet_aton(options.target_ip)
131 except socket.error:
132 parser.error('Invalid IP address specified: %s' %
133 options.target_ip)
134
135 # Convert port range into the desired tuple format.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000136 try:
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100137 if isinstance(options.port_range, str):
138 options.port_range = tuple(
139 int(port) for port in options.port_range.split(','))
140 if len(options.port_range) != 2:
141 parser.error(
142 'Invalid port range specified, please specify two '
143 'integers separated by a comma.')
144 except ValueError:
145 parser.error('Invalid port range specified.')
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000146
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100147 _InitLogging(options.verbose)
148 return options
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000149
150
kjellanderc88b5d52017-04-05 06:42:43 -0700151def _InitLogging(verbose):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100152 """Setup logging."""
153 log_level = _DEFAULT_LOG_LEVEL
154 if verbose:
155 log_level = logging.DEBUG
156 logging.basicConfig(level=log_level, format='%(message)s')
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000157
158
kjellanderc88b5d52017-04-05 06:42:43 -0700159def main():
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100160 options = _ParseArgs()
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000161
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100162 # Build a configuration object. Override any preset configuration settings if
163 # a value of a setting was also given as a flag.
164 connection_config = _PRESETS_DICT[options.preset]
165 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
166 connection_config.receive_bw_kbps = options.receive_bw
167 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
168 connection_config.send_bw_kbps = options.send_bw
169 if options.delay is not _DEFAULT_PRESET.delay_ms:
170 connection_config.delay_ms = options.delay
171 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
172 connection_config.packet_loss_percent = options.packet_loss
173 if options.queue is not _DEFAULT_PRESET.queue_slots:
174 connection_config.queue_slots = options.queue
175 emulator = network_emulator.NetworkEmulator(connection_config,
176 options.port_range)
177 try:
178 emulator.CheckPermissions()
179 except network_emulator.NetworkEmulatorError as e:
180 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
181 return -1
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000182
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100183 if not options.target_ip:
184 external_ip = _GetExternalIp()
185 else:
186 external_ip = options.target_ip
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000187
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100188 logging.info('Constraining traffic to/from IP: %s', external_ip)
189 try:
190 emulator.Emulate(external_ip)
191 logging.info(
192 'Started network emulation with the following configuration:\n'
193 ' Receive bandwidth: %s kbps (%s kB/s)\n'
194 ' Send bandwidth : %s kbps (%s kB/s)\n'
195 ' Delay : %s ms\n'
196 ' Packet loss : %s %%\n'
197 ' Queue slots : %s', connection_config.receive_bw_kbps,
198 connection_config.receive_bw_kbps / 8,
199 connection_config.send_bw_kbps, connection_config.send_bw_kbps / 8,
200 connection_config.delay_ms, connection_config.packet_loss_percent,
201 connection_config.queue_slots)
202 logging.info('Affected traffic: IP traffic on ports %s-%s',
203 options.port_range[0], options.port_range[1])
204 raw_input('Press Enter to abort Network Emulation...')
205 logging.info('Flushing all Dummynet rules...')
206 network_emulator.Cleanup()
207 logging.info('Completed Network Emulation.')
208 return 0
209 except network_emulator.NetworkEmulatorError as e:
210 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
211 return -2
212
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000213
214if __name__ == '__main__':
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100215 sys.exit(main())