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

@ -13,11 +13,11 @@ import netaddr
from core import utils
from core.configservice.dependencies import ConfigServiceDependencies
from core.emulator.data import InterfaceData, LinkData
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.emulator.data import InterfaceData, LinkOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.errors import CoreCommandError, CoreError
from core.executables import BASH, MOUNT, TEST, VCMD, VNODED
from core.nodes.interface import DEFAULT_MTU, CoreInterface, TunTap, Veth
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.netclient import LinuxNetClient, get_net_client
logger = logging.getLogger(__name__)
@ -74,6 +74,7 @@ class NodeBase(abc.ABC):
self.icon: Optional[str] = None
self.position: Position = Position()
self.up: bool = False
self.lock: RLock = RLock()
self.net_client: LinuxNetClient = get_net_client(
self.session.use_ovs(), self.host_cmd
)
@ -96,6 +97,18 @@ class NodeBase(abc.ABC):
"""
raise NotImplementedError
@abc.abstractmethod
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
"""
Adopt an interface, placing within network namespacing for containers
and setting to bridge masters for network like nodes.
:param iface: interface to adopt
:param name: proper name to use for interface
:return: nothing
"""
raise NotImplementedError
def host_cmd(
self,
args: str,
@ -139,6 +152,70 @@ class NodeBase(abc.ABC):
"""
return self.position.get()
def create_iface(
self, iface_data: InterfaceData = None, options: LinkOptions = None
) -> CoreInterface:
"""
Creates an interface and adopts it to a node.
:param iface_data: data to create interface with
:param options: options to create interface with
:return: created interface
"""
with self.lock:
if iface_data and iface_data.id is not None:
if iface_data.id in self.ifaces:
raise CoreError(
f"node({self.id}) interface({iface_data.id}) already exists"
)
iface_id = iface_data.id
else:
iface_id = self.next_iface_id()
mtu = DEFAULT_MTU
if iface_data and iface_data.mtu is not None:
mtu = iface_data.mtu
name = f"veth{self.id}.{iface_id}.{self.session.short_session_id()}"
localname = f"{name}p"
iface = CoreInterface(
iface_id,
name,
localname,
self.session.use_ovs(),
mtu,
node=self,
server=self.server,
)
if iface_data:
if iface_data.mac:
iface.set_mac(iface_data.mac)
for ip in iface_data.get_ips():
iface.add_ip(ip)
if options:
iface.options.update(options)
self.ifaces[iface_id] = iface
if self.up:
iface.startup()
if iface_data and iface_data.name is not None:
name = iface_data.name
else:
name = iface.name
self.adopt_iface(iface, name)
return iface
def delete_iface(self, iface_id: int) -> CoreInterface:
"""
Delete an interface.
:param iface_id: interface id to delete
:return: the removed interface
"""
if iface_id not in self.ifaces:
raise CoreError(f"node({self.name}) interface({iface_id}) does not exist")
iface = self.ifaces.pop(iface_id)
logger.info("node(%s) removing interface(%s)", self.name, iface.name)
iface.shutdown()
return iface
def get_iface(self, iface_id: int) -> CoreInterface:
"""
Retrieve interface based on id.
@ -191,15 +268,6 @@ class NodeBase(abc.ABC):
self.iface_id += 1
return iface_id
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build link data for this node.
:param flags: message flags
:return: list of link data
"""
return []
class CoreNodeBase(NodeBase):
"""
@ -227,14 +295,6 @@ class CoreNodeBase(NodeBase):
self.directory: Optional[Path] = None
self.tmpnodedir: bool = False
@abc.abstractmethod
def startup(self) -> None:
raise NotImplementedError
@abc.abstractmethod
def shutdown(self) -> None:
raise NotImplementedError
@abc.abstractmethod
def create_dir(self, dir_path: Path) -> None:
"""
@ -293,19 +353,6 @@ class CoreNodeBase(NodeBase):
"""
raise NotImplementedError
@abc.abstractmethod
def new_iface(
self, net: "CoreNetworkBase", iface_data: InterfaceData
) -> CoreInterface:
"""
Create a new interface.
:param net: network to associate with
:param iface_data: interface data for new interface
:return: interface index
"""
raise NotImplementedError
@abc.abstractmethod
def path_exists(self, path: str) -> bool:
"""
@ -318,7 +365,7 @@ class CoreNodeBase(NodeBase):
def host_path(self, path: Path, is_dir: bool = False) -> Path:
"""
Return the name of a node"s file on the host filesystem.
Return the name of a node's file on the host filesystem.
:param path: path to translate to host path
:param is_dir: True if path is a directory path, False otherwise
@ -393,54 +440,6 @@ class CoreNodeBase(NodeBase):
if self.tmpnodedir:
self.host_cmd(f"rm -rf {self.directory}")
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
"""
Add network interface to node and set the network interface index if successful.
:param iface: network interface to add
:param iface_id: interface id
:return: nothing
"""
if iface_id in self.ifaces:
raise CoreError(f"interface({iface_id}) already exists")
self.ifaces[iface_id] = iface
iface.node_id = iface_id
def delete_iface(self, iface_id: int) -> None:
"""
Delete a network interface
:param iface_id: interface index to delete
:return: nothing
"""
if iface_id not in self.ifaces:
raise CoreError(f"node({self.name}) interface({iface_id}) does not exist")
iface = self.ifaces.pop(iface_id)
logger.info("node(%s) removing interface(%s)", self.name, iface.name)
iface.detachnet()
iface.shutdown()
def attachnet(self, iface_id: int, net: "CoreNetworkBase") -> None:
"""
Attach a network.
:param iface_id: interface of index to attach
:param net: network to attach
:return: nothing
"""
iface = self.get_iface(iface_id)
iface.attachnet(net)
def detachnet(self, iface_id: int) -> None:
"""
Detach network interface.
:param iface_id: interface id to detach
:return: nothing
"""
iface = self.get_iface(iface_id)
iface.detachnet()
def setposition(self, x: float = None, y: float = None, z: float = None) -> None:
"""
Set position.
@ -455,25 +454,6 @@ class CoreNodeBase(NodeBase):
for iface in self.get_ifaces():
iface.setposition()
def commonnets(
self, node: "CoreNodeBase", want_ctrl: bool = False
) -> List[Tuple["CoreNetworkBase", CoreInterface, CoreInterface]]:
"""
Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple
consisting of (network, interface1, interface2).
:param node: node to get common network with
:param want_ctrl: flag set to determine if control network are wanted
:return: tuples of common networks
"""
common = []
for iface1 in self.get_ifaces(control=want_ctrl):
for iface2 in node.get_ifaces():
if iface1.net == iface2.net:
common.append((iface1.net, iface1, iface2))
return common
class CoreNode(CoreNodeBase):
"""
@ -504,7 +484,6 @@ class CoreNode(CoreNodeBase):
self.directory: Optional[Path] = directory
self.ctrlchnlname: Path = self.session.directory / self.name
self.pid: Optional[int] = None
self.lock: RLock = RLock()
self._mounts: List[Tuple[Path, Path]] = []
self.node_net_client: LinuxNetClient = self.create_node_net_client(
self.session.use_ovs()
@ -585,6 +564,10 @@ class CoreNode(CoreNodeBase):
self._mounts = []
# shutdown all interfaces
for iface in self.get_ifaces():
try:
self.node_net_client.device_flush(iface.name)
except CoreCommandError:
pass
iface.shutdown()
# kill node process if present
try:
@ -691,150 +674,6 @@ class CoreNode(CoreNodeBase):
self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}")
self._mounts.append((src_path, target_path))
def next_iface_id(self) -> int:
"""
Retrieve a new interface index.
:return: new interface index
"""
with self.lock:
return super().next_iface_id()
def newveth(self, iface_id: int = None, ifname: str = None, mtu: int = None) -> int:
"""
Create a new interface.
:param iface_id: id for the new interface
:param ifname: name for the new interface
:param mtu: mtu for interface
:return: nothing
"""
with self.lock:
mtu = mtu if mtu is not None else DEFAULT_MTU
iface_id = iface_id if iface_id is not None else self.next_iface_id()
ifname = ifname if ifname is not None else f"eth{iface_id}"
sessionid = self.session.short_session_id()
try:
suffix = f"{self.id:x}.{iface_id}.{sessionid}"
except TypeError:
suffix = f"{self.id}.{iface_id}.{sessionid}"
localname = f"veth{suffix}"
name = f"{localname}p"
veth = Veth(self.session, name, localname, mtu, self.server, self)
veth.adopt_node(iface_id, ifname, self.up)
return iface_id
def newtuntap(self, iface_id: int = None, ifname: str = None) -> int:
"""
Create a new tunnel tap.
:param iface_id: interface id
:param ifname: interface name
:return: interface index
"""
with self.lock:
iface_id = iface_id if iface_id is not None else self.next_iface_id()
ifname = ifname if ifname is not None else f"eth{iface_id}"
sessionid = self.session.short_session_id()
localname = f"tap{self.id}.{iface_id}.{sessionid}"
name = ifname
tuntap = TunTap(self.session, name, localname, node=self)
if self.up:
tuntap.startup()
try:
self.add_iface(tuntap, iface_id)
except CoreError as e:
tuntap.shutdown()
raise e
return iface_id
def set_mac(self, iface_id: int, mac: str) -> None:
"""
Set hardware address for an interface.
:param iface_id: id of interface to set hardware address for
:param mac: mac address to set
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
iface = self.get_iface(iface_id)
iface.set_mac(mac)
if self.up:
self.node_net_client.device_mac(iface.name, str(iface.mac))
def add_ip(self, iface_id: int, ip: str) -> None:
"""
Add an ip address to an interface in the format "10.0.0.1/24".
:param iface_id: id of interface to add address to
:param ip: address to add to interface
:return: nothing
:raises CoreError: when ip address provided is invalid
:raises CoreCommandError: when a non-zero exit status occurs
"""
iface = self.get_iface(iface_id)
iface.add_ip(ip)
if self.up:
# ipv4 check
broadcast = None
if netaddr.valid_ipv4(ip):
broadcast = "+"
self.node_net_client.create_address(iface.name, ip, broadcast)
def remove_ip(self, iface_id: int, ip: str) -> None:
"""
Remove an ip address from an interface in the format "10.0.0.1/24".
:param iface_id: id of interface to delete address from
:param ip: ip address to remove from interface
:return: nothing
:raises CoreError: when ip address provided is invalid
:raises CoreCommandError: when a non-zero exit status occurs
"""
iface = self.get_iface(iface_id)
iface.remove_ip(ip)
if self.up:
self.node_net_client.delete_address(iface.name, ip)
def ifup(self, iface_id: int) -> None:
"""
Bring an interface up.
:param iface_id: index of interface to bring up
:return: nothing
"""
if self.up:
iface = self.get_iface(iface_id)
self.node_net_client.device_up(iface.name)
def new_iface(
self, net: "CoreNetworkBase", iface_data: InterfaceData
) -> CoreInterface:
"""
Create a new network interface.
:param net: network to associate with
:param iface_data: interface data for new interface
:return: interface index
"""
with self.lock:
if net.has_custom_iface:
return net.custom_iface(self, iface_data)
else:
iface_id = iface_data.id
if iface_id is not None and iface_id in self.ifaces:
raise CoreError(
f"node({self.name}) already has interface({iface_id})"
)
iface_id = self.newveth(iface_id, iface_data.name, iface_data.mtu)
self.attachnet(iface_id, net)
if iface_data.mac:
self.set_mac(iface_id, iface_data.mac)
for ip in iface_data.get_ips():
self.add_ip(iface_id, ip)
self.ifup(iface_id)
return self.get_iface(iface_id)
def _find_parent_path(self, path: Path) -> Optional[Path]:
"""
Check if there is a mounted parent directory created for this node.
@ -910,6 +749,47 @@ class CoreNode(CoreNodeBase):
if mode is not None:
self.host_cmd(f"chmod {mode:o} {host_path}")
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
"""
Adopt interface to the network namespace of the node and setting
the proper name provided.
:param iface: interface to adopt
:param name: proper name for interface
:return: nothing
"""
# TODO: container, checksums off (container only?)
# TODO: container, get flow id (container only?)
# validate iface belongs to node and get id
iface_id = self.get_iface_id(iface)
if iface_id == -1:
raise CoreError(f"adopting unknown iface({iface.name})")
# add iface to container namespace
self.net_client.device_ns(iface.name, str(self.pid))
# update iface name to container name
name = name if name else f"eth{iface_id}"
self.node_net_client.device_name(iface.name, name)
iface.name = name
# turn checksums off
self.node_net_client.checksums_off(iface.name)
# retrieve flow id for container
iface.flow_id = self.node_net_client.get_ifindex(iface.name)
logger.debug("interface flow index: %s - %s", iface.name, iface.flow_id)
# set mac address
self.node_net_client.device_mac(iface.name, str(iface.mac))
logger.debug("interface mac: %s - %s", iface.name, iface.mac)
# set all addresses
for ip in iface.ips():
# ipv4 check
broadcast = None
if netaddr.valid_ipv4(ip):
broadcast = "+"
self.node_net_client.create_address(iface.name, str(ip), broadcast)
# configure iface options
iface.set_config(self)
# set iface up
self.node_net_client.device_up(iface.name)
class CoreNetworkBase(NodeBase):
"""
@ -917,7 +797,6 @@ class CoreNetworkBase(NodeBase):
"""
linktype: LinkTypes = LinkTypes.WIRED
has_custom_iface: bool = False
def __init__(
self,
@ -941,57 +820,6 @@ class CoreNetworkBase(NodeBase):
self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {}
self.linked_lock: threading.Lock = threading.Lock()
@abc.abstractmethod
def startup(self) -> None:
"""
Each object implements its own startup method.
:return: nothing
"""
raise NotImplementedError
@abc.abstractmethod
def shutdown(self) -> None:
"""
Each object implements its own shutdown method.
:return: nothing
"""
raise NotImplementedError
@abc.abstractmethod
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
"""
Link network to another.
:param net: network to link with
:return: created interface
"""
raise NotImplementedError
@abc.abstractmethod
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
"""
Defines custom logic for creating an interface, if required.
:param node: node to create interface for
:param iface_data: data for creating interface
:return: created interface
"""
raise NotImplementedError
def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]:
"""
Return the interface that links this net with another net.
:param net: interface to get link for
:return: interface the provided network is linked to
"""
for iface in self.get_ifaces():
if iface.othernet == net:
return iface
return None
def attach(self, iface: CoreInterface) -> None:
"""
Attach network interface.
@ -1001,6 +829,7 @@ class CoreNetworkBase(NodeBase):
"""
i = self.next_iface_id()
self.ifaces[i] = iface
iface.net = self
iface.net_id = i
with self.linked_lock:
self.linked[iface] = {}
@ -1013,56 +842,11 @@ class CoreNetworkBase(NodeBase):
:return: nothing
"""
del self.ifaces[iface.net_id]
iface.net = None
iface.net_id = None
with self.linked_lock:
del self.linked[iface]
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build link data objects for this network. Each link object describes a link
between this network and a node.
:param flags: message type
:return: list of link data
"""
all_links = []
# build a link message from this network node to each node having a
# connected interface
for iface in self.get_ifaces():
unidirectional = 0
linked_node = iface.node
if linked_node is None:
# two layer-2 switches/hubs linked together
if not iface.othernet:
continue
linked_node = iface.othernet
if linked_node.id == self.id:
continue
if iface.local_options != iface.options:
unidirectional = 1
iface_data = iface.get_data()
link_data = LinkData(
message_type=flags,
type=self.linktype,
node1_id=self.id,
node2_id=linked_node.id,
iface2=iface_data,
options=iface.local_options,
)
link_data.options.unidirectional = unidirectional
all_links.append(link_data)
if unidirectional:
link_data = LinkData(
message_type=MessageFlags.NONE,
type=self.linktype,
node1_id=linked_node.id,
node2_id=self.id,
options=iface.options,
)
link_data.options.unidirectional = unidirectional
all_links.append(link_data)
return all_links
class Position:
"""

