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
38 self._pointopoint = None
39 self._netprefix = None
43 self._logger = logging.getLogger('nepi.testbeds.planetlab')
48 return '<%s for %s>' % (self.__class__.__name__, local)
50 return super(TunProtoBase,self).__str__()
56 raise RuntimeError, "Lost reference to peering interfaces before launching"
58 raise RuntimeError, "Unconnected TUN - missing node"
60 # Make sure all the paths are created where
61 # they have to be created for deployment
62 # Also remove pidfile, if there is one.
63 # Old pidfiles from previous runs can be troublesome.
64 cmd = "mkdir -p %(home)s ; rm -f %(home)s/pid %(home)s/*.so" % {
65 'home' : server.shell_escape(self.home_path)
67 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
69 host = local.node.hostname,
71 user = local.node.slicename,
73 ident_key = local.node.ident_path,
74 server_key = local.node.server_key,
80 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
82 def _install_scripts(self):
86 raise RuntimeError, "Lost reference to peering interfaces before launching"
88 raise RuntimeError, "Unconnected TUN - missing node"
90 # Install the tun_connect script and tunalloc utility
91 from nepi.util import tunchannel
92 from nepi.util import ipaddr2
94 os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
95 os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
96 re.sub(r"([.]py)[co]$", r'\1', tunchannel.__file__, 1), # pyc/o files are version-specific
97 re.sub(r"([.]py)[co]$", r'\1', ipaddr2.__file__, 1), # pyc/o files are version-specific
99 if local.filter_module:
100 filter_sources = filter(bool,map(str.strip,local.filter_module.module.split()))
101 filter_module = filter_sources[0]
103 # Translate paths to builtin sources
104 for i,source in enumerate(filter_sources):
105 if not os.path.exists(source):
106 # Um... try the builtin folder
107 source = os.path.join(os.path.dirname(__file__), "scripts", source)
108 if os.path.exists(source):
110 filter_sources[i] = source
112 sources.extend(set(filter_sources))
116 filter_sources = None
117 dest = "%s@%s:%s" % (
118 local.node.slicename, local.node.hostname,
119 os.path.join(self.home_path,'.'),)
120 (out,err),proc = server.eintr_retry(server.popen_scp)(
123 ident_key = local.node.ident_path,
124 server_key = local.node.server_key
128 raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (sources, out,err,)
130 # Make sure all dependencies are satisfied
131 local.node.wait_dependencies()
135 "gcc -fPIC -shared tunalloc.c -o tunalloc.so && "
137 "wget -q -c -O python-iovec-src.tar.gz %(iovec_url)s && "
138 "mkdir -p python-iovec && "
139 "cd python-iovec && "
140 "tar xzf ../python-iovec-src.tar.gz --strip-components=1 && "
141 "python setup.py build && "
142 "python setup.py install --install-lib .. && "
146 "gcc -fPIC -shared %(sources)s -o %(module)s.so " % {
147 'module' : os.path.basename(filter_module).rsplit('.',1)[0],
148 'sources' : ' '.join(map(os.path.basename,filter_sources))
151 if filter_module is not None and filter_module.endswith('.c')
156 "wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
157 "mkdir -p python-passfd && "
158 "cd python-passfd && "
159 "tar xzf ../python-passfd-src.tar.gz --strip-components=1 && "
160 "python setup.py build && "
161 "python setup.py install --install-lib .. "
163 if local.tun_proto == "fd"
168 'home' : server.shell_escape(self.home_path),
169 'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
170 'iovec_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-iovec/archive/tip.tar.gz",
172 (out,err),proc = server.popen_ssh_command(
174 host = local.node.hostname,
176 user = local.node.slicename,
178 ident_key = local.node.ident_path,
179 server_key = local.node.server_key,
184 raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
186 def launch(self, check_proto):
190 if not peer or not local:
191 raise RuntimeError, "Lost reference to peering interfaces before launching"
193 peer_port = peer.tun_port
194 peer_addr = peer.tun_addr
195 peer_proto = peer.tun_proto
196 peer_cipher = peer.tun_cipher
198 local_port = self.port
199 local_cap = local.capture
200 self._address = local_addr = local.address
201 self._netprefix = local_mask = local.netprefix
202 local_snat = local.snat
203 local_txq = local.txqueuelen
204 self._pointopoint = local_p2p = local.pointopoint
205 local_cipher=local.tun_cipher
206 local_mcast= local.multicast
207 local_bwlim= local.bwlimit
208 local_mcastfwd = local.multicast_forwarder
210 if not local_p2p and hasattr(peer, 'address'):
211 self._pointopoint = local_p2p = peer.address
213 if check_proto != peer_proto:
214 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
216 if local_cipher != peer_cipher:
217 raise RuntimeError, "Peering cipher mismatch: %s != %s" % (local_cipher, peer_cipher)
219 if check_proto == 'gre' and local_cipher.lower() != 'plain':
220 raise RuntimeError, "Misconfigured TUN: %s - GRE tunnels do not support encryption. Got %s, you MUST use PLAIN" % (local, local_cipher,)
222 if local.filter_module:
223 if check_proto not in ('udp', 'tcp'):
224 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (local,)
225 filter_module = filter(bool,map(str.strip,local.filter_module.module.split()))
226 filter_module = os.path.join('.',os.path.basename(filter_module[0]))
227 if filter_module.endswith('.c'):
228 filter_module = filter_module.rsplit('.',1)[0] + '.so'
229 filter_args = local.filter_module.args
234 args = ["python", "tun_connect.py",
235 "-m", str(self.mode),
236 "-t", str(check_proto),
237 "-A", str(local_addr),
238 "-M", str(local_mask),
239 "-C", str(local_cipher),
242 if check_proto == 'fd':
243 passfd_arg = str(peer_addr)
244 if passfd_arg.startswith('\x00'):
245 # cannot shell_encode null characters :(
246 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
248 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
250 "--pass-fd", passfd_arg
252 elif check_proto == 'gre':
255 "-K", str(self.key.strip('='))
259 "-a", str(peer_addr),
264 "-P", str(local_port),
265 "-p", str(peer_port),
266 "-a", str(peer_addr),
273 args.extend(("-Z",str(local_p2p)))
275 args.extend(("-Q",str(local_txq)))
278 elif local_cap == 'pcap':
279 args.extend(('-c','pcap'))
281 args.extend(("-b",str(local_bwlim*1024)))
283 args.extend(("--filter", filter_module))
285 args.extend(("--filter-args", filter_args))
286 if local_mcast and local_mcastfwd:
287 args.extend(("--multicast-forwarder", local_mcastfwd))
289 self._logger.info("Starting %s", self)
292 self._install_scripts()
294 # Start process in a "daemonized" way, using nohup and heavy
295 # stdin/out redirection to avoid connection issues
296 (out,err),proc = rspawn.remote_spawn(
300 home = self.home_path,
303 stderr = rspawn.STDOUT,
306 host = local.node.hostname,
308 user = local.node.slicename,
310 ident_key = local.node.ident_path,
311 server_key = local.node.server_key
315 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
320 # Tunnel should be still running in its node
321 # Just check its pidfile and we're done
328 # Wait for the connection to be established
330 for spin in xrange(30):
331 if self.status() != rspawn.RUNNING:
332 self._logger.warn("FAILED TO CONNECT! %s", self)
336 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
337 "cd %(home)s ; grep -a -c Connected capture" % dict(
338 home = server.shell_escape(self.home_path)),
339 host = local.node.hostname,
341 user = local.node.slicename,
343 ident_key = local.node.ident_path,
344 server_key = local.node.server_key,
346 err_on_timeout = False
350 if out.strip() == '1':
353 # At least listening?
354 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
355 "cd %(home)s ; grep -a -c Listening capture" % dict(
356 home = server.shell_escape(self.home_path)),
357 host = local.node.hostname,
359 user = local.node.slicename,
361 ident_key = local.node.ident_path,
362 server_key = local.node.server_key,
364 err_on_timeout = False
368 time.sleep(min(30.0, retrytime))
371 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
372 "cat %(home)s/capture" % dict(
373 home = server.shell_escape(self.home_path)),
374 host = local.node.hostname,
376 user = local.node.slicename,
378 ident_key = local.node.ident_path,
379 server_key = local.node.server_key,
382 err_on_timeout = False
386 raise RuntimeError, "FAILED TO CONNECT %s: %s%s" % (self,out,err)
390 if not self._if_name:
391 # Inspect the trace to check the assigned iface
394 cmd = "cd %(home)s ; grep -a 'Using tun:' capture | head -1" % dict(
395 home = server.shell_escape(self.home_path))
396 for spin in xrange(30):
397 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
399 host = local.node.hostname,
401 user = local.node.slicename,
403 ident_key = local.node.ident_path,
404 server_key = local.node.server_key,
406 err_on_timeout = False
410 self._logger.debug("if_name: failed cmd %s", cmd)
416 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*).*",out)
418 self._if_name = match.group(1)
421 self._logger.debug("if_name: %r does not match expected pattern from cmd %s", out, cmd)
423 self._logger.debug("if_name: empty output from cmd %s", cmd)
426 self._logger.warn("if_name: Could not get interface name")
434 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
435 "ip link show %s >/dev/null 2>&1 && echo ALIVE || echo DEAD" % (name,),
436 host = local.node.hostname,
438 user = local.node.slicename,
440 ident_key = local.node.ident_path,
441 server_key = local.node.server_key,
443 err_on_timeout = False
450 if out.strip() == 'DEAD':
452 elif out.strip() == 'ALIVE':
460 raise RuntimeError, "Lost reference to local interface"
463 # NOTE: wait a bit for the pidfile to be created
464 if self._started and not self._pid or not self._ppid:
465 pidtuple = rspawn.remote_check_pid(
466 os.path.join(self.home_path,'pid'),
467 host = local.node.hostname,
469 user = local.node.slicename,
471 ident_key = local.node.ident_path,
472 server_key = local.node.server_key
476 self._pid, self._ppid = pidtuple
482 raise RuntimeError, "Lost reference to local interface"
485 if not self._started:
486 return rspawn.NOT_STARTED
487 elif not self._pid or not self._ppid:
488 return rspawn.NOT_STARTED
490 status = rspawn.remote_status(
491 self._pid, self._ppid,
492 host = local.node.hostname,
494 user = local.node.slicename,
496 ident_key = local.node.ident_path,
497 server_key = local.node.server_key
501 def kill(self, nowait = True):
505 raise RuntimeError, "Lost reference to local interface"
507 status = self.status()
508 if status == rspawn.RUNNING:
509 self._logger.info("Stopping %s", self)
511 # kill by ppid+pid - SIGTERM first, then try SIGKILL
513 self._pid, self._ppid,
514 host = local.node.hostname,
516 user = local.node.slicename,
518 ident_key = local.node.ident_path,
519 server_key = local.node.server_key,
527 status = self.status()
528 if status != rspawn.RUNNING:
529 self._logger.info("Stopped %s", self)
532 interval = min(30.0, interval * 1.1)
534 self.kill(nowait=False)
538 if not self.if_alive():
539 self._logger.info("Device down %s", self)
542 interval = min(30.0, interval * 1.1)
547 # Forcibly shut down interface
548 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
549 "sudo -S bash -c 'echo %s > /vsys/vif_down.in'" % (self.if_name,),
550 host = local.node.hostname,
552 user = local.node.slicename,
554 ident_key = local.node.ident_path,
555 server_key = local.node.server_key,
557 err_on_timeout = False
562 # TODO!!! need to set the vif down with vsys/vif_down.in ... which
563 # doesn't currently work.
567 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
568 "sudo -S bash -c 'kill -s USR1 %d'" % (self._pid,),
569 host = local.node.hostname,
571 user = local.node.slicename,
573 ident_key = local.node.ident_path,
574 server_key = local.node.server_key,
576 err_on_timeout = False
581 # TODO!!! need to set the vif up with vsys/vif_up.in ... which
582 # doesn't currently work.
586 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
587 "sudo -S bash -c 'kill -s USR2 %d'" % (self._pid,),
588 host = local.node.hostname,
590 user = local.node.slicename,
592 ident_key = local.node.ident_path,
593 server_key = local.node.server_key,
595 err_on_timeout = False
600 # tracename : (remotename, localname)
601 'packets' : ('capture','capture'),
602 'pcap' : ('pcap','capture.pcap'),
605 def remote_trace_path(self, whichtrace, tracemap = None):
606 tracemap = self._TRACEMAP if not tracemap else tracemap
608 if whichtrace not in tracemap:
611 return os.path.join(self.home_path, tracemap[whichtrace][1])
613 def sync_trace(self, local_dir, whichtrace, tracemap = None):
614 tracemap = self._TRACEMAP if not tracemap else tracemap
616 if whichtrace not in tracemap:
624 local_path = os.path.join(local_dir, tracemap[whichtrace][1])
626 # create parent local folders
627 if os.path.dirname(local_path):
628 proc = subprocess.Popen(
629 ["mkdir", "-p", os.path.dirname(local_path)],
630 stdout = open("/dev/null","w"),
631 stdin = open("/dev/null","r"))
634 raise RuntimeError, "Failed to synchronize trace"
637 (out,err),proc = server.popen_scp(
638 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
639 os.path.join(self.home_path, tracemap[whichtrace][0])),
643 ident_key = local.node.ident_path,
644 server_key = local.node.server_key
648 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
658 class TunProtoUDP(TunProtoBase):
659 def __init__(self, local, peer, home_path, key):
660 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
663 super(TunProtoUDP, self).launch('udp')
665 class TunProtoFD(TunProtoBase):
666 def __init__(self, local, peer, home_path, key):
667 super(TunProtoFD, self).__init__(local, peer, home_path, key)
670 super(TunProtoFD, self).launch('fd')
672 class TunProtoGRE(TunProtoBase):
673 def __init__(self, local, peer, home_path, key):
674 super(TunProtoGRE, self).__init__(local, peer, home_path, key)
675 self.mode = 'pl-gre-ip'
678 super(TunProtoGRE, self).launch('gre')
680 class TunProtoTCP(TunProtoBase):
681 def __init__(self, local, peer, home_path, key):
682 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
685 super(TunProtoTCP, self).launch('tcp')
687 class TapProtoUDP(TunProtoUDP):
688 def __init__(self, local, peer, home_path, key):
689 super(TapProtoUDP, self).__init__(local, peer, home_path, key)
692 class TapProtoTCP(TunProtoTCP):
693 def __init__(self, local, peer, home_path, key):
694 super(TapProtoTCP, self).__init__(local, peer, home_path, key)
697 class TapProtoFD(TunProtoFD):
698 def __init__(self, local, peer, home_path, key):
699 super(TapProtoFD, self).__init__(local, peer, home_path, key)
702 class TapProtoGRE(TunProtoGRE):
703 def __init__(self, local, peer, home_path, key):
704 super(TapProtoGRE, self).__init__(local, peer, home_path, key)
705 self.mode = 'pl-gre-eth'