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 # HACK: SFA doesnt give the node_id!!
65 if not isinstance(self.node._node_id, int):
66 node_data = self._api.GetNodes(filters={'hostname':self.node.hostname}, fields=('node_id',))[0]
67 node_id = node_data['node_id']
69 node_id = self.node._node_id
71 avail = self._api.GetInterfaces(
73 is_primary=self.primary,
74 fields=('interface_id','mac','netmask','ip') )
76 used = set([sibling._interface_id for sibling in siblings
77 if sibling._interface_id is not None])
79 for candidate in avail:
80 candidate_id = candidate['interface_id']
81 if candidate_id not in used:
83 self._interface_id = candidate_id
84 self.address = candidate['ip']
85 self.lladdr = candidate['mac']
86 self.netprefix = candidate['netmask']
87 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
90 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
93 if not self.has_internet:
94 raise RuntimeError, "All external interface devices must be connected to the Internet"
97 class _CrossIface(object):
98 def __init__(self, proto, addr, port, cipher):
99 self.tun_proto = proto
102 self.tun_cipher = cipher
106 self.netprefix = None
109 # Cannot access cross peers
110 self.peer_proto_impl = None
114 self.__class__.__name__,
123 class TunIface(object):
124 _PROTO_MAP = tunproto.TUN_PROTO_MAP
127 def __init__(self, api=None):
129 api = plcapi.PLCAPI()
134 self.netprefix = None
140 self.txqueuelen = 1000
141 self.pointopoint = None
142 self.multicast = False
148 # These get initialized when the iface is connected to its node
151 # These get initialized when the iface is connected to any filter
152 self.filter_module = None
153 self.multicast_forwarder = None
155 # These get initialized when the iface is configured
156 self.external_iface = None
158 # These get initialized when the iface is configured
159 # They're part of the TUN standard attribute set
162 self.tun_cipher = "AES"
164 # These get initialized when the iface is connected to its peer
165 self.peer_iface = None
166 self.peer_proto = None
167 self.peer_addr = None
168 self.peer_port = None
169 self.peer_proto_impl = None
170 self._delay_recover = False
172 # same as peer proto, but for execute-time standard attribute lookups
173 self.tun_proto = None
176 # Generate an initial random cryptographic key to use for tunnelling
177 # Upon connection, both endpoints will agree on a common one based on
179 self.tun_key = ( ''.join(map(chr, [
182 for r in (random.SystemRandom(),) ])
183 ).encode("base64").strip() )
187 return "%s<ip:%s/%s %s%s%s>" % (
188 self.__class__.__name__,
189 self.address, self.netprefix,
190 " up" if self.up else " down",
191 " snat" if self.snat else "",
192 (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
199 if self.peer_proto_impl:
200 return self.peer_proto_impl.if_name
202 def routes_here(self, route):
204 Returns True if the route should be attached to this interface
205 (ie, it references a gateway in this interface's network segment)
207 if self.address and self.netprefix:
208 addr, prefix = self.address, self.netprefix
209 pointopoint = self.pointopoint
210 if not pointopoint and self.peer_iface:
211 pointopoint = self.peer_iface.address
216 dest, destprefix, nexthop, metric = route
218 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
219 gwIp = ipaddr.IPNetwork(nexthop)
222 peerIp = ipaddr.IPNetwork(pointopoint)
231 def add_address(self, address, netprefix, broadcast):
232 if (self.address or self.netprefix or self.netmask) is not None:
233 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
235 raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
237 self.address = address
238 self.netprefix = netprefix
239 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
243 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
244 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
245 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
246 if not self.address or not self.netprefix or not self.netmask:
247 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
248 if self.filter_module and self.peer_proto not in ('udp','tcp',None):
249 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (self,)
250 if self.tun_cipher != 'PLAIN' and self.peer_proto not in ('udp','tcp',None):
251 raise RuntimeError, "Miscofnigured TUN: %s - ciphered tunnels only work with udp or tcp links" % (self,)
253 def _impl_instance(self, home_path):
254 impl = self._PROTO_MAP[self.peer_proto](
255 self, self.peer_iface, home_path, self.tun_key)
256 impl.port = self.tun_port
257 impl.cross_slice = not self.peer_iface or isinstance(self.peer_iface, _CrossIface)
262 self.peer_proto_impl = self._impl_instance(
264 self.peer_proto_impl.recover()
266 self._delay_recover = True
268 def prepare(self, home_path):
269 if not self.peer_iface and (self.peer_proto and self.peer_addr):
271 self.peer_iface = _CrossIface(
277 if not self.peer_proto_impl:
278 self.peer_proto_impl = self._impl_instance(home_path)
279 if self._delay_recover:
280 self.peer_proto_impl.recover()
283 if self.peer_proto_impl:
284 self.peer_proto_impl.launch()
287 if self.peer_proto_impl:
288 self.peer_proto_impl.shutdown()
291 if self.peer_proto_impl:
292 self.peer_proto_impl.destroy()
293 self.peer_proto_impl = None
296 if self.peer_proto_impl:
297 self.peer_proto_impl.wait()
299 def sync_trace(self, local_dir, whichtrace, tracemap = None):
300 if self.peer_proto_impl:
301 return self.peer_proto_impl.sync_trace(local_dir, whichtrace,
306 def remote_trace_path(self, whichtrace, tracemap = None):
307 if self.peer_proto_impl:
308 return self.peer_proto_impl.remote_trace_path(whichtrace, tracemap)
312 def remote_trace_name(self, whichtrace):
315 class TapIface(TunIface):
316 _PROTO_MAP = tunproto.TAP_PROTO_MAP
319 # Yep, it does nothing - yet
320 class Internet(object):
321 def __init__(self, api=None):
323 api = plcapi.PLCAPI()
326 class NetPipe(object):
327 def __init__(self, api=None):
329 api = plcapi.PLCAPI()
345 # These get initialized when the pipe is connected to its node
347 self.configured = False
351 raise RuntimeError, "Undefined NetPipe mode"
352 if not self.portList:
353 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
354 if not (self.plrIn or self.bwIn or self.delayIn):
355 raise RuntimeError, "Undefined NetPipe inbound characteristics"
356 if not (self.plrOut or self.bwOut or self.delayOut):
357 raise RuntimeError, "Undefined NetPipe outbound characteristics"
359 raise RuntimeError, "Unconnected NetPipe"
361 def _add_pipedef(self, bw, plr, delay, options):
363 options.extend(("delay","%dms" % (delay,)))
365 options.extend(("bw","%.8fMbit/s" % (bw,)))
367 options.extend(("plr","%.8f" % (plr,)))
369 def _get_ruledef(self):
372 "@" if self.addrList else "",
377 if self.bwIn or self.plrIn or self.delayIn:
379 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
380 if self.bwOut or self.plrOut or self.delayOut:
381 options.append("OUT")
382 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
383 options = ' '.join(options)
385 return (scope,options)
388 # Rules are safe on their nodes
389 self.configured = True
393 scope, options = self._get_ruledef()
394 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
396 (out,err),proc = server.popen_ssh_command(
398 host = self.node.hostname,
400 user = self.node.slicename,
402 ident_key = self.node.ident_path,
403 server_key = self.node.server_key
407 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
409 # we have to clean up afterwards
410 self.configured = True
415 scope, options = self._get_ruledef()
416 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
418 (out,err),proc = server.popen_ssh_command(
420 host = self.node.hostname,
422 user = self.node.slicename,
424 ident_key = self.node.ident_path,
425 server_key = self.node.server_key
429 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
434 scope, options = self._get_ruledef()
435 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
437 (out,err),proc = server.popen_ssh_command(
439 host = self.node.hostname,
441 user = self.node.slicename,
443 ident_key = self.node.ident_path,
444 server_key = self.node.server_key
448 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
450 self.configured = False
452 def sync_trace(self, local_dir, whichtrace):
453 if whichtrace != 'netpipeStats':
454 raise ValueError, "Unsupported trace %s" % (whichtrace,)
456 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
458 # create parent local folders
459 proc = subprocess.Popen(
460 ["mkdir", "-p", os.path.dirname(local_path)],
461 stdout = open("/dev/null","w"),
462 stdin = open("/dev/null","r"))
465 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
467 (out,err),proc = server.popen_ssh_command(
468 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
469 host = self.node.hostname,
471 user = self.node.slicename,
473 ident_key = self.node.ident_path,
474 server_key = self.node.server_key
478 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
480 # dump results to file
481 f = open(local_path, "wb")
488 class TunFilter(object):
490 # tracename : (remotename, localname)
493 def __init__(self, api=None):
495 api = plcapi.PLCAPI()
502 # These get initialised when the filter is connected
503 self.peer_guid = None
504 self.peer_proto = None
505 self.iface_guid = None
509 def _get(what, self):
514 return getattr(wref, what)
518 def _set(what, self, val):
523 setattr(wref, what, val)
525 tun_proto = property(
526 functools.partial(_get, 'tun_proto'),
527 functools.partial(_set, 'tun_proto') )
529 functools.partial(_get, 'tun_addr'),
530 functools.partial(_set, 'tun_addr') )
532 functools.partial(_get, 'tun_port'),
533 functools.partial(_set, 'tun_port') )
535 functools.partial(_get, 'tun_key'),
536 functools.partial(_set, 'tun_key') )
537 tun_cipher = property(
538 functools.partial(_get, 'tun_cipher'),
539 functools.partial(_set, 'tun_cipher') )
544 def remote_trace_path(self, whichtrace):
546 if iface is not None:
547 return iface.remote_trace_path(whichtrace, self._TRACEMAP)
550 def remote_trace_name(self, whichtrace):
552 if iface is not None:
553 return iface.remote_trace_name(whichtrace, self._TRACEMAP)
556 def sync_trace(self, local_dir, whichtrace):
558 if iface is not None:
559 return iface.sync_trace(local_dir, whichtrace, self._TRACEMAP)
562 class ClassQueueFilter(TunFilter):
564 # tracename : (remotename, localname)
565 'dropped_stats' : ('dropped_stats', 'dropped_stats')
568 def __init__(self, api=None):
569 super(ClassQueueFilter, self).__init__(api)
571 self.module = "classqueue.py"
573 class ToSQueueFilter(TunFilter):
574 def __init__(self, api=None):
575 super(ToSQueueFilter, self).__init__(api)
577 self.module = "tosqueue.py"