daemon/grpc/gui: further updates for a new working wireless node, added grpc support for control and additions to gui for creation

This commit is contained in:
Blake Harnden 2022-03-30 21:13:28 -07:00
parent e4a6ecf3c2
commit 84acb82c18
19 changed files with 399 additions and 90 deletions

View file

@ -16,7 +16,13 @@ from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest, GetConfigServiceDefaultsRequest,
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
) )
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest, LinkedRequest from core.api.grpc.core_pb2 import (
ExecuteScriptRequest,
GetConfigRequest,
LinkedRequest,
WirelessConfigRequest,
WirelessLinkedRequest,
)
from core.api.grpc.emane_pb2 import ( from core.api.grpc.emane_pb2 import (
EmaneLinkRequest, EmaneLinkRequest,
GetEmaneEventChannelRequest, GetEmaneEventChannelRequest,
@ -43,6 +49,7 @@ from core.api.grpc.wlan_pb2 import (
WlanConfig, WlanConfig,
WlanLinkRequest, WlanLinkRequest,
) )
from core.api.grpc.wrappers import LinkOptions
from core.emulator.data import IpPrefixes from core.emulator.data import IpPrefixes
from core.errors import CoreError from core.errors import CoreError
@ -1079,6 +1086,42 @@ class CoreGrpcClient:
) )
self.stub.Linked(request) self.stub.Linked(request)
def wireless_linked(
self,
session_id: int,
wireless_id: int,
node1_id: int,
node2_id: int,
linked: bool,
) -> None:
request = WirelessLinkedRequest(
session_id=session_id,
wireless_id=wireless_id,
node1_id=node1_id,
node2_id=node2_id,
linked=linked,
)
self.stub.WirelessLinked(request)
def wireless_config(
self,
session_id: int,
wireless_id: int,
node1_id: int,
node2_id: int,
options1: LinkOptions,
options2: LinkOptions = None,
) -> None:
request = WirelessConfigRequest(
session_id=session_id,
wireless_id=wireless_id,
node1_id=node1_id,
node2_id=node2_id,
options1=options1.to_proto(),
options2=options2.to_proto(),
)
self.stub.WirelessConfig(request)
def connect(self) -> None: def connect(self) -> None:
""" """
Open connection to server, must be closed manually. Open connection to server, must be closed manually.

View file

@ -26,7 +26,15 @@ from core.api.grpc.configservices_pb2 import (
GetNodeConfigServiceRequest, GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse, GetNodeConfigServiceResponse,
) )
from core.api.grpc.core_pb2 import ExecuteScriptResponse, LinkedRequest, LinkedResponse from core.api.grpc.core_pb2 import (
ExecuteScriptResponse,
LinkedRequest,
LinkedResponse,
WirelessConfigRequest,
WirelessConfigResponse,
WirelessLinkedRequest,
WirelessLinkedResponse,
)
from core.api.grpc.emane_pb2 import ( from core.api.grpc.emane_pb2 import (
EmaneLinkRequest, EmaneLinkRequest,
EmaneLinkResponse, EmaneLinkResponse,
@ -82,6 +90,7 @@ from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, NodeBase from core.nodes.base import CoreNode, NodeBase
from core.nodes.network import CoreNetwork, WlanNode from core.nodes.network import CoreNetwork, WlanNode
from core.nodes.wireless import WirelessNode
from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -1329,3 +1338,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
request.linked, request.linked,
) )
return LinkedResponse() return LinkedResponse()
def WirelessLinked(
self, request: WirelessLinkedRequest, context: ServicerContext
) -> WirelessLinkedResponse:
session = self.get_session(request.session_id, context)
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
wireless.link_control(request.node1_id, request.node2_id, request.linked)
return WirelessLinkedResponse()
def WirelessConfig(
self, request: WirelessConfigRequest, context: ServicerContext
) -> WirelessConfigResponse:
session = self.get_session(request.session_id, context)
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
options1 = request.options1
options2 = options1
if request.HasField("options2"):
options2 = request.options2
wireless.link_config(request.node1_id, request.node2_id, options1, options2)
return WirelessConfigResponse()

View file

@ -67,6 +67,7 @@ class NodeType(Enum):
CONTROL_NET = 13 CONTROL_NET = 13
DOCKER = 15 DOCKER = 15
LXC = 16 LXC = 16
WIRELESS = 17
class LinkType(Enum): class LinkType(Enum):

