Commit 385fbe6c authored by Martín Ferrari's avatar Martín Ferrari

be able to get/set bridge parameters too

parent 57a7a20f
...@@ -23,6 +23,7 @@ class Interface(object): ...@@ -23,6 +23,7 @@ class Interface(object):
return "NETNSif-%.4x%.3x" % (os.getpid(), n) return "NETNSif-%.4x%.3x" % (os.getpid(), n)
def __init__(self, index): def __init__(self, index):
self._changeable_attributes = interface.changeable_attributes
self._idx = index self._idx = index
def __del__(self): def __del__(self):
...@@ -51,7 +52,11 @@ class NSInterface(Interface): ...@@ -51,7 +52,11 @@ class NSInterface(Interface):
# some black magic to automatically get/set interface attributes # some black magic to automatically get/set interface attributes
def __getattr__(self, name): def __getattr__(self, name):
if (name not in interface.changeable_attributes): try:
ca = object.__getattr__(self, '_changeable_attributes')
except:
ca = []
if (name not in ca):
raise AttributeError("'%s' object has no attribute '%s'" % raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name)) (self.__class__.__name__, name))
# I can use attributes now, as long as they are not in # I can use attributes now, as long as they are not in
...@@ -60,7 +65,11 @@ class NSInterface(Interface): ...@@ -60,7 +65,11 @@ class NSInterface(Interface):
return getattr(iface, name) return getattr(iface, name)
def __setattr__(self, name, value): def __setattr__(self, name, value):
if (name not in interface.changeable_attributes): try:
ca = object.__getattr__(self, '_changeable_attributes')
except:
ca = []
if (name not in ca):
if name[0] != '_': # forbid anything that doesn't start with a _ if name[0] != '_': # forbid anything that doesn't start with a _
raise AttributeError("'%s' object has no attribute '%s'" % raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name)) (self.__class__.__name__, name))
...@@ -199,7 +208,11 @@ class ExternalInterface(Interface): ...@@ -199,7 +208,11 @@ class ExternalInterface(Interface):
# some black magic to automatically get/set interface attributes # some black magic to automatically get/set interface attributes
def __getattr__(self, name): def __getattr__(self, name):
if (name not in interface.changeable_attributes): try:
ca = object.__getattr__(self, '_changeable_attributes')
except:
ca = []
if (name not in ca):
raise AttributeError("'%s' object has no attribute '%s'" % raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name)) (self.__class__.__name__, name))
# I can use attributes now, as long as they are not in # I can use attributes now, as long as they are not in
...@@ -208,7 +221,11 @@ class ExternalInterface(Interface): ...@@ -208,7 +221,11 @@ class ExternalInterface(Interface):
return getattr(iface, name) return getattr(iface, name)
def __setattr__(self, name, value): def __setattr__(self, name, value):
if (name not in interface.changeable_attributes): try:
ca = object.__getattr__(self, '_changeable_attributes')
except:
ca = []
if (name not in ca):
if name[0] != '_': # forbid anything that doesn't start with a _ if name[0] != '_': # forbid anything that doesn't start with a _
raise AttributeError("'%s' object has no attribute '%s'" % raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name)) (self.__class__.__name__, name))
...@@ -302,6 +319,7 @@ class Link(ExternalInterface): ...@@ -302,6 +319,7 @@ class Link(ExternalInterface):
iface = netns.iproute.create_bridge(self._gen_br_name()) iface = netns.iproute.create_bridge(self._gen_br_name())
super(Link, self).__init__(iface.index) super(Link, self).__init__(iface.index)
self._changeable_attributes = bridge.changeable_attributes
self._ports = set() self._ports = set()
# register somewhere # register somewhere
...@@ -391,22 +409,6 @@ def _make_setter(attr, conv = lambda x: x): ...@@ -391,22 +409,6 @@ def _make_setter(attr, conv = lambda x: x):
class interface(object): class interface(object):
"""Class for internal use. It is mostly a data container used to easily """Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods.""" pass information around; with some convenience methods."""
@classmethod
def parse_ip(cls, line):
"""Parse a line of ouput from `ip -o link list' and construct and
return a new object with the data."""
match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc \S+' +
r'.*link/\S+ ([0-9a-f:]+) brd ([0-9a-f:]+)', line)
flags = match.group(3).split(",")
return cls(
index = match.group(1),
name = match.group(2),
up = "UP" in flags,
mtu = match.group(4),
lladdr = match.group(5),
arp = not ("NOARP" in flags),
broadcast = match.group(6),
multicast = "MULTICAST" in flags)
# information for other parts of the code # information for other parts of the code
changeable_attributes = ["name", "mtu", "lladdr", "broadcast", "up", changeable_attributes = ["name", "mtu", "lladdr", "broadcast", "up",
...@@ -455,30 +457,71 @@ class interface(object): ...@@ -455,30 +457,71 @@ class interface(object):
return self.__class__(self.index, name, up, mtu, lladdr, broadcast, return self.__class__(self.index, name, up, mtu, lladdr, broadcast,
multicast, arp) multicast, arp)
class bridge(interface):
changeable_attributes = interface.changeable_attributes + ["stp",
"forward_delay", "hello_time", "ageing_time", "max_age"]
# Index should be read-only
stp = property(_make_getter("_stp"), _make_setter("_stp", _any_to_bool))
forward_delay = property(_make_getter("_forward_delay"),
_make_setter("_forward_delay", float))
hello_time = property(_make_getter("_hello_time"),
_make_setter("_hello_time", float))
ageing_time = property(_make_getter("_ageing_time"),
_make_setter("_ageing_time", float))
max_age = property(_make_getter("_max_age"),
_make_setter("_max_age", float))
@classmethod
def upgrade(cls, iface, *kargs, **kwargs):
"""Upgrade a interface to a bridge."""
return cls(iface.index, iface.name, iface.up, iface.mtu, iface.lladdr,
iface.broadcast, iface.multicast, iface.arp, *kargs, **kwargs)
def __init__(self, index = None, name = None, up = None, mtu = None,
lladdr = None, broadcast = None, multicast = None, arp = None,
stp = None, forward_delay = None, hello_time = None,
ageing_time = None, max_age = None):
super(bridge, self).__init__(index, name, up, mtu, lladdr, broadcast,
multicast, arp)
self.stp = stp
self.forward_delay = forward_delay
self.hello_time = hello_time
self.ageing_time = ageing_time
self.max_age = max_age
def __repr__(self):
s = "%s.%s(index = %s, name = %s, up = %s, mtu = %s, lladdr = %s, "
s += "broadcast = %s, multicast = %s, arp = %s, stp = %s, "
s += "forward_delay = %s, hello_time = %s, ageing_time = %s, "
s += "max_age = %s)"
return s % (self.__module__, self.__class__.__name__,
self.index.__repr__(), self.name.__repr__(),
self.up.__repr__(), self.mtu.__repr__(),
self.lladdr.__repr__(), self.broadcast.__repr__(),
self.multicast.__repr__(), self.arp.__repr__(),
self.stp.__repr__(), self.forward_delay.__repr__(),
self.hello_time.__repr__(), self.ageing_time.__repr__(),
self.max_age.__repr__())
def __sub__(self, o):
r = super(bridge, self).__sub__(o)
if type(o) == interface:
return r
r.stp = None if self.stp == o.stp else self.stp
r.hello_time = None if self.hello_time == o.hello_time else \
self.hello_time
r.forward_delay = None if self.forward_delay == o.forward_delay else \
self.forward_delay
r.ageing_time = None if self.ageing_time == o.ageing_time else \
self.ageing_time
r.max_age = None if self.max_age == o.max_age else self.max_age
return r
class address(object): class address(object):
"""Class for internal use. It is mostly a data container used to easily """Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods. __eq__ and __hash__ pass information around; with some convenience methods. __eq__ and __hash__
are defined just to be able to easily find duplicated addresses.""" are defined just to be able to easily find duplicated addresses."""
@classmethod
def parse_ip(cls, line):
"""Parse a line of ouput from `ip -o addr list' (after trimming the
index and interface name) and construct and return a new object with
the data."""
match = re.search(r'^inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None:
return ipv4address(
address = match.group(1),
prefix_len = match.group(2),
broadcast = match.group(3))
match = re.search(r'^inet6 ([0-9a-f:]+)/(\d+)', line)
if match != None:
return ipv6address(
address = match.group(1),
prefix_len = match.group(2))
raise RuntimeError("Problems parsing ip command output")
# broadcast is not taken into account for differentiating addresses # broadcast is not taken into account for differentiating addresses
def __eq__(self, o): def __eq__(self, o):
if not isinstance(o, address): if not isinstance(o, address):
......
# vim:ts=4:sw=4:et:ai:sts=4 # vim:ts=4:sw=4:et:ai:sts=4
import re, subprocess, sys import os, re, subprocess, sys
import netns.interface import netns.interface
# XXX: ideally this should be replaced by netlink communication # XXX: ideally this should be replaced by netlink communication
...@@ -25,7 +25,18 @@ def get_if_data(): ...@@ -25,7 +25,18 @@ def get_if_data():
continue continue
match = re.search(r'^(\d+):\s+(.*)', line) match = re.search(r'^(\d+):\s+(.*)', line)
idx = int(match.group(1)) idx = int(match.group(1))
i = netns.interface.interface.parse_ip(line) match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc \S+' +
r'.*link/\S+ ([0-9a-f:]+) brd ([0-9a-f:]+)', line)
flags = match.group(3).split(",")
i = netns.interface.interface(
index = match.group(1),
name = match.group(2),
up = "UP" in flags,
mtu = match.group(4),
lladdr = match.group(5),
arp = not ("NOARP" in flags),
broadcast = match.group(6),
multicast = "MULTICAST" in flags)
byidx[idx] = bynam[i.name] = i byidx[idx] = bynam[i.name] = i
return byidx, bynam return byidx, bynam
...@@ -146,9 +157,25 @@ def get_addr_data(): ...@@ -146,9 +157,25 @@ def get_addr_data():
if match.group(3): if match.group(3):
bynam[name] = byidx[idx] = [] bynam[name] = byidx[idx] = []
continue # link info continue # link info
bynam[name].append(netns.interface.address.parse_ip(match.group(4))) bynam[name].append(_parse_ip_addr(match.group(4)))
return byidx, bynam return byidx, bynam
def _parse_ip_addr(line):
match = re.search(r'^inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None:
return ipv4address(
address = match.group(1),
prefix_len = match.group(2),
broadcast = match.group(3))
match = re.search(r'^inet6 ([0-9a-f:]+)/(\d+)', line)
if match != None:
return ipv6address(
address = match.group(1),
prefix_len = match.group(2))
raise RuntimeError("Problems parsing ip command output")
def add_addr(iface, address): def add_addr(iface, address):
ifname = _get_if_name(iface) ifname = _get_if_name(iface)
addresses = get_addr_data()[1][ifname] addresses = get_addr_data()[1][ifname]
...@@ -194,32 +221,34 @@ def set_addr(iface, addresses, recover = True): ...@@ -194,32 +221,34 @@ def set_addr(iface, addresses, recover = True):
# Bridge handling # Bridge handling
def get_br_data(): def get_br_data():
ifdata = get_if_data() # brctl stinks too much; it is better to directly use sysfs, it is probably
ipcmd = subprocess.Popen(["brctl", "show"], stdout = subprocess.PIPE) # stable by now
ipdata = ipcmd.communicate()[0] def readval(fname):
assert ipcmd.wait() == 0 f = file(fname)
return f.readline().strip()
byidx = {} byidx = {}
bynam = {} bynam = {}
ports = {} ports = {}
last = None ifdata = get_if_data()
for line in ipdata.split("\n")[1:]: # skip header for i in ifdata[1]: # by name
if line == "": p = '/sys/class/net/%s/bridge/' % i
continue p2 = '/sys/class/net/%s/brif/' % i
if last: try:
match = re.search(r'^\s+(\S+)$', line) os.stat(p)
if match: except:
ports[last].append(ifdata[1][match.group(1)])
continue continue
params = dict(
stp = readval(p + 'stp_state'),
forward_delay = float(readval(p + 'forward_delay')) / 100,
hello_time = float(readval(p + 'hello_time')) / 100,
ageing_time = float(readval(p + 'ageing_time')) / 100,
max_age = float(readval(p + 'max_age')) / 100)
iface = ifdata[1][i]
bynam[i] = byidx[iface.index] = netns.interface.bridge.upgrade(
iface, **params)
ports[iface.index] = [ifdata[1][x].index for x in os.listdir(p2)]
match = re.search(r'^(\S+)\s+\S+\s+\S+\s*(\S+)?', line)
if not match:
raise RuntimeError("Invalid `brctl' command output")
name = match.group(1)
last = idx = ifdata[1][name].index
bynam[name] = byidx[idx] = ifdata[1][name]
ports[idx] = []
return byidx, bynam, ports return byidx, bynam, ports
def create_bridge(br): def create_bridge(br):
...@@ -242,6 +271,39 @@ def del_bridge(br): ...@@ -242,6 +271,39 @@ def del_bridge(br):
brname = _get_if_name(br) brname = _get_if_name(br)
_execute(["brctl", "delbr", brname]) _execute(["brctl", "delbr", brname])
def set_bridge(br, recover = True):
def saveval(fname, val):
f = file(fname, 'w')
f.write(str(val))
f.close()
def do_cmds(basename, cmds, orig_br):
for n, v in cmds:
try:
saveval(basename + n, v)
except:
if recover:
set_bridge(orig_br, recover = False) # rollback
set_if(orig_br, recover = False) # rollback
raise
orig_br = get_br_data()[1][_get_if_name(br)]
diff = br - orig_br # Only set what's needed
cmds = []
if diff.stp != None:
cmds.append(('stp_state', int(diff.stp)))
if diff.forward_delay != None:
cmds.append(('forward_delay', int(diff.forward_delay)))
if diff.hello_time != None:
cmds.append(('hello_time', int(diff.hello_time)))
if diff.ageing_time != None:
cmds.append(('ageing_time', int(diff.ageing_time)))
if diff.max_age != None:
cmds.append(('max_age', int(diff.max_age)))
set_if(diff)
do_cmds('/sys/class/net/%s/bridge/' % br.name, cmds, orig_br)
def add_bridge_port(br, iface): def add_bridge_port(br, iface):
ifname = _get_if_name(iface) ifname = _get_if_name(iface)
brname = _get_if_name(br) brname = _get_if_name(br)
......
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