daemon: initial pass to revamp how node linking and link management is done, provides a consistent way to link all wired nodes and allows them to be configured for tc for the same behavior across the board

This commit is contained in:
Blake Harnden 2022-03-17 15:28:38 -07:00
parent d684b8eb5a
commit cd7f1a641e
19 changed files with 1393 additions and 1556 deletions

View file

@ -35,10 +35,10 @@ from core.emulator.distributed import DistributedController
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
NodeTypes,
)
from core.emulator.links import CoreLink, LinkManager
from core.emulator.sessionconfig import SessionConfig
from core.errors import CoreError
from core.location.event import EventLoop
@ -86,6 +86,7 @@ CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode}
CTRL_NET_ID: int = 9001
LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"]
NT: TypeVar = TypeVar("NT", bound=NodeBase)
WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet]] = (WlanNode, EmaneNet)
class Session:
@ -119,7 +120,8 @@ class Session:
# dict of nodes: all nodes and nets
self.nodes: Dict[int, NodeBase] = {}
self.nodes_lock = threading.Lock()
self.nodes_lock: threading.Lock = threading.Lock()
self.link_manager: LinkManager = LinkManager()
# states and hooks handlers
self.state: EventTypes = EventTypes.DEFINITION_STATE
@ -187,40 +189,6 @@ class Session:
raise CoreError(f"invalid node class: {_class}")
return node_type
def _link_wireless(
self, node1: CoreNodeBase, node2: CoreNodeBase, connect: bool
) -> None:
"""
Objects to deal with when connecting/disconnecting wireless links.
:param node1: node one for wireless link
:param node2: node two for wireless link
:param connect: link interfaces if True, unlink otherwise
:return: nothing
:raises core.CoreError: when objects to link is less than 2, or no common
networks are found
"""
logger.info(
"handling wireless linking node1(%s) node2(%s): %s",
node1.name,
node2.name,
connect,
)
common_networks = node1.commonnets(node1)
if not common_networks:
raise CoreError("no common network found for wireless link/unlink")
for common_network, iface1, iface2 in common_networks:
if not isinstance(common_network, (WlanNode, EmaneNet)):
logger.info(
"skipping common network that is not wireless/emane: %s",
common_network,
)
continue
if connect:
common_network.link(iface1, iface2)
else:
common_network.unlink(iface1, iface2)
def use_ovs(self) -> bool:
return self.options.get_config("ovs") == "1"
@ -231,8 +199,7 @@ class Session:
iface1_data: InterfaceData = None,
iface2_data: InterfaceData = None,
options: LinkOptions = None,
link_type: LinkTypes = LinkTypes.WIRED,
) -> Tuple[CoreInterface, CoreInterface]:
) -> Tuple[Optional[CoreInterface], Optional[CoreInterface]]:
"""
Add a link between nodes.
@ -244,89 +211,120 @@ class Session:
data, defaults to none
:param options: data for creating link,
defaults to no options
:param link_type: type of link to add
:return: tuple of created core interfaces, depending on link
"""
if not options:
options = LinkOptions()
node1 = self.get_node(node1_id, NodeBase)
node2 = self.get_node(node2_id, NodeBase)
iface1 = None
iface2 = None
options = options if options else LinkOptions()
# set mtu
mtu = self.options.get_config_int("mtu") or DEFAULT_MTU
if iface1_data:
iface1_data.mtu = mtu
if iface2_data:
iface2_data.mtu = mtu
# wireless link
if link_type == LinkTypes.WIRELESS:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
self._link_wireless(node1, node2, connect=True)
else:
raise CoreError(
f"cannot wireless link node1({type(node1)}) node2({type(node2)})"
)
# wired link
node1 = self.get_node(node1_id, NodeBase)
node2 = self.get_node(node2_id, NodeBase)
# check for invalid linking
if isinstance(node1, WIRELESS_TYPE) and isinstance(node2, WIRELESS_TYPE):
raise CoreError(f"cannot link node({type(node1)}) node({type(node2)})")
# custom links
iface1 = None
iface2 = None
if isinstance(node1, WlanNode):
iface2 = self._add_wlan_link(node2, iface2_data, node1)
elif isinstance(node2, WlanNode):
iface1 = self._add_wlan_link(node1, iface1_data, node2)
elif isinstance(node1, EmaneNet) and isinstance(node2, CoreNode):
iface2 = self._add_emane_link(node2, iface2_data, node1)
elif isinstance(node2, EmaneNet) and isinstance(node1, CoreNode):
iface1 = self._add_emane_link(node1, iface1_data, node2)
else:
# peer to peer link
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
logger.info("linking ptp: %s - %s", node1.name, node2.name)
start = self.state.should_start()
ptp = self.create_node(PtpNet, start)
iface1 = node1.new_iface(ptp, iface1_data)
iface2 = node2.new_iface(ptp, iface2_data)
iface1.config(options)
if not options.unidirectional:
iface2.config(options)
# link node to net
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
logger.info("linking node to net: %s - %s", node1.name, node2.name)
iface1 = node1.new_iface(node2, iface1_data)
if not isinstance(node2, (EmaneNet, WlanNode)):
iface1.config(options)
# link net to node
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
logger.info("linking net to node: %s - %s", node1.name, node2.name)
iface2 = node2.new_iface(node1, iface2_data)
wireless_net = isinstance(node1, (EmaneNet, WlanNode))
if not options.unidirectional and not wireless_net:
iface2.config(options)
# network to network
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
logger.info(
"linking network to network: %s - %s", node1.name, node2.name
)
iface1 = node1.linknet(node2)
use_local = iface1.net == node1
iface1.config(options, use_local=use_local)
if not options.unidirectional:
iface1.config(options, use_local=not use_local)
else:
raise CoreError(
f"cannot link node1({type(node1)}) node2({type(node2)})"
)
# configure tunnel nodes
key = options.key
if isinstance(node1, TunnelNode):
logger.info("setting tunnel key for: %s", node1.name)
node1.setkey(key, iface1_data)
if isinstance(node2, TunnelNode):
logger.info("setting tunnel key for: %s", node2.name)
node2.setkey(key, iface2_data)
iface1, iface2 = self._add_wired_link(
node1, node2, iface1_data, iface2_data, options
)
# configure tunnel nodes
key = options.key
if isinstance(node1, TunnelNode):
logger.info("setting tunnel key for: %s", node1.name)
node1.setkey(key, iface1_data)
if isinstance(node2, TunnelNode):
logger.info("setting tunnel key for: %s", node2.name)
node2.setkey(key, iface2_data)
self.sdt.add_link(node1_id, node2_id)
return iface1, iface2
def delete_link(
def _add_wlan_link(
self, node: NodeBase, iface_data: InterfaceData, net: WlanNode
) -> CoreInterface:
"""
Create a wlan link.
:param node: node to link to wlan network
:param iface_data: data to create interface with
:param net: wlan network to link to
:return: interface created for node
"""
# create interface
iface = node.create_iface(iface_data)
# attach to wlan
net.attach(iface)
# track link
core_link = CoreLink(node, iface, net, None)
self.link_manager.add(core_link)
return iface
def _add_emane_link(
self, node: CoreNode, iface_data: InterfaceData, net: EmaneNet
) -> CoreInterface:
"""
Create am emane link.
:param node: node to link to emane network
:param iface_data: data to create interface with
:param net: emane network to link to
:return: interface created for node
"""
# create iface tuntap
iface = net.create_tuntap(node, iface_data)
# track link
core_link = CoreLink(node, iface, net, None)
self.link_manager.add(core_link)
return iface
def _add_wired_link(
self,
node1_id: int,
node2_id: int,
iface1_id: int = None,
iface2_id: int = None,
link_type: LinkTypes = LinkTypes.WIRED,
node1: NodeBase,
node2: NodeBase,
iface1_data: InterfaceData = None,
iface2_data: InterfaceData = None,
options: LinkOptions = None,
) -> Tuple[CoreInterface, CoreInterface]:
"""
Create a wired link between two nodes.
:param node1: first node to be linked
:param node2: second node to be linked
:param iface1_data: data to create interface for node1
:param iface2_data: data to create interface for node2
:param options: options to configure interfaces with
:return: interfaces created for both nodes
"""
# create interface
if iface1_data and isinstance(node1, CoreNetworkBase):
iface1_data.id = None
iface1 = node1.create_iface(iface1_data, options)
if iface2_data and isinstance(node2, CoreNetworkBase):
iface2_data.id = None
iface2 = node2.create_iface(iface2_data, options)
# join and attach to ptp bridge
ptp = self.create_node(PtpNet, self.state.should_start())
ptp.attach(iface1)
ptp.attach(iface2)
# track link
core_link = CoreLink(node1, iface1, node2, iface2, ptp)
self.link_manager.add(core_link)
return iface1, iface2
def delete_link(
self, node1_id: int, node2_id: int, iface1_id: int = None, iface2_id: int = None
) -> None:
"""
Delete a link between nodes.
@ -335,63 +333,38 @@ class Session:
:param node2_id: node two id
:param iface1_id: interface id for node one
:param iface2_id: interface id for node two
:param link_type: link type to delete
:return: nothing
:raises core.CoreError: when no common network is found for link being deleted
"""
node1 = self.get_node(node1_id, NodeBase)
node2 = self.get_node(node2_id, NodeBase)
logger.info(
"deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)",
link_type.name,
"deleting link node(%s):interface(%s) node(%s):interface(%s)",
node1.name,
iface1_id,
node2.name,
iface2_id,
)
# wireless link
if link_type == LinkTypes.WIRELESS:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
self._link_wireless(node1, node2, connect=False)
else:
raise CoreError(
"cannot delete wireless link "
f"node1({type(node1)}) node2({type(node2)})"
)
# wired link
iface1 = None
iface2 = None
if isinstance(node1, WlanNode):
iface2 = node2.delete_iface(iface2_id)
node1.detach(iface2)
elif isinstance(node2, WlanNode):
iface1 = node1.delete_iface(iface1_id)
node2.detach(iface1)
elif isinstance(node1, EmaneNet):
iface2 = node2.delete_iface(iface2_id)
node1.detach(iface2)
elif isinstance(node2, EmaneNet):
iface1 = node1.delete_iface(iface1_id)
node2.detach(iface1)
else:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
iface1 = node1.get_iface(iface1_id)
iface2 = node2.get_iface(iface2_id)
if iface1.net != iface2.net:
raise CoreError(
f"node1({node1.name}) node2({node2.name}) "
"not connected to same net"
)
ptp = iface1.net
node1.delete_iface(iface1_id)
node2.delete_iface(iface2_id)
self.delete_node(ptp.id)
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
node1.delete_iface(iface1_id)
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
node2.delete_iface(iface2_id)
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
iface1 = node1.get_linked_iface(node2)
if iface1:
node1.detach(iface1)
iface1.shutdown()
iface2 = node2.get_linked_iface(node1)
if iface2:
node2.detach(iface2)
iface2.shutdown()
if not iface1 and not iface2:
raise CoreError(
f"node1({node1.name}) and node2({node2.name}) are not connected"
)
iface1 = node1.delete_iface(iface1_id)
iface2 = node2.delete_iface(iface2_id)
core_link = self.link_manager.delete(node1, iface1, node2, iface2)
if core_link.ptp:
self.delete_node(core_link.ptp.id)
self.sdt.delete_link(node1_id, node2_id)
def update_link(
@ -401,7 +374,6 @@ class Session:
iface1_id: int = None,
iface2_id: int = None,
options: LinkOptions = None,
link_type: LinkTypes = LinkTypes.WIRED,
) -> None:
"""
Update link information between nodes.
@ -411,7 +383,6 @@ class Session:
:param iface1_id: interface id for node one
:param iface2_id: interface id for node two
:param options: data to update link with
:param link_type: type of link to update
:return: nothing
:raises core.CoreError: when updating a wireless type link, when there is a
unknown link between networks
@ -421,72 +392,28 @@ class Session:
node1 = self.get_node(node1_id, NodeBase)
node2 = self.get_node(node2_id, NodeBase)
logger.info(
"update link(%s) node(%s):interface(%s) node(%s):interface(%s)",
link_type.name,
"update link node(%s):interface(%s) node(%s):interface(%s)",
node1.name,
iface1_id,
node2.name,
iface2_id,
)
# wireless link
if link_type == LinkTypes.WIRELESS:
raise CoreError("cannot update wireless link")
else:
if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase):
iface1 = node1.ifaces.get(iface1_id)
if not iface1:
raise CoreError(
f"node({node1.name}) missing interface({iface1_id})"
)
iface2 = node2.ifaces.get(iface2_id)
if not iface2:
raise CoreError(
f"node({node2.name}) missing interface({iface2_id})"
)
if iface1.net != iface2.net:
raise CoreError(
f"node1({node1.name}) node2({node2.name}) "
"not connected to same net"
)
iface1.config(options)
if not options.unidirectional:
iface2.config(options)
elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase):
iface = node1.get_iface(iface1_id)
if iface.net != node2:
raise CoreError(
f"node1({node1.name}) iface1({iface1_id})"
f" is not linked to node1({node2.name})"
)
iface.config(options)
elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase):
iface = node2.get_iface(iface2_id)
if iface.net != node1:
raise CoreError(
f"node2({node2.name}) iface2({iface2_id})"
f" is not linked to node1({node1.name})"
)
iface.config(options)
elif isinstance(node1, CoreNetworkBase) and isinstance(
node2, CoreNetworkBase
):
iface = node1.get_linked_iface(node2)
if not iface:
iface = node2.get_linked_iface(node1)
if iface:
use_local = iface.net == node1
iface.config(options, use_local=use_local)
if not options.unidirectional:
iface.config(options, use_local=not use_local)
else:
raise CoreError(
f"node1({node1.name}) and node2({node2.name}) are not linked"
)
else:
raise CoreError(
f"cannot update link node1({type(node1)}) node2({type(node2)})"
)
iface1 = node1.get_iface(iface1_id) if iface1_id is not None else None
iface2 = node2.get_iface(iface2_id) if iface2_id is not None else None
core_link = self.link_manager.get_link(node1, iface1, node2, iface2)
if not core_link:
raise CoreError(
f"there is no link for node({node1.name}):interface({iface1_id}) "
f"node({node2.name}):interface({iface2_id})"
)
if iface1:
iface1.options.update(options)
node1_input = node1 if isinstance(node1, CoreNode) else None
iface1.set_config(node1_input)
if iface2 and not options.unidirectional:
iface2.options.update(options)
node2_input = node2 if isinstance(node2, CoreNode) else None
iface2.set_config(node2_input)
def next_node_id(self) -> int:
"""
@ -723,6 +650,7 @@ class Session:
"""
self.emane.shutdown()
self.delete_nodes()
self.link_manager.reset()
self.distributed.shutdown()
self.hooks.clear()
self.emane.reset()
@ -1480,7 +1408,8 @@ class Session:
ip4_mask=ip4_mask,
mtu=DEFAULT_MTU,
)
iface = node.new_iface(control_net, iface_data)
iface = node.create_iface(iface_data)
control_net.attach(iface)
iface.control = True
except ValueError:
msg = f"Control interface not added to node {node.id}. "