Merge pull request #681 from coreemu/enhancement/wireless-node

Enhancement/wireless node
This commit is contained in:
bharnden 2022-03-31 09:56:34 -07:00 committed by GitHub
commit ace0183db5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 530 additions and 46 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

@ -58,6 +58,7 @@ from core.nodes.network import (
WlanNode, WlanNode,
) )
from core.nodes.physical import PhysicalNode, Rj45Node from core.nodes.physical import PhysicalNode, Rj45Node
from core.nodes.wireless import WirelessNode
from core.plugins.sdt import Sdt from core.plugins.sdt import Sdt
from core.services.coreservices import CoreServices from core.services.coreservices import CoreServices
from core.xml import corexml, corexmldeployment from core.xml import corexml, corexmldeployment
@ -80,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:
@ -274,9 +280,9 @@ class Session:
# custom links # custom links
iface1 = None iface1 = None
iface2 = None iface2 = None
if isinstance(node1, WlanNode): if isinstance(node1, (WlanNode, WirelessNode)):
iface2 = self._add_wlan_link(node2, iface2_data, node1) iface2 = self._add_wlan_link(node2, iface2_data, node1)
elif isinstance(node2, WlanNode): elif isinstance(node2, (WlanNode, WirelessNode)):
iface1 = self._add_wlan_link(node1, iface1_data, node2) iface1 = self._add_wlan_link(node1, iface1_data, node2)
elif isinstance(node1, EmaneNet) and isinstance(node2, CoreNode): elif isinstance(node1, EmaneNet) and isinstance(node2, CoreNode):
iface2 = self._add_emane_link(node2, iface2_data, node1) iface2 = self._add_emane_link(node2, iface2_data, node1)
@ -298,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.
@ -392,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):
@ -1111,6 +1120,10 @@ class Session:
# boot node services and then start mobility # boot node services and then start mobility
exceptions = self.boot_nodes() exceptions = self.boot_nodes()
if not exceptions: if not exceptions:
# complete wireless node
for node in self.nodes.values():
if isinstance(node, WirelessNode):
node.post_startup()
self.mobility.startup() self.mobility.startup()
# notify listeners that instantiation is complete # notify listeners that instantiation is complete
event = EventData(event_type=EventTypes.INSTANTIATION_COMPLETE) event = EventData(event_type=EventTypes.INSTANTIATION_COMPLETE)

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

@ -0,0 +1,240 @@
"""
Defines a wireless node that allows programmatic link connectivity and
configuration between pairs of nodes.
"""
import logging
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Dict, Tuple
from core.emulator.data import LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags
from core.errors import CoreError
from core.executables import NFTABLES
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
from core.emulator.distributed import DistributedServer
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):
def __init__(
self,
session: "Session",
_id: int,
name: str,
server: "DistributedServer" = None,
):
super().__init__(session, _id, name, server)
self.bridges: Dict[int, Tuple[CoreInterface, str]] = {}
self.links: Dict[Tuple[int, int], WirelessLink] = {}
self.labels: Dict[Tuple[int, int], str] = {}
def startup(self) -> None:
if self.up:
return
self.up = True
def shutdown(self) -> None:
while self.bridges:
_, (_, bridge_name) = self.bridges.popitem()
self.net_client.delete_bridge(bridge_name)
self.host_cmd(f"{NFTABLES} delete table bridge {bridge_name}")
while self.links:
_, link = self.links.popitem()
link.iface.shutdown()
self.up = False
def attach(self, iface: CoreInterface) -> None:
super().attach(iface)
logging.info("attaching node(%s) iface(%s)", iface.node.name, iface.name)
if self.up:
# create node unique bridge
bridge_name = f"wb{iface.node.id}.{self.id}.{self.session.id}"
self.net_client.create_bridge(bridge_name)
# setup initial bridge rules
self.host_cmd(f'{NFTABLES} "add table bridge {bridge_name}"')
self.host_cmd(
f"{NFTABLES} "
f"'add chain bridge {bridge_name} forward {{type filter hook "
f"forward priority -1; policy drop;}}'"
)
self.host_cmd(
f"{NFTABLES} "
f"'add rule bridge {bridge_name} forward "
f"ibriport != {bridge_name} accept'"
)
# associate node iface with bridge
iface.net_client.set_iface_master(bridge_name, iface.localname)
# assign position callback
iface.poshook = self.position_callback
# save created bridge
self.bridges[iface.node.id] = (iface, bridge_name)
def post_startup(self) -> None:
routes = {}
for node_id, (iface, bridge_name) in self.bridges.items():
for onode_id, (oiface, obridge_name) in self.bridges.items():
if node_id == onode_id:
continue
if node_id < onode_id:
node1, node2 = iface.node, oiface.node
bridge1, bridge2 = bridge_name, obridge_name
else:
node1, node2 = oiface.node, iface.node
bridge1, bridge2 = obridge_name, bridge_name
key = (node1.id, node2.id)
if key in self.links:
continue
# create node to node link
session_id = self.session.short_session_id()
name1 = f"we{self.id}.{node1.id}.{node2.id}.{session_id}"
name2 = f"we{self.id}.{node2.id}.{node1.id}.{session_id}"
link_iface = CoreInterface(0, name1, name2, self.session.use_ovs())
link_iface.startup()
link = WirelessLink(bridge1, bridge2, link_iface)
self.links[key] = link
# assign ifaces to respective bridges
self.net_client.set_iface_master(bridge1, link_iface.name)
self.net_client.set_iface_master(bridge2, link_iface.localname)
# track bridge routes
node1_routes = routes.setdefault(node1.id, set())
node1_routes.add(name1)
node2_routes = routes.setdefault(node2.id, set())
node2_routes.add(name2)
# send link
self.send_link(node1.id, node2.id, MessageFlags.ADD)
for node_id, ifaces in routes.items():
iface, bridge_name = self.bridges[node_id]
ifaces = ",".join(ifaces)
# out routes
self.host_cmd(
f"{NFTABLES} "
f'"add rule bridge {bridge_name} forward '
f"iif {iface.localname} oif {{{ifaces}}} "
f'accept"'
)
# in routes
self.host_cmd(
f"{NFTABLES} "
f'"add rule bridge {bridge_name} forward '
f"iif {{{ifaces}}} oif {iface.localname} "
f'accept"'
)
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)
link = self.links.get(key)
if not link:
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
bridge1, bridge2 = link.bridge1, link.bridge2
iface = link.iface
if linked:
self.net_client.set_iface_master(bridge1, iface.name)
self.net_client.set_iface_master(bridge2, iface.localname)
message_type = MessageFlags.ADD
else:
self.net_client.delete_iface(bridge1, iface.name)
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(
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,
node1_id: int,
node2_id: int,
message_type: MessageFlags,
label: str = None,
) -> None:
"""
Broadcasts out a wireless link/unlink message.
:param node1_id: first node in link
:param node2_id: second node in link
:param message_type: type of link message to send
:param label: label to display for link
:return: nothing
"""
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:
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(
"n1(%s) n2(%s) d(%s)", iface.node.name, oiface.node.name, distance
)
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:
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 {
}