From: Alina Quereilhac Date: Tue, 29 Jul 2014 20:07:50 +0000 (+0200) Subject: Adding LinuxTap X-Git-Tag: nepi-3.2.0~111 X-Git-Url: http://git.onelab.eu/?p=nepi.git;a=commitdiff_plain;h=afadbc0a4f73d0f53c57bd93767714c12f0c6a5e Adding LinuxTap --- diff --git a/src/nepi/resources/linux/tap.py b/src/nepi/resources/linux/tap.py new file mode 100644 index 00000000..7390dfb5 --- /dev/null +++ b/src/nepi/resources/linux/tap.py @@ -0,0 +1,287 @@ +# +# 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.attribute import Attribute, Flags, Types +from nepi.execution.resource import clsinit_copy, ResourceState, \ + reschedule_delay +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): + _rtype = "LinuxTap" + _help = "Creates a TAP device on a Linux host" + _backend = "linux" + + @classmethod + def _register_attributes(cls): + ip4 = Attribute("ip4", "IPv4 Address", + flags = Flags.Design) + + mac = Attribute("mac", "MAC Address", + flags = Flags.Design) + + prefix4 = Attribute("prefix4", "IPv4 network prefix", + type = Types.Integer, + flags = Flags.Design) + + mtu = Attribute("mtu", "Maximum transmition unit for device", + type = Types.Integer) + + devname = Attribute("deviceName", + "Name of the network interface (e.g. eth0, wlan0, etc)", + flags = Flags.NoWrite) + + up = Attribute("up", "Link up", + type = Types.Bool) + + pointopoint = Attribute("pointopoint", "Peer IP address", + flags = Flags.Design) + + txqueuelen = Attribute("txqueuelen", "Length of transmission queue", + flags = Flags.Design) + + txqueuelen = Attribute("txqueuelen", "Length of transmission queue", + flags = Flags.Design) + + gre_key = Attribute("greKey", + "GRE key to be used to configure GRE tunnel", + default = "1", + flags = Flags.Design) + + gre_remote = Attribute("greRemote", + "Public IP of remote endpoint for GRE tunnel", + 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(mac) + cls._register_attribute(prefix4) + cls._register_attribute(mtu) + cls._register_attribute(devname) + cls._register_attribute(up) + cls._register_attribute(pointopoint) + cls._register_attribute(txqueuelen) + cls._register_attribute(gre_key) + cls._register_attribute(gre_remote) + 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 + + @property + def node(self): + node = self.get_connected(LinuxNode.get_rtype()) + if node: return node[0] + return None + + @property + def gre_enabled(self): + if not self._gre_enabled: + from nepi.resources.linux.gretunnel import LinuxGRETunnel + gre = self.get_connected(LinuxGRETunnel.get_rtype()) + if gre: self._gre_enabled = True + + return self._gre_enabled + + 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 upload_start_command(self): + # If GRE mode is enabled, TAP creation is delayed until the + # tunnel is established + if not self.gre_enabled: + # 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. + 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 do_deploy(self): + if not self.node or self.node.state < ResourceState.PROVISIONED: + self.ec.schedule(reschedule_delay, self.deploy) + else: + if not self.get("deviceName"): + self.set("deviceName", "%s%d" % (self.vif_prefix, self.guid)) + + 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 state(self): + state_check_delay = 0.5 + if self._state == ResourceState.STARTED and \ + tdiffsec(tnow(), self._last_state_check) > state_check_delay: + + if self.get("deviceName"): + (out, err), proc = self.node.execute("ifconfig") + + if out.strip().find(self.get("deviceName")) == -1: + # tap is not running is not running (socket not found) + self.set_stopped() + + self._last_state_check = tnow() + + return self._state + + def do_release(self): + # Node needs to wait until all associated RMs are released + # to be released + from nepi.resources.linux.tunnel import LinuxTunnel + rms = self.get_connected(LinuxTunnel.get_rtype()) + + for rm in rms: + if rm.state < ResourceState.STOPPED: + self.ec.schedule(reschedule_delay, self.release) + return + + super(LinuxTap, self).do_release() + + 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"))) + + # Generate GRE connect command + command = ["("] + command.append(self._stop_command) + command.append(") ; (") + command.append(self._start_gre_command) + command.append(")") + + command = " ".join(command) + command = self.replace_paths(command) + + return 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) + + @property + def _stop_command(self): + command = [] + command.append("sudo -S ip link set %s down" % self.get("deviceName")) + command.append("sudo -S ip link del %s" % self.get("deviceName")) + + return ";".join(command) + + @property + def _start_gre_command(self): + command = [] + command.append("sudo -S modprobe ip_gre") + 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.get("greKey") + )) + command.append("sudo -S addr add dev %s %s/%d peer %s/%d" % ( + self.get("deviceName"), + self.get("ip4"), + self.get("prefix4"), + self.get("pointopoint"), + self.get("prefix4"), + )) + command.append("sudo -S ip link set %s up " % self.get("deviceName")) + + return ";".join(command) + + @property + def vif_type(self): + return "IFF_TAP" + + @property + def vif_prefix(self): + return "tap" + + def sock_name(self): + return os.path.join(self.run_home, "tap.sock") + + def valid_connection(self, guid): + # TODO: Validate! + return True + diff --git a/src/nepi/resources/linux/tun.py b/src/nepi/resources/linux/tun.py new file mode 100644 index 00000000..5872f8e0 --- /dev/null +++ b/src/nepi/resources/linux/tun.py @@ -0,0 +1,47 @@ +# +# 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.resource import clsinit_copy +from nepi.resources.linux.tap import LinuxTap + +import os + +@clsinit_copy +class LinuxTun(LinuxTap): + _rtype = "LinuxTun" + _help = "Creates a TUN device on a Linux host" + _backend = "linux" + + def __init__(self, ec, guid): + super(LinuxTun, self).__init__(ec, guid) + self._home = "tun-%s" % self.guid + + @property + def sock_name(self): + return os.path.join(self.run_home, "tun.sock") + + @property + def vif_type(self): + return "IFF_TUN" + + @property + def vif_prefix(self): + return "tun" + + diff --git a/test/resources/linux/tap.py b/test/resources/linux/tap.py new file mode 100755 index 00000000..faa950e1 --- /dev/null +++ b/test/resources/linux/tap.py @@ -0,0 +1,76 @@ +#!/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 test_utils import skipIfNotAlive + +import os +import time +import unittest + +class LinuxTapTestCase(unittest.TestCase): + def setUp(self): + self.host = "roseval.pl.sophia.inria.fr" + self.user = "inria_nepi" + self.identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME']) + self.netblock = "192.168.1" + + @skipIfNotAlive + def t_tap_create(self, host, user, identity): + + ec = ExperimentController(exp_id = "test-tap-create") + + node = ec.register_resource("LinuxNode") + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + ec.set(node, "cleanHome", True) + ec.set(node, "cleanProcesses", True) + + tap = ec.register_resource("LinuxTap") + ec.set(tap, "ip4", "%s.1" % self.netblock) + ec.set(tap, "prefix4", 24) + ec.register_connection(tap, node) + + app = ec.register_resource("LinuxApplication") + cmd = "ping -c3 %s.1" % self.netblock + ec.set(app, "command", cmd) + ec.register_connection(app, node) + + ec.deploy() + + ec.wait_finished(app) + + ping = ec.trace(app, 'stdout') + expected = """3 packets transmitted, 3 received, 0% packet loss""" + self.assertTrue(ping.find(expected) > -1) + + if_name = ec.get(tap, "deviceName") + self.assertTrue(if_name.startswith("tap")) + + ec.shutdown() + + def test_tap_create(self): + self.t_tap_create(self.host, self.user, self.identity) + +if __name__ == '__main__': + unittest.main() + diff --git a/test/resources/linux/tun.py b/test/resources/linux/tun.py new file mode 100755 index 00000000..5a02be7b --- /dev/null +++ b/test/resources/linux/tun.py @@ -0,0 +1,76 @@ +#!/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 test_utils import skipIfNotAlive + +import os +import time +import unittest + +class LinuxTunTestCase(unittest.TestCase): + def setUp(self): + self.host = "roseval.pl.sophia.inria.fr" + self.user = "inria_nepi" + self.identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME']) + self.netblock = "192.168.1" + + @skipIfNotAlive + def t_tun_create(self, host, user, identity): + + ec = ExperimentController(exp_id = "test-tun-create") + + node = ec.register_resource("LinuxNode") + ec.set(node, "hostname", host) + ec.set(node, "username", user) + ec.set(node, "identity", identity) + ec.set(node, "cleanHome", True) + ec.set(node, "cleanProcesses", True) + + tun = ec.register_resource("LinuxTun") + ec.set(tun, "ip4", "%s.1" % self.netblock) + ec.set(tun, "prefix4", 24) + ec.register_connection(tun, node) + + app = ec.register_resource("LinuxApplication") + cmd = "ping -c3 %s.1" % self.netblock + ec.set(app, "command", cmd) + ec.register_connection(app, node) + + ec.deploy() + + ec.wait_finished(app) + + ping = ec.trace(app, 'stdout') + expected = """3 packets transmitted, 3 received, 0% packet loss""" + self.assertTrue(ping.find(expected) > -1) + + if_name = ec.get(tun, "deviceName") + self.assertTrue(if_name.startswith("tun")) + + ec.shutdown() + + def test_tun_create(self): + self.t_tun_create(self.host, self.user, self.identity) + +if __name__ == '__main__': + unittest.main() +