Beispiel #1
0
    def handlenodemsg(self, msg):
        """
        Process a Node Message to add/delete or move a node on
        the SDT display. Node properties are found in session._objs or
        self.remotes for remote nodes (or those not yet instantiated).

        :param msg: node message to handle
        :return: nothing
        """
        # for distributed sessions to work properly, the SDT option should be
        # enabled prior to starting the session
        if not self.is_enabled():
            return False
        # node.(objid, type, icon, name) are used.
        nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
        if not nodenum:
            return
        x = msg.get_tlv(NodeTlvs.X_POSITION.value)
        y = msg.get_tlv(NodeTlvs.Y_POSITION.value)
        z = None
        name = msg.get_tlv(NodeTlvs.NAME.value)

        nodetype = msg.get_tlv(NodeTlvs.TYPE.value)
        model = msg.get_tlv(NodeTlvs.MODEL.value)
        icon = msg.get_tlv(NodeTlvs.ICON.value)

        net = False
        if nodetype == NodeTypes.DEFAULT.value or \
                nodetype == NodeTypes.PHYSICAL.value or \
                nodetype == NodeTypes.XEN.value:
            if model is None:
                model = "router"
            type = model
        elif nodetype is not None:
            type = nodeutils.get_node_class(NodeTypes(nodetype)).type
            net = True
        else:
            type = None

        try:
            node = self.session.get_object(nodenum)
        except KeyError:
            node = None
        if node:
            self.updatenode(node.objid, msg.flags, x, y, z, node.name, node.type, node.icon)
        else:
            if nodenum in self.remotes:
                remote = self.remotes[nodenum]
                if name is None:
                    name = remote.name
                if type is None:
                    type = remote.type
                if icon is None:
                    icon = remote.icon
            else:
                remote = Bunch(objid=nodenum, type=type, icon=icon, name=name, net=net, links=set())
                self.remotes[nodenum] = remote
            remote.pos = (x, y, z)
            self.updatenode(nodenum, msg.flags, x, y, z, name, type, icon)
Beispiel #2
0
    def handlenodemsg(self, msg):
        """
        Process a Node Message to add/delete or move a node on
        the SDT display. Node properties are found in session._objs or
        self.remotes for remote nodes (or those not yet instantiated).

        :param msg: node message to handle
        :return: nothing
        """
        # for distributed sessions to work properly, the SDT option should be
        # enabled prior to starting the session
        if not self.is_enabled():
            return False
        # node.(objid, type, icon, name) are used.
        nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
        if not nodenum:
            return
        x = msg.get_tlv(NodeTlvs.X_POSITION.value)
        y = msg.get_tlv(NodeTlvs.Y_POSITION.value)
        z = None
        name = msg.get_tlv(NodeTlvs.NAME.value)

        nodetype = msg.get_tlv(NodeTlvs.TYPE.value)
        model = msg.get_tlv(NodeTlvs.MODEL.value)
        icon = msg.get_tlv(NodeTlvs.ICON.value)

        net = False
        if nodetype == NodeTypes.DEFAULT.value or \
                nodetype == NodeTypes.PHYSICAL.value:
            if model is None:
                model = "router"
            type = model
        elif nodetype is not None:
            type = nodeutils.get_node_class(NodeTypes(nodetype)).type
            net = True
        else:
            type = None

        try:
            node = self.session.get_object(nodenum)
        except KeyError:
            node = None
        if node:
            self.updatenode(node.objid, msg.flags, x, y, z, node.name, node.type, node.icon)
        else:
            if nodenum in self.remotes:
                remote = self.remotes[nodenum]
                if name is None:
                    name = remote.name
                if type is None:
                    type = remote.type
                if icon is None:
                    icon = remote.icon
            else:
                remote = Bunch(objid=nodenum, type=type, icon=icon, name=name, net=net, links=set())
                self.remotes[nodenum] = remote
            remote.pos = (x, y, z)
            self.updatenode(nodenum, msg.flags, x, y, z, name, type, icon)
