Merge non-handshake stuff
[nepi.git] / src / nepi / testbeds / planetlab / scripts / tun_connect.py
1 import sys
2
3 import socket
4 import fcntl
5 import os
6 import os.path
7 import select
8 import signal
9
10 import struct
11 import ctypes
12 import optparse
13 import threading
14 import subprocess
15 import re
16 import functools
17 import time
18 import base64
19 import traceback
20
21 import tunchannel
22
23 try:
24     import iovec
25     HAS_IOVEC = True
26 except:
27     HAS_IOVEC = False
28
29 tun_name = 'tun0'
30 tun_path = '/dev/net/tun'
31 hostaddr = socket.gethostbyname(socket.gethostname())
32
33 usage = "usage: %prog [options] <remote-endpoint>"
34
35 parser = optparse.OptionParser(usage=usage)
36
37 parser.add_option(
38     "-i", "--iface", dest="tun_name", metavar="DEVICE",
39     default = "tun0",
40     help = "TUN/TAP interface to tap into")
41 parser.add_option(
42     "-d", "--tun-path", dest="tun_path", metavar="PATH",
43     default = "/dev/net/tun",
44     help = "TUN/TAP device file path or file descriptor number")
45 parser.add_option(
46     "-p", "--port", dest="port", metavar="PORT", type="int",
47     default = 15000,
48     help = "Peering TCP port to connect or listen to.")
49 parser.add_option(
50     "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
51     default = None,
52     help = "Path to a unix-domain socket to pass the TUN file descriptor to. "
53            "If given, all other connectivity options are ignored, tun_connect will "
54            "simply wait to be killed after passing the file descriptor, and it will be "
55            "the receiver's responsability to handle the tunneling.")
56
57 parser.add_option(
58     "-m", "--mode", dest="mode", metavar="MODE",
59     default = "none",
60     help = 
61         "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 "
62         "by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), "
63         "and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have "
64         "to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).")
65 parser.add_option(
66     "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS",
67     default = None,
68     help = 
69         "See mode. This specifies the VIF_ADDRESS, "
70         "the IP address of the virtual interface.")
71 parser.add_option(
72     "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK", 
73     default = None,
74     help = 
75         "See mode. This specifies the VIF_MASK, "
76         "a number indicating the network type (ie: 24 for a C-class network).")
77 parser.add_option(
78     "-S", "--vif-snat", dest="vif_snat", 
79     action = "store_true",
80     default = False,
81     help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " )
82 parser.add_option(
83     "-P", "--vif-pointopoint", dest="vif_pointopoint",  metavar="DST_ADDR",
84     default = None,
85     help = 
86         "See mode. This specifies the remote endpoint's virtual address, "
87         "for point-to-point routing configuration. "
88         "Not supported by PlanetLab" )
89 parser.add_option(
90     "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int",
91     default = None,
92     help = 
93         "See mode. This specifies the interface's transmission queue length. " )
94 parser.add_option(
95     "-b", "--bwlimit", dest="bwlimit", metavar="BYTESPERSECOND", type="int",
96     default = None,
97     help = 
98         "This specifies the interface's emulated bandwidth in bytes per second." )
99 parser.add_option(
100     "-u", "--udp", dest="udp", metavar="PORT", type="int",
101     default = None,
102     help = 
103         "Bind to the specified UDP port locally, and send UDP datagrams to the "
104         "remote endpoint, creating a tunnel through UDP rather than TCP." )
105 parser.add_option(
106     "-k", "--key", dest="cipher_key", metavar="KEY",
107     default = None,
108     help = 
109         "Specify a symmetric encryption key with which to protect packets across "
110         "the tunnel. python-crypto must be installed on the system." )
111 parser.add_option(
112     "-K", "--gre-key", dest="gre_key", metavar="KEY", type="string",
113     default = "true",
114     help = 
115         "Specify a demultiplexing 32-bit numeric key for GRE." )
116 parser.add_option(
117     "-C", "--cipher", dest="cipher", metavar="CIPHER",
118     default = 'AES',
119     help = "One of PLAIN, AES, Blowfish, DES, DES3. " )
120 parser.add_option(
121     "-N", "--no-capture", dest="no_capture", 
122     action = "store_true",
123     default = False,
124     help = "If specified, packets won't be logged to standard output "
125            "(default is to log them to standard output). " )
126 parser.add_option(
127     "-c", "--pcap-capture", dest="pcap_capture", metavar="FILE",
128     default = None,
129     help = "If specified, packets won't be logged to standard output, "
130            "but dumped to a pcap-formatted trace in the specified file. " )
131 parser.add_option(
132     "--multicast-forwarder", dest="multicast_fwd", 
133     default = None,
134     help = "If specified, multicast packets will be forwarded to "
135            "the specified unix-domain socket. If the device uses ethernet "
136            "frames, ethernet headers will be stripped and IP packets "
137            "will be forwarded, prefixed with the interface's address." )
138 parser.add_option(
139     "--filter", dest="filter_module", metavar="PATH",
140     default = None,
141     help = "If specified, it should be either a .py or .so module. "
142            "It will be loaded, and all incoming and outgoing packets "
143            "will be routed through it. The filter will not be responsible "
144            "for buffering, packet queueing is performed in tun_connect "
145            "already, so it should not concern itself with it. It should "
146            "not, however, block in one direction if the other is congested.\n"
147            "\n"
148            "Modules are expected to have the following methods:\n"
149            "\tinit(**args)\n"
150            "\t\tIf arguments are given, this method will be called with the\n"
151            "\t\tgiven arguments (as keyword args in python modules, or a single\n"
152            "\t\tstring in c modules).\n"
153            "\taccept_packet(packet, direction):\n"
154            "\t\tDecide whether to drop the packet. Direction is 0 for packets "
155                "coming from the local side to the remote, and 1 is for packets "
156                "coming from the remote side to the local. Return a boolean, "
157                "true if the packet is not to be dropped.\n"
158            "\tfilter_init():\n"
159            "\t\tInitializes a filtering pipe (filter_run). It should "
160                "return two file descriptors to use as a bidirectional "
161                "pipe: local and remote. 'local' is where packets from the "
162                "local side will be written to. After filtering, those packets "
163                "should be written to 'remote', where tun_connect will read "
164                "from, and it will forward them to the remote peer. "
165                "Packets from the remote peer will be written to 'remote', "
166                "where the filter is expected to read from, and eventually "
167                "forward them to the local side. If the file descriptors are "
168                "not nonblocking, they will be set to nonblocking. So it's "
169                "better to set them from the start like that.\n"
170            "\tfilter_run(local, remote):\n"
171            "\t\tIf filter_init is provided, it will be called repeatedly, "
172                "in a separate thread until the process is killed. It should "
173                "sleep at most for a second.\n"
174            "\tfilter_close(local, remote):\n"
175            "\t\tCalled then the process is killed, if filter_init was provided. "
176                "It should, among other things, close the file descriptors.\n"
177            "\n"
178            "Python modules are expected to return a tuple in filter_init, "
179            "either of file descriptors or file objects, while native ones "
180            "will receive two int*.\n"
181            "\n"
182            "Python modules can additionally contain a custom queue class "
183            "that will replace the FIFO used by default. The class should "
184            "be named 'queueclass' and contain an interface compatible with "
185            "collections.deque. That is, indexing (especiall for q[0]), "
186            "bool(q), popleft, appendleft, pop (right), append (right), "
187            "len(q) and clear. When using a custom queue, queue size will "
188            "have no effect, pass an effective queue size to the module "
189            "by using filter_args" )
190 parser.add_option(
191     "--filter-args", dest="filter_args", metavar="FILE",
192     default = None,
193     help = "If specified, packets won't be logged to standard output, "
194            "but dumped to a pcap-formatted trace in the specified file. " )
195
196 (options, remaining_args) = parser.parse_args(sys.argv[1:])
197
198 options.cipher = {
199     'aes' : 'AES',
200     'des' : 'DES',
201     'des3' : 'DES3',
202     'blowfish' : 'Blowfish',
203     'plain' : None,
204 }[options.cipher.lower()]
205
206 ETH_P_ALL = 0x00000003
207 ETH_P_IP = 0x00000800
208 TUNSETIFF = 0x400454ca
209 IFF_NO_PI = 0x00001000
210 IFF_TAP = 0x00000002
211 IFF_TUN = 0x00000001
212 IFF_VNET_HDR = 0x00004000
213 TUN_PKT_STRIP = 0x00000001
214 IFHWADDRLEN = 0x00000006
215 IFNAMSIZ = 0x00000010
216 IFREQ_SZ = 0x00000028
217 FIONREAD = 0x0000541b
218
219 class HostLock(object):
220     # This class is used as a lock to prevent concurrency issues with more
221     # than one instance of netns running in the same machine. Both in 
222     # different processes or different threads.
223     taken = False
224     processcond = threading.Condition()
225     
226     def __init__(self, lockfile):
227         processcond = self.__class__.processcond
228         
229         processcond.acquire()
230         try:
231             # It's not reentrant
232             while self.__class__.taken:
233                 processcond.wait()
234             self.__class__.taken = True
235         finally:
236             processcond.release()
237         
238         self.lockfile = lockfile
239         
240         while True:
241             try:
242                 fcntl.flock(self.lockfile, fcntl.LOCK_EX)
243                 break
244             except (OSError, IOError), e:
245                 if e.args[0] != os.errno.EINTR:
246                     raise
247     
248     def __del__(self):
249         processcond = self.__class__.processcond
250         
251         processcond.acquire()
252         try:
253             if not self.lockfile.closed:
254                 fcntl.flock(self.lockfile, fcntl.LOCK_UN)
255             
256             # It's not reentrant
257             self.__class__.taken = False
258             processcond.notify()
259         finally:
260             processcond.release()
261
262 def ifnam(x):
263     return x+'\x00'*(IFNAMSIZ-len(x))
264
265 def ifreq(iface, flags):
266     # ifreq contains:
267     #   char[IFNAMSIZ] : interface name
268     #   short : flags
269     #   <padding>
270     ifreq = ifnam(iface)+struct.pack("H",flags);
271     ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
272     return ifreq
273
274 def tunopen(tun_path, tun_name):
275     if tun_path.isdigit():
276         # open TUN fd
277         print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
278         tun = os.fdopen(int(tun_path), 'r+b', 0)
279     else:
280         # open TUN path
281         print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
282         tun = open(tun_path, 'r+b', 0)
283
284         # bind file descriptor to the interface
285         fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
286     
287     return tun
288
289 def tunclose(tun_path, tun_name, tun):
290     if tun_path and tun_path.isdigit():
291         # close TUN fd
292         os.close(int(tun_path))
293         tun.close()
294     elif tun:
295         # close TUN object
296         tun.close()
297
298 def noopen(tun_path, tun_name):
299     print >>sys.stderr, "Using tun:", tun_name
300     return None
301 def noclose(tun_path, tun_name, tun):
302     pass
303
304 def tuntap_alloc(kind, tun_path, tun_name):
305     args = ["tunctl"]
306     if kind == "tun":
307         args.append("-n")
308     if tun_name:
309         args.append("-t")
310         args.append(tun_name)
311     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
312     out,err = proc.communicate()
313     if proc.wait():
314         raise RuntimeError, "Could not allocate %s device" % (kind,)
315         
316     match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
317     if not match:
318         raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
319     
320     tun_name = match.group("dev")
321     print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
322     
323     return tun_path, tun_name
324
325 def tuntap_dealloc(tun_path, tun_name):
326     args = ["tunctl", "-d", tun_name]
327     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
328     out,err = proc.communicate()
329     if proc.wait():
330         print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
331
332 def nmask_to_dot_notation(mask):
333     mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00
334     mask = mask[2:] # strip 0x
335     mask = mask.decode("hex") # to bytes
336     mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0
337     return mask
338
339 def vif_start(tun_path, tun_name):
340     args = ["ifconfig", tun_name, options.vif_addr, 
341             "netmask", nmask_to_dot_notation(options.vif_mask),
342             "-arp" ]
343     if options.vif_pointopoint:
344         args.extend(["pointopoint",options.vif_pointopoint])
345     if options.vif_txqueuelen is not None:
346         args.extend(["txqueuelen",str(options.vif_txqueuelen)])
347     args.append("up")
348     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
349     out,err = proc.communicate()
350     if proc.wait():
351         raise RuntimeError, "Error starting virtual interface"
352     
353     if options.vif_snat:
354         # set up SNAT using iptables
355         # TODO: stop vif on error. 
356         #   Not so necessary since deallocating the tun/tap device
357         #   will forcibly stop it, but it would be tidier
358         args = [ "iptables", "-t", "nat", "-A", "POSTROUTING", 
359                  "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
360                  "-j", "SNAT",
361                  "--to-source", hostaddr, "--random" ]
362         proc = subprocess.Popen(args, stdout=subprocess.PIPE)
363         out,err = proc.communicate()
364         if proc.wait():
365             raise RuntimeError, "Error setting up SNAT"
366
367 def vif_stop(tun_path, tun_name):
368     if options.vif_snat:
369         # set up SNAT using iptables
370         args = [ "iptables", "-t", "nat", "-D", "POSTROUTING", 
371                  "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
372                  "-j", "SNAT",
373                  "--to-source", hostaddr, "--random" ]
374         proc = subprocess.Popen(args, stdout=subprocess.PIPE)
375         out,err = proc.communicate()
376     
377     args = ["ifconfig", tun_name, "down"]
378     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
379     out,err = proc.communicate()
380     if proc.wait():
381         print >>sys.stderr, "WARNING: error stopping virtual interface"
382     
383     
384 def pl_tuntap_alloc(kind, tun_path, tun_name):
385     tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
386     c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
387     kind = {"tun":IFF_TUN,
388             "tap":IFF_TAP}[kind]
389     fd = tunalloc_so.tun_alloc(kind, c_tun_name)
390     name = c_tun_name.value
391     return str(fd), name
392
393 _name_reservation = None
394 def pl_tuntap_namealloc(kind, tun_path, tun_name):
395     global _name_reservation
396     # Serialize access
397     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
398     lock = HostLock(lockfile)
399     
400     # We need to do this, fd_tuntap is the only one who can
401     # tell us our slice id (this script runs as root, so no uid),
402     # and the pattern of device names accepted by vsys scripts
403     tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
404     c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
405     nkind= {"tun":IFF_TUN,
406             "tap":IFF_TAP}[kind]
407     fd = tunalloc_so.tun_alloc(nkind, c_tun_name)
408     name = c_tun_name.value
409     os.close(fd)
410
411     base = name[:name.index('-')+1]
412     existing = set(map(str.strip,os.popen("ip a | grep -o '%s[0-9]*'" % (base,)).read().strip().split('\n')))
413     
414     for i in xrange(9000,10000):
415         name = base + str(i)
416         if name not in existing:
417             break
418     else:
419         raise RuntimeError, "Could not assign interface name"
420     
421     _name_reservation = lock
422     
423     return None, name
424
425 def pl_vif_start(tun_path, tun_name):
426     global _name_reservation
427
428     out = []
429     def outreader():
430         out.append(stdout.read())
431         stdout.close()
432         time.sleep(1)
433
434     # Serialize access to vsys
435     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
436     lock = _name_reservation or HostLock(lockfile)
437     _name_reservation = None
438     
439     stdin = open("/vsys/vif_up.in","w")
440     stdout = open("/vsys/vif_up.out","r")
441
442     t = threading.Thread(target=outreader)
443     t.start()
444     
445     stdin.write(tun_name+"\n")
446     stdin.write(options.vif_addr+"\n")
447     stdin.write(str(options.vif_mask)+"\n")
448     if options.vif_snat:
449         stdin.write("snat=1\n")
450     if options.vif_pointopoint:
451         stdin.write("pointopoint=%s\n" % (options.vif_pointopoint,))
452     if options.vif_txqueuelen is not None:
453         stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
454     if options.mode.startswith('pl-gre'):
455         stdin.write("gre=%s\n" % (options.gre_key,))
456         stdin.write("remote=%s\n" % (remaining_args[0],))
457     stdin.close()
458     
459     t.join()
460     out = ''.join(out)
461     if out.strip():
462         print >>sys.stderr, out
463     
464     del lock, lockfile
465
466 def pl_vif_stop(tun_path, tun_name):
467     out = []
468     def outreader():
469         out.append(stdout.read())
470         stdout.close()
471         
472         if options.mode.startswith('pl-gre'):
473             lim = 120
474         else:
475             lim = 2
476         
477         for i in xrange(lim):
478             ifaces = set(map(str.strip,os.popen("ip a | grep -o '%s'" % (tun_name,)).read().strip().split('\n')))
479             if tun_name in ifaces:
480                 time.sleep(1)
481             else:
482                 break
483
484     # Serialize access to vsys
485     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
486     lock = HostLock(lockfile)
487
488     stdin = open("/vsys/vif_down.in","w")
489     stdout = open("/vsys/vif_down.out","r")
490     
491     t = threading.Thread(target=outreader)
492     t.start()
493     
494     stdin.write(tun_name+"\n")
495     stdin.close()
496     
497     t.join()
498     out = ''.join(out)
499     if out.strip():
500         print >>sys.stderr, out
501     
502     del lock, lockfile
503
504
505 def tun_fwd(tun, remote, reconnect = None, accept_local = None, accept_remote = None, slowlocal = True, bwlimit = None):
506     global TERMINATE
507     
508     tunqueue = options.vif_txqueuelen or 1000
509     tunkqueue = 500
510     
511     # in PL mode, we cannot strip PI structs
512     # so we'll have to handle them
513     tunchannel.tun_fwd(tun, remote,
514         with_pi = options.mode.startswith('pl-'),
515         ether_mode = tun_name.startswith('tap'),
516         cipher_key = options.cipher_key,
517         udp = options.udp,
518         TERMINATE = TERMINATE,
519         stderr = None,
520         reconnect = reconnect,
521         tunqueue = tunqueue,
522         tunkqueue = tunkqueue,
523         cipher = options.cipher,
524         accept_local = accept_local,
525         accept_remote = accept_remote,
526         queueclass = queueclass,
527         slowlocal = slowlocal,
528         bwlimit = bwlimit
529     )
530
531
532
533 nop = lambda tun_path, tun_name : (tun_path, tun_name)
534 MODEINFO = {
535     'none' : dict(alloc=nop,
536                   tunopen=tunopen, tunclose=tunclose,
537                   dealloc=nop,
538                   start=nop,
539                   stop=nop),
540     'tun'  : dict(alloc=functools.partial(tuntap_alloc, "tun"),
541                   tunopen=tunopen, tunclose=tunclose,
542                   dealloc=tuntap_dealloc,
543                   start=vif_start,
544                   stop=vif_stop),
545     'tap'  : dict(alloc=functools.partial(tuntap_alloc, "tap"),
546                   tunopen=tunopen, tunclose=tunclose,
547                   dealloc=tuntap_dealloc,
548                   start=vif_start,
549                   stop=vif_stop),
550     'pl-tun'  : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
551                   tunopen=tunopen, tunclose=tunclose,
552                   dealloc=nop,
553                   start=pl_vif_start,
554                   stop=pl_vif_stop),
555     'pl-tap'  : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
556                   tunopen=tunopen, tunclose=tunclose,
557                   dealloc=nop,
558                   start=pl_vif_start,
559                   stop=pl_vif_stop),
560     'pl-gre-ip' : dict(alloc=functools.partial(pl_tuntap_namealloc, "tun"),
561                   tunopen=noopen, tunclose=tunclose,
562                   dealloc=nop,
563                   start=pl_vif_start,
564                   stop=pl_vif_stop),
565     'pl-gre-eth': dict(alloc=functools.partial(pl_tuntap_namealloc, "tap"),
566                   tunopen=noopen, tunclose=noclose,
567                   dealloc=nop,
568                   start=pl_vif_start,
569                   stop=pl_vif_stop),
570 }
571     
572 tun_path = options.tun_path
573 tun_name = options.tun_name
574
575 modeinfo = MODEINFO[options.mode]
576
577 # Try to load filter module
578 filter_thread = None
579 if options.filter_module:
580     print >>sys.stderr, "Loading module", options.filter_module, "with args", options.filter_args
581     if options.filter_module.endswith('.py'):
582         sys.path.append(os.path.dirname(options.filter_module))
583         filter_module = __import__(os.path.basename(options.filter_module).rsplit('.',1)[0])
584         if options.filter_args:
585             try:
586                 filter_args = dict(map(lambda x:x.split('=',1),options.filter_args.split(',')))
587                 filter_module.init(**filter_args)
588             except:
589                 pass
590     elif options.filter_module.endswith('.so'):
591         filter_module = ctypes.cdll.LoadLibrary(options.filter_module)
592         if options.filter_args:
593             try:
594                 filter_module.init(options.filter_args)
595             except:
596                 pass
597     try:
598         accept_packet = filter_module.accept_packet
599         print >>sys.stderr, "Installing packet filter (accept_packet)"
600     except:
601         accept_packet = None
602     
603     try:
604         queueclass = filter_module.queueclass
605         print >>sys.stderr, "Installing custom queue"
606     except:
607         queueclass = None
608     
609     try:
610         _filter_init = filter_module.filter_init
611         filter_run = filter_module.filter_run
612         filter_close = filter_module.filter_close
613         
614         def filter_init():
615             filter_local = ctypes.c_int(0)
616             filter_remote = ctypes.c_int(0)
617             _filter_init(filter_local, filter_remote)
618             return filter_local, filter_remote
619
620         print >>sys.stderr, "Installing packet filter (stream filter)"
621     except:
622         filter_init = None
623         filter_run = None
624         filter_close = None
625 else:
626     accept_packet = None
627     filter_init = None
628     filter_run = None
629     filter_close = None
630     queueclass = None
631
632 # install multicast forwarding hook
633 if options.multicast_fwd:
634     print >>sys.stderr, "Connecting to mcast filter"
635     mcfwd_sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
636     tunchannel.nonblock(mcfwd_sock.fileno())
637
638 # be careful to roll back stuff on exceptions
639 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
640 try:
641     modeinfo['start'](tun_path, tun_name)
642     try:
643         tun = modeinfo['tunopen'](tun_path, tun_name)
644     except:
645         modeinfo['stop'](tun_path, tun_name)
646         raise
647 except:
648     modeinfo['dealloc'](tun_path, tun_name)
649     raise
650
651
652 # Trak SIGTERM, and set global termination flag instead of dying
653 TERMINATE = []
654 def _finalize(sig,frame):
655     global TERMINATE
656     TERMINATE.append(None)
657 signal.signal(signal.SIGTERM, _finalize)
658
659 try:
660     tcpdump = None
661     reconnect = None
662     mcastthread = None
663
664     # install multicast forwarding hook
665     if options.multicast_fwd:
666         print >>sys.stderr, "Installing mcast filter"
667         
668         if HAS_IOVEC:
669             writev = iovec.writev
670         else:
671             os_write = os.write
672             map_ = map
673             str_ = str
674             def writev(fileno, *stuff):
675                 os_write(''.join(map_(str_,stuff)))
676         
677         def accept_packet(packet, direction, 
678                 _up_accept=accept_packet, 
679                 sock=mcfwd_sock, 
680                 sockno=mcfwd_sock.fileno(),
681                 etherProto=tunchannel.etherProto,
682                 etherStrip=tunchannel.etherStrip,
683                 etherMode=tun_name.startswith('tap'),
684                 multicast_fwd = options.multicast_fwd,
685                 vif_addr = socket.inet_aton(options.vif_addr),
686                 connected = [], writev=writev,
687                 len=len, ord=ord):
688             if _up_accept:
689                 rv = _up_accept(packet, direction)
690                 if not rv:
691                     return rv
692
693             if direction == 1:
694                 # Incoming... what?
695                 if etherMode:
696                     if etherProto(packet)=='\x08\x00':
697                         fwd = etherStrip(packet)
698                     else:
699                         fwd = None
700                 else:
701                     fwd = packet
702                 if fwd is not None and len(fwd) >= 20:
703                     if (ord(fwd[16]) & 0xf0) == 0xe0:
704                         # Forward it
705                         if not connected:
706                             try:
707                                 sock.connect(multicast_fwd)
708                                 connected.append(None)
709                             except:
710                                 traceback.print_exc(file=sys.stderr)
711                         if connected:
712                             try:
713                                 writev(sockno, vif_addr,fwd)
714                             except:
715                                 traceback.print_exc(file=sys.stderr)
716             return 1
717
718     
719     if options.pass_fd:
720         if accept_packet or filter_init:
721             raise NotImplementedError, "--pass-fd and --filter are not compatible"
722         
723         if options.pass_fd.startswith("base64:"):
724             options.pass_fd = base64.b64decode(
725                 options.pass_fd[len("base64:"):])
726             options.pass_fd = os.path.expandvars(options.pass_fd)
727         
728         print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
729         
730         # send FD to whoever wants it
731         import passfd
732         
733         sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
734         retrydelay = 1.0
735         for i in xrange(30):
736             if TERMINATE:
737                 raise OSError, "Killed"
738             try:
739                 sock.connect(options.pass_fd)
740                 break
741             except socket.error:
742                 # wait a while, retry
743                 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
744                 time.sleep(min(30.0,retrydelay))
745                 retrydelay *= 1.1
746         else:
747             sock.connect(options.pass_fd)
748         passfd.sendfd(sock, tun.fileno(), '0')
749         
750         # just wait forever
751         def tun_fwd(tun, remote, **kw):
752             global TERMINATE
753             TERM = TERMINATE
754             while not TERM:
755                 time.sleep(1)
756         remote = None
757     elif options.mode.startswith('pl-gre'):
758         if accept_packet or filter_init:
759             raise NotImplementedError, "--mode %s and --filter are not compatible" % (options.mode,)
760         
761         # just wait forever
762         def tun_fwd(tun, remote, **kw):
763             global TERMINATE
764             TERM = TERMINATE
765             while not TERM:
766                 time.sleep(1)
767         remote = remaining_args[0]
768     elif options.udp:
769         # connect to remote endpoint
770         if remaining_args and not remaining_args[0].startswith('-'):
771             print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
772             print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
773             rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
774             retrydelay = 1.0
775             for i in xrange(30):
776                 if TERMINATE:
777                     raise OSError, "Killed"
778                 try:
779                     rsock.bind((hostaddr,options.udp))
780                     break
781                 except socket.error:
782                     # wait a while, retry
783                     print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
784                     time.sleep(min(30.0,retrydelay))
785                     retrydelay *= 1.1
786             else:
787                 rsock.bind((hostaddr,options.udp))
788             rsock.connect((remaining_args[0],options.port))
789         else:
790             print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
791             raise AssertionError, "Error: need a remote endpoint in UDP mode"
792         
793         # Wait for other peer
794         tunchannel.udp_handshake(TERMINATE, rsock)
795         
796         remote = os.fdopen(rsock.fileno(), 'r+b', 0)
797     else:
798         # connect to remote endpoint
799         if remaining_args and not remaining_args[0].startswith('-'):
800             print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
801             rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
802             retrydelay = 1.0
803             for i in xrange(30):
804                 if TERMINATE:
805                     raise OSError, "Killed"
806                 try:
807                     rsock.connect((remaining_args[0],options.port))
808                     break
809                 except socket.error:
810                     # wait a while, retry
811                     print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
812                     time.sleep(min(30.0,retrydelay))
813                     retrydelay *= 1.1
814             else:
815                 rsock.connect((remaining_args[0],options.port))
816         else:
817             print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
818             lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
819             retrydelay = 1.0
820             for i in xrange(30):
821                 if TERMINATE:
822                     raise OSError, "Killed"
823                 try:
824                     lsock.bind((hostaddr,options.port))
825                     break
826                 except socket.error:
827                     # wait a while, retry
828                     print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
829                     time.sleep(min(30.0,retrydelay))
830                     retrydelay *= 1.1
831             else:
832                 lsock.bind((hostaddr,options.port))
833             lsock.listen(1)
834             rsock,raddr = lsock.accept()
835         remote = os.fdopen(rsock.fileno(), 'r+b', 0)
836
837     if filter_init:
838         filter_local, filter_remote = filter_init()
839         
840         def filter_loop():
841             global TERMINATE
842             TERM = TERMINATE
843             run = filter_run
844             local = filter_local
845             remote = filter_remote
846             while not TERM:
847                 run(local, remote)
848             filter_close(local, remote)
849             
850         filter_thread = threading.Thread(target=filter_loop)
851         filter_thread.start()
852     
853     print >>sys.stderr, "Connected"
854
855     if not options.no_capture:
856         # Launch a tcpdump subprocess, to capture and dump packets.
857         # Make sure to catch sigterm and kill the tcpdump as well
858         tcpdump = subprocess.Popen(
859             ["tcpdump","-l","-n","-i",tun_name, "-s", "4096"]
860             + ["-w",options.pcap_capture,"-U"] * bool(options.pcap_capture) )
861     
862     # Try to give us high priority
863     try:
864         os.nice(-20)
865     except:
866         # Ignore errors, we might not have enough privileges,
867         # or perhaps there is no os.nice support in the system
868         pass
869     
870     if not filter_init:
871         tun_fwd(tun, remote,
872             reconnect = reconnect,
873             accept_local = accept_packet,
874             accept_remote = accept_packet,
875             bwlimit = options.bwlimit,
876             slowlocal = True)
877     else:
878         # Hm...
879         # ...ok, we need to:
880         #  1. Forward packets from tun to filter
881         #  2. Forward packets from remote to filter
882         #
883         # 1. needs TUN rate-limiting, while 
884         # 2. needs reconnection
885         #
886         # 1. needs ONLY TUN-side acceptance checks, while
887         # 2. needs ONLY remote-side acceptance checks
888         if isinstance(filter_local, ctypes.c_int):
889             filter_local_fd = filter_local.value
890         else:
891             filter_local_fd = filter_local
892         if isinstance(filter_remote, ctypes.c_int):
893             filter_remote_fd = filter_remote.value
894         else:
895             filter_remote_fd = filter_remote
896
897         def localside():
898             tun_fwd(tun, filter_local_fd,
899                 accept_local = accept_packet,
900                 slowlocal = True)
901         
902         def remoteside():
903             tun_fwd(filter_remote_fd, remote,
904                 reconnect = reconnect,
905                 accept_remote = accept_packet,
906                 bwlimit = options.bwlimit,
907                 slowlocal = False)
908         
909         localthread = threading.Thread(target=localside)
910         remotethread = threading.Thread(target=remoteside)
911         localthread.start()
912         remotethread.start()
913         localthread.join()
914         remotethread.join()
915
916 finally:
917     try:
918         print >>sys.stderr, "Shutting down..."
919     except:
920         # In case sys.stderr is broken
921         pass
922     
923     # tidy shutdown in every case - swallow exceptions
924     TERMINATE.append(None)
925     
926     if mcastthread:
927         try:
928             mcastthread.stop()
929         except:
930             pass
931     
932     if filter_thread:
933         try:
934             filter_thread.join()
935         except:
936             pass
937
938     try:
939         if tcpdump:
940             os.kill(tcpdump.pid, signal.SIGTERM)
941             tcpdump.wait()
942     except:
943         pass
944
945     try:
946         modeinfo['stop'](tun_path, tun_name)
947     except:
948         traceback.print_exc()
949
950     try:
951         modeinfo['tunclose'](tun_path, tun_name, tun)
952     except:
953         traceback.print_exc()
954         
955     try:
956         modeinfo['dealloc'](tun_path, tun_name)
957     except:
958         traceback.print_exc()
959     
960     print >>sys.stderr, "TERMINATED GRACEFULLY"
961