From a87d63093b5e15a39ab9241537cef7b0621d048b Mon Sep 17 00:00:00 2001 From: Claudio-Daniel Freire Date: Thu, 28 Apr 2011 16:19:30 +0200 Subject: [PATCH] Ticket #14: WIP, only intra-PL TUN connections, required groundwork for cross-backend connections --- setup.py | 2 +- src/nepi/testbeds/planetlab/interfaces.py | 57 ++- src/nepi/testbeds/planetlab/metadata_v01.py | 96 ++++- src/nepi/testbeds/planetlab/rspawn.py | 25 +- .../testbeds/planetlab/scripts/tun_connect.py | 2 +- .../testbeds/planetlab/scripts/tunalloc.c | 95 +++++ src/nepi/testbeds/planetlab/tunproto.py | 334 ++++++++++++++++++ src/nepi/util/ipaddr2.py | 7 + src/nepi/util/server.py | 2 +- test/testbeds/planetlab/execute.py | 79 ++++- 10 files changed, 668 insertions(+), 31 deletions(-) create mode 100644 src/nepi/testbeds/planetlab/scripts/tunalloc.c create mode 100644 src/nepi/testbeds/planetlab/tunproto.py diff --git a/setup.py b/setup.py index 170568ff..024bca18 100755 --- 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"] }, ) diff --git a/src/nepi/testbeds/planetlab/interfaces.py b/src/nepi/testbeds/planetlab/interfaces.py index c5e23ac9..a8de10dc 100644 --- a/src/nepi/testbeds/planetlab/interfaces.py +++ b/src/nepi/testbeds/planetlab/interfaces.py @@ -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" % ( 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): diff --git a/src/nepi/testbeds/planetlab/metadata_v01.py b/src/nepi/testbeds/planetlab/metadata_v01.py index 4fead345..7866741a 100644 --- a/src/nepi/testbeds/planetlab/metadata_v01.py +++ b/src/nepi/testbeds/planetlab/metadata_v01.py @@ -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", diff --git a/src/nepi/testbeds/planetlab/rspawn.py b/src/nepi/testbeds/planetlab/rspawn.py index 175540bb..6004462b 100644 --- a/src/nepi/testbeds/planetlab/rspawn.py +++ b/src/nepi/testbeds/planetlab/rspawn.py @@ -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 diff --git a/src/nepi/testbeds/planetlab/scripts/tun_connect.py b/src/nepi/testbeds/planetlab/scripts/tun_connect.py index c416557e..4fba8845 100644 --- a/src/nepi/testbeds/planetlab/scripts/tun_connect.py +++ b/src/nepi/testbeds/planetlab/scripts/tun_connect.py @@ -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 index 00000000..6adcf24a --- /dev/null +++ b/src/nepi/testbeds/planetlab/scripts/tunalloc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..a9f4888a --- /dev/null +++ b/src/nepi/testbeds/planetlab/tunproto.py @@ -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, +} + + diff --git a/src/nepi/util/ipaddr2.py b/src/nepi/util/ipaddr2.py index a88d1b75..88700be1 100644 --- a/src/nepi/util/ipaddr2.py +++ b/src/nepi/util/ipaddr2.py @@ -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 + diff --git a/src/nepi/util/server.py b/src/nepi/util/server.py index 8affb085..f153ab24 100644 --- a/src/nepi/util/server.py +++ b/src/nepi/util/server.py @@ -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 """ diff --git a/test/testbeds/planetlab/execute.py b/test/testbeds/planetlab/execute.py index 4b67d3d6..01671f4c 100755 --- a/test/testbeds/planetlab/execute.py +++ b/test/testbeds/planetlab/execute.py @@ -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[0-9]+)m(?P[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__': -- 2.45.2