blob: 82a6f48ecb73a0ee06b95d204604ac39cf25a0da [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
kjellander@webrtc.org3a6ff412013-09-03 13:01:31 +000027# 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:05 +000029_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
kjellander94f4d9e2017-03-09 06:09:33 -080058def _get_external_ip():
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000059 """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
kjellander94f4d9e2017-03-09 06:09:33 -080065def _parse_args():
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000066 """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:14 +000074 parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID,
kjellander@webrtc.org595749f2012-05-31 20:19:05 +000075 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:14 +0000104 # Find preset by ID, if specified.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000105 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:14 +0000108 # Simple validation of the IP address, if supplied.
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000109 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
kjellander94f4d9e2017-03-09 06:09:33 -0800126 _set_logger(options.verbose)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000127 return options
128
129
kjellander94f4d9e2017-03-09 06:09:33 -0800130def _set_logger(verbose):
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000131 """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
kjellander94f4d9e2017-03-09 06:09:33 -0800138def _main():
139 options = _parse_args()
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000140
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:14 +0000144 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000145 connection_config.receive_bw_kbps = options.receive_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000146 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000147 connection_config.send_bw_kbps = options.send_bw
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000148 if options.delay is not _DEFAULT_PRESET.delay_ms:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000149 connection_config.delay_ms = options.delay
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000150 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000151 connection_config.packet_loss_percent = options.packet_loss
jansson@webrtc.org755e19a2013-03-08 10:50:14 +0000152 if options.queue is not _DEFAULT_PRESET.queue_slots:
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000153 connection_config.queue_slots = options.queue
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000154 emulator = network_emulator.NetworkEmulator(connection_config,
155 options.port_range)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000156 try:
kjellander94f4d9e2017-03-09 06:09:33 -0800157 emulator.check_permissions()
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000158 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56 +0000159 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000160 return -1
161
162 if not options.target_ip:
kjellander94f4d9e2017-03-09 06:09:33 -0800163 external_ip = _get_external_ip()
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000164 else:
165 external_ip = options.target_ip
166
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000167 logging.info('Constraining traffic to/from IP: %s', external_ip)
168 try:
kjellander94f4d9e2017-03-09 06:09:33 -0800169 emulator.emulate(external_ip)
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000170 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...')
kjellander94f4d9e2017-03-09 06:09:33 -0800187 network_emulator.cleanup()
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000188 logging.info('Completed Network Emulation.')
189 return 0
190 except network_emulator.NetworkEmulatorError as e:
jansson@webrtc.org0ef22c22013-03-11 14:52:56 +0000191 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
kjellander@webrtc.org29c5a232012-06-01 08:42:17 +0000192 return -2
kjellander@webrtc.org595749f2012-05-31 20:19:05 +0000193
194if __name__ == '__main__':
kjellander94f4d9e2017-03-09 06:09:33 -0800195 sys.exit(_main())