Simple cross ping experiment between linux and ns3
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Mon, 15 Dec 2014 10:40:26 +0000 (11:40 +0100)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Mon, 15 Dec 2014 10:40:26 +0000 (11:40 +0100)
src/nepi/resources/linux/ns3/tap_fd_link.py [new file with mode: 0644]
src/nepi/resources/linux/route.py [new file with mode: 0644]
src/nepi/resources/linux/tap.py
src/nepi/resources/linux/tun.py
src/nepi/resources/ns3/ns3netdevice.py
test/resources/linux/ns3/cross_ns3_linux.py [new file with mode: 0755]

diff --git a/src/nepi/resources/linux/ns3/tap_fd_link.py b/src/nepi/resources/linux/ns3/tap_fd_link.py
new file mode 100644 (file)
index 0000000..3f90ccb
--- /dev/null
@@ -0,0 +1,120 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2014 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.resource import ResourceManager, ResourceState, \
+        clsinit_copy
+
+import os
+import socket
+import struct
+import fcntl
+
+@clsinit_copy
+class TapFdLink(ResourceManager):
+    """ Interconnects a TAP or TUN Linux device to a FdNetDevice
+    """
+    _rtype = "linux::ns3::TapFdLink"
+
+    def __init__(self, ec, guid):
+        super(TapFdLink, self).__init__(ec, guid)
+        self._tap = None
+        self._fdnetdevice = None
+        self._fd = None
+
+    @property
+    def fdnetdevice(self):
+        if not self._fdnetdevice:
+            from nepi.resources.ns3.ns3fdnetdevice import NS3BaseFdNetDevice
+            devices = self.get_connected(NS3BaseFdNetDevice.get_rtype())
+            if not devices or len(devices) != 1: 
+                msg = "TapFdLink must be connected to exactly one FdNetDevices"
+                self.error(msg)
+                raise RuntimeError, msg
+
+            self._fdnetdevice = devices[0]
+        
+        return self._fdnetdevice
+
+    @property
+    def fdnode(self):
+        return self.fdnetdevice.node
+
+    @property
+    def tap(self):
+        if not self._tap:
+            from nepi.resources.linux.tap import LinuxTap
+            devices = self.get_connected(LinuxTap.get_rtype())
+            if not devices or len(devices) != 1: 
+                msg = "TapFdLink must be connected to exactly one LinuxTap"
+                self.error(msg)
+                raise RuntimeError, msg
+
+            self._tap = devices[0]
+        
+        return self._tap
+
+    @property
+    def tapnode(self):
+        return self.tap.node
+
+    def do_provision(self):
+        tap = self.tap
+        fdnetdevice = self.fdnetdevice
+
+        vif_name = self.ec.get(tap.guid, "deviceName")
+        vif_type = tap.vif_type_flag
+        pi = self.ec.get(tap.guid, "pi")
+
+        self._fd = self.open_tap(vif_name, vif_type, pi)
+
+        fdnetdevice.send_fd(self._fd)
+
+        super(TapFdLink, self).do_provision()
+
+    def do_deploy(self):
+        if self.tap.state < ResourceState.READY or \
+                self.fdnetdevice.state < ResourceState.READY:
+            self.ec.schedule(self.reschedule_delay, self.deploy)
+        else:
+            self.do_discover()
+            self.do_provision()
+
+            super(TapFdLink, self).do_deploy()
+
+    def open_tap(self, vif_name, vif_type, pi):
+        IFF_NO_PI = 0x1000
+        TUNSETIFF = 0x400454ca
+
+        flags = 0
+        flags |= vif_type
+
+        if not pi:
+            flags |= IFF_NO_PI
+
+        fd = os.open("/dev/net/tun", os.O_RDWR)
+
+        err = fcntl.ioctl(fd, TUNSETIFF, struct.pack("16sH", vif_name, flags))
+        if err < 0:
+            os.close(fd)
+            raise RuntimeError("Could not configure device %s" % vif_name)
+
+        return fd
+
+
diff --git a/src/nepi/resources/linux/route.py b/src/nepi/resources/linux/route.py
new file mode 100644 (file)
index 0000000..b012709
--- /dev/null
@@ -0,0 +1,162 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2014 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.resource import clsinit_copy, ResourceState
+from nepi.resources.linux.application import LinuxApplication
+
+import os
+
+@clsinit_copy
+class LinuxRoute(LinuxApplication):
+    _rtype = "linux::Route"
+    _help = "Adds a route to the host using iptools "
+    _backend = "linux"
+
+    @classmethod
+    def _register_attributes(cls):
+        network = Attribute("network", "Network address", flags=Flags.Design)
+        prefix = Attribute("prefix", "IP prefix length", flags=Flags.Design)
+        nexthop = Attribute("nexthop", "Nexthop IP", flags=Flags.Design)
+
+        cls._register_attribute(network)
+        cls._register_attribute(prefix)
+        cls._register_attribute(nexthop)
+
+    def __init__(self, ec, guid):
+        super(LinuxRoute, self).__init__(ec, guid)
+        self._home = "route-%s" % self.guid
+        self._device = None
+
+    @property
+    def device(self):
+        if not self._device:
+            from nepi.resources.linux.tap import LinuxTap
+            from nepi.resources.linux.tun import LinuxTun
+            from nepi.resources.linux.interface import LinuxInterface
+            tap = self.get_connected(LinuxTap.get_rtype())
+            tun = self.get_connected(LinuxTun.get_rtype())
+            interface = self.get_connected(LinuxInterface.get_rtype())
+            if tap: self._device = tap[0]
+            elif tun: self._device = tun[0]
+            elif interface: self._device = interface[0]
+            else:
+                raise RuntimeError, "linux::Routes must be connected to a "\
+                        "linux::TAP, linux::TUN, or linux::Interface"
+        return self._device
+
+    @property
+    def node(self):
+        return self.device.node
+
+    def upload_start_command(self):
+        # We want to make sure the route is configured
+        # before the deploy is over, so we execute the 
+        # start script now and wait until it finishes. 
+        command = self.get("command")
+        command = self.replace_paths(command)
+
+        shfile = os.path.join(self.app_home, "start.sh")
+        self.node.run_and_wait(command, self.run_home,
+            shfile = shfile,
+            overwrite = True)
+
+    def upload_sources(self):
+        # upload stop.sh script
+        stop_command = self.replace_paths(self._stop_command)
+
+        self.node.upload(stop_command,
+                os.path.join(self.app_home, "stop.sh"),
+                text = True,
+                # Overwrite file every time. 
+                # The stop.sh has the path to the socket, which should change
+                # on every experiment run.
+                overwrite = True)
+
+    def do_deploy(self):
+        if not self.device or self.device.state < ResourceState.PROVISIONED:
+            self.ec.schedule(self.reschedule_delay, self.deploy)
+        else:
+            if not self.get("command"):
+                self.set("command", self._start_command)
+
+            self.do_discover()
+            self.do_provision()
+
+            self.set_ready()
+
+    def do_start(self):
+        if self.state == ResourceState.READY:
+            command = self.get("command")
+            self.info("Starting command '%s'" % command)
+
+            self.set_started()
+        else:
+            msg = " Failed to execute command '%s'" % command
+            self.error(msg, out, err)
+            raise RuntimeError, msg
+
+    def do_stop(self):
+        command = self.get('command') or ''
+        
+        if self.state == ResourceState.STARTED:
+            self.info("Stopping command '%s'" % command)
+
+            command = "bash %s" % os.path.join(self.app_home, "stop.sh")
+            (out, err), proc = self.execute_command(command,
+                    blocking = True)
+
+            if err:
+                msg = " Failed to stop command '%s' " % command
+                self.error(msg, out, err)
+
+            self.set_stopped()
+
+    @property
+    def _start_command(self):
+        network = self.get("network")
+        prefix = self.get("prefix")
+        nexthop = self.get("nexthop")
+        devicename = self.device.get("deviceName")
+
+        command = []
+        command.append("sudo -S ip route add %s/%s %s dev %s" % (
+            self.get("network"),
+            self.get("prefix"),
+            "default" if not nexthop else "via %s" % nexthop,
+            devicename))
+
+        return " ".join(command)
+
+    @property
+    def _stop_command(self):
+        network = self.get("network")
+        prefix = self.get("prefix")
+        nexthop = self.get("nexthop")
+        devicename = self.device.get("deviceName")
+
+        command = []
+        command.append("sudo -S ip route del %s/%s %s dev %s" % (
+            self.get("network"),
+            self.get("prefix"),
+            "default" if not nexthop else "via %s" % nexthop,
+            devicename))
+
+        return " ".join(command)
+
index 4f5492c..636e471 100644 (file)
@@ -34,6 +34,9 @@ class LinuxTap(LinuxApplication):
     _help = "Creates a TAP device on a Linux host"
     _backend = "linux"
 
