23 tun_path = '/dev/net/tun'
24 hostaddr = socket.gethostbyname(socket.gethostname())
26 usage = "usage: %prog [options] <remote-endpoint>"
28 parser = optparse.OptionParser(usage=usage)
31 "-i", "--iface", dest="tun_name", metavar="DEVICE",
33 help = "TUN/TAP interface to tap into")
35 "-d", "--tun-path", dest="tun_path", metavar="PATH",
36 default = "/dev/net/tun",
37 help = "TUN/TAP device file path or file descriptor number")
39 "-p", "--port", dest="port", metavar="PORT", type="int",
41 help = "Peering TCP port to connect or listen to.")
43 "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
45 help = "Path to a unix-domain socket to pass the TUN file descriptor to. "
46 "If given, all other connectivity options are ignored, tun_connect will "
47 "simply wait to be killed after passing the file descriptor, and it will be "
48 "the receiver's responsability to handle the tunneling.")
51 "-m", "--mode", dest="mode", metavar="MODE",
54 "Set mode. One of none, tun, tap, pl-tun, pl-tap. In any mode except none, a TUN/TAP will be created "
55 "by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), "
56 "and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have "
57 "to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).")
59 "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS",
62 "See mode. This specifies the VIF_ADDRESS, "
63 "the IP address of the virtual interface.")
65 "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK",
68 "See mode. This specifies the VIF_MASK, "
69 "a number indicating the network type (ie: 24 for a C-class network).")
71 "-S", "--vif-snat", dest="vif_snat",
72 action = "store_true",
74 help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " )
76 "-P", "--vif-pointopoint", dest="vif_pointopoint", metavar="DST_ADDR",
79 "See mode. This specifies the remote endpoint's virtual address, "
80 "for point-to-point routing configuration. "
81 "Not supported by PlanetLab" )
83 "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int",
86 "See mode. This specifies the interface's transmission queue length. " )
88 "-u", "--udp", dest="udp", metavar="PORT", type="int",
91 "Bind to the specified UDP port locally, and send UDP datagrams to the "
92 "remote endpoint, creating a tunnel through UDP rather than TCP." )
94 "-k", "--key", dest="cipher_key", metavar="KEY",
97 "Specify a symmetric encryption key with which to protect packets across "
98 "the tunnel. python-crypto must be installed on the system." )
100 "-N", "--no-capture", dest="no_capture",
101 action = "store_true",
103 help = "If specified, packets won't be logged to standard error "
104 "(default is to log them to standard error). " )
106 (options, remaining_args) = parser.parse_args(sys.argv[1:])
109 ETH_P_ALL = 0x00000003
110 ETH_P_IP = 0x00000800
111 TUNSETIFF = 0x400454ca
112 IFF_NO_PI = 0x00001000
115 IFF_VNET_HDR = 0x00004000
116 TUN_PKT_STRIP = 0x00000001
117 IFHWADDRLEN = 0x00000006
118 IFNAMSIZ = 0x00000010
119 IFREQ_SZ = 0x00000028
120 FIONREAD = 0x0000541b
123 return x+'\x00'*(IFNAMSIZ-len(x))
125 def ifreq(iface, flags):
127 # char[IFNAMSIZ] : interface name
130 ifreq = ifnam(iface)+struct.pack("H",flags);
131 ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
134 def tunopen(tun_path, tun_name):
135 if tun_path.isdigit():
137 print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
138 tun = os.fdopen(int(tun_path), 'r+b', 0)
141 print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
142 tun = open(tun_path, 'r+b', 0)
144 # bind file descriptor to the interface
145 fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
149 def tunclose(tun_path, tun_name, tun):
150 if tun_path.isdigit():
152 os.close(int(tun_path))
158 def tuntap_alloc(kind, tun_path, tun_name):
164 args.append(tun_name)
165 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
166 out,err = proc.communicate()
168 raise RuntimeError, "Could not allocate %s device" % (kind,)
170 match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
172 raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
174 tun_name = match.group("dev")
175 print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
177 return tun_path, tun_name
179 def tuntap_dealloc(tun_path, tun_name):
180 args = ["tunctl", "-d", tun_name]
181 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
182 out,err = proc.communicate()
184 print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
186 def nmask_to_dot_notation(mask):
187 mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00
188 mask = mask[2:] # strip 0x
189 mask = mask.decode("hex") # to bytes
190 mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0
193 def vif_start(tun_path, tun_name):
194 args = ["ifconfig", tun_name, options.vif_addr,
195 "netmask", nmask_to_dot_notation(options.vif_mask),
197 if options.vif_pointopoint:
198 args.extend(["pointopoint",options.vif_pointopoint])
199 if options.vif_txqueuelen is not None:
200 args.extend(["txqueuelen",str(options.vif_txqueuelen)])
202 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
203 out,err = proc.communicate()
205 raise RuntimeError, "Error starting virtual interface"
208 # set up SNAT using iptables
209 # TODO: stop vif on error.
210 # Not so necessary since deallocating the tun/tap device
211 # will forcibly stop it, but it would be tidier
212 args = [ "iptables", "-t", "nat", "-A", "POSTROUTING",
213 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
215 "--to-source", hostaddr, "--random" ]
216 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
217 out,err = proc.communicate()
219 raise RuntimeError, "Error setting up SNAT"
221 def vif_stop(tun_path, tun_name):
223 # set up SNAT using iptables
224 args = [ "iptables", "-t", "nat", "-D", "POSTROUTING",
225 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
227 "--to-source", hostaddr, "--random" ]
228 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
229 out,err = proc.communicate()
231 args = ["ifconfig", tun_name, "down"]
232 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
233 out,err = proc.communicate()
235 print >>sys.stderr, "WARNING: error stopping virtual interface"
238 def pl_tuntap_alloc(kind, tun_path, tun_name):
239 tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
240 c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
241 kind = {"tun":IFF_TUN,
243 fd = tunalloc_so.tun_alloc(kind, c_tun_name)
244 name = c_tun_name.value
247 def pl_vif_start(tun_path, tun_name):
248 stdin = open("/vsys/vif_up.in","w")
249 stdout = open("/vsys/vif_up.out","r")
250 stdin.write(tun_name+"\n")
251 stdin.write(options.vif_addr+"\n")
252 stdin.write(str(options.vif_mask)+"\n")
254 stdin.write("snat=1\n")
255 if options.vif_pointopoint:
256 stdin.write("pointopoint=%s\n" % (options.vif_pointopoint,))
257 if options.vif_txqueuelen is not None:
258 stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
263 print >>sys.stderr, out
265 def pl_vif_stop(tun_path, tun_name):
266 stdin = open("/vsys/vif_down.in","w")
267 stdout = open("/vsys/vif_down.out","r")
268 stdin.write(tun_name+"\n")
273 print >>sys.stderr, out
276 def tun_fwd(tun, remote):
279 # in PL mode, we cannot strip PI structs
280 # so we'll have to handle them
281 tunchannel.tun_fwd(tun, remote,
282 with_pi = options.mode.startswith('pl-'),
283 ether_mode = tun_name.startswith('tap'),
284 cipher_key = options.cipher_key,
286 TERMINATE = TERMINATE,
287 stderr = open("/dev/null","w") if options.no_capture
293 nop = lambda tun_path, tun_name : (tun_path, tun_name)
295 'none' : dict(alloc=nop,
296 tunopen=tunopen, tunclose=tunclose,
300 'tun' : dict(alloc=functools.partial(tuntap_alloc, "tun"),
301 tunopen=tunopen, tunclose=tunclose,
302 dealloc=tuntap_dealloc,
305 'tap' : dict(alloc=functools.partial(tuntap_alloc, "tap"),
306 tunopen=tunopen, tunclose=tunclose,
307 dealloc=tuntap_dealloc,
310 'pl-tun' : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
311 tunopen=tunopen, tunclose=tunclose,
315 'pl-tap' : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
316 tunopen=tunopen, tunclose=tunclose,
322 tun_path = options.tun_path
323 tun_name = options.tun_name
325 modeinfo = MODEINFO[options.mode]
327 # be careful to roll back stuff on exceptions
328 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
330 modeinfo['start'](tun_path, tun_name)
332 tun = modeinfo['tunopen'](tun_path, tun_name)
334 modeinfo['stop'](tun_path, tun_name)
337 modeinfo['dealloc'](tun_path, tun_name)
341 # Trak SIGTERM, and set global termination flag instead of dying
343 def _finalize(sig,frame):
345 TERMINATE.append(None)
346 signal.signal(signal.SIGTERM, _finalize)
352 if options.pass_fd.startswith("base64:"):
353 options.pass_fd = base64.b64decode(
354 options.pass_fd[len("base64:"):])
355 options.pass_fd = os.path.expandvars(options.pass_fd)
357 print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
359 # send FD to whoever wants it
362 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
366 sock.connect(options.pass_fd)
369 # wait a while, retry
370 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
371 time.sleep(min(30.0,retrydelay))
374 sock.connect(options.pass_fd)
375 passfd.sendfd(sock, tun.fileno(), '0')
377 # Launch a tcpdump subprocess, to capture and dump packets,
378 # we will not be able to capture them ourselves.
379 # Make sure to catch sigterm and kill the tcpdump as well
380 tcpdump = subprocess.Popen(
381 ["tcpdump","-l","-n","-i",tun_name])
384 def tun_fwd(tun, remote):
389 # connect to remote endpoint
390 if remaining_args and not remaining_args[0].startswith('-'):
391 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
392 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
393 rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
397 rsock.bind((hostaddr,options.udp))
400 # wait a while, retry
401 print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
402 time.sleep(min(30.0,retrydelay))
405 rsock.bind((hostaddr,options.udp))
406 rsock.connect((remaining_args[0],options.port))
408 print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
409 raise AssertionError, "Error: need a remote endpoint in UDP mode"
410 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
412 # connect to remote endpoint
413 if remaining_args and not remaining_args[0].startswith('-'):
414 print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
415 rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
419 rsock.connect((remaining_args[0],options.port))
422 # wait a while, retry
423 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
424 time.sleep(min(30.0,retrydelay))
427 rsock.connect((remaining_args[0],options.port))
429 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
430 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
434 lsock.bind((hostaddr,options.port))
437 # wait a while, retry
438 print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
439 time.sleep(min(30.0,retrydelay))
442 lsock.bind((hostaddr,options.port))
444 rsock,raddr = lsock.accept()
445 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
447 print >>sys.stderr, "Connected"
452 os.kill(tcpdump.pid, signal.SIGTERM)
456 print >>sys.stderr, "Shutting down..."
458 # In case sys.stderr is broken
461 # tidy shutdown in every case - swallow exceptions
463 modeinfo['stop'](tun_path, tun_name)
468 modeinfo['tunclose'](tun_path, tun_name, tun)
473 modeinfo['dealloc'](tun_path, tun_name)
477 print >>sys.stderr, "TERMINATED GRACEFULLY"