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