Simple cross ping experiment between linux and ns3
[nepi.git] / src / nepi / resources / linux / tap.py
index 24efdff..636e471 100644 (file)
 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
 
 from nepi.execution.attribute import Attribute, Flags, Types
 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
 
 from nepi.execution.attribute import Attribute, Flags, Types
-from nepi.execution.resource import clsinit_copy, ResourceState, \
-        reschedule_delay
+from nepi.execution.resource import clsinit_copy, ResourceState
 from nepi.resources.linux.application import LinuxApplication
 from nepi.resources.linux.node import LinuxNode
 from nepi.util.timefuncs import tnow, tdiffsec
 
 import os
 from nepi.resources.linux.application import LinuxApplication
 from nepi.resources.linux.node import LinuxNode
 from nepi.util.timefuncs import tnow, tdiffsec
 
 import os
-import socket
 import time
 
 PYTHON_VSYS_VERSION = "1.0"
 
 @clsinit_copy
 class LinuxTap(LinuxApplication):
 import time
 
 PYTHON_VSYS_VERSION = "1.0"
 
 @clsinit_copy
 class LinuxTap(LinuxApplication):
-    _rtype = "LinuxTap"
+    _rtype = "linux::Tap"
     _help = "Creates a TAP device on a Linux host"
     _backend = "linux"
 
     _help = "Creates a TAP device on a Linux host"
     _backend = "linux"
 
+    IFF_TUN = 0x0001
+    IFF_TAP = 0x0002
+
     @classmethod
     def _register_attributes(cls):
     @classmethod
     def _register_attributes(cls):
-        ip4 = Attribute("ip4", "IPv4 Address",
+        endpoint_ip = Attribute("endpoint_ip", "IPv4 Address",
               flags = Flags.Design)
 
         mac = Attribute("mac", "MAC Address",
                 flags = Flags.Design)
 
               flags = Flags.Design)
 
         mac = Attribute("mac", "MAC Address",
                 flags = Flags.Design)
 
-        prefix4 = Attribute("prefix4", "IPv4 network prefix",
+        endpoint_prefix = Attribute("endpoint_prefix", "IPv4 network prefix",
                 type = Types.Integer,
                 flags = Flags.Design)
 
                 type = Types.Integer,
                 flags = Flags.Design)
 
@@ -76,13 +77,17 @@ class LinuxTap(LinuxApplication):
                 "Public IP of remote endpoint for GRE tunnel", 
                 flags = Flags.Design)
 
                 "Public IP of remote endpoint for GRE tunnel", 
                 flags = Flags.Design)
 
+        pi = Attribute("pi", "Add PI (protocol information) header", 
+                default = False,
+                type = Types.Bool)
         tear_down = Attribute("tearDown", 
                 "Bash script to be executed before releasing the resource",
                 flags = Flags.Design)
 
         tear_down = Attribute("tearDown", 
                 "Bash script to be executed before releasing the resource",
                 flags = Flags.Design)
 
-        cls._register_attribute(ip4)
+        cls._register_attribute(endpoint_ip)
         cls._register_attribute(mac)
         cls._register_attribute(mac)
-        cls._register_attribute(prefix4)
+        cls._register_attribute(endpoint_prefix)
         cls._register_attribute(mtu)
         cls._register_attribute(devname)
         cls._register_attribute(up)
         cls._register_attribute(mtu)
         cls._register_attribute(devname)
         cls._register_attribute(up)
@@ -90,18 +95,20 @@ class LinuxTap(LinuxApplication):
         cls._register_attribute(txqueuelen)
         cls._register_attribute(gre_key)
         cls._register_attribute(gre_remote)
         cls._register_attribute(txqueuelen)
         cls._register_attribute(gre_key)
         cls._register_attribute(gre_remote)
+        cls._register_attribute(pi)
         cls._register_attribute(tear_down)
 
     def __init__(self, ec, guid):
         super(LinuxTap, self).__init__(ec, guid)
         self._home = "tap-%s" % self.guid
         self._gre_enabled = False
         cls._register_attribute(tear_down)
 
     def __init__(self, ec, guid):
         super(LinuxTap, self).__init__(ec, guid)
         self._home = "tap-%s" % self.guid
         self._gre_enabled = False
+        self._tunnel_mode = False
 
     @property
     def node(self):
         node = self.get_connected(LinuxNode.get_rtype())
         if node: return node[0]
 
     @property
     def node(self):
         node = self.get_connected(LinuxNode.get_rtype())
         if node: return node[0]
-        return None
+        raise RuntimeError, "linux::TAP/TUN devices must be connected to a linux::Node"
 
     @property
     def gre_enabled(self):
 
     @property
     def gre_enabled(self):
