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