2 # -*- coding: utf-8 -*-
16 from nepi.util import server
18 class TunProtoBase(object):
19 def __init__(self, local, peer, home_path, key):
20 # Weak references, since ifaces do have a reference to the
21 # tunneling protocol implementation - we don't want strong
22 # circular references.
23 self.peer = weakref.ref(peer)
24 self.local = weakref.ref(local)
29 self.cross_slice = False
31 self.home_path = home_path
40 self._logger = logging.getLogger('nepi.testbeds.planetlab')
45 return '<%s for %s>' % (self.__class__.__name__, local)
47 return super(TunProtoBase,self).__str__()
53 raise RuntimeError, "Lost reference to peering interfaces before launching"
55 raise RuntimeError, "Unconnected TUN - missing node"
57 # Make sure all the paths are created where
58 # they have to be created for deployment
59 # Also remove pidfile, if there is one.
60 # Old pidfiles from previous runs can be troublesome.
61 cmd = "mkdir -p %(home)s ; rm -f %(home)s/pid %(home)s/*.so" % {
62 'home' : server.shell_escape(self.home_path)
64 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
66 host = local.node.hostname,
68 user = local.node.slicename,
70 ident_key = local.node.ident_path,
71 server_key = local.node.server_key,
77 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
79 def _install_scripts(self):
83 raise RuntimeError, "Lost reference to peering interfaces before launching"
85 raise RuntimeError, "Unconnected TUN - missing node"
87 # Install the tun_connect script and tunalloc utility
88 from nepi.util import tunchannel
89 from nepi.util import ipaddr2
91 os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
92 os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
93 re.sub(r"([.]py)[co]$", r'\1', tunchannel.__file__, 1), # pyc/o files are version-specific
94 re.sub(r"([.]py)[co]$", r'\1', ipaddr2.__file__, 1), # pyc/o files are version-specific
96 if local.filter_module:
97 filter_sources = filter(bool,map(str.strip,local.filter_module.module.split()))
98 filter_module = filter_sources[0]
100 # Translate paths to builtin sources
101 for i,source in enumerate(filter_sources):
102 if not os.path.exists(source):
103 # Um... try the builtin folder
104 source = os.path.join(os.path.dirname(__file__), "scripts", source)
105 if os.path.exists(source):
107 filter_sources[i] = source
109 sources.extend(set(filter_sources))
113 filter_sources = None
114 dest = "%s@%s:%s" % (
115 local.node.slicename, local.node.hostname,
116 os.path.join(self.home_path,'.'),)
117 (out,err),proc = server.eintr_retry(server.popen_scp)(
120 ident_key = local.node.ident_path,
121 server_key = local.node.server_key
125 raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (sources, out,err,)
127 # Make sure all dependencies are satisfied
128 local.node.wait_dependencies()
132 "gcc -fPIC -shared tunalloc.c -o tunalloc.so && "
134 "wget -q -c -O python-iovec-src.tar.gz %(iovec_url)s && "
135 "mkdir -p python-iovec && "
136 "cd python-iovec && "
137 "tar xzf ../python-iovec-src.tar.gz --strip-components=1 && "
138 "python setup.py build && "
139 "python setup.py install --install-lib .. && "
143 "gcc -fPIC -shared %(sources)s -o %(module)s.so " % {
144 'module' : os.path.basename(filter_module).rsplit('.',1)[0],
145 'sources' : ' '.join(map(os.path.basename,filter_sources))
148 if filter_module is not None and filter_module.endswith('.c')
153 "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
154 "mkdir -p python-passfd && "
155 "cd python-passfd && "
156 "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
157 "python setup.py build && "
158 "python setup.py install --install-lib .. "
160 if local.tun_proto == "fd"
165 'home' : server.shell_escape(self.home_path),
166 'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
167 'iovec_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-iovec/archive/tip.tar.gz",
169 (out,err),proc = server.popen_ssh_command(
171 host = local.node.hostname,
173 user = local.node.slicename,
175 ident_key = local.node.ident_path,
176 server_key = local.node.server_key,
181 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
183 def launch(self, check_proto):
187 if not peer or not local:
188 raise RuntimeError, "Lost reference to peering interfaces before launching"
190 peer_port = peer.tun_port
191 peer_addr = peer.tun_addr
192 peer_proto = peer.tun_proto
193 peer_cipher = peer.tun_cipher
195 local_port = self.port
196 local_cap = local.capture
197 local_addr = local.address
198 local_mask = local.netprefix
199 local_snat = local.snat
200 local_txq = local.txqueuelen
201 local_p2p = local.pointopoint
202 local_cipher=local.tun_cipher
203 local_mcast= local.multicast
204 local_bwlim= local.bwlimit
205 local_mcastfwd = local.multicast_forwarder
207 if not local_p2p and hasattr(peer, 'address'):
208 local_p2p = peer.address
210 if check_proto != peer_proto:
211 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
213 if local_cipher != peer_cipher:
214 raise RuntimeError, "Peering cipher mismatch: %s != %s" % (local_cipher, peer_cipher)
216 if check_proto == 'gre' and local_cipher.lower() != 'plain':
217 raise RuntimeError, "Misconfigured TUN: %s - GRE tunnels do not support encryption. Got %s, you MUST use PLAIN" % (local, local_cipher,)
219 if local.filter_module:
220 if check_proto not in ('udp', 'tcp'):
221 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (local,)
222 filter_module = filter(bool,map(str.strip,local.filter_module.module.split()))
223 filter_module = os.path.join('.',os.path.basename(filter_module[0]))
224 if filter_module.endswith('.c'):
225 filter_module = filter_module.rsplit('.',1)[0] + '.so'
226 filter_args = local.filter_module.args
231 args = ["python", "tun_connect.py",
232 "-m", str(self.mode),
233 "-t", str(check_proto),
234 "-A", str(local_addr),
235 "-M", str(local_mask),
236 "-C", str(local_cipher),
239 if check_proto == 'fd':
240 passfd_arg = str(peer_addr)
241 if passfd_arg.startswith('\x00'):
242 # cannot shell_encode null characters :(
243 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
245 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
247 "--pass-fd", passfd_arg
249 elif check_proto == 'gre':
252 "-K", str(self.key.strip('='))
256 "-a", str(peer_addr),
261 "-P", str(local_port),
262 "-p", str(peer_port),
263 "-a", str(peer_addr),
270 args.extend(("-Z",str(local_p2p)))
272 args.extend(("-Q",str(local_txq)))
275 elif local_cap == 'pcap':
276 args.extend(('-c','pcap'))
278 args.extend(("-b",str(local_bwlim*1024)))
280 args.extend(("--filter", filter_module))
282 args.extend(("--filter-args", filter_args))
283 if local_mcast and local_mcastfwd:
284 args.extend(("--multicast-forwarder", local_mcastfwd))
286 self._logger.info("Starting %s", self)
289 self._install_scripts()
291 # Start process in a "daemonized" way, using nohup and heavy
292 # stdin/out redirection to avoid connection issues
293 (out,err),proc = rspawn.remote_spawn(
297 home = self.home_path,
300 stderr = rspawn.STDOUT,
303 host = local.node.hostname,
305 user = local.node.slicename,
307 ident_key = local.node.ident_path,
308 server_key = local.node.server_key
312 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
317 # Tunnel should be still running in its node
318 # Just check its pidfile and we're done
325 # Wait for the connection to be established
327 for spin in xrange(30):
328 if self.status() != rspawn.RUNNING:
329 self._logger.warn("FAILED TO CONNECT! %s", self)
333 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
334 "cd %(home)s ; grep -a -c Connected capture" % dict(
335 home = server.shell_escape(self.home_path)),
336 host = local.node.hostname,
338 user = local.node.slicename,
340 ident_key = local.node.ident_path,
341 server_key = local.node.server_key,
343 err_on_timeout = False
347 if out.strip() == '1':
350 # At least listening?
351 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
352 "cd %(home)s ; grep -a -c Listening capture" % dict(
353 home = server.shell_escape(self.home_path)),
354 host = local.node.hostname,
356 user = local.node.slicename,
358 ident_key = local.node.ident_path,
359 server_key = local.node.server_key,
361 err_on_timeout = False
365 time.sleep(min(30.0, retrytime))
368 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
369 "cat %(home)s/capture" % dict(
370 home = server.shell_escape(self.home_path)),
371 host = local.node.hostname,
373 user = local.node.slicename,
375 ident_key = local.node.ident_path,
376 server_key = local.node.server_key,
379 err_on_timeout = False
383 raise RuntimeError, "FAILED TO CONNECT %s: %s%s" % (self,out,err)
387 if not self._if_name:
388 # Inspect the trace to check the assigned iface
391 cmd = "cd %(home)s ; grep -a 'Using tun:' capture | head -1" % dict(
392 home = server.shell_escape(self.home_path))
393 for spin in xrange(30):
394 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
396 host = local.node.hostname,
398 user = local.node.slicename,
400 ident_key = local.node.ident_path,
401 server_key = local.node.server_key,
403 err_on_timeout = False
407 self._logger.debug("if_name: failed cmd %s", cmd)
413 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*).*",out)
415 self._if_name = match.group(1)
418 self._logger.debug("if_name: %r does not match expected pattern from cmd %s", out, cmd)
420 self._logger.debug("if_name: empty output from cmd %s", cmd)
423 self._logger.warn("if_name: Could not get interface name")
431 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
432 "ip link show %s >/dev/null 2>&1 && echo ALIVE || echo DEAD" % (name,),
433 host = local.node.hostname,
435 user = local.node.slicename,
437 ident_key = local.node.ident_path,
438 server_key = local.node.server_key,
440 err_on_timeout = False
447 if out.strip() == 'DEAD':
449 elif out.strip() == 'ALIVE':
457 raise RuntimeError, "Lost reference to local interface"
460 # NOTE: wait a bit for the pidfile to be created
461 if self._started and not self._pid or not self._ppid:
462 pidtuple = rspawn.remote_check_pid(
463 os.path.join(self.home_path,'pid'),
464 host = local.node.hostname,
466 user = local.node.slicename,
468 ident_key = local.node.ident_path,
469 server_key = local.node.server_key
473 self._pid, self._ppid = pidtuple
479 raise RuntimeError, "Lost reference to local interface"
482 if not self._started:
483 return rspawn.NOT_STARTED
484 elif not self._pid or not self._ppid:
485 return rspawn.NOT_STARTED
487 status = rspawn.remote_status(
488 self._pid, self._ppid,
489 host = local.node.hostname,
491 user = local.node.slicename,
493 ident_key = local.node.ident_path,
494 server_key = local.node.server_key
498 def kill(self, nowait = True):
502 raise RuntimeError, "Lost reference to local interface"
504 status = self.status()
505 if status == rspawn.RUNNING:
506 self._logger.info("Stopping %s", self)
508 # kill by ppid+pid - SIGTERM first, then try SIGKILL
510 self._pid, self._ppid,
511 host = local.node.hostname,
513 user = local.node.slicename,
515 ident_key = local.node.ident_path,
516 server_key = local.node.server_key,
524 status = self.status()
525 if status != rspawn.RUNNING:
526 self._logger.info("Stopped %s", self)
529 interval = min(30.0, interval * 1.1)
531 self.kill(nowait=False)
535 if not self.if_alive():
536 self._logger.info("Device down %s", self)
539 interval = min(30.0, interval * 1.1)
544 # Forcibly shut down interface
545 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
546 "sudo -S bash -c 'echo %s > /vsys/vif_down.in'" % (self.if_name,),
547 host = local.node.hostname,
549 user = local.node.slicename,
551 ident_key = local.node.ident_path,
552 server_key = local.node.server_key,
554 err_on_timeout = False
558 # tracename : (remotename, localname)
559 'packets' : ('capture','capture'),
560 'pcap' : ('pcap','capture.pcap'),
563 def remote_trace_path(self, whichtrace, tracemap = None):
564 tracemap = self._TRACEMAP if not tracemap else tracemap
566 if whichtrace not in tracemap:
569 return os.path.join(self.home_path, tracemap[whichtrace][1])
571 def sync_trace(self, local_dir, whichtrace, tracemap = None):
572 tracemap = self._TRACEMAP if not tracemap else tracemap
574 if whichtrace not in tracemap:
582 local_path = os.path.join(local_dir, tracemap[whichtrace][1])
584 # create parent local folders
585 if os.path.dirname(local_path):
586 proc = subprocess.Popen(
587 ["mkdir", "-p", os.path.dirname(local_path)],
588 stdout = open("/dev/null","w"),
589 stdin = open("/dev/null","r"))
592 raise RuntimeError, "Failed to synchronize trace"
595 (out,err),proc = server.popen_scp(
596 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
597 os.path.join(self.home_path, tracemap[whichtrace][0])),
601 ident_key = local.node.ident_path,
602 server_key = local.node.server_key
606 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
616 class TunProtoUDP(TunProtoBase):
617 def __init__(self, local, peer, home_path, key):
618 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
621 super(TunProtoUDP, self).launch('udp')
623 class TunProtoFD(TunProtoBase):
624 def __init__(self, local, peer, home_path, key):
625 super(TunProtoFD, self).__init__(local, peer, home_path, key)
628 super(TunProtoFD, self).launch('fd')
630 class TunProtoGRE(TunProtoBase):
631 def __init__(self, local, peer, home_path, key):
632 super(TunProtoGRE, self).__init__(local, peer, home_path, key)
633 self.mode = 'pl-gre-ip'
636 super(TunProtoGRE, self).launch('gre')
638 class TunProtoTCP(TunProtoBase):
639 def __init__(self, local, peer, home_path, key):
640 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
643 super(TunProtoTCP, self).launch('tcp')
645 class TapProtoUDP(TunProtoUDP):
646 def __init__(self, local, peer, home_path, key):
647 super(TapProtoUDP, self).__init__(local, peer, home_path, key)
650 class TapProtoTCP(TunProtoTCP):
651 def __init__(self, local, peer, home_path, key):
652 super(TapProtoTCP, self).__init__(local, peer, home_path, key)
655 class TapProtoFD(TunProtoFD):
656 def __init__(self, local, peer, home_path, key):
657 super(TapProtoFD, self).__init__(local, peer, home_path, key)
660 class TapProtoGRE(TunProtoGRE):
661 def __init__(self, local, peer, home_path, key):
662 super(TapProtoGRE, self).__init__(local, peer, home_path, key)
663 self.mode = 'pl-gre-eth'