Beispiel #3
0
    def parse_layer2_device(self, device):
        objid, device_name = self.get_common_attributes(device)
        logger.info('parsing layer-2 device: name=%s id=%s' % (device_name, objid))

        try:
            return self.session.get_object(objid)
        except KeyError:
            logger.exception("error geting object: %s", objid)

        device_type = self.device_type(device)
        if device_type == 'hub':
            device_class = nodeutils.get_node_class(NodeTypes.HUB)
        elif device_type == 'switch':
            device_class = nodeutils.get_node_class(NodeTypes.SWITCH)
        else:
            logger.warn('unknown layer-2 device type: \'%s\'' % device_type)
            assert False  # XXX for testing

        n = self.create_core_object(device_class, objid, device_name, device, None)
        return n
Beispiel #4
0
    def parse_layer2_device(self, device):
        objid, device_name = self.get_common_attributes(device)
        logger.info('parsing layer-2 device: name=%s id=%s' % (device_name, objid))

        try:
            return self.session.get_object(objid)
        except KeyError:
            logger.exception("error geting object: %s", objid)

        device_type = self.device_type(device)
        if device_type == 'hub':
            device_class = nodeutils.get_node_class(NodeTypes.HUB)
        elif device_type == 'switch':
            device_class = nodeutils.get_node_class(NodeTypes.SWITCH)
        else:
            logger.warn('unknown layer-2 device type: \'%s\'' % device_type)
            assert False  # XXX for testing

        n = self.create_core_object(device_class, objid, device_name, device, None)
        return n
Beispiel #5
0
 def network_class(self, network, network_type):
     """
     Return the corresponding CORE network class for the given
     network/network_type.
     """
     if network_type in ['ethernet', 'satcom']:
         return nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
     elif network_type == 'wireless':
         channel = xmlutils.get_first_child_by_tag_name(network, 'channel')
         if channel:
             # use an explicit CORE type if it exists
             coretype = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
             if coretype:
                 if coretype == 'basic_range':
                     return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
                 elif coretype.startswith('emane'):
                     return nodeutils.get_node_class(NodeTypes.EMANE)
                 else:
                     logger.warn('unknown network type: \'%s\'', coretype)
                     return xmlutils.xml_type_to_node_class(coretype)
         return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
     logger.warn('unknown network type: \'%s\'', network_type)
     return None
Beispiel #6
0
 def network_class(self, network, network_type):
     """
     Return the corresponding CORE network class for the given
     network/network_type.
     """
     if network_type in ['ethernet', 'satcom']:
         return nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
     elif network_type == 'wireless':
         channel = xmlutils.get_first_child_by_tag_name(network, 'channel')
         if channel:
             # use an explicit CORE type if it exists
             coretype = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
             if coretype:
                 if coretype == 'basic_range':
                     return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
                 elif coretype.startswith('emane'):
                     return nodeutils.get_node_class(NodeTypes.EMANE)
                 else:
                     logger.warn('unknown network type: \'%s\'', coretype)
                     return xmlutils.xml_type_to_node_class(coretype)
         return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
     logger.warn('unknown network type: \'%s\'', network_type)
     return None
Beispiel #7
0
def open_session_xml(session, filename, start=False, nodecls=None):
    """
    Import a session from the EmulationScript XML format.
    """

    # set default node class when one is not provided
    if not nodecls:
        nodecls = nodeutils.get_node_class(NodeTypes.DEFAULT)

    options = {'start': start, 'nodecls': nodecls}
    doc = core_document_parser(session, filename, options)
    if start:
        session.name = os.path.basename(filename)
        session.filename = filename
        session.instantiate()
Beispiel #8
0
 def parsenodes(self):
     for node in self.np.getElementsByTagName("Node"):
         id, name, type = self.getcommonattributes(node)
         if type == "rj45":
             nodecls = nodeutils.get_node_class(NodeTypes.RJ45)
         else:
             nodecls = self.nodecls
         n = self.session.add_object(cls=nodecls, objid=id, name=name, start=self.start)
         if name in self.coords:
             x, y, z = self.coords[name]
             n.setposition(x, y, z)
         n.type = type
         xmlutils.get_params_set_attrs(node, ("icon", "canvas", "opaque"), n)
         if hasattr(n, "canvas") and n.canvas is not None:
             n.canvas = int(n.canvas)
         for ifc in node.getElementsByTagName("interface"):
             self.parseinterface(n, ifc)
