From: Claudio-Daniel Freire Date: Mon, 18 Apr 2011 07:17:35 +0000 (+0200) Subject: Add scripts folder, for scripts that will be needed during deployment. X-Git-Tag: nepi_v2~155 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=a6bc39ddc909b991a4800750d701bcc17737489a;p=nepi.git Add scripts folder, for scripts that will be needed during deployment. Add tun_connect script, a script to set up tunnelling in both generic unix and planetlab nodes. --- diff --git a/scripts/tun_connect.py b/scripts/tun_connect.py new file mode 100644 index 00000000..948b78cd --- /dev/null +++ b/scripts/tun_connect.py @@ -0,0 +1,435 @@ +import sys + +import socket +import fcntl +import os +import select + +import struct +import ctypes +import optparse +import threading +import subprocess +import re +import functools + +tun_name = 'tun0' +tun_path = '/dev/net/tun' +hostaddr = socket.gethostbyname(socket.gethostname()) + +usage = "usage: %prog [options] " + +parser = optparse.OptionParser(usage=usage) + +parser.add_option( + "-i", "--iface", dest="tun_name", metavar="DEVICE", + default = "tun0", + help = "TUN/TAP interface to tap into") +parser.add_option( + "-d", "--tun-path", dest="tun_path", metavar="PATH", + default = "/dev/net/tun", + help = "TUN/TAP device file path or file descriptor number") + +parser.add_option( + "-m", "--mode", dest="mode", metavar="MODE", + default = "none", + help = + "Set mode. One of none, tun, tap, pl-tun, pl-tap. In any mode except none, a TUN/TAP will be created " + "by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), " + "and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have " + "to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).") +parser.add_option( + "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS", + default = None, + help = + "See mode. This specifies the VIF_ADDRESS, " + "the IP address of the virtual interface.") +parser.add_option( + "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK", + default = None, + help = + "See mode. This specifies the VIF_MASK, " + "a number indicating the network type (ie: 24 for a C-class network).") +parser.add_option( + "-S", "--vif-snat", dest="vif_snat", + action = "store_true", + default = False, + help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " ) +parser.add_option( + "-P", "--vif-pointopoint", dest="vif_pointopoint", metavar="DST_ADDR", + default = None, + help = + "See mode. This specifies the remote endpoint's virtual address, " + "for point-to-point routing configuration. " + "Not supported by PlanetLab" ) +parser.add_option( + "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int", + default = None, + help = + "See mode. This specifies the interface's transmission queue length. " ) + +(options, remaining_args) = parser.parse_args(sys.argv[1:]) + + +ETH_P_ALL = 0x00000003 +ETH_P_IP = 0x00000800 +TUNSETIFF = 0x400454ca +IFF_NO_PI = 0x00001000 +IFF_TAP = 0x00000002 +IFF_TUN = 0x00000001 +IFF_VNET_HDR = 0x00004000 +TUN_PKT_STRIP = 0x00000001 +IFHWADDRLEN = 0x00000006 +IFNAMSIZ = 0x00000010 +IFREQ_SZ = 0x00000028 +FIONREAD = 0x0000541b + +def ifnam(x): + return x+'\x00'*(IFNAMSIZ-len(x)) + +def ifreq(iface, flags): + # ifreq contains: + # char[IFNAMSIZ] : interface name + # short : flags + # + ifreq = ifnam(iface)+struct.pack("H",flags); + ifreq += '\x00' * (len(ifreq)-IFREQ_SZ) + return ifreq + +def tunopen(tun_path, tun_name): + if tun_path.isdigit(): + # open TUN fd + print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path + tun = os.fdopen(int(tun_path), 'r+b', 0) + else: + # open TUN path + print >>sys.stderr, "Using tun:", tun_name, "at", tun_path + tun = open(tun_path, 'r+b', 0) + + # bind file descriptor to the interface + fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN)) + + return tun + +def tunclose(tun_path, tun_name, tun): + if tun_path.isdigit(): + # close TUN fd + os.close(int(tun_path)) + tun.close() + else: + # close TUN object + tun.close() + +def tuntap_alloc(kind, tun_path, tun_name): + args = ["tunctl"] + if kind == "tun": + args.append("-n") + if tun_name: + args.append("-t") + args.append(tun_name) + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + if proc.wait(): + raise RuntimeError, "Could not allocate %s device" % (kind,) + + match = re.search(r"Set '(?P(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I) + if not match: + raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out) + + tun_name = match.group("dev") + print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name) + + return tun_path, tun_name + +def tuntap_dealloc(tun_path, tun_name): + args = ["tunctl", "-d", tun_name] + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + if proc.wait(): + print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,) + +def nmask_to_dot_notation(mask): + mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00 + mask = mask[2:] # strip 0x + mask = mask.decode("hex") # to bytes + mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0 + return mask + +def vif_start(tun_path, tun_name): + args = ["ifconfig", tun_name, options.vif_addr, + "netmask", nmask_to_dot_notation(options.vif_mask), + "-arp" ] + if options.vif_pointopoint: + args.extend(["pointopoint",options.vif_pointopoint]) + if options.vif_txqueuelen is not None: + args.extend(["txqueuelen",str(options.vif_txqueuelen)]) + args.append("up") + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + if proc.wait(): + raise RuntimeError, "Error starting virtual interface" + + if options.vif_snat: + # set up SNAT using iptables + # TODO: stop vif on error. + # Not so necessary since deallocating the tun/tap device + # will forcibly stop it, but it would be tidier + args = [ "iptables", "-t", "nat", "-A", "POSTROUTING", + "-s", "%s/%d" % (options.vif_addr, options.vif_mask), + "-j", "SNAT", + "--to-source", hostaddr, "--random" ] + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + if proc.wait(): + raise RuntimeError, "Error setting up SNAT" + +def vif_stop(tun_path, tun_name): + if options.vif_snat: + # set up SNAT using iptables + args = [ "iptables", "-t", "nat", "-D", "POSTROUTING", + "-s", "%s/%d" % (options.vif_addr, options.vif_mask), + "-j", "SNAT", + "--to-source", hostaddr, "--random" ] + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + + args = ["ifconfig", tun_name, "down"] + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + out,err = proc.communicate() + if proc.wait(): + print >>sys.stderr, "WARNING: error stopping virtual interface" + + +def pl_tuntap_alloc(kind, tun_path, tun_name): + tunalloc_so = ctypes.cdll.LoadLibrary("./vsys-scripts/support/tunalloc.so") + c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated! + kind = {"tun":IFF_TUN, + "tap":IFF_TAP}[kind] + fd = tunalloc_so.tun_alloc(kind, c_tun_name) + name = c_tun_name.value + return str(fd), name + +def pl_vif_start(tun_path, tun_name): + stdin = open("/vsys/vif_up.in","w") + stdout = open("/vsys/vif_up.out","r") + stdin.write(tun_name+"\n") + stdin.write(options.vif_addr+"\n") + stdin.write(str(options.vif_mask)+"\n") + if options.vif_snat: + stdin.write("snat=1\n") + if options.vif_txqueuelen is not None: + stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,)) + stdin.close() + out = stdout.read() + stdout.close() + if out.strip(): + print >>sys.stderr, out + + +def ipfmt(ip): + ipbytes = map(ord,ip.decode("hex")) + return '.'.join(map(str,ipbytes)) + +def formatPacket(packet): + packet = packet.encode("hex") + return '-'.join( ( + packet[0:1], #version + packet[1:2], #header length + packet[2:4], #diffserv/ECN + packet[4:8], #total length + packet[8:12], #ident + packet[12:16], #flags/fragment offs + packet[16:18], #ttl + packet[18:20], #ip-proto + packet[20:24], #checksum + ipfmt(packet[24:32]), # src-ip + ipfmt(packet[32:40]), # dst-ip + packet[40:48] if (int(packet[1]) > 5) else "", # options + packet[48:] if (int(packet[1]) > 5) else packet[40:], # payload + ) ) + +def packetReady(buf): + if len(buf) < 4: + return False + _,totallen = struct.unpack('HH',buf[:4]) + totallen = socket.htons(totallen) + return len(buf) >= totallen + +def pullPacket(buf): + _,totallen = struct.unpack('HH',buf[:4]) + totallen = socket.htons(totallen) + return buf[:totallen], buf[totallen:] + +def etherStrip(buf): + if len(buf) < 14: + return buf + if buf[12:14] == '\x08\x10' and buf[16:18] == '\x08\x00': + # tagged ethernet frame + return buf[18:] + elif buf[12:14] == '\x08\x00': + # untagged ethernet frame + return buf[14:] + else: + return buf + +def etherWrap(packet): + return ( + "\x00"*6*2 # bogus src and dst mac + +"\x08\x00" # IPv4 + +packet # payload + +"\x00"*4 # bogus crc + ) + +def piStrip(buf): + if len(buf) < 4: + return buf + else: + return buf[4:] + +def piWrap(buf): + return ( + "\x00\x00\x08\x00" # PI: 16 bits flags, 16 bits proto + +buf + ) + +abortme = False +def tun_fwd(tun, remote): + global abortme + + # in PL mode, we cannot strip PI structs + # so we'll have to handle them + with_pi = options.mode.startswith('pl-') + ether_mode = tun_name.startswith('tap') + + # Limited frame parsing, to preserve packet boundaries. + # Which is needed, since /dev/net/tun is unbuffered + fwbuf = "" + bkbuf = "" + while not abortme: + rdrdy, wrdy, errs = select.select((tun,remote),(tun,remote),(tun,remote),1) + + # check for errors + if errs: + break + + # check to see if we can write + if remote in wrdy and packetReady(fwbuf): + packet, fwbuf = pullPacket(fwbuf) + os.write(remote.fileno(), packet) + print >>sys.stderr, '>', formatPacket(packet) + if ether_mode: + # strip ethernet crc + fwbuf = fwbuf[4:] + if tun in wrdy and packetReady(bkbuf): + packet, bkbuf = pullPacket(bkbuf) + formatted = formatPacket(packet) + if ether_mode: + packet = etherWrap(packet) + if with_pi: + packet = piWrap(packet) + os.write(tun.fileno(), packet) + print >>sys.stderr, '<', formatted + + # check incoming data packets + if tun in rdrdy: + packet = os.read(tun.fileno(),2000) # tun.read blocks until it gets 2k! + if with_pi: + packet = piStrip(packet) + fwbuf += packet + if ether_mode: + fwbuf = etherStrip(fwbuf) + if remote in rdrdy: + packet = os.read(remote.fileno(),2000) # remote.read blocks until it gets 2k! + bkbuf += packet + + + +nop = lambda tun_path, tun_name : (tun_path, tun_name) +MODEINFO = { + 'none' : dict(alloc=nop, + tunopen=tunopen, tunclose=tunclose, + dealloc=nop, + start=nop, + stop=nop), + 'tun' : dict(alloc=functools.partial(tuntap_alloc, "tun"), + tunopen=tunopen, tunclose=tunclose, + dealloc=tuntap_dealloc, + start=vif_start, + stop=vif_stop), + 'tap' : dict(alloc=functools.partial(tuntap_alloc, "tap"), + tunopen=tunopen, tunclose=tunclose, + dealloc=tuntap_dealloc, + start=vif_start, + stop=vif_stop), + 'pl-tun' : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"), + tunopen=tunopen, tunclose=tunclose, + dealloc=nop, + start=pl_vif_start, + stop=nop), + 'pl-tap' : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"), + tunopen=tunopen, tunclose=tunclose, + dealloc=nop, + start=pl_vif_start, + stop=nop), +} + +tun_path = options.tun_path +tun_name = options.tun_name + +modeinfo = MODEINFO[options.mode] + +# be careful to roll back stuff on exceptions +tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name) +try: + modeinfo['start'](tun_path, tun_name) + try: + tun = modeinfo['tunopen'](tun_path, tun_name) + except: + modeinfo['stop'](tun_path, tun_name) + raise +except: + modeinfo['dealloc'](tun_path, tun_name) + raise + + +try: + # connect to remote endpoint + if remaining_args and not remaining_args[0].startswith('-'): + print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],15000) + rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + rsock.connect((remaining_args[0],15000)) + else: + print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,15000) + lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + lsock.bind((hostaddr,15000)) + lsock.listen(1) + rsock,raddr = lsock.accept() + remote = os.fdopen(rsock.fileno(), 'r+b', 0) + + print >>sys.stderr, "Connected" + + tun_fwd(tun, remote) +finally: + try: + print >>sys.stderr, "Shutting down..." + except: + # In case sys.stderr is broken + pass + + # tidy shutdown in every case - swallow exceptions + try: + modeinfo['tunclose'](tun_path, tun_name, tun) + except: + pass + + try: + modeinfo['stop'](tun_path, tun_name) + except: + pass + + try: + modeinfo['dealloc'](tun_path, tun_name) + except: + pass + +