2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
5 import nepi.util.ipaddr2 as ipaddr2
6 import nepi.util.server as server
17 class NodeIface(object):
18 def __init__(self, api=None):
26 # These get initialized at configuration time
32 self._interface_id = None
34 # These get initialized when the iface is connected to its node
37 # These get initialized when the iface is connected to the internet
38 self.has_internet = False
41 return "%s<ip:%s/%s up mac:%s>" % (
42 self.__class__.__name__,
43 self.address, self.netmask,
49 def add_address(self, address, netprefix, broadcast):
50 raise RuntimeError, "Cannot add explicit addresses to public interface"
52 def pick_iface(self, siblings):
54 Picks an interface using the PLCAPI to query information about the node.
56 Needs an assigned node.
59 siblings: other NodeIface elements attached to the same node
62 if self.node is None or self.node._node_id is None:
63 raise RuntimeError, "Cannot pick interface without an assigned node"
65 avail = self._api.GetInterfaces(
66 node_id=self.node._node_id,
67 is_primary=self.primary,
68 fields=('interface_id','mac','netmask','ip') )
70 used = set([sibling._interface_id for sibling in siblings
71 if sibling._interface_id is not None])
73 for candidate in avail:
74 candidate_id = candidate['interface_id']
75 if candidate_id not in used:
77 self._interface_id = candidate_id
78 self.address = candidate['ip']
79 self.lladdr = candidate['mac']
80 self.netprefix = candidate['netmask']
81 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
84 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
87 if not self.has_internet:
88 raise RuntimeError, "All external interface devices must be connected to the Internet"
91 class _CrossIface(object):
92 def __init__(self, proto, addr, port, cipher):
93 self.tun_proto = proto
96 self.tun_cipher = cipher
98 # Cannot access cross peers
99 self.peer_proto_impl = None
103 self.__class__.__name__,
112 class TunIface(object):
113 _PROTO_MAP = tunproto.TUN_PROTO_MAP
116 def __init__(self, api=None):
118 api = plcapi.PLCAPI()
123 self.netprefix = None
129 self.txqueuelen = None
130 self.pointopoint = None
131 self.multicast = False
137 # These get initialized when the iface is connected to its node
140 # These get initialized when the iface is connected to any filter
141 self.filter_module = None
143 # These get initialized when the iface is configured
144 self.external_iface = None
146 # These get initialized when the iface is configured
147 # They're part of the TUN standard attribute set
150 self.tun_cipher = "AES"
152 # These get initialized when the iface is connected to its peer
153 self.peer_iface = None
154 self.peer_proto = None
155 self.peer_addr = None
156 self.peer_port = None
157 self.peer_proto_impl = None
158 self._delay_recover = False
160 # same as peer proto, but for execute-time standard attribute lookups
161 self.tun_proto = None
164 # Generate an initial random cryptographic key to use for tunnelling
165 # Upon connection, both endpoints will agree on a common one based on
167 self.tun_key = ( ''.join(map(chr, [
170 for r in (random.SystemRandom(),) ])
171 ).encode("base64").strip() )
175 return "%s<ip:%s/%s %s%s%s>" % (
176 self.__class__.__name__,
177 self.address, self.netprefix,
178 " up" if self.up else " down",
179 " snat" if self.snat else "",
180 (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
187 if self.peer_proto_impl:
188 return self.peer_proto_impl.if_name
190 def routes_here(self, route):
192 Returns True if the route should be attached to this interface
193 (ie, it references a gateway in this interface's network segment)
195 if self.address and self.netprefix:
196 addr, prefix = self.address, self.netprefix
197 pointopoint = self.pointopoint
199 pointopoint = self.peer_iface.address
204 dest, destprefix, nexthop, metric = route
206 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
207 gwIp = ipaddr.IPNetwork(nexthop)
210 peerIp = ipaddr.IPNetwork(pointopoint)
219 def add_address(self, address, netprefix, broadcast):
220 if (self.address or self.netprefix or self.netmask) is not None:
221 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
223 raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
225 self.address = address
226 self.netprefix = netprefix
227 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
231 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
232 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
233 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
234 if not self.address or not self.netprefix or not self.netmask:
235 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
236 if self.filter_module and self.peer_proto not in ('udp','tcp',None):
237 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (self,)
238 if self.tun_cipher != 'PLAIN' and self.peer_proto not in ('udp','tcp',None):
239 raise RuntimeError, "Miscofnigured TUN: %s - ciphered tunnels only work with udp or tcp links" % (self,)
241 def _impl_instance(self, home_path):
242 impl = self._PROTO_MAP[self.peer_proto](
243 self, self.peer_iface, home_path, self.tun_key)
244 impl.port = self.tun_port
249 self.peer_proto_impl = self._impl_instance(
251 False) # no way to know, no need to know
252 self.peer_proto_impl.recover()
254 self._delay_recover = True
256 def prepare(self, home_path):
257 if not self.peer_iface and (self.peer_proto and self.peer_addr and self.peer_port):
259 self.peer_iface = _CrossIface(
265 if not self.peer_proto_impl:
266 self.peer_proto_impl = self._impl_instance(home_path)
267 if self._delay_recover:
268 self.peer_proto_impl.recover()
271 if self.peer_proto_impl:
272 self.peer_proto_impl.launch()
275 if self.peer_proto_impl:
276 self.peer_proto_impl.shutdown()
279 if self.peer_proto_impl:
280 self.peer_proto_impl.destroy()
281 self.peer_proto_impl = None
284 if self.peer_proto_impl:
285 self.peer_proto_impl.wait()
287 def sync_trace(self, local_dir, whichtrace, tracemap = None):
288 if self.peer_proto_impl:
289 return self.peer_proto_impl.sync_trace(local_dir, whichtrace,
294 def remote_trace_path(self, whichtrace, tracemap = None):
295 if self.peer_proto_impl:
296 return self.peer_proto_impl.remote_trace_path(whichtrace, tracemap)
300 def remote_trace_name(self, whichtrace):
303 class TapIface(TunIface):
304 _PROTO_MAP = tunproto.TAP_PROTO_MAP
307 # Yep, it does nothing - yet
308 class Internet(object):
309 def __init__(self, api=None):
311 api = plcapi.PLCAPI()
314 class NetPipe(object):
315 def __init__(self, api=None):
317 api = plcapi.PLCAPI()
333 # These get initialized when the pipe is connected to its node
335 self.configured = False
339 raise RuntimeError, "Undefined NetPipe mode"
340 if not self.portList:
341 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
342 if not (self.plrIn or self.bwIn or self.delayIn):
343 raise RuntimeError, "Undefined NetPipe inbound characteristics"
344 if not (self.plrOut or self.bwOut or self.delayOut):
345 raise RuntimeError, "Undefined NetPipe outbound characteristics"
347 raise RuntimeError, "Unconnected NetPipe"
349 def _add_pipedef(self, bw, plr, delay, options):
351 options.extend(("delay","%dms" % (delay,)))
353 options.extend(("bw","%.8fMbit/s" % (bw,)))
355 options.extend(("plr","%.8f" % (plr,)))
357 def _get_ruledef(self):
360 "@" if self.addrList else "",
365 if self.bwIn or self.plrIn or self.delayIn:
367 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
368 if self.bwOut or self.plrOut or self.delayOut:
369 options.append("OUT")
370 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
371 options = ' '.join(options)
373 return (scope,options)
376 # Rules are safe on their nodes
377 self.configured = True
381 scope, options = self._get_ruledef()
382 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
384 (out,err),proc = server.popen_ssh_command(
386 host = self.node.hostname,
388 user = self.node.slicename,
390 ident_key = self.node.ident_path,
391 server_key = self.node.server_key
395 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
397 # we have to clean up afterwards
398 self.configured = True
403 scope, options = self._get_ruledef()
404 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
406 (out,err),proc = server.popen_ssh_command(
408 host = self.node.hostname,
410 user = self.node.slicename,
412 ident_key = self.node.ident_path,
413 server_key = self.node.server_key
417 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
422 scope, options = self._get_ruledef()
423 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
425 (out,err),proc = server.popen_ssh_command(
427 host = self.node.hostname,
429 user = self.node.slicename,
431 ident_key = self.node.ident_path,
432 server_key = self.node.server_key
436 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
438 self.configured = False
440 def sync_trace(self, local_dir, whichtrace):
441 if whichtrace != 'netpipeStats':
442 raise ValueError, "Unsupported trace %s" % (whichtrace,)
444 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
446 # create parent local folders
447 proc = subprocess.Popen(
448 ["mkdir", "-p", os.path.dirname(local_path)],
449 stdout = open("/dev/null","w"),
450 stdin = open("/dev/null","r"))
453 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
455 (out,err),proc = server.popen_ssh_command(
456 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
457 host = self.node.hostname,
459 user = self.node.slicename,
461 ident_key = self.node.ident_path,
462 server_key = self.node.server_key
466 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
468 # dump results to file
469 f = open(local_path, "wb")
476 class TunFilter(object):
478 # tracename : (remotename, localname)
481 def __init__(self, api=None):
483 api = plcapi.PLCAPI()
490 # These get initialised when the filter is connected
491 self.peer_guid = None
492 self.peer_proto = None
493 self.iface_guid = None
497 def _get(what, self):
502 return getattr(wref, what)
506 def _set(what, self, val):
511 setattr(wref, what, val)
513 tun_proto = property(
514 functools.partial(_get, 'tun_proto'),
515 functools.partial(_set, 'tun_proto') )
517 functools.partial(_get, 'tun_addr'),
518 functools.partial(_set, 'tun_addr') )
520 functools.partial(_get, 'tun_port'),
521 functools.partial(_set, 'tun_port') )
523 functools.partial(_get, 'tun_key'),
524 functools.partial(_set, 'tun_key') )
525 tun_cipher = property(
526 functools.partial(_get, 'tun_cipher'),
527 functools.partial(_set, 'tun_cipher') )
532 def remote_trace_path(self, whichtrace):
534 if iface is not None:
535 return iface.remote_trace_path(whichtrace, self._TRACEMAP)
538 def remote_trace_name(self, whichtrace):
540 if iface is not None:
541 return iface.remote_trace_name(whichtrace, self._TRACEMAP)
544 def sync_trace(self, local_dir, whichtrace):
546 if iface is not None:
547 return iface.sync_trace(local_dir, whichtrace, self._TRACEMAP)
550 class ClassQueueFilter(TunFilter):
552 # tracename : (remotename, localname)
553 'dropped_stats' : ('dropped_stats', 'dropped_stats')
556 def __init__(self, api=None):
557 super(ClassQueueFilter, self).__init__(api)
559 self.module = "classqueue.py"
561 class ToSQueueFilter(TunFilter):
562 def __init__(self, api=None):
563 super(ToSQueueFilter, self).__init__(api)
565 self.module = "tosqueue.py"