Merge TCP handshake stuff
[nepi.git] / src / nepi / testbeds / planetlab / interfaces.py
index c5e23ac..56720a3 100644 (file)
@@ -8,6 +8,11 @@ import plcapi
 import subprocess
 import os
 import os.path
+import random
+import ipaddr
+import functools
+
+import tunproto
 
 class NodeIface(object):
     def __init__(self, api=None):
@@ -38,6 +43,8 @@ class NodeIface(object):
             self.address, self.netmask,
             self.lladdr,
         )
+    
+    __repr__ = __str__
 
     def add_address(self, address, netprefix, broadcast):
         raise RuntimeError, "Cannot add explicit addresses to public interface"
@@ -81,7 +88,31 @@ class NodeIface(object):
             raise RuntimeError, "All external interface devices must be connected to the Internet"
     
 
+class _CrossIface(object):
+    def __init__(self, proto, addr, port, cipher):
+        self.tun_proto = proto
+        self.tun_addr = addr
+        self.tun_port = port
+        self.tun_cipher = cipher
+        
+        # Cannot access cross peers
+        self.peer_proto_impl = None
+    
+    def __str__(self):
+        return "%s%r" % (
+            self.__class__.__name__,
+            ( self.tun_proto,
+              self.tun_addr,
+              self.tun_port,
+              self.tun_cipher ) 
+        )
+    
+    __repr__ = __str__
+
 class TunIface(object):
+    _PROTO_MAP = tunproto.TUN_PROTO_MAP
+    _KIND = 'TUN'
+
     def __init__(self, api=None):
         if not api:
             api = plcapi.PLCAPI()
@@ -93,34 +124,187 @@ class TunIface(object):
         self.netmask = None
         
         self.up = None
-        self.device_name = None
         self.mtu = None
         self.snat = False
+        self.txqueuelen = None
+        self.pointopoint = None
+        self.multicast = False
+        self.bwlimit = None
+        
+        # Enabled traces
+        self.capture = False
 
         # These get initialized when the iface is connected to its node
         self.node = None
+        
+        # These get initialized when the iface is connected to any filter
+        self.filter_module = None
+        self.multicast_forwarder = None
+        
+        # These get initialized when the iface is configured
+        self.external_iface = None
+        
+        # These get initialized when the iface is configured
+        # They're part of the TUN standard attribute set
+        self.tun_port = None
+        self.tun_addr = None
+        self.tun_cipher = "AES"
+        
+        # These get initialized when the iface is connected to its peer
+        self.peer_iface = None
+        self.peer_proto = None
+        self.peer_addr = None
+        self.peer_port = None
+        self.peer_proto_impl = None
+        self._delay_recover = False
+
+        # same as peer proto, but for execute-time standard attribute lookups
+        self.tun_proto = None 
+        
+        
+        # Generate an initial random cryptographic key to use for tunnelling
+        # Upon connection, both endpoints will agree on a common one based on
+        # this one.
+        self.tun_key = ( ''.join(map(chr, [ 
+                    r.getrandbits(8) 
+                    for i in xrange(32) 
+                    for r in (random.SystemRandom(),) ])
+                ).encode("base64").strip() )        
+        
 
     def __str__(self):
