Configurable cipher for tunnelling
[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
121 (options, remaining_args) = parser.parse_args(sys.argv[1:])
122
123 options.cipher = {
124     'aes' : 'AES',
125     'des' : 'DES',
126     'des3' : 'DES3',
127     'blowfish' : 'Blowfish',
128     'plain' : None,
129 }[options.cipher.lower()]
130
131 ETH_P_ALL = 0x00000003
132 ETH_P_IP = 0x00000800
133 TUNSETIFF = 0x400454ca
134 IFF_NO_PI = 0x00001000
135 IFF_TAP = 0x00000002
136 IFF_TUN = 0x00000001
137 IFF_VNET_HDR = 0x00004000
138 TUN_PKT_STRIP = 0x00000001
139 IFHWADDRLEN = 0x00000006
140 IFNAMSIZ = 0x00000010
141 IFREQ_SZ = 0x00000028
142 FIONREAD = 0x0000541b
143
144 class HostLock(object):
145     # This class is used as a lock to prevent concurrency issues with more
146     # than one instance of netns running in the same machine. Both in 
147     # different processes or different threads.
148     taken = False
149     processcond = threading.Condition()
150     
151     def __init__(self, lockfile):
152         processcond = self.__class__.processcond
153         
154         processcond.acquire()
155         try:
156             # It's not reentrant
157             while self.__class__.taken:
158                 processcond.wait()
159             self.__class__.taken = True
160         finally:
161             processcond.release()
162         
163         self.lockfile = lockfile
164         
165         while True:
166             try:
167                 fcntl.flock(self.lockfile, fcntl.LOCK_EX)
168                 break
169             except (OSError, IOError), e:
170                 if e.args[0] != os.errno.EINTR:
171                     raise
172     
173     def __del__(self):
174         processcond = self.__class__.processcond
175         
176         processcond.acquire()
177         try:
178             if not self.lockfile.closed:
179                 fcntl.flock(self.lockfile, fcntl.LOCK_UN)
180             
181             # It's not reentrant
182             self.__class__.taken = False
183             processcond.notify()
184         finally:
185             processcond.release()
186
187 def ifnam(x):
188     return x+'\x00'*(IFNAMSIZ-len(x))
189
190 def ifreq(iface, flags):
191     # ifreq contains:
192     #   char[IFNAMSIZ] : interface name
193     #   short : flags
194     #   <padding>
195     ifreq = ifnam(iface)+struct.pack("H",flags);
196     ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
197     return ifreq
198
199 def tunopen(tun_path, tun_name):
200     if tun_path.isdigit():
201         # open TUN fd
202         print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
203         tun = os.fdopen(int(tun_path), 'r+b', 0)
204     else:
205         # open TUN path
206         print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
207         tun = open(tun_path, 'r+b', 0)
208
209         # bind file descriptor to the interface
210         fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
211     
212     return tun
213
214 def tunclose(tun_path, tun_name, tun):
215     if tun_path.isdigit():
216         # close TUN fd
217         os.close(int(tun_path))
218         tun.close()
219     else:
220         # close TUN object
221         tun.close()
222
223 def noopen(tun_path, tun_name):
224     print >>sys.stderr, "Using tun:", tun_name
225     return None
226 def noclose(tun_path, tun_name, tun):
227     pass
228
229 def tuntap_alloc(kind, tun_path, tun_name):
230     args = ["tunctl"]
231     if kind == "tun":
232         args.append("-n")
233     if tun_name:
234         args.append("-t")
235         args.append(tun_name)
236     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
237     out,err = proc.communicate()
238     if proc.wait():
239         raise RuntimeError, "Could not allocate %s device" % (kind,)
240         
241     match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
242     if not match:
243         raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
244     
245     tun_name = match.group("dev")
246     print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
247     
248     return tun_path, tun_name
249
250 def tuntap_dealloc(tun_path, tun_name):
251     args = ["tunctl", "-d", tun_name]
252     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
253     out,err = proc.communicate()
254     if proc.wait():
255         print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
256
257 def nmask_to_dot_notation(mask):
258     mask = hex(((1 << mask) - 1) << (32 - mask)) # 24 -> 0xFFFFFF00
259     mask = mask[2:] # strip 0x
260     mask = mask.decode("hex") # to bytes
261     mask = '.'.join(map(str,map(ord,mask))) # to 255.255.255.0
262     return mask
263
264 def vif_start(tun_path, tun_name):
265     args = ["ifconfig", tun_name, options.vif_addr, 
266             "netmask", nmask_to_dot_notation(options.vif_mask),
267             "-arp" ]
268     if options.vif_pointopoint:
269         args.extend(["pointopoint",options.vif_pointopoint])
270     if options.vif_txqueuelen is not None:
271         args.extend(["txqueuelen",str(options.vif_txqueuelen)])
272     args.append("up")
273     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
274     out,err = proc.communicate()
275     if proc.wait():
276         raise RuntimeError, "Error starting virtual interface"
277     
278     if options.vif_snat:
279         # set up SNAT using iptables
280         # TODO: stop vif on error. 
281         #   Not so necessary since deallocating the tun/tap device
282         #   will forcibly stop it, but it would be tidier
283         args = [ "iptables", "-t", "nat", "-A", "POSTROUTING", 
284                  "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
285                  "-j", "SNAT",
286                  "--to-source", hostaddr, "--random" ]
287         proc = subprocess.Popen(args, stdout=subprocess.PIPE)
288         out,err = proc.communicate()
289         if proc.wait():
290             raise RuntimeError, "Error setting up SNAT"
291
292 def vif_stop(tun_path, tun_name):
293     if options.vif_snat:
294         # set up SNAT using iptables
295         args = [ "iptables", "-t", "nat", "-D", "POSTROUTING", 
296                  "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
297                  "-j", "SNAT",
298                  "--to-source", hostaddr, "--random" ]
299         proc = subprocess.Popen(args, stdout=subprocess.PIPE)
300         out,err = proc.communicate()
301     
302     args = ["ifconfig", tun_name, "down"]
303     proc = subprocess.Popen(args, stdout=subprocess.PIPE)
304     out,err = proc.communicate()
305     if proc.wait():
306         print >>sys.stderr, "WARNING: error stopping virtual interface"
307     
308     
309 def pl_tuntap_alloc(kind, tun_path, tun_name):
310     tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
311     c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
312     kind = {"tun":IFF_TUN,
313             "tap":IFF_TAP}[kind]
314     fd = tunalloc_so.tun_alloc(kind, c_tun_name)
315     name = c_tun_name.value
316     return str(fd), name
317
318 _name_reservation = None
319 def pl_tuntap_namealloc(kind, tun_path, tun_name):
320     global _name_reservation
321     # Serialize access
322     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
323     _name_reservation = lock = HostLock(lockfile)
324     
325     # We need to do this, fd_tuntap is the only one who can
326     # tell us our slice id (this script runs as root, so no uid),
327     # and the pattern of device names accepted by vsys scripts
328     tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
329     c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
330     nkind= {"tun":IFF_TUN,
331             "tap":IFF_TAP}[kind]
332     fd = tunalloc_so.tun_alloc(nkind, c_tun_name)
333     name = c_tun_name.value
334     os.close(fd)
335
336     base = name[:name.index('-')+1]
337     existing = set(map(str.strip,os.popen("ip a | grep -o '%s[0-9]*'" % (base,)).read().strip().split('\n')))
338     
339     for i in xrange(9000,10000):
340         name = base + str(i)
341         if name not in existing:
342             break
343     else:
344         raise RuntimeError, "Could not assign interface name"
345     
346     return None, name
347
348 def pl_vif_start(tun_path, tun_name):
349     global _name_reservation
350
351     out = []
352     def outreader():
353         stdout = open("/vsys/vif_up.out","r")
354         out.append(stdout.read())
355         stdout.close()
356         time.sleep(1)
357
358     # Serialize access to vsys
359     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
360     lock = _name_reservation or HostLock(lockfile)
361     _name_reservation = None
362     
363     stdin = open("/vsys/vif_up.in","w")
364
365     t = threading.Thread(target=outreader)
366     t.start()
367     
368     stdin.write(tun_name+"\n")
369     stdin.write(options.vif_addr+"\n")
370     stdin.write(str(options.vif_mask)+"\n")
371     if options.vif_snat:
372         stdin.write("snat=1\n")
373     if options.vif_pointopoint:
374         stdin.write("pointopoint=%s\n" % (options.vif_pointopoint,))
375     if options.vif_txqueuelen is not None:
376         stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
377     if options.mode.startswith('pl-gre'):
378         stdin.write("gre=%d\n" % (options.gre_key,))
379         stdin.write("remote=%s\n" % (remaining_args[0],))
380     stdin.close()
381     
382     t.join()
383     out = ''.join(out)
384     if out.strip():
385         print >>sys.stderr, out
386     
387     del lock, lockfile
388
389 def pl_vif_stop(tun_path, tun_name):
390     out = []
391     def outreader():
392         stdout = open("/vsys/vif_down.out","r")
393         out.append(stdout.read())
394         stdout.close()
395         
396         while True:
397             ifaces = set(map(str.strip,os.popen("ip a | grep -o '%s'" % (tun_name,)).read().strip().split('\n')))
398             if tun_name in ifaces:
399                 time.sleep(1)
400             else:
401                 break
402
403     # Serialize access to vsys
404     lockfile = open("/tmp/nepi-tun-connect.lock", "a")
405     lock = HostLock(lockfile)
406
407     stdin = open("/vsys/vif_down.in","w")
408     
409     t = threading.Thread(target=outreader)
410     t.start()
411     
412     stdin.write(tun_name+"\n")
413     stdin.close()
414     
415     t.join()
416     out = ''.join(out)
417     if out.strip():
418         print >>sys.stderr, out
419     
420     del lock, lockfile
421
422
423 def tun_fwd(tun, remote, reconnect = None):
424     global TERMINATE
425     
426     tunqueue = options.vif_txqueuelen or 1000
427     tunkqueue = 500
428     
429     # in PL mode, we cannot strip PI structs
430     # so we'll have to handle them
431     tunchannel.tun_fwd(tun, remote,
432         with_pi = options.mode.startswith('pl-'),
433         ether_mode = tun_name.startswith('tap'),
434         cipher_key = options.cipher_key,
435         udp = options.udp,
436         TERMINATE = TERMINATE,
437         stderr = None,
438         reconnect = reconnect,
439         tunqueue = tunqueue,
440         tunkqueue = tunkqueue,
441         cipher = options.cipher
442     )
443
444
445
446 nop = lambda tun_path, tun_name : (tun_path, tun_name)
447 MODEINFO = {
448     'none' : dict(alloc=nop,
449                   tunopen=tunopen, tunclose=tunclose,
450                   dealloc=nop,
451                   start=nop,
452                   stop=nop),
453     'tun'  : dict(alloc=functools.partial(tuntap_alloc, "tun"),
454                   tunopen=tunopen, tunclose=tunclose,
455                   dealloc=tuntap_dealloc,
456                   start=vif_start,
457                   stop=vif_stop),
458     'tap'  : dict(alloc=functools.partial(tuntap_alloc, "tap"),
459                   tunopen=tunopen, tunclose=tunclose,
460                   dealloc=tuntap_dealloc,
461                   start=vif_start,
462                   stop=vif_stop),
463     'pl-tun'  : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
464                   tunopen=tunopen, tunclose=tunclose,
465                   dealloc=nop,
466                   start=pl_vif_start,
467                   stop=pl_vif_stop),
468     'pl-tap'  : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
469                   tunopen=tunopen, tunclose=tunclose,
470                   dealloc=nop,
471                   start=pl_vif_start,
472                   stop=pl_vif_stop),
473     'pl-gre-ip' : dict(alloc=functools.partial(pl_tuntap_namealloc, "tun"),
474                   tunopen=noopen, tunclose=tunclose,
475                   dealloc=nop,
476                   start=pl_vif_start,
477                   stop=pl_vif_stop),
478     'pl-gre-eth': dict(alloc=functools.partial(pl_tuntap_namealloc, "tap"),
479                   tunopen=noopen, tunclose=noclose,
480                   dealloc=nop,
481                   start=pl_vif_start,
482                   stop=pl_vif_stop),
483 }
484     
485 tun_path = options.tun_path
486 tun_name = options.tun_name
487
488 modeinfo = MODEINFO[options.mode]
489
490 # be careful to roll back stuff on exceptions
491 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
492 try:
493     modeinfo['start'](tun_path, tun_name)
494     try:
495         tun = modeinfo['tunopen'](tun_path, tun_name)
496     except:
497         modeinfo['stop'](tun_path, tun_name)
498         raise
499 except:
500     modeinfo['dealloc'](tun_path, tun_name)
501     raise
502
503
504 # Trak SIGTERM, and set global termination flag instead of dying
505 TERMINATE = []
506 def _finalize(sig,frame):
507     global TERMINATE
508     TERMINATE.append(None)
509 signal.signal(signal.SIGTERM, _finalize)
510
511 try:
512     tcpdump = None
513     reconnect = None
514     
515     if options.pass_fd:
516         if options.pass_fd.startswith("base64:"):
517             options.pass_fd = base64.b64decode(
518                 options.pass_fd[len("base64:"):])
519             options.pass_fd = os.path.expandvars(options.pass_fd)
520         
521         print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
522         
523         # send FD to whoever wants it
524         import passfd
525         
526         sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
527         retrydelay = 1.0
528         for i in xrange(30):
529             try:
530                 sock.connect(options.pass_fd)
531                 break
532             except socket.error:
533                 # wait a while, retry
534                 print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
535                 time.sleep(min(30.0,retrydelay))
536                 retrydelay *= 1.1
537         else:
538             sock.connect(options.pass_fd)
539         passfd.sendfd(sock, tun.fileno(), '0')
540         
541         # just wait forever
542         def tun_fwd(tun, remote, **kw):
543             while not TERMINATE:
544                 time.sleep(1)
545         remote = None
546     elif options.mode.startswith('pl-gre'):
547         # just wait forever
548         def tun_fwd(tun, remote, **kw):
549             while not TERMINATE:
550                 time.sleep(1)
551         remote = remaining_args[0]
552     elif options.udp:
553         # connect to remote endpoint
554         if remaining_args and not remaining_args[0].startswith('-'):
555             print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
556             print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
557             rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
558             retrydelay = 1.0
559             for i in xrange(30):
560                 try:
561                     rsock.bind((hostaddr,options.udp))
562                     break
563                 except socket.error:
564                     # wait a while, retry
565                     print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
566                     time.sleep(min(30.0,retrydelay))
567                     retrydelay *= 1.1
568             else:
569                 rsock.bind((hostaddr,options.udp))
570             rsock.connect((remaining_args[0],options.port))
571         else:
572             print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
573             raise AssertionError, "Error: need a remote endpoint in UDP mode"
574         remote = os.fdopen(rsock.fileno(), 'r+b', 0)
575     else:
576         # connect to remote endpoint
577         if remaining_args and not remaining_args[0].startswith('-'):
578             print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
579             rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
580             retrydelay = 1.0
581             for i in xrange(30):
582                 try:
583                     rsock.connect((remaining_args[0],options.port))
584                     break
585                 except socket.error:
586                     # wait a while, retry
587                     print >>sys.stderr, "%s: Could not connect. Retrying in a sec..." % (time.strftime('%c'),)
588                     time.sleep(min(30.0,retrydelay))
589                     retrydelay *= 1.1
590             else:
591                 rsock.connect((remaining_args[0],options.port))
592         else:
593             print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
594             lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
595             retrydelay = 1.0
596             for i in xrange(30):
597                 try:
598                     lsock.bind((hostaddr,options.port))
599                     break
600                 except socket.error:
601                     # wait a while, retry
602                     print >>sys.stderr, "%s: Could not bind. Retrying in a sec..." % (time.strftime('%c'),)
603                     time.sleep(min(30.0,retrydelay))
604                     retrydelay *= 1.1
605             else:
606                 lsock.bind((hostaddr,options.port))
607             lsock.listen(1)
608             rsock,raddr = lsock.accept()
609         remote = os.fdopen(rsock.fileno(), 'r+b', 0)
610
611     if not options.no_capture:
612         # Launch a tcpdump subprocess, to capture and dump packets.
613         # Make sure to catch sigterm and kill the tcpdump as well
614         tcpdump = subprocess.Popen(
615             ["tcpdump","-l","-n","-i",tun_name, "-s", "4096"]
616             + ["-w",options.pcap_capture,"-U"] * bool(options.pcap_capture) )
617     
618     print >>sys.stderr, "Connected"
619     
620     # Try to give us high priority
621     try:
622         os.nice(-20)
623     except:
624         # Ignore errors, we might not have enough privileges,
625         # or perhaps there is no os.nice support in the system
626         pass
627
628     tun_fwd(tun, remote,
629         reconnect = reconnect)
630
631 finally:
632     try:
633         print >>sys.stderr, "Shutting down..."
634     except:
635         # In case sys.stderr is broken
636         pass
637     
638     # tidy shutdown in every case - swallow exceptions
639
640     try:
641         if tcpdump:
642             os.kill(tcpdump.pid, signal.SIGTERM)
643             tcpdump.wait()
644     except:
645         pass
646
647     try:
648         modeinfo['stop'](tun_path, tun_name)
649     except:
650         traceback.print_exc()
651
652     try:
653         modeinfo['tunclose'](tun_path, tun_name, tun)
654     except:
655         traceback.print_exc()
656         
657     try:
658         modeinfo['dealloc'](tun_path, tun_name)
659     except:
660         traceback.print_exc()
661     
662     print >>sys.stderr, "TERMINATED GRACEFULLY"
663