View file

@ -4,14 +4,25 @@ from typing import Any, Dict, List
from core.config import Configuration from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import WlanNode from core.nodes.network import WlanNode
from core.nodes.wireless import WirelessNode
GROUP: str = "FRR" GROUP: str = "FRR"
FRR_STATE_DIR: str = "/var/run/frr" FRR_STATE_DIR: str = "/var/run/frr"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
def has_mtu_mismatch(iface: CoreInterface) -> bool: def has_mtu_mismatch(iface: CoreInterface) -> bool:
""" """
Helper to detect MTU mismatch and add the appropriate FRR Helper to detect MTU mismatch and add the appropriate FRR
@ -324,7 +335,7 @@ class FRRBabel(FrrService, ConfigService):
return self.render_text(text, data) return self.render_text(text, data)
def frr_iface_config(self, iface: CoreInterface) -> str: def frr_iface_config(self, iface: CoreInterface) -> str:
if isinstance(iface.net, (WlanNode, EmaneNet)): if is_wireless(iface.net):
text = """ text = """
babel wireless babel wireless
no babel split-horizon no babel split-horizon

View file

@ -5,16 +5,27 @@ from typing import Any, Dict, List
from core.config import Configuration from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import PtpNet, WlanNode from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node from core.nodes.physical import Rj45Node
from core.nodes.wireless import WirelessNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
GROUP: str = "Quagga" GROUP: str = "Quagga"
QUAGGA_STATE_DIR: str = "/var/run/quagga" QUAGGA_STATE_DIR: str = "/var/run/quagga"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
def has_mtu_mismatch(iface: CoreInterface) -> bool: def has_mtu_mismatch(iface: CoreInterface) -> bool:
""" """
Helper to detect MTU mismatch and add the appropriate OSPF Helper to detect MTU mismatch and add the appropriate OSPF
@ -265,7 +276,7 @@ class Ospfv3mdr(Ospfv3):
def quagga_iface_config(self, iface: CoreInterface) -> str: def quagga_iface_config(self, iface: CoreInterface) -> str:
config = super().quagga_iface_config(iface) config = super().quagga_iface_config(iface)
if isinstance(iface.net, (WlanNode, EmaneNet)): if is_wireless(iface.net):
config = self.clean_text( config = self.clean_text(
f""" f"""
{config} {config}
@ -390,7 +401,7 @@ class Babel(QuaggaService, ConfigService):
return self.render_text(text, data) return self.render_text(text, data)
def quagga_iface_config(self, iface: CoreInterface) -> str: def quagga_iface_config(self, iface: CoreInterface) -> str:
if isinstance(iface.net, (WlanNode, EmaneNet)): if is_wireless(iface.net):
text = """ text = """
babel wireless babel wireless
no babel split-horizon no babel split-horizon

View file

@ -49,6 +49,7 @@ class NodeTypes(Enum):
CONTROL_NET = 13 CONTROL_NET = 13
DOCKER = 15 DOCKER = 15
LXC = 16 LXC = 16
WIRELESS = 17
class LinkTypes(Enum): class LinkTypes(Enum):

View file

@ -81,13 +81,18 @@ NODES: Dict[NodeTypes, Type[NodeBase]] = {
NodeTypes.CONTROL_NET: CtrlNet, NodeTypes.CONTROL_NET: CtrlNet,
NodeTypes.DOCKER: DockerNode, NodeTypes.DOCKER: DockerNode,
NodeTypes.LXC: LxcNode, NodeTypes.LXC: LxcNode,
NodeTypes.WIRELESS: WirelessNode,
} }
NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES} NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES}
CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode} CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode}
CTRL_NET_ID: int = 9001 CTRL_NET_ID: int = 9001
LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"] LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"]
NT: TypeVar = TypeVar("NT", bound=NodeBase) NT: TypeVar = TypeVar("NT", bound=NodeBase)
WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet]] = (WlanNode, EmaneNet) WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet], Type[WirelessNode]] = (
WlanNode,
EmaneNet,
WirelessNode,
)
class Session: class Session:
@ -299,7 +304,10 @@ class Session:
return iface1, iface2 return iface1, iface2
def _add_wlan_link( def _add_wlan_link(
self, node: NodeBase, iface_data: InterfaceData, net: WlanNode self,
node: NodeBase,
iface_data: InterfaceData,
net: Union[WlanNode, WirelessNode],
) -> CoreInterface: ) -> CoreInterface:
""" """
Create a wlan link. Create a wlan link.
@ -393,10 +401,10 @@ class Session:
) )
iface1 = None iface1 = None
iface2 = None iface2 = None
if isinstance(node1, WlanNode): if isinstance(node1, (WlanNode, WirelessNode)):
iface2 = node2.delete_iface(iface2_id) iface2 = node2.delete_iface(iface2_id)
node1.detach(iface2) node1.detach(iface2)
elif isinstance(node2, WlanNode): elif isinstance(node2, (WlanNode, WirelessNode)):
iface1 = node1.delete_iface(iface1_id) iface1 = node1.delete_iface(iface1_id)
node2.detach(iface1) node2.detach(iface1)
elif isinstance(node1, EmaneNet): elif isinstance(node1, EmaneNet):

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -53,6 +53,7 @@ class ImageEnum(Enum):
LINK = "link" LINK = "link"
HUB = "hub" HUB = "hub"
WLAN = "wlan" WLAN = "wlan"
WIRELESS = "wireless"
EMANE = "emane" EMANE = "emane"
RJ45 = "rj45" RJ45 = "rj45"
TUNNEL = "tunnel" TUNNEL = "tunnel"

