2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
5 import nepi.util.ipaddr2 as ipaddr2
6 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
97 # Cannot access cross peers
98 self.peer_proto_impl = None
102 self.__class__.__name__,
111 class TunIface(object):
112 _PROTO_MAP = tunproto.TUN_PROTO_MAP
115 def __init__(self, api=None):
117 api = plcapi.PLCAPI()
122 self.netprefix = None
126 self.device_name = None
129 self.txqueuelen = None
130 self.pointopoint = None
135 # These get initialized when the iface is connected to its node
138 # These get initialized when the iface is configured
139 self.external_iface = None
141 # These get initialized when the iface is configured
142 # They're part of the TUN standard attribute set
145 self.tun_cipher = "AES"
147 # These get initialized when the iface is connected to its peer
148 self.peer_iface = None
149 self.peer_proto = None
150 self.peer_addr = None
151 self.peer_port = None
152 self.peer_proto_impl = None
153 self._delay_recover = False
155 # same as peer proto, but for execute-time standard attribute lookups
156 self.tun_proto = None
159 # Generate an initial random cryptographic key to use for tunnelling
160 # Upon connection, both endpoints will agree on a common one based on
162 self.tun_key = ( ''.join(map(chr, [
165 for r in (random.SystemRandom(),) ])
166 ).encode("base64").strip() )
170 return "%s<ip:%s/%s %s%s%s>" % (
171 self.__class__.__name__,
172 self.address, self.netprefix,
173 " up" if self.up else " down",
174 " snat" if self.snat else "",
175 (" p2p %s" % (self.pointopoint,)) if self.pointopoint else "",
182 if self.peer_proto_impl:
183 return self.peer_proto_impl.if_name
185 def routes_here(self, route):
187 Returns True if the route should be attached to this interface
188 (ie, it references a gateway in this interface's network segment)
190 if self.address and self.netprefix:
191 addr, prefix = self.address, self.netprefix
192 pointopoint = self.pointopoint
194 pointopoint = self.peer_iface.address
199 dest, destprefix, nexthop, metric = route
201 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
202 gwIp = ipaddr.IPNetwork(nexthop)
205 peerIp = ipaddr.IPNetwork(pointopoint)
214 def add_address(self, address, netprefix, broadcast):
215 if (self.address or self.netprefix or self.netmask) is not None:
216 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
218 raise ValueError, "%s interfaces cannot broadcast in PlanetLab (%s)" % (self._KIND,broadcast)
220 self.address = address
221 self.netprefix = netprefix
222 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
226 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
227 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
228 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
229 if not self.address or not self.netprefix or not self.netmask:
230 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
232 def _impl_instance(self, home_path, listening):
233 impl = self._PROTO_MAP[self.peer_proto](
234 self, self.peer_iface, home_path, self.tun_key, listening)
235 impl.port = self.tun_port
240 self.peer_proto_impl = self._impl_instance(
242 False) # no way to know, no need to know
243 self.peer_proto_impl.recover()
245 self._delay_recover = True
247 def prepare(self, home_path, listening):
248 if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
250 self.peer_iface = _CrossIface(
256 if not self.peer_proto_impl:
257 self.peer_proto_impl = self._impl_instance(home_path, listening)
258 if self._delay_recover:
259 self.peer_proto_impl.recover()
261 self.peer_proto_impl.prepare()
264 if self.peer_proto_impl:
265 self.peer_proto_impl.setup()
268 if self.peer_proto_impl:
269 self.peer_proto_impl.shutdown()
272 if self.peer_proto_impl:
273 self.peer_proto_impl.destroy()
274 self.peer_proto_impl = None
276 def async_launch_wait(self):
277 if self.peer_proto_impl:
278 self.peer_proto_impl.async_launch_wait()
280 def sync_trace(self, local_dir, whichtrace):
281 if self.peer_proto_impl:
282 return self.peer_proto_impl.sync_trace(local_dir, whichtrace)
286 def remote_trace_path(self, whichtrace):
287 if self.peer_proto_impl:
288 return self.peer_proto_impl.remote_trace_path(whichtrace)
292 class TapIface(TunIface):
293 _PROTO_MAP = tunproto.TAP_PROTO_MAP
296 # Yep, it does nothing - yet
297 class Internet(object):
298 def __init__(self, api=None):
300 api = plcapi.PLCAPI()
303 class NetPipe(object):
304 def __init__(self, api=None):
306 api = plcapi.PLCAPI()
322 # These get initialized when the pipe is connected to its node
324 self.configured = False
328 raise RuntimeError, "Undefined NetPipe mode"
329 if not self.portList:
330 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
331 if not (self.plrIn or self.bwIn or self.delayIn):
332 raise RuntimeError, "Undefined NetPipe inbound characteristics"
333 if not (self.plrOut or self.bwOut or self.delayOut):
334 raise RuntimeError, "Undefined NetPipe outbound characteristics"
336 raise RuntimeError, "Unconnected NetPipe"
338 def _add_pipedef(self, bw, plr, delay, options):
340 options.extend(("delay","%dms" % (delay,)))
342 options.extend(("bw","%.8fMbit/s" % (bw,)))
344 options.extend(("plr","%.8f" % (plr,)))
346 def _get_ruledef(self):
349 "@" if self.addrList else "",
354 if self.bwIn or self.plrIn or self.delayIn:
356 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
357 if self.bwOut or self.plrOut or self.delayOut:
358 options.append("OUT")
359 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
360 options = ' '.join(options)
362 return (scope,options)
365 # Rules are safe on their nodes
366 self.configured = True
370 scope, options = self._get_ruledef()
371 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
373 (out,err),proc = server.popen_ssh_command(
375 host = self.node.hostname,
377 user = self.node.slicename,
379 ident_key = self.node.ident_path,
380 server_key = self.node.server_key
384 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
386 # we have to clean up afterwards
387 self.configured = True
392 scope, options = self._get_ruledef()
393 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
395 (out,err),proc = server.popen_ssh_command(
397 host = self.node.hostname,
399 user = self.node.slicename,
401 ident_key = self.node.ident_path,
402 server_key = self.node.server_key
406 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
411 scope, options = self._get_ruledef()
412 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
414 (out,err),proc = server.popen_ssh_command(
416 host = self.node.hostname,
418 user = self.node.slicename,
420 ident_key = self.node.ident_path,
421 server_key = self.node.server_key
425 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
427 self.configured = False
429 def sync_trace(self, local_dir, whichtrace):
430 if whichtrace != 'netpipeStats':
431 raise ValueError, "Unsupported trace %s" % (whichtrace,)
433 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
435 # create parent local folders
436 proc = subprocess.Popen(
437 ["mkdir", "-p", os.path.dirname(local_path)],
438 stdout = open("/dev/null","w"),
439 stdin = open("/dev/null","r"))
442 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
444 (out,err),proc = server.popen_ssh_command(
445 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
446 host = self.node.hostname,
448 user = self.node.slicename,
450 ident_key = self.node.ident_path,
451 server_key = self.node.server_key
455 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
457 # dump results to file
458 f = open(local_path, "wb")