Beispiel #9
0
    def open_xml(self, file_name, start=False):
        """
        Import a session from the EmulationScript XML format.

        :param str file_name: xml file to load session from
        :param bool start: instantiate session if true, false otherwise
        :return: nothing
        """
        # clear out existing session
        self.clear()

        # set default node class when one is not provided
        node_class = nodeutils.get_node_class(NodeTypes.DEFAULT)
        options = {"start": start, "nodecls": node_class}
        core_document_parser(self, file_name, options)
        if start:
            self.name = os.path.basename(file_name)
            self.file_name = file_name
            self.instantiate()
Beispiel #10
0
 def parsenodes(self):
     for node in self.np.getElementsByTagName("Node"):
         id, name, type = self.getcommonattributes(node)
         if type == "rj45":
             nodecls = nodeutils.get_node_class(NodeTypes.RJ45)
         else:
             nodecls = self.nodecls
         n = self.session.add_object(cls=nodecls,
                                     objid=id,
                                     name=name,
                                     start=self.start)
         if name in self.coords:
             x, y, z = self.coords[name]
             n.setposition(x, y, z)
         n.type = type
         xmlutils.get_params_set_attrs(node, ("icon", "canvas", "opaque"),
                                       n)
         if hasattr(n, "canvas") and n.canvas is not None:
             n.canvas = int(n.canvas)
         for ifc in node.getElementsByTagName("interface"):
             self.parseinterface(n, ifc)
Beispiel #11
0
    def add_node(self,
                 _type=NodeTypes.DEFAULT,
                 _id=None,
                 node_options=NodeOptions()):
        """
        Add a node to the session, based on the provided node data.

        :param core.enumerations.NodeTypes _type: type of node to create
        :param int _id: id for node, defaults to None for generated id
        :param core.emulator.emudata.NodeOptions node_options: data to create node with
        :return: created node
        """

        # retrieve node class for given node type
        try:
            node_class = nodeutils.get_node_class(_type)
        except KeyError:
            logger.error("invalid node type to create: %s", _type)
            return None

        # set node start based on current session state, override and check when rj45
        start = self.state > EventTypes.DEFINITION_STATE.value
        enable_rj45 = self.options.get_config("enablerj45") == "1"
        if _type == NodeTypes.RJ45 and not enable_rj45:
            start = False

        # determine node id
        if not _id:
            while True:
                _id = self.node_id_gen.next()
                if _id not in self.objects:
                    break

        # generate name if not provided
        name = node_options.name
        if not name:
            name = "%s%s" % (node_class.__name__, _id)

        # create node
        logger.info("creating node(%s) id(%s) name(%s) start(%s)",
                    node_class.__name__, _id, name, start)
        node = self.add_object(cls=node_class,
                               objid=_id,
                               name=name,
                               start=start)

        # set node attributes
        node.icon = node_options.icon
        node.canvas = node_options.canvas
        node.opaque = node_options.opaque

        # set node position and broadcast it
        self.set_node_position(node, node_options)

        # add services to default and physical nodes only
        if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]:
            node.type = node_options.model
            logger.debug("set node type: %s", node.type)
            self.services.add_services(node, node.type, node_options.services)

        # boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes
        is_boot_node = isinstance(
            node, PyCoreNode) and not nodeutils.is_node(node, NodeTypes.RJ45)
        if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node:
            self.write_objects()
            self.add_remove_control_interface(node=node, remove=False)
            self.services.boot_services(node)

        return node
