2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
5 import nepi.util.ipaddr2 as ipaddr2
6 import nepi.util.server as server
15 class NodeIface(object):
16 def __init__(self, api=None):
24 # These get initialized at configuration time
30 self._interface_id = None
32 # These get initialized when the iface is connected to its node
35 # These get initialized when the iface is connected to the internet
36 self.has_internet = False
39 return "%s<ip:%s/%s up mac:%s>" % (
40 self.__class__.__name__,
41 self.address, self.netmask,
45 def add_address(self, address, netprefix, broadcast):
46 raise RuntimeError, "Cannot add explicit addresses to public interface"
48 def pick_iface(self, siblings):
50 Picks an interface using the PLCAPI to query information about the node.
52 Needs an assigned node.
55 siblings: other NodeIface elements attached to the same node
58 if self.node is None or self.node._node_id is None:
59 raise RuntimeError, "Cannot pick interface without an assigned node"
61 avail = self._api.GetInterfaces(
62 node_id=self.node._node_id,
63 is_primary=self.primary,
64 fields=('interface_id','mac','netmask','ip') )
66 used = set([sibling._interface_id for sibling in siblings
67 if sibling._interface_id is not None])
69 for candidate in avail:
70 candidate_id = candidate['interface_id']
71 if candidate_id not in used:
73 self._interface_id = candidate_id
74 self.address = candidate['ip']
75 self.lladdr = candidate['mac']
76 self.netprefix = candidate['netmask']
77 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
80 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
83 if not self.has_internet:
84 raise RuntimeError, "All external interface devices must be connected to the Internet"
87 class _CrossIface(object):
88 def __init__(self, proto, addr, port):
89 self.tun_proto = proto
93 # Cannot access cross peers
94 self.peer_proto_impl = None
98 self.__class__.__name__,
104 class TunIface(object):
105 _PROTO_MAP = tunproto.TUN_PROTO_MAP
108 def __init__(self, api=None):
110 api = plcapi.PLCAPI()
115 self.netprefix = None
119 self.device_name = None
122 self.txqueuelen = None
123 self.pointopoint = None
128 # These get initialized when the iface is connected to its node
131 # These get initialized when the iface is configured
132 self.external_iface = None
134 # These get initialized when the iface is configured
135 # They're part of the TUN standard attribute set
139 # These get initialized when the iface is connected to its peer
140 self.peer_iface = None
141 self.peer_proto = None
142 self.peer_addr = None
143 self.peer_port = None
144 self.peer_proto_impl = None
146 # same as peer proto, but for execute-time standard attribute lookups
147 self.tun_proto = None
150 # Generate an initial random cryptographic key to use for tunnelling
151 # Upon connection, both endpoints will agree on a common one based on
153 self.tun_key = ( ''.join(map(chr, [
156 for r in (random.SystemRandom(),) ])
157 ).encode("base64").strip() )
161 return "%s<ip:%s/%s %s%s>" % (
162 self.__class__.__name__,
163 self.address, self.netprefix,
164 " up" if self.up else " down",
165 " snat" if self.snat else "",
168 def add_address(self, address, netprefix, broadcast):
169 if (self.address or self.netprefix or self.netmask) is not None:
170 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
172 raise ValueError, "%s interfaces cannot broadcast in PlanetLab" % (self._KIND,)
174 self.address = address
175 self.netprefix = netprefix
176 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
180 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
181 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
182 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
183 if not self.address or not self.netprefix or not self.netmask:
184 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
186 def _impl_instance(self, home_path, listening):
187 impl = self._PROTO_MAP[self.peer_proto](
188 self, self.peer_iface, home_path, self.tun_key, listening)
189 impl.port = self.tun_port
192 def prepare(self, home_path, listening):
193 if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
195 self.peer_iface = _CrossIface(
200 if not self.peer_proto_impl:
201 self.peer_proto_impl = self._impl_instance(home_path, listening)
202 self.peer_proto_impl.prepare()
205 if self.peer_proto_impl:
206 self.peer_proto_impl.setup()
209 if self.peer_proto_impl:
210 self.peer_proto_impl.shutdown()
211 self.peer_proto_impl = None
213 def async_launch_wait(self):
214 if self.peer_proto_impl:
215 self.peer_proto_impl.async_launch_wait()
217 def sync_trace(self, local_dir, whichtrace):
218 if self.peer_proto_impl:
219 return self.peer_proto_impl.sync_trace(local_dir, whichtrace)
223 class TapIface(TunIface):
224 _PROTO_MAP = tunproto.TAP_PROTO_MAP
227 # Yep, it does nothing - yet
228 class Internet(object):
229 def __init__(self, api=None):
231 api = plcapi.PLCAPI()
234 class NetPipe(object):
235 def __init__(self, api=None):
237 api = plcapi.PLCAPI()
253 # These get initialized when the pipe is connected to its node
255 self.configured = False
259 raise RuntimeError, "Undefined NetPipe mode"
260 if not self.portList:
261 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
262 if not (self.plrIn or self.bwIn or self.delayIn):
263 raise RuntimeError, "Undefined NetPipe inbound characteristics"
264 if not (self.plrOut or self.bwOut or self.delayOut):
265 raise RuntimeError, "Undefined NetPipe outbound characteristics"
267 raise RuntimeError, "Unconnected NetPipe"
269 def _add_pipedef(self, bw, plr, delay, options):
271 options.extend(("delay","%dms" % (delay,)))
273 options.extend(("bw","%.8fMbit/s" % (bw,)))
275 options.extend(("plr","%.8f" % (plr,)))
277 def _get_ruledef(self):
280 "@" if self.addrList else "",
285 if self.bwIn or self.plrIn or self.delayIn:
287 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
288 if self.bwOut or self.plrOut or self.delayOut:
289 options.append("OUT")
290 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
291 options = ' '.join(options)
293 return (scope,options)
297 scope, options = self._get_ruledef()
298 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
300 (out,err),proc = server.popen_ssh_command(
302 host = self.node.hostname,
304 user = self.node.slicename,
306 ident_key = self.node.ident_path,
307 server_key = self.node.server_key
311 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
313 # we have to clean up afterwards
314 self.configured = True
319 scope, options = self._get_ruledef()
320 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
322 (out,err),proc = server.popen_ssh_command(
324 host = self.node.hostname,
326 user = self.node.slicename,
328 ident_key = self.node.ident_path,
329 server_key = self.node.server_key
333 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
338 scope, options = self._get_ruledef()
339 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
341 (out,err),proc = server.popen_ssh_command(
343 host = self.node.hostname,
345 user = self.node.slicename,
347 ident_key = self.node.ident_path,
348 server_key = self.node.server_key
352 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
354 self.configured = False
356 def sync_trace(self, local_dir, whichtrace):
357 if whichtrace != 'netpipeStats':
358 raise ValueError, "Unsupported trace %s" % (whichtrace,)
360 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
362 # create parent local folders
363 proc = subprocess.Popen(
364 ["mkdir", "-p", os.path.dirname(local_path)],
365 stdout = open("/dev/null","w"),
366 stdin = open("/dev/null","r"))
369 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
371 (out,err),proc = server.popen_ssh_command(
372 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
373 host = self.node.hostname,
375 user = self.node.slicename,
377 ident_key = self.node.ident_path,
378 server_key = self.node.server_key
382 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
384 # dump results to file
385 f = open(local_path, "wb")