summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/tools_webrtc/network_emulator
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/tools_webrtc/network_emulator
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/tools_webrtc/network_emulator')
-rw-r--r--third_party/libwebrtc/tools_webrtc/network_emulator/config.py36
-rwxr-xr-xthird_party/libwebrtc/tools_webrtc/network_emulator/emulate.py209
-rw-r--r--third_party/libwebrtc/tools_webrtc/network_emulator/network_emulator.py195
3 files changed, 440 insertions, 0 deletions
diff --git a/third_party/libwebrtc/tools_webrtc/network_emulator/config.py b/third_party/libwebrtc/tools_webrtc/network_emulator/config.py
new file mode 100644
index 0000000000..9a18bdce45
--- /dev/null
+++ b/third_party/libwebrtc/tools_webrtc/network_emulator/config.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env vpython3
+
+# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+"""Configuration class for network emulation."""
+
+
+class ConnectionConfig:
+ """Configuration containing the characteristics of a network connection."""
+
+ def __init__(self, num, name, receive_bw_kbps, send_bw_kbps, delay_ms,
+ packet_loss_percent, queue_slots):
+ self.num = num
+ self.name = name
+ self.receive_bw_kbps = receive_bw_kbps
+ self.send_bw_kbps = send_bw_kbps
+ self.delay_ms = delay_ms
+ self.packet_loss_percent = packet_loss_percent
+ self.queue_slots = queue_slots
+
+ def __str__(self):
+ """String representing the configuration.
+
+ Returns:
+ A string formatted and padded like this example:
+ 12 Name 375 kbps 375 kbps 10 145 ms 0.1 %
+ """
+ left_aligned_name = self.name.ljust(24, ' ')
+ return '%2s %24s %5s kbps %5s kbps %4s %5s ms %3s %%' % (
+ self.num, left_aligned_name, self.receive_bw_kbps, self.send_bw_kbps,
+ self.queue_slots, self.delay_ms, self.packet_loss_percent)
diff --git a/third_party/libwebrtc/tools_webrtc/network_emulator/emulate.py b/third_party/libwebrtc/tools_webrtc/network_emulator/emulate.py
new file mode 100755
index 0000000000..a35ccd36c8
--- /dev/null
+++ b/third_party/libwebrtc/tools_webrtc/network_emulator/emulate.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env vpython3
+
+# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+"""Script for constraining traffic on the local machine."""
+
+import logging
+import optparse
+import socket
+import sys
+
+import config
+import network_emulator
+
+_DEFAULT_LOG_LEVEL = logging.INFO
+
+# Default port range to apply network constraints on.
+_DEFAULT_PORT_RANGE = (32768, 65535)
+
+# The numbers below are gathered from Google stats from the presets of the Apple
+# developer tool called Network Link Conditioner.
+_PRESETS = [
+ config.ConnectionConfig(1, 'Generic, Bad', 95, 95, 250, 2, 100),
+ config.ConnectionConfig(2, 'Generic, Average', 375, 375, 145, 0.1, 100),
+ config.ConnectionConfig(3, 'Generic, Good', 1000, 1000, 35, 0, 100),
+ config.ConnectionConfig(4, '3G, Average Case', 780, 330, 100, 0, 100),
+ config.ConnectionConfig(5, '3G, Good', 850, 420, 90, 0, 100),
+ config.ConnectionConfig(6, '3G, Lossy Network', 780, 330, 100, 1, 100),
+ config.ConnectionConfig(7, 'Cable Modem', 6000, 1000, 2, 0, 10),
+ config.ConnectionConfig(8, 'DSL', 2000, 256, 5, 0, 10),
+ config.ConnectionConfig(9, 'Edge, Average Case', 240, 200, 400, 0, 100),
+ config.ConnectionConfig(10, 'Edge, Good', 250, 200, 350, 0, 100),
+ config.ConnectionConfig(11, 'Edge, Lossy Network', 240, 200, 400, 1, 100),
+ config.ConnectionConfig(12, 'Wifi, Average Case', 40000, 33000, 1, 0, 100),
+ config.ConnectionConfig(13, 'Wifi, Good', 45000, 40000, 1, 0, 100),
+ config.ConnectionConfig(14, 'Wifi, Lossy', 40000, 33000, 1, 0, 100),
+]
+_PRESETS_DICT = dict((p.num, p) for p in _PRESETS)
+
+_DEFAULT_PRESET_ID = 2
+_DEFAULT_PRESET = _PRESETS_DICT[_DEFAULT_PRESET_ID]
+
+
+class NonStrippingEpilogOptionParser(optparse.OptionParser):
+ """Custom parser to let us show the epilog without weird line breaking."""
+
+ def format_epilog(self, formatter):
+ return self.epilog
+
+
+def _GetExternalIp():
+ """Finds out the machine's external IP by connecting to google.com."""
+ external_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ external_socket.connect(('google.com', 80))
+ return external_socket.getsockname()[0]
+
+
+def _ParseArgs():
+ """Define and parse the command-line arguments."""
+ presets_string = '\n'.join(str(p) for p in _PRESETS)
+ parser = NonStrippingEpilogOptionParser(epilog=(
+ '\nAvailable presets:\n'
+ ' Bandwidth (kbps) Packet\n'
+ 'ID Name Receive Send Queue Delay loss \n'
+ '-- ---- --------- -------- ----- ------- ------\n'
+ '%s\n' % presets_string))
+ parser.add_option('-p',
+ '--preset',
+ type='int',
+ default=_DEFAULT_PRESET_ID,
+ help=('ConnectionConfig configuration, specified by ID. '
+ 'Default: %default'))
+ parser.add_option('-r',
+ '--receive-bw',
+ type='int',
+ default=_DEFAULT_PRESET.receive_bw_kbps,
+ help=('Receive bandwidth in kilobit/s. Default: %default'))
+ parser.add_option('-s',
+ '--send-bw',
+ type='int',
+ default=_DEFAULT_PRESET.send_bw_kbps,
+ help=('Send bandwidth in kilobit/s. Default: %default'))
+ parser.add_option('-d',
+ '--delay',
+ type='int',
+ default=_DEFAULT_PRESET.delay_ms,
+ help=('Delay in ms. Default: %default'))
+ parser.add_option('-l',
+ '--packet-loss',
+ type='float',
+ default=_DEFAULT_PRESET.packet_loss_percent,
+ help=('Packet loss in %. Default: %default'))
+ parser.add_option('-q',
+ '--queue',
+ type='int',
+ default=_DEFAULT_PRESET.queue_slots,
+ help=('Queue size as number of slots. Default: %default'))
+ parser.add_option('--port-range',
+ default='%s,%s' % _DEFAULT_PORT_RANGE,
+ help=('Range of ports for constrained network. Specify as '
+ 'two comma separated integers. Default: %default'))
+ parser.add_option('--target-ip',
+ default=None,
+ help=('The interface IP address to apply the rules for. '
+ 'Default: the external facing interface IP address.'))
+ parser.add_option('-v',
+ '--verbose',
+ action='store_true',
+ default=False,
+ help=('Turn on verbose output. Will print all \'ipfw\' '
+ 'commands that are executed.'))
+
+ options = parser.parse_args()[0]
+
+ # Find preset by ID, if specified.
+ if options.preset and options.preset not in _PRESETS_DICT:
+ parser.error('Invalid preset: %s' % options.preset)
+
+ # Simple validation of the IP address, if supplied.
+ if options.target_ip:
+ try:
+ socket.inet_aton(options.target_ip)
+ except socket.error:
+ parser.error('Invalid IP address specified: %s' % options.target_ip)
+
+ # Convert port range into the desired tuple format.
+ try:
+ if isinstance(options.port_range, str):
+ options.port_range = tuple(
+ int(port) for port in options.port_range.split(','))
+ if len(options.port_range) != 2:
+ parser.error('Invalid port range specified, please specify two '
+ 'integers separated by a comma.')
+ except ValueError:
+ parser.error('Invalid port range specified.')
+
+ _InitLogging(options.verbose)
+ return options
+
+
+def _InitLogging(verbose):
+ """Setup logging."""
+ log_level = _DEFAULT_LOG_LEVEL
+ if verbose:
+ log_level = logging.DEBUG
+ logging.basicConfig(level=log_level, format='%(message)s')
+
+
+def main():
+ options = _ParseArgs()
+
+ # Build a configuration object. Override any preset configuration settings if
+ # a value of a setting was also given as a flag.
+ connection_config = _PRESETS_DICT[options.preset]
+ if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
+ connection_config.receive_bw_kbps = options.receive_bw
+ if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
+ connection_config.send_bw_kbps = options.send_bw
+ if options.delay is not _DEFAULT_PRESET.delay_ms:
+ connection_config.delay_ms = options.delay
+ if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
+ connection_config.packet_loss_percent = options.packet_loss
+ if options.queue is not _DEFAULT_PRESET.queue_slots:
+ connection_config.queue_slots = options.queue
+ emulator = network_emulator.NetworkEmulator(connection_config,
+ options.port_range)
+ try:
+ emulator.CheckPermissions()
+ except network_emulator.NetworkEmulatorError as e:
+ logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
+ return -1
+
+ if not options.target_ip:
+ external_ip = _GetExternalIp()
+ else:
+ external_ip = options.target_ip
+
+ logging.info('Constraining traffic to/from IP: %s', external_ip)
+ try:
+ emulator.Emulate(external_ip)
+ logging.info(
+ 'Started network emulation with the following configuration:\n'
+ ' Receive bandwidth: %s kbps (%s kB/s)\n'
+ ' Send bandwidth : %s kbps (%s kB/s)\n'
+ ' Delay : %s ms\n'
+ ' Packet loss : %s %%\n'
+ ' Queue slots : %s', connection_config.receive_bw_kbps,
+ connection_config.receive_bw_kbps / 8, connection_config.send_bw_kbps,
+ connection_config.send_bw_kbps / 8, connection_config.delay_ms,
+ connection_config.packet_loss_percent, connection_config.queue_slots)
+ logging.info('Affected traffic: IP traffic on ports %s-%s',
+ options.port_range[0], options.port_range[1])
+ input('Press Enter to abort Network Emulation...')
+ logging.info('Flushing all Dummynet rules...')
+ network_emulator.Cleanup()
+ logging.info('Completed Network Emulation.')
+ return 0
+ except network_emulator.NetworkEmulatorError as e:
+ logging.error('Error: %s\n\nCause: %s', e.fail_msg, e.error)
+ return -2
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/third_party/libwebrtc/tools_webrtc/network_emulator/network_emulator.py b/third_party/libwebrtc/tools_webrtc/network_emulator/network_emulator.py
new file mode 100644
index 0000000000..a7776a5f92
--- /dev/null
+++ b/third_party/libwebrtc/tools_webrtc/network_emulator/network_emulator.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env vpython3
+
+# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
+#
+# Use of this source code is governed by a BSD-style license
+# that can be found in the LICENSE file in the root of the source
+# tree. An additional intellectual property rights grant can be found
+# in the file PATENTS. All contributing project authors may
+# be found in the AUTHORS file in the root of the source tree.
+"""Script for constraining traffic on the local machine."""
+
+import ctypes
+import logging
+import os
+import subprocess
+import sys
+
+
+class NetworkEmulatorError(BaseException):
+ """Exception raised for errors in the network emulator.
+
+ Attributes:
+ fail_msg: User defined error message.
+ cmd: Command for which the exception was raised.
+ returncode: Return code of running the command.
+ stdout: Output of running the command.
+ stderr: Error output of running the command.
+ """
+
+ def __init__(self,
+ fail_msg,
+ cmd=None,
+ returncode=None,
+ output=None,
+ error=None):
+ BaseException.__init__(self, fail_msg)
+ self.fail_msg = fail_msg
+ self.cmd = cmd
+ self.returncode = returncode
+ self.output = output
+ self.error = error
+
+
+class NetworkEmulator:
+ """A network emulator that can constrain the network using Dummynet."""
+
+ def __init__(self, connection_config, port_range):
+ """Constructor.
+
+ Args:
+ connection_config: A config.ConnectionConfig object containing the
+ characteristics for the connection to be emulation.
+ port_range: Tuple containing two integers defining the port range.
+ """
+ self._pipe_counter = 0
+ self._rule_counter = 0
+ self._port_range = port_range
+ self._connection_config = connection_config
+
+ def Emulate(self, target_ip):
+ """Starts a network emulation by setting up Dummynet rules.
+
+ Args:
+ target_ip: The IP address of the interface that shall be that have the
+ network constraints applied to it.
+ """
+ receive_pipe_id = self._CreateDummynetPipe(
+ self._connection_config.receive_bw_kbps,
+ self._connection_config.delay_ms,
+ self._connection_config.packet_loss_percent,
+ self._connection_config.queue_slots)
+ logging.debug('Created receive pipe: %s', receive_pipe_id)
+ send_pipe_id = self._CreateDummynetPipe(
+ self._connection_config.send_bw_kbps, self._connection_config.delay_ms,
+ self._connection_config.packet_loss_percent,
+ self._connection_config.queue_slots)
+ logging.debug('Created send pipe: %s', send_pipe_id)
+
+ # Adding the rules will start the emulation.
+ incoming_rule_id = self._CreateDummynetRule(receive_pipe_id, 'any',
+ target_ip, self._port_range)
+ logging.debug('Created incoming rule: %s', incoming_rule_id)
+ outgoing_rule_id = self._CreateDummynetRule(send_pipe_id, target_ip, 'any',
+ self._port_range)
+ logging.debug('Created outgoing rule: %s', outgoing_rule_id)
+
+ @staticmethod
+ def CheckPermissions():
+ """Checks if permissions are available to run Dummynet commands.
+
+ Raises:
+ NetworkEmulatorError: If permissions to run Dummynet commands are not
+ available.
+ """
+ try:
+ if os.getuid() != 0:
+ raise NetworkEmulatorError('You must run this script with sudo.')
+ except AttributeError as permission_error:
+
+ # AttributeError will be raised on Windows.
+ if ctypes.windll.shell32.IsUserAnAdmin() == 0:
+ raise NetworkEmulatorError('You must run this script with administrator'
+ ' privileges.') from permission_error
+
+ def _CreateDummynetRule(self, pipe_id, from_address, to_address, port_range):
+ """Creates a network emulation rule and returns its ID.
+
+ Args:
+ pipe_id: integer ID of the pipe.
+ from_address: The IP address to match source address. May be an IP or
+ 'any'.
+ to_address: The IP address to match destination address. May be an IP or
+ 'any'.
+ port_range: The range of ports the rule shall be applied on. Must be
+ specified as a tuple of with two integers.
+ Returns:
+ The ID of the rule, starting at 100. The rule ID increments with 100 for
+ each rule being added.
+ """
+ self._rule_counter += 100
+ add_part = [
+ 'add', self._rule_counter, 'pipe', pipe_id, 'ip', 'from', from_address,
+ 'to', to_address
+ ]
+ _RunIpfwCommand(add_part + ['src-port', '%s-%s' % port_range],
+ 'Failed to add Dummynet src-port rule.')
+ _RunIpfwCommand(add_part + ['dst-port', '%s-%s' % port_range],
+ 'Failed to add Dummynet dst-port rule.')
+ return self._rule_counter
+
+ def _CreateDummynetPipe(self, bandwidth_kbps, delay_ms, packet_loss_percent,
+ queue_slots):
+ """Creates a Dummynet pipe and return its ID.
+
+ Args:
+ bandwidth_kbps: Bandwidth.
+ delay_ms: Delay for a one-way trip of a packet.
+ packet_loss_percent: Float value of packet loss, in percent.
+ queue_slots: Size of the queue.
+ Returns:
+ The ID of the pipe, starting at 1.
+ """
+ self._pipe_counter += 1
+ cmd = [
+ 'pipe', self._pipe_counter, 'config', 'bw',
+ str(bandwidth_kbps / 8) + 'KByte/s', 'delay',
+ '%sms' % delay_ms, 'plr', (packet_loss_percent / 100.0), 'queue',
+ queue_slots
+ ]
+ error_message = 'Failed to create Dummynet pipe. '
+ if sys.platform.startswith('linux'):
+ error_message += ('Make sure you have loaded the ipfw_mod.ko module to '
+ 'your kernel (sudo insmod /path/to/ipfw_mod.ko).')
+ _RunIpfwCommand(cmd, error_message)
+ return self._pipe_counter
+
+
+def Cleanup():
+ """Stops the network emulation by flushing all Dummynet rules.
+
+ Notice that this will flush any rules that may have been created previously
+ before starting the emulation.
+ """
+ _RunIpfwCommand(['-f', 'flush'], 'Failed to flush Dummynet rules!')
+ _RunIpfwCommand(['-f', 'pipe', 'flush'], 'Failed to flush Dummynet pipes!')
+
+
+def _RunIpfwCommand(command, fail_msg=None):
+ """Executes a command and prefixes the appropriate command for
+ Windows or Linux/UNIX.
+
+ Args:
+ command: Command list to execute.
+ fail_msg: Message describing the error in case the command fails.
+
+ Raises:
+ NetworkEmulatorError: If command fails a message is set by the fail_msg
+ parameter.
+ """
+ if sys.platform == 'win32':
+ ipfw_command = ['ipfw.exe']
+ else:
+ ipfw_command = ['sudo', '-n', 'ipfw']
+
+ cmd_list = ipfw_command[:] + [str(x) for x in command]
+ cmd_string = ' '.join(cmd_list)
+ logging.debug('Running command: %s', cmd_string)
+ process = subprocess.Popen(cmd_list,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ output, error = process.communicate()
+ if process.returncode != 0:
+ raise NetworkEmulatorError(fail_msg, cmd_string, process.returncode, output,
+ error)
+ return output.strip()