+    IFF_TUN = 0x0001
+    IFF_TAP = 0x0002
+
     @classmethod
     def _register_attributes(cls):
         endpoint_ip = Attribute("endpoint_ip", "IPv4 Address",
@@ -105,7 +108,7 @@ class LinuxTap(LinuxApplication):
     def node(self):
         node = self.get_connected(LinuxNode.get_rtype())
         if node: return node[0]
-        raise RuntimeError, "TAP/TUN devices must be connected to Node"
+        raise RuntimeError, "linux::TAP/TUN devices must be connected to a linux::Node"
 
     @property
     def gre_enabled(self):
@@ -222,7 +225,7 @@ class LinuxTap(LinuxApplication):
                 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)
@@ -524,6 +527,10 @@ class LinuxTap(LinuxApplication):
     def vif_type(self):
         return "IFF_TAP"
 
+    @property
+    def vif_type_flag(self):
+        return LinuxTap.IFF_TAP
     @property
     def vif_prefix(self):
         return "tap"
index 96317e7..b9537b1 100644 (file)
@@ -40,6 +40,10 @@ class LinuxTun(LinuxTap):
     def vif_type(self):
         return "IFF_TUN"
 
+    @property
+    def vif_type_flag(self):
+        return LinuxTap.IFF_TAP
+
     @property
     def vif_prefix(self):
         return "tun"