@@ -112,7 +119,37 @@ class LinuxTap(LinuxApplication):
 
         return self._gre_enabled
 
 
         return self._gre_enabled
 
+    @property
+    def tunnel_mode(self):
+        if not self._tunnel_mode:
+            from nepi.resources.linux.tunnel import LinuxTunnel
+            tunnel = self.get_connected(LinuxTunnel.get_rtype())
+            if tunnel: self._tunnel_mode = True
+
+        return self._tunnel_mode
+
     def upload_sources(self):
     def upload_sources(self):
+        scripts = []
+
+        # udp-connect python script
+        udp_connect = os.path.join(os.path.dirname(__file__), "scripts",
+                "linux-udp-connect.py")
+        
+        scripts.append(udp_connect)
+
+        # tunnel creation python script
+        tunchannel = os.path.join(os.path.dirname(__file__), "scripts", 
+                "tunchannel.py")
+
+        scripts.append(tunchannel)
+
+        # Upload scripts
+        scripts = ";".join(scripts)
+
+        self.node.upload(scripts,
+                os.path.join(self.node.src_dir),
+                overwrite = False)
+
         # upload stop.sh script
         stop_command = self.replace_paths(self._stop_command)
 
         # upload stop.sh script
         stop_command = self.replace_paths(self._stop_command)
 
@@ -127,7 +164,7 @@ class LinuxTap(LinuxApplication):
     def upload_start_command(self):
         # If GRE mode is enabled, TAP creation is delayed until the
         # tunnel is established
     def upload_start_command(self):
         # If GRE mode is enabled, TAP creation is delayed until the
         # tunnel is established
-        if not self.gre_enabled:
+        if not self.tunnel_mode:
             # We want to make sure the device is up and running
             # before the deploy is over, so we execute the 
             # start script now and wait until it finishes. 
             # We want to make sure the device is up and running
             # before the deploy is over, so we execute the 
             # start script now and wait until it finishes. 
@@ -141,7 +178,7 @@ class LinuxTap(LinuxApplication):
 
     def do_deploy(self):
         if not self.node or self.node.state < ResourceState.PROVISIONED:
 
     def do_deploy(self):
         if not self.node or self.node.state < ResourceState.PROVISIONED:
-            self.ec.schedule(reschedule_delay, self.deploy)
+            self.ec.schedule(self.reschedule_delay, self.deploy)
         else:
             if not self.get("deviceName"):
                 self.set("deviceName", "%s%d" % (self.vif_prefix, self.guid)) 
         else:
             if not self.get("deviceName"):
                 self.set("deviceName", "%s%d" % (self.vif_prefix, self.guid)) 
@@ -188,7 +225,7 @@ class LinuxTap(LinuxApplication):
                 tdiffsec(tnow(), self._last_state_check) > state_check_delay:
 
             if self.get("deviceName"):
                 tdiffsec(tnow(), self._last_state_check) > state_check_delay:
 
             if self.get("deviceName"):
-                (out, err), proc = self.node.execute("ifconfig")
+                (out, err), proc = self.node.execute("ip a")
 
                 if out.strip().find(self.get("deviceName")) == -1: 
                     # tap is not running is not running (socket not found)
 
                 if out.strip().find(self.get("deviceName")) == -1: 
                     # tap is not running is not running (socket not found)
@@ -206,7 +243,7 @@ class LinuxTap(LinuxApplication):
 
         for rm in rms:
             if rm.state < ResourceState.STOPPED:
 
         for rm in rms:
             if rm.state < ResourceState.STOPPED:
-                self.ec.schedule(reschedule_delay, self.release)
+                self.ec.schedule(self.reschedule_delay, self.release)
                 return 
 
         super(LinuxTap, self).do_release()
                 return 
 
         super(LinuxTap, self).do_release()
@@ -218,14 +255,13 @@ class LinuxTap(LinuxApplication):
 
         # upload command to connect.sh script
         shfile = os.path.join(connection_app_home, "gre-connect.sh")
 
         # upload command to connect.sh script
         shfile = os.path.join(connection_app_home, "gre-connect.sh")
-        endpoint.node.upload(gre_connect_command,
-                shfile,
-                text = True, 
+        self.node.upload_command(gre_connect_command,
+                shfile = shfile,
                 overwrite = False)
 
         # invoke connect script
         cmd = "bash %s" % shfile
                 overwrite = False)
 
         # invoke connect script
         cmd = "bash %s" % shfile
-        (out, err), proc = self.node.run(cmd, connection_run_home) 
+        (out, err), proc = self.node.run(cmd, connection_run_home)
              
         # check if execution errors occurred
         msg = " Failed to connect endpoints "
              
         # check if execution errors occurred
         msg = " Failed to connect endpoints "