Beispiel #12
0
    def add_link(self,
                 node_one_id,
                 node_two_id,
                 interface_one=None,
                 interface_two=None,
                 link_options=LinkOptions()):
        """
        Add a link between nodes.

        :param int node_one_id: node one id
        :param int node_two_id: node two id
        :param core.emulator.emudata.InterfaceData interface_one: node one interface data, defaults to none
        :param core.emulator.emudata.InterfaceData interface_two: node two interface data, defaults to none
        :param core.emulator.emudata.LinkOptions link_options: data for creating link, defaults to no options
        :return:
        """
        # get node objects identified by link data
        node_one, node_two, net_one, net_two, tunnel = self._link_nodes(
            node_one_id, node_two_id)

        if node_one:
            node_one.lock.acquire()
        if node_two:
            node_two.lock.acquire()

        try:
            # wireless link
            if link_options.type == LinkTypes.WIRELESS:
                objects = [node_one, node_two, net_one, net_two]
                self._link_wireless(objects, connect=True)
            # wired link
            else:
                # 2 nodes being linked, ptp network
                if all([node_one, node_two]) and not net_one:
                    logger.info("adding link for peer to peer nodes: %s - %s",
                                node_one.name, node_two.name)
                    ptp_class = nodeutils.get_node_class(
                        NodeTypes.PEER_TO_PEER)
                    start = self.state > EventTypes.DEFINITION_STATE.value
                    net_one = self.add_object(cls=ptp_class, start=start)

                # node to network
                if node_one and net_one:
                    logger.info("adding link from node to network: %s - %s",
                                node_one.name, net_one.name)
                    interface = create_interface(node_one, net_one,
                                                 interface_one)
                    link_config(net_one, interface, link_options)

                # network to node
                if node_two and net_one:
                    logger.info("adding link from network to node: %s - %s",
                                node_two.name, net_one.name)
                    interface = create_interface(node_two, net_one,
                                                 interface_two)
                    if not link_options.unidirectional:
                        link_config(net_one, interface, link_options)

                # network to network
                if net_one and net_two:
                    logger.info("adding link from network to network: %s",
                                net_one.name, net_two.name)
                    if nodeutils.is_node(net_two, NodeTypes.RJ45):
                        interface = net_two.linknet(net_one)
                    else:
                        interface = net_one.linknet(net_two)

                    link_config(net_one, interface, link_options)

                    if not link_options.unidirectional:
                        interface.swapparams("_params_up")
                        link_config(net_two,
                                    interface,
                                    link_options,
                                    devname=interface.name)
                        interface.swapparams("_params_up")

                # a tunnel node was found for the nodes
                addresses = []
                if not node_one and all([net_one, interface_one]):
                    addresses.extend(interface_one.get_addresses())

                if not node_two and all([net_two, interface_two]):
                    addresses.extend(interface_two.get_addresses())

                # tunnel node logic
                key = link_options.key
                if key and nodeutils.is_node(net_one, NodeTypes.TUNNEL):
                    logger.info("setting tunnel key for: %s", net_one.name)
                    net_one.setkey(key)
                    if addresses:
                        net_one.addrconfig(addresses)
                if key and nodeutils.is_node(net_two, NodeTypes.TUNNEL):
                    logger.info("setting tunnel key for: %s", net_two.name)
                    net_two.setkey(key)
                    if addresses:
                        net_two.addrconfig(addresses)

                # physical node connected with tunnel
                if not net_one and not net_two and (node_one or node_two):
                    if node_one and nodeutils.is_node(node_one,
                                                      NodeTypes.PHYSICAL):
                        logger.info("adding link for physical node: %s",
                                    node_one.name)
                        addresses = interface_one.get_addresses()
                        node_one.adoptnetif(tunnel, interface_one.id,
                                            interface_one.mac, addresses)
                        link_config(node_one, tunnel, link_options)
                    elif node_two and nodeutils.is_node(
                            node_two, NodeTypes.PHYSICAL):
                        logger.info("adding link for physical node: %s",
                                    node_two.name)
                        addresses = interface_two.get_addresses()
                        node_two.adoptnetif(tunnel, interface_two.id,
                                            interface_two.mac, addresses)
                        link_config(node_two, tunnel, link_options)
        finally:
            if node_one:
                node_one.lock.release()
            if node_two:
                node_two.lock.release()
