1 # -*- coding: utf-8 -*-
3 from constants import TESTBED_ID
4 import nepi.util.ipaddr2 as ipaddr2
5 import nepi.util.server as server
16 class NodeIface(object):
17 def __init__(self, api=None):
25 # These get initialized at configuration time
31 self._interface_id = None
33 # These get initialized when the iface is connected to its node
36 # These get initialized when the iface is connected to the internet
37 self.has_internet = False
40 return "%s<ip:%s/%s up mac:%s>" % (
41 self.__class__.__name__,
42 self.address, self.netmask,
48 def add_address(self, address, netprefix, broadcast):
49 raise RuntimeError, "Cannot add explicit addresses to public interface"
51 def pick_iface(self, siblings):
53 Picks an interface using the PLCAPI to query information about the node.
55 Needs an assigned node.
58 siblings: other NodeIface elements attached to the same node
61 if self.node is None or self.node._node_id is None:
62 raise RuntimeError, "Cannot pick interface without an assigned node"
64 avail = self._api.GetInterfaces(
65 node_id=self.node._node_id,
66 is_primary=self.primary,
67 fields=('interface_id','mac','netmask','ip') )
69 used = set([sibling._interface_id for sibling in siblings
70 if sibling._interface_id is not None])
72 for candidate in avail:
73 candidate_id = candidate['interface_id']
74 if candidate_id not in used:
76 self._interface_id = candidate_id
77 self.address = candidate['ip']
78 self.lladdr = candidate['mac']
79 self.netprefix = candidate['netmask']
80 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
83 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
86 if not self.has_internet:
87 raise RuntimeError, "All external interface devices must be connected to the Internet"
90 class _CrossIface(object):
91 def __init__(self, proto, addr, port, cipher):
92 self.tun_proto = proto
95 self.tun_cipher = cipher
102 # Cannot access cross peers
103 self.peer_proto_impl = None
107 self.__class__.__name__,
116 class TunIface(object):
117 _PROTO_MAP = tunproto.TUN_PROTO_MAP
120 def __init__(self, api=None):
122 api = plcapi.PLCAPI()
127 self.netprefix = None
133 self.txqueuelen = 1000
134 self.pointopoint = None
135 self.multicast = False
141 # These get initialized when the iface is connected to its node
144 # These get initialized when the iface is connected to any filter
145 self.filter_module = None
146 self.multicast_forwarder = None
148 # These get initialized when the iface is configured
149 self.external_iface = None
151 # These get initialized when the iface is configured
152 # They're part of the TUN standard attribute set
155 self.tun_cipher = "AES"
157 # These get initialized when the iface is connected to its peer
158 self.peer_iface = None
159 self.peer_proto = None
160 self.peer_addr = None
161 self.peer_port = None
162 self.peer_proto_impl = None
163 self._delay_recover = False
165 # same as peer proto, but for execute-time standard attribute lookups
166 self.tun_proto = None
169 # Generate an initial random cryptographic key to use for tunnelling
170 # Upon connection, both endpoints will agree on a common one based on
172 self.tun_key = ( ''.join(map(chr, [
175 for r in (random.SystemRandom(),) ])
176 ).encode("base64").strip() )
180 return "%s<ip:%s/%s %s%s%s>" % (
181 self.__class__.__name__,
182 self.address, self.netprefix,
183 " up" if self.up else " down",
184 " snat" if self.snat else "",
185 (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
192 if self.peer_proto_impl:
193 return self.peer_proto_impl.if_name
195 def routes_here(self, route):
197 Returns True if the route should be attached to this interface
198 (ie, it references a gateway in this interface's network segment)
200 if self.address and self.netprefix:
201 addr, prefix = self.address, self.netprefix
202 pointopoint = self.pointopoint
203 if not pointopoint and self.peer_iface:
204 pointopoint = self.peer_iface.address
209 dest, destprefix, nexthop, metric = route
211 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
212 gwIp = ipaddr.IPNetwork(nexthop)
215 peerIp = ipaddr.IPNetwork(pointopoint)
224 def add_address(self, address, netprefix, broadcast):
225 if (self.address or self.netprefix or self.netmask) is not None:
226 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
228 raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
230 self.address = address
231 self.netprefix = netprefix
232 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
236 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
237 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
238 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
239 if not self.address or not self.netprefix or not self.netmask:
240 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
241 if self.filter_module and self.peer_proto not in ('udp','tcp',None):
242 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (self,)
243 if self.tun_cipher != 'PLAIN' and self.peer_proto not in ('udp','tcp',None):
244 raise RuntimeError, "Miscofnigured TUN: %s - ciphered tunnels only work with udp or tcp links" % (self,)
246 def _impl_instance(self, home_path):
247 impl = self._PROTO_MAP[self.peer_proto](
248 self, self.peer_iface, home_path, self.tun_key)
249 impl.port = self.tun_port
250 impl.cross_slice = not self.peer_iface or isinstance(self.peer_iface, _CrossIface)
255 self.peer_proto_impl = self._impl_instance(
257 self.peer_proto_impl.recover()
259 self._delay_recover = True
261 def prepare(self, home_path):
262 if not self.peer_iface and (self.peer_proto and self.peer_addr):
264 self.peer_iface = _CrossIface(
270 if not self.peer_proto_impl:
271 self.peer_proto_impl = self._impl_instance(home_path)
272 if self._delay_recover:
273 self.peer_proto_impl.recover()
276 if self.peer_proto_impl:
277 self.peer_proto_impl.launch()
280 if self.peer_proto_impl:
281 self.peer_proto_impl.shutdown()
284 if self.peer_proto_impl:
285 self.peer_proto_impl.destroy()
286 self.peer_proto_impl = None
289 if self.peer_proto_impl:
290 self.peer_proto_impl.wait()
292 def sync_trace(self, local_dir, whichtrace, tracemap = None):
293 if self.peer_proto_impl:
294 return self.peer_proto_impl.sync_trace(local_dir, whichtrace,
299 def remote_trace_path(self, whichtrace, tracemap = None):
300 if self.peer_proto_impl:
301 return self.peer_proto_impl.remote_trace_path(whichtrace, tracemap)
305 def remote_trace_name(self, whichtrace):
308 class TapIface(TunIface):
309 _PROTO_MAP = tunproto.TAP_PROTO_MAP
312 # Yep, it does nothing - yet
313 class Internet(object):
314 def __init__(self, api=None):
316 api = plcapi.PLCAPI()
319 class NetPipe(object):
320 def __init__(self, api=None):
322 api = plcapi.PLCAPI()
338 # These get initialized when the pipe is connected to its node
340 self.configured = False
344 raise RuntimeError, "Undefined NetPipe mode"
345 if not self.portList:
346 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
347 if not (self.plrIn or self.bwIn or self.delayIn):
348 raise RuntimeError, "Undefined NetPipe inbound characteristics"
349 if not (self.plrOut or self.bwOut or self.delayOut):
350 raise RuntimeError, "Undefined NetPipe outbound characteristics"
352 raise RuntimeError, "Unconnected NetPipe"
354 def _add_pipedef(self, bw, plr, delay, options):
356 options.extend(("delay","%dms" % (delay,)))
358 options.extend(("bw","%.8fMbit/s" % (bw,)))
360 options.extend(("plr","%.8f" % (plr,)))
362 def _get_ruledef(self):
365 "@" if self.addrList else "",
370 if self.bwIn or self.plrIn or self.delayIn:
372 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
373 if self.bwOut or self.plrOut or self.delayOut:
374 options.append("OUT")
375 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
376 options = ' '.join(options)
378 return (scope,options)
381 # Rules are safe on their nodes
382 self.configured = True
386 scope, options = self._get_ruledef()
387 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
389 (out,err),proc = server.popen_ssh_command(
391 host = self.node.hostname,
393 user = self.node.slicename,
395 ident_key = self.node.ident_path,
396 server_key = self.node.server_key
400 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
402 # we have to clean up afterwards
403 self.configured = True
408 scope, options = self._get_ruledef()
409 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
411 (out,err),proc = server.popen_ssh_command(
413 host = self.node.hostname,
415 user = self.node.slicename,
417 ident_key = self.node.ident_path,
418 server_key = self.node.server_key
422 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
427 scope, options = self._get_ruledef()
428 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
430 (out,err),proc = server.popen_ssh_command(
432 host = self.node.hostname,
434 user = self.node.slicename,
436 ident_key = self.node.ident_path,
437 server_key = self.node.server_key
441 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
443 self.configured = False
445 def sync_trace(self, local_dir, whichtrace):
446 if whichtrace != 'netpipeStats':
447 raise ValueError, "Unsupported trace %s" % (whichtrace,)
449 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
451 # create parent local folders
452 proc = subprocess.Popen(
453 ["mkdir", "-p", os.path.dirname(local_path)],
454 stdout = open("/dev/null","w"),
455 stdin = open("/dev/null","r"))
458 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
460 (out,err),proc = server.popen_ssh_command(
461 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
462 host = self.node.hostname,
464 user = self.node.slicename,
466 ident_key = self.node.ident_path,
467 server_key = self.node.server_key
471 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
473 # dump results to file
474 f = open(local_path, "wb")
481 class TunFilter(object):
483 # tracename : (remotename, localname)
486 def __init__(self, api=None):
488 api = plcapi.PLCAPI()
495 # These get initialised when the filter is connected
496 self.peer_guid = None
497 self.peer_proto = None
498 self.iface_guid = None
502 def _get(what, self):
507 return getattr(wref, what)
511 def _set(what, self, val):
516 setattr(wref, what, val)
518 tun_proto = property(
519 functools.partial(_get, 'tun_proto'),
520 functools.partial(_set, 'tun_proto') )
522 functools.partial(_get, 'tun_addr'),
523 functools.partial(_set, 'tun_addr') )
525 functools.partial(_get, 'tun_port'),
526 functools.partial(_set, 'tun_port') )
528 functools.partial(_get, 'tun_key'),
529 functools.partial(_set, 'tun_key') )
530 tun_cipher = property(
531 functools.partial(_get, 'tun_cipher'),
532 functools.partial(_set, 'tun_cipher') )
537 def remote_trace_path(self, whichtrace):
539 if iface is not None:
540 return iface.remote_trace_path(whichtrace, self._TRACEMAP)
543 def remote_trace_name(self, whichtrace):
545 if iface is not None:
546 return iface.remote_trace_name(whichtrace, self._TRACEMAP)
549 def sync_trace(self, local_dir, whichtrace):
551 if iface is not None:
552 return iface.sync_trace(local_dir, whichtrace, self._TRACEMAP)
555 class ClassQueueFilter(TunFilter):
557 # tracename : (remotename, localname)
558 'dropped_stats' : ('dropped_stats', 'dropped_stats')
561 def __init__(self, api=None):
562 super(ClassQueueFilter, self).__init__(api)
564 self.module = "classqueue.py"
566 class ToSQueueFilter(TunFilter):
567 def __init__(self, api=None):
568 super(ToSQueueFilter, self).__init__(api)
570 self.module = "tosqueue.py"