21 tun_path = '/dev/net/tun'
22 hostaddr = socket.gethostbyname(socket.gethostname())
24 usage = "usage: %prog [options] <remote-endpoint>"
26 parser = optparse.OptionParser(usage=usage)
29 "-i", "--iface", dest="tun_name", metavar="DEVICE",
31 help = "TUN/TAP interface to tap into")
33 "-d", "--tun-path", dest="tun_path", metavar="PATH",
34 default = "/dev/net/tun",
35 help = "TUN/TAP device file path or file descriptor number")
37 "-p", "--port", dest="port", metavar="PORT", type="int",
39 help = "Peering TCP port to connect or listen to.")
41 "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
43 help = "Path to a unix-domain socket to pass the TUN file descriptor to. "
44 "If given, all other connectivity options are ignored, tun_connect will "
45 "simply wait to be killed after passing the file descriptor, and it will be "
46 "the receiver's responsability to handle the tunneling.")
49 "-m", "--mode", dest="mode", metavar="MODE",
52 "Set mode. One of none, tun, tap, pl-tun, pl-tap. In any mode except none, a TUN/TAP will be created "
53 "by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), "
54 "and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have "
55 "to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).")
57 "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS",
60 "See mode. This specifies the VIF_ADDRESS, "
61 "the IP address of the virtual interface.")
63 "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK",
66 "See mode. This specifies the VIF_MASK, "
67 "a number indicating the network type (ie: 24 for a C-class network).")
69 "-S", "--vif-snat", dest="vif_snat",
70 action = "store_true",
72 help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " )
74 "-P", "--vif-pointopoint", dest="vif_pointopoint", metavar="DST_ADDR",
77 "See mode. This specifies the remote endpoint's virtual address, "
78 "for point-to-point routing configuration. "
79 "Not supported by PlanetLab" )
81 "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int",
84 "See mode. This specifies the interface's transmission queue length. " )
86 "-u", "--udp", dest="udp", metavar="PORT", type="int",
89 "Bind to the specified UDP port locally, and send UDP datagrams to the "
90 "remote endpoint, creating a tunnel through UDP rather than TCP." )
92 "-k", "--key", dest="cipher_key", metavar="KEY",
95 "Specify a symmetric encryption key with which to protect packets across "
96 "the tunnel. python-crypto must be installed on the system." )
98 (options, remaining_args) = parser.parse_args(sys.argv[1:])
101 ETH_P_ALL = 0x00000003
102 ETH_P_IP = 0x00000800
103 TUNSETIFF = 0x400454ca
104 IFF_NO_PI = 0x00001000
107 IFF_VNET_HDR = 0x00004000
108 TUN_PKT_STRIP = 0x00000001
109 IFHWADDRLEN = 0x00000006
110 IFNAMSIZ = 0x00000010
111 IFREQ_SZ = 0x00000028
112 FIONREAD = 0x0000541b
115 return x+'\x00'*(IFNAMSIZ-len(x))
117 def ifreq(iface, flags):
119 # char[IFNAMSIZ] : interface name
122 ifreq = ifnam(iface)+struct.pack("H",flags);
123 ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
126 def tunopen(tun_path, tun_name):
127 if tun_path.isdigit():
129 print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
130 tun = os.fdopen(int(tun_path), 'r+b', 0)
133 print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
134 tun = open(tun_path, 'r+b', 0)
136 # bind file descriptor to the interface
137 fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
141 def tunclose(tun_path, tun_name, tun):
142 if tun_path.isdigit():
144 os.close(int(tun_path))
150 def tuntap_alloc(kind, tun_path, tun_name):
156 args.append(tun_name)
157 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
158 out,err = proc.communicate()
160 raise RuntimeError, "Could not allocate %s device" % (kind,)
162 match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
164 raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
166 tun_name = match.group("dev")
167 print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
169 return tun_path, tun_name
171 def tuntap_dealloc(tun_path, tun_name):
172 args = ["tunctl", "-d", tun_name]
173 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
174 out,err = proc.communicate()
176 print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
178 def nmask_to_dot_notation(mask):
179 mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00
180 mask = mask[2:] # strip 0x
181 mask = mask.decode("hex") # to bytes
182 mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0
185 def vif_start(tun_path, tun_name):
186 args = ["ifconfig", tun_name, options.vif_addr,
187 "netmask", nmask_to_dot_notation(options.vif_mask),
189 if options.vif_pointopoint:
190 args.extend(["pointopoint",options.vif_pointopoint])
191 if options.vif_txqueuelen is not None:
192 args.extend(["txqueuelen",str(options.vif_txqueuelen)])
194 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
195 out,err = proc.communicate()
197 raise RuntimeError, "Error starting virtual interface"
200 # set up SNAT using iptables
201 # TODO: stop vif on error.
202 # Not so necessary since deallocating the tun/tap device
203 # will forcibly stop it, but it would be tidier
204 args = [ "iptables", "-t", "nat", "-A", "POSTROUTING",
205 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
207 "--to-source", hostaddr, "--random" ]
208 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
209 out,err = proc.communicate()
211 raise RuntimeError, "Error setting up SNAT"
213 def vif_stop(tun_path, tun_name):
215 # set up SNAT using iptables
216 args = [ "iptables", "-t", "nat", "-D", "POSTROUTING",
217 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
219 "--to-source", hostaddr, "--random" ]
220 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
221 out,err = proc.communicate()
223 args = ["ifconfig", tun_name, "down"]
224 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
225 out,err = proc.communicate()
227 print >>sys.stderr, "WARNING: error stopping virtual interface"
230 def pl_tuntap_alloc(kind, tun_path, tun_name):
231 tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
232 c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
233 kind = {"tun":IFF_TUN,
235 fd = tunalloc_so.tun_alloc(kind, c_tun_name)
236 name = c_tun_name.value
239 def pl_vif_start(tun_path, tun_name):
240 stdin = open("/vsys/vif_up.in","w")
241 stdout = open("/vsys/vif_up.out","r")
242 stdin.write(tun_name+"\n")
243 stdin.write(options.vif_addr+"\n")
244 stdin.write(str(options.vif_mask)+"\n")
246 stdin.write("snat=1\n")
247 if options.vif_txqueuelen is not None:
248 stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
253 print >>sys.stderr, out
257 ipbytes = map(ord,ip.decode("hex"))
258 return '.'.join(map(str,ipbytes))
264 '8863' : 'PPPoE discover',
268 def etherProto(packet):
269 packet = packet.encode("hex")
271 if packet[12:14] == "\x81\x00":
279 def formatPacket(packet, ether_mode):
281 stripped_packet = etherStrip(packet)
282 if not stripped_packet:
283 packet = packet.encode("hex")
285 return "malformed eth " + packet.encode("hex")
287 if packet[24:28] == "8100":
289 ethertype = tagtype.get(packet[32:36], 'eth')
290 return ethertype + " " + ( '-'.join( (
291 packet[0:12], # MAC dest
292 packet[12:24], # MAC src
293 packet[24:32], # VLAN tag
294 packet[32:36], # Ethertype/len
295 packet[36:], # Payload
299 ethertype = tagtype.get(packet[24:28], 'eth')
300 return ethertype + " " + ( '-'.join( (
301 packet[0:12], # MAC dest
302 packet[12:24], # MAC src
303 packet[24:28], # Ethertype/len
304 packet[28:], # Payload
307 packet = stripped_packet
308 packet = packet.encode("hex")
310 return "malformed ip " + packet
312 return "ip " + ( '-'.join( (
313 packet[0:1], #version
314 packet[1:2], #header length
315 packet[2:4], #diffserv/ECN
316 packet[4:8], #total length
318 packet[12:16], #flags/fragment offs
320 packet[18:20], #ip-proto
321 packet[20:24], #checksum
322 ipfmt(packet[24:32]), # src-ip
323 ipfmt(packet[32:40]), # dst-ip
324 packet[40:48] if (int(packet[1],16) > 5) else "", # options
325 packet[48:] if (int(packet[1],16) > 5) else packet[40:], # payload
328 def packetReady(buf, ether_mode):
334 _,totallen = struct.unpack('HH',buf[:4])
335 totallen = socket.htons(totallen)
336 return len(buf) >= totallen
338 def pullPacket(buf, ether_mode):
342 _,totallen = struct.unpack('HH',buf[:4])
343 totallen = socket.htons(totallen)
344 return buf[:totallen], buf[totallen:]
349 if buf[12:14] == '\x08\x10' and buf[16:18] in '\x08\x00':
350 # tagged ethernet frame
352 elif buf[12:14] == '\x08\x00':
353 # untagged ethernet frame
358 def etherWrap(packet):
360 "\x00"*6*2 # bogus src and dst mac
363 +"\x00"*4 # bogus crc
372 def piWrap(buf, ether_mode):
374 proto = etherProto(buf)
378 "\x00\x00" # PI: 16 bits flags
379 +proto # 16 bits proto
383 def encrypt(packet, crypter):
385 padding = crypter.block_size - len(packet) % crypter.block_size
386 packet += chr(padding) * padding
389 return crypter.encrypt(packet)
391 def decrypt(packet, crypter):
393 packet = crypter.decrypt(packet)
396 padding = ord(packet[-1])
397 if not (0 < padding <= crypter.block_size):
399 raise RuntimeError, "Truncated packet"
400 packet = packet[:-padding]
405 def tun_fwd(tun, remote):
408 # in PL mode, we cannot strip PI structs
409 # so we'll have to handle them
410 with_pi = options.mode.startswith('pl-')
411 ether_mode = tun_name.startswith('tap')
415 if options.cipher_key:
416 import Crypto.Cipher.AES
419 hashed_key = hashlib.sha256(options.cipher_key).digest()
420 crypter = Crypto.Cipher.AES.new(
422 Crypto.Cipher.AES.MODE_ECB)
426 traceback.print_exc()
431 print >>sys.stderr, "Packets are transmitted in CIPHER"
433 print >>sys.stderr, "Packets are transmitted in PLAINTEXT"
435 # Limited frame parsing, to preserve packet boundaries.
436 # Which is needed, since /dev/net/tun is unbuffered
441 if packetReady(bkbuf, ether_mode):
443 if packetReady(fwbuf, ether_mode):
445 rdrdy, wrdy, errs = select.select((tun,remote),wset,(tun,remote),1)
451 # check to see if we can write
452 if remote in wrdy and packetReady(fwbuf, ether_mode):
453 packet, fwbuf = pullPacket(fwbuf, ether_mode)
456 enpacket = encrypt(packet, crypter)
459 os.write(remote.fileno(), enpacket)
462 # in UDP mode, we ignore errors - packet loss man...
464 print >>sys.stderr, '>', formatPacket(packet, ether_mode)
465 if tun in wrdy and packetReady(bkbuf, ether_mode):
466 packet, bkbuf = pullPacket(bkbuf, ether_mode)
467 formatted = formatPacket(packet, ether_mode)
469 packet = piWrap(packet, ether_mode)
470 os.write(tun.fileno(), packet)
471 print >>sys.stderr, '<', formatted
473 # check incoming data packets
475 packet = os.read(tun.fileno(),2000) # tun.read blocks until it gets 2k!
477 packet = piStrip(packet)
481 packet = os.read(remote.fileno(),2000) # remote.read blocks until it gets 2k!
483 packet = decrypt(packet, crypter)
486 # in UDP mode, we ignore errors - packet loss man...
492 nop = lambda tun_path, tun_name : (tun_path, tun_name)
494 'none' : dict(alloc=nop,
495 tunopen=tunopen, tunclose=tunclose,
499 'tun' : dict(alloc=functools.partial(tuntap_alloc, "tun"),
500 tunopen=tunopen, tunclose=tunclose,
501 dealloc=tuntap_dealloc,
504 'tap' : dict(alloc=functools.partial(tuntap_alloc, "tap"),
505 tunopen=tunopen, tunclose=tunclose,
506 dealloc=tuntap_dealloc,
509 'pl-tun' : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
510 tunopen=tunopen, tunclose=tunclose,
514 'pl-tap' : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
515 tunopen=tunopen, tunclose=tunclose,
521 tun_path = options.tun_path
522 tun_name = options.tun_name
524 modeinfo = MODEINFO[options.mode]
526 # be careful to roll back stuff on exceptions
527 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
529 modeinfo['start'](tun_path, tun_name)
531 tun = modeinfo['tunopen'](tun_path, tun_name)
533 modeinfo['stop'](tun_path, tun_name)
536 modeinfo['dealloc'](tun_path, tun_name)
544 if options.pass_fd.startswith("base64:"):
545 options.pass_fd = base64.b64decode(
546 options.pass_fd[len("base64:"):])
547 options.pass_fd = os.path.expandvars(options.pass_fd)
549 print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
551 # send FD to whoever wants it
554 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
556 sock.connect(options.pass_fd)
558 # wait a while, retry
559 print >>sys.stderr, "Could not connect. Retrying in a sec..."
561 sock.connect(options.pass_fd)
562 passfd.sendfd(sock, tun.fileno(), '0')
564 # Launch a tcpdump subprocess, to capture and dump packets,
565 # we will not be able to capture them ourselves.
566 # Make sure to catch sigterm and kill the tcpdump as well
567 tcpdump = subprocess.Popen(
568 ["tcpdump","-l","-n","-i",tun_name])
570 def _finalize(sig,frame):
571 os.kill(tcpdump.pid, signal.SIGTERM)
573 if callable(_oldterm):
577 _oldterm = signal.signal(signal.SIGTERM, _finalize)
580 def tun_fwd(tun, remote):
585 # connect to remote endpoint
586 if remaining_args and not remaining_args[0].startswith('-'):
587 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
588 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
589 rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
590 rsock.bind((hostaddr,options.udp))
591 rsock.connect((remaining_args[0],options.port))
593 print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
594 raise AssertionError, "Error: need a remote endpoint in UDP mode"
595 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
597 # connect to remote endpoint
598 if remaining_args and not remaining_args[0].startswith('-'):
599 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
600 rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
601 rsock.connect((remaining_args[0],options.port))
603 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
604 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
605 lsock.bind((hostaddr,options.port))
607 rsock,raddr = lsock.accept()
608 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
610 print >>sys.stderr, "Connected"
615 os.kill(tcpdump.pid, signal.SIGTERM)
619 print >>sys.stderr, "Shutting down..."
621 # In case sys.stderr is broken
624 # tidy shutdown in every case - swallow exceptions
626 modeinfo['tunclose'](tun_path, tun_name, tun)
631 modeinfo['stop'](tun_path, tun_name)
636 modeinfo['dealloc'](tun_path, tun_name)