15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""A script for configuring constraint networks.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Sets up a constrained network configuration on a specific port. Traffic on this
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)port will be redirected to another local server port.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The configuration includes bandwidth, latency, and packet loss.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import collections
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import traffic_control
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Default logging is ERROR. Use --verbose to enable DEBUG logging.
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_DEFAULT_LOG_LEVEL = logging.ERROR
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Dispatcher = collections.namedtuple('Dispatcher', ['dispatch', 'requires_ports',
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                   'desc'])
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Map of command names to traffic_control functions.
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)COMMANDS = {
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Adds a new constrained network configuration.
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'add': Dispatcher(traffic_control.CreateConstrainedPort,
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      requires_ports=True, desc='Add a new constrained port.'),
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Deletes an existing constrained network configuration.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'del': Dispatcher(traffic_control.DeleteConstrainedPort,
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                      requires_ports=True, desc='Delete a constrained port.'),
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Deletes all constrained network configurations.
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'teardown': Dispatcher(traffic_control.TearDown,
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           requires_ports=False,
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           desc='Teardown all constrained ports.')
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _ParseArgs():
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Define and parse command-line arguments.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Returns:
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    tuple as (command, configuration):
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    command: one of the possible commands to setup, delete or teardown the
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             constrained network.
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    configuration: a map of constrained network properties to their values.
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser = optparse.OptionParser()
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  indent_first = parser.formatter.indent_increment
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  opt_width = parser.formatter.help_position - indent_first
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cmd_usage = []
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for s in COMMANDS:
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd_usage.append('%*s%-*s%s' %
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     (indent_first, '', opt_width, s, COMMANDS[s].desc))
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.usage = ('usage: %%prog {%s} [options]\n\n%s' %
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  ('|'.join(COMMANDS.keys()), '\n'.join(cmd_usage)))
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--port', type='int',
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='The port to apply traffic control constraints to.')
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--server-port', type='int',
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='Port to forward traffic on --port to.')
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--bandwidth', type='int',
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='Bandwidth of the network in kbit/s.')
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--latency', type='int',
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Latency (delay) added to each outgoing packet in '
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'ms.'))
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--loss', type='int',
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help='Packet-loss percentage on outgoing packets. ')
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('--interface', type='string',
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    help=('Interface to setup constraints on. Use "lo" for a '
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'local client.'))
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    default=False, help='Turn on verbose output.')
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  options, args = parser.parse_args()
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _SetLogger(options.verbose)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check a valid command was entered
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if not args or args[0].lower() not in COMMANDS:
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    parser.error('Please specify a command {%s}.' % '|'.join(COMMANDS.keys()))
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  user_cmd = args[0].lower()
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check if required options are available
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if COMMANDS[user_cmd].requires_ports:
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not (options.port and options.server_port):
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      parser.error('Please provide port and server-port values.')
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  config = {
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'port': options.port,
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'server_port': options.server_port,
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'interface': options.interface,
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'latency': options.latency,
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'bandwidth': options.bandwidth,
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      'loss': options.loss
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return user_cmd, config
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def _SetLogger(verbose):
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  log_level = _DEFAULT_LOG_LEVEL
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if verbose:
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    log_level = logging.DEBUG
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  logging.basicConfig(level=log_level, format='%(message)s')
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Main():
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Get the command and configuration of the network to set up."""
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  user_cmd, config = _ParseArgs()
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  try:
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    COMMANDS[user_cmd].dispatch(config)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  except traffic_control.TrafficControlError as e:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    logging.error('Error: %s\n\nOutput: %s', e.msg, e.error)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Main()
124