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
142 self.multicast_forwarder = None
144 # These get initialized when the iface is configured
145 self.external_iface = None
147 # These get initialized when the iface is configured
148 # They're part of the TUN standard attribute set
151 self.tun_cipher = "AES"
153 # These get initialized when the iface is connected to its peer
154 self.peer_iface = None
155 self.peer_proto = None
156 self.peer_addr = None
157 self.peer_port = None
158 self.peer_proto_impl = None
159 self._delay_recover = False
161 # same as peer proto, but for execute-time standard attribute lookups
162 self.tun_proto = None
165 # Generate an initial random cryptographic key to use for tunnelling
166 # Upon connection, both endpoints will agree on a common one based on
168 self.tun_key = ( ''.join(map(chr, [
171 for r in (random.SystemRandom(),) ])
172 ).encode("base64").strip() )
176 return "%s<ip:%s/%s %s%s%s>" % (
177 self.__class__.__name__,
178 self.address, self.netprefix,
179 " up" if self.up else " down",
180 " snat" if self.snat else "",
181 (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
188 if self.peer_proto_impl:
189 return self.peer_proto_impl.if_name
191 def routes_here(self, route):
193 Returns True if the route should be attached to this interface
194 (ie, it references a gateway in this interface's network segment)
196 if self.address and self.netprefix:
197 addr, prefix = self.address, self.netprefix
198 pointopoint = self.pointopoint
200 pointopoint = self.peer_iface.address
205 dest, destprefix, nexthop, metric = route
207 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
208 gwIp = ipaddr.IPNetwork(nexthop)
211 peerIp = ipaddr.IPNetwork(pointopoint)
220 def add_address(self, address, netprefix, broadcast):
221 if (self.address or self.netprefix or self.netmask) is not None:
222 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
224 raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
226 self.address = address
227 self.netprefix = netprefix
228 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
232 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
233 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
234 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
235 if not self.address or not self.netprefix or not self.netmask:
236 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
237 if self.filter_module and self.peer_proto not in ('udp','tcp',None):
238 raise RuntimeError, "Miscofnigured TUN: %s - filtered tunnels only work with udp or tcp links" % (self,)
239 if self.tun_cipher != 'PLAIN' and self.peer_proto not in ('udp','tcp',None):
240 raise RuntimeError, "Miscofnigured TUN: %s - ciphered tunnels only work with udp or tcp links" % (self,)
242 def _impl_instance(self, home_path, listening):
243 impl = self._PROTO_MAP[self.peer_proto](
244 self, self.peer_iface, home_path, self.tun_key, listening)
245 impl.port = self.tun_port
246 impl.cross_slice = not self.peer_iface or isinstance(self.peer_iface, _CrossIface)
251 self.peer_proto_impl = self._impl_instance(
253 False) # no way to know, no need to know
254 self.peer_proto_impl.recover()
256 self._delay_recover = True
258 def prepare(self, home_path, listening):
259 if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
261 self.peer_iface = _CrossIface(
267 if not self.peer_proto_impl:
268 self.peer_proto_impl = self._impl_instance(home_path, listening)
269 if self._delay_recover:
270 self.peer_proto_impl.recover()
272 self.peer_proto_impl.prepare()
275 if self.peer_proto_impl:
276 self.peer_proto_impl.setup()
279 if self.peer_proto_impl:
280 self.peer_proto_impl.shutdown()
283 if self.peer_proto_impl:
284 self.peer_proto_impl.destroy()
285 self.peer_proto_impl = None
287 def async_launch_wait(self):
288 if self.peer_proto_impl:
289 self.peer_proto_impl.async_launch_wait()
291 def sync_trace(self, local_dir, whichtrace, tracemap = None):
292 if self.peer_proto_impl:
293 return self.peer_proto_impl.sync_trace(local_dir, whichtrace,
298 def remote_trace_path(self, whichtrace, tracemap = None):
299 if self.peer_proto_impl:
300 return self.peer_proto_impl.remote_trace_path(whichtrace, tracemap)
304 def remote_trace_name(self, whichtrace):
307 class TapIface(TunIface):
308 _PROTO_MAP = tunproto.TAP_PROTO_MAP
311 # Yep, it does nothing - yet
312 class Internet(object):
313 def __init__(self, api=None):
315 api = plcapi.PLCAPI()
318 class NetPipe(object):
319 def __init__(self, api=None):
321 api = plcapi.PLCAPI()
337 # These get initialized when the pipe is connected to its node
339 self.configured = False
343 raise RuntimeError, "Undefined NetPipe mode"
344 if not self.portList:
345 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
346 if not (self.plrIn or self.bwIn or self.delayIn):
347 raise RuntimeError, "Undefined NetPipe inbound characteristics"
348 if not (self.plrOut or self.bwOut or self.delayOut):
349 raise RuntimeError, "Undefined NetPipe outbound characteristics"
351 raise RuntimeError, "Unconnected NetPipe"
353 def _add_pipedef(self, bw, plr, delay, options):
355 options.extend(("delay","%dms" % (delay,)))
357 options.extend(("bw","%.8fMbit/s" % (bw,)))
359 options.extend(("plr","%.8f" % (plr,)))
361 def _get_ruledef(self):
364 "@" if self.addrList else "",
369 if self.bwIn or self.plrIn or self.delayIn:
371 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
372 if self.bwOut or self.plrOut or self.delayOut:
373 options.append("OUT")
374 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
375 options = ' '.join(options)
377 return (scope,options)
380 # Rules are safe on their nodes
381 self.configured = True
385 scope, options = self._get_ruledef()
386 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
388 (out,err),proc = server.popen_ssh_command(
390 host = self.node.hostname,
392 user = self.node.slicename,
394 ident_key = self.node.ident_path,
395 server_key = self.node.server_key
399 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
401 # we have to clean up afterwards
402 self.configured = True
407 scope, options = self._get_ruledef()
408 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
410 (out,err),proc = server.popen_ssh_command(
412 host = self.node.hostname,
414 user = self.node.slicename,
416 ident_key = self.node.ident_path,
417 server_key = self.node.server_key
421 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
426 scope, options = self._get_ruledef()
427 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
429 (out,err),proc = server.popen_ssh_command(
431 host = self.node.hostname,
433 user = self.node.slicename,
435 ident_key = self.node.ident_path,
436 server_key = self.node.server_key
440 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
442 self.configured = False
444 def sync_trace(self, local_dir, whichtrace):
445 if whichtrace != 'netpipeStats':
446 raise ValueError, "Unsupported trace %s" % (whichtrace,)
448 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
450 # create parent local folders
451 proc = subprocess.Popen(
452 ["mkdir", "-p", os.path.dirname(local_path)],
453 stdout = open("/dev/null","w"),
454 stdin = open("/dev/null","r"))
457 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
459 (out,err),proc = server.popen_ssh_command(
460 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
461 host = self.node.hostname,
463 user = self.node.slicename,
465 ident_key = self.node.ident_path,
466 server_key = self.node.server_key
470 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
472 # dump results to file
473 f = open(local_path, "wb")
480 class TunFilter(object):
482 # tracename : (remotename, localname)
485 def __init__(self, api=None):
487 api = plcapi.PLCAPI()
494 # These get initialised when the filter is connected
495 self.peer_guid = None
496 self.peer_proto = None
497 self.iface_guid = None
501 def _get(what, self):
506 return getattr(wref, what)
510 def _set(what, self, val):
515 setattr(wref, what, val)
517 tun_proto = property(
518 functools.partial(_get, 'tun_proto'),
519 functools.partial(_set, 'tun_proto') )
521 functools.partial(_get, 'tun_addr'),
522 functools.partial(_set, 'tun_addr') )
524 functools.partial(_get, 'tun_port'),
525 functools.partial(_set, 'tun_port') )
527 functools.partial(_get, 'tun_key'),
528 functools.partial(_set, 'tun_key') )
529 tun_cipher = property(
530 functools.partial(_get, 'tun_cipher'),
531 functools.partial(_set, 'tun_cipher') )
536 def remote_trace_path(self, whichtrace):
538 if iface is not None:
539 return iface.remote_trace_path(whichtrace, self._TRACEMAP)
542 def remote_trace_name(self, whichtrace):
544 if iface is not None:
545 return iface.remote_trace_name(whichtrace, self._TRACEMAP)
548 def sync_trace(self, local_dir, whichtrace):
550 if iface is not None:
551 return iface.sync_trace(local_dir, whichtrace, self._TRACEMAP)
554 class ClassQueueFilter(TunFilter):
556 # tracename : (remotename, localname)
557 'dropped_stats' : ('dropped_stats', 'dropped_stats')
560 def __init__(self, api=None):
561 super(ClassQueueFilter, self).__init__(api)
563 self.module = "classqueue.py"
565 class ToSQueueFilter(TunFilter):
566 def __init__(self, api=None):
567 super(ToSQueueFilter, self).__init__(api)
569 self.module = "tosqueue.py"