@@ -249,11 +285,114 @@ class LinuxTap(LinuxApplication):
         
         return True
 
         
         return True
 
+    def initiate_udp_connection(self, remote_endpoint, connection_app_home, 
+            connection_run_home, cipher, cipher_key, bwlimit, txqueuelen):
+        port = self.udp_connect(remote_endpoint, connection_app_home, 
+            connection_run_home, cipher, cipher_key, bwlimit, txqueuelen)
+        return port
+
+    def udp_connect(self, remote_endpoint, connection_app_home, 
+            connection_run_home, cipher, cipher_key, bwlimit, txqueuelen):
+        udp_connect_command = self._udp_connect_command(
+                remote_endpoint, connection_run_home,
+                cipher, cipher_key, bwlimit, txqueuelen)
+
+        # upload command to connect.sh script
+        shfile = os.path.join(self.app_home, "udp-connect.sh")
+        self.node.upload_command(udp_connect_command,
+                shfile = shfile,
+                overwrite = False)
+
+        # invoke connect script
+        cmd = "bash %s" % shfile
+        (out, err), proc = self.node.run(cmd, self.run_home) 
+             
+        # check if execution errors occurred
+        msg = "Failed to connect endpoints "
+        
+        if proc.poll():
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+    
+        # Wait for pid file to be generated
+        self._pid, self._ppid = self.node.wait_pid(self.run_home)
+        
+        # If the process is not running, check for error information
+        # on the remote machine
+        if not self._pid or not self._ppid:
+            (out, err), proc = self.node.check_errors(self.run_home)
+            # Out is what was written in the stderr file
+            if err:
+                msg = " Failed to start command '%s' " % command
+                self.error(msg, out, err)
+                raise RuntimeError, msg
+
+        port = self.wait_local_port()
+
+        return port
+
+    def _udp_connect_command(self, remote_endpoint, connection_run_home, 
+            cipher, cipher_key, bwlimit, txqueuelen):
+
+        # Set the remote endpoint
+        self.set("pointopoint", remote_endpoint.get("endpoint_ip"))
+        
+        # Planetlab TAPs always use PI headers
+        from nepi.resources.planetlab.tap import PlanetlabTap
+        if self.is_rm_instance(PlanetlabTap.get_rtype()):
+            self.set("pi", True)
+
+        remote_ip = remote_endpoint.node.get("ip")
+
+        local_port_file = os.path.join(self.run_home, 
+                "local_port")
+
+        remote_port_file = os.path.join(self.run_home, 
+                "remote_port")
+
+        ret_file = os.path.join(self.run_home, 
+                "ret_file")
+
+        # Generate UDP connect command
+        # Use the start command to configure TAP with peer info
+        start_command = self._start_command
+        
+        command = ["( "]
+        command.append(start_command)
+
+        # Use pl-vid-udp-connect.py to stablish the tunnel between endpoints
+        command.append(") & (")
+        command.append("sudo -S")
+        command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
+        command.append("python ${SRC}/linux-udp-connect.py")
+        command.append("-N %s" % self.get("deviceName"))
+        command.append("-t %s" % self.vif_type)
+        if self.get("pi"):
+            command.append("-p")
+        command.append("-l %s " % local_port_file)
+        command.append("-r %s " % remote_port_file)
+        command.append("-H %s " % remote_ip)
+        command.append("-R %s " % ret_file)
+        if cipher:
+            command.append("-c %s " % cipher)
+        if cipher_key:
+            command.append("-k %s " % cipher_key)
+        if txqueuelen:
+            command.append("-q %s " % txqueuelen)
+        if bwlimit:
+            command.append("-b %s " % bwlimit)
+
+        command.append(")")
+
+        command = " ".join(command)
+        command = self.replace_paths(command)
+
+        return command
+
     def _gre_connect_command(self, remote_endpoint, connection_run_home): 
         # Set the remote endpoint
     def _gre_connect_command(self, remote_endpoint, connection_run_home): 
         # Set the remote endpoint
-        self.set("pointopoint", remote_endpoint.get("ip4"))
-        self.set("greRemote", socket.gethostbyname(
-            remote_endpoint.node.get("hostname")))
+        self.set("pointopoint", remote_endpoint.get("endpoint_ip"))
+        self.set("greRemote", remote_endpoint.node.get("ip"))
 
         # Generate GRE connect command
         command = ["("]
 
         # Generate GRE connect command
         command = ["("]
@@ -267,17 +406,93 @@ class LinuxTap(LinuxApplication):
 
         return command
 
 
         return command
 
