Commit a6425bf6 authored by Martín Ferrari's avatar Martín Ferrari

Interface class and friends, almost ready.

parent 374f505c
# vim:ts=4:sw=4:et:ai:sts=4 # vim:ts=4:sw=4:et:ai:sts=4
import re, socket import os, re, socket, weakref
import netns.iproute
__all__ = ['interface', 'address', 'ipv6address', 'ipv4address'] __all__ = ['NodeInterface', 'P2PInterface', 'ExternalInterface']
# helpers # FIXME: should nodes register interfaces?
class _Interface(object):
"""Just a base class for the *Interface classes: assign names and handle
destruction."""
_nextid = 0
@staticmethod
def _gen_next_id():
n = _Interface._nextid
_Interface._nextid += 1
return n
@staticmethod
def _gen_if_name():
n = _Interface._gen_next_id()
# Max 15 chars
return "NETNSif-%.4x%.3x" % (os.getpid(), n)
class _NSInterface(_Interface):
"""Add user-facing methods for interfaces that go into a netns."""
def destroy(self):
try:
# no need to check _ns_if, exceptions are ignored anyways
self._slave.del_if(self._ns_if)
except:
# Maybe it already went away, or the slave died. Anyway, better
# ignore the error
pass
def __del__(self):
self.destroy()
@property
def index(self):
return self._ns_if
# some black magic to automatically get/set interface attributes
def __getattr__(self, name):
if (name not in interface.changeable_attributes):
raise AttributeError("'%s' object has no attribute '%s'" %
(self.__class__.__name__, name))
# I can use attributes now, as long as they are not in
# changeable_attributes
iface = self._slave.get_if_data(self._ns_if)
return getattr(iface, name)
def __setattr__(self, name, value):
if (name not in interface.changeable_attributes):
super(_Interface, self).__setattr__(name, value)
return
iface = interface(index = self._ns_if)
setattr(iface, name, value)
return self._slave.set_if(iface)
def add_v4_address(self, address, prefix_len, broadcast = None):
pass
def add_v6_address(self, address, prefix_len):
pass
class NodeInterface(_NSInterface):
"""Class to create and handle a virtual interface inside a name space, it
can be connected to a Link object with emulation of link
characteristics."""
def __init__(self, node):
"""Create a new interface. `node' is the name space in which this
interface should be put."""
if1 = interface(name = self._gen_if_name())
if2 = interface(name = self._gen_if_name())
ctl, ns = netns.iproute.create_if_pair(if1, if2)
try:
netns.iproute.change_netns(ns, node.pid)
except:
netns.iproute.del_if(ctl)
# the other interface should go away automatically
raise
self._ctl_if = ctl.index
self._ns_if = ns.index
self._slave = node._slave
node._add_interface(self)
@property
def control_index(self):
return self._ctl_if
class P2PInterface(_NSInterface):
"""Class to create and handle point-to-point interfaces between name
spaces, without using Link objects. Those do not allow any kind of traffic
shaping.
As two interfaces need to be created, instead of using the class
constructor, use the P2PInterface.create_pair() static method."""
@staticmethod
def create_pair(node1, node2):
"""Create and return a pair of connected P2PInterface objects, assigned
to name spaces represented by `node1' and `node2'."""
if1 = interface(name = P2PInterface._gen_if_name())
if2 = interface(name = P2PInterface._gen_if_name())
pair = netns.iproute.create_if_pair(if1, if2)
try:
netns.iproute.change_netns(pair[0], node1.pid)
netns.iproute.change_netns(pair[1], node2.pid)
except:
netns.iproute.del_if(pair[0])
# the other interface should go away automatically
raise
o1 = P2PInterface.__new__(P2PInterface)
o1._slave = node1._slave
o1._ns_if = pair[0].index
node1._add_interface(o1)
o2 = P2PInterface.__new__(P2PInterface)
o2._slave = node2._slave
o2._ns_if = pair[1].index
node2._add_interface(o2)
return o1, o2
def __init__(self):
"Not to be called directly. Use P2PInterface.create_pair()"
raise RuntimeError(P2PInterface.__init__.__doc__)
class ExternalInterface(_Interface):
"""Class to handle already existing interfaces. This kind of interfaces can
only be connected to Link objects and not assigned to a name space.
On destruction, the code will try to restore the interface to the state it
was in before being imported into netns."""
def __init__(self, iface):
iface = netns.iproute.get_if(iface)
self._ctl_if = iface.index
self._original_state = iface
def destroy(self): # override: restore as much as possible
try:
netns.iproute.set_if(self._original_state)
except:
pass
@property
def control_index(self):
return self._ctl_if
# don't look after this :-)
# helpers
def _any_to_bool(any): def _any_to_bool(any):
if isinstance(any, bool): if isinstance(any, bool):
return any return any
...@@ -33,9 +176,14 @@ def _make_setter(attr, conv = lambda x: x): ...@@ -33,9 +176,14 @@ def _make_setter(attr, conv = lambda x: x):
setattr(self, attr, conv(value)) setattr(self, attr, conv(value))
return setter return setter
# classes for internal use
class interface(object): class interface(object):
"""Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods."""
@classmethod @classmethod
def parse_ip(cls, line): 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+' + match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc \S+' +
r'.*link/\S+ ([0-9a-f:]+) brd ([0-9a-f:]+)', line) r'.*link/\S+ ([0-9a-f:]+) brd ([0-9a-f:]+)', line)
flags = match.group(3).split(",") flags = match.group(3).split(",")
...@@ -49,6 +197,10 @@ class interface(object): ...@@ -49,6 +197,10 @@ class interface(object):
broadcast = match.group(6), broadcast = match.group(6),
multicast = "MULTICAST" in flags) multicast = "MULTICAST" in flags)
# information for other parts of the code
changeable_attributes = ["name", "mtu", "lladdr", "broadcast", "up",
"multicast", "arp"]
index = property(_make_getter("_index"), _make_setter("_index", int)) index = property(_make_getter("_index"), _make_setter("_index", int))
up = property(_make_getter("_up"), _make_setter("_up", _any_to_bool)) up = property(_make_getter("_up"), _make_setter("_up", _any_to_bool))
mtu = property(_make_getter("_mtu"), _make_setter("_mtu", int)) mtu = property(_make_getter("_mtu"), _make_setter("_mtu", int))
...@@ -90,8 +242,14 @@ class interface(object): ...@@ -90,8 +242,14 @@ class interface(object):
multicast, arp) multicast, arp)
class address(object): class address(object):
"""Class for internal use. It is mostly a data container used to easily
pass information around; with some convenience methods. __eq__ and __hash__
are defined just to be able to easily find duplicated addresses."""
@classmethod @classmethod
def parse_ip(cls, line): 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) match = re.search(r'^inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None: if match != None:
return ipv4address( return ipv4address(
......
...@@ -4,13 +4,15 @@ ...@@ -4,13 +4,15 @@
import os, socket, sys, traceback, unshare, weakref import os, socket, sys, traceback, unshare, weakref
import netns.protocol, netns.subprocess_ import netns.protocol, netns.subprocess_
__all__ = ['Node', 'get_nodes']
class Node(object): class Node(object):
_nodes = weakref.WeakValueDictionary() _nodes = weakref.WeakValueDictionary()
_nextnode = 0 _nextnode = 0
@classmethod @staticmethod
def get_nodes(cls): def get_nodes():
s = sorted(Node._nodes.items(), key = lambda x: x[0]) s = sorted(Node._nodes.items(), key = lambda x: x[0])
return [ x[1] for x in s ] return [x[1] for x in s]
def __init__(self, debug = False, nonetns = False): def __init__(self, debug = False, nonetns = False):
"""Create a new node in the emulation. Implemented as a separate """Create a new node in the emulation. Implemented as a separate
...@@ -23,6 +25,8 @@ class Node(object): ...@@ -23,6 +25,8 @@ class Node(object):
self._pid = pid self._pid = pid
self._slave = netns.protocol.Client(fd, fd, debug) self._slave = netns.protocol.Client(fd, fd, debug)
self._processes = weakref.WeakValueDictionary() self._processes = weakref.WeakValueDictionary()
self._interfaces = weakref.WeakValueDictionary()
Node._nodes[Node._nextnode] = self Node._nodes[Node._nextnode] = self
Node._nextnode += 1 Node._nextnode += 1
...@@ -33,10 +37,17 @@ class Node(object): ...@@ -33,10 +37,17 @@ class Node(object):
for p in self._processes.values(): for p in self._processes.values():
p.destroy() p.destroy()
del self._processes del self._processes
for i in self._interfaces.values():
i.destroy()
del self._interfaces
del self._pid del self._pid
self._slave.shutdown() self._slave.shutdown()
del self._slave del self._slave
@property
def pid(self):
return self._pid
# Subprocesses # Subprocesses
def _add_subprocess(self, subprocess): def _add_subprocess(self, subprocess):
self._processes[subprocess.pid] = subprocess self._processes[subprocess.pid] = subprocess
...@@ -52,12 +63,20 @@ class Node(object): ...@@ -52,12 +63,20 @@ class Node(object):
def backticks_raise(self, *kargs, **kwargs): def backticks_raise(self, *kargs, **kwargs):
return netns.subprocess_.backticks_raise(self, *kargs, **kwargs) return netns.subprocess_.backticks_raise(self, *kargs, **kwargs)
@property # Interfaces
def pid(self): def _add_interface(self, interface):
return self._pid self._interfaces[interface.index] = interface
def add_if(self, **kwargs):
i = netns.interface.NodeInterface(self)
for k, v in kwargs.items():
setattr(i, k, v)
return i
def get_interfaces(self):
# FIXME: loopback et al
s = sorted(self._interfaces.items(), key = lambda x: x[0])
return [x[1] for x in s]
def add_if(self, mac_address = None, mtu = None):
return Interface(mac_address, mtu)
def add_route(self, prefix, prefix_len, nexthop = None, interface = None): def add_route(self, prefix, prefix_len, nexthop = None, interface = None):
assert nexthop or interface assert nexthop or interface
def add_default_route(self, nexthop, interface = None): def add_default_route(self, nexthop, interface = None):
...@@ -65,17 +84,6 @@ class Node(object): ...@@ -65,17 +84,6 @@ class Node(object):
def get_routes(self): def get_routes(self):
return set() return set()
class Interface(object):
def __init__(self, mac_address = None, mtu = None):
self.name = None
self.mac_address = mac_address
self.mtu = mtu
self.valid = True
def add_v4_address(self, address, prefix_len, broadcast = None):
pass
def add_v6_address(self, address, prefix_len):
pass
# Handle the creation of the child; parent gets (fd, pid), child creates and # Handle the creation of the child; parent gets (fd, pid), child creates and
# runs a Server(); never returns. # runs a Server(); never returns.
# Requires CAP_SYS_ADMIN privileges to run. # Requires CAP_SYS_ADMIN privileges to run.
...@@ -111,3 +119,5 @@ def _start_child(debug, nonetns): ...@@ -111,3 +119,5 @@ def _start_child(debug, nonetns):
os._exit(0) os._exit(0)
# NOTREACHED # NOTREACHED
get_nodes = Node.get_nodes
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