View file

@ -18,7 +18,11 @@ NETWORK_NODES: List["NodeDraw"] = []
NODE_ICONS = {} NODE_ICONS = {}
CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC}
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC} IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} WIRELESS_NODES: Set[NodeType] = {
NodeType.WIRELESS_LAN,
NodeType.EMANE,
NodeType.WIRELESS,
}
RJ45_NODES: Set[NodeType] = {NodeType.RJ45} RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH} BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET} IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
@ -46,6 +50,7 @@ def setup() -> None:
(ImageEnum.HUB, NodeType.HUB, "Hub"), (ImageEnum.HUB, NodeType.HUB, "Hub"),
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"), (ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"), (ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
(ImageEnum.WIRELESS, NodeType.WIRELESS, "Wireless"),
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"), (ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"), (ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"), (ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),

View file

@ -766,8 +766,9 @@ class CoreNode(CoreNodeBase):
raise CoreError(f"adopting unknown iface({iface.name})") raise CoreError(f"adopting unknown iface({iface.name})")
# add iface to container namespace # add iface to container namespace
self.net_client.device_ns(iface.name, str(self.pid)) self.net_client.device_ns(iface.name, str(self.pid))
# update iface name to container name # use default iface name for container, if a unique name was not provided
name = name if name else f"eth{iface_id}" if iface.name == name:
name = f"eth{iface_id}"
self.node_net_client.device_name(iface.name, name) self.node_net_client.device_name(iface.name, name)
iface.name = name iface.name = name
# turn checksums off # turn checksums off

View file

@ -1,13 +1,19 @@
"""
Defines a wireless node that allows programmatic link connectivity and
configuration between pairs of nodes.
"""
import logging import logging
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Tuple from typing import TYPE_CHECKING, Dict, Tuple
from core.emulator.data import LinkOptions from core.emulator.data import LinkData, LinkOptions
from core.emulator.links import CoreLink from core.emulator.enumerations import LinkTypes, MessageFlags
from core.errors import CoreError from core.errors import CoreError
from core.executables import NFTABLES from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.nodes.network import PtpNet
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emulator.session import Session from core.emulator.session import Session
@ -16,6 +22,24 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def calc_distance(
point1: Tuple[float, float, float], point2: Tuple[float, float, float]
) -> float:
a = point1[0] - point2[0]
b = point1[1] - point2[1]
c = 0
if point1[2] is not None and point2[2] is not None:
c = point1[2] - point2[2]
return math.hypot(math.hypot(a, b), c)
@dataclass
class WirelessLink:
bridge1: str
bridge2: str
iface: CoreInterface
class WirelessNode(CoreNetworkBase): class WirelessNode(CoreNetworkBase):
def __init__( def __init__(
self, self,
@ -26,7 +50,8 @@ class WirelessNode(CoreNetworkBase):
): ):
super().__init__(session, _id, name, server) super().__init__(session, _id, name, server)
self.bridges: Dict[int, Tuple[CoreInterface, str]] = {} self.bridges: Dict[int, Tuple[CoreInterface, str]] = {}
self.links: Dict[Tuple[int, int], CoreLink] = {} self.links: Dict[Tuple[int, int], WirelessLink] = {}
self.labels: Dict[Tuple[int, int], str] = {}
def startup(self) -> None: def startup(self) -> None:
if self.up: if self.up:
@ -39,10 +64,8 @@ class WirelessNode(CoreNetworkBase):
self.net_client.delete_bridge(bridge_name) self.net_client.delete_bridge(bridge_name)
self.host_cmd(f"{NFTABLES} delete table bridge {bridge_name}") self.host_cmd(f"{NFTABLES} delete table bridge {bridge_name}")
while self.links: while self.links:
key, core_link = self.links.popitem() _, link = self.links.popitem()
core_link.iface1.shutdown() link.iface.shutdown()
core_link.iface2.shutdown()
core_link.ptp.shutdown()
self.up = False self.up = False
def attach(self, iface: CoreInterface) -> None: def attach(self, iface: CoreInterface) -> None:
@ -90,26 +113,20 @@ class WirelessNode(CoreNetworkBase):
session_id = self.session.short_session_id() session_id = self.session.short_session_id()
name1 = f"we{self.id}.{node1.id}.{node2.id}.{session_id}" name1 = f"we{self.id}.{node1.id}.{node2.id}.{session_id}"
name2 = f"we{self.id}.{node2.id}.{node1.id}.{session_id}" name2 = f"we{self.id}.{node2.id}.{node1.id}.{session_id}"
iface1 = CoreInterface(0, name1, f"{name1}p", self.session.use_ovs()) link_iface = CoreInterface(0, name1, name2, self.session.use_ovs())
iface1.startup() link_iface.startup()
iface2 = CoreInterface(0, name2, f"{name2}p", self.session.use_ovs()) link = WirelessLink(bridge1, bridge2, link_iface)
iface2.startup() self.links[key] = link
link_name = f"wl{node1.id}.{node2.id}.{self.session.id}"
ptp = PtpNet(self.session)
ptp.brname = link_name
ptp.startup()
ptp.attach(iface1)
ptp.attach(iface2)
core_link = CoreLink(node1, iface1, node2, iface2, ptp)
self.links[key] = core_link
# assign ifaces to respective bridges # assign ifaces to respective bridges
self.net_client.set_iface_master(bridge1, iface1.name) self.net_client.set_iface_master(bridge1, link_iface.name)
self.net_client.set_iface_master(bridge2, iface2.name) self.net_client.set_iface_master(bridge2, link_iface.localname)
# track bridge routes # track bridge routes
node1_routes = routes.setdefault(node1.id, set()) node1_routes = routes.setdefault(node1.id, set())
node1_routes.add(name1) node1_routes.add(name1)
node2_routes = routes.setdefault(node2.id, set()) node2_routes = routes.setdefault(node2.id, set())
node2_routes.add(name2) node2_routes.add(name2)
# send link
self.send_link(node1.id, node2.id, MessageFlags.ADD)
for node_id, ifaces in routes.items(): for node_id, ifaces in routes.items():
iface, bridge_name = self.bridges[node_id] iface, bridge_name = self.bridges[node_id]
ifaces = ",".join(ifaces) ifaces = ",".join(ifaces)
@ -130,41 +147,94 @@ class WirelessNode(CoreNetworkBase):
def link_control(self, node1_id: int, node2_id: int, linked: bool) -> None: def link_control(self, node1_id: int, node2_id: int, linked: bool) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id) key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
core_link = self.links.get(key) link = self.links.get(key)
if not core_link: if not link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})") raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
ptp = core_link.ptp bridge1, bridge2 = link.bridge1, link.bridge2
iface1, iface2 = core_link.iface1, core_link.iface2 iface = link.iface
if linked: if linked:
ptp.attach(iface1) self.net_client.set_iface_master(bridge1, iface.name)
ptp.attach(iface2) self.net_client.set_iface_master(bridge2, iface.localname)
message_type = MessageFlags.ADD
else: else:
ptp.detach(iface1) self.net_client.delete_iface(bridge1, iface.name)
ptp.detach(iface2) self.net_client.delete_iface(bridge2, iface.localname)
message_type = MessageFlags.DELETE
label = self.labels.get(key)
self.send_link(key[0], key[1], message_type, label)
def link_config( def link_config(
self, node1_id: int, node2_id: int, options1: LinkOptions, options2: LinkOptions
) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
link = self.links.get(key)
if not link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
iface = link.iface
iface.options.update(options1)
iface.set_config()
name, localname = iface.name, iface.localname
iface.name, iface.localname = localname, name
iface.options.update(options2)
iface.set_config()
iface.name, iface.localname = name, localname
if options1 == options2:
if options1.is_clear():
label = ""
else:
label = f"{options1.loss:.2f}%/{options1.delay}us"
else:
label = (
f"({options1.loss:.2f}%/{options1.delay}us) "
f"({options2.loss:.2f}%/{options2.delay}us)"
)
self.labels[key] = label
self.send_link(key[0], key[1], MessageFlags.NONE, label)
def send_link(
self, self,
node1_id: int, node1_id: int,
node2_id: int, node2_id: int,
iface1_options: LinkOptions, message_type: MessageFlags,
iface2_options: LinkOptions, label: str = None,
) -> None: ) -> None:
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id) """
core_link = self.links.get(key) Broadcasts out a wireless link/unlink message.
if not core_link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})") :param node1_id: first node in link
iface1, iface2 = core_link.iface1, core_link.iface2 :param node2_id: second node in link
iface1.options.update(iface1_options) :param message_type: type of link message to send
iface1.set_config() :param label: label to display for link
iface2.options.update(iface2_options) :return: nothing
iface2.set_config() """
color = self.session.get_link_color(self.id)
link_data = LinkData(
message_type=message_type,
type=LinkTypes.WIRELESS,
node1_id=node1_id,
node2_id=node2_id,
network_id=self.id,
color=color,
label=label,
)
self.session.broadcast_link(link_data)
def position_callback(self, iface: CoreInterface) -> None: def position_callback(self, iface: CoreInterface) -> None:
for oiface, bridge_name in self.bridges.values():
if iface == oiface:
continue
point1 = iface.node.position.get()
point2 = oiface.node.position.get()
distance = calc_distance(point1, point2) - 250
distance = max(distance, 0.0)
logger.info( logger.info(
"received position callback for node(%s) iface(%s)", "n1(%s) n2(%s) d(%s)", iface.node.name, oiface.node.name, distance
iface.node.name,
iface.name,
) )
loss = min((distance / 500) * 100.0, 100.0)
node1_id = iface.node.id
node2_id = oiface.node.id
options = LinkOptions(loss=loss, delay=0)
self.link_config(node1_id, node2_id, options, options)
def adopt_iface(self, iface: CoreInterface, name: str) -> None: def adopt_iface(self, iface: CoreInterface, name: str) -> None:
raise CoreError(f"{type(self)} does not support adopt interface") raise CoreError(f"{type(self)} does not support adopt interface")

