Add scripts folder, for scripts that will be needed during deployment.
authorClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Mon, 18 Apr 2011 07:17:35 +0000 (09:17 +0200)
committerClaudio-Daniel Freire <claudio-daniel.freire@inria.fr>
Mon, 18 Apr 2011 07:17:35 +0000 (09:17 +0200)
Add tun_connect script, a script to set up tunnelling in both generic unix and planetlab nodes.

scripts/tun_connect.py [new file with mode: 0644]

diff --git a/scripts/tun_connect.py b/scripts/tun_connect.py
new file mode 100644 (file)
index 0000000..948b78c
--- /dev/null
@@ -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] <remote-endpoint>"
+
+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
+    #   <padding>
+    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<dev>(?: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
+
+