2 # -*- coding: utf-8 -*-
14 from nepi.util import server
16 class TunProtoBase(object):
17 def __init__(self, local, peer, home_path, key):
18 # Weak references, since ifaces do have a reference to the
19 # tunneling protocol implementation - we don't want strong
20 # circular references.
21 self.peer = weakref.ref(peer)
22 self.local = weakref.ref(local)
28 self.home_path = home_path
40 raise RuntimeError, "Lost reference to peering interfaces before launching"
42 raise RuntimeError, "Unconnected TUN - missing node"
44 # Make sure all the paths are created where
45 # they have to be created for deployment
46 cmd = "mkdir -p %s" % (server.shell_escape(self.home_path),)
47 (out,err),proc = server.popen_ssh_command(
49 host = local.node.hostname,
51 user = local.node.slicename,
53 ident_key = local.node.ident_path,
54 server_key = local.node.server_key
58 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
61 def _install_scripts(self):
65 raise RuntimeError, "Lost reference to peering interfaces before launching"
67 raise RuntimeError, "Unconnected TUN - missing node"
69 # Install the tun_connect script and tunalloc utility
70 from nepi.util import tunchannel
72 os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
73 os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
74 re.sub(r"([.]py)[co]$", r'\1', tunchannel.__file__, 1), # pyc/o files are version-specific
77 local.node.slicename, local.node.hostname,
78 os.path.join(self.home_path,'.'),)
79 (out,err),proc = server.popen_scp(
82 ident_key = local.node.ident_path,
83 server_key = local.node.server_key
87 raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (sources, out,err,)
89 # Make sure all dependencies are satisfied
90 local.node.wait_dependencies()
93 "cd %(home)s && gcc -fPIC -shared tunalloc.c -o tunalloc.so"
95 "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
96 "mkdir -p python-passfd && "
97 "cd python-passfd && "
98 "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
99 "python setup.py build && "
100 "python setup.py install --install-lib .. "
102 if local.tun_proto == "fd" else ""
105 'home' : server.shell_escape(self.home_path),
106 'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
108 (out,err),proc = server.popen_ssh_command(
110 host = local.node.hostname,
112 user = local.node.slicename,
114 ident_key = local.node.ident_path,
115 server_key = local.node.server_key
119 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
121 def launch(self, check_proto, listen, extra_args=[]):
125 if not peer or not local:
126 raise RuntimeError, "Lost reference to peering interfaces before launching"
128 peer_port = peer.tun_port
129 peer_addr = peer.tun_addr
130 peer_proto= peer.tun_proto
132 local_port = self.port
133 local_cap = local.capture
134 local_addr = local.address
135 local_mask = local.netprefix
136 local_snat = local.snat
137 local_txq = local.txqueuelen
138 local_p2p = local.pointopoint
140 if not local_p2p and hasattr(peer, 'address'):
141 local_p2p = peer.address
143 if check_proto != peer_proto:
144 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
146 if not listen and ((peer_proto != 'fd' and not peer_port) or not peer_addr):
147 raise RuntimeError, "Misconfigured peer: %s" % (peer,)
149 if listen and ((peer_proto != 'fd' and not local_port) or not local_addr or not local_mask):
150 raise RuntimeError, "Misconfigured TUN: %s" % (local,)
152 args = ["python", "tun_connect.py",
153 "-m", str(self.mode),
154 "-A", str(local_addr),
155 "-M", str(local_mask)]
157 if check_proto == 'fd':
158 passfd_arg = str(peer_addr)
159 if passfd_arg.startswith('\x00'):
160 # cannot shell_encode null characters :(
161 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
163 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
165 "--pass-fd", passfd_arg
169 "-p", str(local_port if listen else peer_port),
176 args.extend(("-P",str(local_p2p)))
178 args.extend(("-Q",str(local_txq)))
182 args.extend(map(str,extra_args))
183 if not listen and check_proto != 'fd':
184 args.append(str(peer_addr))
187 self._install_scripts()
189 # Start process in a "daemonized" way, using nohup and heavy
190 # stdin/out redirection to avoid connection issues
191 (out,err),proc = rspawn.remote_spawn(
195 home = self.home_path,
198 stderr = rspawn.STDOUT,
201 host = local.node.hostname,
203 user = local.node.slicename,
205 ident_key = local.node.ident_path,
206 server_key = local.node.server_key
210 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
214 def _launch_and_wait(self, *p, **kw):
217 self.launch(*p, **kw)
219 # Wait for the process to be started
220 while self.status() == rspawn.NOT_STARTED:
223 # Wait for the connection to be established
224 for spin in xrange(30):
225 if self.status() != rspawn.RUNNING:
228 (out,err),proc = server.popen_ssh_command(
229 "cd %(home)s ; grep -c Connected capture" % dict(
230 home = server.shell_escape(self.home_path)),
231 host = local.node.hostname,
233 user = local.node.slicename,
235 ident_key = local.node.ident_path,
236 server_key = local.node.server_key
242 if out.strip() != '0':
249 if not self._if_name:
250 # Inspect the trace to check the assigned iface
253 for spin in xrange(30):
254 (out,err),proc = server.popen_ssh_command(
255 "cd %(home)s ; grep 'Using tun:' capture | head -1" % dict(
256 home = server.shell_escape(self.home_path)),
257 host = local.node.hostname,
259 user = local.node.slicename,
261 ident_key = local.node.ident_path,
262 server_key = local.node.server_key
270 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*) +.*",out)
272 self._if_name = match.group(1)
275 def async_launch(self, check_proto, listen, extra_args=[]):
276 if not self._launcher:
277 self._launcher = threading.Thread(
278 target = self._launch_and_wait,
279 args = (check_proto, listen, extra_args))
280 self._launcher.start()
282 def async_launch_wait(self):
284 self._launcher.join()
285 if not self._started:
286 raise RuntimeError, "Failed to launch TUN forwarder"
287 elif not self._started:
294 raise RuntimeError, "Lost reference to local interface"
297 # NOTE: wait a bit for the pidfile to be created
298 if self._started and not self._pid or not self._ppid:
299 pidtuple = rspawn.remote_check_pid(
300 os.path.join(self.home_path,'pid'),
301 host = local.node.hostname,
303 user = local.node.slicename,
305 ident_key = local.node.ident_path,
306 server_key = local.node.server_key
310 self._pid, self._ppid = pidtuple
316 raise RuntimeError, "Lost reference to local interface"
319 if not self._started:
320 return rspawn.NOT_STARTED
321 elif not self._pid or not self._ppid:
322 return rspawn.NOT_STARTED
324 status = rspawn.remote_status(
325 self._pid, self._ppid,
326 host = local.node.hostname,
328 user = local.node.slicename,
330 ident_key = local.node.ident_path
338 raise RuntimeError, "Lost reference to local interface"
340 status = self.status()
341 if status == rspawn.RUNNING:
342 # kill by ppid+pid - SIGTERM first, then try SIGKILL
344 self._pid, self._ppid,
345 host = local.node.hostname,
347 user = local.node.slicename,
349 ident_key = local.node.ident_path,
350 server_key = local.node.server_key,
354 def sync_trace(self, local_dir, whichtrace):
355 if whichtrace != 'packets':
363 local_path = os.path.join(local_dir, 'capture')
365 # create parent local folders
366 proc = subprocess.Popen(
367 ["mkdir", "-p", os.path.dirname(local_path)],
368 stdout = open("/dev/null","w"),
369 stdin = open("/dev/null","r"))
372 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
375 (out,err),proc = server.popen_scp(
376 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
377 os.path.join(self.home_path, 'capture')),
381 ident_key = local.node.ident_path,
382 server_key = local.node.server_key
386 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
395 eg: set up listening ports
397 raise NotImplementedError
405 raise NotImplementedError
411 raise NotImplementedError
414 class TunProtoUDP(TunProtoBase):
415 def __init__(self, local, peer, home_path, key, listening):
416 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
417 self.listening = listening
423 self.async_launch('udp', False, ("-u",str(self.port)))
428 class TunProtoFD(TunProtoBase):
429 def __init__(self, local, peer, home_path, key, listening):
430 super(TunProtoFD, self).__init__(local, peer, home_path, key)
431 self.listening = listening
437 self.async_launch('fd', False)
442 class TunProtoTCP(TunProtoBase):
443 def __init__(self, local, peer, home_path, key, listening):
444 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
445 self.listening = listening
449 self.async_launch('tcp', True)
452 if not self.listening:
453 # make sure our peer is ready
455 if peer and peer.peer_proto_impl:
456 peer.peer_proto_impl.async_launch_wait()
458 if not self._started:
459 self.launch('tcp', False)
461 # make sure WE are ready
462 self.async_launch_wait()
469 class TapProtoUDP(TunProtoUDP):
470 def __init__(self, local, peer, home_path, key, listening):
471 super(TapProtoUDP, self).__init__(local, peer, home_path, key, listening)
474 class TapProtoTCP(TunProtoTCP):
475 def __init__(self, local, peer, home_path, key, listening):
476 super(TapProtoTCP, self).__init__(local, peer, home_path, key, listening)
479 class TapProtoFD(TunProtoFD):
480 def __init__(self, local, peer, home_path, key, listening):
481 super(TapProtoFD, self).__init__(local, peer, home_path, key, listening)