| OLD | NEW |
| (Empty) |
| 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 | |
| 12 | |
| 13 import logging | |
| 14 import optparse | |
| 15 import socket | |
| 16 import sys | |
| 17 | |
| 18 import config | |
| 19 import network_emulator | |
| 20 | |
| 21 | |
| 22 _DEFAULT_LOG_LEVEL = logging.INFO | |
| 23 | |
| 24 # Default port range to apply network constraints on. | |
| 25 _DEFAULT_PORT_RANGE = (32768, 65535) | |
| 26 | |
| 27 # The numbers below are gathered from Google stats from the presets of the Apple | |
| 28 # developer tool called Network Link Conditioner. | |
| 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 _GetExternalIp(): | |
| 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 _ParseArgs(): | |
| 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)) | |
| 74 parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID, | |
| 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 | |
| 104 # Find preset by ID, if specified. | |
| 105 if options.preset and not _PRESETS_DICT.has_key(options.preset): | |
| 106 parser.error('Invalid preset: %s' % options.preset) | |
| 107 | |
| 108 # Simple validation of the IP address, if supplied. | |
| 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 _InitLogging(options.verbose) | |
| 127 return options | |
| 128 | |
| 129 | |
| 130 def _InitLogging(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(): | |
| 139 options = _ParseArgs() | |
| 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] | |
| 144 if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps: | |
| 145 connection_config.receive_bw_kbps = options.receive_bw | |
| 146 if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps: | |
| 147 connection_config.send_bw_kbps = options.send_bw | |
| 148 if options.delay is not _DEFAULT_PRESET.delay_ms: | |
| 149 connection_config.delay_ms = options.delay | |
| 150 if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent: | |
| 151 connection_config.packet_loss_percent = options.packet_loss | |
| 152 if options.queue is not _DEFAULT_PRESET.queue_slots: | |
| 153 connection_config.queue_slots = options.queue | |
| 154 emulator = network_emulator.NetworkEmulator(connection_config, | |
| 155 options.port_range) | |
| 156 try: | |
| 157 emulator.CheckPermissions() | |
| 158 except network_emulator.NetworkEmulatorError as e: | |
| 159 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) | |
| 160 return -1 | |
| 161 | |
| 162 if not options.target_ip: | |
| 163 external_ip = _GetExternalIp() | |
| 164 else: | |
| 165 external_ip = options.target_ip | |
| 166 | |
| 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...') | |
| 187 network_emulator.Cleanup() | |
| 188 logging.info('Completed Network Emulation.') | |
| 189 return 0 | |
| 190 except network_emulator.NetworkEmulatorError as e: | |
| 191 logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) | |
| 192 return -2 | |
| 193 | |
| 194 if __name__ == '__main__': | |
| 195 sys.exit(main()) | |
| OLD | NEW |