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
38 # Generate an initial random cryptographic key to use for tunnelling
39 # Upon connection, both endpoints will agree on a common one based on
41 self.tun_key = ( ''.join(map(chr, [
44 for r in (random.SystemRandom(),) ])
45 ).encode("base64").strip() )
48 return "%s<ip:%s/%s up mac:%s>" % (
49 self.__class__.__name__,
50 self.address, self.netmask,
54 def add_address(self, address, netprefix, broadcast):
55 raise RuntimeError, "Cannot add explicit addresses to public interface"
57 def pick_iface(self, siblings):
59 Picks an interface using the PLCAPI to query information about the node.
61 Needs an assigned node.
64 siblings: other NodeIface elements attached to the same node
67 if self.node is None or self.node._node_id is None:
68 raise RuntimeError, "Cannot pick interface without an assigned node"
70 avail = self._api.GetInterfaces(
71 node_id=self.node._node_id,
72 is_primary=self.primary,
73 fields=('interface_id','mac','netmask','ip') )
75 used = set([sibling._interface_id for sibling in siblings
76 if sibling._interface_id is not None])
78 for candidate in avail:
79 candidate_id = candidate['interface_id']
80 if candidate_id not in used:
82 self._interface_id = candidate_id
83 self.address = candidate['ip']
84 self.lladdr = candidate['mac']
85 self.netprefix = candidate['netmask']
86 self.netmask = ipaddr2.ipv4_dot2mask(self.netprefix) if self.netprefix else None
89 raise RuntimeError, "Cannot configure interface: cannot find suitable interface in PlanetLab node"
92 if not self.has_internet:
93 raise RuntimeError, "All external interface devices must be connected to the Internet"
96 class _CrossIface(object):
97 def __init__(self, proto, addr, port):
98 self.tun_proto = proto
102 class TunIface(object):
103 _PROTO_MAP = tunproto.TUN_PROTO_MAP
106 def __init__(self, api=None):
108 api = plcapi.PLCAPI()
113 self.netprefix = None
117 self.device_name = None
120 self.txqueuelen = None
125 # These get initialized when the iface is connected to its node
128 # These get initialized when the iface is configured
129 self.external_iface = None
131 # These get initialized when the iface is configured
132 # They're part of the TUN standard attribute set
137 # These get initialized when the iface is connected to its peer
138 self.peer_iface = None
139 self.peer_proto = None
140 self.peer_proto_impl = None
142 # same as peer proto, but for execute-time standard attribute lookups
143 self.tun_proto = None
146 return "%s<ip:%s/%s %s%s>" % (
147 self.__class__.__name__,
148 self.address, self.netprefix,
149 " up" if self.up else " down",
150 " snat" if self.snat else "",
153 def add_address(self, address, netprefix, broadcast):
154 if (self.address or self.netprefix or self.netmask) is not None:
155 raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
157 raise ValueError, "%s interfaces cannot broadcast in PlanetLab" % (self._KIND,)
159 self.address = address
160 self.netprefix = netprefix
161 self.netmask = ipaddr2.ipv4_mask2dot(netprefix)
165 raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
166 if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
167 raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
168 if not self.address or not self.netprefix or not self.netmask:
169 raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
171 def prepare(self, home_path, listening):
172 if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
174 self.peer_iface = CrossIface(
179 if not self.peer_proto_impl:
180 self.peer_proto_impl = self._PROTO_MAP[self.peer_proto](
181 self, self.peer_iface, home_path, self.tun_key, listening)
182 self.peer_proto_impl.port = self.tun_port
183 self.peer_proto_impl.prepare()
186 if self.peer_proto_impl:
187 self.peer_proto_impl.setup()
190 if self.peer_proto_impl:
191 self.peer_proto_impl.shutdown()
192 self.peer_proto_impl = None
194 def sync_trace(self, local_dir, whichtrace):
195 if self.peer_proto_impl:
196 return self.peer_proto_impl.sync_trace(local_dir, whichtrace)
200 class TapIface(TunIface):
201 _PROTO_MAP = tunproto.TAP_PROTO_MAP
204 # Yep, it does nothing - yet
205 class Internet(object):
206 def __init__(self, api=None):
208 api = plcapi.PLCAPI()
211 class NetPipe(object):
212 def __init__(self, api=None):
214 api = plcapi.PLCAPI()
230 # These get initialized when the pipe is connected to its node
232 self.configured = False
236 raise RuntimeError, "Undefined NetPipe mode"
237 if not self.portList:
238 raise RuntimeError, "Undefined NetPipe port list - must always define the scope"
239 if not (self.plrIn or self.bwIn or self.delayIn):
240 raise RuntimeError, "Undefined NetPipe inbound characteristics"
241 if not (self.plrOut or self.bwOut or self.delayOut):
242 raise RuntimeError, "Undefined NetPipe outbound characteristics"
244 raise RuntimeError, "Unconnected NetPipe"
246 def _add_pipedef(self, bw, plr, delay, options):
248 options.extend(("delay","%dms" % (delay,)))
250 options.extend(("bw","%.8fMbit/s" % (bw,)))
252 options.extend(("plr","%.8f" % (plr,)))
254 def _get_ruledef(self):
257 "@" if self.addrList else "",
262 if self.bwIn or self.plrIn or self.delayIn:
264 self._add_pipedef(self.bwIn, self.plrIn, self.delayIn, options)
265 if self.bwOut or self.plrOut or self.delayOut:
266 options.append("OUT")
267 self._add_pipedef(self.bwOut, self.plrOut, self.delayOut, options)
268 options = ' '.join(options)
270 return (scope,options)
274 scope, options = self._get_ruledef()
275 command = "sudo -S netconfig config %s %s %s" % (self.mode, scope, options)
277 (out,err),proc = server.popen_ssh_command(
279 host = self.node.hostname,
281 user = self.node.slicename,
283 ident_key = self.node.ident_path,
284 server_key = self.node.server_key
288 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
290 # we have to clean up afterwards
291 self.configured = True
296 scope, options = self._get_ruledef()
297 command = "sudo -S netconfig refresh %s %s %s" % (self.mode, scope, options)
299 (out,err),proc = server.popen_ssh_command(
301 host = self.node.hostname,
303 user = self.node.slicename,
305 ident_key = self.node.ident_path,
306 server_key = self.node.server_key
310 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
315 scope, options = self._get_ruledef()
316 command = "sudo -S netconfig delete %s %s" % (self.mode, scope)
318 (out,err),proc = server.popen_ssh_command(
320 host = self.node.hostname,
322 user = self.node.slicename,
324 ident_key = self.node.ident_path,
325 server_key = self.node.server_key
329 raise RuntimeError, "Failed instal build sources: %s %s" % (out,err,)
331 self.configured = False
333 def sync_trace(self, local_dir, whichtrace):
334 if whichtrace != 'netpipeStats':
335 raise ValueError, "Unsupported trace %s" % (whichtrace,)
337 local_path = os.path.join(local_dir, "netpipe_stats_%s" % (self.mode,))
339 # create parent local folders
340 proc = subprocess.Popen(
341 ["mkdir", "-p", os.path.dirname(local_path)],
342 stdout = open("/dev/null","w"),
343 stdin = open("/dev/null","r"))
346 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
348 (out,err),proc = server.popen_ssh_command(
349 "echo 'Rules:' ; sudo -S netconfig show rules ; echo 'Pipes:' ; sudo -S netconfig show pipes",
350 host = self.node.hostname,
352 user = self.node.slicename,
354 ident_key = self.node.ident_path,
355 server_key = self.node.server_key
359 raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
361 # dump results to file
362 f = open(local_path, "wb")