- Checks the OS and runs the appropriate commands for Dummynet (ipfw)

- Added pipe rule flush handling
- Also fixed a bug preventing any rule settings other than default from being 
  used no matter what preset was chosen
- Fixed some comments.

BUGS=none
TEST= Windows and linux
Review URL: https://webrtc-codereview.appspot.com/1158006

git-svn-id: http://webrtc.googlecode.com/svn/trunk@3639 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
jansson@webrtc.org 2013-03-08 10:50:14 +00:00
parent 971278a962
commit 755e19adfc
2 changed files with 53 additions and 47 deletions

View File

@ -9,15 +9,16 @@
"""Script for constraining traffic on the local machine.""" """Script for constraining traffic on the local machine."""
import logging import logging
import optparse import optparse
import os
import socket import socket
import sys import sys
import config import config
import network_emulator import network_emulator
_DEFAULT_LOG_LEVEL = logging.INFO _DEFAULT_LOG_LEVEL = logging.INFO
# Default port range to apply network constraints on. # Default port range to apply network constraints on.
@ -68,7 +69,7 @@ def _parse_args():
'ID Name Receive Send Queue Delay loss \n' 'ID Name Receive Send Queue Delay loss \n'
'-- ---- --------- -------- ----- ------- ------\n' '-- ---- --------- -------- ----- ------- ------\n'
'%s\n' % presets_string)) '%s\n' % presets_string))
parser.add_option('-p', '--preset', type='int', default=2, parser.add_option('-p', '--preset', type='int', default=_DEFAULT_PRESET_ID,
help=('ConnectionConfig configuration, specified by ID. ' help=('ConnectionConfig configuration, specified by ID. '
'Default: %default')) 'Default: %default'))
parser.add_option('-r', '--receive-bw', type='int', parser.add_option('-r', '--receive-bw', type='int',
@ -98,11 +99,11 @@ def _parse_args():
options = parser.parse_args()[0] options = parser.parse_args()[0]
# Find preset by ID, if specified: # Find preset by ID, if specified.
if options.preset and not _PRESETS_DICT.has_key(options.preset): if options.preset and not _PRESETS_DICT.has_key(options.preset):
parser.error('Invalid preset: %s' % options.preset) parser.error('Invalid preset: %s' % options.preset)
# Simple validation of the IP address, if supplied: # Simple validation of the IP address, if supplied.
if options.target_ip: if options.target_ip:
try: try:
socket.inet_aton(options.target_ip) socket.inet_aton(options.target_ip)
@ -133,27 +134,21 @@ def _set_logger(verbose):
def _main(): def _main():
"""Checks arguments, permissions and runs a network emulation."""
if os.name != 'posix':
print >> sys.stderr, 'This script is only supported on Linux and Mac.'
return 1
options = _parse_args() options = _parse_args()
# Build a configuration object. Override any preset configuration settings if # Build a configuration object. Override any preset configuration settings if
# a value of a setting was also given as a flag. # a value of a setting was also given as a flag.
connection_config = _PRESETS_DICT[options.preset] connection_config = _PRESETS_DICT[options.preset]
if options.receive_bw: if options.receive_bw is not _DEFAULT_PRESET.receive_bw_kbps:
connection_config.receive_bw_kbps = options.receive_bw connection_config.receive_bw_kbps = options.receive_bw
if options.send_bw: if options.send_bw is not _DEFAULT_PRESET.send_bw_kbps:
connection_config.send_bw_kbps = options.send_bw connection_config.send_bw_kbps = options.send_bw
if options.delay: if options.delay is not _DEFAULT_PRESET.delay_ms:
connection_config.delay_ms = options.delay connection_config.delay_ms = options.delay
if options.packet_loss: if options.packet_loss is not _DEFAULT_PRESET.packet_loss_percent:
connection_config.packet_loss_percent = options.packet_loss connection_config.packet_loss_percent = options.packet_loss
if options.queue: if options.queue is not _DEFAULT_PRESET.queue_slots:
connection_config.queue_slots = options.queue connection_config.queue_slots = options.queue
emulator = network_emulator.NetworkEmulator(connection_config, emulator = network_emulator.NetworkEmulator(connection_config,
options.port_range) options.port_range)
try: try:

View File

@ -9,6 +9,7 @@
"""Script for constraining traffic on the local machine.""" """Script for constraining traffic on the local machine."""
import ctypes
import logging import logging
import os import os
import subprocess import subprocess
@ -88,21 +89,26 @@ class NetworkEmulator(object):
NetworkEmulatorError: If permissions to run Dummynet commands are not NetworkEmulatorError: If permissions to run Dummynet commands are not
available. available.
""" """
if os.geteuid() != 0: try:
_run_shell_command( if os.getuid() != 0:
['sudo', '-n', 'ipfw', '-h'], raise NetworkEmulatorError('You must run this script with sudo.')
msg=('Cannot run \'ipfw\' command. This script must be run as ' except AttributeError:
'root or have password-less sudo access to this command.'))
@staticmethod # AttributeError will be raised on Windows.
def cleanup(): if ctypes.windll.shell32.IsUserAnAdmin() == 0:
raise NetworkEmulatorError('You must run this script with administrator'
'privileges.')
def cleanup(self):
"""Stops the network emulation by flushing all Dummynet rules. """Stops the network emulation by flushing all Dummynet rules.
Notice that this will flush any rules that may have been created previously Notice that this will flush any rules that may have been created previously
before starting the emulation. before starting the emulation.
""" """
_run_shell_command(['sudo', 'ipfw', '-f', 'flush'], self._run_ipfw_command(['-f', 'flush'],
'Failed to flush Dummynet rules!') 'Failed to flush Dummynet rules!')
self._run_ipfw_command(['-f', 'pipe', 'flush'],
'Failed to flush Dummynet pipes!')
def _create_dummynet_rule(self, pipe_id, from_address, to_address, def _create_dummynet_rule(self, pipe_id, from_address, to_address,
port_range): port_range):
@ -121,11 +127,11 @@ class NetworkEmulator(object):
each rule being added. each rule being added.
""" """
self._rule_counter += 100 self._rule_counter += 100
add_part = ['sudo', 'ipfw', 'add', self._rule_counter, 'pipe', pipe_id, add_part = ['add', self._rule_counter, 'pipe', pipe_id,
'ip', 'from', from_address, 'to', to_address] 'ip', 'from', from_address, 'to', to_address]
_run_shell_command(add_part + ['src-port', '%s-%s' % port_range], self._run_ipfw_command(add_part + ['src-port', '%s-%s' % port_range],
'Failed to add Dummynet src-port rule.') 'Failed to add Dummynet src-port rule.')
_run_shell_command(add_part + ['dst-port', '%s-%s' % port_range], self._run_ipfw_command(add_part + ['dst-port', '%s-%s' % port_range],
'Failed to add Dummynet dst-port rule.') 'Failed to add Dummynet dst-port rule.')
return self._rule_counter return self._rule_counter
@ -142,7 +148,7 @@ class NetworkEmulator(object):
The ID of the pipe, starting at 1. The ID of the pipe, starting at 1.
""" """
self._pipe_counter += 1 self._pipe_counter += 1
cmd = ['sudo', 'ipfw', 'pipe', self._pipe_counter, 'config', cmd = ['pipe', self._pipe_counter, 'config',
'bw', str(bandwidth_kbps/8) + 'KByte/s', 'bw', str(bandwidth_kbps/8) + 'KByte/s',
'delay', '%sms' % delay_ms, 'delay', '%sms' % delay_ms,
'plr', (packet_loss_percent/100.0), 'plr', (packet_loss_percent/100.0),
@ -150,10 +156,13 @@ class NetworkEmulator(object):
error_message = 'Failed to create Dummynet pipe. ' error_message = 'Failed to create Dummynet pipe. '
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):
error_message += ('Make sure you have loaded the ipfw_mod.ko module to ' error_message += ('Make sure you have loaded the ipfw_mod.ko module to '
'your kernel (sudo insmod /path/to/ipfw_mod.ko)') 'your kernel (sudo insmod /path/to/ipfw_mod.ko).')
_run_shell_command(cmd, error_message) self._run_ipfw_command(cmd, error_message)
return self._pipe_counter return self._pipe_counter
def _run_ipfw_command(self, command, msg=None):
"""Executes a command and it prefixes the appropriate command for
Windows or Linux/UNIX.
def _run_shell_command(command, msg=None): def _run_shell_command(command, msg=None):
"""Executes a command. """Executes a command.
@ -162,20 +171,22 @@ def _run_shell_command(command, msg=None):
command: Command list to execute. command: Command list to execute.
msg: Message describing the error in case the command fails. msg: Message describing the error in case the command fails.
Returns:
The standard output from running the command.
Raises: Raises:
NetworkEmulatorError: If command fails. Message is set by the msg NetworkEmulatorError: If command fails. Message is set by the msg
parameter. parameter.
""" """
cmd_list = [str(x) for x in command] if sys.platform == 'win32':
cmd = ' '.join(cmd_list) ipfw_command = ['ipfw.exe']
logging.debug('Running command: %s', cmd) 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, process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
output, error = process.communicate() output, error = process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise NetworkEmulatorError(msg, cmd, process.returncode, output, error) raise NetworkEmulatorError(msg, cmd_string, process.returncode, output,
error)
return output.strip() return output.strip()