merge
[nepi.git] / src / nepi / testbeds / netns / metadata_v01.py
index 7b9cecb..2d88b02 100644 (file)
@@ -8,33 +8,100 @@ from nepi.util import validation
 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
         STATUS_FINISHED
 
+from nepi.util.tunchannel_impl import \
+    preconfigure_tunchannel, postconfigure_tunchannel, \
+    wait_tunchannel, create_tunchannel, \
+    crossconnect_tunchannel_peer_init, \
+    crossconnect_tunchannel_peer_compl
+
+import functools
+
 NODE = "Node"
 P2PIFACE = "P2PNodeInterface"
 TAPIFACE = "TapNodeInterface"
 NODEIFACE = "NodeInterface"
 SWITCH = "Switch"
 APPLICATION = "Application"
+TUNCHANNEL = "TunChannel"
 
 NS3_TESTBED_ID = "ns3"
 FDNETDEV = "ns3::FileDescriptorNetDevice"
 
+def _follow_trace(testbed_instance, guid, trace_id, filename):
+    filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
+    trace = open(filepath, "wb")
+    testbed_instance.follow_trace(guid, trace_id, trace, filename)
+    return trace
+
 ### Connection functions ####
 
-def connect_switch(testbed_instance, switch, interface):
+def connect_switch(testbed_instance, switch_guid, interface_guid):
+    switch = testbed_instance._elements[switch_guid]
+    interface = testbed_instance._elements[interface_guid]
     switch.connect(interface)
    
-#XXX: This connection function cannot be use to transfer a file descriptor
-# to a remote tap device
-def connect_fd_local(testbed_instance, tap, fdnd):
+def connect_fd(testbed_instance, tap_guid, cross_data):
     import passfd
     import socket
-    fd = tap.file_descriptor
-    address = fdnd.socket_address
+    tap = testbed_instance._elements[tap_guid]
+    address = cross_data["tun_addr"]
     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
     sock.connect(address)
-    passfd.sendfd(sock, fd, '0')
+    passfd.sendfd(sock, tap.fd, '0')
     # TODO: after succesful transfer, the tap device should close the fd
 
+def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid):
+    tap = testbed_instance._elements[tap_guid]
+    chan = testbed_instance._elements[chan_guid]
+
+    # Create a file object for the tap's interface device 
+    # and send it to the channel. It should comply with all the
+    # requirements for the channel's tun_socket.
+    import os
+    chan.tun_socket = os.fdopen(tap.fd)
+    
+    # Set the channel to ethernet mode (it's a tap)
+    chan.ethernet_mode = True
+    
+    # Check to see if the device uses PI headers
+    # It's normally so
+    with_pi = True
+    try:
+        import fcntl
+        import struct
+        TUNGETIFF = 0x800454d2
+        IFF_NO_PI = 0x00001000
+        struct_ifreq = "x"*16+"H"+"x"*22
+        flags = struct.unpack(struct_ifreq,
+            fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )
+        with_pi = (0 == (flags & IFF_NO_PI))
+    except:
+        # maybe the kernel doesn't support the IOCTL,
+        # in which case, we assume it uses PI headers (as is usual)
+        pass
+    chan.with_pi = with_pi
+
+### Trace functions ###
+
+def nodepcap_trace(testbed_instance, guid, trace_id):
+    node = testbed_instance._elements[guid]
+    parameters = testbed_instance._get_parameters(guid)
+    filename = "%d-pcap.stdout" % guid
+    stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
+    filename = "%d-pcap.stderr" % guid
+    stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
+    filename = "%d-node.pcap" % guid
+    filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
+    command = "tcpdump -i 'any' -w %s" % filepath
+    user = "root"
+    trace = node.Popen(command, shell = True, stdout = stdout, 
+            stderr = stderr, user = user)
+    testbed_instance.follow_trace(guid, trace_id, trace, filename)
+
+trace_functions = dict({
+    "pcap": nodepcap_trace,
+    })
+
 ### Creation functions ###
 
 def create_node(testbed_instance, guid):
@@ -102,18 +169,17 @@ def create_application(testbed_instance, guid):
 def start_application(testbed_instance, guid):
     parameters = testbed_instance._get_parameters(guid)
     traces = testbed_instance._get_traces(guid)
-    user = parameters["user"]
     command = parameters["command"]
+    user = None
+    if "user" in parameters:
+        user = parameters["user"]
     stdout = stderr = None
     if "stdout" in traces:
-        filename = testbed_instance.trace_filename(guid, "stdout")
-        stdout = open(filename, "wb")
-        testbed_instance.follow_trace("stdout", stdout)
+        filename = "%d-stdout.trace" % guid
+        stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
     if "stderr" in traces:
-        filename = testbed_instance.trace_filename(guid, "stderr")
-        stderr = open(filename, "wb")
-        testbed_instance.follow_trace("stderr", stderr)
-
+        filename = "%d-stderr.trace" % guid
+        stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
     node_guid = testbed_instance.get_connected(guid, "node", "apps")
     if len(node_guid) == 0:
         raise RuntimeError("Can't instantiate interface %d outside netns \
@@ -123,6 +189,11 @@ def start_application(testbed_instance, guid):
             stderr = stderr, user = user)
     testbed_instance.elements[guid] = element
 
+def stop_application(testbed_instance, guid):
+    #app = testbed_instance.elements[guid]
+    #app.signal()
+    pass
+
 ### Status functions ###
 
 def status_application(testbed_instance, guid):