View file

@ -4,7 +4,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging import logging
import socket import socket
from typing import IO, TYPE_CHECKING, Dict, List, Optional, Set, Tuple from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from core.constants import CORE_CONF_DIR, CORE_DATA_DIR from core.constants import CORE_CONF_DIR, CORE_DATA_DIR
@ -12,22 +12,15 @@ from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData, NodeData from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import EventTypes, MessageFlags from core.emulator.enumerations import EventTypes, MessageFlags
from core.errors import CoreError from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, NodeBase from core.nodes.base import NodeBase
from core.nodes.network import WlanNode from core.nodes.network import WlanNode
from core.nodes.wireless import WirelessNode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
from core.emulator.session import Session from core.emulator.session import Session
def get_link_id(node1_id: int, node2_id: int, network_id: int) -> str:
link_id = f"{node1_id}-{node2_id}"
if network_id is not None:
link_id = f"{link_id}-{network_id}"
return link_id
CORE_LAYER: str = "CORE" CORE_LAYER: str = "CORE"
NODE_LAYER: str = "CORE::Nodes" NODE_LAYER: str = "CORE::Nodes"
LINK_LAYER: str = "CORE::Links" LINK_LAYER: str = "CORE::Links"
@ -36,6 +29,17 @@ CORE_LAYERS: List[str] = [CORE_LAYER, LINK_LAYER, NODE_LAYER, WIRED_LINK_LAYER]
DEFAULT_LINK_COLOR: str = "red" DEFAULT_LINK_COLOR: str = "red"
def is_wireless(node: NodeBase) -> bool:
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
def get_link_id(node1_id: int, node2_id: int, network_id: int) -> str:
link_id = f"{node1_id}-{node2_id}"
if network_id is not None:
link_id = f"{link_id}-{network_id}"
return link_id
class Sdt: class Sdt:
""" """
Helper class for exporting session objects to NRL"s SDT3D. Helper class for exporting session objects to NRL"s SDT3D.
@ -67,7 +71,7 @@ class Sdt:
:param session: session this manager is tied to :param session: session this manager is tied to
""" """
self.session: "Session" = session self.session: "Session" = session
self.sock: Optional[IO] = None self.sock: Optional[socket.socket] = None
self.connected: bool = False self.connected: bool = False
self.url: str = self.DEFAULT_SDT_URL self.url: str = self.DEFAULT_SDT_URL
self.address: Optional[Tuple[Optional[str], Optional[int]]] = None self.address: Optional[Tuple[Optional[str], Optional[int]]] = None
@ -210,26 +214,23 @@ class Sdt:
:return: nothing :return: nothing
""" """
nets = []
# create layers
for layer in CORE_LAYERS: for layer in CORE_LAYERS:
self.cmd(f"layer {layer}") self.cmd(f"layer {layer}")
with self.session.nodes_lock: with self.session.nodes_lock:
for node_id in self.session.nodes: nets = []
node = self.session.nodes[node_id] for node in self.session.nodes.values():
if isinstance(node, CoreNetworkBase): if isinstance(node, (EmaneNet, WlanNode)):
nets.append(node) nets.append(node)
if not isinstance(node, NodeBase): if not isinstance(node, NodeBase):
continue continue
self.add_node(node) self.add_node(node)
for link in self.session.link_manager.links():
for net in nets: if is_wireless(link.node1) or is_wireless(link.node2):
all_links = net.links(flags=MessageFlags.ADD)
for link_data in all_links:
is_wireless = isinstance(net, (WlanNode, EmaneNet))
if is_wireless and link_data.node1_id == net.id:
continue continue
link_data = link.get_data(MessageFlags.ADD)
self.handle_link_update(link_data)
for net in nets:
for link_data in net.links(MessageFlags.ADD):
self.handle_link_update(link_data) self.handle_link_update(link_data)
def get_node_position(self, node: NodeBase) -> Optional[str]: def get_node_position(self, node: NodeBase) -> Optional[str]:
@ -341,7 +342,7 @@ class Sdt:
result = False result = False
try: try:
node = self.session.get_node(node_id, NodeBase) node = self.session.get_node(node_id, NodeBase)
result = isinstance(node, (WlanNode, EmaneNet)) result = isinstance(node, (WlanNode, EmaneNet, WirelessNode))
except CoreError: except CoreError:
pass pass
return result return result

