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)
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
205 if not local_p2p and hasattr(peer, 'address'):
206 local_p2p = peer.address
208 if check_proto != peer_proto:
209 raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
211 if local_cipher != peer_cipher:
212 raise RuntimeError, "Peering cipher mismatch: %s != %s" % (local_cipher, peer_cipher)
214 if check_proto == 'gre' and local_cipher.lower() != 'plain':
215 raise RuntimeError, "Misconfigured TUN: %s - GRE tunnels do not support encryption. Got %s, you MUST use PLAIN" % (local, local_cipher,)
217 if local.filter_module:
218 if check_proto not in ('udp', 'tcp'):
219 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (local,)
220 filter_module = filter(bool,map(str.strip,local.filter_module.module.split()))
221 filter_module = os.path.join('.',os.path.basename(filter_module[0]))
222 if filter_module.endswith('.c'):
223 filter_module = filter_module.rsplit('.',1)[0] + '.so'
224 filter_args = local.filter_module.args
229 args = ["python", "tun_connect.py",
230 "-m", str(self.mode),
231 "-t", str(check_proto),
232 "-A", str(local_addr),
233 "-M", str(local_mask),
234 "-C", str(local_cipher)]
236 if check_proto == 'fd':
237 passfd_arg = str(peer_addr)
238 if passfd_arg.startswith('\x00'):
239 # cannot shell_encode null characters :(
240 passfd_arg = "base64:"+base64.b64encode(passfd_arg)
242 passfd_arg = '$HOME/'+server.shell_escape(passfd_arg)
244 "--pass-fd", passfd_arg
246 elif check_proto == 'gre':
248 "-K", str(min(local_port, peer_port)),
249 "-a", str(peer_addr),
254 "-P", str(local_port),
255 "-p", str(peer_port),
256 "-a", str(peer_addr),
263 args.extend(("-Z",str(local_p2p)))
265 args.extend(("-Q",str(local_txq)))
268 elif local_cap == 'pcap':
269 args.extend(('-c','pcap'))
271 args.append("--multicast")
273 args.extend(("-b",str(local_bwlim*1024)))
275 args.extend(("--filter", filter_module))
277 args.extend(("--filter-args", filter_args))
279 self._logger.info("Starting %s", self)
282 self._install_scripts()
284 # Start process in a "daemonized" way, using nohup and heavy
285 # stdin/out redirection to avoid connection issues
286 (out,err),proc = rspawn.remote_spawn(
290 home = self.home_path,
293 stderr = rspawn.STDOUT,
296 host = local.node.hostname,
298 user = local.node.slicename,
300 ident_key = local.node.ident_path,
301 server_key = local.node.server_key
305 raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
310 # Tunnel should be still running in its node
311 # Just check its pidfile and we're done
318 # Wait for the connection to be established
320 for spin in xrange(30):
321 if self.status() != rspawn.RUNNING:
322 self._logger.warn("FAILED TO CONNECT! %s", self)
326 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
327 "cd %(home)s ; grep -c Connected capture" % dict(
328 home = server.shell_escape(self.home_path)),
329 host = local.node.hostname,
331 user = local.node.slicename,
333 ident_key = local.node.ident_path,
334 server_key = local.node.server_key,
336 err_on_timeout = False
340 if out.strip() == '1':
343 # At least listening?
344 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
345 "cd %(home)s ; grep -c Listening capture" % dict(
346 home = server.shell_escape(self.home_path)),
347 host = local.node.hostname,
349 user = local.node.slicename,
351 ident_key = local.node.ident_path,
352 server_key = local.node.server_key,
354 err_on_timeout = False
358 time.sleep(min(30.0, retrytime))
361 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
362 "cat %(home)s/capture" % dict(
363 home = server.shell_escape(self.home_path)),
364 host = local.node.hostname,
366 user = local.node.slicename,
368 ident_key = local.node.ident_path,
369 server_key = local.node.server_key,
372 err_on_timeout = False
376 raise RuntimeError, "FAILED TO CONNECT %s: %s%s" % (self,out,err)
380 if not self._if_name:
381 # Inspect the trace to check the assigned iface
384 cmd = "cd %(home)s ; grep 'Using tun:' capture | head -1" % dict(
385 home = server.shell_escape(self.home_path))
386 for spin in xrange(30):
387 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
389 host = local.node.hostname,
391 user = local.node.slicename,
393 ident_key = local.node.ident_path,
394 server_key = local.node.server_key,
396 err_on_timeout = False
400 self._logger.debug("if_name: failed cmd %s", cmd)
406 match = re.match(r"Using +tun: +([-a-zA-Z0-9]*).*",out)
408 self._if_name = match.group(1)
411 self._logger.debug("if_name: %r does not match expected pattern from cmd %s", out, cmd)
413 self._logger.debug("if_name: empty output from cmd %s", cmd)
416 self._logger.warn("if_name: Could not get interface name")
424 (out,err),proc = server.eintr_retry(server.popen_ssh_command)(
425 "ip show %s >/dev/null 2>&1 && echo ALIVE || echo DEAD" % (name,),
426 host = local.node.hostname,
428 user = local.node.slicename,
430 ident_key = local.node.ident_path,
431 server_key = local.node.server_key,
433 err_on_timeout = False
440 if out.strip() == 'DEAD':
442 elif out.strip() == 'ALIVE':
450 raise RuntimeError, "Lost reference to local interface"
453 # NOTE: wait a bit for the pidfile to be created
454 if self._started and not self._pid or not self._ppid:
455 pidtuple = rspawn.remote_check_pid(
456 os.path.join(self.home_path,'pid'),
457 host = local.node.hostname,
459 user = local.node.slicename,
461 ident_key = local.node.ident_path,
462 server_key = local.node.server_key
466 self._pid, self._ppid = pidtuple
472 raise RuntimeError, "Lost reference to local interface"
475 if not self._started:
476 return rspawn.NOT_STARTED
477 elif not self._pid or not self._ppid:
478 return rspawn.NOT_STARTED
480 status = rspawn.remote_status(
481 self._pid, self._ppid,
482 host = local.node.hostname,
484 user = local.node.slicename,
486 ident_key = local.node.ident_path,
487 server_key = local.node.server_key
491 def kill(self, nowait = True):
495 raise RuntimeError, "Lost reference to local interface"
497 status = self.status()
498 if status == rspawn.RUNNING:
499 self._logger.info("Stopping %s", self)
501 # kill by ppid+pid - SIGTERM first, then try SIGKILL
503 self._pid, self._ppid,
504 host = local.node.hostname,
506 user = local.node.slicename,
508 ident_key = local.node.ident_path,
509 server_key = local.node.server_key,
517 status = self.status()
518 if status != rspawn.RUNNING:
519 self._logger.info("Stopped %s", self)
522 interval = min(30.0, interval * 1.1)
524 self.kill(nowait=False)
528 if not self.if_alive():
529 self._logger.info("Device down %s", self)
532 interval = min(30.0, interval * 1.1)
535 # tracename : (remotename, localname)
536 'packets' : ('capture','capture'),
537 'pcap' : ('pcap','capture.pcap'),
540 def remote_trace_path(self, whichtrace, tracemap = None):
541 tracemap = self._TRACEMAP if not tracemap else tracemap
543 if whichtrace not in tracemap:
546 return os.path.join(self.home_path, tracemap[whichtrace][1])
548 def sync_trace(self, local_dir, whichtrace, tracemap = None):
549 tracemap = self._TRACEMAP if not tracemap else tracemap
551 if whichtrace not in tracemap:
559 local_path = os.path.join(local_dir, tracemap[whichtrace][1])
561 # create parent local folders
562 if os.path.dirname(local_path):
563 proc = subprocess.Popen(
564 ["mkdir", "-p", os.path.dirname(local_path)],
565 stdout = open("/dev/null","w"),
566 stdin = open("/dev/null","r"))
569 raise RuntimeError, "Failed to synchronize trace"
572 (out,err),proc = server.popen_scp(
573 '%s@%s:%s' % (local.node.slicename, local.node.hostname,
574 os.path.join(self.home_path, tracemap[whichtrace][0])),
578 ident_key = local.node.ident_path,
579 server_key = local.node.server_key
583 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
593 class TunProtoUDP(TunProtoBase):
594 def __init__(self, local, peer, home_path, key):
595 super(TunProtoUDP, self).__init__(local, peer, home_path, key)
598 super(TunProtoUDP, self).launch('udp')
600 class TunProtoFD(TunProtoBase):
601 def __init__(self, local, peer, home_path, key):
602 super(TunProtoFD, self).__init__(local, peer, home_path, key)
605 super(TunProtoFD, self).launch('fd')
607 class TunProtoGRE(TunProtoBase):
608 def __init__(self, local, peer, home_path, key):
609 super(TunProtoGRE, self).__init__(local, peer, home_path, key)
610 self.mode = 'pl-gre-ip'
613 super(TunProtoGRE, self).launch('gre')
615 class TunProtoTCP(TunProtoBase):
616 def __init__(self, local, peer, home_path, key):
617 super(TunProtoTCP, self).__init__(local, peer, home_path, key)
620 super(TunProtoTCP, self).launch('tcp')
622 class TapProtoUDP(TunProtoUDP):
623 def __init__(self, local, peer, home_path, key):
624 super(TapProtoUDP, self).__init__(local, peer, home_path, key)
627 class TapProtoTCP(TunProtoTCP):
628 def __init__(self, local, peer, home_path, key):
629 super(TapProtoTCP, self).__init__(local, peer, home_path, key)
632 class TapProtoFD(TunProtoFD):
633 def __init__(self, local, peer, home_path, key):
634 super(TapProtoFD, self).__init__(local, peer, home_path, key)
637 class TapProtoGRE(TunProtoGRE):
638 def __init__(self, local, peer, home_path, key):
639 super(TapProtoGRE, self).__init__(local, peer, home_path, key)
640 self.mode = 'pl-gre-eth'