blob: e25670574fd58ac822da9783fdaddbeac1264ec6 [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
12import logging
13import optparse
14import os
15import 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
21_DEFAULT_LOG_LEVEL = logging.INFO
22
23# Default port range to apply network constraints on.
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +000024_DEFAULT_PORT_RANGE = (32768, 65535)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000025
26_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),
41 ]
42_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):
49 """Custom parser to let us show the epilog without weird line breaking."""
50
51 def format_epilog(self, formatter):
52 return self.epilog
53
54
55def _get_external_ip():
56 """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]
60
61
62def _parse_args():
63 """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', '--preset', type='int', default=2,
72 help=('ConnectionConfig configuration, specified by ID. '
73 'Default: %default'))
74 parser.add_option('-r', '--receive-bw', type='int',
75 default=_DEFAULT_PRESET.receive_bw_kbps,
76 help=('Receive bandwidth in kilobit/s. Default: %default'))
77 parser.add_option('-s', '--send-bw', type='int',
78 default=_DEFAULT_PRESET.send_bw_kbps,
79 help=('Send bandwidth in kilobit/s. Default: %default'))
80 parser.add_option('-d', '--delay', type='int',
81 default=_DEFAULT_PRESET.delay_ms,
82 help=('Delay in ms. Default: %default'))
83 parser.add_option('-l', '--packet-loss', type='float',
84 default=_DEFAULT_PRESET.packet_loss_percent,
85 help=('Packet loss in %. Default: %default'))
86 parser.add_option('-q', '--queue', type='int',
87 default=_DEFAULT_PRESET.queue_slots,
88 help=('Queue size as number of slots. Default: %default'))
89 parser.add_option('--port-range', default='%s,%s' % _DEFAULT_PORT_RANGE,
90 help=('Range of ports for constrained network. Specify as '
91 'two comma separated integers. Default: %default'))
92 parser.add_option('--target-ip', default=None,
93 help=('The interface IP address to apply the rules for. '
94 'Default: the external facing interface IP address.'))
95 parser.add_option('-v', '--verbose', action='store_true', default=False,
96 help=('Turn on verbose output. Will print all \'ipfw\' '
97 'commands that are executed.'))
98
99 options = parser.parse_args()[0]
100
101 # Find preset by ID, if specified:
102 if options.preset and not _PRESETS_DICT.has_key(options.preset):
103 parser.error('Invalid preset: %s' % options.preset)
104
105 # Simple validation of the IP address, if supplied:
106 if options.target_ip:
107 try:
108 socket.inet_aton(options.target_ip)
109 except socket.error:
110 parser.error('Invalid IP address specified: %s' % options.target_ip)
111
112 # Convert port range into the desired tuple format.
113 try:
114 if isinstance(options.port_range, str):
115 options.port_range = tuple(int(port) for port in
116 options.port_range.split(','))
117 if len(options.port_range) != 2:
118 parser.error('Invalid port range specified, please specify two '
119 'integers separated by a comma.')
120 except ValueError:
121 parser.error('Invalid port range specified.')
122
123 _set_logger(options.verbose)
124 return options
125
126
127def _set_logger(verbose):
128 """Setup logging."""
129 log_level = _DEFAULT_LOG_LEVEL
130 if verbose:
131 log_level = logging.DEBUG
132 logging.basicConfig(level=log_level, format='%(message)s')
133
134
135def _main():
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000136 """Checks arguments, permissions and runs a network emulation."""
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000137 if os.name != 'posix':
138 print >> sys.stderr, 'This script is only supported on Linux and Mac.'
139 return 1
140
141 options = _parse_args()
142
143 # Build a configuration object. Override any preset configuration settings if
144 # a value of a setting was also given as a flag.
145 connection_config = _PRESETS_DICT[options.preset]
146 if options.receive_bw:
147 connection_config.receive_bw_kbps = options.receive_bw
148 if options.send_bw:
149 connection_config.send_bw_kbps = options.send_bw
150 if options.delay:
151 connection_config.delay_ms = options.delay
152 if options.packet_loss:
153 connection_config.packet_loss_percent = options.packet_loss
154 if options.queue:
155 connection_config.queue_slots = options.queue
156
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000157 emulator = network_emulator.NetworkEmulator(connection_config,
158 options.port_range)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000159 try:
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000160 emulator.check_permissions()
161 except network_emulator.NetworkEmulatorError as e:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000162 logging.error('Error: %s\n\nCause: %s', e.msg, e.error)
163 return -1
164
165 if not options.target_ip:
166 external_ip = _get_external_ip()
167 else:
168 external_ip = options.target_ip
169
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000170 logging.info('Constraining traffic to/from IP: %s', external_ip)
171 try:
172 emulator.emulate(external_ip)
173 logging.info('Started network emulation with the following configuration:\n'
174 ' Receive bandwidth: %s kbps (%s kB/s)\n'
175 ' Send bandwidth : %s kbps (%s kB/s)\n'
176 ' Delay : %s ms\n'
177 ' Packet loss : %s %%\n'
178 ' Queue slots : %s',
179 connection_config.receive_bw_kbps,
180 connection_config.receive_bw_kbps/8,
181 connection_config.send_bw_kbps,
182 connection_config.send_bw_kbps/8,
183 connection_config.delay_ms,
184 connection_config.packet_loss_percent,
185 connection_config.queue_slots)
186 logging.info('Affected traffic: IP traffic on ports %s-%s',
187 options.port_range[0], options.port_range[1])
188 raw_input('Press Enter to abort Network Emulation...')
189 logging.info('Flushing all Dummynet rules...')
190 emulator.cleanup()
191 logging.info('Completed Network Emulation.')
192 return 0
193 except network_emulator.NetworkEmulatorError as e:
194 logging.error('Error: %s\n\nCause: %s', e.msg, e.error)
195 return -2
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000196
197if __name__ == '__main__':
198 sys.exit(_main())