From 4a4647e608e7f848fcc74ccc69524a80c7391876 Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Mon, 15 Dec 2014 11:40:26 +0100 Subject: [PATCH] Simple cross ping experiment between linux and ns3 --- src/nepi/resources/linux/ns3/tap_fd_link.py | 120 +++++++++++++ src/nepi/resources/linux/route.py | 162 ++++++++++++++++++ src/nepi/resources/linux/tap.py | 11 +- src/nepi/resources/linux/tun.py | 4 + src/nepi/resources/ns3/ns3netdevice.py | 2 + test/resources/linux/ns3/cross_ns3_linux.py | 178 ++++++++++++++++++++ 6 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 src/nepi/resources/linux/ns3/tap_fd_link.py create mode 100644 src/nepi/resources/linux/route.py create mode 100755 test/resources/linux/ns3/cross_ns3_linux.py 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 index 00000000..3f90ccb7 --- /dev/null +++ b/src/nepi/resources/linux/ns3/tap_fd_link.py @@ -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 . +# +# Author: Alina Quereilhac + +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 index 00000000..b0127097 --- /dev/null +++ b/src/nepi/resources/linux/route.py @@ -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 . +# +# Author: Alina Quereilhac + +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) + diff --git a/src/nepi/resources/linux/tap.py b/src/nepi/resources/linux/tap.py index 4f5492c7..636e4710 100644 --- a/src/nepi/resources/linux/tap.py +++ b/src/nepi/resources/linux/tap.py @@ -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" diff --git a/src/nepi/resources/linux/tun.py b/src/nepi/resources/linux/tun.py index 96317e72..b9537b12 100644 --- a/src/nepi/resources/linux/tun.py +++ b/src/nepi/resources/linux/tun.py @@ -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" diff --git a/src/nepi/resources/ns3/ns3netdevice.py b/src/nepi/resources/ns3/ns3netdevice.py index 719f38fe..e7c94cf6 100644 --- a/src/nepi/resources/ns3/ns3netdevice.py +++ b/src/nepi/resources/ns3/ns3netdevice.py @@ -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 index 00000000..8221ea7b --- /dev/null +++ b/test/resources/linux/ns3/cross_ns3_linux.py @@ -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 . +# +# Author: Alina Quereilhac + +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() + -- 2.43.0