24 tun_path = '/dev/net/tun'
25 hostaddr = socket.gethostbyname(socket.gethostname())
27 usage = "usage: %prog [options] <remote-endpoint>"
29 parser = optparse.OptionParser(usage=usage)
32 "-i", "--iface", dest="tun_name", metavar="DEVICE",
34 help = "TUN/TAP interface to tap into")
36 "-d", "--tun-path", dest="tun_path", metavar="PATH",
37 default = "/dev/net/tun",
38 help = "TUN/TAP device file path or file descriptor number")
40 "-p", "--port", dest="port", metavar="PORT", type="int",
42 help = "Peering TCP port to connect or listen to.")
44 "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
46 help = "Path to a unix-domain socket to pass the TUN file descriptor to. "
47 "If given, all other connectivity options are ignored, tun_connect will "
48 "simply wait to be killed after passing the file descriptor, and it will be "
49 "the receiver's responsability to handle the tunneling.")
52 "-m", "--mode", dest="mode", metavar="MODE",
55 "Set mode. One of none, tun, tap, pl-tun, pl-tap, pl-gre-ip, pl-gre-eth. In any mode except none, a TUN/TAP will be created "
56 "by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), "
57 "and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have "
58 "to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).")
60 "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS",
63 "See mode. This specifies the VIF_ADDRESS, "
64 "the IP address of the virtual interface.")
66 "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK",
69 "See mode. This specifies the VIF_MASK, "
70 "a number indicating the network type (ie: 24 for a C-class network).")
72 "-S", "--vif-snat", dest="vif_snat",
73 action = "store_true",
75 help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " )
77 "-P", "--vif-pointopoint", dest="vif_pointopoint", metavar="DST_ADDR",
80 "See mode. This specifies the remote endpoint's virtual address, "
81 "for point-to-point routing configuration. "
82 "Not supported by PlanetLab" )
84 "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int",
87 "See mode. This specifies the interface's transmission queue length. " )
89 "-u", "--udp", dest="udp", metavar="PORT", type="int",
92 "Bind to the specified UDP port locally, and send UDP datagrams to the "
93 "remote endpoint, creating a tunnel through UDP rather than TCP." )
95 "-k", "--key", dest="cipher_key", metavar="KEY",
98 "Specify a symmetric encryption key with which to protect packets across "
99 "the tunnel. python-crypto must be installed on the system." )
101 "-K", "--gre-key", dest="gre_key", metavar="KEY", type="int",
104 "Specify a demultiplexing 32-bit numeric key for GRE." )
106 "-C", "--cipher", dest="cipher", metavar="CIPHER",
108 help = "One of PLAIN, AES, Blowfish, DES, DES3. " )
110 "-N", "--no-capture", dest="no_capture",
111 action = "store_true",
113 help = "If specified, packets won't be logged to standard output "
114 "(default is to log them to standard output). " )
116 "-c", "--pcap-capture", dest="pcap_capture", metavar="FILE",
118 help = "If specified, packets won't be logged to standard output, "
119 "but dumped to a pcap-formatted trace in the specified file. " )
121 (options, remaining_args) = parser.parse_args(sys.argv[1:])
127 'blowfish' : 'Blowfish',
129 }[options.cipher.lower()]
131 ETH_P_ALL = 0x00000003
132 ETH_P_IP = 0x00000800
133 TUNSETIFF = 0x400454ca
134 IFF_NO_PI = 0x00001000
137 IFF_VNET_HDR = 0x00004000
138 TUN_PKT_STRIP = 0x00000001
139 IFHWADDRLEN = 0x00000006
140 IFNAMSIZ = 0x00000010
141 IFREQ_SZ = 0x00000028
142 FIONREAD = 0x0000541b
144 class HostLock(object):
145 # This class is used as a lock to prevent concurrency issues with more
146 # than one instance of netns running in the same machine. Both in
147 # different processes or different threads.
149 processcond = threading.Condition()
151 def __init__(self, lockfile):
152 processcond = self.__class__.processcond
154 processcond.acquire()
157 while self.__class__.taken:
159 self.__class__.taken = True
161 processcond.release()
163 self.lockfile = lockfile
167 fcntl.flock(self.lockfile, fcntl.LOCK_EX)
169 except (OSError, IOError), e:
170 if e.args[0] != os.errno.EINTR:
174 processcond = self.__class__.processcond
176 processcond.acquire()
178 if not self.lockfile.closed:
179 fcntl.flock(self.lockfile, fcntl.LOCK_UN)
182 self.__class__.taken = False
185 processcond.release()
188 return x+'\x00'*(IFNAMSIZ-len(x))
190 def ifreq(iface, flags):
192 # char[IFNAMSIZ] : interface name
195 ifreq = ifnam(iface)+struct.pack("H",flags);
196 ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
199 def tunopen(tun_path, tun_name):
200 if tun_path.isdigit():
202 print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
203 tun = os.fdopen(int(tun_path), 'r+b', 0)
206 print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
207 tun = open(tun_path, 'r+b', 0)
209 # bind file descriptor to the interface
210 fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
214 def tunclose(tun_path, tun_name, tun):
215 if tun_path.isdigit():
217 os.close(int(tun_path))
223 def noopen(tun_path, tun_name):
224 print >>sys.stderr, "Using tun:", tun_name
226 def noclose(tun_path, tun_name, tun):
229 def tuntap_alloc(kind, tun_path, tun_name):
235 args.append(tun_name)
236 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
237 out,err = proc.communicate()
239 raise RuntimeError, "Could not allocate %s device" % (kind,)
241 match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
243 raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
245 tun_name = match.group("dev")
246 print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
248 return tun_path, tun_name
250 def tuntap_dealloc(tun_path, tun_name):
251 args = ["tunctl", "-d", tun_name]
252 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
253 out,err = proc.communicate()
255 print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
257 def nmask_to_dot_notation(mask):
258 mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00
259 mask = mask[2:] # strip 0x
260 mask = mask.decode("hex") # to bytes
261 mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0
264 def vif_start(tun_path, tun_name):
265 args = ["ifconfig", tun_name, options.vif_addr,
266 "netmask", nmask_to_dot_notation(options.vif_mask),
268 if options.vif_pointopoint:
269 args.extend(["pointopoint",options.vif_pointopoint])
270 if options.vif_txqueuelen is not None:
271 args.extend(["txqueuelen",str(options.vif_txqueuelen)])
273 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
274 out,err = proc.communicate()
276 raise RuntimeError, "Error starting virtual interface"
279 # set up SNAT using iptables
280 # TODO: stop vif on error.
281 # Not so necessary since deallocating the tun/tap device
282 # will forcibly stop it, but it would be tidier
283 args = [ "iptables", "-t", "nat", "-A", "POSTROUTING",
284 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
286 "--to-source", hostaddr, "--random" ]
287 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
288 out,err = proc.communicate()
290 raise RuntimeError, "Error setting up SNAT"
292 def vif_stop(tun_path, tun_name):
294 # set up SNAT using iptables
295 args = [ "iptables", "-t", "nat", "-D", "POSTROUTING",
296 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
298 "--to-source", hostaddr, "--random" ]
299 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
300 out,err = proc.communicate()
302 args = ["ifconfig", tun_name, "down"]
303 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
304 out,err = proc.communicate()
306 print >>sys.stderr, "WARNING: error stopping virtual interface"
309 def pl_tuntap_alloc(kind, tun_path, tun_name):
310 tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
311 c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
312 kind = {"tun":IFF_TUN,
314 fd = tunalloc_so.tun_alloc(kind, c_tun_name)
315 name = c_tun_name.value
318 _name_reservation = None
319 def pl_tuntap_namealloc(kind, tun_path, tun_name):
320 global _name_reservation
322 lockfile = open("/tmp/nepi-tun-connect.lock", "a")
323 _name_reservation = lock = HostLock(lockfile)
325 # We need to do this, fd_tuntap is the only one who can
326 # tell us our slice id (this script runs as root, so no uid),
327 # and the pattern of device names accepted by vsys scripts
328 tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
329 c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
330 nkind= {"tun":IFF_TUN,
332 fd = tunalloc_so.tun_alloc(nkind, c_tun_name)
333 name = c_tun_name.value
336 base = name[:name.index('-')+1]
337 existing = set(map(str.strip,os.popen("ip a | grep -o '%s[0-9]*'" % (base,)).read().strip().split('\n')))
339 for i in xrange(9000,10000):
341 if name not in existing:
344 raise RuntimeError, "Could not assign interface name"
348 def pl_vif_start(tun_path, tun_name):
349 global _name_reservation
353 stdout = open("/vsys/vif_up.out","r")
354 out.append(stdout.read())
358 # Serialize access to vsys
359 lockfile = open("/tmp/nepi-tun-connect.lock", "a")
360 lock = _name_reservation or HostLock(lockfile)
361 _name_reservation = None
363 stdin = open("/vsys/vif_up.in","w")
365 t = threading.Thread(target=outreader)
368 stdin.write(tun_name+"\n")
369 stdin.write(options.vif_addr+"\n")
370 stdin.write(str(options.vif_mask)+"\n")
372 stdin.write("snat=1\n")
373 if options.vif_pointopoint:
374 stdin.write("pointopoint=%s\n" % (options.vif_pointopoint,))
375 if options.vif_txqueuelen is not None:
376 stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
377 if options.mode.startswith('pl-gre'):
378 stdin.write("gre=%d\n" % (options.gre_key,))
379 stdin.write("remote=%s\n" % (remaining_args[0],))
385 print >>sys.stderr, out
389 def pl_vif_stop(tun_path, tun_name):
392 stdout = open("/vsys/vif_down.out","r")
393 out.append(stdout.read())
397 ifaces = set(map(str.strip,os.popen("ip a | grep -o '%s'" % (tun_name,)).read().strip().split('\n')))
398 if tun_name in ifaces:
403 # Serialize access to vsys
404 lockfile = open("/tmp/nepi-tun-connect.lock", "a")
405 lock = HostLock(lockfile)
407 stdin = open("/vsys/vif_down.in","w")
409 t = threading.Thread(target=outreader)
412 stdin.write(tun_name+"\n")
418 print >>sys.stderr, out
423 def tun_fwd(tun, remote, reconnect = None):
426 tunqueue = options.vif_txqueuelen or 1000
429 # in PL mode, we cannot strip PI structs
430 # so we'll have to handle them
431 tunchannel.tun_fwd(tun, remote,
432 with_pi = options.mode.startswith('pl-'),
433 ether_mode = tun_name.startswith('tap'),
434 cipher_key = options.cipher_key,
436 TERMINATE = TERMINATE,
438 reconnect = reconnect,
440 tunkqueue = tunkqueue,
441 cipher = options.cipher
446 nop = lambda tun_path, tun_name : (tun_path, tun_name)
448 'none' : dict(alloc=nop,
449 tunopen=tunopen, tunclose=tunclose,
453 'tun' : dict(alloc=functools.partial(tuntap_alloc, "tun"),
454 tunopen=tunopen, tunclose=tunclose,
455 dealloc=tuntap_dealloc,
458 'tap' : dict(alloc=functools.partial(tuntap_alloc, "tap"),
459 tunopen=tunopen, tunclose=tunclose,
460 dealloc=tuntap_dealloc,
463 'pl-tun' : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
464 tunopen=tunopen, tunclose=tunclose,
468 'pl-tap' : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
469 tunopen=tunopen, tunclose=tunclose,
473 'pl-gre-ip' : dict(alloc=functools.partial(pl_tuntap_namealloc, "tun"),
474 tunopen=noopen, tunclose=tunclose,
478 'pl-gre-eth': dict(alloc=functools.partial(pl_tuntap_namealloc, "tap"),
479 tunopen=noopen, tunclose=noclose,
485 tun_path = options.tun_path
486 tun_name = options.tun_name
488 modeinfo = MODEINFO[options.mode]
490 # be careful to roll back stuff on exceptions
491 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
493 modeinfo['start'](tun_path, tun_name)
495 tun = modeinfo['tunopen'](tun_path, tun_name)
497 modeinfo['stop'](tun_path, tun_name)
500 modeinfo['dealloc'](tun_path, tun_name)
504 # Trak SIGTERM, and set global termination flag instead of dying
506 def _finalize(sig,frame):
508 TERMINATE.append(None)
509 signal.signal(signal.SIGTERM, _finalize)
516 if options.pass_fd.startswith("base64:"):
517 options.pass_fd = base64.b64decode(
518 options.pass_fd[len("base64:"):])
519 options.pass_fd = os.path.expandvars(options.pass_fd)
521 print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
523 # send FD to whoever wants it
526 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
530 sock.connect(options.pass_fd)
533 # wait a while, retry
534 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
535 time.sleep(min(30.0,retrydelay))
538 sock.connect(options.pass_fd)
539 passfd.sendfd(sock, tun.fileno(), '0')
542 def tun_fwd(tun, remote, **kw):
546 elif options.mode.startswith('pl-gre'):
548 def tun_fwd(tun, remote, **kw):
551 remote = remaining_args[0]
553 # connect to remote endpoint
554 if remaining_args and not remaining_args[0].startswith('-'):
555 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
556 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
557 rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
561 rsock.bind((hostaddr,options.udp))
564 # wait a while, retry
565 print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
566 time.sleep(min(30.0,retrydelay))
569 rsock.bind((hostaddr,options.udp))
570 rsock.connect((remaining_args[0],options.port))
572 print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
573 raise AssertionError, "Error: need a remote endpoint in UDP mode"
574 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
576 # connect to remote endpoint
577 if remaining_args and not remaining_args[0].startswith('-'):
578 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
579 rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
583 rsock.connect((remaining_args[0],options.port))
586 # wait a while, retry
587 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
588 time.sleep(min(30.0,retrydelay))
591 rsock.connect((remaining_args[0],options.port))
593 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
594 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
598 lsock.bind((hostaddr,options.port))
601 # wait a while, retry
602 print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
603 time.sleep(min(30.0,retrydelay))
606 lsock.bind((hostaddr,options.port))
608 rsock,raddr = lsock.accept()
609 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
611 if not options.no_capture:
612 # Launch a tcpdump subprocess, to capture and dump packets.
613 # Make sure to catch sigterm and kill the tcpdump as well
614 tcpdump = subprocess.Popen(
615 ["tcpdump","-l","-n","-i",tun_name, "-s", "4096"]
616 + ["-w",options.pcap_capture,"-U"] * bool(options.pcap_capture) )
618 print >>sys.stderr, "Connected"
620 # Try to give us high priority
624 # Ignore errors, we might not have enough privileges,
625 # or perhaps there is no os.nice support in the system
629 reconnect = reconnect)
633 print >>sys.stderr, "Shutting down..."
635 # In case sys.stderr is broken
638 # tidy shutdown in every case - swallow exceptions
642 os.kill(tcpdump.pid, signal.SIGTERM)
648 modeinfo['stop'](tun_path, tun_name)
650 traceback.print_exc()
653 modeinfo['tunclose'](tun_path, tun_name, tun)
655 traceback.print_exc()
658 modeinfo['dealloc'](tun_path, tun_name)
660 traceback.print_exc()
662 print >>sys.stderr, "TERMINATED GRACEFULLY"