Ticket #14: WIP, only intra-PL TUN connections, required groundwork for cross-backend...
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 28 Apr 2011 14:19:30 +0000 (16:19 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Thu, 28 Apr 2011 14:19:30 +0000 (16:19 +0200)
setup.py
src/nepi/testbeds/planetlab/interfaces.py
src/nepi/testbeds/planetlab/metadata_v01.py
src/nepi/testbeds/planetlab/rspawn.py
src/nepi/testbeds/planetlab/scripts/tun_connect.py
src/nepi/testbeds/planetlab/scripts/tunalloc.c [new file with mode: 0644]
src/nepi/testbeds/planetlab/tunproto.py [new file with mode: 0644]
src/nepi/util/ipaddr2.py
src/nepi/util/server.py
test/testbeds/planetlab/execute.py

index 170568f..024bca1 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -20,5 +20,5 @@ setup(
             "nepi.util.parser",
             "nepi.util" ],
         package_dir = {"": "src"},
-        package_data = {"nepi.testbeds.planetlab" : ["scripts/*.py", "scripts/consts.c"] },
+        package_data = {"nepi.testbeds.planetlab" : ["scripts/*.py", "scripts/*.c"] },
     )
index c5e23ac..a8de10d 100644 (file)
@@ -9,6 +9,8 @@ import subprocess
 import os
 import os.path
 
+import tunproto
+
 class NodeIface(object):
     def __init__(self, api=None):
         if not api:
@@ -96,14 +98,34 @@ class TunIface(object):
         self.device_name = None
         self.mtu = None
         self.snat = False
+        self.txqueuelen = 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 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
+        
+        # These get initialized when the iface is connected to its peer
+        self.peer_iface = None
+        self.peer_proto = None
+        self.peer_proto_impl = None
+
+        # same as peer proto, but for execute-time standard attribute lookups
+        self.tun_proto = None 
 
     def __str__(self):
         return "%s<ip:%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 "",
         )
@@ -116,11 +138,38 @@ class TunIface(object):
         
         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 TUN iface - missing node"
+        if self.peer_iface and self.peer_proto not in tunproto.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 TUN iface - missing address"
+    
+    def prepare(self, home_path, listening):
+        if self.peer_iface:
+            if not self.peer_proto_impl:
+                self.peer_proto_impl = tunproto.PROTO_MAP[self.peer_proto](
+                    self, self.peer_iface, home_path, listening)
+                self.peer_proto_impl.port = self.tun_port
+            self.peer_proto_impl.prepare()
+    
+    def setup(self):
+        if self.peer_iface:
+            self.peer_proto_impl.setup()
     
+    def destroy(self):
+        if self.peer_proto_impl:
+            self.peer_proto_impl.shutdown()
+            self.peer_proto_impl = None
+
+    def sync_trace(self, local_dir, whichtrace):
+        if self.peer_proto_impl:
+            return self.peer_proto_impl.sync_trace(local_dir, whichtrace)
+        else:
+            return None
 
 # Yep, it does nothing - yet
 class Internet(object):
index 4fead34..7866741 100644 (file)
@@ -10,6 +10,10 @@ from nepi.util import validation
 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
         STATUS_FINISHED
 
+import functools
+import os
+import os.path
+
 NODE = "Node"
 NODEIFACE = "NodeInterface"
 TUNIFACE = "TunInterface"
@@ -84,6 +88,11 @@ def connect_tun_iface_node(testbed_instance, node, iface):
     iface.node = node
     node.required_vsys.update(('fd_tuntap', 'vif_up'))
 
+def connect_tun_iface_peer(proto, testbed_instance, iface, peer_iface):
+    iface.peer_iface = peer_iface
+    iface.peer_proto = \
+    iface.tun_proto = proto
+
 def connect_app(testbed_instance, node, app):
     app.node = node
     
@@ -109,7 +118,7 @@ def create_node(testbed_instance, guid):
     # by counting connected devices
     dev_guids = testbed_instance.get_connected(guid, "node", "devs")
     num_open_ifaces = sum( # count True values
-        TUNEIFACE == testbed_instance._get_factory_id(guid)
+        NODEIFACE == testbed_instance._get_factory_id(guid)
         for guid in dev_guids )
     element.min_num_external_ifaces = num_open_ifaces
     
@@ -188,18 +197,44 @@ def configure_nodeiface(testbed_instance, guid):
     # Do some validations
     element.validate()
 
