Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 1 | #!/usr/bin/env vpython3 |
| 2 | |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 3 | # Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| 4 | # |
| 5 | # Use of this source code is governed by a BSD-style license |
| 6 | # that can be found in the LICENSE file in the root of the source |
| 7 | # tree. An additional intellectual property rights grant can be found |
| 8 | # in the file PATENTS. All contributing project authors may |
| 9 | # be found in the AUTHORS file in the root of the source tree. |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 10 | """Script for constraining traffic on the local machine.""" |
| 11 | |
| 12 | import logging |
| 13 | import optparse |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 14 | import socket |
| 15 | import sys |
| 16 | |
| 17 | import config |
kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 +0000 | [diff] [blame] | 18 | import network_emulator |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 19 | |
| 20 | _DEFAULT_LOG_LEVEL = logging.INFO |
| 21 | |
| 22 | # Default port range to apply network constraints on. |
kjellander@webrtc.org | 29c5a23 | 2012-06-01 08:42:17 +0000 | [diff] [blame] | 23 | _DEFAULT_PORT_RANGE = (32768, 65535) |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 24 | |
kjellander@webrtc.org | 3a6ff41 | 2013-09-03 13:01:31 +0000 | [diff] [blame] | 25 | # The numbers below are gathered from Google stats from the presets of the Apple |
| 26 | # developer tool called Network Link Conditioner. |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 27 | _PRESETS = [ |
| 28 | config.ConnectionConfig(1, 'Generic, Bad', 95, 95, 250, 2, 100), |
| 29 | config.ConnectionConfig(2, 'Generic, Average', 375, 375, 145, 0.1, 100), |
| 30 | config.ConnectionConfig(3, 'Generic, Good', 1000, 1000, 35, 0, 100), |
| 31 | config.ConnectionConfig(4, '3G, Average Case', 780, 330, 100, 0, 100), |
| 32 | config.ConnectionConfig(5, '3G, Good', 850, 420, 90, 0, 100), |
| 33 | config.ConnectionConfig(6, '3G, Lossy Network', 780, 330, 100, 1, 100), |
| 34 | config.ConnectionConfig(7, 'Cable Modem', 6000, 1000, 2, 0, 10), |
| 35 | config.ConnectionConfig(8, 'DSL', 2000, 256, 5, 0, 10), |
| 36 | config.ConnectionConfig(9, 'Edge, Average Case', 240, 200, 400, 0, 100), |
| 37 | config.ConnectionConfig(10, 'Edge, Good', 250, 200, 350, 0, 100), |
| 38 | config.ConnectionConfig(11, 'Edge, Lossy Network', 240, 200, 400, 1, 100), |
| 39 | config.ConnectionConfig(12, 'Wifi, Average Case', 40000, 33000, 1, 0, 100), |
| 40 | config.ConnectionConfig(13, 'Wifi, Good', 45000, 40000, 1, 0, 100), |
| 41 | config.ConnectionConfig(14, 'Wifi, Lossy', 40000, 33000, 1, 0, 100), |
Mirko Bonadei | 8cc6695 | 2020-10-30 10:13:45 +0100 | [diff] [blame] | 42 | ] |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 43 | _PRESETS_DICT = dict((p.num, p) for p in _PRESETS) |
| 44 | |
| 45 | _DEFAULT_PRESET_ID = 2 |
| 46 | _DEFAULT_PRESET = _PRESETS_DICT[_DEFAULT_PRESET_ID] |
| 47 | |
| 48 | |
| 49 | class NonStrippingEpilogOptionParser(optparse.OptionParser): |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 50 | """Custom parser to let us show the epilog without weird line breaking.""" |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 51 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 52 | def format_epilog(self, formatter): |
| 53 | return self.epilog |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 54 | |
| 55 | |
kjellander | c88b5d5 | 2017-04-05 06:42:43 -0700 | [diff] [blame] | 56 | def _GetExternalIp(): |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 57 | """Finds out the machine's external IP by connecting to google.com.""" |
| 58 | external_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| 59 | external_socket.connect(('google.com', 80)) |
| 60 | return external_socket.getsockname()[0] |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 61 | |
| 62 | |
kjellander | c88b5d5 | 2017-04-05 06:42:43 -0700 | [diff] [blame] | 63 | def _ParseArgs(): |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 64 | """Define and parse the command-line arguments.""" |
| 65 | presets_string = '\n'.join(str(p) for p in _PRESETS) |
| 66 | parser = NonStrippingEpilogOptionParser(epilog=( |
| 67 | '\nAvailable presets:\n' |
| 68 | ' Bandwidth (kbps) Packet\n' |
| 69 | 'ID Name Receive Send Queue Delay loss \n' |
| 70 | '-- ---- --------- -------- ----- ------- ------\n' |
| 71 | '%s\n' % presets_string)) |
| 72 | parser.add_option('-p', |
| 73 | '--preset', |
| 74 | type='int', |
| 75 | default=_DEFAULT_PRESET_ID, |
| 76 | help=('ConnectionConfig configuration, specified by ID. ' |
| 77 | 'Default: %default')) |
| 78 | parser.add_option('-r', |
| 79 | '--receive-bw', |
| 80 | type='int', |
| 81 | default=_DEFAULT_PRESET.receive_bw_kbps, |
| 82 | help=('Receive bandwidth in kilobit/s. Default: %default')) |
| 83 | parser.add_option('-s', |
| 84 | '--send-bw', |
| 85 | type='int', |
| 86 | default=_DEFAULT_PRESET.send_bw_kbps, |
| 87 | help=('Send bandwidth in kilobit/s. Default: %default')) |
| 88 | parser.add_option('-d', |
| 89 | '--delay', |
| 90 | type='int', |
| 91 | default=_DEFAULT_PRESET.delay_ms, |
| 92 | help=('Delay in ms. Default: %default')) |
| 93 | parser.add_option('-l', |
| 94 | '--packet-loss', |
| 95 | type='float', |
| 96 | default=_DEFAULT_PRESET.packet_loss_percent, |
| 97 | help=('Packet loss in %. Default: %default')) |
| 98 | parser.add_option('-q', |
| 99 | '--queue', |
| 100 | type='int', |
| 101 | default=_DEFAULT_PRESET.queue_slots, |
| 102 | help=('Queue size as number of slots. Default: %default')) |
| 103 | parser.add_option('--port-range', |
| 104 | default='%s,%s' % _DEFAULT_PORT_RANGE, |
| 105 | help=('Range of ports for constrained network. Specify as ' |
| 106 | 'two comma separated integers. Default: %default')) |
| 107 | parser.add_option('--target-ip', |
| 108 | default=None, |
| 109 | help=('The interface IP address to apply the rules for. ' |
| 110 | 'Default: the external facing interface IP address.')) |
| 111 | parser.add_option('-v', |
| 112 | '--verbose', |
| 113 | action='store_true', |
| 114 | default=False, |
| 115 | help=('Turn on verbose output. Will print all \'ipfw\' ' |
| 116 | 'commands that are executed.')) |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 117 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 118 | options = parser.parse_args()[0] |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 119 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 120 | # Find preset by ID, if specified. |
| 121 | if options.preset and options.preset not in _PRESETS_DICT: |
| 122 | parser.error('Invalid preset: %s' % options.preset) |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 123 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 124 | # Simple validation of the IP address, if supplied. |
| 125 | if options.target_ip: |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 126 | try: |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 127 | socket.inet_aton(options.target_ip) |
| 128 | except socket.error: |
| 129 | parser.error('Invalid IP address specified: %s' % options.target_ip) |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 130 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 131 | # Convert port range into the desired tuple format. |
| 132 | try: |
| 133 | if isinstance(options.port_range, str): |
| 134 | options.port_range = tuple( |
| 135 | int(port) for port in options.port_range.split(',')) |
| 136 | if len(options.port_range) != 2: |
| 137 | parser.error('Invalid port range specified, please specify two ' |
| 138 | 'integers separated by a comma.') |
| 139 | except ValueError: |
| 140 | parser.error('Invalid port range specified.') |
| 141 | |
| 142 | _InitLogging(options.verbose) |
| 143 | return options |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 144 | |
| 145 | |
kjellander | c88b5d5 | 2017-04-05 06:42:43 -0700 | [diff] [blame] | 146 | def _InitLogging(verbose): |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 147 | """Setup logging.""" |
| 148 | log_level = _DEFAULT_LOG_LEVEL |
| 149 | if verbose: |
| 150 | log_level = logging.DEBUG |
| 151 | logging.basicConfig(level=log_level, format='%(message)s') |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 152 | |
| 153 | |
kjellander | c88b5d5 | 2017-04-05 06:42:43 -0700 | [diff] [blame] | 154 | def main(): |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 155 | options = _ParseArgs() |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 156 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 157 | # Build a configuration object. Override any preset configuration settings if |
| 158 | # a value of a setting was also given as a flag. |
| 159 | connection_config = _PRESETS_DICT[options.preset] |
| 160 | if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps: |
| 161 | connection_config.receive_bw_kbps = options.receive_bw |
| 162 | if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps: |
| 163 | connection_config.send_bw_kbps = options.send_bw |
| 164 | if options.delay is not _DEFAULT_PRESET.delay_ms: |
| 165 | connection_config.delay_ms = options.delay |
| 166 | if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent: |
| 167 | connection_config.packet_loss_percent = options.packet_loss |
| 168 | if options.queue is not _DEFAULT_PRESET.queue_slots: |
| 169 | connection_config.queue_slots = options.queue |
| 170 | emulator = network_emulator.NetworkEmulator(connection_config, |
| 171 | options.port_range) |
| 172 | try: |
| 173 | emulator.CheckPermissions() |
| 174 | except network_emulator.NetworkEmulatorError as e: |
| 175 | logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) |
| 176 | return -1 |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 177 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 178 | if not options.target_ip: |
| 179 | external_ip = _GetExternalIp() |
| 180 | else: |
| 181 | external_ip = options.target_ip |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 182 | |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 183 | logging.info('Constraining traffic to/from IP: %s', external_ip) |
| 184 | try: |
| 185 | emulator.Emulate(external_ip) |
| 186 | logging.info( |
| 187 | 'Started network emulation with the following configuration:\n' |
| 188 | ' Receive bandwidth: %s kbps (%s kB/s)\n' |
| 189 | ' Send bandwidth : %s kbps (%s kB/s)\n' |
| 190 | ' Delay : %s ms\n' |
| 191 | ' Packet loss : %s %%\n' |
| 192 | ' Queue slots : %s', connection_config.receive_bw_kbps, |
| 193 | connection_config.receive_bw_kbps / 8, connection_config.send_bw_kbps, |
| 194 | connection_config.send_bw_kbps / 8, connection_config.delay_ms, |
| 195 | connection_config.packet_loss_percent, connection_config.queue_slots) |
| 196 | logging.info('Affected traffic: IP traffic on ports %s-%s', |
| 197 | options.port_range[0], options.port_range[1]) |
| 198 | input('Press Enter to abort Network Emulation...') |
| 199 | logging.info('Flushing all Dummynet rules...') |
| 200 | network_emulator.Cleanup() |
| 201 | logging.info('Completed Network Emulation.') |
| 202 | return 0 |
| 203 | except network_emulator.NetworkEmulatorError as e: |
| 204 | logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error) |
| 205 | return -2 |
Mirko Bonadei | 8cc6695 | 2020-10-30 10:13:45 +0100 | [diff] [blame] | 206 | |
kjellander@webrtc.org | 595749f | 2012-05-31 20:19:05 +0000 | [diff] [blame] | 207 | |
| 208 | if __name__ == '__main__': |
Christoffer Jansson | 4e8a773 | 2022-02-08 09:01:12 +0100 | [diff] [blame] | 209 | sys.exit(main()) |