--- /dev/null
+#
+# 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
+
+
--- /dev/null
+#
+# 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)
+
_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",
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):
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)
def vif_type(self):
return "IFF_TAP"
+ @property
+ def vif_type_flag(self):
+ return LinuxTap.IFF_TAP
+
@property
def vif_prefix(self):
return "tap"
def vif_type(self):
return "IFF_TUN"
+ @property
+ def vif_type_flag(self):
+ return LinuxTap.IFF_TAP
+
@property
def vif_prefix(self):
return "tun"
classname = "WimaxHelper"
elif rtype == "ns3::WifiNetDevice":
classname = "YansWifiPhyHelper"
+ elif rtype == "ns3::FdNetDevice":
+ classname = "FdNetDeviceHelper"
self._device_helper_uuid = self.simulation.create(classname)
--- /dev/null
+#!/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()
+