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