Commit 3006f202 authored by Martín Ferrari's avatar Martín Ferrari

Routing support!

parent 206eed94
......@@ -24,6 +24,12 @@ def _positive(val):
raise ValueError("Invalid value: %d" % v)
return v
def _non_empty_str(val):
if val == '':
return None
else:
return str(val)
def _fix_lladdr(addr):
foo = addr.lower()
if ':' in addr:
......@@ -209,7 +215,47 @@ class ipv6address(address):
return s % (self.__module__, self.__class__.__name__,
self.address.__repr__(), self.prefix_len)
# XXX: ideally this should be replaced by netlink communication
class route(object):
tipes = ['unicast', 'local', 'broadcast', 'multicast', 'throw',
'unreachable', 'prohibit', 'blackhole', 'nat']
tipe = property(_make_getter("_tipe", tipes.__getitem__),
_make_setter("_tipe", tipes.index))
prefix = property(_make_getter("_prefix"),
_make_setter("_prefix", _non_empty_str))
prefix_len = property(_make_getter("_plen"),
_make_setter("_plen", int))
nexthop = property(_make_getter("_nexthop"),
_make_setter("_nexthop", _non_empty_str))
metric = property(_make_getter("_metric"),
_make_setter("_metric", int))
device = property(_make_getter("_device"),
_make_setter("_device", _positive))
def __init__(self, tipe = 'unicast', prefix = None, prefix_len = 0,
nexthop = None, device = None, metric = 0):
self.tipe = tipe
self.prefix = prefix
self.prefix_len = prefix_len
self.nexthop = nexthop
self.device = device
self.metric = metric
assert nexthop or device
def __repr__(self):
s = "%s.%s(tipe = %s, prefix = %s, prefix_len = %s, nexthop = %s, "
s += "device = %s, metric = %s)"
return s % (self.__module__, self.__class__.__name__,
self.tipe.__repr__(), self.prefix.__repr__(),
self.prefix_len.__repr__(), self.nexthop.__repr__(),
self.device.__repr__(), self.metric.__repr__())
def __eq__(self, o):
if not isinstance(o, route):
return False
return (self.tipe == o.tipe and self.prefix == o.prefix and
self.prefix_len == o.prefix_len and self.nexthop == o.nexthop
and self.device == o.device and self.metric == o.metric)
# helpers
def _execute(cmd):
......@@ -228,6 +274,7 @@ def _get_if_name(iface):
return iface
return get_if(iface).name
# XXX: ideally this should be replaced by netlink communication
# Interface handling
def get_if_data():
"""Gets current interface information. Returns a tuple (byidx, bynam) in
......@@ -579,31 +626,33 @@ def get_all_route_data():
prefix_len = 0
else:
prefix, foo, prefix_len = prefix.partition('/')
ret.append((tipe, prefix, int(prefix_len), nexthop, device))
ret.append(route(tipe, prefix, prefix_len, nexthop, device.index))
return ret
def get_route_data():
# filter out non-unicast routes
return [x for x in get_all_route_data() if x[0] == 'unicast']
return [x for x in get_all_route_data() if x.tipe == 'unicast']
def del_route(tipe, prefix, prefix_len, nexthop, device):
_add_del_route('del', tipe, prefix, prefix_len, nexthop, device)
def add_route(route):
if route in get_all_route_data():
raise ValueError('Route already exists')
_add_del_route('add', route)
def add_route(tipe, prefix, prefix_len, nexthop, device):
_add_del_route('add', tipe, prefix, prefix_len, nexthop, device)
def del_route(route):
if route not in get_all_route_data():
raise ValueError('Route does not exist')
_add_del_route('del', route)
def _add_del_route(action, tipe, prefix, prefix_len, nexthop, device):
def _add_del_route(action, route):
cmd = ['ip', 'route', action]
if device:
device = _get_if_name(device)
if tipe and tipe != 'unicast':
cmd += [tipe]
if prefix:
cmd += ["%s/%d" % (prefix, prefix_len)]
if route.tipe != 'unicast':
cmd += [route.tipe]
if route.prefix:
cmd += ["%s/%d" % (route.prefix, route.prefix_len)]
else:
cmd += ['default']
if nexthop:
cmd += ['via', nexthop]
if device:
cmd += ['dev', device]
if route.nexthop:
cmd += ['via', route.nexthop]
if route.device:
cmd += ['dev', _get_if_name(route.device)]
_execute(cmd)
......@@ -85,8 +85,8 @@ class Node(object):
def del_if(self, iface):
"""Doesn't destroy the interface if it wasn't created by us."""
del self._interfaces[iface.index]
iface.destroy()
del self._interfaces[iface.index]
def get_interfaces(self):
ifaces = self._slave.get_if_data()
......@@ -105,13 +105,23 @@ class Node(object):
return sorted(ret, key = lambda x: x.index)
# FIXME: Routing
def add_route(self, prefix, prefix_len, nexthop = None, interface = None):
assert nexthop or interface
def add_default_route(self, nexthop, interface = None):
return self.add_route('0.0.0.0', 0, nexthop, interface)
def add_route(self, *args, **kwargs):
# Accepts either a route object or all its constructor's parameters
if len(args) == 1 and not kwargs:
r = args[0]
else:
r = netns.iproute.route(*args, **kwargs)
return self._slave.add_route(r)
def del_route(self, *args, **kwargs):
if len(args) == 1 and not kwargs:
r = args[0]
else:
r = netns.iproute.route(*args, **kwargs)
return self._slave.del_route(r)
def get_routes(self):
return set()
return self._slave.get_route_data()
# Handle the creation of the child; parent gets (fd, pid), child creates and
# runs a Server(); never returns.
......
......@@ -37,8 +37,8 @@ _proto_commands = {
},
"ROUT": {
"LIST": ("", ""),
"ADD": ("bbibi", ""),
"DEL": ("bbibi", "")
"ADD": ("bbibii", ""),
"DEL": ("bbibii", "")
},
"PROC": {
"CRTE": ("b", "b*"),
......@@ -375,16 +375,20 @@ class Server(object):
self.reply(200, "Done.")
def do_ROUT_LIST(self, cmdname):
netns.iproute.get_route_data()
rdata = netns.iproute.get_route_data()
self.reply(200, ["# Routing data follows."] +
yaml.dump(addrdata).split("\n"))
yaml.dump(rdata).split("\n"))
def do_ROUT_ADD(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr):
netns.iproute.add_route(tipe, prefix, prefixlen, nexthop, ifnr)
def do_ROUT_ADD(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr,
metric):
netns.iproute.add_route(netns.iproute.route(tipe, prefix, prefixlen,
nexthop, ifnr, metric))
self.reply(200, "Done.")
def do_ROUT_DEL(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr):
netns.iproute.del_route(tipe, prefix, prefixlen, nexthop, ifnr)
def do_ROUT_DEL(self, cmdname, tipe, prefix, prefixlen, nexthop, ifnr,
metric):
netns.iproute.del_route(netns.iproute.route(tipe, prefix, prefixlen,
nexthop, ifnr, metric))
self.reply(200, "Done.")
# ============================================================================
......@@ -586,28 +590,23 @@ class Client(object):
data = self._read_and_check_reply()
return yaml.load(data)
def add_route(self,tipe, prefix, prefix_len, nexthop, ifnr):
_add_del_route("ADD", tipe, prefix, prefix_len, nexthop, ifnr)
def del_route(self, tipe, prefix, prefix_len, nexthop, ifnr):
_add_del_route("DEL", tipe, prefix, prefix_len, nexthop, ifnr)
def _add_del_route(self, action, tipe, prefix, prefix_len, nexthop, ifnr):
if not tipe:
tipe = 'unicast'
if not prefix:
prefix = ''
prefix_len = 0
if not nexthop:
nexthop = ''
if not ifnr:
ifnr = 0
args = ["ROUT", action, tipe, prefix, prefix_len, nexthop, ifnr]
args[2:6] = map(_b64, args[2:6])
self._send_cmd(args)
def add_route(self, route):
self._add_del_route("ADD", route)
def del_route(self, route):
self._add_del_route("DEL", route)
def _add_del_route(self, action, route):
args = ["ROUT", action, _b64(route.tipe), _b64(route.prefix),
route.prefix_len, _b64(route.nexthop), route.device,
route.metric]
self._send_cmd(*args)
self._read_and_check_reply()
def _b64(text):
if text == None:
# easier this way
text = ''
text = str(text)
if len(text) == 0 or filter(lambda x: ord(x) <= ord(" ") or
ord(x) > ord("z") or x == "=", text):
......
......@@ -5,6 +5,14 @@ import netns, test_util
import os, unittest
class TestRouting(unittest.TestCase):
def test_base_routing(self):
node = netns.Node(nonetns = True)
routes = node.get_routes()
if(len(routes)):
self.assertRaises(ValueError, node.add_route, routes[0])
routes[0].device += 1 # should be enough to make it unique
self.assertRaises(ValueError, node.del_route, routes[0])
@test_util.skipUnless(os.getuid() == 0, "Test requires root privileges")
def test_routing(self):
node = netns.Node()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment