def test_command_error(self): """Run a regular command returning an error""" Commands.run("true") with self.assertRaises(CommandError) as ce: Commands.run("false") self.assertEqual(ce.exception.command, "false") self.assertEqual(ce.exception.retcode, 1)
def test_command_orders_with_errors(self): """Run commands in a specific order with one command return an error""" self.commands.insert(30, "false") with self.assertRaises(CommandError): Commands.run(*self.commands) self.assertEqual(file(self.testfile).read(), "".join("%d\n" % i for i in range(1, 32)))
def test_several_inexistant_commands(self): """Run several inexistant command""" with self.assertRaises(CommandError) as ce: Commands.run("i_do_not_exist", "unknown_command") self.assertEqual(ce.exception.command, "i_do_not_exist") self.assertEqual(ce.exception.retcode, errno.ENOENT) self.assertEqual(ce.exception.index, 0)
def test_command_orders_with_errors(self): """Run commands in a specific order with one command return an error""" self.commands.insert(30, "false") with self.assertRaises(CommandError): Commands.run(*self.commands) self.assertEqual( file(self.testfile).read(), "".join("%d\n" % i for i in range(1, 32)))
def test_inexistant_command(self): """Run an inexistant command""" with self.assertRaises(CommandError) as ce: Commands.run("i_do_not_exist") self.assertEqual(ce.exception.command, "i_do_not_exist") self.assertEqual(ce.exception.retcode, errno.ENOENT) # Also check that we can build the exception description str(ce.exception)
def test_mix_inexistant_commands(self): """Run one inexistant command and one regular command""" with self.assertRaises(CommandError) as ce: Commands.run("echo hello", "unknown_command") self.assertEqual(ce.exception.command, "unknown_command") self.assertEqual(ce.exception.retcode, errno.ENOENT) self.assertEqual(ce.exception.index, 1) with self.assertRaises(CommandError) as ce: Commands.run("unknown_command", "echo hello") self.assertEqual(ce.exception.command, "unknown_command") self.assertEqual(ce.exception.retcode, errno.ENOENT) self.assertEqual(ce.exception.index, 0)
def stats(self): """Return statistics for each interface and client.""" if self.router is None: return {} # Setup is not done yet stats = {} output = "\n".join([Commands.run("%(iptables)s -t mangle -v -S %(accounting)s", iptables=iptables, **self.config) for iptables in self.iptables]) for line in output.split("\n"): mo = self.STATSRE.match(line.strip()) if mo: interface = mo.group('interface') direction = mo.group('direction') client = mo.group('client') if interface not in stats: stats[interface] = {} stats[interface]['details'] = {} if client not in stats[interface]['details']: stats[interface]['details'][client]= {} stats[interface]['details'][client][direction] = int(mo.group("bytes")) for interface in stats: up = down = clients = 0 for client in stats[interface]['details']: clients = clients + 1 up = up + stats[interface]['details'][client].get("up", 0) down = down + stats[interface]['details'][client].get("down", 0) stats[interface]["clients"] = clients stats[interface]["up"] = up stats[interface]["down"] = down return stats
def test_several_commands(self): """Test several commands using variables""" self.assertEqual(Commands.run("echo %(var1)s %(var2)s", "echo %(var1)s %(var3)s", "echo bye", var1="hello1", var2="hello2", var3="hello3"), ["hello1 hello2\n", "hello1 hello3\n", "bye\n"])
def test_several_commands(self): """Test several commands using variables""" self.assertEqual( Commands.run("echo %(var1)s %(var2)s", "echo %(var1)s %(var3)s", "echo bye", var1="hello1", var2="hello2", var3="hello3"), ["hello1 hello2\n", "hello1 hello3\n", "bye\n"])
def test_error_command_withoutput(self): """Test a command with non-0 result and output""" result = Commands.run_noerr("grep stuff /i_do_not_exist") self.assertEqual(result[:6], "grep: ")
def test_false_command(self): """Test a command that return non 0 result""" self.assertEqual(Commands.run_noerr("false"), "")
def test_inexistant_command(self): """Test an inexistant command with run_noerr""" with self.assertRaises(CommandError) as ce: Commands.run_noerr("i_do_not_exist") self.assertEqual(ce.exception.command, "i_do_not_exist") self.assertEqual(ce.exception.retcode, errno.ENOENT)
def test_run_one_command(self): """Run one command""" Commands.run("echo hello")
def test_no_commands(self): """Run no command""" self.assertEqual(Commands.run(), None)
def test_run_several_commands(self): """Run several commands""" Commands.run("echo hello", "echo hi", "echo good bye")
def test_output_several_commands(self): """Run several commands and check their outputs""" self.assertEqual(Commands.run("echo hello", "echo hi"), ["hello\n", "hi\n"])
def test_several_variables(self): """Test a command using several variables""" self.assertEqual(Commands.run("echo %(var1)s %(var2)s", var1="hello1", var2="hello2"), "hello1 hello2\n")
def test_command_orders(self): """Run commands in a specific order""" Commands.run(*self.commands) self.assertEqual( file(self.testfile).read(), "".join("%d\n" % i for i in range(1, 100)))
def test_command_orders(self): """Run commands in a specific order""" Commands.run(*self.commands) self.assertEqual(file(self.testfile).read(), "".join("%d\n" % i for i in range(1, 100)))
def test_output_one_command(self): """Run one command and check its output""" self.assertEqual(Commands.run("echo hello"), "hello\n")
def test_several_variables(self): """Test a command using several variables""" self.assertEqual( Commands.run("echo %(var1)s %(var2)s", var1="hello1", var2="hello2"), "hello1 hello2\n")
def test_one_variable(self): """Test a command using one variable""" self.assertEqual(Commands.run("echo %(var)s", var="hello"), "hello\n")
def setup(self): """Setup the binder for the first time. Cleaning is also handled here since the binder has no way to clean on exit. """ self.interfaces = self.router.interfaces.keys() # Ordered interface list self.interfaces.sort() self.mark = Mark(len(self.interfaces), # Netfilter mark producer self.config['max_users']) self.slots = SlotsProvider(self.config['max_users']) # Slot producer self.tickets = TicketsProvider() # Ticket producer # Netfilter for chain in [ "prerouting", "accounting", "postrouting" ]: subs = dict(chain = self.config[chain], chain_upper = chain.upper()) if chain == "accounting": subs['chain_upper'] = "POSTROUTING" logger.info("setup %(chain)s chain" % subs) # Cleanup old iptables rules for iptables in self.iptables: Commands.run_noerr("%(iptables)s -t mangle -D %(chain_upper)s -j %(chain)s", "%(iptables)s -t mangle -F %(chain)s", "%(iptables)s -t mangle -X %(chain)s", iptables=iptables, **subs) # Setup the new chains Commands.run("%(iptables)s -t mangle -N %(chain)s", "%(iptables)s -t mangle -I %(chain_upper)s -j %(chain)s", iptables=iptables, **subs) # Setup QoS for interface in self.interfaces + self.router.incoming: logger.info("setup QoS for interface %s" % interface) Commands.run_noerr("tc qdisc del dev %(interface)s root", interface=interface) Commands.run( # Flush QoS "tc qdisc add dev %(interface)s root handle 1: drr", # Default class "tc class add dev %(interface)s parent 1: classid 1:2 drr", "tc qdisc add dev %(interface)s parent 1:2 handle 12: sfq", # Use default class for unmatched traffic "tc filter add dev %(interface)s protocol arp parent 1:0" " prio 1 u32 match u32 0 0 flowid 1:2", # ARP interface=interface, **self.config) for iptables in self.iptables: Commands.run( "%(iptables)s -t mangle -A %(postrouting)s" " -o %(interface)s -j CLASSIFY --set-class 1:2", # IP iptables=iptables, interface=interface, **self.config) # Setup routing rules for interface in self.interfaces: logger.info("setup ip rules for interface %s" % interface) for ip in self.ipcmd: Commands.run_noerr("%(ip)s rule del fwmark %(mark)s table %(interface)s", mark="%s/%s" % self.mark(self.interfaces.index(interface)), ip = ip, interface=interface) Commands.run("%(ip)s rule add fwmark %(mark)s table %(interface)s", ip = ip, mark="%s/%s" % self.mark(self.interfaces.index(interface)), interface=interface)
def bind(self, client, interface, qos, bind=True): """Bind or unbind a user. This is the method that will issue all `tc` and `iptables` commands to ensure the binding of the user to the chosen interface and QoS. :param client: IP of the user :type client: string :param interface: name of the outgoing interface :type interface: string :param qos: QoS name :type qos: string :param slot: QoS slot :type slot: integer :param bind: bind or unbind? :type bind: boolean """ ticket = self.tickets.get(client) slot = self.slots.get(client) mark = self.mark(self.interfaces.index(interface), slot) # tc qdisc and classes for the user def build(interface, qos, what): r = self.router.interfaces[interface].qos[qos].settings.get(what, None) result = { 'up': r, 'down': r } if type(r) is dict: result['up'] = r.get('up', None) result['down'] = r.get('down', None) return result bw = build(interface, qos, "bandwidth") netem = build(interface, qos, "netem") for iface in [interface,] + self.router.incoming: direction = (iface in self.router.incoming) and 'down' or 'up' opts=dict(iface=iface, mark=mark[0], ticket=ticket, bw=bw[direction], netem=netem[direction], add=(bind and "add" or "del")) # Create a deficit round robin scheduler Commands.run("tc class %(add)s dev %(iface)s parent 1: classid 1:%(ticket)s0 drr", **opts) if bw[direction] is not None and bind: # TBF for bandwidth limit... Commands.run( "tc qdisc %(add)s dev %(iface)s parent 1:%(ticket)s0 handle %(ticket)s0:" " tbf rate %(bw)s", **opts) if netem[direction] is not None and bind: # ...and netem Commands.run( "tc qdisc %(add)s dev %(iface)s parent %(ticket)s0:1 " " handle %(ticket)s1:" " netem %(netem)s", **opts) elif netem[direction] is not None and bind: # Just netem Commands.run( "tc qdisc %(add)s dev %(iface)s parent 1:%(ticket)s0 handle %(ticket)s0:" " netem %(netem)s", **opts) elif bind: # No QoS: just use SFQ Commands.run( "tc qdisc %(add)s dev %(iface)s parent 1:%(ticket)s0" " handle %(ticket)s0: sfq", **opts) # iptables to classify and accounting opts = dict( A=(bind and "A" or "D"), outgoing=interface, client=client, mark=mark[0], mask=mark[1], ticket=ticket, iptables=self.isipv6(client) and "ip6tables" or "iptables", **self.config) for incoming in self.router.incoming: Commands.run( # Mark the incoming packet from the client "%(iptables)s -t mangle -%(A)s %(prerouting)s -i %(incoming)s" " -s %(client)s -j MARK --set-mark %(mark)s/%(mask)s", incoming=incoming, **opts) Commands.run( # Keep the mark only if we reached the output interface "%(iptables)s -t mangle -%(A)s %(postrouting)s " " -o %(outgoing)s -s %(client)s -m mark --mark %(mark)s/%(mask)s" " -j CONNMARK --save-mark --nfmask %(mask)s --ctmask %(mask)s", # Classify. Outgoing "%(iptables)s -t mangle -%(A)s %(postrouting)s" " -o %(outgoing)s -m connmark --mark %(mark)s/%(mask)s" " -j CLASSIFY --set-class 1:%(ticket)s0", **opts) for incoming in self.router.incoming: Commands.run( # Classify. Incoming "%(iptables)s -t mangle -%(A)s %(postrouting)s" " -o %(incoming)s -m connmark --mark %(mark)s/%(mask)s" " -j CLASSIFY --set-class 1:%(ticket)s0", incoming=incoming, **opts) Commands.run( # Accounting. Outgoing "%(iptables)s -t mangle -%(A)s %(accounting)s" " -o %(outgoing)s -m connmark --mark %(mark)s/%(mask)s" " -m comment --comment up-%(outgoing)s-%(client)s", **opts) for incoming in self.router.incoming: Commands.run( # Accouting. Incoming "%(iptables)s -t mangle -%(A)s %(accounting)s" " -o %(incoming)s -m connmark --mark %(mark)s/%(mask)s" " -m comment --comment down-%(outgoing)s-%(client)s", incoming=incoming, **opts)