 0ef22c24c0
			
		
	
	0ef22c24c0
	
	
	
		
			
			I had to create this CL due to comitting before the final comments in the last CL http://review.webrtc.org/1157005/ in revision: https://code.google.com/p/webrtc/source/detail?r=3642 Changed e.msg to e.fail_msg in logging.error in emulate.py Added space to error message for windows in check_permissions() in network_emulator BUG=none TEST=Windows and linux Review URL: https://webrtc-codereview.appspot.com/1167006 git-svn-id: http://webrtc.googlecode.com/svn/trunk@3646 4adac7df-926f-26a2-2b94-8c16560cd09d
		
			
				
	
	
		
			189 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| #  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(object):
 | |
|   """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._create_dummynet_pipe(
 | |
|         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._create_dummynet_pipe(
 | |
|         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._create_dummynet_rule(receive_pipe_id, 'any',
 | |
|                                                   target_ip, self._port_range)
 | |
|     logging.debug('Created incoming rule: %s', incoming_rule_id)
 | |
|     outgoing_rule_id = self._create_dummynet_rule(send_pipe_id, target_ip,
 | |
|                                                   'any', self._port_range)
 | |
|     logging.debug('Created outgoing rule: %s', outgoing_rule_id)
 | |
| 
 | |
|   @staticmethod
 | |
|   def check_permissions():
 | |
|     """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:
 | |
| 
 | |
|     # AttributeError will be raised on Windows.
 | |
|       if ctypes.windll.shell32.IsUserAnAdmin() == 0:
 | |
|         raise NetworkEmulatorError('You must run this script with administrator'
 | |
|                                    ' privileges.')
 | |
| 
 | |
|   def _create_dummynet_rule(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]
 | |
|     _run_ipfw_command(add_part + ['src-port', '%s-%s' % port_range],
 | |
|                             'Failed to add Dummynet src-port rule.')
 | |
|     _run_ipfw_command(add_part + ['dst-port', '%s-%s' % port_range],
 | |
|                             'Failed to add Dummynet dst-port rule.')
 | |
|     return self._rule_counter
 | |
| 
 | |
|   def _create_dummynet_pipe(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).')
 | |
|     _run_ipfw_command(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.
 | |
|   """
 | |
|   _run_ipfw_command(['-f', 'flush'],
 | |
|                           'Failed to flush Dummynet rules!')
 | |
|   _run_ipfw_command(['-f', 'pipe', 'flush'],
 | |
|                           'Failed to flush Dummynet pipes!')
 | |
| 
 | |
| def _run_ipfw_command(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() |