blob: 1c2cf3aaade51e89a75905746f4fe2bf5fb32a05 [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.
9
10"""Script for constraining traffic on the local machine."""
11
jansson@webrtc.org755e19a2013-03-08 10:50:14 +000012
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000013import logging
14import optparse
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000015import socket
16import sys
17
18import config
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000019import network_emulator
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000020
jansson@webrtc.org755e19a2013-03-08 10:50:14 +000021
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000022_DEFAULT_LOG_LEVEL = logging.INFO
23
24# Default port range to apply network constraints on.
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000025_DEFAULT_PORT_RANGE = (32768, 65535)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000026
27_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),
42 ]
43_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):
50 """Custom parser to let us show the epilog without weird line breaking."""
51
52 def format_epilog(self, formatter):
53 return self.epilog
54
55
56def _get_external_ip():
57 """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]
61
62
63def _parse_args():
64 """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))
jansson@webrtc.org755e19a2013-03-08 10:50:14 +000072 parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID,
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000073 help=('ConnectionConfig configuration, specified by ID. '
74 'Default: %default'))
75 parser.add_option('-r', '--receive-bw', type='int',
76 default=_DEFAULT_PRESET.receive_bw_kbps,
77 help=('Receive bandwidth in kilobit/s. Default: %default'))
78 parser.add_option('-s', '--send-bw', type='int',
79 default=_DEFAULT_PRESET.send_bw_kbps,
80 help=('Send bandwidth in kilobit/s. Default: %default'))
81 parser.add_option('-d', '--delay', type='int',
82 default=_DEFAULT_PRESET.delay_ms,
83 help=('Delay in ms. Default: %default'))
84 parser.add_option('-l', '--packet-loss', type='float',
85 default=_DEFAULT_PRESET.packet_loss_percent,
86 help=('Packet loss in %. Default: %default'))
87 parser.add_option('-q', '--queue', type='int',
88 default=_DEFAULT_PRESET.queue_slots,
89 help=('Queue size as number of slots. Default: %default'))
90 parser.add_option('--port-range', default='%s,%s' % _DEFAULT_PORT_RANGE,
91 help=('Range of ports for constrained network. Specify as '
92 'two comma separated integers. Default: %default'))
93 parser.add_option('--target-ip', default=None,
94 help=('The interface IP address to apply the rules for. '
95 'Default: the external facing interface IP address.'))
96 parser.add_option('-v', '--verbose', action='store_true', default=False,
97 help=('Turn on verbose output. Will print all \'ipfw\' '
98 'commands that are executed.'))
99
100 options = parser.parse_args()[0]
101
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000102 # Find preset by ID, if specified.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000103 if options.preset and not _PRESETS_DICT.has_key(options.preset):
104 parser.error('Invalid preset: %s' % options.preset)
105
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000106 # Simple validation of the IP address, if supplied.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000107 if options.target_ip:
108 try:
109 socket.inet_aton(options.target_ip)
110 except socket.error:
111 parser.error('Invalid IP address specified: %s' % options.target_ip)
112
113 # Convert port range into the desired tuple format.
114 try:
115 if isinstance(options.port_range, str):
116 options.port_range = tuple(int(port) for port in
117 options.port_range.split(','))
118 if len(options.port_range) != 2:
119 parser.error('Invalid port range specified, please specify two '
120 'integers separated by a comma.')
121 except ValueError:
122 parser.error('Invalid port range specified.')
123
124 _set_logger(options.verbose)
125 return options
126
127
128def _set_logger(verbose):
129 """Setup logging."""
130 log_level = _DEFAULT_LOG_LEVEL
131 if verbose:
132 log_level = logging.DEBUG
133 logging.basicConfig(level=log_level, format='%(message)s')
134
135
136def _main():
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000137 options = _parse_args()
138
139 # Build a configuration object. Override any preset configuration settings if
140 # a value of a setting was also given as a flag.
141 connection_config = _PRESETS_DICT[options.preset]
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000142 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000143 connection_config.receive_bw_kbps = options.receive_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000144 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000145 connection_config.send_bw_kbps = options.send_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000146 if options.delay is not _DEFAULT_PRESET.delay_ms:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000147 connection_config.delay_ms = options.delay
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000148 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000149 connection_config.packet_loss_percent = options.packet_loss
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000150 if options.queue is not _DEFAULT_PRESET.queue_slots:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000151 connection_config.queue_slots = options.queue
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000152 emulator = network_emulator.NetworkEmulator(connection_config,
153 options.port_range)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000154 try:
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000155 emulator.check_permissions()
156 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56 +0000157 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000158 return -1
159
160 if not options.target_ip:
161 external_ip = _get_external_ip()
162 else:
163 external_ip = options.target_ip
164
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000165 logging.info('Constraining traffic to/from IP: %s', external_ip)
166 try:
167 emulator.emulate(external_ip)
168 logging.info('Started network emulation with the following configuration:\n'
169 ' Receive bandwidth: %s kbps (%s kB/s)\n'
170 ' Send bandwidth : %s kbps (%s kB/s)\n'
171 ' Delay : %s ms\n'
172 ' Packet loss : %s %%\n'
173 ' Queue slots : %s',
174 connection_config.receive_bw_kbps,
175 connection_config.receive_bw_kbps/8,
176 connection_config.send_bw_kbps,
177 connection_config.send_bw_kbps/8,
178 connection_config.delay_ms,
179 connection_config.packet_loss_percent,
180 connection_config.queue_slots)
181 logging.info('Affected traffic: IP traffic on ports %s-%s',
182 options.port_range[0], options.port_range[1])
183 raw_input('Press Enter to abort Network Emulation...')
184 logging.info('Flushing all Dummynet rules...')
jansson@webrtc.org5d3ced52013-03-08 13:43:36 +0000185 network_emulator.cleanup()
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000186 logging.info('Completed Network Emulation.')
187 return 0
188 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56 +0000189 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000190 return -2
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000191
192if __name__ == '__main__':
193 sys.exit(_main())