blob: 82a6f48ecb73a0ee06b95d204604ac39cf25a0da [file] [log] [blame]
kjellander@webrtc.org595749f2012-05-31 20:19:051#!/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:1412
kjellander@webrtc.org595749f2012-05-31 20:19:0513import logging
14import optparse
kjellander@webrtc.org595749f2012-05-31 20:19:0515import socket
16import sys
17
18import config
kjellander@webrtc.org29c5a232012-06-01 08:42:1719import network_emulator
kjellander@webrtc.org595749f2012-05-31 20:19:0520
jansson@webrtc.org755e19a2013-03-08 10:50:1421
kjellander@webrtc.org595749f2012-05-31 20:19:0522_DEFAULT_LOG_LEVEL = logging.INFO
23
24# Default port range to apply network constraints on.
kjellander@webrtc.org29c5a232012-06-01 08:42:1725_DEFAULT_PORT_RANGE = (32768, 65535)
kjellander@webrtc.org595749f2012-05-31 20:19:0526
kjellander@webrtc.org3a6ff412013-09-03 13:01:3127# The numbers below are gathered from Google stats from the presets of the Apple
28# developer tool called Network Link Conditioner.
kjellander@webrtc.org595749f2012-05-31 20:19:0529_PRESETS = [
30 config.ConnectionConfig(1, 'Generic, Bad', 95, 95, 250, 2, 100),
31 config.ConnectionConfig(2, 'Generic, Average', 375, 375, 145, 0.1, 100),
32 config.ConnectionConfig(3, 'Generic, Good', 1000, 1000, 35, 0, 100),
33 config.ConnectionConfig(4, '3G, Average Case', 780, 330, 100, 0, 100),
34 config.ConnectionConfig(5, '3G, Good', 850, 420, 90, 0, 100),
35 config.ConnectionConfig(6, '3G, Lossy Network', 780, 330, 100, 1, 100),
36 config.ConnectionConfig(7, 'Cable Modem', 6000, 1000, 2, 0, 10),
37 config.ConnectionConfig(8, 'DSL', 2000, 256, 5, 0, 10),
38 config.ConnectionConfig(9, 'Edge, Average Case', 240, 200, 400, 0, 100),
39 config.ConnectionConfig(10, 'Edge, Good', 250, 200, 350, 0, 100),
40 config.ConnectionConfig(11, 'Edge, Lossy Network', 240, 200, 400, 1, 100),
41 config.ConnectionConfig(12, 'Wifi, Average Case', 40000, 33000, 1, 0, 100),
42 config.ConnectionConfig(13, 'Wifi, Good', 45000, 40000, 1, 0, 100),
43 config.ConnectionConfig(14, 'Wifi, Lossy', 40000, 33000, 1, 0, 100),
44 ]
45_PRESETS_DICT = dict((p.num, p) for p in _PRESETS)
46
47_DEFAULT_PRESET_ID = 2
48_DEFAULT_PRESET = _PRESETS_DICT[_DEFAULT_PRESET_ID]
49
50
51class NonStrippingEpilogOptionParser(optparse.OptionParser):
52 """Custom parser to let us show the epilog without weird line breaking."""
53
54 def format_epilog(self, formatter):
55 return self.epilog
56
57
58def _get_external_ip():
59 """Finds out the machine's external IP by connecting to google.com."""
60 external_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
61 external_socket.connect(('google.com', 80))
62 return external_socket.getsockname()[0]
63
64
65def _parse_args():
66 """Define and parse the command-line arguments."""
67 presets_string = '\n'.join(str(p) for p in _PRESETS)
68 parser = NonStrippingEpilogOptionParser(epilog=(
69 '\nAvailable presets:\n'
70 ' Bandwidth (kbps) Packet\n'
71 'ID Name Receive Send Queue Delay loss \n'
72 '-- ---- --------- -------- ----- ------- ------\n'
73 '%s\n' % presets_string))
jansson@webrtc.org755e19a2013-03-08 10:50:1474 parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID,
kjellander@webrtc.org595749f2012-05-31 20:19:0575 help=('ConnectionConfig configuration, specified by ID. '
76 'Default: %default'))
77 parser.add_option('-r', '--receive-bw', type='int',
78 default=_DEFAULT_PRESET.receive_bw_kbps,
79 help=('Receive bandwidth in kilobit/s. Default: %default'))
80 parser.add_option('-s', '--send-bw', type='int',
81 default=_DEFAULT_PRESET.send_bw_kbps,
82 help=('Send bandwidth in kilobit/s. Default: %default'))
83 parser.add_option('-d', '--delay', type='int',
84 default=_DEFAULT_PRESET.delay_ms,
85 help=('Delay in ms. Default: %default'))
86 parser.add_option('-l', '--packet-loss', type='float',
87 default=_DEFAULT_PRESET.packet_loss_percent,
88 help=('Packet loss in %. Default: %default'))
89 parser.add_option('-q', '--queue', type='int',
90 default=_DEFAULT_PRESET.queue_slots,
91 help=('Queue size as number of slots. Default: %default'))
92 parser.add_option('--port-range', default='%s,%s' % _DEFAULT_PORT_RANGE,
93 help=('Range of ports for constrained network. Specify as '
94 'two comma separated integers. Default: %default'))
95 parser.add_option('--target-ip', default=None,
96 help=('The interface IP address to apply the rules for. '
97 'Default: the external facing interface IP address.'))
98 parser.add_option('-v', '--verbose', action='store_true', default=False,
99 help=('Turn on verbose output. Will print all \'ipfw\' '
100 'commands that are executed.'))
101
102 options = parser.parse_args()[0]
103
jansson@webrtc.org755e19a2013-03-08 10:50:14104 # Find preset by ID, if specified.
kjellander@webrtc.org595749f2012-05-31 20:19:05105 if options.preset and not _PRESETS_DICT.has_key(options.preset):
106 parser.error('Invalid preset: %s' % options.preset)
107
jansson@webrtc.org755e19a2013-03-08 10:50:14108 # Simple validation of the IP address, if supplied.
kjellander@webrtc.org595749f2012-05-31 20:19:05109 if options.target_ip:
110 try:
111 socket.inet_aton(options.target_ip)
112 except socket.error:
113 parser.error('Invalid IP address specified: %s' % options.target_ip)
114
115 # Convert port range into the desired tuple format.
116 try:
117 if isinstance(options.port_range, str):
118 options.port_range = tuple(int(port) for port in
119 options.port_range.split(','))
120 if len(options.port_range) != 2:
121 parser.error('Invalid port range specified, please specify two '
122 'integers separated by a comma.')
123 except ValueError:
124 parser.error('Invalid port range specified.')
125
126 _set_logger(options.verbose)
127 return options
128
129
130def _set_logger(verbose):
131 """Setup logging."""
132 log_level = _DEFAULT_LOG_LEVEL
133 if verbose:
134 log_level = logging.DEBUG
135 logging.basicConfig(level=log_level, format='%(message)s')
136
137
138def _main():
kjellander@webrtc.org595749f2012-05-31 20:19:05139 options = _parse_args()
140
141 # Build a configuration object. Override any preset configuration settings if
142 # a value of a setting was also given as a flag.
143 connection_config = _PRESETS_DICT[options.preset]
jansson@webrtc.org755e19a2013-03-08 10:50:14144 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05145 connection_config.receive_bw_kbps = options.receive_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14146 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05147 connection_config.send_bw_kbps = options.send_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14148 if options.delay is not _DEFAULT_PRESET.delay_ms:
kjellander@webrtc.org595749f2012-05-31 20:19:05149 connection_config.delay_ms = options.delay
jansson@webrtc.org755e19a2013-03-08 10:50:14150 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
kjellander@webrtc.org595749f2012-05-31 20:19:05151 connection_config.packet_loss_percent = options.packet_loss
jansson@webrtc.org755e19a2013-03-08 10:50:14152 if options.queue is not _DEFAULT_PRESET.queue_slots:
kjellander@webrtc.org595749f2012-05-31 20:19:05153 connection_config.queue_slots = options.queue
kjellander@webrtc.org29c5a232012-06-01 08:42:17154 emulator = network_emulator.NetworkEmulator(connection_config,
155 options.port_range)
kjellander@webrtc.org595749f2012-05-31 20:19:05156 try:
kjellander@webrtc.org29c5a232012-06-01 08:42:17157 emulator.check_permissions()
158 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56159 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org595749f2012-05-31 20:19:05160 return -1
161
162 if not options.target_ip:
163 external_ip = _get_external_ip()
164 else:
165 external_ip = options.target_ip
166
kjellander@webrtc.org29c5a232012-06-01 08:42:17167 logging.info('Constraining traffic to/from IP: %s', external_ip)
168 try:
169 emulator.emulate(external_ip)
170 logging.info('Started network emulation with the following configuration:\n'
171 ' Receive bandwidth: %s kbps (%s kB/s)\n'
172 ' Send bandwidth : %s kbps (%s kB/s)\n'
173 ' Delay : %s ms\n'
174 ' Packet loss : %s %%\n'
175 ' Queue slots : %s',
176 connection_config.receive_bw_kbps,
177 connection_config.receive_bw_kbps/8,
178 connection_config.send_bw_kbps,
179 connection_config.send_bw_kbps/8,
180 connection_config.delay_ms,
181 connection_config.packet_loss_percent,
182 connection_config.queue_slots)
183 logging.info('Affected traffic: IP traffic on ports %s-%s',
184 options.port_range[0], options.port_range[1])
185 raw_input('Press Enter to abort Network Emulation...')
186 logging.info('Flushing all Dummynet rules...')
jansson@webrtc.org5d3ced52013-03-08 13:43:36187 network_emulator.cleanup()
kjellander@webrtc.org29c5a232012-06-01 08:42:17188 logging.info('Completed Network Emulation.')
189 return 0
190 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56191 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org29c5a232012-06-01 08:42:17192 return -2
kjellander@webrtc.org595749f2012-05-31 20:19:05193
194if __name__ == '__main__':
195 sys.exit(_main())