from constants import TESTBED_ID
from nepi.core import metadata
from nepi.core.attributes import Attribute
-from nepi.util import validation
-from nepi.util.constants import AF_INET, STATUS_NOT_STARTED, STATUS_RUNNING, \
- STATUS_FINISHED
+from nepi.util import tags, validation
+from nepi.util.constants import ApplicationStatus as AS, \
+ FactoryCategories as FC
+from nepi.util.tunchannel_impl import \
+ preconfigure_tunchannel, postconfigure_tunchannel, \
+ wait_tunchannel, create_tunchannel, \
+ crossconnect_tunchannel_peer_init, \
+ crossconnect_tunchannel_peer_compl
+
+import functools
+
+# Factories
NODE = "Node"
P2PIFACE = "P2PNodeInterface"
TAPIFACE = "TapNodeInterface"
NODEIFACE = "NodeInterface"
SWITCH = "Switch"
APPLICATION = "Application"
+TUNCHANNEL = "TunChannel"
NS3_TESTBED_ID = "ns3"
FDNETDEV = "ns3::FileDescriptorNetDevice"
+def _follow_trace(testbed_instance, guid, trace_id, filename):
+ filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
+ trace = open(filepath, "wb")
+ testbed_instance.follow_trace(guid, trace_id, trace, filename)
+ return trace
+
### Connection functions ####
-def connect_switch(testbed_instance, switch, interface):
+def connect_switch(testbed_instance, switch_guid, interface_guid):
+ switch = testbed_instance._elements[switch_guid]
+ interface = testbed_instance._elements[interface_guid]
switch.connect(interface)
-#XXX: This connection function cannot be use to transfer a file descriptor
-# to a remote tap device
-def connect_fd_local(testbed_instance, tap, fdnd):
+def connect_fd(testbed_instance, tap_guid, cross_data):
import passfd
import socket
- fd = tap.file_descriptor
- address = fdnd.socket_address
+ tap = testbed_instance._elements[tap_guid]
+ address = cross_data["tun_addr"]
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.connect(address)
- passfd.sendfd(sock, fd, '0')
+ passfd.sendfd(sock, tap.fd, '0')
# TODO: after succesful transfer, the tap device should close the fd
+def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid):
+ tap = testbed_instance._elements[tap_guid]
+ chan = testbed_instance._elements[chan_guid]
+
+ # Create a file object for the tap's interface device
+ # and send it to the channel. It should comply with all the
+ # requirements for the channel's tun_socket.
+ import os
+ chan.tun_socket = os.fdopen(tap.fd)
+
+ # Set the channel to ethernet mode (it's a tap)
+ chan.ethernet_mode = True
+
+ # Check to see if the device uses PI headers
+ # It's normally so
+ with_pi = True
+ try:
+ import fcntl
+ import struct
+ TUNGETIFF = 0x800454d2
+ IFF_NO_PI = 0x00001000
+ struct_ifreq = "x"*16+"H"+"x"*22
+ flags = struct.unpack(struct_ifreq,
+ fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )
+ with_pi = (0 == (flags & IFF_NO_PI))
+ except:
+ # maybe the kernel doesn't support the IOCTL,
+ # in which case, we assume it uses PI headers (as is usual)
+ pass
+ chan.with_pi = with_pi
+
+### Trace functions ###
+
+def nodepcap_trace(testbed_instance, guid, trace_id):
+ node = testbed_instance._elements[guid]
+ parameters = testbed_instance._get_parameters(guid)
+ filename = "%d-pcap.stdout" % guid
+ stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
+ filename = "%d-pcap.stderr" % guid
+ stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
+ filename = "%d-node.pcap" % guid
+ filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
+ command = "tcpdump -i 'any' -w %s" % filepath
+ user = "root"
+ trace = node.Popen(command, shell = True, stdout = stdout,
+ stderr = stderr, user = user)
+ testbed_instance.follow_trace(guid, trace_id, trace, filename)
+
+trace_functions = dict({
+ "pcap": nodepcap_trace,
+ })
+
### Creation functions ###
-def create_node(testbed_instance, guid, parameters, factory_parameters):
+def create_node(testbed_instance, guid):
+ parameters = testbed_instance._get_parameters(guid)
forward_X11 = False
if "forward_X11" in parameters:
forward_X11 = parameters["forward_X11"]
element = testbed_instance.netns.Node(forward_X11 = forward_X11)
testbed_instance.elements[guid] = element
-def create_p2piface(testbed_instance, guid, parameters, factory_parameters):
+def create_p2piface(testbed_instance, guid):
if guid in testbed_instance.elements:
# The interface pair was already instantiated
return
testbed_instance.elements[guid] = element1
testbed_instance.elements[guid2] = element2
-def create_tapiface(testbed_instance, guid, parameters, factory_parameters):
+def create_tapiface(testbed_instance, guid):
node_guid = testbed_instance.get_connected(guid, "node", "devs")
if len(node_guid) == 0:
raise RuntimeError("Can't instantiate interface %d outside netns \
element = node.add_tap()
testbed_instance.elements[guid] = element
-def create_nodeiface(testbed_instance, guid, parameters, factory_parameters):
+def create_nodeiface(testbed_instance, guid):
node_guid = testbed_instance.get_connected(guid, "node", "devs")
if len(node_guid) == 0:
raise RuntimeError("Can't instantiate interface %d outside netns \
element = node.add_if()
testbed_instance.elements[guid] = element
-def create_switch(testbed_instance, guid, parameters, factory_parameters):
+def create_switch(testbed_instance, guid):
element = testbed_instance.netns.Switch()
testbed_instance.elements[guid] = element
-def create_application(testbed_instance, guid, parameters, factory_parameters):
+def create_application(testbed_instance, guid):
testbed_instance.elements[guid] = None # Delayed construction
### Start/Stop functions ###
-def start_application(testbed_instance, guid, parameters, traces):
- user = parameters["user"]
+def start_application(testbed_instance, guid):
+ parameters = testbed_instance._get_parameters(guid)
+ traces = testbed_instance._get_traces(guid)
command = parameters["command"]
+ user = None
+ if "user" in parameters:
+ user = parameters["user"]
stdout = stderr = None
if "stdout" in traces:
- filename = testbed_instance.trace_filename(guid, "stdout")
- stdout = open(filename, "wb")
- testbed_instance.follow_trace("stdout", stdout)
+ filename = "%d-stdout.trace" % guid
+ stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
if "stderr" in traces:
- filename = testbed_instance.trace_filename(guid, "stderr")
- stderr = open(filename, "wb")
- testbed_instance.follow_trace("stderr", stderr)
-
+ filename = "%d-stderr.trace" % guid
+ stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
node_guid = testbed_instance.get_connected(guid, "node", "apps")
if len(node_guid) == 0:
raise RuntimeError("Can't instantiate interface %d outside netns \
stderr = stderr, user = user)
testbed_instance.elements[guid] = element
+def stop_application(testbed_instance, guid):
+ #app = testbed_instance.elements[guid]
+ #app.signal()
+ pass
+
### Status functions ###
-def status_application(testbed_instance, guid, parameters, traces):
+def status_application(testbed_instance, guid):
if guid not in testbed_instance.elements.keys():
- return STATUS_NOT_STARTED
+ return AS.STATUS_NOT_STARTED
app = testbed_instance.elements[guid]
if app.poll() == None:
- return STATUS_RUNNING
- return STATUS_FINISHED
+ return AS.STATUS_RUNNING
+ return AS.STATUS_FINISHED
+
+### Configure functions ###
+
+def configure_traces(testbed_instance, guid):
+ traces = testbed_instance._get_traces(guid)
+ for trace_id in traces:
+ if trace_id not in trace_functions:
+ continue
+ trace_func = trace_functions[trace_id]
+ trace_func(testbed_instance, guid, trace_id)
+
+def configure_device(testbed_instance, guid):
+ configure_traces(testbed_instance, guid)
+ element = testbed_instance._elements[guid]
+ if not guid in testbed_instance._add_address:
+ return
+ addresses = testbed_instance._add_address[guid]
+ for address in addresses:
+ (address, netprefix, broadcast) = address
+ # TODO: Decide if we should add a ipv4 or ipv6 address
+ element.add_v4_address(address, netprefix)
+
+def configure_node(testbed_instance, guid):
+ configure_traces(testbed_instance, guid)
+ element = testbed_instance._elements[guid]
+ if not guid in testbed_instance._add_route:
+ return
+ routes = testbed_instance._add_route[guid]
+ for route in routes:
+ (destination, netprefix, nexthop, metric) = route
+ element.add_route(prefix = destination, prefix_len = netprefix,
+ nexthop = nexthop, metric = metric)
### Factory information ###
"max": 1,
"min": 0
}),
- "fd": dict({
- "help": "Connector to a network interface that can receive a file descriptor",
- "name": "fd",
- "max": 1,
+ "->fd": dict({
+ "help": "File descriptor receptor for devices with file descriptors",
+ "name": "->fd",
+ "max": 1,
+ "min": 0
+ }),
+ "fd->": dict({
+ "help": "File descriptor provider for devices with file descriptors",
+ "name": "fd->",
+ "max": 1,
"min": 0
}),
"switch": dict({
"name": "switch",
"max": 1,
"min": 0
- })
+ }),
+ "tcp": dict({
+ "help": "ip-ip tunneling over TCP link",
+ "name": "tcp",
+ "max": 1,
+ "min": 0
+ }),
+ "udp": dict({
+ "help": "ip-ip tunneling over UDP datagrams",
+ "name": "udp",
+ "max": 1,
+ "min": 0
+ }),
})
connections = [
dict({
"from": (TESTBED_ID, NODE, "devs"),
"to": (TESTBED_ID, P2PIFACE, "node"),
- "code": None,
"can_cross": False
}),
dict({
"from": (TESTBED_ID, NODE, "devs"),
"to": (TESTBED_ID, TAPIFACE, "node"),
- "code": None,
"can_cross": False
}),
dict({
"from": (TESTBED_ID, NODE, "devs"),
"to": (TESTBED_ID, NODEIFACE, "node"),
- "code": None,
"can_cross": False
}),
dict({
"from": (TESTBED_ID, P2PIFACE, "p2p"),
"to": (TESTBED_ID, P2PIFACE, "p2p"),
- "code": None,
"can_cross": False
}),
dict({
- "from": (TESTBED_ID, TAPIFACE, "fd"),
- "to": (NS3_TESTBED_ID, FDNETDEV, "fd"),
- "code": connect_fd_local,
+ "from": (TESTBED_ID, TAPIFACE, "fd->"),
+ "to": (None, None, "->fd"),
+ "compl_code": connect_fd,
"can_cross": True
}),
dict({
"from": (TESTBED_ID, SWITCH, "devs"),
"to": (TESTBED_ID, NODEIFACE, "switch"),
- "code": connect_switch,
+ "init_code": connect_switch,
"can_cross": False
}),
dict({
"from": (TESTBED_ID, NODE, "apps"),
"to": (TESTBED_ID, APPLICATION, "node"),
- "code": None,
"can_cross": False
- })
+ }),
+ dict({
+ "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
+ "to": (TESTBED_ID, TAPIFACE, "fd->" ),
+ "init_code": connect_tunchannel_tap,
+ "can_cross": False
+ }),
+ dict({
+ "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
+ "to": (None, None, "tcp"),
+ "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
+ "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
+ "can_cross": True
+ }),
+ dict({
+ "from": (TESTBED_ID, TUNCHANNEL, "udp"),
+ "to": (None, None, "udp"),
+ "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
+ "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
+ "can_cross": True
+ }),
]
attributes = dict({
"help": "Forward x11 from main namespace to the node",
"type": Attribute.BOOL,
"value": False,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_bool
}),
"name": "lladdr",
"help": "Mac address",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_mac_address
}),
"help": "Link up",
"type": Attribute.BOOL,
"value": False,
- "range": None,
- "allowed": None,
"validation_function": validation.is_bool
}),
"device_name": dict({
"name": "name",
"help": "Device name",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_string
}),
"name": "mtu",
"help": "Maximum transmition unit for device",
"type": Attribute.INTEGER,
- "value": None,
- "range": None,
- "allowed": None,
"validation_function": validation.is_integer
}),
"broadcast": dict({
"name": "broadcast",
"help": "Broadcast address",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"validation_function": validation.is_string # TODO: should be is address!
}),
"multicast": dict({
"help": "Multicast enabled",
"type": Attribute.BOOL,
"value": False,
- "range": None,
- "allowed": None,
"validation_function": validation.is_bool
}),
"arp": dict({
"help": "ARP enabled",
"type": Attribute.BOOL,
"value": False,
- "range": None,
- "allowed": None,
"validation_function": validation.is_bool
}),
"command": dict({
"name": "command",
"help": "Command line string",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_string
}),
"name": "user",
"help": "System user",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_string
}),
"name": "stdin",
"help": "Standard input",
"type": Attribute.STRING,
- "value": None,
- "range": None,
- "allowed": None,
"flags": Attribute.DesignOnly,
"validation_function": validation.is_string
}),
- "max_addresses": dict({
- "name": "MaxAddresses",
- "help": "Maximum number of addresses allowed by the device",
- "type": Attribute.INTEGER,
- "value": None,
- "range": None,
- "allowed": None,
- "flags": Attribute.Invisible,
- "validation_function": validation.is_integer
- }),
- "family": dict({
- "name": "Family",
- "help": "IP address family",
- "type": Attribute.INTEGER,
- "value": AF_INET,
- "range": None,
- "allowed": None,
- "flags": Attribute.Invisible,
- "validation_function": validation.is_integer
- }),
})
traces = dict({
"stderr": dict({
"name": "stderr",
"help": "Application standard error",
+ }),
+ "node_pcap": dict({
+ "name": "pcap",
+ "help": "tcpdump at all node interfaces",
})
})
-factories_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH,
+create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE,
+ TUNCHANNEL, SWITCH,
APPLICATION ]
+configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE,
+ TUNCHANNEL, SWITCH,
+ NODE, APPLICATION ]
+
factories_info = dict({
NODE: dict({
- "allow_routes": True,
"help": "Emulated Node with virtualized network stack",
- "category": "topology",
+ "category": FC.CATEGORY_NODES,
"create_function": create_node,
- "start_function": None,
- "stop_function": None,
- "status_function": None,
+ "configure_function": configure_node,
"box_attributes": ["forward_X11"],
- "connector_types": ["devs", "apps"]
+ "connector_types": ["devs", "apps"],
+ "traces": ["node_pcap"],
+ "tags": [tags.NODE, tags.ALLOW_ROUTES],
}),
P2PIFACE: dict({
- "allow_addresses": True,
"help": "Point to point network interface",
- "category": "devices",
+ "category": FC.CATEGORY_DEVICES,
"create_function": create_p2piface,
- "start_function": None,
- "stop_function": None,
- "status_function": None,
- "factory_attributes": ["family", "max_addresses"],
+ "configure_function": configure_device,
"box_attributes": ["lladdr", "up", "device_name", "mtu",
"multicast", "broadcast", "arp"],
- "connector_types": ["node", "p2p"]
+ "connector_types": ["node", "p2p"],
+ "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
}),
TAPIFACE: dict({
- "allow_addresses": True,
"help": "Tap device network interface",
- "category": "devices",
+ "category": FC.CATEGORY_DEVICES,
"create_function": create_tapiface,
- "start_function": None,
- "stop_function": None,
- "status_function": None,
- "factory_attributes": ["family", "max_addresses"],
+ "configure_function": configure_device,
"box_attributes": ["lladdr", "up", "device_name", "mtu",
"multicast", "broadcast", "arp"],
- "connector_types": ["node", "fd"]
+ "connector_types": ["node", "fd->"],
+ "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
}),
NODEIFACE: dict({
- "allow_addresses": True,
"help": "Node network interface",
- "category": "devices",
+ "category": FC.CATEGORY_DEVICES,
"create_function": create_nodeiface,
- "start_function": None,
- "stop_function": None,
- "status_function": None,
- "factory_attributes": ["family", "max_addresses"],
+ "configure_function": configure_device,
"box_attributes": ["lladdr", "up", "device_name", "mtu",
"multicast", "broadcast", "arp"],
- "connector_types": ["node", "switch"]
+ "connector_types": ["node", "switch"],
+ "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
}),
SWITCH: dict({
"display_name": "Switch",
"help": "Switch interface",
- "category": "devices",
+ "category": FC.CATEGORY_DEVICES,
"create_function": create_switch,
- "start_function": None,
- "stop_function": None,
- "status_function": None,
"box_attributes": ["up", "device_name", "mtu", "multicast"],
#TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
#TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
#TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
#TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
#TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
- "connector_types": ["devs"]
+ "connector_types": ["devs"],
+ "tags": [tags.SWITCH],
}),
APPLICATION: dict({
"help": "Generic executable command line application",
- "category": "applications",
+ "category": FC.CATEGORY_APPLICATIONS,
"create_function": create_application,
"start_function": start_application,
- "stop_function": None,
+ "stop_function": stop_application,
"status_function": status_application,
"box_attributes": ["command", "user"],
"connector_types": ["node"],
- "traces": ["stdout", "stderr"]
+ "traces": ["stdout", "stderr"],
+ "tags": [tags.APPLICATION],
}),
+ TUNCHANNEL : dict({
+ "category": FC.CATEGORY_TUNNELS,
+ "create_function": create_tunchannel,
+ "preconfigure_function": preconfigure_tunchannel,
+ "configure_function": postconfigure_tunchannel,
+ "prestart_function": wait_tunchannel,
+ "help": "Channel to forward "+TAPIFACE+" data to "
+ "other TAP interfaces supporting the NEPI tunneling protocol.",
+ "connector_types": ["->fd", "udp", "tcp"],
+ "allow_addresses": False,
+ "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key"],
+ "tags": [tags.TUNNEL],
+ }),
})
testbed_attributes = dict({
"help": "Enable netns debug output",
"type": Attribute.BOOL,
"value": False,
- "range": None,
- "allowed": None,
"validation_function": validation.is_bool
}),
- "home_directory": dict({
- "name": "homeDirectory",
- "help": "Path to the directory where traces and other files \
- will be stored",
- "type": Attribute.STRING,
- "value": False,
- "range": None,
- "allowed": None,
- "flags": Attribute.DesignOnly,
- "validation_function": validation.is_string
- })
})
class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
@property
- def connections_types(self):
- return connection_types
+ def connector_types(self):
+ return connector_types
@property
def connections(self):
return traces
@property
- def factories_order(self):
- return factories_order
+ def create_order(self):
+ return create_order
+
+ @property
+ def configure_order(self):
+ return configure_order
@property
def factories_info(self):