-def configure_tuniface(testbed_instance, guid):
+def preconfigure_tuniface(testbed_instance, guid):
     element = testbed_instance._elements[guid]
-    if not guid in testbed_instance._add_address:
-        return
     
-    addresses = testbed_instance._add_address[guid]
-    for address in addresses:
-        (address, netprefix, broadcast) = address
-        element.add_address(address, netprefix, broadcast)
+    # Set custom addresses if any
+    if guid in testbed_instance._add_address:
+        addresses = testbed_instance._add_address[guid]
+        for address in addresses:
+            (address, netprefix, broadcast) = address
+            element.add_address(address, netprefix, broadcast)
+    
+    # Link to external interface, if any
+    for iface in testbed_instance._elements.itervalues():
+        if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
+            element.external_iface = iface
+            break
+
+    # Set standard TUN attributes
+    element.tun_addr = element.external_iface.address
+    element.tun_port = 15000 + int(guid)
+
+    # Set enabled traces
+    traces = testbed_instance._get_traces(guid)
+    element.capture = 'packets' in traces
     
     # Do some validations
     element.validate()
+    
+    # First-phase setup
+    element.prepare( 
+        'tun-%s' % (guid,),
+        id(element) < id(element.peer_iface) )
+
+def postconfigure_tuniface(testbed_instance, guid):
+    element = testbed_instance._elements[guid]
+    
+    # Second-phase setup
+    element.setup()
+    
 
 def configure_node(testbed_instance, guid):
     node = testbed_instance._elements[guid]
