2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
5 from nepi.core import metadata
6 from nepi.core.attributes import Attribute
7 from nepi.util import tags, validation
8 from nepi.util.constants import ApplicationStatus as AS, \
9 FactoryCategories as FC
11 from nepi.util.tunchannel_impl import \
12 preconfigure_tunchannel, postconfigure_tunchannel, \
13 wait_tunchannel, create_tunchannel, \
14 crossconnect_tunchannel_peer_init, \
15 crossconnect_tunchannel_peer_compl
21 P2PIFACE = "P2PNodeInterface"
22 TAPIFACE = "TapNodeInterface"
23 NODEIFACE = "NodeInterface"
25 APPLICATION = "Application"
26 TUNCHANNEL = "TunChannel"
28 NS3_TESTBED_ID = "ns3"
29 FDNETDEV = "ns3::FileDescriptorNetDevice"
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_tap(testbed_instance, chan_guid, tap_guid):
55 tap = testbed_instance._elements[tap_guid]
56 chan = testbed_instance._elements[chan_guid]
58 # Create a file object for the tap's interface device
59 # and send it to the channel. It should comply with all the
60 # requirements for the channel's tun_socket.
62 chan.tun_socket = os.fdopen(tap.fd)
64 # Set the channel to ethernet mode (it's a tap)
65 chan.ethernet_mode = True
67 # Check to see if the device uses PI headers
73 TUNGETIFF = 0x800454d2
74 IFF_NO_PI = 0x00001000
75 struct_ifreq = "x"*16+"H"+"x"*22
76 flags = struct.unpack(struct_ifreq,
77 fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )
78 with_pi = (0 == (flags & IFF_NO_PI))
80 # maybe the kernel doesn't support the IOCTL,
81 # in which case, we assume it uses PI headers (as is usual)
83 chan.with_pi = with_pi
85 ### Trace functions ###
87 def nodepcap_trace(testbed_instance, guid, trace_id):
88 node = testbed_instance._elements[guid]
89 parameters = testbed_instance._get_parameters(guid)
90 filename = "%d-pcap.stdout" % guid
91 stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
92 filename = "%d-pcap.stderr" % guid
93 stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
94 filename = "%d-node.pcap" % guid
95 filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
96 command = "tcpdump -i 'any' -w %s" % filepath
98 trace = node.Popen(command, shell = True, stdout = stdout,
99 stderr = stderr, user = user)
100 testbed_instance.follow_trace(guid, trace_id, trace, filename)
102 trace_functions = dict({
103 "pcap": nodepcap_trace,
106 ### Creation functions ###
108 def create_node(testbed_instance, guid):
109 parameters = testbed_instance._get_parameters(guid)
111 if "forward_X11" in parameters:
112 forward_X11 = parameters["forward_X11"]
113 del parameters["forward_X11"]
114 element = testbed_instance.netns.Node(forward_X11 = forward_X11)
115 testbed_instance.elements[guid] = element
117 def create_p2piface(testbed_instance, guid):
118 if guid in testbed_instance.elements:
119 # The interface pair was already instantiated
121 # search for the node asociated with the p2piface
122 node1_guid = testbed_instance.get_connected(guid, "node", "devs")
123 if len(node1_guid) == 0:
124 raise RuntimeError("Can't instantiate interface %d outside netns \
126 node1 = testbed_instance.elements[node1_guid[0]]
127 # search for the pair p2piface
128 p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
129 if len(p2p_guid) == 0:
130 raise RuntimeError("Can't instantiate p2p interface %d. \
131 Missing interface pair" % guid)
133 node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
134 if len(node2_guid) == 0:
135 raise RuntimeError("Can't instantiate interface %d outside netns \
137 node2 = testbed_instance.elements[node2_guid[0]]
138 element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
140 testbed_instance.elements[guid] = element1
141 testbed_instance.elements[guid2] = element2
143 def create_tapiface(testbed_instance, guid):
144 node_guid = testbed_instance.get_connected(guid, "node", "devs")
145 if len(node_guid) == 0:
146 raise RuntimeError("Can't instantiate interface %d outside netns \
148 node = testbed_instance.elements[node_guid[0]]
149 element = node.add_tap()
150 testbed_instance.elements[guid] = element
152 def create_nodeiface(testbed_instance, guid):
153 node_guid = testbed_instance.get_connected(guid, "node", "devs")
154 if len(node_guid) == 0:
155 raise RuntimeError("Can't instantiate interface %d outside netns \
157 node = testbed_instance.elements[node_guid[0]]
158 element = node.add_if()
159 testbed_instance.elements[guid] = element
161 def create_switch(testbed_instance, guid):
162 element = testbed_instance.netns.Switch()
163 testbed_instance.elements[guid] = element
165 def create_application(testbed_instance, guid):
166 testbed_instance.elements[guid] = None # Delayed construction
168 ### Start/Stop functions ###
170 def start_application(testbed_instance, guid):
171 parameters = testbed_instance._get_parameters(guid)
172 traces = testbed_instance._get_traces(guid)
173 command = parameters["command"]
175 if "user" in parameters:
176 user = parameters["user"]
177 stdout = stderr = None
178 if "stdout" in traces:
179 filename = "%d-stdout.trace" % guid
180 stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
181 if "stderr" in traces:
182 filename = "%d-stderr.trace" % guid
183 stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
184 node_guid = testbed_instance.get_connected(guid, "node", "apps")
185 if len(node_guid) == 0:
186 raise RuntimeError("Can't instantiate interface %d outside netns \
188 node = testbed_instance.elements[node_guid[0]]
189 element = node.Popen(command, shell = True, stdout = stdout,
190 stderr = stderr, user = user)
191 testbed_instance.elements[guid] = element
193 def stop_application(testbed_instance, guid):
194 #app = testbed_instance.elements[guid]
198 ### Status functions ###
200 def status_application(testbed_instance, guid):
201 if guid not in testbed_instance.elements.keys():
202 return AS.STATUS_NOT_STARTED
203 app = testbed_instance.elements[guid]
204 if app.poll() == None:
205 return AS.STATUS_RUNNING
206 return AS.STATUS_FINISHED
208 ### Configure functions ###
210 def configure_traces(testbed_instance, guid):
211 traces = testbed_instance._get_traces(guid)
212 for trace_id in traces:
213 if trace_id not in trace_functions:
215 trace_func = trace_functions[trace_id]
216 trace_func(testbed_instance, guid, trace_id)
218 def configure_device(testbed_instance, guid):
219 configure_traces(testbed_instance, guid)
220 element = testbed_instance._elements[guid]
221 if not guid in testbed_instance._add_address:
223 addresses = testbed_instance._add_address[guid]
224 for address in addresses:
225 (address, netprefix, broadcast) = address
226 # TODO: Decide if we should add a ipv4 or ipv6 address
227 element.add_v4_address(address, netprefix)
229 def configure_node(testbed_instance, guid):
230 configure_traces(testbed_instance, guid)
231 element = testbed_instance._elements[guid]
232 if not guid in testbed_instance._add_route:
234 routes = testbed_instance._add_route[guid]
236 (destination, netprefix, nexthop, metric) = route
237 element.add_route(prefix = destination, prefix_len = netprefix,
238 nexthop = nexthop, metric = metric)
240 ### Factory information ###
242 connector_types = dict({
244 "help": "Connector from node to applications",
250 "help": "Connector from node to network interfaces",
256 "help": "Connector to a Node",
262 "help": "Connector to a P2PInterface",
268 "help": "File descriptor receptor for devices with file descriptors",
274 "help": "File descriptor provider for devices with file descriptors",
280 "help": "Connector to a switch",
286 "help": "ip-ip tunneling over TCP link",
292 "help": "ip-ip tunneling over UDP datagrams",
301 "from": (TESTBED_ID, NODE, "devs"),
302 "to": (TESTBED_ID, P2PIFACE, "node"),
306 "from": (TESTBED_ID, NODE, "devs"),
307 "to": (TESTBED_ID, TAPIFACE, "node"),
311 "from": (TESTBED_ID, NODE, "devs"),
312 "to": (TESTBED_ID, NODEIFACE, "node"),
316 "from": (TESTBED_ID, P2PIFACE, "p2p"),
317 "to": (TESTBED_ID, P2PIFACE, "p2p"),
321 "from": (TESTBED_ID, TAPIFACE, "fd->"),
322 "to": (None, None, "->fd"),
323 "compl_code": connect_fd,
327 "from": (TESTBED_ID, SWITCH, "devs"),
328 "to": (TESTBED_ID, NODEIFACE, "switch"),
329 "init_code": connect_switch,
333 "from": (TESTBED_ID, NODE, "apps"),
334 "to": (TESTBED_ID, APPLICATION, "node"),
338 "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
339 "to": (TESTBED_ID, TAPIFACE, "fd->" ),
340 "init_code": connect_tunchannel_tap,
344 "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
345 "to": (None, None, "tcp"),
346 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
347 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
351 "from": (TESTBED_ID, TUNCHANNEL, "udp"),
352 "to": (None, None, "udp"),
353 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
354 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
360 "forward_X11": dict({
361 "name": "forward_X11",
362 "help": "Forward x11 from main namespace to the node",
363 "type": Attribute.BOOL,
365 "flags": Attribute.DesignOnly,
366 "validation_function": validation.is_bool
370 "help": "Mac address",
371 "type": Attribute.STRING,
372 "flags": Attribute.DesignOnly,
373 "validation_function": validation.is_mac_address
378 "type": Attribute.BOOL,
380 "validation_function": validation.is_bool
382 "device_name": dict({
384 "help": "Device name",
385 "type": Attribute.STRING,
386 "flags": Attribute.DesignOnly,
387 "validation_function": validation.is_string
391 "help": "Maximum transmition unit for device",
392 "type": Attribute.INTEGER,
393 "validation_function": validation.is_integer
397 "help": "Broadcast address",
398 "type": Attribute.STRING,
399 "validation_function": validation.is_string # TODO: should be is address!
403 "help": "Multicast enabled",
404 "type": Attribute.BOOL,
406 "validation_function": validation.is_bool
410 "help": "ARP enabled",
411 "type": Attribute.BOOL,
413 "validation_function": validation.is_bool
417 "help": "Command line string",
418 "type": Attribute.STRING,
419 "flags": Attribute.DesignOnly,
420 "validation_function": validation.is_string
424 "help": "System user",
425 "type": Attribute.STRING,
426 "flags": Attribute.DesignOnly,
427 "validation_function": validation.is_string
431 "help": "Standard input",
432 "type": Attribute.STRING,
433 "flags": Attribute.DesignOnly,
434 "validation_function": validation.is_string
441 "help": "Standard output stream"
445 "help": "Application standard error",
449 "help": "tcpdump at all node interfaces",
453 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE,
457 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE,
461 factories_info = dict({
463 "help": "Emulated Node with virtualized network stack",
464 "category": FC.CATEGORY_NODES,
465 "create_function": create_node,
466 "configure_function": configure_node,
467 "box_attributes": ["forward_X11"],
468 "connector_types": ["devs", "apps"],
469 "traces": ["node_pcap"],
470 "tags": [tags.NODE, tags.ALLOW_ROUTES],
473 "help": "Point to point network interface",
474 "category": FC.CATEGORY_DEVICES,
475 "create_function": create_p2piface,
476 "configure_function": configure_device,
477 "box_attributes": ["lladdr", "up", "device_name", "mtu",
478 "multicast", "broadcast", "arp"],
479 "connector_types": ["node", "p2p"],
480 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
483 "help": "Tap device network interface",
484 "category": FC.CATEGORY_DEVICES,
485 "create_function": create_tapiface,
486 "configure_function": configure_device,
487 "box_attributes": ["lladdr", "up", "device_name", "mtu",
488 "multicast", "broadcast", "arp"],
489 "connector_types": ["node", "fd->"],
490 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
493 "help": "Node network interface",
494 "category": FC.CATEGORY_DEVICES,
495 "create_function": create_nodeiface,
496 "configure_function": configure_device,
497 "box_attributes": ["lladdr", "up", "device_name", "mtu",
498 "multicast", "broadcast", "arp"],
499 "connector_types": ["node", "switch"],
500 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
503 "display_name": "Switch",
504 "help": "Switch interface",
505 "category": FC.CATEGORY_DEVICES,
506 "create_function": create_switch,
507 "box_attributes": ["up", "device_name", "mtu", "multicast"],
508 #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
509 #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
510 #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
511 #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
512 #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
513 "connector_types": ["devs"],
514 "tags": [tags.SWITCH],
517 "help": "Generic executable command line application",
518 "category": FC.CATEGORY_APPLICATIONS,
519 "create_function": create_application,
520 "start_function": start_application,
521 "stop_function": stop_application,
522 "status_function": status_application,
523 "box_attributes": ["command", "user"],
524 "connector_types": ["node"],
525 "traces": ["stdout", "stderr"],
526 "tags": [tags.APPLICATION],
529 "category": FC.CATEGORY_TUNNELS,
530 "create_function": create_tunchannel,
531 "preconfigure_function": preconfigure_tunchannel,
532 "configure_function": postconfigure_tunchannel,
533 "prestart_function": wait_tunchannel,
534 "help": "Channel to forward "+TAPIFACE+" data to "
535 "other TAP interfaces supporting the NEPI tunneling protocol.",
536 "connector_types": ["->fd", "udp", "tcp"],
537 "allow_addresses": False,
538 "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"],
539 "tags": [tags.TUNNEL],
543 testbed_attributes = dict({
544 "enable_debug": dict({
545 "name": "enableDebug",
546 "help": "Enable netns debug output",
547 "type": Attribute.BOOL,
549 "validation_function": validation.is_bool
553 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
555 def connector_types(self):
556 return connector_types
559 def connections(self):
563 def attributes(self):
571 def create_order(self):
575 def configure_order(self):
576 return configure_order
579 def factories_info(self):
580 return factories_info
583 def testbed_attributes(self):
584 return testbed_attributes