2 # -*- coding: utf-8 -*-
15 from nepi.util import server
17 class TunProtoBase(object):
18 def __init__(self, local, peer, home_path, key):
19 # Weak references, since ifaces do have a reference to the
20 # tunneling protocol implementation - we don't want strong
21 # circular references.
22 self.peer = weakref.ref(peer)
23 self.local = weakref.ref(local)
29 self.home_path = home_path
33 self._starting = False
41 return '<%s for %s>' % (self.__class__.__name__, local)
43 return super(TunProtoBase,self).__str__()
49 raise RuntimeError, "Lost reference to peering interfaces before launching"
51 raise RuntimeError, "Unconnected TUN - missing node"
53 # Make sure all the paths are created where
54 # they have to be created for deployment
55 # Also remove pidfile, if there is one.
56 # Old pidfiles from previous runs can be troublesome.
57 cmd = "mkdir -p %(home)s ; rm -f %(home)s/pid" % {
58 'home' : server.shell_escape(self.home_path)
60 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
62 host = local.node.hostname,
64 user = local.node.slicename,
66 ident_key = local.node.ident_path,
67 server_key = local.node.server_key
71 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
74 def _install_scripts(self):
78 raise RuntimeError, "Lost reference to peering interfaces before launching"
80 raise RuntimeError, "Unconnected TUN - missing node"
82 # Install the tun_connect script and tunalloc utility
83 from nepi.util import tunchannel
85 os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
86 os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
87 re.sub(r"([.]py)[co]$", r'\1', tunchannel.__file__, 1), # pyc/o files are version-specific
90 local.node.slicename, local.node.hostname,
91 os.path.join(self.home_path,'.'),)
92 (out,err),proc = server.eintr_retry(server.popen_scp)(
95 ident_key = local.node.ident_path,
96 server_key = local.node.server_key
100 raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (sources, out,err,)
102 # Make sure all dependencies are satisfied
103 local.node.wait_dependencies()
106 "cd %(home)s && gcc -fPIC -shared tunalloc.c -o tunalloc.so"
108 "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
109 "mkdir -p python-passfd && "
110 "cd python-passfd && "
111 "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
112 "python setup.py build && "
113 "python setup.py install --install-lib .. "
115 if local.tun_proto == "fd" else ""
118 'home' : server.shell_escape(self.home_path),
119 'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
121 (out,err),proc = server.popen_ssh_command(
123 host = local.node.hostname,
125 user = local.node.slicename,
127 ident_key = local.node.ident_path,
128 server_key = local.node.server_key
132 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
134 def launch(self, check_proto, listen, extra_args=[]):
136 raise AssertionError, "Double start"
138 self._starting = True
143 if not peer or not local:
144 raise RuntimeError, "Lost reference to peering interfaces before launching"
146 peer_port = peer.tun_port
147 peer_addr = peer.tun_addr
148 peer_proto= peer.tun_proto
150 local_port = self.port
151 local_cap = local.capture
152 local_addr = local.address
153 local_mask = local.netprefix
154 local_snat = local.snat
155 local_txq = local.txqueuelen
156 local_p2p = local.pointopoint
158 if not local_p2p and hasattr(peer, 'address'):
159 local_p2p = peer.address
161 if check_proto != peer_proto:
162 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
164 if not listen and ((peer_proto != 'fd' and not peer_port) or not peer_addr):
165 raise RuntimeError, "Misconfigured peer: %s" % (peer,)
167 if listen and ((peer_proto != 'fd' and not local_port) or not local_addr or not local_mask):
168 raise RuntimeError, "Misconfigured TUN: %s" % (local,)
170 args = ["python", "tun_connect.py",
171 "-m", str(self.mode),
172 "-A", str(local_addr),
173 "-M", str(local_mask)]
175 if check_proto == 'fd':
176 passfd_arg = str(peer_addr)
177 if passfd_arg.startswith('\x00'):
178 # cannot shell_encode null characters :(
179 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
181 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
183 "--pass-fd", passfd_arg
187 "-p", str(local_port if listen else peer_port),
194 args.extend(("-P",str(local_p2p)))
196 args.extend(("-Q",str(local_txq)))
200 args.extend(map(str,extra_args))
201 if not listen and check_proto != 'fd':
202 args.append(str(peer_addr))
204 print >>sys.stderr, "Starting", self
207 self._install_scripts()
209 # Start process in a "daemonized" way, using nohup and heavy
210 # stdin/out redirection to avoid connection issues
211 (out,err),proc = rspawn.remote_spawn(
215 home = self.home_path,
218 stderr = rspawn.STDOUT,
221 host = local.node.hostname,
223 user = local.node.slicename,
225 ident_key = local.node.ident_path,
226 server_key = local.node.server_key
230 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
234 def _launch_and_wait(self, *p, **kw):
236 self.__launch_and_wait(*p, **kw)
240 self._launcher._exc.append(sys.exc_info())
244 def __launch_and_wait(self, *p, **kw):
247 self.launch(*p, **kw)
249 # Wait for the process to be started
250 while self.status() == rspawn.NOT_STARTED:
253 # Wait for the connection to be established
254 for spin in xrange(30):
255 if self.status() != rspawn.RUNNING:
256 print >>sys.stderr, "FAILED TO CONNECT! ", self
259 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
260 "cd %(home)s ; grep -c Connected capture" % dict(
261 home = server.shell_escape(self.home_path)),
262 host = local.node.hostname,
264 user = local.node.slicename,
266 ident_key = local.node.ident_path,
267 server_key = local.node.server_key
273 if out.strip() != '0':
278 print >>sys.stderr, "FAILED TO CONNECT! ", self
282 if not self._if_name:
283 # Inspect the trace to check the assigned iface
286 for spin in xrange(30):
287 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
288 "cd %(home)s ; grep 'Using tun:' capture | head -1" % dict(
289 home = server.shell_escape(self.home_path)),
290 host = local.node.hostname,
292 user = local.node.slicename,
294 ident_key = local.node.ident_path,
295 server_key = local.node.server_key
303 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*) +.*",out)
305 self._if_name = match.group(1)
308 def async_launch(self, check_proto, listen, extra_args=[]):
309 if not self._launcher:
310 self._launcher = threading.Thread(
311 target = self._launch_and_wait,
312 args = (check_proto, listen, extra_args))
313 self._launcher._exc = []
314 self._launcher.start()
316 def async_launch_wait(self):
318 self._launcher.join()
319 if not self._started:
320 if self._launcher._exc:
321 exctyp,exval,exctrace = self._launcher._exc[0]
322 raise exctyp,exval,exctrace
324 raise RuntimeError, "Failed to launch TUN forwarder"
325 elif not self._started:
326 print >>sys.stderr, "SYNC",
333 raise RuntimeError, "Lost reference to local interface"
336 # NOTE: wait a bit for the pidfile to be created
337 if self._started and not self._pid or not self._ppid:
338 pidtuple = rspawn.remote_check_pid(
339 os.path.join(self.home_path,'pid'),
340 host = local.node.hostname,
342 user = local.node.slicename,
344 ident_key = local.node.ident_path,
345 server_key = local.node.server_key
349 self._pid, self._ppid = pidtuple
355 raise RuntimeError, "Lost reference to local interface"
358 if not self._started:
359 return rspawn.NOT_STARTED
360 elif not self._pid or not self._ppid:
361 return rspawn.NOT_STARTED
363 status = rspawn.remote_status(
364 self._pid, self._ppid,
365 host = local.node.hostname,
367 user = local.node.slicename,
369 ident_key = local.node.ident_path,
370 server_key = local.node.server_key
378 raise RuntimeError, "Lost reference to local interface"
380 status = self.status()
381 if status == rspawn.RUNNING:
382 print >>sys.stderr, "Stopping", self
384 # kill by ppid+pid - SIGTERM first, then try SIGKILL
386 self._pid, self._ppid,
387 host = local.node.hostname,
389 user = local.node.slicename,
391 ident_key = local.node.ident_path,
392 server_key = local.node.server_key,
400 status = self.status()
401 if status != rspawn.RUNNING:
402 print >>sys.stderr, "Stopped", self
405 interval = min(30.0, interval * 1.1)
407 def remote_trace_path(self, whichtrace):
408 if whichtrace != 'packets':
411 return os.path.join(self.home_path, 'capture')
413 def sync_trace(self, local_dir, whichtrace):
414 if whichtrace != 'packets':
422 local_path = os.path.join(local_dir, 'capture')
424 # create parent local folders
425 if os.path.dirname(local_path):
426 proc = subprocess.Popen(
427 ["mkdir", "-p", os.path.dirname(local_path)],
428 stdout = open("/dev/null","w"),
429 stdin = open("/dev/null","r"))
432 raise RuntimeError, "Failed to synchronize trace"
435 (out,err),proc = server.popen_scp(
436 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
437 os.path.join(self.home_path, 'capture')),
441 ident_key = local.node.ident_path,
442 server_key = local.node.server_key
446 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
455 eg: set up listening ports
457 raise NotImplementedError
465 raise NotImplementedError
471 raise NotImplementedError
480 class TunProtoUDP(TunProtoBase):
481 def __init__(self, local, peer, home_path, key, listening):
482 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
483 self.listening = listening
489 self.async_launch('udp', False, ("-u",str(self.port)))
497 def launch(self, check_proto='udp', listen=False, extra_args=None):
498 if extra_args is None:
499 extra_args = ("-u",str(self.port))
500 super(TunProtoUDP, self).launch(check_proto, listen, extra_args)
502 class TunProtoFD(TunProtoBase):
503 def __init__(self, local, peer, home_path, key, listening):
504 super(TunProtoFD, self).__init__(local, peer, home_path, key)
505 self.listening = listening
511 self.async_launch('fd', False)
519 def launch(self, check_proto='fd', listen=False, extra_args=[]):
520 super(TunProtoFD, self).launch(check_proto, listen, extra_args)
522 class TunProtoTCP(TunProtoBase):
523 def __init__(self, local, peer, home_path, key, listening):
524 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
525 self.listening = listening
529 self.async_launch('tcp', True)
532 if not self.listening:
533 # make sure our peer is ready
535 if peer and peer.peer_proto_impl:
536 peer.peer_proto_impl.async_launch_wait()
538 if not self._started:
539 self.async_launch('tcp', False)
549 def launch(self, check_proto='tcp', listen=None, extra_args=[]):
551 listen = self.listening
552 super(TunProtoTCP, self).launch(check_proto, listen, extra_args)
554 class TapProtoUDP(TunProtoUDP):
555 def __init__(self, local, peer, home_path, key, listening):
556 super(TapProtoUDP, self).__init__(local, peer, home_path, key, listening)
559 class TapProtoTCP(TunProtoTCP):
560 def __init__(self, local, peer, home_path, key, listening):
561 super(TapProtoTCP, self).__init__(local, peer, home_path, key, listening)
564 class TapProtoFD(TunProtoFD):
565 def __init__(self, local, peer, home_path, key, listening):
566 super(TapProtoFD, self).__init__(local, peer, home_path, key, listening)