1 # -*- 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)
28 self.cross_slice = False
30 self.home_path = home_path
39 self._logger = logging.getLogger('nepi.testbeds.planetlab')
44 return '<%s for %s>' % (self.__class__.__name__, local)
46 return super(TunProtoBase,self).__str__()
52 raise RuntimeError, "Lost reference to peering interfaces before launching"
54 raise RuntimeError, "Unconnected TUN - missing node"
56 # Make sure all the paths are created where
57 # they have to be created for deployment
58 # Also remove pidfile, if there is one.
59 # Old pidfiles from previous runs can be troublesome.
60 cmd = "mkdir -p %(home)s ; rm -f %(home)s/pid %(home)s/*.so" % {
61 'home' : server.shell_escape(self.home_path)
63 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
65 host = local.node.hostname,
67 user = local.node.slicename,
69 ident_key = local.node.ident_path,
70 server_key = local.node.server_key,
76 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
78 def _install_scripts(self):
82 raise RuntimeError, "Lost reference to peering interfaces before launching"
84 raise RuntimeError, "Unconnected TUN - missing node"
86 # Install the tun_connect script and tunalloc utility
87 from nepi.util import tunchannel
88 from nepi.util import ipaddr2
90 os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
91 os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
92 re.sub(r"([.]py)[co]$", r'\1', tunchannel.__file__, 1), # pyc/o files are version-specific
93 re.sub(r"([.]py)[co]$", r'\1', ipaddr2.__file__, 1), # pyc/o files are version-specific
95 if local.filter_module:
96 filter_sources = filter(bool,map(str.strip,local.filter_module.module.split()))
97 filter_module = filter_sources[0]
99 # Translate paths to builtin sources
100 for i,source in enumerate(filter_sources):
101 if not os.path.exists(source):
102 # Um... try the builtin folder
103 source = os.path.join(os.path.dirname(__file__), "scripts", source)
104 if os.path.exists(source):
106 filter_sources[i] = source
108 sources.extend(set(filter_sources))
112 filter_sources = None
113 dest = "%s@%s:%s" % (
114 local.node.slicename, local.node.hostname,
115 os.path.join(self.home_path,'.'),)
116 (out,err),proc = server.eintr_retry(server.popen_scp)(
119 ident_key = local.node.ident_path,
120 server_key = local.node.server_key
124 raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (sources, out,err,)
126 # Make sure all dependencies are satisfied
127 local.node.wait_dependencies()
131 "gcc -fPIC -shared tunalloc.c -o tunalloc.so && "
133 "wget -q -c -O python-iovec-src.tar.gz %(iovec_url)s && "
134 "mkdir -p python-iovec && "
135 "cd python-iovec && "
136 "tar xzf ../python-iovec-src.tar.gz --strip-components=1 && "
137 "python setup.py build && "
138 "python setup.py install --install-lib .. && "
142 "gcc -fPIC -shared %(sources)s -o %(module)s.so " % {
143 'module' : os.path.basename(filter_module).rsplit('.',1)[0],
144 'sources' : ' '.join(map(os.path.basename,filter_sources))
147 if filter_module is not None and filter_module.endswith('.c')
152 "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
153 "mkdir -p python-passfd && "
154 "cd python-passfd && "
155 "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
156 "python setup.py build && "
157 "python setup.py install --install-lib .. "
159 if local.tun_proto == "fd"
164 'home' : server.shell_escape(self.home_path),
165 'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
166 'iovec_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-iovec/archive/tip.tar.gz",
168 (out,err),proc = server.popen_ssh_command(
170 host = local.node.hostname,
172 user = local.node.slicename,
174 ident_key = local.node.ident_path,
175 server_key = local.node.server_key,
180 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
182 def launch(self, check_proto):
186 if not peer or not local:
187 raise RuntimeError, "Lost reference to peering interfaces before launching"
189 peer_port = peer.tun_port
190 peer_addr = peer.tun_addr
191 peer_proto = peer.tun_proto
192 peer_cipher = peer.tun_cipher
194 local_port = self.port
195 local_cap = local.capture
196 local_addr = local.address
197 local_mask = local.netprefix
198 local_snat = local.snat
199 local_txq = local.txqueuelen
200 local_p2p = local.pointopoint
201 local_cipher=local.tun_cipher
202 local_mcast= local.multicast
203 local_bwlim= local.bwlimit
204 local_mcastfwd = local.multicast_forwarder
206 if not local_p2p and hasattr(peer, 'address'):
207 local_p2p = peer.address
209 if check_proto != peer_proto:
210 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
212 if local_cipher != peer_cipher:
213 raise RuntimeError, "Peering cipher mismatch: %s != %s" % (local_cipher, peer_cipher)
215 if check_proto == 'gre' and local_cipher.lower() != 'plain':
216 raise RuntimeError, "Misconfigured TUN: %s - GRE tunnels do not support encryption. Got %s, you MUST use PLAIN" % (local, local_cipher,)
218 if local.filter_module:
219 if check_proto not in ('udp', 'tcp'):
220 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (local,)
221 filter_module = filter(bool,map(str.strip,local.filter_module.module.split()))
222 filter_module = os.path.join('.',os.path.basename(filter_module[0]))
223 if filter_module.endswith('.c'):
224 filter_module = filter_module.rsplit('.',1)[0] + '.so'
225 filter_args = local.filter_module.args
230 args = ["python", "tun_connect.py",
231 "-m", str(self.mode),
232 "-t", str(check_proto),
233 "-A", str(local_addr),
234 "-M", str(local_mask),
235 "-C", str(local_cipher),
238 if check_proto == 'fd':
239 passfd_arg = str(peer_addr)
240 if passfd_arg.startswith('\x00'):
241 # cannot shell_encode null characters :(
242 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
244 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
246 "--pass-fd", passfd_arg
248 elif check_proto == 'gre':
251 "-K", str(self.key.strip('='))
255 "-a", str(peer_addr),
260 "-P", str(local_port),
261 "-p", str(peer_port),
262 "-a", str(peer_addr),
269 args.extend(("-Z",str(local_p2p)))
271 args.extend(("-Q",str(local_txq)))
274 elif local_cap == 'pcap':
275 args.extend(('-c','pcap'))
277 args.extend(("-b",str(local_bwlim*1024)))
279 args.extend(("--filter", filter_module))
281 args.extend(("--filter-args", filter_args))
282 if local_mcast and local_mcastfwd:
283 args.extend(("--multicast-forwarder", local_mcastfwd))
285 self._logger.info("Starting %s", self)
288 self._install_scripts()
290 # Start process in a "daemonized" way, using nohup and heavy
291 # stdin/out redirection to avoid connection issues
292 (out,err),proc = rspawn.remote_spawn(
296 home = self.home_path,
299 stderr = rspawn.STDOUT,
302 host = local.node.hostname,
304 user = local.node.slicename,
306 ident_key = local.node.ident_path,
307 server_key = local.node.server_key
311 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
316 # Tunnel should be still running in its node
317 # Just check its pidfile and we're done
324 # Wait for the connection to be established
326 for spin in xrange(30):
327 if self.status() != rspawn.RUNNING:
328 self._logger.warn("FAILED TO CONNECT! %s", self)
332 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
333 "cd %(home)s ; grep -a -c Connected capture" % dict(
334 home = server.shell_escape(self.home_path)),
335 host = local.node.hostname,
337 user = local.node.slicename,
339 ident_key = local.node.ident_path,
340 server_key = local.node.server_key,
342 err_on_timeout = False
346 if out.strip() == '1':
349 # At least listening?
350 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
351 "cd %(home)s ; grep -a -c Listening capture" % dict(
352 home = server.shell_escape(self.home_path)),
353 host = local.node.hostname,
355 user = local.node.slicename,
357 ident_key = local.node.ident_path,
358 server_key = local.node.server_key,
360 err_on_timeout = False
364 time.sleep(min(30.0, retrytime))
367 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
368 "cat %(home)s/capture" % dict(
369 home = server.shell_escape(self.home_path)),
370 host = local.node.hostname,
372 user = local.node.slicename,
374 ident_key = local.node.ident_path,
375 server_key = local.node.server_key,
378 err_on_timeout = False
382 raise RuntimeError, "FAILED TO CONNECT %s: %s%s" % (self,out,err)
386 if not self._if_name:
387 # Inspect the trace to check the assigned iface
390 cmd = "cd %(home)s ; grep -a 'Using tun:' capture | head -1" % dict(
391 home = server.shell_escape(self.home_path))
392 for spin in xrange(30):
393 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
395 host = local.node.hostname,
397 user = local.node.slicename,
399 ident_key = local.node.ident_path,
400 server_key = local.node.server_key,
402 err_on_timeout = False
406 self._logger.debug("if_name: failed cmd %s", cmd)
412 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*).*",out)
414 self._if_name = match.group(1)
417 self._logger.debug("if_name: %r does not match expected pattern from cmd %s", out, cmd)
419 self._logger.debug("if_name: empty output from cmd %s", cmd)
422 self._logger.warn("if_name: Could not get interface name")
430 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
431 "ip link show %s >/dev/null 2>&1 && echo ALIVE || echo DEAD" % (name,),
432 host = local.node.hostname,
434 user = local.node.slicename,
436 ident_key = local.node.ident_path,
437 server_key = local.node.server_key,
439 err_on_timeout = False
446 if out.strip() == 'DEAD':
448 elif out.strip() == 'ALIVE':
456 raise RuntimeError, "Lost reference to local interface"
459 # NOTE: wait a bit for the pidfile to be created
460 if self._started and not self._pid or not self._ppid:
461 pidtuple = rspawn.remote_check_pid(
462 os.path.join(self.home_path,'pid'),
463 host = local.node.hostname,
465 user = local.node.slicename,
467 ident_key = local.node.ident_path,
468 server_key = local.node.server_key
472 self._pid, self._ppid = pidtuple
478 raise RuntimeError, "Lost reference to local interface"
481 if not self._started:
482 return rspawn.NOT_STARTED
483 elif not self._pid or not self._ppid:
484 return rspawn.NOT_STARTED
486 status = rspawn.remote_status(
487 self._pid, self._ppid,
488 host = local.node.hostname,
490 user = local.node.slicename,
492 ident_key = local.node.ident_path,
493 server_key = local.node.server_key
497 def kill(self, nowait = True):
501 raise RuntimeError, "Lost reference to local interface"
503 status = self.status()
504 if status == rspawn.RUNNING:
505 self._logger.info("Stopping %s", self)
507 # kill by ppid+pid - SIGTERM first, then try SIGKILL
509 self._pid, self._ppid,
510 host = local.node.hostname,
512 user = local.node.slicename,
514 ident_key = local.node.ident_path,
515 server_key = local.node.server_key,
523 status = self.status()
524 if status != rspawn.RUNNING:
525 self._logger.info("Stopped %s", self)
528 interval = min(30.0, interval * 1.1)
530 self.kill(nowait=False)
534 if not self.if_alive():
535 self._logger.info("Device down %s", self)
538 interval = min(30.0, interval * 1.1)
543 # Forcibly shut down interface
544 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
545 "sudo -S bash -c 'echo %s > /vsys/vif_down.in'" % (self.if_name,),
546 host = local.node.hostname,
548 user = local.node.slicename,
550 ident_key = local.node.ident_path,
551 server_key = local.node.server_key,
553 err_on_timeout = False
557 # tracename : (remotename, localname)
558 'packets' : ('capture','capture'),
559 'pcap' : ('pcap','capture.pcap'),
562 def remote_trace_path(self, whichtrace, tracemap = None):
563 tracemap = self._TRACEMAP if not tracemap else tracemap
565 if whichtrace not in tracemap:
568 return os.path.join(self.home_path, tracemap[whichtrace][1])
570 def sync_trace(self, local_dir, whichtrace, tracemap = None):
571 tracemap = self._TRACEMAP if not tracemap else tracemap
573 if whichtrace not in tracemap:
581 local_path = os.path.join(local_dir, tracemap[whichtrace][1])
583 # create parent local folders
584 if os.path.dirname(local_path):
585 proc = subprocess.Popen(
586 ["mkdir", "-p", os.path.dirname(local_path)],
587 stdout = open("/dev/null","w"),
588 stdin = open("/dev/null","r"))
591 raise RuntimeError, "Failed to synchronize trace"
594 (out,err),proc = server.popen_scp(
595 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
596 os.path.join(self.home_path, tracemap[whichtrace][0])),
600 ident_key = local.node.ident_path,
601 server_key = local.node.server_key
605 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
615 class TunProtoUDP(TunProtoBase):
616 def __init__(self, local, peer, home_path, key):
617 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
620 super(TunProtoUDP, self).launch('udp')
622 class TunProtoFD(TunProtoBase):
623 def __init__(self, local, peer, home_path, key):
624 super(TunProtoFD, self).__init__(local, peer, home_path, key)
627 super(TunProtoFD, self).launch('fd')
629 class TunProtoGRE(TunProtoBase):
630 def __init__(self, local, peer, home_path, key):
631 super(TunProtoGRE, self).__init__(local, peer, home_path, key)
632 self.mode = 'pl-gre-ip'
635 super(TunProtoGRE, self).launch('gre')
637 class TunProtoTCP(TunProtoBase):
638 def __init__(self, local, peer, home_path, key):
639 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
642 super(TunProtoTCP, self).launch('tcp')
644 class TapProtoUDP(TunProtoUDP):
645 def __init__(self, local, peer, home_path, key):
646 super(TapProtoUDP, self).__init__(local, peer, home_path, key)
649 class TapProtoTCP(TunProtoTCP):
650 def __init__(self, local, peer, home_path, key):
651 super(TapProtoTCP, self).__init__(local, peer, home_path, key)
654 class TapProtoFD(TunProtoFD):
655 def __init__(self, local, peer, home_path, key):
656 super(TapProtoFD, self).__init__(local, peer, home_path, key)
659 class TapProtoGRE(TunProtoGRE):
660 def __init__(self, local, peer, home_path, key):
661 super(TapProtoGRE, self).__init__(local, peer, home_path, key)
662 self.mode = 'pl-gre-eth'