1 # -*- coding: utf-8 -*-
3 from constants import TESTBED_ID, TESTBED_VERSION
4 from nepi.core import metadata
5 from nepi.core.attributes import Attribute
6 from nepi.util import tags, validation
7 from nepi.util.constants import ApplicationStatus as AS, \
8 FactoryCategories as FC, DeploymentConfiguration as DC
10 from nepi.util.tunchannel_impl import \
11 preconfigure_tunchannel, postconfigure_tunchannel, \
12 prestart_tunchannel, create_tunchannel, \
13 crossconnect_tunchannel_peer_init, \
14 crossconnect_tunchannel_peer_compl
20 P2PIFACE = "P2PNodeInterface"
21 TAPIFACE = "TapNodeInterface"
22 TUNIFACE = "TunNodeInterface"
23 NODEIFACE = "NodeInterface"
25 APPLICATION = "Application"
26 TUNCHANNEL = "TunChannel"
28 NS3_TESTBED_ID = "ns3"
29 FDNETDEV = "ns3::FdNetDevice"
31 def _follow_trace(testbed_instance, guid, trace_id, filename):
32 filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
33 trace = open(filepath, "wb")
34 testbed_instance.follow_trace(guid, trace_id, trace, filename)
37 ### Connection functions ####
39 def connect_switch(testbed_instance, switch_guid, interface_guid):
40 switch = testbed_instance._elements[switch_guid]
41 interface = testbed_instance._elements[interface_guid]
42 switch.connect(interface)
44 def connect_fd(testbed_instance, tap_guid, cross_data):
47 tap = testbed_instance._elements[tap_guid]
48 address = cross_data["tun_addr"]
49 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
51 passfd.sendfd(sock, tap.fd, '0')
52 # TODO: after succesful transfer, the tap device should close the fd
54 def connect_tunchannel_tun(testbed_instance, chan_guid, tap_guid):
55 connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid, ethernet_mode=False)
57 def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid, ethernet_mode=True):
58 tap = testbed_instance._elements[tap_guid]
59 chan = testbed_instance._elements[chan_guid]
61 # Create a file object for the tap's interface device
62 # and send it to the channel. It should comply with all the
63 # requirements for the channel's tun_socket.
65 chan.tun_socket = os.fdopen(tap.fd)
67 # Set the channel to ethernet mode (it's a tap)
68 chan.ethernet_mode = ethernet_mode
70 # Check to see if the device uses PI headers
76 TUNGETIFF = 0x800454d2
77 IFF_NO_PI = 0x00001000
78 struct_ifreq = "x"*16+"H"+"x"*22
79 flags = struct.unpack(struct_ifreq,
80 fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )[0]
81 with_pi = (0 == (flags & IFF_NO_PI))
83 # maybe the kernel doesn't support the IOCTL,
84 # in which case, we assume it uses PI headers (as is usual)
86 chan.with_pi = with_pi
88 ### Trace functions ###
90 def nodepcap_trace(testbed_instance, guid, trace_id):
91 node = testbed_instance._elements[guid]
92 parameters = testbed_instance._get_parameters(guid)
93 filename = "%d-pcap.stdout" % guid
94 stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
95 filename = "%d-pcap.stderr" % guid
96 stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
97 filename = "%d-node.pcap" % guid
98 filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
99 command = "tcpdump -i 'any' -w %s" % filepath
101 trace = node.Popen(command, shell = True, stdout = stdout,
102 stderr = stderr, user = user)
103 testbed_instance.follow_trace(guid, trace_id, trace, filename)
105 trace_functions = dict({
106 "pcap": nodepcap_trace,
109 ### Creation functions ###
111 def create_node(testbed_instance, guid):
112 parameters = testbed_instance._get_parameters(guid)
114 if "forward_X11" in parameters:
115 forward_X11 = parameters["forward_X11"]
116 del parameters["forward_X11"]
117 element = testbed_instance.netns.Node(forward_X11 = forward_X11)
118 testbed_instance.elements[guid] = element
120 def create_p2piface(testbed_instance, guid):
121 if guid in testbed_instance.elements:
122 # The interface pair was already instantiated
124 # search for the node asociated with the p2piface
125 node1_guid = testbed_instance.get_connected(guid, "node", "devs")
126 if len(node1_guid) == 0:
127 raise RuntimeError("Can't instantiate interface %d outside netns \
129 node1 = testbed_instance.elements[node1_guid[0]]
130 # search for the pair p2piface
131 p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
132 if len(p2p_guid) == 0:
133 raise RuntimeError("Can't instantiate p2p interface %d. \
134 Missing interface pair" % guid)
136 node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
137 if len(node2_guid) == 0:
138 raise RuntimeError("Can't instantiate interface %d outside netns \
140 node2 = testbed_instance.elements[node2_guid[0]]
141 element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
143 testbed_instance.elements[guid] = element1
144 testbed_instance.elements[guid2] = element2
146 def create_tapiface(testbed_instance, guid):
147 node_guid = testbed_instance.get_connected(guid, "node", "devs")
148 if len(node_guid) == 0:
149 raise RuntimeError("Can't instantiate interface %d outside netns \
151 node = testbed_instance.elements[node_guid[0]]
152 element = node.add_tap()
153 testbed_instance.elements[guid] = element
155 def create_tuniface(testbed_instance, guid):
156 node_guid = testbed_instance.get_connected(guid, "node", "devs")
157 if len(node_guid) == 0:
158 raise RuntimeError("Can't instantiate interface %d outside netns \
160 node = testbed_instance.elements[node_guid[0]]
161 element = node.add_tun()
162 testbed_instance.elements[guid] = element
164 def create_nodeiface(testbed_instance, guid):
165 node_guid = testbed_instance.get_connected(guid, "node", "devs")
166 if len(node_guid) == 0:
167 raise RuntimeError("Can't instantiate interface %d outside netns \
169 node = testbed_instance.elements[node_guid[0]]
170 element = node.add_if()
171 testbed_instance.elements[guid] = element
173 def create_switch(testbed_instance, guid):
174 element = testbed_instance.netns.Switch()
175 testbed_instance.elements[guid] = element
177 def create_application(testbed_instance, guid):
178 testbed_instance.elements[guid] = None # Delayed construction
180 ### Start/Stop functions ###
182 def start_application(testbed_instance, guid):
183 parameters = testbed_instance._get_parameters(guid)
184 traces = testbed_instance._get_traces(guid)
185 command = parameters["command"]
187 if "user" in parameters:
188 user = parameters["user"]
189 stdout = stderr = None
190 if "stdout" in traces:
191 filename = "%d-stdout.trace" % guid
192 stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
193 if "stderr" in traces:
194 filename = "%d-stderr.trace" % guid
195 stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
196 node_guid = testbed_instance.get_connected(guid, "node", "apps")
197 if len(node_guid) == 0:
198 raise RuntimeError("Can't instantiate interface %d outside netns \
200 node = testbed_instance.elements[node_guid[0]]
201 element = node.Popen(command, shell = True, stdout = stdout,
202 stderr = stderr, user = user)
203 testbed_instance.elements[guid] = element
205 def stop_application(testbed_instance, guid):
206 #app = testbed_instance.elements[guid]
210 ### Status functions ###
212 def status_application(testbed_instance, guid):
213 if guid not in testbed_instance.elements.keys():
214 return AS.STATUS_NOT_STARTED
215 app = testbed_instance.elements[guid]
216 if app.poll() == None:
217 return AS.STATUS_RUNNING
218 return AS.STATUS_FINISHED
220 ### Configure functions ###
222 def configure_traces(testbed_instance, guid):
223 traces = testbed_instance._get_traces(guid)
224 for trace_id in traces:
225 if trace_id not in trace_functions:
227 trace_func = trace_functions[trace_id]
228 trace_func(testbed_instance, guid, trace_id)
230 def configure_device(testbed_instance, guid):
231 configure_traces(testbed_instance, guid)
232 element = testbed_instance._elements[guid]
233 if not guid in testbed_instance._add_address:
235 addresses = testbed_instance._add_address[guid]
236 for address in addresses:
237 (address, netprefix, broadcast) = address
238 # TODO: Decide if we should add a ipv4 or ipv6 address
239 element.add_v4_address(address, netprefix)
241 def configure_node(testbed_instance, guid):
242 configure_traces(testbed_instance, guid)
243 element = testbed_instance._elements[guid]
244 if not guid in testbed_instance._add_route:
246 routes = testbed_instance._add_route[guid]
248 (destination, netprefix, nexthop, metric) = route
249 element.add_route(prefix = destination, prefix_len = netprefix,
250 nexthop = nexthop, metric = metric)
252 ### Factory information ###
254 connector_types = dict({
256 "help": "Connector from node to applications",
262 "help": "Connector from node to network interfaces",
268 "help": "Connector to a Node",
274 "help": "Connector to a P2PInterface",
280 "help": "File descriptor receptor for devices with file descriptors",
286 "help": "File descriptor provider for devices with file descriptors",
292 "help": "Connector to a switch",
298 "help": "ip-ip tunneling over TCP link",
304 "help": "ip-ip tunneling over UDP datagrams",
313 "from": (TESTBED_ID, NODE, "devs"),
314 "to": (TESTBED_ID, P2PIFACE, "node"),
318 "from": (TESTBED_ID, NODE, "devs"),
319 "to": (TESTBED_ID, TAPIFACE, "node"),
323 "from": (TESTBED_ID, NODE, "devs"),
324 "to": (TESTBED_ID, TUNIFACE, "node"),
328 "from": (TESTBED_ID, NODE, "devs"),
329 "to": (TESTBED_ID, NODEIFACE, "node"),
333 "from": (TESTBED_ID, P2PIFACE, "p2p"),
334 "to": (TESTBED_ID, P2PIFACE, "p2p"),
338 "from": (TESTBED_ID, TAPIFACE, "fd->"),
339 "to": (None, None, "->fd"),
340 "compl_code": connect_fd,
344 "from": (TESTBED_ID, TUNIFACE, "fd->"),
345 "to": (None, None, "->fd"),
346 "compl_code": connect_fd,
350 "from": (TESTBED_ID, SWITCH, "devs"),
351 "to": (TESTBED_ID, NODEIFACE, "switch"),
352 "init_code": connect_switch,
356 "from": (TESTBED_ID, NODE, "apps"),
357 "to": (TESTBED_ID, APPLICATION, "node"),
361 "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
362 "to": (TESTBED_ID, TAPIFACE, "fd->" ),
363 "init_code": connect_tunchannel_tap,
367 "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
368 "to": (TESTBED_ID, TUNIFACE, "fd->" ),
369 "init_code": connect_tunchannel_tun,
373 "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
374 "to": (None, None, "tcp"),
375 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
376 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
380 "from": (TESTBED_ID, TUNCHANNEL, "udp"),
381 "to": (None, None, "udp"),
382 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
383 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
389 "forward_X11": dict({
390 "name": "forward_X11",
391 "help": "Forward x11 from main namespace to the node",
392 "type": Attribute.BOOL,
394 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
395 "validation_function": validation.is_bool
399 "help": "Mac address",
400 "type": Attribute.STRING,
401 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
402 "validation_function": validation.is_mac_address
407 "type": Attribute.BOOL,
409 "flags": Attribute.NoDefaultValue,
410 "validation_function": validation.is_bool
412 "device_name": dict({
414 "help": "Device name",
415 "type": Attribute.STRING,
416 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
417 "validation_function": validation.is_string
421 "help": "Maximum transmition unit for device",
422 "type": Attribute.INTEGER,
423 "validation_function": validation.is_integer
427 "help": "Broadcast address",
428 "type": Attribute.STRING,
429 "validation_function": validation.is_string # TODO: should be is address!
433 "help": "Multicast enabled",
434 "type": Attribute.BOOL,
436 "validation_function": validation.is_bool
440 "help": "ARP enabled",
441 "type": Attribute.BOOL,
443 "validation_function": validation.is_bool
447 "help": "Command line string",
448 "type": Attribute.STRING,
449 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
450 "validation_function": validation.is_string
454 "help": "System user",
455 "type": Attribute.STRING,
456 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
457 "validation_function": validation.is_string
461 "help": "Standard input",
462 "type": Attribute.STRING,
463 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
464 "validation_function": validation.is_string
471 "help": "Standard output stream"
475 "help": "Application standard error",
479 "help": "tcpdump at all node interfaces",
483 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE,
484 TUNIFACE, TUNCHANNEL, SWITCH,
487 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE,
488 TUNIFACE, TUNCHANNEL, SWITCH,
491 factories_info = dict({
493 "help": "Emulated Node with virtualized network stack",
494 "category": FC.CATEGORY_NODES,
495 "create_function": create_node,
496 "configure_function": configure_node,
497 "box_attributes": ["forward_X11"],
498 "connector_types": ["devs", "apps"],
499 "traces": ["node_pcap"],
500 "tags": [tags.NODE, tags.ALLOW_ROUTES],
503 "help": "Point to point network interface",
504 "category": FC.CATEGORY_DEVICES,
505 "create_function": create_p2piface,
506 "configure_function": configure_device,
507 "box_attributes": ["lladdr", "up", "device_name", "mtu",
508 "multicast", "broadcast", "arp"],
509 "connector_types": ["node", "p2p"],
510 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
513 "help": "Tap device network interface",
514 "category": FC.CATEGORY_DEVICES,
515 "create_function": create_tapiface,
516 "configure_function": configure_device,
517 "box_attributes": ["lladdr", "up", "device_name", "mtu",
518 "multicast", "broadcast", "arp"],
519 "connector_types": ["node", "fd->"],
520 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
523 "help": "Tun device network interface",
524 "category": FC.CATEGORY_DEVICES,
525 "create_function": create_tuniface,
526 "configure_function": configure_device,
527 "box_attributes": ["lladdr", "up", "device_name", "mtu",
528 "multicast", "broadcast", "arp"],
529 "connector_types": ["node", "fd->"],
530 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
533 "help": "Node network interface",
534 "category": FC.CATEGORY_DEVICES,
535 "create_function": create_nodeiface,
536 "configure_function": configure_device,
537 "box_attributes": ["lladdr", "up", "device_name", "mtu",
538 "multicast", "broadcast", "arp"],
539 "connector_types": ["node", "switch"],
540 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
543 "display_name": "Switch",
544 "help": "Switch interface",
545 "category": FC.CATEGORY_DEVICES,
546 "create_function": create_switch,
547 "box_attributes": ["up", "device_name", "mtu", "multicast"],
548 #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
549 #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
550 #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
551 #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
552 #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
553 "connector_types": ["devs"],
554 "tags": [tags.SWITCH],
557 "help": "Generic executable command line application",
558 "category": FC.CATEGORY_APPLICATIONS,
559 "create_function": create_application,
560 "start_function": start_application,
561 "stop_function": stop_application,
562 "status_function": status_application,
563 "box_attributes": ["command", "user"],
564 "connector_types": ["node"],
565 "traces": ["stdout", "stderr"],
566 "tags": [tags.APPLICATION],
569 "category": FC.CATEGORY_TUNNELS,
570 "create_function": create_tunchannel,
571 "preconfigure_function": preconfigure_tunchannel,
572 "configure_function": postconfigure_tunchannel,
573 "prestart_function": prestart_tunchannel,
574 "help": "Channel to forward "+TAPIFACE+" data to "
575 "other TAP interfaces supporting the NEPI tunneling protocol.",
576 "connector_types": ["->fd", "udp", "tcp"],
577 "allow_addresses": False,
578 "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher"],
579 "tags": [tags.TUNNEL],
583 testbed_attributes = dict({
584 "enable_debug": dict({
585 "name": "enableDebug",
586 "help": "Enable netns debug output",
587 "type": Attribute.BOOL,
589 "validation_function": validation.is_bool
593 supported_recovery_policies = [
597 class MetadataInfo(metadata.MetadataInfo):
599 def connector_types(self):
600 return connector_types
603 def connections(self):
607 def attributes(self):
615 def create_order(self):
619 def configure_order(self):
620 return configure_order
623 def factories_info(self):
624 return factories_info
627 def testbed_attributes(self):
628 return testbed_attributes
631 def testbed_id(self):
635 def testbed_version(self):
636 return TESTBED_VERSION
639 def supported_recover_policies(self):
640 return supported_recovery_policies