Added WaypointMobilityModel to ns3 backend
[nepi.git] / src / nepi / testbeds / netns / metadata_v01.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from constants import TESTBED_ID
5 from nepi.core import metadata
6 from nepi.core.attributes import Attribute
7 from nepi.util import validation
8 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
9         STATUS_FINISHED
10
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
16
17 import functools
18
19 NODE = "Node"
20 P2PIFACE = "P2PNodeInterface"
21 TAPIFACE = "TapNodeInterface"
22 NODEIFACE = "NodeInterface"
23 SWITCH = "Switch"
24 APPLICATION = "Application"
25 TUNCHANNEL = "TunChannel"
26
27 NS3_TESTBED_ID = "ns3"
28 FDNETDEV = "ns3::FileDescriptorNetDevice"
29
30 def _follow_trace(testbed_instance, guid, trace_id, filename):
31     filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
32     trace = open(filepath, "wb")
33     testbed_instance.follow_trace(guid, trace_id, trace, filename)
34     return trace
35
36 ### Connection functions ####
37
38 def connect_switch(testbed_instance, switch_guid, interface_guid):
39     switch = testbed_instance._elements[switch_guid]
40     interface = testbed_instance._elements[interface_guid]
41     switch.connect(interface)
42    
43 def connect_fd(testbed_instance, tap_guid, cross_data):
44     import passfd
45     import socket
46     tap = testbed_instance._elements[tap_guid]
47     address = cross_data["tun_addr"]
48     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
49     sock.connect(address)
50     passfd.sendfd(sock, tap.fd, '0')
51     # TODO: after succesful transfer, the tap device should close the fd
52
53 def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid):
54     tap = testbed_instance._elements[tap_guid]
55     chan = testbed_instance._elements[chan_guid]
56
57     # Create a file object for the tap's interface device 
58     # and send it to the channel. It should comply with all the
59     # requirements for the channel's tun_socket.
60     import os
61     chan.tun_socket = os.fdopen(tap.fd)
62     
63     # Set the channel to ethernet mode (it's a tap)
64     chan.ethernet_mode = True
65     
66     # Check to see if the device uses PI headers
67     # It's normally so
68     with_pi = True
69     try:
70         import fcntl
71         import struct
72         TUNGETIFF = 0x800454d2
73         IFF_NO_PI = 0x00001000
74         struct_ifreq = "x"*16+"H"+"x"*22
75         flags = struct.unpack(struct_ifreq,
76             fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )
77         with_pi = (0 == (flags & IFF_NO_PI))
78     except:
79         # maybe the kernel doesn't support the IOCTL,
80         # in which case, we assume it uses PI headers (as is usual)
81         pass
82     chan.with_pi = with_pi
83
84 ### Trace functions ###
85
86 def nodepcap_trace(testbed_instance, guid, trace_id):
87     node = testbed_instance._elements[guid]
88     parameters = testbed_instance._get_parameters(guid)
89     filename = "%d-cap.stdout" % guid
90     stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
91     filename = "%d-pcap.stderr" % guid
92     stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
93     filename = "%d-node.pcap" % guid
94     filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
95     command = "tcpdump -i 'any' -w %s" % filepath
96     user = "root"
97     trace = node.Popen(command, shell = True, stdout = stdout, 
98             stderr = stderr, user = user)
99     testbed_instance.follow_trace(guid, trace_id, trace, filename)
100
101 trace_functions = dict({
102     "pcap": nodepcap_trace,
103     })
104
105 ### Creation functions ###
106
107 def create_node(testbed_instance, guid):
108     parameters = testbed_instance._get_parameters(guid)
109     forward_X11 = False
110     if "forward_X11" in parameters:
111         forward_X11 = parameters["forward_X11"]
112         del parameters["forward_X11"]
113     element = testbed_instance.netns.Node(forward_X11 = forward_X11)
114     testbed_instance.elements[guid] = element
115
116 def create_p2piface(testbed_instance, guid):
117     if guid in testbed_instance.elements:
118         # The interface pair was already instantiated
119         return
120     # search for the node asociated with the p2piface
121     node1_guid = testbed_instance.get_connected(guid, "node", "devs")
122     if len(node1_guid) == 0:
123         raise RuntimeError("Can't instantiate interface %d outside netns \
124                 node" % guid)
125     node1 = testbed_instance.elements[node1_guid[0]]
126     # search for the pair p2piface
127     p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
128     if len(p2p_guid) == 0:
129         raise RuntimeError("Can't instantiate p2p interface %d. \
130                 Missing interface pair" % guid)
131     guid2 = p2p_guid[0]
132     node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
133     if len(node2_guid) == 0:
134         raise RuntimeError("Can't instantiate interface %d outside netns \
135                 node" % guid2)
136     node2 = testbed_instance.elements[node2_guid[0]]
137     element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
138         node1, node2)
139     testbed_instance.elements[guid] = element1
140     testbed_instance.elements[guid2] = element2
141
142 def create_tapiface(testbed_instance, guid):
143     node_guid = testbed_instance.get_connected(guid, "node", "devs")
144     if len(node_guid) == 0:
145         raise RuntimeError("Can't instantiate interface %d outside netns \
146                 node" % guid)
147     node = testbed_instance.elements[node_guid[0]]
148     element = node.add_tap()
149     testbed_instance.elements[guid] = element
150
151 def create_nodeiface(testbed_instance, guid):
152     node_guid = testbed_instance.get_connected(guid, "node", "devs")
153     if len(node_guid) == 0:
154         raise RuntimeError("Can't instantiate interface %d outside netns \
155                 node" % guid)
156     node = testbed_instance.elements[node_guid[0]]
157     element = node.add_if()
158     testbed_instance.elements[guid] = element
159
160 def create_switch(testbed_instance, guid):
161     element = testbed_instance.netns.Switch()
162     testbed_instance.elements[guid] = element
163
164 def create_application(testbed_instance, guid):
165     testbed_instance.elements[guid] = None # Delayed construction 
166
167 ### Start/Stop functions ###
168
169 def start_application(testbed_instance, guid):
170     parameters = testbed_instance._get_parameters(guid)
171     traces = testbed_instance._get_traces(guid)
172     command = parameters["command"]
173     user = None
174     if "user" in parameters:
175         user = parameters["user"]
176     stdout = stderr = None
177     if "stdout" in traces:
178         filename = "%d-stdout.trace" % guid
179         stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
180     if "stderr" in traces:
181         filename = "%d-stderr.trace" % guid
182         stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
183     node_guid = testbed_instance.get_connected(guid, "node", "apps")
184     if len(node_guid) == 0:
185         raise RuntimeError("Can't instantiate interface %d outside netns \
186                 node" % guid)
187     node = testbed_instance.elements[node_guid[0]]
188     element  = node.Popen(command, shell = True, stdout = stdout, 
189             stderr = stderr, user = user)
190     testbed_instance.elements[guid] = element
191
192 ### Status functions ###
193
194 def status_application(testbed_instance, guid):
195     if guid not in testbed_instance.elements.keys():
196         return STATUS_NOT_STARTED
197     app = testbed_instance.elements[guid]
198     if app.poll() == None:
199         return STATUS_RUNNING
200     return STATUS_FINISHED
201
202 ### Configure functions ###
203
204 def configure_traces(testbed_instance, guid):
205     traces = testbed_instance._get_traces(guid)
206     for trace_id in traces:
207         if trace_id not in trace_functions:
208             continue
209         trace_func = trace_functions[trace_id]
210         trace_func(testbed_instance, guid, trace_id)
211
212 def configure_device(testbed_instance, guid):
213     configure_traces(testbed_instance, guid)
214     element = testbed_instance._elements[guid]
215     if not guid in testbed_instance._add_address:
216         return
217     addresses = testbed_instance._add_address[guid]
218     for address in addresses:
219         (address, netprefix, broadcast) = address
220         # TODO: Decide if we should add a ipv4 or ipv6 address
221         element.add_v4_address(address, netprefix)
222
223 def configure_node(testbed_instance, guid):
224     configure_traces(testbed_instance, guid)
225     element = testbed_instance._elements[guid]
226     if not guid in testbed_instance._add_route:
227         return
228     routes = testbed_instance._add_route[guid]
229     for route in routes:
230         (destination, netprefix, nexthop) = route
231         element.add_route(prefix = destination, prefix_len = netprefix,
232             nexthop = nexthop)
233
234 ### Factory information ###
235
236 connector_types = dict({
237     "apps": dict({
238                 "help": "Connector from node to applications", 
239                 "name": "apps",
240                 "max": -1, 
241                 "min": 0
242             }),
243     "devs": dict({
244                 "help": "Connector from node to network interfaces", 
245                 "name": "devs",
246                 "max": -1, 
247                 "min": 0
248             }),
249     "node": dict({
250                 "help": "Connector to a Node", 
251                 "name": "node",
252                 "max": 1, 
253                 "min": 1
254             }),
255     "p2p": dict({
256                 "help": "Connector to a P2PInterface", 
257                 "name": "p2p",
258                 "max": 1, 
259                 "min": 0
260             }),
261     "->fd": dict({
262                 "help": "File descriptor receptor for devices with file descriptors",
263                 "name": "->fd",
264                 "max": 1,
265                 "min": 0
266             }),
267     "fd->": dict({
268                 "help": "File descriptor provider for devices with file descriptors",
269                 "name": "fd->",
270                 "max": 1,
271                 "min": 0
272             }),
273     "switch": dict({
274                 "help": "Connector to a switch", 
275                 "name": "switch",
276                 "max": 1, 
277                 "min": 0
278             }),
279     "tcp": dict({
280                 "help": "ip-ip tunneling over TCP link", 
281                 "name": "tcp",
282                 "max": 1, 
283                 "min": 0
284             }),
285     "udp": dict({
286                 "help": "ip-ip tunneling over UDP datagrams", 
287                 "name": "udp",
288                 "max": 1, 
289                 "min": 0
290             }),
291    })
292
293 connections = [
294     dict({
295         "from": (TESTBED_ID, NODE, "devs"),
296         "to":   (TESTBED_ID, P2PIFACE, "node"),
297         "can_cross": False
298     }),
299     dict({
300         "from": (TESTBED_ID, NODE, "devs"),
301         "to":   (TESTBED_ID, TAPIFACE, "node"),
302         "can_cross": False
303     }),
304     dict({
305         "from": (TESTBED_ID, NODE, "devs"),
306         "to":   (TESTBED_ID, NODEIFACE, "node"),
307         "can_cross": False
308     }),
309     dict({
310         "from": (TESTBED_ID, P2PIFACE, "p2p"),
311         "to":   (TESTBED_ID, P2PIFACE, "p2p"),
312         "can_cross": False
313     }),
314     dict({
315         "from": (TESTBED_ID, TAPIFACE, "fd->"),
316         "to":   (None, None, "->fd"),
317         "compl_code": connect_fd,
318         "can_cross": True
319     }),
320      dict({
321         "from": (TESTBED_ID, SWITCH, "devs"),
322         "to":   (TESTBED_ID, NODEIFACE, "switch"),
323         "init_code": connect_switch,
324         "can_cross": False
325     }),
326     dict({
327         "from": (TESTBED_ID, NODE, "apps"),
328         "to":   (TESTBED_ID, APPLICATION, "node"),
329         "can_cross": False
330     }),
331     dict({
332         "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
333         "to":   (TESTBED_ID, TAPIFACE, "fd->" ),
334         "init_code": connect_tunchannel_tap,
335         "can_cross": False
336     }),
337     dict({
338         "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
339         "to":   (None, None, "tcp"),
340         "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
341         "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
342         "can_cross": True
343     }),
344     dict({
345         "from": (TESTBED_ID, TUNCHANNEL, "udp"),
346         "to":   (None, None, "udp"),
347         "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
348         "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
349         "can_cross": True
350     }),
351 ]
352
353 attributes = dict({
354     "forward_X11": dict({      
355                 "name": "forward_X11",
356                 "help": "Forward x11 from main namespace to the node",
357                 "type": Attribute.BOOL, 
358                 "value": False,
359                 "flags": Attribute.DesignOnly,
360                 "validation_function": validation.is_bool
361             }),
362     "lladdr": dict({      
363                 "name": "lladdr", 
364                 "help": "Mac address", 
365                 "type": Attribute.STRING,
366                 "flags": Attribute.DesignOnly,
367                 "validation_function": validation.is_mac_address
368             }),
369     "up": dict({
370                 "name": "up",
371                 "help": "Link up",
372                 "type": Attribute.BOOL,
373                 "value": False,
374                 "validation_function": validation.is_bool
375             }),
376     "device_name": dict({
377                 "name": "name",
378                 "help": "Device name",
379                 "type": Attribute.STRING,
380                 "flags": Attribute.DesignOnly,
381                 "validation_function": validation.is_string
382             }),
383     "mtu":  dict({
384                 "name": "mtu", 
385                 "help": "Maximum transmition unit for device",
386                 "type": Attribute.INTEGER,
387                 "validation_function": validation.is_integer
388             }),
389     "broadcast": dict({ 
390                 "name": "broadcast",
391                 "help": "Broadcast address",
392                 "type": Attribute.STRING,
393                 "validation_function": validation.is_string # TODO: should be is address!
394             }),
395     "multicast": dict({      
396                 "name": "multicast",
397                 "help": "Multicast enabled",
398                 "type": Attribute.BOOL,
399                 "value": False,
400                 "validation_function": validation.is_bool
401             }),
402     "arp": dict({
403                 "name": "arp",
404                 "help": "ARP enabled",
405                 "type": Attribute.BOOL,
406                 "value": False,
407                 "validation_function": validation.is_bool
408             }),
409     "command": dict({
410                 "name": "command",
411                 "help": "Command line string",
412                 "type": Attribute.STRING,
413                 "flags": Attribute.DesignOnly,
414                 "validation_function": validation.is_string
415             }),
416     "user": dict({
417                 "name": "user",
418                 "help": "System user",
419                 "type": Attribute.STRING,
420                 "flags": Attribute.DesignOnly,
421                 "validation_function": validation.is_string
422             }),
423     "stdin": dict({
424                 "name": "stdin",
425                 "help": "Standard input",
426                 "type": Attribute.STRING,
427                 "flags": Attribute.DesignOnly,
428                 "validation_function": validation.is_string
429             }),
430     })
431
432 traces = dict({
433     "stdout": dict({
434                 "name": "stdout",
435                 "help": "Standard output stream"
436               }),
437     "stderr": dict({
438                 "name": "stderr",
439                 "help": "Application standard error",
440         }),
441     "node_pcap": dict({
442                 "name": "pcap",
443                 "help": "tcpdump at all node interfaces",
444         }) 
445     })
446
447 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, 
448         TUNCHANNEL, SWITCH,
449         APPLICATION ]
450
451 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, 
452         TUNCHANNEL, SWITCH, 
453         NODE, APPLICATION ]
454
455 factories_info = dict({
456     NODE: dict({
457             "allow_routes": True,
458             "help": "Emulated Node with virtualized network stack",
459             "category": "topology",
460             "create_function": create_node,
461             "configure_function": configure_node,
462             "box_attributes": ["forward_X11"],
463             "connector_types": ["devs", "apps"],
464             "traces": ["node_pcap"]
465        }),
466     P2PIFACE: dict({
467             "allow_addresses": True,
468             "help": "Point to point network interface",
469             "category": "devices",
470             "create_function": create_p2piface,
471             "configure_function": configure_device,
472             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
473                 "multicast", "broadcast", "arp"],
474             "connector_types": ["node", "p2p"]
475        }),
476     TAPIFACE: dict({
477             "allow_addresses": True,
478             "help": "Tap device network interface",
479             "category": "devices",
480             "create_function": create_tapiface,
481             "configure_function": configure_device,
482             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
483                 "multicast", "broadcast", "arp"],
484             "connector_types": ["node", "fd->"]
485         }),
486     NODEIFACE: dict({
487             "allow_addresses": True,
488             "help": "Node network interface",
489             "category": "devices",
490             "create_function": create_nodeiface,
491             "configure_function": configure_device,
492             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
493                 "multicast", "broadcast", "arp"],
494             "connector_types": ["node", "switch"]
495         }),
496     SWITCH: dict({
497             "display_name": "Switch",
498             "help": "Switch interface",
499             "category": "devices",
500             "create_function": create_switch,
501             "box_attributes": ["up", "device_name", "mtu", "multicast"],
502              #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
503              #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
504              #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
505              #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
506              #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
507            "connector_types": ["devs"]
508         }),
509     APPLICATION: dict({
510             "help": "Generic executable command line application",
511             "category": "applications",
512             "create_function": create_application,
513             "start_function": start_application,
514             "status_function": status_application,
515             "box_attributes": ["command", "user"],
516             "connector_types": ["node"],
517             "traces": ["stdout", "stderr"]
518         }),
519      TUNCHANNEL : dict({
520         "category": "Channel",
521         "create_function": create_tunchannel,
522         "preconfigure_function": preconfigure_tunchannel,
523         "configure_function": postconfigure_tunchannel,
524         "start_function": wait_tunchannel,
525         "help": "Channel to forward "+TAPIFACE+" data to "
526                 "other TAP interfaces supporting the NEPI tunneling protocol.",
527         "connector_types": ["->fd", "udp", "tcp"],
528         "allow_addresses": False,
529         "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"]
530     }),
531 })
532
533 testbed_attributes = dict({
534         "enable_debug": dict({
535                 "name": "enableDebug",
536                 "help": "Enable netns debug output",
537                 "type": Attribute.BOOL,
538                 "value": False,
539                 "validation_function": validation.is_bool
540             }),
541     })
542
543 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
544     @property
545     def connector_types(self):
546         return connector_types
547
548     @property
549     def connections(self):
550         return connections
551
552     @property
553     def attributes(self):
554         return attributes
555
556     @property
557     def traces(self):
558         return traces
559
560     @property
561     def create_order(self):
562         return create_order
563
564     @property
565     def configure_order(self):
566         return configure_order
567
568     @property
569     def factories_info(self):
570         return factories_info
571
572     @property
573     def testbed_attributes(self):
574         return testbed_attributes
575