2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID, TESTBED_VERSION
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, DeploymentConfiguration as DC
11 from nepi.util.tunchannel_impl import \
12 preconfigure_tunchannel, postconfigure_tunchannel, \
13 prestart_tunchannel, create_tunchannel, \
14 crossconnect_tunchannel_peer_init, \
15 crossconnect_tunchannel_peer_compl
21 P2PIFACE = "P2PNodeInterface"
22 TAPIFACE = "TapNodeInterface"
23 TUNIFACE = "TunNodeInterface"
24 NODEIFACE = "NodeInterface"
26 APPLICATION = "Application"
27 TUNCHANNEL = "TunChannel"
29 NS3_TESTBED_ID = "ns3"
30 FDNETDEV = "ns3::FdNetDevice"
32 def _follow_trace(testbed_instance, guid, trace_id, filename):
33 filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
34 trace = open(filepath, "wb")
35 testbed_instance.follow_trace(guid, trace_id, trace, filename)
38 ### Connection functions ####
40 def connect_switch(testbed_instance, switch_guid, interface_guid):
41 switch = testbed_instance._elements[switch_guid]
42 interface = testbed_instance._elements[interface_guid]
43 switch.connect(interface)
45 def connect_fd(testbed_instance, tap_guid, cross_data):
48 tap = testbed_instance._elements[tap_guid]
49 address = cross_data["tun_addr"]
50 sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
52 passfd.sendfd(sock, tap.fd, '0')
53 # TODO: after succesful transfer, the tap device should close the fd
55 def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid):
56 tap = testbed_instance._elements[tap_guid]
57 chan = testbed_instance._elements[chan_guid]
59 # Create a file object for the tap's interface device
60 # and send it to the channel. It should comply with all the
61 # requirements for the channel's tun_socket.
63 chan.tun_socket = os.fdopen(tap.fd)
65 # Set the channel to ethernet mode (it's a tap)
66 chan.ethernet_mode = True
68 # Check to see if the device uses PI headers
74 TUNGETIFF = 0x800454d2
75 IFF_NO_PI = 0x00001000
76 struct_ifreq = "x"*16+"H"+"x"*22
77 flags = struct.unpack(struct_ifreq,
78 fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )
79 with_pi = (0 == (flags & IFF_NO_PI))
81 # maybe the kernel doesn't support the IOCTL,
82 # in which case, we assume it uses PI headers (as is usual)
84 chan.with_pi = with_pi
86 ### Trace functions ###
88 def nodepcap_trace(testbed_instance, guid, trace_id):
89 node = testbed_instance._elements[guid]
90 parameters = testbed_instance._get_parameters(guid)
91 filename = "%d-pcap.stdout" % guid
92 stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
93 filename = "%d-pcap.stderr" % guid
94 stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
95 filename = "%d-node.pcap" % guid
96 filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
97 command = "tcpdump -i 'any' -w %s" % filepath
99 trace = node.Popen(command, shell = True, stdout = stdout,
100 stderr = stderr, user = user)
101 testbed_instance.follow_trace(guid, trace_id, trace, filename)
103 trace_functions = dict({
104 "pcap": nodepcap_trace,
107 ### Creation functions ###
109 def create_node(testbed_instance, guid):
110 parameters = testbed_instance._get_parameters(guid)
112 if "forward_X11" in parameters:
113 forward_X11 = parameters["forward_X11"]
114 del parameters["forward_X11"]
115 element = testbed_instance.netns.Node(forward_X11 = forward_X11)
116 testbed_instance.elements[guid] = element
118 def create_p2piface(testbed_instance, guid):
119 if guid in testbed_instance.elements:
120 # The interface pair was already instantiated
122 # search for the node asociated with the p2piface
123 node1_guid = testbed_instance.get_connected(guid, "node", "devs")
124 if len(node1_guid) == 0:
125 raise RuntimeError("Can't instantiate interface %d outside netns \
127 node1 = testbed_instance.elements[node1_guid[0]]
128 # search for the pair p2piface
129 p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
130 if len(p2p_guid) == 0:
131 raise RuntimeError("Can't instantiate p2p interface %d. \
132 Missing interface pair" % guid)
134 node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
135 if len(node2_guid) == 0:
136 raise RuntimeError("Can't instantiate interface %d outside netns \
138 node2 = testbed_instance.elements[node2_guid[0]]
139 element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
141 testbed_instance.elements[guid] = element1
142 testbed_instance.elements[guid2] = element2
144 def create_tapiface(testbed_instance, guid):
145 node_guid = testbed_instance.get_connected(guid, "node", "devs")
146 if len(node_guid) == 0:
147 raise RuntimeError("Can't instantiate interface %d outside netns \
149 node = testbed_instance.elements[node_guid[0]]
150 element = node.add_tap()
151 testbed_instance.elements[guid] = element
153 def create_tuniface(testbed_instance, guid):
154 node_guid = testbed_instance.get_connected(guid, "node", "devs")
155 if len(node_guid) == 0:
156 raise RuntimeError("Can't instantiate interface %d outside netns \
158 node = testbed_instance.elements[node_guid[0]]
159 element = node.add_tun()
160 testbed_instance.elements[guid] = element
162 def create_nodeiface(testbed_instance, guid):
163 node_guid = testbed_instance.get_connected(guid, "node", "devs")
164 if len(node_guid) == 0:
165 raise RuntimeError("Can't instantiate interface %d outside netns \
167 node = testbed_instance.elements[node_guid[0]]
168 element = node.add_if()
169 testbed_instance.elements[guid] = element
171 def create_switch(testbed_instance, guid):
172 element = testbed_instance.netns.Switch()
173 testbed_instance.elements[guid] = element
175 def create_application(testbed_instance, guid):
176 testbed_instance.elements[guid] = None # Delayed construction
178 ### Start/Stop functions ###
180 def start_application(testbed_instance, guid):
181 parameters = testbed_instance._get_parameters(guid)
182 traces = testbed_instance._get_traces(guid)
183 command = parameters["command"]
185 if "user" in parameters:
186 user = parameters["user"]
187 stdout = stderr = None
188 if "stdout" in traces:
189 filename = "%d-stdout.trace" % guid
190 stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
191 if "stderr" in traces:
192 filename = "%d-stderr.trace" % guid
193 stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
194 node_guid = testbed_instance.get_connected(guid, "node", "apps")
195 if len(node_guid) == 0:
196 raise RuntimeError("Can't instantiate interface %d outside netns \
198 node = testbed_instance.elements[node_guid[0]]
199 element = node.Popen(command, shell = True, stdout = stdout,
200 stderr = stderr, user = user)
201 testbed_instance.elements[guid] = element
203 def stop_application(testbed_instance, guid):
204 #app = testbed_instance.elements[guid]
208 ### Status functions ###
210 def status_application(testbed_instance, guid):
211 if guid not in testbed_instance.elements.keys():
212 return AS.STATUS_NOT_STARTED
213 app = testbed_instance.elements[guid]
214 if app.poll() == None:
215 return AS.STATUS_RUNNING
216 return AS.STATUS_FINISHED
218 ### Configure functions ###
220 def configure_traces(testbed_instance, guid):
221 traces = testbed_instance._get_traces(guid)
222 for trace_id in traces:
223 if trace_id not in trace_functions:
225 trace_func = trace_functions[trace_id]
226 trace_func(testbed_instance, guid, trace_id)
228 def configure_device(testbed_instance, guid):
229 configure_traces(testbed_instance, guid)
230 element = testbed_instance._elements[guid]
231 if not guid in testbed_instance._add_address:
233 addresses = testbed_instance._add_address[guid]
234 for address in addresses:
235 (address, netprefix, broadcast) = address
236 # TODO: Decide if we should add a ipv4 or ipv6 address
237 element.add_v4_address(address, netprefix)
239 def configure_node(testbed_instance, guid):
240 configure_traces(testbed_instance, guid)
241 element = testbed_instance._elements[guid]
242 if not guid in testbed_instance._add_route:
244 routes = testbed_instance._add_route[guid]
246 (destination, netprefix, nexthop, metric) = route
247 element.add_route(prefix = destination, prefix_len = netprefix,
248 nexthop = nexthop, metric = metric)
250 ### Factory information ###
252 connector_types = dict({
254 "help": "Connector from node to applications",
260 "help": "Connector from node to network interfaces",
266 "help": "Connector to a Node",
272 "help": "Connector to a P2PInterface",
278 "help": "File descriptor receptor for devices with file descriptors",
284 "help": "File descriptor provider for devices with file descriptors",
290 "help": "Connector to a switch",
296 "help": "ip-ip tunneling over TCP link",
302 "help": "ip-ip tunneling over UDP datagrams",
311 "from": (TESTBED_ID, NODE, "devs"),
312 "to": (TESTBED_ID, P2PIFACE, "node"),
316 "from": (TESTBED_ID, NODE, "devs"),
317 "to": (TESTBED_ID, TAPIFACE, "node"),
321 "from": (TESTBED_ID, NODE, "devs"),
322 "to": (TESTBED_ID, TUNIFACE, "node"),
326 "from": (TESTBED_ID, NODE, "devs"),
327 "to": (TESTBED_ID, NODEIFACE, "node"),
331 "from": (TESTBED_ID, P2PIFACE, "p2p"),
332 "to": (TESTBED_ID, P2PIFACE, "p2p"),
336 "from": (TESTBED_ID, TAPIFACE, "fd->"),
337 "to": (None, None, "->fd"),
338 "compl_code": connect_fd,
342 "from": (TESTBED_ID, TUNIFACE, "fd->"),
343 "to": (None, None, "->fd"),
344 "compl_code": connect_fd,
348 "from": (TESTBED_ID, SWITCH, "devs"),
349 "to": (TESTBED_ID, NODEIFACE, "switch"),
350 "init_code": connect_switch,
354 "from": (TESTBED_ID, NODE, "apps"),
355 "to": (TESTBED_ID, APPLICATION, "node"),
359 "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
360 "to": (TESTBED_ID, TAPIFACE, "fd->" ),
361 "init_code": connect_tunchannel_tap,
365 "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
366 "to": (TESTBED_ID, TUNIFACE, "fd->" ),
367 "init_code": connect_tunchannel_tap,
371 "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
372 "to": (None, None, "tcp"),
373 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
374 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
378 "from": (TESTBED_ID, TUNCHANNEL, "udp"),
379 "to": (None, None, "udp"),
380 "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
381 "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
387 "forward_X11": dict({
388 "name": "forward_X11",
389 "help": "Forward x11 from main namespace to the node",
390 "type": Attribute.BOOL,
392 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
393 "validation_function": validation.is_bool
397 "help": "Mac address",
398 "type": Attribute.STRING,
399 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
400 "validation_function": validation.is_mac_address
405 "type": Attribute.BOOL,
407 "validation_function": validation.is_bool
409 "device_name": dict({
411 "help": "Device name",
412 "type": Attribute.STRING,
413 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
414 "validation_function": validation.is_string
418 "help": "Maximum transmition unit for device",
419 "type": Attribute.INTEGER,
420 "validation_function": validation.is_integer
424 "help": "Broadcast address",
425 "type": Attribute.STRING,
426 "validation_function": validation.is_string # TODO: should be is address!
430 "help": "Multicast enabled",
431 "type": Attribute.BOOL,
433 "validation_function": validation.is_bool
437 "help": "ARP enabled",
438 "type": Attribute.BOOL,
440 "validation_function": validation.is_bool
444 "help": "Command line string",
445 "type": Attribute.STRING,
446 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
447 "validation_function": validation.is_string
451 "help": "System user",
452 "type": Attribute.STRING,
453 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
454 "validation_function": validation.is_string
458 "help": "Standard input",
459 "type": Attribute.STRING,
460 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
461 "validation_function": validation.is_string
468 "help": "Standard output stream"
472 "help": "Application standard error",
476 "help": "tcpdump at all node interfaces",
480 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE,
481 TUNIFACE, TUNCHANNEL, SWITCH,
484 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE,
485 TUNIFACE, TUNCHANNEL, SWITCH,
488 factories_info = dict({
490 "help": "Emulated Node with virtualized network stack",
491 "category": FC.CATEGORY_NODES,
492 "create_function": create_node,
493 "configure_function": configure_node,
494 "box_attributes": ["forward_X11"],
495 "connector_types": ["devs", "apps"],
496 "traces": ["node_pcap"],
497 "tags": [tags.NODE, tags.ALLOW_ROUTES],
500 "help": "Point to point network interface",
501 "category": FC.CATEGORY_DEVICES,
502 "create_function": create_p2piface,
503 "configure_function": configure_device,
504 "box_attributes": ["lladdr", "up", "device_name", "mtu",
505 "multicast", "broadcast", "arp"],
506 "connector_types": ["node", "p2p"],
507 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
510 "help": "Tap device network interface",
511 "category": FC.CATEGORY_DEVICES,
512 "create_function": create_tapiface,
513 "configure_function": configure_device,
514 "box_attributes": ["lladdr", "up", "device_name", "mtu",
515 "multicast", "broadcast", "arp"],
516 "connector_types": ["node", "fd->"],
517 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
520 "help": "Tun device network interface",
521 "category": FC.CATEGORY_DEVICES,
522 "create_function": create_tuniface,
523 "configure_function": configure_device,
524 "box_attributes": ["lladdr", "up", "device_name", "mtu",
525 "multicast", "broadcast", "arp"],
526 "connector_types": ["node", "fd->"],
527 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
530 "help": "Node network interface",
531 "category": FC.CATEGORY_DEVICES,
532 "create_function": create_nodeiface,
533 "configure_function": configure_device,
534 "box_attributes": ["lladdr", "up", "device_name", "mtu",
535 "multicast", "broadcast", "arp"],
536 "connector_types": ["node", "switch"],
537 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
540 "display_name": "Switch",
541 "help": "Switch interface",
542 "category": FC.CATEGORY_DEVICES,
543 "create_function": create_switch,
544 "box_attributes": ["up", "device_name", "mtu", "multicast"],
545 #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
546 #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
547 #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
548 #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
549 #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
550 "connector_types": ["devs"],
551 "tags": [tags.SWITCH],
554 "help": "Generic executable command line application",
555 "category": FC.CATEGORY_APPLICATIONS,
556 "create_function": create_application,
557 "start_function": start_application,
558 "stop_function": stop_application,
559 "status_function": status_application,
560 "box_attributes": ["command", "user"],
561 "connector_types": ["node"],
562 "traces": ["stdout", "stderr"],
563 "tags": [tags.APPLICATION],
566 "category": FC.CATEGORY_TUNNELS,
567 "create_function": create_tunchannel,
568 "preconfigure_function": preconfigure_tunchannel,
569 "configure_function": postconfigure_tunchannel,
570 "prestart_function": prestart_tunchannel,
571 "help": "Channel to forward "+TAPIFACE+" data to "
572 "other TAP interfaces supporting the NEPI tunneling protocol.",
573 "connector_types": ["->fd", "udp", "tcp"],
574 "allow_addresses": False,
575 "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher"],
576 "tags": [tags.TUNNEL],
580 testbed_attributes = dict({
581 "enable_debug": dict({
582 "name": "enableDebug",
583 "help": "Enable netns debug output",
584 "type": Attribute.BOOL,
586 "validation_function": validation.is_bool
590 supported_recovery_policies = [
594 class MetadataInfo(metadata.MetadataInfo):
596 def connector_types(self):
597 return connector_types
600 def connections(self):
604 def attributes(self):
612 def create_order(self):
616 def configure_order(self):
617 return configure_order
620 def factories_info(self):
621 return factories_info
624 def testbed_attributes(self):
625 return testbed_attributes
628 def testbed_id(self):
632 def testbed_version(self):
633 return TESTBED_VERSION
636 def supported_recover_policies(self):
637 return supported_recovery_policies