Ticket #14: cross-connections between NS3 and PlanetLab through file descriptors...
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 4 May 2011 15:03:01 +0000 (17:03 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Wed, 4 May 2011 15:03:01 +0000 (17:03 +0200)
src/nepi/testbeds/netns/metadata_v01.py
src/nepi/testbeds/ns3/factories_metadata_v3_9_RC3.py
src/nepi/testbeds/ns3/metadata_v3_9_RC3.py
src/nepi/testbeds/planetlab/interfaces.py
src/nepi/testbeds/planetlab/metadata_v01.py
src/nepi/testbeds/planetlab/scripts/tun_connect.py
src/nepi/testbeds/planetlab/tunproto.py

index 60cc096..caa65c4 100644 (file)
@@ -29,7 +29,7 @@ def connect_fd(testbed_instance, tap_guid, cross_data):
     import passfd
     import socket
     tap = testbed_instance._elements[tap_guid]
-    address = cross_data["LinuxSocketAddress"]
+    address = cross_data["tun_addr"]
     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
     sock.connect(address)
     passfd.sendfd(sock, tap.fd, '0')
@@ -182,10 +182,16 @@ connector_types = dict({
                 "max": 1, 
                 "min": 0
             }),
-    "fd": dict({
-                "help": "Connector to a network interface that can receive a file descriptor", 
-                "name": "fd",
-                "max": 1, 
+    "->fd": dict({
+                "help": "File descriptor receptor for devices with file descriptors",
+                "name": "->fd",
+                "max": 1,
+                "min": 0
+            }),
+    "fd->": dict({
+                "help": "File descriptor provider for devices with file descriptors",
+                "name": "fd->",
+                "max": 1,
                 "min": 0
             }),
     "switch": dict({
@@ -218,8 +224,8 @@ connections = [
         "can_cross": False
     }),
     dict({
-        "from": (TESTBED_ID, TAPIFACE, "fd"),
-        "to":   (NS3_TESTBED_ID, FDNETDEV, "fd"),
+        "from": (TESTBED_ID, TAPIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
         "compl_code": connect_fd,
         "can_cross": True
     }),
@@ -360,7 +366,7 @@ factories_info = dict({
             "configure_function": configure_device,
             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
                 "multicast", "broadcast", "arp"],
-            "connector_types": ["node", "fd"]
+            "connector_types": ["node", "fd->"]
         }),
     NODEIFACE: dict({
             "allow_addresses": True,
index be21fc9..5b8a79f 100644 (file)
@@ -625,10 +625,11 @@ factories_info = dict({
         "create_function": create_element,
         "configure_function": configure_device,
         "help": "Network interface associated to a file descriptor",
-        "connector_types": ["node", "fd"],
+        "connector_types": ["node", "->fd"],
         "allow_addresses": True,
         "box_attributes": ["Address", 
-            "LinuxSocketAddress"],
+            "LinuxSocketAddress",
+            "tun_proto", "tun_addr", "tun_port", "tun_key"],
         "traces": ["fdpcap"]
     }),
      "ns3::CsmaNetDevice": dict({
index 9ce7f03..03bc41b 100644 (file)
@@ -96,6 +96,12 @@ def connect_fd(testbed_instance, fdnd_guid, cross_data):
     # to see how the address should be decoded
     address = endpoint.replace(":", "").decode('hex')[2:]
     testbed_instance.set(fdnd_guid, "LinuxSocketAddress", address)
+    
+    # Set tun standard contract attributes
+    testbed_instance.set(fdnd_guid, "tun_addr", address)
+    testbed_instance.set(fdnd_guid, "tun_proto", "fd")
+    testbed_instance.set(fdnd_guid, "tun_port", 0)
+    testbed_instance.set(fdnd_guid, "tun_key", "\xff"*32) # unimportant, fds aren't encrypted
 
 ### Connector information ###
 
@@ -154,9 +160,15 @@ connector_types = dict({
                 "max": 1,
                 "min": 0
             }),
-    "fd": dict({
-                "help": "Connector to interconnect devices with file descriptors",
-                "name": "fd",
+    "->fd": dict({
+                "help": "File descriptor receptor for devices with file descriptors",
+                "name": "->fd",
+                "max": 1,
+                "min": 0
+            }),
+    "fd->": dict({
+                "help": "File descriptor provider for devices with file descriptors",
+                "name": "fd->",
                 "max": 1,
                 "min": 0
             }),
@@ -482,8 +494,8 @@ connections = [
         "can_cross": False
     }),
     dict({
-        "from": ( "ns3", "ns3::FileDescriptorNetDevice", "fd" ),
-        "to":   ( "netns", "TapNodeInterface", "fd" ),
+        "from": ( "ns3", "ns3::FileDescriptorNetDevice", "->fd" ),
+        "to":   ( None, None, "fd->" ),
         "init_code": connect_fd,
         "can_cross": True
     }),
index 014ccee..30895b8 100644 (file)
@@ -168,6 +168,12 @@ class TunIface(object):
         if not self.address or not self.netprefix or not self.netmask:
             raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
     
+    def _impl_instance(self, home_path, listening):
+        impl = self._PROTO_MAP[self.peer_proto](
+            self, self.peer_iface, home_path, self.tun_key, listening)
+        impl.port = self.tun_port
+        return impl
+    
     def prepare(self, home_path, listening):
         if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
             # Ad-hoc peer_iface
@@ -177,9 +183,7 @@ class TunIface(object):
                 self.peer_port)
         if self.peer_iface:
             if not self.peer_proto_impl:
-                self.peer_proto_impl = self._PROTO_MAP[self.peer_proto](
-                    self, self.peer_iface, home_path, self.tun_key, listening)
-                self.peer_proto_impl.port = self.tun_port
+                self.peer_proto_impl = self._impl_instance(home_path, listening)
             self.peer_proto_impl.prepare()
     
     def setup(self):
index 81718da..62e39fa 100644 (file)
@@ -96,7 +96,7 @@ def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
         raise RuntimeError, "Use of TUN interfaces requires emulation"
     iface.node = node
     node.required_vsys.update(('fd_tuntap', 'vif_up'))
-    node.required_packages.add('python-crypto')
+    node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc'))
 
 def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
     iface = testbed_instance._elements[iface_guid]
@@ -118,8 +118,18 @@ def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_i
     preconfigure_tuniface(testbed_instance, iface_guid)
 
 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
+    # refresh (refreshable) attributes for second-phase
+    iface = testbed_instance._elements[iface_guid]
+    iface.peer_addr = peer_iface_data.get("tun_addr")
+    iface.peer_proto = peer_iface_data.get("tun_proto")
+    iface.peer_port = peer_iface_data.get("tun_port")
+    
     postconfigure_tuniface(testbed_instance, iface_guid)
 
+def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
+    crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
+    crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
+
 def connect_dep(testbed_instance, node_guid, app_guid):
     node = testbed_instance._elements[node_guid]
     app = testbed_instance._elements[app_guid]
@@ -437,6 +447,12 @@ connector_types = dict({
                 "max": 1, 
                 "min": 0
             }),
+    "fd->": dict({
+                "help": "TUN device file descriptor provider", 
+                "name": "fd->",
+                "max": 1, 
+                "min": 0
+            }),
    })
 
 connections = [
@@ -532,6 +548,12 @@ connections = [
         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
         "can_cross": True
     }),
+    dict({
+        "from": (TESTBED_ID, TUNIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
+        "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
+        "can_cross": True
+    }),
     dict({
         "from": (TESTBED_ID, TAPIFACE, "tcp"),
         "to":   (None, None, "tcp"),
@@ -546,6 +568,12 @@ connections = [
         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
         "can_cross": True
     }),
+    dict({
+        "from": (TESTBED_ID, TAPIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
+        "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
+        "can_cross": True
+    }),
 ]
 
 attributes = dict({
@@ -910,7 +938,7 @@ factories_info = dict({
                 "tun_proto", "tun_addr", "tun_port", "tun_key"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp"]
+            "connector_types": ["node","udp","tcp","fd->"]
         }),
     TAPIFACE: dict({
             "allow_addresses": True,
@@ -925,7 +953,7 @@ factories_info = dict({
                 "tun_proto", "tun_addr", "tun_port"
             ],
             "traces": ["packets"],
-            "connector_types": ["node","udp","tcp"]
+            "connector_types": ["node","udp","tcp","fd->"]
         }),
     APPLICATION: dict({
             "help": "Generic executable command line application",
index 415423c..19b0897 100644 (file)
@@ -12,6 +12,7 @@ import threading
 import subprocess
 import re
 import functools
+import time
 
 tun_name = 'tun0'
 tun_path = '/dev/net/tun'
@@ -33,6 +34,13 @@ parser.add_option(
     "-p", "--port", dest="port", metavar="PORT", type="int",
     default = 15000,
     help = "Peering TCP port to connect or listen to.")
+parser.add_option(
+    "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
+    default = None,
+    help = "Path to a unix-domain socket to pass the TUN file descriptor to. "
+           "If given, all other connectivity options are ignored, tun_connect will "
+           "simply wait to be killed after passing the file descriptor, and it will be "
+           "the receiver's responsability to handle the tunneling.")
 
 parser.add_option(
     "-m", "--mode", dest="mode", metavar="MODE",
@@ -526,7 +534,19 @@ except:
 
 
 try:
-    if options.udp:
+    if options.pass_fd:
+        # send FD to whoever wants it
+        import passfd
+        
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        sock.connect(options.pass_fd)
+        passfd.sendfd(sock, tun.fileno(), '0')
+        
+        # just wait forever
+        def tun_fwd(tun, remote):
+            while True:
+                time.sleep(1)
+    elif options.udp:
         # connect to remote endpoint
         if remaining_args and not remaining_args[0].startswith('-'):
             print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
index 03293f2..5ba9e1c 100644 (file)
@@ -80,7 +80,22 @@ class TunProtoBase(object):
         if proc.wait():
             raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (source, out,err,)
 
-        cmd = "cd %s && gcc -shared tunalloc.c -o tunalloc.so" % (server.shell_escape(self.home_path),)
+        cmd = ( (
+            "cd %(home)s && gcc -shared tunalloc.c -o tunalloc.so"
+            + ( " && "
+                "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
+                "mkdir -p python-passfd && "
+                "cd python-passfd && "
+                "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
+                "python setup.py build && "
+                "python setup.py install --install-lib .. "
+                
+                if local.tun_proto == "fd" else ""
+            ) )
+        % {
+            'home' : server.shell_escape(self.home_path),
+            'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
+        } )
         (out,err),proc = server.popen_ssh_command(
             cmd,
             host = local.node.hostname,
@@ -122,11 +137,17 @@ class TunProtoBase(object):
             raise RuntimeError, "Misconfigured TUN: %s" % (local,)
         
         args = ["python", "tun_connect.py", 
-            "-m", str(self.mode),
-            "-p", str(local_port if listen else peer_port),
-            "-A", str(local_addr),
-            "-M", str(local_mask),
-            "-k", str(self.key)]
+            "-m", str(self.mode)]
+        
+        if check_proto == 'fd':
+            args.extend([
+                "--pass-fd", str(peer_addr)])
+        else:
+            args.extend([
+                "-p", str(local_port if listen else peer_port),
+                "-A", str(local_addr),
+                "-M", str(local_mask),
+                "-k", str(self.key)])
         
         if local_snat:
             args.append("-S")
@@ -319,6 +340,20 @@ class TunProtoUDP(TunProtoBase):
     def shutdown(self):
         self.kill()
 
+class TunProtoFD(TunProtoBase):
+    def __init__(self, local, peer, home_path, key, listening):
+        super(TunProtoFD, self).__init__(local, peer, home_path, key)
+        self.listening = listening
+    
+    def prepare(self):
+        pass
+    
+    def setup(self):
+        self.async_launch('fd', False)
+    
+    def shutdown(self):
+        self.kill()
+
 class TunProtoTCP(TunProtoBase):
     def __init__(self, local, peer, home_path, key, listening):
         super(TunProtoTCP, self).__init__(local, peer, home_path, key)
@@ -355,6 +390,11 @@ class TapProtoTCP(TunProtoTCP):
         super(TapProtoTCP, self).__init__(local, peer, home_path, key, listening)
         self.mode = 'pl-tap'
 
+class TapProtoFD(TunProtoFD):
+    def __init__(self, local, peer, home_path, key, listening):
+        super(TapProtoUDP, self).__init__(local, peer, home_path, key, listening)
+        self.mode = 'pl-tap'
+
 
 
 TUN_PROTO_MAP = {