View file

@ -7,15 +7,26 @@ from typing import Optional, Tuple
import netaddr import netaddr
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNode from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import PtpNet, WlanNode from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node from core.nodes.physical import Rj45Node
from core.nodes.wireless import WirelessNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
FRR_STATE_DIR: str = "/var/run/frr" FRR_STATE_DIR: str = "/var/run/frr"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
class FRRZebra(CoreService): class FRRZebra(CoreService):
name: str = "FRRzebra" name: str = "FRRzebra"
group: str = "FRR" group: str = "FRR"
@ -593,7 +604,7 @@ class FRRBabel(FrrService):
@classmethod @classmethod
def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): if is_wireless(iface.net):
return " babel wireless\n no babel split-horizon\n" return " babel wireless\n no babel split-horizon\n"
else: else:
return " babel wired\n babel split-horizon\n" return " babel wired\n babel split-horizon\n"

View file

@ -7,15 +7,26 @@ import netaddr
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes from core.emulator.enumerations import LinkTypes
from core.nodes.base import CoreNode from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import PtpNet, WlanNode from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node from core.nodes.physical import Rj45Node
from core.nodes.wireless import WirelessNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
QUAGGA_STATE_DIR: str = "/var/run/quagga" QUAGGA_STATE_DIR: str = "/var/run/quagga"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
class Zebra(CoreService): class Zebra(CoreService):
name: str = "zebra" name: str = "zebra"
group: str = "Quagga" group: str = "Quagga"
@ -431,7 +442,7 @@ class Ospfv3mdr(Ospfv3):
@classmethod @classmethod
def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
cfg = cls.mtu_check(iface) cfg = cls.mtu_check(iface)
if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): if is_wireless(iface.net):
return ( return (
cfg cfg
+ """\ + """\

View file

@ -16,6 +16,7 @@ from core.nodes.docker import DockerNode
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface
from core.nodes.lxd import LxcNode from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet, GreTapBridge, WlanNode from core.nodes.network import CtrlNet, GreTapBridge, WlanNode
from core.nodes.wireless import WirelessNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -542,8 +543,8 @@ class CoreXmlWriter:
iface2 = self.create_iface_element("iface2", iface2) iface2 = self.create_iface_element("iface2", iface2)
link_element.append(iface2) link_element.append(iface2)
# check for options, don't write for emane/wlan links # check for options, don't write for emane/wlan links
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet)) is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet, WirelessNode))
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet)) is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet, WirelessNode))
if not (is_node1_wireless or is_node2_wireless): if not (is_node1_wireless or is_node2_wireless):
unidirectional = 1 if unidirectional else 0 unidirectional = 1 if unidirectional else 0
options_element = etree.Element("options") options_element = etree.Element("options")