-        return "%s<ip:%s/%s %s%s>" % (
+        return "%s<ip:%s/%s %s%s%s>" % (
             self.__class__.__name__,
-            self.address, self.netmask,
+            self.address, self.netprefix,
             " up" if self.up else " down",
             " snat" if self.snat else "",
+            (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
         )
+    
+    __repr__ = __str__
+    
+    @property
+    def if_name(self):
+        if self.peer_proto_impl:
+            return self.peer_proto_impl.if_name
 
+    def routes_here(self, route):
+        """
+        Returns True if the route should be attached to this interface
+        (ie, it references a gateway in this interface's network segment)
+        """
+        if self.address and self.netprefix:
+            addr, prefix = self.address, self.netprefix
+            pointopoint = self.pointopoint
+            if not pointopoint:
+                pointopoint = self.peer_iface.address
+            
+            if pointopoint:
+                prefix = 32
+                
+            dest, destprefix, nexthop, metric = route
+            
+            myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
+            gwIp = ipaddr.IPNetwork(nexthop)
+            
+            if pointopoint:
+                peerIp = ipaddr.IPNetwork(pointopoint)
+                
+                if gwIp == peerIp:
+                    return True
+            else:
+                if gwIp in myNet:
+                    return True
+        return False
+    
     def add_address(self, address, netprefix, broadcast):
         if (self.address or self.netprefix or self.netmask) is not None:
-            raise RuntimeError, "Cannot add more than one address to TUN interfaces"
+            raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
         if broadcast:
-            raise ValueError, "TUN interfaces cannot broadcast in PlanetLab"
+            raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
         
         self.address = address
         self.netprefix = netprefix
-        self.netmask = ipaddr2.ipv4_dot2mask(netprefix)
-
+        self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
+    
     def validate(self):
-        pass
+        if not self.node:
+            raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
+        if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
+            raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
+        if not self.address or not self.netprefix or not self.netmask:
+            raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
+        if self.filter_module and self.peer_proto not in ('udp','tcp',None):
+            raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (self,)
+        if self.tun_cipher != 'PLAIN' and self.peer_proto not in ('udp','tcp',None):
+            raise RuntimeError, "Miscofnigured TUN: %s - ciphered tunnels only work with udp or tcp links" % (self,)
+    
+    def _impl_instance(self, home_path):
+        impl = self._PROTO_MAP[self.peer_proto](
+            self, self.peer_iface, home_path, self.tun_key)
+        impl.port = self.tun_port
+        impl.cross_slice = not self.peer_iface or isinstance(self.peer_iface, _CrossIface)
+        return impl
+    
+    def recover(self):
+        if self.peer_proto:
+            self.peer_proto_impl = self._impl_instance(
+                self._home_path,
+                False) # no way to know, no need to know
+            self.peer_proto_impl.recover()
+        else:
+            self._delay_recover = True
+    
+    def prepare(self, home_path):
+        if not self.peer_iface and (self.peer_proto and self.peer_addr and self.peer_port):
+            # Ad-hoc peer_iface
+            self.peer_iface = _CrossIface(
+                self.peer_proto,
+                self.peer_addr,
+                self.peer_port,
+                self.peer_cipher)
+        if self.peer_iface:
+            if not self.peer_proto_impl:
+                self.peer_proto_impl = self._impl_instance(home_path)
+            if self._delay_recover:
+                self.peer_proto_impl.recover()
+    
+    def launch(self):
+        if self.peer_proto_impl:
+            self.peer_proto_impl.launch()
     
+    def cleanup(self):
+        if self.peer_proto_impl:
+            self.peer_proto_impl.shutdown()
+
+    def destroy(self):
+        if self.peer_proto_impl:
+            self.peer_proto_impl.destroy()
+            self.peer_proto_impl = None
+
+    def wait(self):
+        if self.peer_proto_impl:
+            self.peer_proto_impl.wait()
+
+    def sync_trace(self, local_dir, whichtrace, tracemap = None):
+        if self.peer_proto_impl:
+            return self.peer_proto_impl.sync_trace(local_dir, whichtrace,
+                    tracemap)
+        else:
+            return None
+
+    def remote_trace_path(self, whichtrace, tracemap = None):
+        if self.peer_proto_impl:
+            return self.peer_proto_impl.remote_trace_path(whichtrace, tracemap)
+        else:
+            return None
+
+    def remote_trace_name(self, whichtrace):
+        return whichtrace
+
+class TapIface(TunIface):
+    _PROTO_MAP = tunproto.TAP_PROTO_MAP
+    _KIND = 'TAP'
 
 # Yep, it does nothing - yet
 class Internet(object):
@@ -189,6 +373,10 @@ class NetPipe(object):
         options = ' '.join(options)
         
         return (scope,options)
+    
+    def recover(self):
+        # Rules are safe on their nodes
+        self.configured = True
 
     def configure(self):
         # set up rule
@@ -287,3 +475,94 @@ class NetPipe(object):
         
         return local_path
     
+class TunFilter(object):
+    _TRACEMAP = {
+        # tracename : (remotename, localname)
+    }
+    
+    def __init__(self, api=None):
+        if not api:
+            api = plcapi.PLCAPI()
+        self._api = api
+        
+        # Attributes
+        self.module = None
+        self.args = None
+
+        # These get initialised when the filter is connected
+        self.peer_guid = None
+        self.peer_proto = None
+        self.iface_guid = None
+        self.peer = None
+        self.iface = None
+    
+    def _get(what, self):
+        wref = self.iface
+        if wref:
+            wref = wref()
+        if wref:
+            return getattr(wref, what)
+        else:
+            return None
+
+    def _set(what, self, val):
+        wref = self.iface
+        if wref:
+            wref = wref()
+        if wref:
+            setattr(wref, what, val)
+    
+    tun_proto = property(
+        functools.partial(_get, 'tun_proto'),
+        functools.partial(_set, 'tun_proto') )
+    tun_addr = property(
+        functools.partial(_get, 'tun_addr'),
+        functools.partial(_set, 'tun_addr') )
+    tun_port = property(
+        functools.partial(_get, 'tun_port'),
+        functools.partial(_set, 'tun_port') )
+    tun_key = property(
+        functools.partial(_get, 'tun_key'),
+        functools.partial(_set, 'tun_key') )
+    tun_cipher = property(
+        functools.partial(_get, 'tun_cipher'),
+        functools.partial(_set, 'tun_cipher') )
+    
+    del _get
+    del _set
+
+    def remote_trace_path(self, whichtrace):
+        iface = self.iface()
+        if iface is not None:
+            return iface.remote_trace_path(whichtrace, self._TRACEMAP)
+        return None
+
+    def remote_trace_name(self, whichtrace):
+        iface = self.iface()
+        if iface is not None:
+            return iface.remote_trace_name(whichtrace, self._TRACEMAP)
+        return None
+
+    def sync_trace(self, local_dir, whichtrace):
+        iface = self.iface()
+        if iface is not None:
+            return iface.sync_trace(local_dir, whichtrace, self._TRACEMAP)
+        return None
+
+class ClassQueueFilter(TunFilter):
+    _TRACEMAP = {
+        # tracename : (remotename, localname)
+        'dropped_stats' : ('dropped_stats', 'dropped_stats')
+    }
+    
+    def __init__(self, api=None):
+        super(ClassQueueFilter, self).__init__(api)
+        # Attributes
+        self.module = "classqueue.py"
+
+class ToSQueueFilter(TunFilter):
+    def __init__(self, api=None):
+        super(ToSQueueFilter, self).__init__(api)
+        # Attributes
+        self.module = "tosqueue.py"
+