@@ -135,7 +206,16 @@ def status_application(testbed_instance, guid):
 
 ### Configure functions ###
 
+def configure_traces(testbed_instance, guid):
+    traces = testbed_instance._get_traces(guid)
+    for trace_id in traces:
+        if trace_id not in trace_functions:
+            continue
+        trace_func = trace_functions[trace_id]
+        trace_func(testbed_instance, guid, trace_id)
+
 def configure_device(testbed_instance, guid):
+    configure_traces(testbed_instance, guid)
     element = testbed_instance._elements[guid]
     if not guid in testbed_instance._add_address:
         return
@@ -146,6 +226,7 @@ def configure_device(testbed_instance, guid):
         element.add_v4_address(address, netprefix)
 
 def configure_node(testbed_instance, guid):
+    configure_traces(testbed_instance, guid)
     element = testbed_instance._elements[guid]
     if not guid in testbed_instance._add_route:
         return
@@ -182,10 +263,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({
@@ -193,52 +280,79 @@ connector_types = dict({
                 "name": "switch",
                 "max": 1, 
                 "min": 0
-            })
+            }),
+    "tcp": dict({
+                "help": "ip-ip tunneling over TCP link", 
+                "name": "tcp",
+                "max": 1, 
+                "min": 0
+            }),
+    "udp": dict({
+                "help": "ip-ip tunneling over UDP datagrams", 
+                "name": "udp",
+                "max": 1, 
+                "min": 0
+            }),
    })
 
 connections = [
     dict({
         "from": (TESTBED_ID, NODE, "devs"),
         "to":   (TESTBED_ID, P2PIFACE, "node"),
-        "code": None,
         "can_cross": False
     }),
     dict({
         "from": (TESTBED_ID, NODE, "devs"),
         "to":   (TESTBED_ID, TAPIFACE, "node"),
-        "code": None,
         "can_cross": False
     }),
     dict({
         "from": (TESTBED_ID, NODE, "devs"),
         "to":   (TESTBED_ID, NODEIFACE, "node"),
-        "code": None,
         "can_cross": False
     }),
     dict({
         "from": (TESTBED_ID, P2PIFACE, "p2p"),
         "to":   (TESTBED_ID, P2PIFACE, "p2p"),
-        "code": None,
         "can_cross": False
     }),
     dict({
-        "from": (TESTBED_ID, TAPIFACE, "fd"),
-        "to":   (NS3_TESTBED_ID, FDNETDEV, "fd"),
-        "code": connect_fd_local,
+        "from": (TESTBED_ID, TAPIFACE, "fd->"),
+        "to":   (None, None, "->fd"),
+        "compl_code": connect_fd,
         "can_cross": True
     }),
      dict({
         "from": (TESTBED_ID, SWITCH, "devs"),
         "to":   (TESTBED_ID, NODEIFACE, "switch"),
-        "code": connect_switch,
+        "init_code": connect_switch,
         "can_cross": False
     }),
     dict({
         "from": (TESTBED_ID, NODE, "apps"),
         "to":   (TESTBED_ID, APPLICATION, "node"),
-        "code": None,
         "can_cross": False
-    })
+    }),
+    dict({
+        "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
+        "to":   (TESTBED_ID, TAPIFACE, "fd->" ),
+        "init_code": connect_tunchannel_tap,
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
+        "to":   (None, None, "tcp"),
+        "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
+        "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
+        "can_cross": True
+    }),
+    dict({
+        "from": (TESTBED_ID, TUNCHANNEL, "udp"),
+        "to":   (None, None, "udp"),
+        "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
+        "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
+        "can_cross": True
+    }),
 ]
 
 attributes = dict({
@@ -328,14 +442,20 @@ traces = dict({
     "stderr": dict({
                 "name": "stderr",
                 "help": "Application standard error",
+        }),
+    "node_pcap": dict({
+                "name": "pcap",
+                "help": "tcpdump at all node interfaces",
         }) 
     })
 
-create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH,
+create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, 
+        TUNCHANNEL, SWITCH,
         APPLICATION ]
 
-configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, NODE,
-        APPLICATION ]
+configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, 
+        TUNCHANNEL, SWITCH, 
+        NODE, APPLICATION ]
 
 factories_info = dict({
     NODE: dict({
@@ -345,7 +465,8 @@ factories_info = dict({
             "create_function": create_node,
             "configure_function": configure_node,
             "box_attributes": ["forward_X11"],
-            "connector_types": ["devs", "apps"]
+            "connector_types": ["devs", "apps"],
+            "traces": ["node_pcap"]
        }),
     P2PIFACE: dict({
             "allow_addresses": True,
@@ -365,7 +486,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,
@@ -395,11 +516,24 @@ factories_info = dict({
             "category": "applications",
             "create_function": create_application,
             "start_function": start_application,
+            "stop_function": stop_application,
             "status_function": status_application,
             "box_attributes": ["command", "user"],
             "connector_types": ["node"],
             "traces": ["stdout", "stderr"]
         }),
+     TUNCHANNEL : dict({
+        "category": "Channel",
+        "create_function": create_tunchannel,
+        "preconfigure_function": preconfigure_tunchannel,
+        "configure_function": postconfigure_tunchannel,
+        "start_function": wait_tunchannel,
+        "help": "Channel to forward "+TAPIFACE+" data to "
+                "other TAP interfaces supporting the NEPI tunneling protocol.",
+        "connector_types": ["->fd", "udp", "tcp"],
+        "allow_addresses": False,
+        "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"]
+    }),
 })
 
 testbed_attributes = dict({