@@ -284,6 +319,19 @@ connector_types = dict({
                 "max": 2, 
                 "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 = [
@@ -317,6 +365,18 @@ connections = [
         "code": connect_node_netpipe,
         "can_cross": False
     }),
+    dict({
+        "from": (TESTBED_ID, TUNIFACE, "tcp"),
+        "to":   (TESTBED_ID, TUNIFACE, "tcp"),
+        "code": functools.partial(connect_tun_iface_peer,"tcp"),
+        "can_cross": False
+    }),
+    dict({
+        "from": (TESTBED_ID, TUNIFACE, "udp"),
+        "to":   (TESTBED_ID, TUNIFACE, "udp"),
+        "code": functools.partial(connect_tun_iface_peer,"udp"),
+        "can_cross": False
+    }),
 ]
 
 attributes = dict({
@@ -448,6 +508,14 @@ attributes = dict({
                 "value": False,
                 "validation_function": validation.is_bool
             }),
+    "txqueuelen":  dict({
+                "name": "mask", 
+                "help": "Transmission queue length (in packets)",
+                "type": Attribute.INTEGER,
+                "flags": Attribute.DesignOnly,
+                "range" : (1,10000),
+                "validation_function": validation.is_integer
+            }),
             
     "command": dict({
                 "name": "command",
@@ -609,6 +677,11 @@ traces = dict({
                 "name": "netpipeStats",
                 "help": "Information about rule match counters, packets dropped, etc.",
               }),
+
+    "packets": dict({
+                "name": "packets",
+                "help": "Detailled log of all packets going through the interface",
+              }),
     })
 
 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, APPLICATION ]
@@ -650,11 +723,14 @@ factories_info = dict({
             "help": "Virtual TUN network interface",
             "category": "devices",
             "create_function": create_tuniface,
-            "preconfigure_function": configure_tuniface,
+            "preconfigure_function": preconfigure_tuniface,
+            "configure_function": postconfigure_tuniface,
             "box_attributes": [
                 "up", "device_name", "mtu", "snat",
+                "txqueuelen"
             ],
-            "connector_types": ["node"]
+            "traces": ["packets"],
+            "connector_types": ["node","udp","tcp"]
         }),
     APPLICATION: dict({
             "help": "Generic executable command line application",
index 175540b..6004462 100644 (file)
@@ -61,20 +61,27 @@ def remote_spawn(command, pidfile, stdout='/dev/null', stderr=STDOUT, stdin='/de
         stderr = '&1'
     else:
         stderr = ' ' + stderr
-    (out,err),proc = server.popen_ssh_command(
-        "%(create)s%(gohome)s rm -f %(pidfile)s ; ( echo $$ $PPID > %(pidfile)s ; %(sudo)s nohup %(command)s > %(stdout)s 2>%(stderr)s < %(stdin)s ) &" % {
-            'command' : command,
-            
-            'stdout' : stdout,
-            'stderr' : stderr,
-            'stdin' : stdin,
+    
+    daemon_command = '{ { %(command)s  > %(stdout)s 2>%(stderr)s < %(stdin)s & } ; echo $! 1 > %(pidfile)s ; }' % {
+        'command' : command,
+        'pidfile' : server.shell_escape(pidfile),
+        
+        'stdout' : stdout,
+        'stderr' : stderr,
+        'stdin' : stdin,
+    }
+    
+    cmd = "%(create)s%(gohome)s rm -f %(pidfile)s ; %(sudo)s nohup bash -c %(command)s " % {
+            'command' : server.shell_escape(daemon_command),
             
             'sudo' : 'sudo -S' if sudo else '',
             
             'pidfile' : server.shell_escape(pidfile),
             'gohome' : 'cd %s ; ' % (server.shell_escape(home),) if home else '',
             'create' : 'mkdir -p %s ; ' % (server.shell_escape,) if create_home else '',
-        },
+        }
+    (out,err),proc = server.popen_ssh_command(
+        cmd,
         host = host,
         port = port,
         user = user,
@@ -194,7 +201,7 @@ def remote_kill(pid, ppid, sudo = False,
 
     (out,err),proc = server.popen_ssh_command(
         """
-%(sudo)s kill %(pid)d %(ppid)d 
+%(sudo)s kill %(pid)d
 for x in 1 2 3 4 5 6 7 8 9 0 ; do 
     sleep 0.1 
     if [ `ps --pid %(ppid)d -o pid | grep -c %(pid)d` == `0` ]; then
index c416557..4fba884 100644 (file)
@@ -205,7 +205,7 @@ def vif_stop(tun_path, tun_name):
     
     
 def pl_tuntap_alloc(kind, tun_path, tun_name):
-    tunalloc_so = ctypes.cdll.LoadLibrary("./vsys-scripts/support/tunalloc.so")
+    tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
     c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
     kind = {"tun":IFF_TUN,
             "tap":IFF_TAP}[kind]
diff --git a/src/nepi/testbeds/planetlab/scripts/tunalloc.c b/src/nepi/testbeds/planetlab/scripts/tunalloc.c
new file mode 100644 (file)
index 0000000..6adcf24
--- /dev/null
@@ -0,0 +1,95 @@
+/* Slice-side code to allocate tuntap interface in root slice
+ * Based on bmsocket.c
+ *  Thom Haddow - 08/10/09
+ *
+ * Call tun_alloc() with IFFTUN or IFFTAP as an argument to get back fd to
+ * new tuntap interface. Interface name can be acquired via TUNGETIFF ioctl.
+ */
+
+#include <sys/un.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#define VSYS_TUNTAP "/vsys/fd_tuntap.control"
+
+/* Reads vif FD from "fd", writes interface name to vif_name, and returns vif FD.
+ * vif_name should be IFNAMSIZ chars long. */
+int receive_vif_fd(int fd, char *vif_name)
+{
+       struct msghdr msg;
+       struct iovec iov;
+       int rv;
+       size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+       struct cmsghdr *cmsg;
+
+    /* Use IOV to read interface name */
+       iov.iov_base = vif_name;
+       iov.iov_len = IFNAMSIZ;
+
+       msg.msg_name = 0;
+       msg.msg_namelen = 0;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       /* old BSD implementations should use msg_accrights instead of
+        * msg_control; the interface is different. */
+       msg.msg_control = ccmsg;
+       msg.msg_controllen = sizeof(ccmsg);
+
+       while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+       if (rv == -1) {
+               perror("recvmsg");
+               return -1;
+       }
+       if(!rv) {
+               /* EOF */
+               return -1;
+       }
+
+       cmsg = CMSG_FIRSTHDR(&msg);
+       if (!cmsg->cmsg_type == SCM_RIGHTS) {
+               fprintf(stderr, "got control message of unknown type %d\n",
+                       cmsg->cmsg_type);
+               return -1;
+       }
+       return *(int*)CMSG_DATA(cmsg);
+}
+
+
+int tun_alloc(int iftype, char *if_name)
+{
+    int control_fd;
+    struct sockaddr_un addr;
+    int remotefd;
+
+    control_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (control_fd == -1) {
+        perror("Could not create UNIX socket\n");
+        exit(-1);
+    }
+
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    /* Clear structure */
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, VSYS_TUNTAP,
+            sizeof(addr.sun_path) - 1);
+
+    if (connect(control_fd, (struct sockaddr *) &addr,
+                sizeof(struct sockaddr_un)) == -1) {
+        perror("Could not connect to Vsys control socket");
+        exit(-1);
+    }
+
+    /* passing type param */
+    if (send(control_fd, &iftype, sizeof(iftype), 0) != sizeof(iftype)) {
+        perror("Could not send paramater to Vsys control socket");
+        exit(-1);
+    }
+
+    remotefd = receive_vif_fd(control_fd, if_name);
+    return remotefd;
+}
diff --git a/src/nepi/testbeds/planetlab/tunproto.py b/src/nepi/testbeds/planetlab/tunproto.py
new file mode 100644 (file)
index 0000000..a9f4888
--- /dev/null
@@ -0,0 +1,334 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import weakref
+import os
+import os.path
+import rspawn
+import subprocess
+
+from nepi.util import server
+
+class TunProtoBase(object):
+    def __init__(self, local, peer, home_path):
+        # Weak references, since ifaces do have a reference to the
+        # tunneling protocol implementation - we don't want strong
+        # circular references.
+        self.peer = weakref.ref(peer)
+        self.local = weakref.ref(local)
+        
+        self.port = 15000
+        self.mode = 'pl-tun'
+        
+        self.home_path = home_path
+        
+        self._started = False
+        self._pid = None
+        self._ppid = None
+
+    def _make_home(self):
+        local = self.local()
+        
+        if not local:
+            raise RuntimeError, "Lost reference to peering interfaces before launching"
+        if not local.node:
+            raise RuntimeError, "Unconnected TUN - missing node"
+        
+        # Make sure all the paths are created where 
+        # they have to be created for deployment
+        cmd = "mkdir -p %s" % (server.shell_escape(self.home_path),)
+        (out,err),proc = server.popen_ssh_command(
+            cmd,
+            host = local.node.hostname,
+            port = None,
+            user = local.node.slicename,
+            agent = None,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+        
+        if proc.wait():
+            raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
+        
+    
+    def _install_scripts(self):
+        local = self.local()
+        
+        if not local:
+            raise RuntimeError, "Lost reference to peering interfaces before launching"
+        if not local.node:
+            raise RuntimeError, "Unconnected TUN - missing node"
+        
+        # Install the tun_connect script and tunalloc utility
+        source = os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py')
+        dest = "%s@%s:%s" % (
+            local.node.slicename, local.node.hostname, 
+            os.path.join(self.home_path,'.'),)
+        (out,err),proc = server.popen_scp(
+            source,
+            dest,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+    
+        if proc.wait():
+            raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (source, out,err,)
+
+        source = os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c')
+        (out,err),proc = server.popen_scp(
+            source,
+            dest,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+    
+        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),)
+        (out,err),proc = server.popen_ssh_command(
+            cmd,
+            host = local.node.hostname,
+            port = None,
+            user = local.node.slicename,
+            agent = None,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+        
+        if proc.wait():
+            raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
+        
+    
+    def launch(self, check_proto, listen, extra_args=[]):
+        peer = self.peer()
+        local = self.local()
+        
+        if not peer or not local:
+            raise RuntimeError, "Lost reference to peering interfaces before launching"
+        
+        peer_port = peer.tun_port
+        peer_addr = peer.tun_addr
+        peer_proto= peer.tun_proto
+        
+        local_port = self.port
+        local_cap  = local.capture
+        local_addr = local.address
+        local_mask = local.netprefix
+        local_snat = local.snat
+        local_txq  = local.txqueuelen
+        
+        if check_proto != peer_proto:
+            raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
+        
+        if not listen and (not peer_port or not peer_addr):
+            raise RuntimeError, "Misconfigured peer: %s" % (peer,)
+        
+        if listen and (not local_port or not local_addr or not local_mask):
+            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)]
+        
+        if local_snat:
+            args.append("-S")
+        if local_txq:
+            args.extend(("-Q",str(local_txq)))
+        if extra_args:
+            args.extend(map(str,extra_args))
+        if not listen:
+            args.append(str(peer_addr))
+        
+        self._make_home()
+        self._install_scripts()
+        
+        # Start process in a "daemonized" way, using nohup and heavy
+        # stdin/out redirection to avoid connection issues
+        (out,err),proc = rspawn.remote_spawn(
+            " ".join(args),
+            
+            pidfile = './pid',
+            home = self.home_path,
+            stdin = '/dev/null',
+            stdout = 'capture' if local_cap else '/dev/null',
+            stderr = rspawn.STDOUT,
+            sudo = True,
+            
+            host = local.node.hostname,
+            port = None,
+            user = local.node.slicename,
+            agent = None,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+        
+        if proc.wait():
+            raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
+
+        self._started = True
+
+    def checkpid(self):            
+        local = self.local()
+        
+        if not local:
+            raise RuntimeError, "Lost reference to local interface"
+        
+        # Get PID/PPID
+        # NOTE: wait a bit for the pidfile to be created
+        if self._started and not self._pid or not self._ppid:
+            pidtuple = rspawn.remote_check_pid(
+                os.path.join(self.home_path,'pid'),
+                host = local.node.hostname,
+                port = None,
+                user = local.node.slicename,
+                agent = None,
+                ident_key = local.node.ident_path,
+                server_key = local.node.server_key
+                )
+            
+            if pidtuple:
+                self._pid, self._ppid = pidtuple
+    
+    def status(self):
+        local = self.local()
+        
+        if not local:
+            raise RuntimeError, "Lost reference to local interface"
+        
+        self.checkpid()
+        if not self._started:
+            return rspawn.NOT_STARTED
+        elif not self._pid or not self._ppid:
+            return rspawn.NOT_STARTED
+        else:
+            status = rspawn.remote_status(
+                self._pid, self._ppid,
+                host = local.node.hostname,
+                port = None,
+                user = local.node.slicename,
+                agent = None,
+                ident_key = local.node.ident_path
+                )
+            return status
+    
+    def kill(self):
+        local = self.local()
+        
+        if not local:
+            raise RuntimeError, "Lost reference to local interface"
+        
+        status = self.status()
+        if status == rspawn.RUNNING:
+            # kill by ppid+pid - SIGTERM first, then try SIGKILL
+            rspawn.remote_kill(
+                self._pid, self._ppid,
+                host = local.node.hostname,
+                port = None,
+                user = local.node.slicename,
+                agent = None,
+                ident_key = local.node.ident_path,
+                server_key = local.node.server_key,
+                sudo = True
+                )
+        
+    def sync_trace(self, local_dir, whichtrace):
+        if whichtrace != 'packets':
+            return None
+        
+        local = self.local()
+        
+        if not local:
+            return None
+        
+        local_path = os.path.join(local_dir, 'capture')
+        
+        # create parent local folders
+        proc = subprocess.Popen(
+            ["mkdir", "-p", os.path.dirname(local_path)],
+            stdout = open("/dev/null","w"),
+            stdin = open("/dev/null","r"))
+
+        if proc.wait():
+            raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
+        
+        # sync files
+        (out,err),proc = server.popen_scp(
+            '%s@%s:%s' % (local.node.slicename, local.node.hostname, 
+                os.path.join(self.home_path, 'capture')),
+            local_path,
+            port = None,
+            agent = None,
+            ident_key = local.node.ident_path,
+            server_key = local.node.server_key
+            )
+        
+        if proc.wait():
+            raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
+        
+        return local_path
+        
+        
+    def prepare(self):
+        """
+        First-phase setup
+        
+        eg: set up listening ports
+        """
+        raise NotImplementedError
+    
+    def setup(self):
+        """
+        Second-phase setup
+        
+        eg: connect to peer
+        """
+        raise NotImplementedError
+    
+    def shutdown(self):
+        """
+        Cleanup
+        """
+        raise NotImplementedError
+        
+
+class TunProtoUDP(TunProtoBase):
+    def __init__(self, local, peer, home_path, listening):
+        super(TunProtoTCP, self).__init__(local, peer, home_path)
+        self.listening = listening
+    
+    def prepare(self):
+        pass
+    
+    def setup(self):
+        self.launch('udp', False, ("-U",))
+    
+    def shutdown(self):
+        self.kill()
+
+class TunProtoTCP(TunProtoBase):
+    def __init__(self, local, peer, home_path, listening):
+        super(TunProtoTCP, self).__init__(local, peer, home_path)
+        self.listening = listening
+    
+    def prepare(self):
+        if self.listening:
+            self.launch('tcp', True)
+    
+    def setup(self):
+        if not self.listening:
+            self.launch('tcp', False)
+        
+        self.checkpid()
+    
+    def shutdown(self):
+        self.kill()
+
+PROTO_MAP = {
+    'tcp' : TunProtoTCP,
+    'udp' : TunProtoUDP,
+}
+
+
index a88d1b7..88700be 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
+import struct
 
 def ipv4_dot2mask(mask):
     mask = mask.split('.',4) # a.b.c.d -> [a,b,c,d]
@@ -18,3 +19,9 @@ def ipv4_dot2mask(mask):
     
     return n
 
+def ipv4_mask2dot(mask):
+    mask = ((1L << mask)-1) << (32 - mask)
+    mask = struct.pack(">I",mask)
+    mask = '.'.join(map(str,map(ord,mask)))
+    return mask
+
index 8affb08..f153ab2 100644 (file)
@@ -32,7 +32,7 @@ else:
 
 
 
-SHELL_SAFE = re.compile('[-a-zA-Z0-9_=+:.,/]*')
+SHELL_SAFE = re.compile('^[-a-zA-Z0-9_=+:.,/]*$')
 
 def shell_escape(s):
     """ Escapes strings so that they are safe to use as command-line arguments """
index 4b67d3d..01671f4 100755 (executable)
@@ -51,6 +51,7 @@ class PlanetLabExecuteTestCase(unittest.TestCase):
         instance.defer_create(7, "Application")
         instance.defer_create_set(7, "command", "ping -qc1 {#[GUID-5].addr[0].[Address]#}")
         instance.defer_add_trace(7, "stdout")
+        instance.defer_add_trace(7, "stderr")
         instance.defer_connect(7, "node", 2, "apps")
 
         instance.do_setup()
@@ -95,6 +96,7 @@ class PlanetLabExecuteTestCase(unittest.TestCase):
         instance.defer_create_set(5, "command", "gfortran --version")
         instance.defer_create_set(5, "depends", "gcc-gfortran")
         instance.defer_add_trace(5, "stdout")
+        instance.defer_add_trace(5, "stderr")
         instance.defer_connect(5, "node", 2, "apps")
 
         instance.do_setup()
@@ -130,6 +132,7 @@ class PlanetLabExecuteTestCase(unittest.TestCase):
         instance.defer_create_set(10, "install", "cp consts ${SOURCES}/consts")
         instance.defer_create_set(10, "sources", os.path.join(os.path.dirname(planetlab.__file__),'scripts','consts.c'))
         instance.defer_add_trace(10, "stdout")
+        instance.defer_add_trace(10, "stderr")
         instance.defer_connect(10, "node", 2, "apps")
 
         instance.do_setup()
@@ -173,6 +176,7 @@ FIONREAD = 0x[0-9a-fA-F]{8}.*
         instance.defer_create(4, "Internet")
         instance.defer_connect(3, "inet", 4, "devs")
         instance.defer_create(5, "TunInterface")
+        instance.defer_add_address(5, "192.168.2.2", 24, False)
         instance.defer_connect(2, "devs", 5, "node")
         instance.defer_create(6, "Application")
         instance.defer_create_set(6, "command", """
@@ -185,6 +189,7 @@ echo 'OKIDOKI'
 """)
         instance.defer_create_set(6, "sudo", True) # only sudo has access to /vsys
         instance.defer_add_trace(6, "stdout")
+        instance.defer_add_trace(6, "stderr")
         instance.defer_connect(6, "node", 2, "apps")
 
         instance.do_setup()
@@ -226,6 +231,7 @@ echo 'OKIDOKI'
         instance.defer_connect(2, "pipes", 7, "node")
         instance.defer_create(8, "Application")
         instance.defer_create_set(8, "command", "time wget -q -O /dev/null http://www.google.com/") # Fetch ~10kb
+        instance.defer_add_trace(8, "stdout")
         instance.defer_add_trace(8, "stderr")
         instance.defer_connect(8, "node", 2, "apps")
 
@@ -240,19 +246,20 @@ echo 'OKIDOKI'
             time.sleep(0.5)
         test_result = (instance.trace(8, "stderr") or "").strip()
         comp_result = r".*real\s*(?P<min>[0-9]+)m(?P<sec>[0-9]+[.][0-9]+)s.*"
+        netpipe_stats = instance.trace(7, "netpipeStats")
         
+        instance.stop()
+        instance.shutdown()
+
+        # asserts at the end, to make sure there's proper cleanup
         match = re.match(comp_result, test_result, re.MULTILINE)
         self.assertTrue(match, "Unexpected output: %s" % (test_result,))
         
         minutes = int(match.group("min"))
         seconds = float(match.group("sec"))
         self.assertTrue((minutes * 60 + seconds) > 1.0, "Emulation not effective: %s" % (test_result,))
-        
-        netpipe_stats = instance.trace(7, "netpipeStats")
+
         self.assertTrue(netpipe_stats, "Unavailable netpipe stats")
-        
-        instance.stop()
-        instance.shutdown()
 
     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
     def test_tun_emulation_requirement(self):
@@ -265,10 +272,12 @@ echo 'OKIDOKI'
         instance.defer_create(4, "Internet")
         instance.defer_connect(3, "inet", 4, "devs")
         instance.defer_create(5, "TunInterface")
+        instance.defer_add_address(5, "192.168.2.2", 24, False)
         instance.defer_connect(2, "devs", 5, "node")
         instance.defer_create(6, "Application")
         instance.defer_create_set(6, "command", "false")
         instance.defer_add_trace(6, "stdout")
+        instance.defer_add_trace(6, "stderr")
         instance.defer_connect(6, "node", 2, "apps")
 
         try:
@@ -280,6 +289,66 @@ echo 'OKIDOKI'
             self.fail("Usage of TUN without emulation should fail")
         except Exception,e:
             pass
+
+    @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+    def test_tun_ping(self):
+        instance = self.make_instance()
+        
+        instance.defer_create(2, "Node")
+        instance.defer_create_set(2, "hostname", "onelab11.pl.sophia.inria.fr")
+        instance.defer_create_set(2, "emulation", True) # require emulation
+        instance.defer_create(3, "Node")
+        instance.defer_create_set(3, "hostname", "onelab10.pl.sophia.inria.fr")
+        instance.defer_create_set(3, "emulation", True) # require emulation
+        instance.defer_create(4, "NodeInterface")
+        instance.defer_connect(2, "devs", 4, "node")
+        instance.defer_create(5, "Internet")
+        instance.defer_connect(4, "inet", 5, "devs")
+        instance.defer_create(6, "NodeInterface")
+        instance.defer_connect(3, "devs", 6, "node")
+        instance.defer_connect(6, "inet", 5, "devs")
+        instance.defer_create(7, "TunInterface")
+        instance.defer_add_address(7, "192.168.2.2", 24, False)
+        instance.defer_connect(2, "devs", 7, "node")
+        instance.defer_create(8, "TunInterface")
+        instance.defer_add_address(8, "192.168.2.3", 24, False)
+        instance.defer_connect(3, "devs", 8, "node")
+        instance.defer_connect(7, "tcp", 8, "tcp")
+        instance.defer_create(9, "Application")
+        instance.defer_create_set(9, "command", "ping -qc1 {#[GUID-8].addr[0].[Address]#}")
+        instance.defer_add_trace(9, "stdout")
+        instance.defer_add_trace(9, "stderr")
+        instance.defer_connect(9, "node", 2, "apps")
+
+        instance.do_setup()
+        instance.do_create()
+        instance.do_connect()
+        instance.do_preconfigure()
+        
+        # Manually replace netref
+        instance.set(TIME_NOW, 9, "command",
+            instance.get(TIME_NOW, 9, "command")
+                .replace("{#[GUID-8].addr[0].[Address]#}", 
+                    instance.get_address(8, 0, "Address") )
+        )
+        
+        instance.do_configure()
+        
+        instance.start()
+        while instance.status(9) != STATUS_FINISHED:
+            time.sleep(0.5)
+        ping_result = instance.trace(9, "stdout") or ""
+        comp_result = r"""PING .* \(.*\) \d*\(\d*\) bytes of data.
+
+--- .* ping statistics ---
+1 packets transmitted, 1 received, 0% packet loss, time \d*ms.*
+"""
+        instance.stop()
+        instance.shutdown()
+
+        # asserts at the end, to make sure there's proper cleanup
+        self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
+            "Unexpected trace:\n" + ping_result)
         
 
 if __name__ == '__main__':