View file

@ -4,7 +4,6 @@ virtual ethernet classes that implement the interfaces available under Linux.
import logging
import math
import time
from pathlib import Path
from typing import TYPE_CHECKING, Callable, Dict, List, Optional
@ -20,11 +19,12 @@ from core.nodes.netclient import LinuxNetClient, get_net_client
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.emulator.distributed import DistributedServer
from core.emulator.session import Session
from core.nodes.base import CoreNetworkBase, CoreNode
from core.emulator.distributed import DistributedServer
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
DEFAULT_MTU: int = 1500
IFACE_NAME_LENGTH: int = 15
def tc_clear_cmd(name: str) -> str:
@ -78,35 +78,42 @@ class CoreInterface:
def __init__(
self,
session: "Session",
_id: int,
name: str,
localname: str,
use_ovs: bool,
mtu: int = DEFAULT_MTU,
node: "NodeBase" = None,
server: "DistributedServer" = None,
node: "CoreNode" = None,
) -> None:
"""
Creates a CoreInterface instance.
:param session: core session instance
:param _id: interface id for associated node
:param name: interface name
:param localname: interface local name
:param use_ovs: True to use ovs, False otherwise
:param mtu: mtu value
:param node: node associated with this interface
:param server: remote server node will run on, default is None for localhost
:param node: node for interface
"""
if len(name) >= 16:
raise CoreError(f"interface name ({name}) too long, max 16")
if len(localname) >= 16:
raise CoreError(f"interface local name ({localname}) too long, max 16")
self.session: "Session" = session
self.node: Optional["CoreNode"] = node
if len(name) >= IFACE_NAME_LENGTH:
raise CoreError(
f"interface name ({name}) too long, max {IFACE_NAME_LENGTH}"
)
if len(localname) >= IFACE_NAME_LENGTH:
raise CoreError(
f"interface local name ({localname}) too long, max {IFACE_NAME_LENGTH}"
)
self.id: int = _id
self.node: Optional["NodeBase"] = node
# id of interface for network, used by wlan/emane
self.net_id: Optional[int] = None
self.name: str = name
self.localname: str = localname
self.up: bool = False
self.mtu: int = mtu
self.net: Optional[CoreNetworkBase] = None
self.othernet: Optional[CoreNetworkBase] = None
self.ip4s: List[netaddr.IPNetwork] = []
self.ip6s: List[netaddr.IPNetwork] = []
self.mac: Optional[netaddr.EUI] = None
@ -114,20 +121,12 @@ class CoreInterface:
self.poshook: Callable[[CoreInterface], None] = lambda x: None
# used with EMANE
self.transport_type: TransportType = TransportType.VIRTUAL
# id of interface for node
self.node_id: Optional[int] = None
# id of interface for network
self.net_id: Optional[int] = None
# id used to find flow data
self.flow_id: Optional[int] = None
self.server: Optional["DistributedServer"] = server
self.net_client: LinuxNetClient = get_net_client(
self.session.use_ovs(), self.host_cmd
)
self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd)
self.control: bool = False
# configuration data
self.has_local_netem: bool = False
self.local_options: LinkOptions = LinkOptions()
self.has_netem: bool = False
self.options: LinkOptions = LinkOptions()
@ -161,7 +160,13 @@ class CoreInterface:
:return: nothing
"""
pass
self.net_client.create_veth(self.localname, self.name)
if self.mtu > 0:
self.net_client.set_mtu(self.name, self.mtu)
self.net_client.set_mtu(self.localname, self.mtu)
self.net_client.device_up(self.name)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self) -> None:
"""
@ -169,29 +174,14 @@ class CoreInterface:
:return: nothing
"""
pass
def attachnet(self, net: "CoreNetworkBase") -> None:
"""
Attach network.
:param net: network to attach
:return: nothing
"""
if self.net:
self.detachnet()
self.net = None
net.attach(self)
self.net = net
def detachnet(self) -> None:
"""
Detach from a network.
:return: nothing
"""
if self.net is not None:
self.net.detach(self)
if not self.up:
return
if self.localname:
try:
self.net_client.delete_device(self.localname)
except CoreCommandError:
pass
self.up = False
def add_ip(self, ip: str) -> None:
"""
@ -303,41 +293,24 @@ class CoreInterface:
"""
return self.transport_type == TransportType.VIRTUAL
def config(self, options: LinkOptions, use_local: bool = True) -> None:
"""
Configure interface using tc based on existing state and provided
link options.
:param options: options to configure with
:param use_local: True to use localname for device, False for name
:return: nothing
"""
# determine name, options, and if anything has changed
name = self.localname if use_local else self.name
current_options = self.local_options if use_local else self.options
changed = current_options.update(options)
# nothing more to do when nothing has changed or not up
if not changed or not self.up:
return
def set_config(self, node: "CoreNode" = None) -> None:
# clear current settings
if current_options.is_clear():
clear_local_netem = use_local and self.has_local_netem
clear_netem = not use_local and self.has_netem
if clear_local_netem or clear_netem:
cmd = tc_clear_cmd(name)
self.host_cmd(cmd)
if use_local:
self.has_local_netem = False
if self.options.is_clear():
if self.has_netem:
cmd = tc_clear_cmd(self.name)
if node:
node.cmd(cmd)
else:
self.has_netem = False
self.host_cmd(cmd)
self.has_netem = False
# set updated settings
else:
cmd = tc_cmd(name, current_options, self.mtu)
self.host_cmd(cmd)
if use_local:
self.has_local_netem = True
cmd = tc_cmd(self.name, self.options, self.mtu)
if node:
node.cmd(cmd)
else:
self.has_netem = True
self.host_cmd(cmd)
self.has_netem = True
def get_data(self) -> InterfaceData:
"""
@ -345,231 +318,22 @@ class CoreInterface:
:return: interface data
"""
if self.node:
iface_id = self.node.get_iface_id(self)
else:
iface_id = self.othernet.get_iface_id(self)
data = InterfaceData(
id=iface_id, name=self.name, mac=str(self.mac) if self.mac else None
)
ip4 = self.get_ip4()
if ip4:
data.ip4 = str(ip4.ip)
data.ip4_mask = ip4.prefixlen
ip4_addr = str(ip4.ip) if ip4 else None
ip4_mask = ip4.prefixlen if ip4 else None
ip6 = self.get_ip6()
if ip6:
data.ip6 = str(ip6.ip)
data.ip6_mask = ip6.prefixlen
return data
class Veth(CoreInterface):
"""
Provides virtual ethernet functionality for core nodes.
"""
def adopt_node(self, iface_id: int, name: str, start: bool) -> None:
"""
Adopt this interface to the provided node, configuring and associating
with the node as needed.
:param iface_id: interface id for node
:param name: name of interface fo rnode
:param start: True to start interface, False otherwise
:return: nothing
"""
if start:
self.startup()
self.net_client.device_ns(self.name, str(self.node.pid))
self.node.node_net_client.checksums_off(self.name)
self.flow_id = self.node.node_net_client.get_ifindex(self.name)
logger.debug("interface flow index: %s - %s", self.name, self.flow_id)
mac = self.node.node_net_client.get_mac(self.name)
logger.debug("interface mac: %s - %s", self.name, mac)
self.set_mac(mac)
self.node.node_net_client.device_name(self.name, name)
self.name = name
try:
self.node.add_iface(self, iface_id)
except CoreError as e:
self.shutdown()
raise e
def startup(self) -> None:
"""
Interface startup logic.
:return: nothing
:raises CoreCommandError: when there is a command exception
"""
self.net_client.create_veth(self.localname, self.name)
if self.mtu > 0:
self.net_client.set_mtu(self.name, self.mtu)
self.net_client.set_mtu(self.localname, self.mtu)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self) -> None:
"""
Interface shutdown logic.
:return: nothing
"""
if not self.up:
return
if self.node:
try:
self.node.node_net_client.device_flush(self.name)
except CoreCommandError:
pass
if self.localname:
try:
self.net_client.delete_device(self.localname)
except CoreCommandError:
pass
self.up = False
class TunTap(CoreInterface):
"""
TUN/TAP virtual device in TAP mode
"""
def startup(self) -> None:
"""
Startup logic for a tunnel tap.
:return: nothing
"""
# TODO: more sophisticated TAP creation here
# Debian does not support -p (tap) option, RedHat does.
# For now, this is disabled to allow the TAP to be created by another
# system (e.g. EMANE"s emanetransportd)
# check_call(["tunctl", "-t", self.name])
# self.install()
self.up = True
def shutdown(self) -> None:
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up:
return
try:
self.node.node_net_client.device_flush(self.name)
except CoreCommandError:
logger.exception("error shutting down tunnel tap")
self.up = False
def waitfor(
self, func: Callable[[], int], attempts: int = 10, maxretrydelay: float = 0.25
) -> bool:
"""
Wait for func() to return zero with exponential backoff.
:param func: function to wait for a result of zero
:param attempts: number of attempts to wait for a zero result
:param maxretrydelay: maximum retry delay
:return: True if wait succeeded, False otherwise
"""
delay = 0.01
result = False
for i in range(1, attempts + 1):
r = func()
if r == 0:
result = True
break
msg = f"attempt {i} failed with nonzero exit status {r}"
if i < attempts + 1:
msg += ", retrying..."
logger.info(msg)
time.sleep(delay)
delay += delay
if delay > maxretrydelay:
delay = maxretrydelay
else:
msg += ", giving up"
logger.info(msg)
return result
def waitfordevicelocal(self) -> None:
"""
Check for presence of a local device - tap device may not
appear right away waits
:return: wait for device local response
"""
logger.debug("waiting for device local: %s", self.localname)
def localdevexists():
try:
self.net_client.device_show(self.localname)
return 0
except CoreCommandError:
return 1
self.waitfor(localdevexists)
def waitfordevicenode(self) -> None:
"""
Check for presence of a node device - tap device may not appear right away waits.
:return: nothing
"""
logger.debug("waiting for device node: %s", self.name)
def nodedevexists():
try:
self.node.node_net_client.device_show(self.name)
return 0
except CoreCommandError:
return 1
count = 0
while True:
result = self.waitfor(nodedevexists)
if result:
break
# TODO: emane specific code
# check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running
should_retry = count < 5
is_emane = self.session.emane.is_emane_net(self.net)
is_emane_running = self.session.emane.emanerunning(self.node)
if all([should_retry, is_emane, is_emane_running]):
count += 1
else:
raise RuntimeError("node device failed to exist")
def install(self) -> None:
"""
Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace
program (running on the host) has had a chance to open the socket
end of the TAP.
:return: nothing
:raises CoreCommandError: when there is a command exception
"""
self.waitfordevicelocal()
netns = str(self.node.pid)
self.net_client.device_ns(self.localname, netns)
self.node.node_net_client.device_name(self.localname, self.name)
self.node.node_net_client.device_up(self.name)
def set_ips(self) -> None:
"""
Set interface ip addresses.
:return: nothing
"""
self.waitfordevicenode()
for ip in self.ips():
self.node.node_net_client.create_address(self.name, str(ip))
ip6_addr = str(ip6.ip) if ip6 else None
ip6_mask = ip6.prefixlen if ip6 else None
mac = str(self.mac) if self.mac else None
return InterfaceData(
id=self.id,
name=self.name,
mac=mac,
ip4=ip4_addr,
ip4_mask=ip4_mask,
ip6=ip6_addr,
ip6_mask=ip6_mask,
)
class GreTap(CoreInterface):
@ -594,7 +358,7 @@ class GreTap(CoreInterface):
"""
Creates a GreTap instance.
:param session: core session instance
:param session: session for this gre tap
:param remoteip: remote address
:param key: gre tap key
:param node: related core node
@ -612,7 +376,7 @@ class GreTap(CoreInterface):
sessionid = session.short_session_id()
localname = f"gt.{self.id}.{sessionid}"
name = f"{localname}p"
super().__init__(session, name, localname, mtu, server, node)
super().__init__(0, name, localname, session.use_ovs(), mtu, node, server)
self.transport_type: TransportType = TransportType.RAW
self.remote_ip: str = remoteip
self.ttl: int = ttl