+    def establish_udp_connection(self, remote_endpoint, port):
+        # upload remote port number to file
+        rem_port = "%s\n" % port
+        self.node.upload(rem_port,
+                os.path.join(self.run_home, "remote_port"),
+                text = True, 
+                overwrite = False)
+
+    def verify_connection(self):
+        self.wait_result()
+
+    def terminate_connection(self):
+        if  self._pid and self._ppid:
+            (out, err), proc = self.node.kill(self._pid, self._ppid, 
+                    sudo = True) 
+
+            # check if execution errors occurred
+            if proc.poll() and err:
+                msg = " Failed to Kill the Tap"
+                self.error(msg, out, err)
+                raise RuntimeError, msg
+
+    def check_status(self):
+        return self.node.status(self._pid, self._ppid)
+
+    def wait_local_port(self):
+        """ Waits until the local_port file for the endpoint is generated, 
+        and returns the port number 
+        
+        """
+        return self.wait_file("local_port")
+
+    def wait_result(self):
+        """ Waits until the return code file for the endpoint is generated 
+        
+        """ 
+        return self.wait_file("ret_file")
+    def wait_file(self, filename):
+        """ Waits until file on endpoint is generated """
+        result = None
+        delay = 1.0
+
+        for i in xrange(20):
+            (out, err), proc = self.node.check_output(
+                    self.run_home, filename)
+            if out:
+                result = out.strip()
+                break
+            else:
+                time.sleep(delay)
+                delay = delay * 1.5
+        else:
+            msg = "Couldn't retrieve %s" % filename
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+
+        return result
+
     @property
     def _start_command(self):
         command = []
     @property
     def _start_command(self):
         command = []
-        command.append("sudo -S ip tuntap add %s mode tap" % self.get("deviceName"))
-        command.append("sudo -S ip link set %s up" % self.get("deviceName"))
-        command.append("sudo -S ip addr add %s/%d dev %s" % (
-            self.get("ip4"),
-            self.get("prefix4"),
-            self.get("deviceName"),
-            ))
-        return ";".join(command)
+        if not self.gre_enabled:
+            # Make sure to clean TAP if it existed
+            stop_command = self._stop_command
+            
+            start_command = []
+            start_command.append("sudo -S ip tuntap add %s mode %s %s" % (
+                self.get("deviceName"),
+                self.vif_prefix,
+                "pi" if self.get("pi") else ""))
+            start_command.append("sudo -S ip link set %s up" % self.get("deviceName"))
+            start_command.append("sudo -S ip addr add %s/%d dev %s" % (
+                self.get("endpoint_ip"),
+                self.get("endpoint_prefix"),
+                self.get("deviceName"),
+                ))
+
+            start_command = ";".join(start_command)
+
+            command.append("(")
+            command.append(stop_command)
+            command.append(") ; (")
+            command.append(start_command)
+            command.append(")")
+
+        return " ".join(command)
 
     @property
     def _stop_command(self):
 
     @property
     def _stop_command(self):
@@ -294,15 +509,15 @@ class LinuxTap(LinuxApplication):
         command.append("sudo -S ip link add %s type gre remote %s local %s ttl 64 csum key %s" % (
                 self.get("deviceName"),
                 self.get("greRemote"),
         command.append("sudo -S ip link add %s type gre remote %s local %s ttl 64 csum key %s" % (
                 self.get("deviceName"),
                 self.get("greRemote"),
-                socket.gethostbyname(self.node.get("hostname")),
+                self.node.get("ip"),
                 self.get("greKey")
             ))
                 self.get("greKey")
             ))
-        command.append("sudo -S addr add dev %s %s/%d peer %s/%d" % (
-                self.get("deviceName"),
-                self.get("ip4"),
-                self.get("prefix4"),
+        command.append("sudo -S ip addr add %s/%d peer %s/%d dev %s" % (
+                self.get("endpoint_ip"),
+                self.get("endpoint_prefix"),
                 self.get("pointopoint"),
                 self.get("pointopoint"),
-                self.get("prefix4"),
+                self.get("endpoint_prefix"),
+                self.get("deviceName"),
                 ))
         command.append("sudo -S ip link set %s up " % self.get("deviceName"))
 
                 ))
         command.append("sudo -S ip link set %s up " % self.get("deviceName"))
 
@@ -312,6 +527,10 @@ class LinuxTap(LinuxApplication):
     def vif_type(self):
         return "IFF_TAP"
 
     def vif_type(self):
         return "IFF_TAP"
 
+    @property
+    def vif_type_flag(self):
+        return LinuxTap.IFF_TAP
     @property
     def vif_prefix(self):
         return "tap"
     @property
     def vif_prefix(self):
         return "tap"