blob: 2876939ead15c08bf767e2f840e183606de2af90 [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
12import logging
13import os
14import subprocess
kjellander@webrtc.org29c5a232012-06-01 08:42:1715import sys
kjellander@webrtc.org595749f2012-05-31 20:19:0516
17
kjellander@webrtc.org29c5a232012-06-01 08:42:1718class NetworkEmulatorError(BaseException):
19 """Exception raised for errors in the network emulator.
kjellander@webrtc.org595749f2012-05-31 20:19:0520
21 Attributes:
22 msg: User defined error message.
23 cmd: Command for which the exception was raised.
24 returncode: Return code of running the command.
25 stdout: Output of running the command.
26 stderr: Error output of running the command.
27 """
28
29 def __init__(self, msg, cmd=None, returncode=None, output=None,
30 error=None):
31 BaseException.__init__(self, msg)
32 self.msg = msg
33 self.cmd = cmd
34 self.returncode = returncode
35 self.output = output
36 self.error = error
37
38
kjellander@webrtc.org29c5a232012-06-01 08:42:1739class NetworkEmulator(object):
40 """A network emulator that can constrain the network using Dummynet."""
kjellander@webrtc.org595749f2012-05-31 20:19:0541
42 def __init__(self, connection_config, port_range):
43 """Constructor.
44
45 Args:
46 connection_config: A config.ConnectionConfig object containing the
kjellander@webrtc.org29c5a232012-06-01 08:42:1747 characteristics for the connection to be emulation.
kjellander@webrtc.org595749f2012-05-31 20:19:0548 port_range: Tuple containing two integers defining the port range.
49 """
50 self._pipe_counter = 0
51 self._rule_counter = 0
52 self._port_range = port_range
53 self._connection_config = connection_config
54
kjellander@webrtc.org29c5a232012-06-01 08:42:1755 def emulate(self, target_ip):
56 """Starts a network emulation by setting up Dummynet rules.
kjellander@webrtc.org595749f2012-05-31 20:19:0557
58 Args:
59 target_ip: The IP address of the interface that shall be that have the
60 network constraints applied to it.
61 """
62 receive_pipe_id = self._create_dummynet_pipe(
63 self._connection_config.receive_bw_kbps,
64 self._connection_config.delay_ms,
65 self._connection_config.packet_loss_percent,
66 self._connection_config.queue_slots)
67 logging.debug('Created receive pipe: %s', receive_pipe_id)
68 send_pipe_id = self._create_dummynet_pipe(
69 self._connection_config.send_bw_kbps,
70 self._connection_config.delay_ms,
71 self._connection_config.packet_loss_percent,
72 self._connection_config.queue_slots)
73 logging.debug('Created send pipe: %s', send_pipe_id)
74
kjellander@webrtc.org29c5a232012-06-01 08:42:1775 # Adding the rules will start the emulation.
kjellander@webrtc.org595749f2012-05-31 20:19:0576 incoming_rule_id = self._create_dummynet_rule(receive_pipe_id, 'any',
77 target_ip, self._port_range)
78 logging.debug('Created incoming rule: %s', incoming_rule_id)
79 outgoing_rule_id = self._create_dummynet_rule(send_pipe_id, target_ip,
80 'any', self._port_range)
81 logging.debug('Created outgoing rule: %s', outgoing_rule_id)
82
83 def check_permissions(self):
84 """Checks if permissions are available to run Dummynet commands.
85
86 Raises:
kjellander@webrtc.org29c5a232012-06-01 08:42:1787 NetworkEmulatorError: If permissions to run Dummynet commands are not
kjellander@webrtc.org595749f2012-05-31 20:19:0588 available.
89 """
90 if os.geteuid() != 0:
91 self._run_shell_command(
92 ['sudo', '-n', 'ipfw', '-h'],
93 msg=('Cannot run \'ipfw\' command. This script must be run as '
94 'root or have password-less sudo access to this command.'))
95
96 def cleanup(self):
kjellander@webrtc.org29c5a232012-06-01 08:42:1797 """Stops the network emulation by flushing all Dummynet rules.
kjellander@webrtc.org595749f2012-05-31 20:19:0598
99 Notice that this will flush any rules that may have been created previously
kjellander@webrtc.org29c5a232012-06-01 08:42:17100 before starting the emulation.
kjellander@webrtc.org595749f2012-05-31 20:19:05101 """
102 self._run_shell_command(['sudo', 'ipfw', '-f', 'flush'],
103 'Failed to flush Dummynet rules!')
104
105 def _create_dummynet_rule(self, pipe_id, from_address, to_address,
106 port_range):
kjellander@webrtc.org29c5a232012-06-01 08:42:17107 """Creates a network emulation rule and returns its ID.
kjellander@webrtc.org595749f2012-05-31 20:19:05108
109 Args:
110 pipe_id: integer ID of the pipe.
111 from_address: The IP address to match source address. May be an IP or
112 'any'.
113 to_address: The IP address to match destination address. May be an IP or
114 'any'.
115 port_range: The range of ports the rule shall be applied on. Must be
116 specified as a tuple of with two integers.
117 Returns:
118 The ID of the rule, starting at 100. The rule ID increments with 100 for
119 each rule being added.
120 """
121 self._rule_counter += 100
122 add_part = ['sudo', 'ipfw', 'add', self._rule_counter, 'pipe', pipe_id,
123 'ip', 'from', from_address, 'to', to_address]
124 self._run_shell_command(add_part + ['src-port', '%s-%s' % port_range],
125 'Failed to add Dummynet src-port rule.')
126 self._run_shell_command(add_part + ['dst-port', '%s-%s' % port_range],
127 'Failed to add Dummynet dst-port rule.')
128 return self._rule_counter
129
130 def _create_dummynet_pipe(self, bandwidth_kbps, delay_ms, packet_loss_percent,
131 queue_slots):
132 """Creates a Dummynet pipe and return its ID.
133
134 Args:
135 bandwidth_kbps: Bandwidth.
136 delay_ms: Delay for a one-way trip of a packet.
137 packet_loss_percent: Float value of packet loss, in percent.
138 queue_slots: Size of the queue.
139 Returns:
140 The ID of the pipe, starting at 1.
141 """
142 self._pipe_counter += 1
143 cmd = ['sudo', 'ipfw', 'pipe', self._pipe_counter, 'config',
144 'bw', str(bandwidth_kbps/8) + 'KByte/s',
145 'delay', '%sms' % delay_ms,
146 'plr', (packet_loss_percent/100.0),
147 'queue', queue_slots]
kjellander@webrtc.org29c5a232012-06-01 08:42:17148 error_message = 'Failed to create Dummynet pipe. '
149 if sys.platform.startswith('linux'):
150 error_message += ('Make sure you have loaded the ipfw_mod.ko module to '
151 'your kernel (sudo insmod /path/to/ipfw_mod.ko)')
152 self._run_shell_command(cmd, error_message)
kjellander@webrtc.org595749f2012-05-31 20:19:05153 return self._pipe_counter
154
155 def _run_shell_command(self, command, msg=None):
156 """Executes a command.
157
158 Args:
159 command: Command list to execute.
160 msg: Message describing the error in case the command fails.
161
162 Returns:
163 The standard output from running the command.
164
165 Raises:
kjellander@webrtc.org29c5a232012-06-01 08:42:17166 NetworkEmulatorError: If command fails. Message is set by the msg
kjellander@webrtc.org595749f2012-05-31 20:19:05167 parameter.
168 """
169 cmd_list = [str(x) for x in command]
170 cmd = ' '.join(cmd_list)
171 logging.debug('Running command: %s', cmd)
172
173 process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
174 stderr=subprocess.PIPE)
175 output, error = process.communicate()
176 if process.returncode != 0:
kjellander@webrtc.org29c5a232012-06-01 08:42:17177 raise NetworkEmulatorError(msg, cmd, process.returncode, output, error)
kjellander@webrtc.org595749f2012-05-31 20:19:05178 return output.strip()