Beispiel #13
0
    def handlenodemsg(self, message):
        """
        Determine and return the servers to which this node message should
        be forwarded. Also keep track of link-layer nodes and the mapping of
        nodes to servers.

        :param core.api.coreapi.CoreMessage message: message to handle
        :return: boolean for handling locally and set of servers
        :rtype: tuple
        """
        servers = set()
        handle_locally = False
        serverfiletxt = None

        # snoop Node Message for emulation server TLV and record mapping
        n = message.tlv_data[NodeTlvs.NUMBER.value]

        # replicate link-layer nodes on all servers
        nodetype = message.get_tlv(NodeTlvs.TYPE.value)
        if nodetype is not None:
            try:
                nodecls = nodeutils.get_node_class(NodeTypes(nodetype))
            except KeyError:
                logger.warn("broker invalid node type %s", nodetype)
                return handle_locally, servers
            if nodecls is None:
                logger.warn("broker unimplemented node type %s", nodetype)
                return handle_locally, servers
            if issubclass(nodecls, PyCoreNet) and nodetype != NodeTypes.WIRELESS_LAN.value:
                # network node replicated on all servers; could be optimized
                # don"t replicate WLANs, because ebtables rules won"t work
                servers = self.getservers()
                handle_locally = True
                self.addnet(n)
                for server in servers:
                    self.addnodemap(server, n)
                # do not record server name for networks since network
                # nodes are replicated across all server
                return handle_locally, servers
            elif issubclass(nodecls, PyCoreNode):
                name = message.get_tlv(NodeTlvs.NAME.value)
                if name:
                    serverfiletxt = "%s %s %s" % (n, name, nodecls)
                if issubclass(nodecls, PhysicalNode):
                    # remember physical nodes
                    self.addphys(n)

        # emulation server TLV specifies server
        servername = message.get_tlv(NodeTlvs.EMULATION_SERVER.value)
        server = self.getserverbyname(servername)
        if server is not None:
            self.addnodemap(server, n)
            if server not in servers:
                servers.add(server)
            if serverfiletxt and self.session.master:
                self.writenodeserver(serverfiletxt, server)

        # hook to update coordinates of physical nodes
        if n in self.physical_nodes:
            self.session.mobility.physnodeupdateposition(message)

        return handle_locally, servers
Beispiel #14
0
    def add_remove_control_net(self,
                               net_index,
                               remove=False,
                               conf_required=True):
        """
        Create a control network bridge as necessary.
        When the remove flag is True, remove the bridge that connects control
        interfaces. The conf_reqd flag, when False, causes a control network
        bridge to be added even if one has not been configured.

        :param int net_index: network index
        :param bool remove: flag to check if it should be removed
        :param bool conf_required: flag to check if conf is required
        :return: control net object
        :rtype: core.netns.nodes.CtrlNet
        """
        logger.debug(
            "add/remove control net: index(%s) remove(%s) conf_required(%s)",
            net_index, remove, conf_required)
        prefix_spec_list = self.get_control_net_prefixes()
        prefix_spec = prefix_spec_list[net_index]
        if not prefix_spec:
            if conf_required:
                # no controlnet needed
                return None
            else:
                control_net_class = nodeutils.get_node_class(
                    NodeTypes.CONTROL_NET)
                prefix_spec = control_net_class.DEFAULT_PREFIX_LIST[net_index]
        logger.debug("prefix spec: %s", prefix_spec)

        server_interface = self.get_control_net_server_interfaces()[net_index]

        # return any existing controlnet bridge
        try:
            control_net = self.get_control_net_object(net_index)

            if remove:
                self.delete_object(control_net.objid)
                return None

            return control_net
        except KeyError:
            if remove:
                return None

        # build a new controlnet bridge
        object_id = "ctrl%dnet" % net_index

        # use the updown script for control net 0 only.
        updown_script = None

        if net_index == 0:
            updown_script = self.options.get_config("controlnet_updown_script")
            if not updown_script:
                logger.warning("controlnet updown script not configured")

        prefixes = prefix_spec.split()
        if len(prefixes) > 1:
            # a list of per-host prefixes is provided
            assign_address = True
            if self.master:
                try:
                    # split first (master) entry into server and prefix
                    prefix = prefixes[0].split(":", 1)[1]
                except IndexError:
                    # no server name. possibly only one server
                    prefix = prefixes[0]
            else:
                # slave servers have their name and localhost in the serverlist
                servers = self.broker.getservernames()
                servers.remove("localhost")
                prefix = None

                for server_prefix in prefixes:
                    try:
                        # split each entry into server and prefix
                        server, p = server_prefix.split(":")
                    except ValueError:
                        server = ""
                        p = None

                    if server == servers[0]:
                        # the server name in the list matches this server
                        prefix = p
                        break

                if not prefix:
                    logger.error(
                        "Control network prefix not found for server '%s'" %
                        servers[0])
                    assign_address = False
                    try:
                        prefix = prefixes[0].split(':', 1)[1]
                    except IndexError:
                        prefix = prefixes[0]
        # len(prefixes) == 1
        else:
            # TODO: can we get the server name from the servers.conf or from the node assignments?
            # with one prefix, only master gets a ctrlnet address
            assign_address = self.master
            prefix = prefixes[0]

        control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
        control_net = self.add_object(cls=control_net_class,
                                      objid=object_id,
                                      prefix=prefix,
                                      assign_address=assign_address,
                                      updown_script=updown_script,
                                      serverintf=server_interface)

        # tunnels between controlnets will be built with Broker.addnettunnels()
        # TODO: potentially remove documentation saying object ids are ints
        # TODO: need to move broker code out of the session object
        self.broker.addnet(object_id)
        for server in self.broker.getservers():
            self.broker.addnodemap(server, object_id)

        return control_net