index 719f38f..e7c94cf 100644 (file)
@@ -116,6 +116,8 @@ class NS3BaseNetDevice(NS3Base):
                 classname = "WimaxHelper"
             elif rtype == "ns3::WifiNetDevice":
                 classname = "YansWifiPhyHelper"
+            elif rtype == "ns3::FdNetDevice":
+                classname = "FdNetDeviceHelper"
 
             self._device_helper_uuid = self.simulation.create(classname)
 
diff --git a/test/resources/linux/ns3/cross_ns3_linux.py b/test/resources/linux/ns3/cross_ns3_linux.py
new file mode 100755 (executable)
index 0000000..8221ea7
--- /dev/null
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.ec import ExperimentController 
+from nepi.execution.resource import ResourceState, ResourceAction
+from nepi.execution.trace import TraceAttr
+
+from test_utils import skipIfNotAlive
+
+import os
+import time
+import unittest
+
+def add_ns3_node(ec, simu):
+    node = ec.register_resource("ns3::Node")
+    ec.register_connection(node, simu)
+
+    ipv4 = ec.register_resource("ns3::Ipv4L3Protocol")
+    ec.register_connection(node, ipv4)
+
+    arp = ec.register_resource("ns3::ArpL3Protocol")
+    ec.register_connection(node, arp)
+    
+    icmp = ec.register_resource("ns3::Icmpv4L4Protocol")
+    ec.register_connection(node, icmp)
+
+    udp = ec.register_resource("ns3::UdpL4Protocol")
+    ec.register_connection(node, udp)
+
+    return node
+
+def add_fd_device(ec, node, ip, prefix):
+    dev = ec.register_resource("ns3::FdNetDevice")
+    ec.set(dev, "ip", ip)
+    ec.set(dev, "prefix", prefix)
+    ec.register_connection(node, dev)
+
+    return dev
+
+def add_point2point_device(ec, node, ip, prefix):
+    dev = ec.register_resource("ns3::PointToPointNetDevice")
+    ec.set(dev, "ip", ip)
+    ec.set(dev, "prefix", prefix)
+    ec.register_connection(node, dev)
+
+    queue = ec.register_resource("ns3::DropTailQueue")
+    ec.register_connection(dev, queue)
+
+    return dev
+
+class LinuxNS3FdNetDeviceTest(unittest.TestCase):
+    def setUp(self):
+        self.fedora_host = "nepi2.pl.sophia.inria.fr"
+        self.fedora_user = "inria_nepi"
+        self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'])
+
+    @skipIfNotAlive
+    def t_cross_ping(self, host, user = None, identity = None):
+        ec = ExperimentController(exp_id = "test-linux-ns3-tap-fd")
+        
+        node = ec.register_resource("linux::Node")
+        if host == "localhost":
+            ec.set(node, "hostname", "localhost")
+        else:
+            ec.set(node, "hostname", host)
+            ec.set(node, "username", user)
+            ec.set(node, "identity", identity)
+        
+        ec.set(node, "cleanProcesses", True)
+        ec.set(node, "cleanExperiment", True)
+
+        simu = ec.register_resource("linux::ns3::Simulation")
+        ec.set(simu, "simulatorImplementationType", "ns3::RealtimeSimulatorImpl")
+        ec.set(simu, "checksumEnabled", True)
+        ec.set(simu, "verbose", True)
+        #ec.set(simu, "buildMode", "debug")
+        #ec.set(simu, "nsLog", "FdNetDevice")
+        ec.register_connection(simu, node)
+
+        nsnode1 = add_ns3_node(ec, simu)
+        dev1 = add_point2point_device(ec, nsnode1, "10.0.0.1", "30")
+
+        nsnode2 = add_ns3_node(ec, simu)
+        dev2 = add_point2point_device(ec, nsnode2, "10.0.0.2", "30")
+        
+        # Add routes - n1 - n6
+        r1 = ec.register_resource("ns3::Route")
+        ec.set(r1, "network", "10.0.1.0")
+        ec.set(r1, "prefix", "30")
+        ec.set(r1, "nexthop", "10.0.0.1")
+        ec.register_connection(r1, nsnode2)
+
+        # Create channel
+        chan = ec.register_resource("ns3::PointToPointChannel")
+        ec.set(chan, "Delay", "0s")
+        ec.register_connection(chan, dev1)
+        ec.register_connection(chan, dev2)
+
+        # enable traces
+        """
+        ec.enable_trace(dev1, "pcap")
+        ec.enable_trace(dev1, "promiscPcap")
+        ec.enable_trace(dev1, "ascii")
+
+        ec.enable_trace(dev2, "pcap")
+        ec.enable_trace(dev2, "promiscPcap")
+        ec.enable_trace(dev2, "ascii")
+        """
+
+        fddev = add_fd_device(ec, nsnode1, "10.0.1.2", "30")
+        ec.enable_trace(fddev, "pcap")
+        ec.enable_trace(fddev, "promiscPcap")
+        ec.enable_trace(fddev, "ascii")
+
+        tap = ec.register_resource("linux::Tap")
+        ec.set(tap, "endpoint_ip", "10.0.1.1")
+        ec.set(tap, "endpoint_prefix", 30)
+        ec.register_connection(tap, node)
+
+        crosslink = ec.register_resource("linux::ns3::TapFdLink")
+        ec.register_connection(crosslink, tap)
+        ec.register_connection(crosslink, fddev)
+
+        r2 = ec.register_resource("linux::Route")
+        ec.set(r2, "network", "10.0.0.0")
+        ec.set(r2, "prefix", "30")
+        ec.set(r2, "nexthop", "10.0.1.2")
+        ec.register_connection(r2, tap)
+
+        app = ec.register_resource("linux::Application")
+        ec.set(app, "command", "ping -c3 10.0.0.1")
+        ec.register_connection(app, node)
+
+        ec.register_condition(app, ResourceAction.START, simu, 
+                ResourceState.STARTED, time="5s")
+
+        ec.deploy()
+
+        ec.wait_finished([app])
+
+        stdout = ec.trace(app, "stdout")
+        expected = "3 packets transmitted, 3 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+
+        ## Releasing to force ns3 to flush the traces
+        ec.release()
+        pcap = ec.trace(fddev, "pcap")
+
+        self.assertTrue(len(pcap) > 4000)
+        ec.shutdown()
+
+    def ztest_cross_ping_fedora(self):
+        self.t_cross_ping(self.fedora_host, self.fedora_user, self.fedora_identity)
+
+    def test_cross_ping_local(self):
+        self.t_cross_ping("localhost")
+
+
+if __name__ == '__main__':
+    unittest.main()
+