From: Thierry Parmentelat Date: Mon, 3 Sep 2012 14:02:01 +0000 (+0200) Subject: created from Alina's mercurial repo -- hg clone http://nepi.inria.fr/code/python... X-Git-Tag: vsys-scripts-0.95-44~4 X-Git-Url: http://git.onelab.eu/?p=vsys-scripts.git;a=commitdiff_plain;h=d9f473b1015f3f14cab94463efec6f67451a4072 created from Alina's mercurial repo -- hg clone nepi.inria.fr/code/python-vsys --- diff --git a/slice-context/Makefile b/slice-context/Makefile new file mode 100644 index 0000000..0dacbf0 --- /dev/null +++ b/slice-context/Makefile @@ -0,0 +1,44 @@ +SRC = src +TEST = test +BUILDDIR = build +DISTDIR = dist + +SUBBUILDDIR = $(shell python -c 'import distutils.util, sys; print "lib.%s-%s" % (distutils.util.get_platform(), sys.version[0:3])') +BUILDDIR := $(BUILDDIR)/$(SUBBUILDDIR) + +COVERAGE = $(or $(shell which coverage), $(shell which python-coverage), \ + coverage) + +all: + ./setup.py build + +install: all + ./setup.py install + +test: all + retval=0; \ + for i in `find "$(TEST)" -perm -u+x -type f`; do \ + echo $$i; \ + PYTHONPATH="$(BUILDDIR):$$PYTHONPATH" $$i || retval=$$?; \ + done; exit $$retval + +coverage: all + rm -f .coverage + for i in `find "$(TEST)" -perm -u+x -type f`; do \ + set -e; \ + PYTHONPATH="$(BUILDDIR):$$PYTHONPATH" $(COVERAGE) -x $$i; \ + done + $(COVERAGE) -r -m `find "$(BUILDDIR)" -name \\*.py -type f` + rm -f .coverage + +clean: + ./setup.py clean + rm -f `find -name \*.pyc` .coverage + +distclean: clean + rm -rf "$(DISTDIR)" + +dist: + ./setup.py sdist + +.PHONY: clean distclean dist test coverage install diff --git a/slice-context/setup.cfg b/slice-context/setup.cfg new file mode 100644 index 0000000..e1b8322 --- /dev/null +++ b/slice-context/setup.cfg @@ -0,0 +1,2 @@ +[clean] +all = 1 diff --git a/slice-context/setup.py b/slice-context/setup.py new file mode 100755 index 0000000..87236e3 --- /dev/null +++ b/slice-context/setup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from distutils.core import setup, Extension +import sys +sys.path.append("src") +import vsys + +ext = Extension('_vsys', sources = ['src/vsys.c']) + +setup( name = 'python-vsys', + version = '0.1', + description = 'Python functions to wrap PlanetLab vsys API', + long_description = vsys.__doc__, + author = 'Alina Quereilhac', + author_email = 'alina.quereilhac@inria.fr', + url = 'http://nepi.inria.fr/code/python-vsys', + platforms = 'Linux', + package_dir = {'': 'src'}, + ext_modules = [ext], + py_modules = ['vsys']) diff --git a/slice-context/src/vsys.c b/slice-context/src/vsys.c new file mode 100644 index 0000000..186e47f --- /dev/null +++ b/slice-context/src/vsys.c @@ -0,0 +1,428 @@ +/* + * vsys.c: CPython functions to wrap PlanetLab vsys API' + * Alina Quereilhac - 02/09/2012 + * + * Copyright (c) 2012 INRIA + * + */ + +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VSYS_TUNTAP "/vsys/fd_tuntap.control" +#define VSYS_VIFUP_IN "/vsys/vif_up.in" +#define VSYS_VIFUP_OUT "/vsys/vif_up.out" +#define VSYS_VIFDOWN_IN "/vsys/vif_down.in" +#define VSYS_VIFDOWN_OUT "/vsys/vif_down.out" +#define VSYS_VROUTE_IN "/vsys/vroute.in" +#define VSYS_VROUTE_OUT "/vsys/vroute.out" + +const size_t SIZE = 4096; + +int _fd_tuntap(int if_type, int no_pi, char *if_name); +int _vif_up(const char *if_name, const char *ip, const char *prefix, int snat, + char *msg); +int _vif_down(const char *if_name, char *msg); +int _vroute (const char *action, const char *network, const char *prefix, + const char *host, const char *device, char *msg); + +/* Python wrapper for _fd_tuntap */ +static PyObject * +fd_tuntap(PyObject *self, PyObject *args) { + int if_type, fd, no_pi = 0; + char *if_name; + PyObject *retval; + + if(!PyArg_ParseTuple(args, "i|i", &if_type, &no_pi)) + return NULL; + + if((if_name = malloc(SIZE)) == NULL) + return PyErr_SetFromErrno(PyExc_OSError); + + // just in case, initialize the buffer to 0 + memset(if_name, 0, SIZE); + + Py_BEGIN_ALLOW_THREADS; + fd = _fd_tuntap(if_type, no_pi, if_name); + Py_END_ALLOW_THREADS; + + retval = Py_BuildValue("is", fd, if_name); + free(if_name); + return retval; +} + +/* Python wrapper for _vif_up */ +static PyObject * +vif_up(PyObject *self, PyObject *args) { + int ret, snat = 0; //false + char *if_name, *ip, *prefix, *msg; + PyObject *retval; + + if(!PyArg_ParseTuple(args, "sss|i", &if_name, &ip, &prefix, &snat)) + return NULL; + + if((msg = malloc(SIZE)) == NULL) + return PyErr_SetFromErrno(PyExc_OSError); + + // just in case, initialize the buffer to 0 + memset(msg, 0, SIZE); + + Py_BEGIN_ALLOW_THREADS; + ret = _vif_up(if_name, ip, prefix, snat, msg); + Py_END_ALLOW_THREADS; + + retval = Py_BuildValue("is", ret, msg); + free(msg); + return retval; +} + +/* Python wrapper for _vif_down */ +static PyObject * +vif_down(PyObject *self, PyObject *args) { + int ret; + char *if_name, *msg; + PyObject *retval; + + if(!PyArg_ParseTuple(args, "s", &if_name)) + return NULL; + + if((msg = malloc(SIZE)) == NULL) + return PyErr_SetFromErrno(PyExc_OSError); + + // just in case, initialize the buffer to 0 + memset(msg, 0, SIZE); + + Py_BEGIN_ALLOW_THREADS; + ret = _vif_down(if_name, msg); + Py_END_ALLOW_THREADS; + + retval = Py_BuildValue("is", ret, msg); + free(msg); + return retval; +} + +/* Python wrapper for _vroute */ +static PyObject * +vroute(PyObject *self, PyObject *args) { + int ret; + char *action, *network, *prefix, *host, *device, *msg; + PyObject *retval; + + if(!PyArg_ParseTuple(args, "sssss", &action, &network, &prefix, &host, &device)) + return NULL; + + if((msg = malloc(SIZE)) == NULL) + return PyErr_SetFromErrno(PyExc_OSError); + + // just in case, initialize the buffer to 0 + memset(msg, 0, SIZE); + + Py_BEGIN_ALLOW_THREADS; + ret = _vroute(action, network, prefix, host, device, msg); + Py_END_ALLOW_THREADS; + + retval = Py_BuildValue("is", ret, msg); + free(msg); + return retval; +} + +static PyMethodDef methods[] = { + {"fd_tuntap", fd_tuntap, METH_VARARGS, "(fd, if_name) = fd_tuntap(if_type, no_pi = False)"}, + {"vif_up", vif_up, METH_VARARGS, "(code, msg) = vif_up(if_name, ip, prefix, snat = False)"}, + {"vif_down", vif_down, METH_VARARGS, " (code, msg) = vif_up(if_name)"}, + {"vroute", vroute, METH_VARARGS, "(code, msg) = vroute(action, network, prefix, host, device)"}, + {NULL, NULL, 0, NULL} +}; + +PyMODINIT_FUNC init_vsys(void) { + PyObject *m; + m = Py_InitModule("_vsys", methods); + if (m == NULL) + return; +} + +/* + * _fifo_push(): Pushes parameters into a fifo. + * + * Parameters: + * fifo_in: the name of the fifo to write parameters to + * fifo_out: the name of the fifo to read error from + * input: parameters formated as a string to push into the input fifo + * msg: buffer to return error to user + * + * Return value: + * On success, _fifo_push returns 0. + * On error, a negative integer is returned. + * + */ +static int _fifo_push (const char *fifo_in, const char *fifo_out, + const char *input, char *msg) { + FILE *in; + FILE *out; + int nbytes; + + in = fopen (fifo_in, "a"); + + if (in == NULL){ + snprintf (msg, SIZE, "Failed to open FIFO %s", fifo_in); + return -1; + } + + out = fopen (fifo_out, "r"); + + if (out == NULL){ + snprintf (msg, SIZE, "Failed to open FIFO %s", fifo_out); + return -2; + } + + // send inputs to the fifo_in + fprintf (in, input); + + // force flush and close the fifo_in so the program on the other side + // can process input. + fclose (in); + + nbytes = fread(msg, SIZE, 1, out); + + // the error buffer will not be empty if we read an error + if (strcmp(msg, "") != 0){ + // an errror was read from the fifo_out ... + return -3; + } + + fclose (out); + + return 0; +} + +/* + * _receive_fd(): Receives the file descriptor associated to the virtual + * device + * + * Parameters: + * vsys_sock: the vsys control socket which send the fd + * if_name: the buffer where the device name assigned by PlanetLab + * will be stored after creating the virtual interface + * + * Return value: + * On success, _receive_fd returns the file descriptor associated to the device. + * On error, a negative integer is returned. + * + */ +static int _receive_fd(int vsys_sock, char *if_name) { + struct msghdr msg; + struct iovec iov; + size_t ccmsg[CMSG_SPACE (sizeof(int)) / sizeof(size_t)]; + struct cmsghdr *cmsg; + int rv; + + // Use IOV to read interface name + iov.iov_base = if_name; + iov.iov_len = IFNAMSIZ; + + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + // old BSD implementations should use msg_accrights instead of msg_control. + // the interface is different. + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + while (((rv = recvmsg (vsys_sock, &msg, 0)) == -1) && errno == EINTR) + {} + + cmsg = CMSG_FIRSTHDR (&msg); + if (cmsg->cmsg_type != SCM_RIGHTS){ + snprintf (if_name, SIZE, "Got control message of unknown type"); + return -1; + } + + int* retfd = (int*)CMSG_DATA (cmsg); + return *retfd; +} + +/* + * _fd_tuntap(): Creates a TAP or TUN device in PlanetLab, and returns the + * device name and the associated file descriptor. + * + * Parameters: + * if_type: the type of virtual device. Either IFF_TAP (0x0001) or + * IFF_TUN (0x0002) + * no_pi: set flag IFF_NO_PI + * if_name: the buffer where the device name assigned by PlanetLab + * will be stored after creating the virtual interface. + * When an error ocurrs, if_name is used as message buffer + * + * Return value: + * On success, _fd_tuntap returns the file descriptor associated to the device. + * On error, a negative integer is returned. + * + */ +int _fd_tuntap(int if_type, int no_pi, char *if_name) { + int vsys_sock, fd; + struct sockaddr_un addr; + + vsys_sock = socket (AF_UNIX, SOCK_STREAM, 0); + + if (vsys_sock == -1){ + snprintf (if_name, SIZE, strerror(errno)); + return -2; + } + + memset (&addr, 0, sizeof(struct sockaddr_un)); + + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, VSYS_TUNTAP, sizeof(addr.sun_path) - 1); + + int ret = connect (vsys_sock, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)); + + if (ret == -1){ + snprintf (if_name, SIZE, strerror(errno)); + return -3; + } + + // send vif type (either TAP or TUN) + ret = send (vsys_sock, &if_type, sizeof(if_type), 0); + + if (ret != sizeof(if_type)){ + snprintf (if_name, SIZE, "Could not send parameter to vsys control socket"); + return -4; + } + + // receive the file descriptor associated to the virtual interface + fd = _receive_fd (vsys_sock, if_name); + + if (fd < 0){ + snprintf (if_name, SIZE, "Invalid file descriptor"); + return -5; + } + +/** +* XXX: For now unsupported functionality +* + struct ifreq ifr; + ret = ioctl (fd, TUNGETIFF, (void *)&ifr); + if (ret == -1){ + close (fd); + snprintf (if_name, SIZE, strerror(errno)); + return -6; + } + + // flag IFF_NO_PI: don't add the extra PI header + if (no_pi){ + ifr.ifr_flags |= IFF_NO_PI; + } + + // flag IFF_ONE_QUEUE: disable normal network device queue and use only the + // tun/tap driver internal queue. QoS tools will not be + // able to control queueing for the device in this case. + ifr.ifr_flags |= IFF_ONE_QUEUE; + + ret = ioctl (fd, TUNSETIFF, (void *)&ifr); + if (ret == -1){ + close (fd); + snprintf (if_name, SIZE, strerror(errno)); + return -7; + } +*/ + + return fd; + +} + +/* + * _vif_up(): Configures a virtual interface with the values given by the user + * and sets its state UP + * + * Parameters: + * if_name: the name of the virtual interface + * ip: the IP address to be assigned to the interface + * prefix: the network prefix associated to the IP address + * snat: whether to enable SNAT on the virtual interface. + * msg: buffer to return error to user + * + * Return value: + * On success, return 0. + * On error, a negative integer is returned. + * + */ +int _vif_up (const char *if_name, const char *ip, const char *prefix, + int snat, char *msg) { + + char input[SIZE]; // big buffer + + snprintf (input, SIZE, "%s\n%s\n%s\nsnat=%d\n", if_name, ip, prefix, snat); + + return _fifo_push (VSYS_VIFUP_IN, VSYS_VIFUP_OUT, input, msg); + +} + +/* + * _vif_down(): Sets the state of a virtual interface DOWN + * + * Parameters: + * if_name: the name of the virtual interface + * msg: buffer to return error to user + * + * Return value: + * On success, return 0. + * On error, a negative integer is returned. + * + */ +int _vif_down (const char *if_name, char *msg) { + + char input[SIZE]; + + snprintf(input, SIZE, "%s\n", if_name); + + return _fifo_push (VSYS_VIFDOWN_IN, VSYS_VIFDOWN_OUT, input, msg); + +} + +/* + * vroute() : Adds or removes routes on PlanetLab virtual interfaces (TAP/TUN). + * + * Note that all networks and gateways must belong to the virtual + * network segment associated to the vsys_vnet tag for the slice. + * + * Parameters: + * action: either 'add' or 'del' + * network: destintation network + * prefix: destination network prefix + * host: IP of gateway virtual interface + * device: name of the gateway virtual interface + * msg: buffer to return error to user + * + * Return value: + * On success, vroute returns 0. + * On error, a negative integer is returned. + * + */ +int _vroute (const char *action, const char *network, const char *prefix, + const char *host, const char *device, char *msg) { + + char input[SIZE]; + + snprintf(input, SIZE, "%s %s/%s gw %s %s\n", action, network, prefix, host, device); + + return _fifo_push (VSYS_VROUTE_IN, VSYS_VROUTE_OUT, input, msg); + +} + diff --git a/slice-context/src/vsys.py b/slice-context/src/vsys.py new file mode 100644 index 0000000..aefc560 --- /dev/null +++ b/slice-context/src/vsys.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +# vsys.py: Python functions to wrap PlanetLab vsys API' +# Alina Quereilhac - 02/09/2012 +# +# Copyright (c) 202 INRIA +# + +''' This extension provides easy to use Python functions to +interact with the PlanetLab vsys API. + +The vsys API is presented in the following publication: + +Vsys: A programmable sudo +S Bhatia, G Di Stasi, T Haddow, A Bavier, S Muir, L Peterson +In USENIX 2011 + +''' + +IFF_TUN = 0x0001 +IFF_TAP = 0x0002 + +def vif_up(if_name, ip, prefix, snat = False): + """ Configures a virtual interface with the values given by the user and + sets its state UP + + Parameters: + if_name: the name of the virtual interface + ip: the IP address to be assigned to the interface + prefix: the network prefix associated to the IP address + snat: whether to enable SNAT on the virtual interface. + + Return value: + On success, return 0. + On error, a RuntimeError is raised.""" + + import _vsys + (code, msg) = _vsys.vif_up(if_name, ip, str(prefix), snat) + + if code < 0: + raise RuntimeError(msg) + + +def vif_down(if_name): + """ Sets the state of a virtual interface DOWN + + Parameters: + if_name: the name of the virtual interface + + Return value: + On success, return 0. + On error, a RuntimeError is raised.""" + + import _vsys + (code, msg) = _vsys.vif_down(if_name) + + if code < 0: + raise RuntimeError(msg) + +def fd_tuntap(if_type, no_pi = False): + """Creates a TAP or TUN device in PlanetLab, and returns the device name and + the associated file descriptor. + + Parameters: + if_type: the type of virtual device. Either IFF_TAP (0x0001) or + IFF_TUN (0x0002) + no_pi: set flag IFF_NO_PI + + Return value: + On success, fd_tuntap returns a tuple containing the file descriptor + associated to the device and the device name assigned by the PlanetLab + vsys script. + On error, a RuntimeError is raised.""" + + import _vsys + (fd, if_name) = _vsys.fd_tuntap(if_type, no_pi) + + if fd < 0: + raise RuntimeError(if_name) + + return (fd, if_name) + +def vroute(action, network, prefix, host, device): + """ Adds or removes routes on PlanetLab virtual interfaces (TAP/TUN). + + Note that all networks and gateways must belong to the virtual + network segment associated to the vsys_vnet tag for the slice. + + Parameters: + action: either 'add' or 'del' + network: destintation network + prefix: destination network prefix + host: IP of gateway virtual interface + device: name of the gateway virtual interface + + Return value: + On success, vroute returns 0. + On error, a RuntimeError is raised.""" + + import _vsys + (code, msg) = _vsys.vroute(action, network, str(prefix), host, device) + + if code < 0: + raise RuntimeError(msg) + + diff --git a/slice-context/test/test_vsys.py b/slice-context/test/test_vsys.py new file mode 100755 index 0000000..fc68803 --- /dev/null +++ b/slice-context/test/test_vsys.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2012 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 2 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, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Author: Alina Quereilhac +# + +import subprocess +import unittest +import time + +from vsys import fd_tuntap, vif_up, vif_down, vroute, IFF_TAP, IFF_TUN +import _vsys + +class TestVsys(unittest.TestCase): + def setUp(self): + self._network = "192.168.2.0" + self._prefix = 30 + self._ip = "192.168.2.2" + self._remote_net = "192.168.2.4" + + def _create_vif(self, if_type, no_pi = False): + ####### create virtual device + (fd, if_name) = fd_tuntap(if_type, no_pi) + self.assertTrue(fd > 0) + + ###### configure virtual device + vif_up(if_name, self._ip, self._prefix) + + # wait for prcocess to see the new configuration... + time.sleep(5) + + ###### test ip responds to pings + p = subprocess.Popen(["ping", "-qc3", self._ip], stdout=subprocess.PIPE, stdin=subprocess.PIPE) + out, err = p.communicate() + + self.assertFalse(err) + + expected = """PING %(ip)s (%(ip)s) 56(84) bytes of data. + +--- %(ip)s ping statistics --- +3 packets transmitted, 3 received, 0%% packet loss""" % {'ip': self._ip} + + self.assertTrue(out.startswith(expected), out) + + ###### add route + vroute ("add", self._remote_net, self._prefix, self._ip, if_name) + + # wait for prcocess to see the new configuration... + time.sleep(5) + + ###### test routes + p = subprocess.Popen(["ip", "r"], stdout=subprocess.PIPE, stdin=subprocess.PIPE) + out, err = p.communicate() + + self.assertFalse(err) + self.assertTrue(out.find(self._remote_net) >= 0 ) + + ###### del route + vroute ("del", self._remote_net, self._prefix, self._ip, if_name) + + # wait for prcocess to see the new configuration... + time.sleep(5) + + ##### test routes + p = subprocess.Popen(["ip", "r"], stdout=subprocess.PIPE, stdin=subprocess.PIPE) + out, err = p.communicate() + + self.assertFalse(err) + self.assertTrue(out.find(self._remote_net) < 0 ) + + ##### delete interface + vif_down(if_name) + + # wait for prcocess to see the new configuration... + time.sleep(5) + + ###### test ip NOT responds to pings + p = subprocess.Popen(["ping", "-qc3", self._ip], stdout=subprocess.PIPE, stdin=subprocess.PIPE) + out, err = p.communicate() + + self.assertFalse(err) + + expected = """PING %(ip)s (%(ip)s) 56(84) bytes of data. + +--- %(ip)s ping statistics --- +3 packets transmitted, 0 received, 100%% packet loss""" % {'ip': self._ip} + + self.assertTrue(out.startswith(expected), out) + + + def test_create_tun(self): + self._create_vif(IFF_TUN) + + def test_create_tap(self): + self._create_vif(IFF_TAP) + + def test_create_tun_no_pi(self): + self._create_vif(IFF_TUN, no_pi = True) + + def test_create_tap_no_pi(self): + self._create_vif(IFF_TAP, no_pi = True) + + + +if __name__ == '__main__': + unittest.main() +