blob: 9bdf7bd95b8857ee2604a98e425765a9098fca22 [file] [log] [blame]
kjellander@webrtc.org595749f2012-05-31 20:19:051#!/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:1412import ctypes
kjellander@webrtc.org595749f2012-05-31 20:19:0513import logging
14import os
15import subprocess
kjellander@webrtc.org29c5a232012-06-01 08:42:1716import sys
kjellander@webrtc.org595749f2012-05-31 20:19:0517
18
kjellander@webrtc.org29c5a232012-06-01 08:42:1719class NetworkEmulatorError(BaseException):
20 """Exception raised for errors in the network emulator.
kjellander@webrtc.org595749f2012-05-31 20:19:0521
22 Attributes:
jansson@webrtc.org5d3ced52013-03-08 13:43:3623 fail_msg: User defined error message.
kjellander@webrtc.org595749f2012-05-31 20:19:0524 cmd: Command for which the exception was raised.
25 returncode: Return code of running the command.
26 stdout: Output of running the command.
27 stderr: Error output of running the command.
28 """
29
jansson@webrtc.org5d3ced52013-03-08 13:43:3630 def __init__(self, fail_msg, cmd=None, returncode=None, output=None,
kjellander@webrtc.org595749f2012-05-31 20:19:0531 error=None):
jansson@webrtc.org5d3ced52013-03-08 13:43:3632 BaseException.__init__(self, fail_msg)
33 self.fail_msg = fail_msg
kjellander@webrtc.org595749f2012-05-31 20:19:0534 self.cmd = cmd
35 self.returncode = returncode
36 self.output = output
37 self.error = error
38
39
kjellander@webrtc.org29c5a232012-06-01 08:42:1740class NetworkEmulator(object):
41 """A network emulator that can constrain the network using Dummynet."""
kjellander@webrtc.org595749f2012-05-31 20:19:0542
43 def __init__(self, connection_config, port_range):
44 """Constructor.
45
46 Args:
47 connection_config: A config.ConnectionConfig object containing the
kjellander@webrtc.org29c5a232012-06-01 08:42:1748 characteristics for the connection to be emulation.
kjellander@webrtc.org595749f2012-05-31 20:19:0549 port_range: Tuple containing two integers defining the port range.
50 """
51 self._pipe_counter = 0
52 self._rule_counter = 0
53 self._port_range = port_range
54 self._connection_config = connection_config
55
kjellander@webrtc.org29c5a232012-06-01 08:42:1756 def emulate(self, target_ip):
57 """Starts a network emulation by setting up Dummynet rules.
kjellander@webrtc.org595749f2012-05-31 20:19:0558
59 Args:
60 target_ip: The IP address of the interface that shall be that have the
61 network constraints applied to it.
62 """
63 receive_pipe_id = self._create_dummynet_pipe(
64 self._connection_config.receive_bw_kbps,
65 self._connection_config.delay_ms,
66 self._connection_config.packet_loss_percent,
67 self._connection_config.queue_slots)
68 logging.debug('Created receive pipe: %s', receive_pipe_id)
69 send_pipe_id = self._create_dummynet_pipe(
70 self._connection_config.send_bw_kbps,
71 self._connection_config.delay_ms,
72 self._connection_config.packet_loss_percent,
73 self._connection_config.queue_slots)
74 logging.debug('Created send pipe: %s', send_pipe_id)
75
kjellander@webrtc.org29c5a232012-06-01 08:42:1776 # Adding the rules will start the emulation.
kjellander@webrtc.org595749f2012-05-31 20:19:0577 incoming_rule_id = self._create_dummynet_rule(receive_pipe_id, 'any',
78 target_ip, self._port_range)
79 logging.debug('Created incoming rule: %s', incoming_rule_id)
80 outgoing_rule_id = self._create_dummynet_rule(send_pipe_id, target_ip,
81 'any', self._port_range)
82 logging.debug('Created outgoing rule: %s', outgoing_rule_id)
83
phoglund@webrtc.org5d3713932013-03-07 09:59:4384 @staticmethod
85 def check_permissions():
kjellander@webrtc.org595749f2012-05-31 20:19:0586 """Checks if permissions are available to run Dummynet commands.
87
88 Raises:
kjellander@webrtc.org29c5a232012-06-01 08:42:1789 NetworkEmulatorError: If permissions to run Dummynet commands are not
kjellander@webrtc.org595749f2012-05-31 20:19:0590 available.
91 """
jansson@webrtc.org755e19a2013-03-08 10:50:1492 try:
93 if os.getuid() != 0:
94 raise NetworkEmulatorError('You must run this script with sudo.')
95 except AttributeError:
kjellander@webrtc.org595749f2012-05-31 20:19:0596
jansson@webrtc.org755e19a2013-03-08 10:50:1497 # AttributeError will be raised on Windows.
98 if ctypes.windll.shell32.IsUserAnAdmin() == 0:
99 raise NetworkEmulatorError('You must run this script with administrator'
jansson@webrtc.org0ef22c22013-03-11 14:52:56100 ' privileges.')
jansson@webrtc.org755e19a2013-03-08 10:50:14101
kjellander@webrtc.org595749f2012-05-31 20:19:05102 def _create_dummynet_rule(self, pipe_id, from_address, to_address,
103 port_range):
kjellander@webrtc.org29c5a232012-06-01 08:42:17104 """Creates a network emulation rule and returns its ID.
kjellander@webrtc.org595749f2012-05-31 20:19:05105
106 Args:
107 pipe_id: integer ID of the pipe.
108 from_address: The IP address to match source address. May be an IP or
109 'any'.
110 to_address: The IP address to match destination address. May be an IP or
111 'any'.
112 port_range: The range of ports the rule shall be applied on. Must be
113 specified as a tuple of with two integers.
114 Returns:
115 The ID of the rule, starting at 100. The rule ID increments with 100 for
116 each rule being added.
117 """
118 self._rule_counter += 100
jansson@webrtc.org755e19a2013-03-08 10:50:14119 add_part = ['add', self._rule_counter, 'pipe', pipe_id,
kjellander@webrtc.org595749f2012-05-31 20:19:05120 'ip', 'from', from_address, 'to', to_address]
jansson@webrtc.org5d3ced52013-03-08 13:43:36121 _run_ipfw_command(add_part + ['src-port', '%s-%s' % port_range],
jansson@webrtc.org755e19a2013-03-08 10:50:14122 'Failed to add Dummynet src-port rule.')
jansson@webrtc.org5d3ced52013-03-08 13:43:36123 _run_ipfw_command(add_part + ['dst-port', '%s-%s' % port_range],
jansson@webrtc.org755e19a2013-03-08 10:50:14124 'Failed to add Dummynet dst-port rule.')
kjellander@webrtc.org595749f2012-05-31 20:19:05125 return self._rule_counter
126
127 def _create_dummynet_pipe(self, bandwidth_kbps, delay_ms, packet_loss_percent,
128 queue_slots):
129 """Creates a Dummynet pipe and return its ID.
130
131 Args:
132 bandwidth_kbps: Bandwidth.
133 delay_ms: Delay for a one-way trip of a packet.
134 packet_loss_percent: Float value of packet loss, in percent.
135 queue_slots: Size of the queue.
136 Returns:
137 The ID of the pipe, starting at 1.
138 """
139 self._pipe_counter += 1
jansson@webrtc.org755e19a2013-03-08 10:50:14140 cmd = ['pipe', self._pipe_counter, 'config',
kjellander@webrtc.org595749f2012-05-31 20:19:05141 'bw', str(bandwidth_kbps/8) + 'KByte/s',
142 'delay', '%sms' % delay_ms,
143 'plr', (packet_loss_percent/100.0),
144 'queue', queue_slots]
kjellander@webrtc.org29c5a232012-06-01 08:42:17145 error_message = 'Failed to create Dummynet pipe. '
146 if sys.platform.startswith('linux'):
147 error_message += ('Make sure you have loaded the ipfw_mod.ko module to '
jansson@webrtc.org755e19a2013-03-08 10:50:14148 'your kernel (sudo insmod /path/to/ipfw_mod.ko).')
jansson@webrtc.org5d3ced52013-03-08 13:43:36149 _run_ipfw_command(cmd, error_message)
kjellander@webrtc.org595749f2012-05-31 20:19:05150 return self._pipe_counter
151
jansson@webrtc.org5d3ced52013-03-08 13:43:36152def cleanup():
153 """Stops the network emulation by flushing all Dummynet rules.
kjellander@webrtc.org595749f2012-05-31 20:19:05154
jansson@webrtc.org5d3ced52013-03-08 13:43:36155 Notice that this will flush any rules that may have been created previously
156 before starting the emulation.
157 """
158 _run_ipfw_command(['-f', 'flush'],
159 'Failed to flush Dummynet rules!')
160 _run_ipfw_command(['-f', 'pipe', 'flush'],
161 'Failed to flush Dummynet pipes!')
162
163def _run_ipfw_command(command, fail_msg=None):
164 """Executes a command and prefixes the appropriate command for
165 Windows or Linux/UNIX.
kjellander@webrtc.org595749f2012-05-31 20:19:05166
phoglund@webrtc.org5d3713932013-03-07 09:59:43167 Args:
168 command: Command list to execute.
jansson@webrtc.org5d3ced52013-03-08 13:43:36169 fail_msg: Message describing the error in case the command fails.
kjellander@webrtc.org595749f2012-05-31 20:19:05170
jansson@webrtc.org5d3ced52013-03-08 13:43:36171 Raises:
jansson@webrtc.org0ef22c22013-03-11 14:52:56172 NetworkEmulatorError: If command fails a message is set by the fail_msg
jansson@webrtc.org5d3ced52013-03-08 13:43:36173 parameter.
174 """
175 if sys.platform == 'win32':
176 ipfw_command = ['ipfw.exe']
177 else:
178 ipfw_command = ['sudo', '-n', 'ipfw']
phoglund@webrtc.org5d3713932013-03-07 09:59:43179
jansson@webrtc.org5d3ced52013-03-08 13:43:36180 cmd_list = ipfw_command[:] + [str(x) for x in command]
181 cmd_string = ' '.join(cmd_list)
182 logging.debug('Running command: %s', cmd_string)
183 process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
184 stderr=subprocess.PIPE)
185 output, error = process.communicate()
186 if process.returncode != 0:
187 raise NetworkEmulatorError(fail_msg, cmd_string, process.returncode, output,
188 error)
Henrik Kjellander57e5fd22015-05-25 10:55:39189 return output.strip()