24 tun_path = '/dev/net/tun'
25 hostaddr = socket.gethostbyname(socket.gethostname())
27 usage = "usage: %prog [options] <remote-endpoint>"
29 parser = optparse.OptionParser(usage=usage)
32 "-i", "--iface", dest="tun_name", metavar="DEVICE",
34 help = "TUN/TAP interface to tap into")
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")
40 "-p", "--port", dest="port", metavar="PORT", type="int",
42 help = "Peering TCP port to connect or listen to.")
44 "--pass-fd", dest="pass_fd", metavar="UNIX_SOCKET",
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.")
52 "-m", "--mode", dest="mode", metavar="MODE",
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).")
60 "-A", "--vif-address", dest="vif_addr", metavar="VIF_ADDRESS",
63 "See mode. This specifies the VIF_ADDRESS, "
64 "the IP address of the virtual interface.")
66 "-M", "--vif-mask", dest="vif_mask", type="int", metavar="VIF_MASK",
69 "See mode. This specifies the VIF_MASK, "
70 "a number indicating the network type (ie: 24 for a C-class network).")
72 "-S", "--vif-snat", dest="vif_snat",
73 action = "store_true",
75 help = "See mode. This specifies whether SNAT will be enabled for the virtual interface. " )
77 "-P", "--vif-pointopoint", dest="vif_pointopoint", metavar="DST_ADDR",
80 "See mode. This specifies the remote endpoint's virtual address, "
81 "for point-to-point routing configuration. "
82 "Not supported by PlanetLab" )
84 "-Q", "--vif-txqueuelen", dest="vif_txqueuelen", metavar="SIZE", type="int",
87 "See mode. This specifies the interface's transmission queue length. " )
89 "-u", "--udp", dest="udp", metavar="PORT", type="int",
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." )
95 "-k", "--key", dest="cipher_key", metavar="KEY",
98 "Specify a symmetric encryption key with which to protect packets across "
99 "the tunnel. python-crypto must be installed on the system." )
101 "-K", "--gre-key", dest="gre_key", metavar="KEY", type="int",
104 "Specify a demultiplexing 32-bit numeric key for GRE." )
106 "-C", "--cipher", dest="cipher", metavar="CIPHER",
108 help = "One of PLAIN, AES, Blowfish, DES, DES3. " )
110 "-N", "--no-capture", dest="no_capture",
111 action = "store_true",
113 help = "If specified, packets won't be logged to standard output "
114 "(default is to log them to standard output). " )
116 "-c", "--pcap-capture", dest="pcap_capture", metavar="FILE",
118 help = "If specified, packets won't be logged to standard output, "
119 "but dumped to a pcap-formatted trace in the specified file. " )
121 "--filter", dest="filter_module", metavar="PATH",
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"
130 "Modules are expected to have the following methods:\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"
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"
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"
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" )
173 "--filter-args", dest="filter_args", metavar="FILE",
175 help = "If specified, packets won't be logged to standard output, "
176 "but dumped to a pcap-formatted trace in the specified file. " )
178 (options, remaining_args) = parser.parse_args(sys.argv[1:])
184 'blowfish' : 'Blowfish',
186 }[options.cipher.lower()]
188 ETH_P_ALL = 0x00000003
189 ETH_P_IP = 0x00000800
190 TUNSETIFF = 0x400454ca
191 IFF_NO_PI = 0x00001000
194 IFF_VNET_HDR = 0x00004000
195 TUN_PKT_STRIP = 0x00000001
196 IFHWADDRLEN = 0x00000006
197 IFNAMSIZ = 0x00000010
198 IFREQ_SZ = 0x00000028
199 FIONREAD = 0x0000541b
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.
206 processcond = threading.Condition()
208 def __init__(self, lockfile):
209 processcond = self.__class__.processcond
211 processcond.acquire()
214 while self.__class__.taken:
216 self.__class__.taken = True
218 processcond.release()
220 self.lockfile = lockfile
224 fcntl.flock(self.lockfile, fcntl.LOCK_EX)
226 except (OSError, IOError), e:
227 if e.args[0] != os.errno.EINTR:
231 processcond = self.__class__.processcond
233 processcond.acquire()
235 if not self.lockfile.closed:
236 fcntl.flock(self.lockfile, fcntl.LOCK_UN)
239 self.__class__.taken = False
242 processcond.release()
245 return x+'\x00'*(IFNAMSIZ-len(x))
247 def ifreq(iface, flags):
249 # char[IFNAMSIZ] : interface name
252 ifreq = ifnam(iface)+struct.pack("H",flags);
253 ifreq += '\x00' * (len(ifreq)-IFREQ_SZ)
256 def tunopen(tun_path, tun_name):
257 if tun_path.isdigit():
259 print >>sys.stderr, "Using tun:", tun_name, "fd", tun_path
260 tun = os.fdopen(int(tun_path), 'r+b', 0)
263 print >>sys.stderr, "Using tun:", tun_name, "at", tun_path
264 tun = open(tun_path, 'r+b', 0)
266 # bind file descriptor to the interface
267 fcntl.ioctl(tun.fileno(), TUNSETIFF, ifreq(tun_name, IFF_NO_PI|IFF_TUN))
271 def tunclose(tun_path, tun_name, tun):
272 if tun_path and tun_path.isdigit():
274 os.close(int(tun_path))
280 def noopen(tun_path, tun_name):
281 print >>sys.stderr, "Using tun:", tun_name
283 def noclose(tun_path, tun_name, tun):
286 def tuntap_alloc(kind, tun_path, tun_name):
292 args.append(tun_name)
293 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
294 out,err = proc.communicate()
296 raise RuntimeError, "Could not allocate %s device" % (kind,)
298 match = re.search(r"Set '(?P<dev>(?:tun|tap)[0-9]*)' persistent and owned by .*", out, re.I)
300 raise RuntimeError, "Could not allocate %s device - tunctl said: %s" % (kind, out)
302 tun_name = match.group("dev")
303 print >>sys.stderr, "Allocated %s device: %s" % (kind, tun_name)
305 return tun_path, tun_name
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()
312 print >> sys.stderr, "WARNING: error deallocating %s device" % (tun_name,)
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
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),
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)])
330 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
331 out,err = proc.communicate()
333 raise RuntimeError, "Error starting virtual interface"
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),
343 "--to-source", hostaddr, "--random" ]
344 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
345 out,err = proc.communicate()
347 raise RuntimeError, "Error setting up SNAT"
349 def vif_stop(tun_path, tun_name):
351 # set up SNAT using iptables
352 args = [ "iptables", "-t", "nat", "-D", "POSTROUTING",
353 "-s", "%s/%d" % (options.vif_addr, options.vif_mask),
355 "--to-source", hostaddr, "--random" ]
356 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
357 out,err = proc.communicate()
359 args = ["ifconfig", tun_name, "down"]
360 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
361 out,err = proc.communicate()
363 print >>sys.stderr, "WARNING: error stopping virtual interface"
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,
371 fd = tunalloc_so.tun_alloc(kind, c_tun_name)
372 name = c_tun_name.value
375 _name_reservation = None
376 def pl_tuntap_namealloc(kind, tun_path, tun_name):
377 global _name_reservation
379 lockfile = open("/tmp/nepi-tun-connect.lock", "a")
380 lock = HostLock(lockfile)
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,
389 fd = tunalloc_so.tun_alloc(nkind, c_tun_name)
390 name = c_tun_name.value
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')))
396 for i in xrange(9000,10000):
398 if name not in existing:
401 raise RuntimeError, "Could not assign interface name"
403 _name_reservation = lock
407 def pl_vif_start(tun_path, tun_name):
408 global _name_reservation
412 out.append(stdout.read())
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
421 stdin = open("/vsys/vif_up.in","w")
422 stdout = open("/vsys/vif_up.out","r")
424 t = threading.Thread(target=outreader)
427 stdin.write(tun_name+"\n")
428 stdin.write(options.vif_addr+"\n")
429 stdin.write(str(options.vif_mask)+"\n")
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],))
444 print >>sys.stderr, out
448 def pl_vif_stop(tun_path, tun_name):
451 out.append(stdout.read())
454 if options.mode.startswith('pl-gre'):
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:
466 # Serialize access to vsys
467 lockfile = open("/tmp/nepi-tun-connect.lock", "a")
468 lock = HostLock(lockfile)
470 stdin = open("/vsys/vif_down.in","w")
471 stdout = open("/vsys/vif_down.out","r")
473 t = threading.Thread(target=outreader)
476 stdin.write(tun_name+"\n")
482 print >>sys.stderr, out
487 def tun_fwd(tun, remote, reconnect = None, accept_local = None, accept_remote = None, slowlocal = True):
490 tunqueue = options.vif_txqueuelen or 1000
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,
500 TERMINATE = TERMINATE,
502 reconnect = reconnect,
504 tunkqueue = tunkqueue,
505 cipher = options.cipher,
506 accept_local = accept_local,
507 accept_remote = accept_remote,
508 queueclass = queueclass,
509 slowlocal = slowlocal
514 nop = lambda tun_path, tun_name : (tun_path, tun_name)
516 'none' : dict(alloc=nop,
517 tunopen=tunopen, tunclose=tunclose,
521 'tun' : dict(alloc=functools.partial(tuntap_alloc, "tun"),
522 tunopen=tunopen, tunclose=tunclose,
523 dealloc=tuntap_dealloc,
526 'tap' : dict(alloc=functools.partial(tuntap_alloc, "tap"),
527 tunopen=tunopen, tunclose=tunclose,
528 dealloc=tuntap_dealloc,
531 'pl-tun' : dict(alloc=functools.partial(pl_tuntap_alloc, "tun"),
532 tunopen=tunopen, tunclose=tunclose,
536 'pl-tap' : dict(alloc=functools.partial(pl_tuntap_alloc, "tap"),
537 tunopen=tunopen, tunclose=tunclose,
541 'pl-gre-ip' : dict(alloc=functools.partial(pl_tuntap_namealloc, "tun"),
542 tunopen=noopen, tunclose=tunclose,
546 'pl-gre-eth': dict(alloc=functools.partial(pl_tuntap_namealloc, "tap"),
547 tunopen=noopen, tunclose=noclose,
553 tun_path = options.tun_path
554 tun_name = options.tun_name
556 modeinfo = MODEINFO[options.mode]
558 # Try to load filter module
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:
567 filter_args = dict(map(lambda x:x.split('=',1),options.filter_args.split(',')))
568 filter_module.init(**filter_args)
571 elif options.filter_module.endswith('.so'):
572 filter_module = ctypes.cdll.LoadLibrary(options.filter_module)
573 if options.filter_args:
575 filter_module.init(options.filter_args)
579 accept_packet = filter_module.accept_packet
580 print >>sys.stderr, "Installing packet filter (accept_packet)"
585 queueclass = filter_module.queueclass
586 print >>sys.stderr, "Installing custom queue"
591 _filter_init = filter_module.filter_init
592 filter_run = filter_module.filter_run
593 filter_close = filter_module.filter_close
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
601 print >>sys.stderr, "Installing packet filter (stream filter)"
613 # be careful to roll back stuff on exceptions
614 tun_path, tun_name = modeinfo['alloc'](tun_path, tun_name)
616 modeinfo['start'](tun_path, tun_name)
618 tun = modeinfo['tunopen'](tun_path, tun_name)
620 modeinfo['stop'](tun_path, tun_name)
623 modeinfo['dealloc'](tun_path, tun_name)
627 # Trak SIGTERM, and set global termination flag instead of dying
629 def _finalize(sig,frame):
631 TERMINATE.append(None)
632 signal.signal(signal.SIGTERM, _finalize)
639 if accept_packet or filter_init:
640 raise NotImplementedError, "--pass-fd and --filter are not compatible"
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)
647 print >>sys.stderr, "Sending FD to: %r" % (options.pass_fd,)
649 # send FD to whoever wants it
652 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
656 raise OSError, "Killed"
658 sock.connect(options.pass_fd)
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))
666 sock.connect(options.pass_fd)
667 passfd.sendfd(sock, tun.fileno(), '0')
670 def tun_fwd(tun, remote, **kw):
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,)
681 def tun_fwd(tun, remote, **kw):
686 remote = remaining_args[0]
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)
696 raise OSError, "Killed"
698 rsock.bind((hostaddr,options.udp))
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))
706 rsock.bind((hostaddr,options.udp))
707 rsock.connect((remaining_args[0],options.port))
709 print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
710 raise AssertionError, "Error: need a remote endpoint in UDP mode"
712 # Wait for other peer
715 while not endme and not TERMINATE:
725 keepalive_thread = threading.Thread(target=keepalive)
726 keepalive_thread.start()
730 raise OSError, "Killed"
732 heartbeat = rsock.recv(10)
735 time.sleep(min(30.0,retrydelay))
738 heartbeat = rsock.recv(10)
740 keepalive_thread.join()
742 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
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)
751 raise OSError, "Killed"
753 rsock.connect((remaining_args[0],options.port))
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))
761 rsock.connect((remaining_args[0],options.port))
763 print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
764 lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
768 raise OSError, "Killed"
770 lsock.bind((hostaddr,options.port))
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))
778 lsock.bind((hostaddr,options.port))
780 rsock,raddr = lsock.accept()
781 remote = os.fdopen(rsock.fileno(), 'r+b', 0)
784 filter_local, filter_remote = filter_init()
791 remote = filter_remote
794 filter_close(local, remote)
796 filter_thread = threading.Thread(target=filter_loop)
797 filter_thread.start()
799 print >>sys.stderr, "Connected"
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) )
808 # Try to give us high priority
812 # Ignore errors, we might not have enough privileges,
813 # or perhaps there is no os.nice support in the system
818 reconnect = reconnect,
819 accept_local = accept_packet,
820 accept_remote = accept_packet,
825 # 1. Forward packets from tun to filter
826 # 2. Forward packets from remote to filter
828 # 1. needs TUN rate-limiting, while
829 # 2. needs reconnection
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
836 filter_local_fd = filter_local
837 if isinstance(filter_remote, ctypes.c_int):
838 filter_remote_fd = filter_remote.value
840 filter_remote_fd = filter_remote
843 tun_fwd(tun, filter_local_fd,
844 accept_local = accept_packet,
848 tun_fwd(filter_remote_fd, remote,
849 reconnect = reconnect,
850 accept_remote = accept_packet,
853 localthread = threading.Thread(target=localside)
854 remotethread = threading.Thread(target=remoteside)
862 print >>sys.stderr, "Shutting down..."
864 # In case sys.stderr is broken
867 # tidy shutdown in every case - swallow exceptions
868 TERMINATE.append(None)
878 os.kill(tcpdump.pid, signal.SIGTERM)
884 modeinfo['stop'](tun_path, tun_name)
886 traceback.print_exc()
889 modeinfo['tunclose'](tun_path, tun_name, tun)
891 traceback.print_exc()
894 modeinfo['dealloc'](tun_path, tun_name)
896 traceback.print_exc()
898 print >>sys.stderr, "TERMINATED GRACEFULLY"