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,
46 def add_address(self, address, netprefix, broadcast):
47 raise RuntimeError, "Cannot add explicit addresses to public interface"
49 def pick_iface(self, siblings):
51 Picks an interface using the PLCAPI to query information about the node.
53 Needs an assigned node.
56 siblings: other NodeIface elements attached to the same node
59 if self.node is None or self.node._node_id is None:
60 raise RuntimeError, "Cannot pick interface without an assigned node"
62 avail = self._api.GetInterfaces(
63 node_id=self.node._node_id,
64 is_primary=self.primary,
65 fields=('interface_id','mac','netmask','ip') )
67 used = set([sibling._interface_id for sibling in siblings
68 if sibling._interface_id is not None])
70 for candidate in avail:
71 candidate_id = candidate['interface_id']
72 if candidate_id not in used:
74 self._interface_id = candidate_id
75 self.address = candidate['ip']
76 self.lladdr = candidate['mac']
77 self.netprefix = candidate['netmask']
78 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
81 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
84 if not self.has_internet:
85 raise RuntimeError, "All external interface devices must be connected to the Internet"
88 class _CrossIface(object):
89 def __init__(self, proto, addr, port):
90 self.tun_proto = proto
94 # Cannot access cross peers
95 self.peer_proto_impl = None
99 self.__class__.__name__,
105 class TunIface(object):
106 _PROTO_MAP = tunproto.TUN_PROTO_MAP
109 def __init__(self, api=None):
111 api = plcapi.PLCAPI()
116 self.netprefix = None
120 self.device_name = None
123 self.txqueuelen = None
124 self.pointopoint = None
129 # These get initialized when the iface is connected to its node
132 # These get initialized when the iface is configured
133 self.external_iface = None
135 # These get initialized when the iface is configured
136 # They're part of the TUN standard attribute set
140 # These get initialized when the iface is connected to its peer
141 self.peer_iface = None
142 self.peer_proto = None
143 self.peer_addr = None
144 self.peer_port = None
145 self.peer_proto_impl = None
147 # same as peer proto, but for execute-time standard attribute lookups
148 self.tun_proto = None
151 # Generate an initial random cryptographic key to use for tunnelling
152 # Upon connection, both endpoints will agree on a common one based on
154 self.tun_key = ( ''.join(map(chr, [
157 for r in (random.SystemRandom(),) ])
158 ).encode("base64").strip() )
162 return "%s<ip:%s/%s %s%s>" % (
163 self.__class__.__name__,
164 self.address, self.netprefix,
165 " up" if self.up else " down",
166 " snat" if self.snat else "",
171 if self.peer_proto_impl:
172 return self.peer_proto_impl.if_name
174 def routes_here(self, route):
176 Returns True if the route should be attached to this interface
177 (ie, it references a gateway in this interface's network segment)
179 if self.address and self.netprefix:
180 addr, prefix = self.address, self.netprefix
184 dest, destprefix, nexthop = route
186 myNet = ipaddr.IPNetwork("%s/%d" % (addr, prefix))
187 gwIp = ipaddr.IPNetwork(nexthop)
189 tgtIp = ipaddr.IPNetwork(dest
190 + (("/%d" % destprefix) if destprefix else "") )
192 if gwIp in myNet or tgtIp in myNet:
196 peerIp = ipaddr.IPNetwork(self.pointopoint)
201 def add_address(self, address, netprefix, broadcast):
202 if (self.address or self.netprefix or self.netmask) is not None:
203 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
205 raise ValueError, "%s interfaces cannot broadcast in PlanetLab" % (self._KIND,)
207 self.address = address
208 self.netprefix = netprefix
209 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
213 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
214 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
215 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
216 if not self.address or not self.netprefix or not self.netmask:
217 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
219 def _impl_instance(self, home_path, listening):
220 impl = self._PROTO_MAP[self.peer_proto](
221 self, self.peer_iface, home_path, self.tun_key, listening)
222 impl.port = self.tun_port
225 def prepare(self, home_path, listening):
226 if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
228 self.peer_iface = _CrossIface(
233 if not self.peer_proto_impl:
234 self.peer_proto_impl = self._impl_instance(home_path, listening)
235 self.peer_proto_impl.prepare()
238 if self.peer_proto_impl:
239 self.peer_proto_impl.setup()
242 if self.peer_proto_impl:
243 self.peer_proto_impl.shutdown()
244 self.peer_proto_impl = None
246 def async_launch_wait(self):
247 if self.peer_proto_impl:
248 self.peer_proto_impl.async_launch_wait()
250 def sync_trace(self, local_dir, whichtrace):
251 if self.peer_proto_impl:
252 return self.peer_proto_impl.sync_trace(local_dir, whichtrace)
256 class TapIface(TunIface):
257 _PROTO_MAP = tunproto.TAP_PROTO_MAP
260 # Yep, it does nothing - yet
261 class Internet(object):
262 def __init__(self, api=None):
264 api = plcapi.PLCAPI()
267 class NetPipe(object):
268 def __init__(self, api=None):
270 api = plcapi.PLCAPI()
286 # These get initialized when the pipe is connected to its node
288 self.configured = False
292 raise RuntimeError, "Undefined NetPipe mode"
293 if not self.portList:
294 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
295 if not (self.plrIn or self.bwIn or self.delayIn):
296 raise RuntimeError, "Undefined NetPipe inbound characteristics"
297 if not (self.plrOut or self.bwOut or self.delayOut):
298 raise RuntimeError, "Undefined NetPipe outbound characteristics"
300 raise RuntimeError, "Unconnected NetPipe"
302 def _add_pipedef(self, bw, plr, delay, options):
304 options.extend(("delay","%dms" % (delay,)))
306 options.extend(("bw","%.8fMbit/s" % (bw,)))
308 options.extend(("plr","%.8f" % (plr,)))
310 def _get_ruledef(self):
313 "@" if self.addrList else "",
318 if self.bwIn or self.plrIn or self.delayIn:
320 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
321 if self.bwOut or self.plrOut or self.delayOut:
322 options.append("OUT")
323 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
324 options = ' '.join(options)
326 return (scope,options)
330 scope, options = self._get_ruledef()
331 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
333 (out,err),proc = server.popen_ssh_command(
335 host = self.node.hostname,
337 user = self.node.slicename,
339 ident_key = self.node.ident_path,
340 server_key = self.node.server_key
344 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
346 # we have to clean up afterwards
347 self.configured = True
352 scope, options = self._get_ruledef()
353 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
355 (out,err),proc = server.popen_ssh_command(
357 host = self.node.hostname,
359 user = self.node.slicename,
361 ident_key = self.node.ident_path,
362 server_key = self.node.server_key
366 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
371 scope, options = self._get_ruledef()
372 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
374 (out,err),proc = server.popen_ssh_command(
376 host = self.node.hostname,
378 user = self.node.slicename,
380 ident_key = self.node.ident_path,
381 server_key = self.node.server_key
385 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
387 self.configured = False
389 def sync_trace(self, local_dir, whichtrace):
390 if whichtrace != 'netpipeStats':
391 raise ValueError, "Unsupported trace %s" % (whichtrace,)
393 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
395 # create parent local folders
396 proc = subprocess.Popen(
397 ["mkdir", "-p", os.path.dirname(local_path)],
398 stdout = open("/dev/null","w"),
399 stdin = open("/dev/null","r"))
402 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
404 (out,err),proc = server.popen_ssh_command(
405 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
406 host = self.node.hostname,
408 user = self.node.slicename,
410 ident_key = self.node.ident_path,
411 server_key = self.node.server_key
415 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
417 # dump results to file
418 f = open(local_path, "wb")