| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 1 | #!/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.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 12 |  | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 13 | import logging | 
|  | 14 | import optparse | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 15 | import socket | 
|  | 16 | import sys | 
|  | 17 |  | 
|  | 18 | import config | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 19 | import network_emulator | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 20 |  | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 21 |  | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 22 | _DEFAULT_LOG_LEVEL = logging.INFO | 
|  | 23 |  | 
|  | 24 | # Default port range to apply network constraints on. | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 25 | _DEFAULT_PORT_RANGE = (32768, 65535) | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 26 |  | 
| kjellander@webrtc.org | 3a6ff41 | 2013-09-03 13:01:31 | [diff] [blame] | 27 | # The numbers below are gathered from Google stats from the presets of the Apple | 
|  | 28 | # developer tool called Network Link Conditioner. | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 29 | _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 |  | 
|  | 51 | class 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 |  | 
|  | 58 | def _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 |  | 
|  | 65 | def _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.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 74 | parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID, | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 75 | 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.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 104 | # Find preset by ID, if specified. | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 105 | if options.preset and not _PRESETS_DICT.has_key(options.preset): | 
|  | 106 | parser.error('Invalid preset: %s' % options.preset) | 
|  | 107 |  | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 108 | # Simple validation of the IP address, if supplied. | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 109 | 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 |  | 
|  | 130 | def _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 |  | 
|  | 138 | def _main(): | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 139 | 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.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 144 | if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps: | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 145 | connection_config.receive_bw_kbps = options.receive_bw | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 146 | if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps: | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 147 | connection_config.send_bw_kbps = options.send_bw | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 148 | if options.delay is not _DEFAULT_PRESET.delay_ms: | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 149 | connection_config.delay_ms = options.delay | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 150 | if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent: | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 151 | connection_config.packet_loss_percent = options.packet_loss | 
| jansson@webrtc.org | 755e19a | 2013-03-08 10:50:14 | [diff] [blame] | 152 | if options.queue is not _DEFAULT_PRESET.queue_slots: | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 153 | connection_config.queue_slots = options.queue | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 154 | emulator = network_emulator.NetworkEmulator(connection_config, | 
|  | 155 | options.port_range) | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 156 | try: | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 157 | emulator.check_permissions() | 
|  | 158 | except network_emulator.NetworkEmulatorError as e: | 
| jansson@webrtc.org | 0ef22c2 | 2013-03-11 14:52:56 | [diff] [blame] | 159 | logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 160 | 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.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 167 | 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.org | 5d3ced5 | 2013-03-08 13:43:36 | [diff] [blame] | 187 | network_emulator.cleanup() | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 188 | logging.info('Completed Network Emulation.') | 
|  | 189 | return 0 | 
|  | 190 | except network_emulator.NetworkEmulatorError as e: | 
| jansson@webrtc.org | 0ef22c2 | 2013-03-11 14:52:56 | [diff] [blame] | 191 | logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) | 
| kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 | [diff] [blame] | 192 | return -2 | 
| kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 | [diff] [blame] | 193 |  | 
|  | 194 | if __name__ == '__main__': | 
|  | 195 | sys.exit(_main()) |