View file

@ -0,0 +1,29 @@
from core.api.grpc import client
from core.api.grpc.wrappers import NodeType, Position
# interface helper
iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64")
# create grpc client and connect
core = client.CoreGrpcClient()
core.connect()
# add session
session = core.create_session()
# create nodes
position = Position(x=200, y=200)
wlan = session.add_node(1, _type=NodeType.WIRELESS, position=position)
position = Position(x=100, y=100)
node1 = session.add_node(2, model="mdr", position=position)
position = Position(x=300, y=100)
node2 = session.add_node(3, model="mdr", position=position)
# create links
iface1 = iface_helper.create_iface(node1.id, 0)
session.add_link(node1=node1, node2=wlan, iface1=iface1)
iface1 = iface_helper.create_iface(node2.id, 0)
session.add_link(node1=node2, node2=wlan, iface1=iface1)
# start session
core.start_session(session)

View file

@ -0,0 +1,46 @@
# required imports
import logging
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes
from core.nodes.base import CoreNode
from core.nodes.network import WlanNode
# enable info logging
logging.basicConfig(level=logging.INFO)
# ip nerator for example
ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24")
# create emulator instance for creating sessions and utility methods
coreemu = CoreEmu()
session = coreemu.create_session()
# must be in configuration state for nodes to start
session.set_state(EventTypes.CONFIGURATION_STATE)
# create wireless
options = NodeOptions(x=200, y=200)
wireless = session.add_node(WlanNode, options=options)
# create nodes
options = NodeOptions(model="mdr", x=100, y=100)
n1 = session.add_node(CoreNode, options=options)
options = NodeOptions(model="mdr", x=300, y=100)
n2 = session.add_node(CoreNode, options=options)
# link nodes to wireless
iface1 = ip_prefixes.create_iface(n1)
session.add_link(n1.id, wireless.id, iface1)
iface1 = ip_prefixes.create_iface(n2)
session.add_link(n2.id, wireless.id, iface1)
# start session
session.instantiate()
# do whatever you like here
input("press enter to shutdown")
# stop session
session.shutdown()

View file

@ -100,6 +100,12 @@ service CoreApi {
rpc WlanLink (wlan.WlanLinkRequest) returns (wlan.WlanLinkResponse) { rpc WlanLink (wlan.WlanLinkRequest) returns (wlan.WlanLinkResponse) {
} }
// wireless rpc
rpc WirelessLinked (WirelessLinkedRequest) returns (WirelessLinkedResponse) {
}
rpc WirelessConfig (WirelessConfigRequest) returns (WirelessConfigResponse) {
}
// emane rpc // emane rpc
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) { rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
} }
@ -697,3 +703,26 @@ message LinkedRequest {
message LinkedResponse { message LinkedResponse {
} }
message WirelessLinkedRequest {
int32 session_id = 1;
int32 wireless_id = 2;
int32 node1_id = 3;
int32 node2_id = 4;
bool linked = 5;
}
message WirelessLinkedResponse {
}
message WirelessConfigRequest {
int32 session_id = 1;
int32 wireless_id = 2;
int32 node1_id = 3;
int32 node2_id = 4;
LinkOptions options1 = 5;
LinkOptions options2 = 6;
}
message WirelessConfigResponse {
}