View file

@ -22,8 +22,8 @@ from core.emulator.enumerations import (
)
from core.errors import CoreCommandError, CoreError
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase, CoreNode
from core.nodes.interface import CoreInterface, GreTap, Veth
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, GreTap
from core.nodes.netclient import get_net_client
logger = logging.getLogger(__name__)
@ -280,6 +280,17 @@ class CoreNetwork(CoreNetworkBase):
self.up = True
nft_queue.start()
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
"""
Adopt interface and set it to use this bridge as master.
:param iface: interface to adpopt
:param name: formal name for interface
:return: nothing
"""
iface.net_client.set_iface_master(self.brname, iface.name)
iface.set_config()
def shutdown(self) -> None:
"""
Linux bridge shutdown logic.
@ -309,9 +320,9 @@ class CoreNetwork(CoreNetworkBase):
:param iface: network interface to attach
:return: nothing
"""
super().attach(iface)
if self.up:
iface.net_client.set_iface_master(self.brname, iface.localname)
super().attach(iface)
def detach(self, iface: CoreInterface) -> None:
"""
@ -320,9 +331,9 @@ class CoreNetwork(CoreNetworkBase):
:param iface: network interface to detach
:return: nothing
"""
super().detach(iface)
if self.up:
iface.net_client.delete_iface(self.brname, iface.localname)
super().detach(iface)
def is_linked(self, iface1: CoreInterface, iface2: CoreInterface) -> bool:
"""
@ -378,67 +389,6 @@ class CoreNetwork(CoreNetworkBase):
self.linked[iface1][iface2] = True
nft_queue.update(self)
def linknet(self, net: CoreNetworkBase) -> CoreInterface:
"""
Link this bridge with another by creating a veth pair and installing
each device into each bridge.
:param net: network to link with
:return: created interface
"""
sessionid = self.session.short_session_id()
try:
_id = f"{self.id:x}"
except TypeError:
_id = str(self.id)
try:
net_id = f"{net.id:x}"
except TypeError:
net_id = str(net.id)
localname = f"veth{_id}.{net_id}.{sessionid}"
name = f"veth{net_id}.{_id}.{sessionid}"
iface = Veth(self.session, name, localname)
if self.up:
iface.startup()
self.attach(iface)
if net.up and net.brname:
iface.net_client.set_iface_master(net.brname, iface.name)
i = net.next_iface_id()
net.ifaces[i] = iface
with net.linked_lock:
net.linked[iface] = {}
iface.net = self
iface.othernet = net
return iface
def get_linked_iface(self, net: CoreNetworkBase) -> Optional[CoreInterface]:
"""
Return the interface of that links this net with another net
(that were linked using linknet()).
:param net: interface to get link for
:return: interface the provided network is linked to
"""
for iface in self.get_ifaces():
if iface.othernet == net:
return iface
return None
def add_ips(self, ips: List[str]) -> None:
"""
Add ip addresses on the bridge in the format "10.0.0.1/24".
:param ips: ip address to add
:return: nothing
"""
if not self.up:
return
for ip in ips:
self.net_client.create_address(self.brname, ip)
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
raise CoreError(f"{type(self).__name__} does not support, custom interfaces")
class GreTapBridge(CoreNetwork):
"""
@ -686,15 +636,6 @@ class CtrlNet(CoreNetwork):
super().shutdown()
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Do not include CtrlNet in link messages describing this session.
:param flags: message flags
:return: list of link data
"""
return []
class PtpNet(CoreNetwork):
"""
@ -714,50 +655,6 @@ class PtpNet(CoreNetwork):
raise CoreError("ptp links support at most 2 network interfaces")
super().attach(iface)
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
:param flags: message flags
:return: list of link data
"""
all_links = []
if len(self.ifaces) != 2:
return all_links
ifaces = self.get_ifaces()
iface1 = ifaces[0]
iface2 = ifaces[1]
unidirectional = 0 if iface1.local_options == iface2.local_options else 1
iface1_data = iface1.get_data()
iface2_data = iface2.get_data()
link_data = LinkData(
message_type=flags,
type=self.linktype,
node1_id=iface1.node.id,
node2_id=iface2.node.id,
iface1=iface1_data,
iface2=iface2_data,
options=iface1.local_options,
)
link_data.options.unidirectional = unidirectional
all_links.append(link_data)
# build a 2nd link message for the upstream link parameters
# (swap if1 and if2)
if unidirectional:
link_data = LinkData(
message_type=MessageFlags.NONE,
type=self.linktype,
node1_id=iface2.node.id,
node2_id=iface1.node.id,
iface1=InterfaceData(id=iface2_data.id),
iface2=InterfaceData(id=iface1_data.id),
options=iface2.local_options,
)
link_data.options.unidirectional = unidirectional
all_links.append(link_data)
return all_links
class SwitchNode(CoreNetwork):
"""