Beispiel #15
0
    def add_remove_control_net(self, net_index, remove=False, conf_required=True):
        """
        Create a control network bridge as necessary.
        When the remove flag is True, remove the bridge that connects control
        interfaces. The conf_reqd flag, when False, causes a control network
        bridge to be added even if one has not been configured.

        :param int net_index: network index
        :param bool remove: flag to check if it should be removed
        :param bool conf_required: flag to check if conf is required
        :return: control net object
        :rtype: core.netns.nodes.CtrlNet
        """
        logger.debug("add/remove control net: index(%s) remove(%s) conf_required(%s)", net_index, remove, conf_required)
        prefix_spec_list = self.get_control_net_prefixes()
        prefix_spec = prefix_spec_list[net_index]
        if not prefix_spec:
            if conf_required:
                # no controlnet needed
                return None
            else:
                control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
                prefix_spec = control_net_class.DEFAULT_PREFIX_LIST[net_index]
        logger.debug("prefix spec: %s", prefix_spec)

        server_interface = self.get_control_net_server_interfaces()[net_index]

        # return any existing controlnet bridge
        try:
            control_net = self.get_control_net_object(net_index)

            if remove:
                self.delete_object(control_net.objid)
                return None

            return control_net
        except KeyError:
            if remove:
                return None

        # build a new controlnet bridge
        object_id = "ctrl%dnet" % net_index

        # use the updown script for control net 0 only.
        updown_script = None

        if net_index == 0:
            updown_script = self.options.get_config("controlnet_updown_script")
            if not updown_script:
                logger.warning("controlnet updown script not configured")

        prefixes = prefix_spec.split()
        if len(prefixes) > 1:
            # a list of per-host prefixes is provided
            assign_address = True
            if self.master:
                try:
                    # split first (master) entry into server and prefix
                    prefix = prefixes[0].split(":", 1)[1]
                except IndexError:
                    # no server name. possibly only one server
                    prefix = prefixes[0]
            else:
                # slave servers have their name and localhost in the serverlist
                servers = self.broker.getservernames()
                servers.remove("localhost")
                prefix = None

                for server_prefix in prefixes:
                    try:
                        # split each entry into server and prefix
                        server, p = server_prefix.split(":")
                    except ValueError:
                        server = ""
                        p = None

                    if server == servers[0]:
                        # the server name in the list matches this server
                        prefix = p
                        break

                if not prefix:
                    logger.error("Control network prefix not found for server '%s'" % servers[0])
                    assign_address = False
                    try:
                        prefix = prefixes[0].split(':', 1)[1]
                    except IndexError:
                        prefix = prefixes[0]
        # len(prefixes) == 1
        else:
            # TODO: can we get the server name from the servers.conf or from the node assignments?
            # with one prefix, only master gets a ctrlnet address
            assign_address = self.master
            prefix = prefixes[0]

        control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
        control_net = self.add_object(cls=control_net_class, objid=object_id, prefix=prefix,
                                      assign_address=assign_address,
                                      updown_script=updown_script, serverintf=server_interface)

        # tunnels between controlnets will be built with Broker.addnettunnels()
        # TODO: potentially remove documentation saying object ids are ints
        # TODO: need to move broker code out of the session object
        self.broker.addnet(object_id)
        for server in self.broker.getservers():
            self.broker.addnodemap(server, object_id)

        return control_net