Merge pull request #681 from coreemu/enhancement/wireless-node
Enhancement/wireless node
This commit is contained in:
commit
ace0183db5
19 changed files with 530 additions and 46 deletions
|
@ -16,7 +16,13 @@ from core.api.grpc.configservices_pb2 import (
|
|||
GetConfigServiceDefaultsRequest,
|
||||
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 (
|
||||
EmaneLinkRequest,
|
||||
GetEmaneEventChannelRequest,
|
||||
|
@ -43,6 +49,7 @@ from core.api.grpc.wlan_pb2 import (
|
|||
WlanConfig,
|
||||
WlanLinkRequest,
|
||||
)
|
||||
from core.api.grpc.wrappers import LinkOptions
|
||||
from core.emulator.data import IpPrefixes
|
||||
from core.errors import CoreError
|
||||
|
||||
|
@ -1079,6 +1086,42 @@ class CoreGrpcClient:
|
|||
)
|
||||
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:
|
||||
"""
|
||||
Open connection to server, must be closed manually.
|
||||
|
|
|
@ -26,7 +26,15 @@ from core.api.grpc.configservices_pb2 import (
|
|||
GetNodeConfigServiceRequest,
|
||||
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 (
|
||||
EmaneLinkRequest,
|
||||
EmaneLinkResponse,
|
||||
|
@ -82,6 +90,7 @@ from core.errors import CoreCommandError, CoreError
|
|||
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||
from core.nodes.base import CoreNode, NodeBase
|
||||
from core.nodes.network import CoreNetwork, WlanNode
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.services.coreservices import ServiceManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -1329,3 +1338,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
|||
request.linked,
|
||||
)
|
||||
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()
|
||||
|
|
|
@ -67,6 +67,7 @@ class NodeType(Enum):
|
|||
CONTROL_NET = 13
|
||||
DOCKER = 15
|
||||
LXC = 16
|
||||
WIRELESS = 17
|
||||
|
||||
|
||||
class LinkType(Enum):
|
||||
|
|
|
@ -4,14 +4,25 @@ from typing import Any, Dict, List
|
|||
from core.config import Configuration
|
||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||
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.network import WlanNode
|
||||
from core.nodes.wireless import WirelessNode
|
||||
|
||||
GROUP: str = "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:
|
||||
"""
|
||||
Helper to detect MTU mismatch and add the appropriate FRR
|
||||
|
@ -324,7 +335,7 @@ class FRRBabel(FrrService, ConfigService):
|
|||
return self.render_text(text, data)
|
||||
|
||||
def frr_iface_config(self, iface: CoreInterface) -> str:
|
||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||
if is_wireless(iface.net):
|
||||
text = """
|
||||
babel wireless
|
||||
no babel split-horizon
|
||||
|
|
|
@ -5,16 +5,27 @@ from typing import Any, Dict, List
|
|||
from core.config import Configuration
|
||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
||||
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.network import PtpNet, WlanNode
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.nodes.wireless import WirelessNode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
GROUP: str = "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:
|
||||
"""
|
||||
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:
|
||||
config = super().quagga_iface_config(iface)
|
||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||
if is_wireless(iface.net):
|
||||
config = self.clean_text(
|
||||
f"""
|
||||
{config}
|
||||
|
@ -390,7 +401,7 @@ class Babel(QuaggaService, ConfigService):
|
|||
return self.render_text(text, data)
|
||||
|
||||
def quagga_iface_config(self, iface: CoreInterface) -> str:
|
||||
if isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||
if is_wireless(iface.net):
|
||||
text = """
|
||||
babel wireless
|
||||
no babel split-horizon
|
||||
|
|
|
@ -49,6 +49,7 @@ class NodeTypes(Enum):
|
|||
CONTROL_NET = 13
|
||||
DOCKER = 15
|
||||
LXC = 16
|
||||
WIRELESS = 17
|
||||
|
||||
|
||||
class LinkTypes(Enum):
|
||||
|
|
|
@ -58,6 +58,7 @@ from core.nodes.network import (
|
|||
WlanNode,
|
||||
)
|
||||
from core.nodes.physical import PhysicalNode, Rj45Node
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.plugins.sdt import Sdt
|
||||
from core.services.coreservices import CoreServices
|
||||
from core.xml import corexml, corexmldeployment
|
||||
|
@ -80,13 +81,18 @@ NODES: Dict[NodeTypes, Type[NodeBase]] = {
|
|||
NodeTypes.CONTROL_NET: CtrlNet,
|
||||
NodeTypes.DOCKER: DockerNode,
|
||||
NodeTypes.LXC: LxcNode,
|
||||
NodeTypes.WIRELESS: WirelessNode,
|
||||
}
|
||||
NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES}
|
||||
CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode}
|
||||
CTRL_NET_ID: int = 9001
|
||||
LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"]
|
||||
NT: TypeVar = TypeVar("NT", bound=NodeBase)
|
||||
WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet]] = (WlanNode, EmaneNet)
|
||||
WIRELESS_TYPE: Tuple[Type[WlanNode], Type[EmaneNet], Type[WirelessNode]] = (
|
||||
WlanNode,
|
||||
EmaneNet,
|
||||
WirelessNode,
|
||||
)
|
||||
|
||||
|
||||
class Session:
|
||||
|
@ -274,9 +280,9 @@ class Session:
|
|||
# custom links
|
||||
iface1 = None
|
||||
iface2 = None
|
||||
if isinstance(node1, WlanNode):
|
||||
if isinstance(node1, (WlanNode, WirelessNode)):
|
||||
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)
|
||||
elif isinstance(node1, EmaneNet) and isinstance(node2, CoreNode):
|
||||
iface2 = self._add_emane_link(node2, iface2_data, node1)
|
||||
|
@ -298,7 +304,10 @@ class Session:
|
|||
return iface1, iface2
|
||||
|
||||
def _add_wlan_link(
|
||||
self, node: NodeBase, iface_data: InterfaceData, net: WlanNode
|
||||
self,
|
||||
node: NodeBase,
|
||||
iface_data: InterfaceData,
|
||||
net: Union[WlanNode, WirelessNode],
|
||||
) -> CoreInterface:
|
||||
"""
|
||||
Create a wlan link.
|
||||
|
@ -392,10 +401,10 @@ class Session:
|
|||
)
|
||||
iface1 = None
|
||||
iface2 = None
|
||||
if isinstance(node1, WlanNode):
|
||||
if isinstance(node1, (WlanNode, WirelessNode)):
|
||||
iface2 = node2.delete_iface(iface2_id)
|
||||
node1.detach(iface2)
|
||||
elif isinstance(node2, WlanNode):
|
||||
elif isinstance(node2, (WlanNode, WirelessNode)):
|
||||
iface1 = node1.delete_iface(iface1_id)
|
||||
node2.detach(iface1)
|
||||
elif isinstance(node1, EmaneNet):
|
||||
|
@ -1111,6 +1120,10 @@ class Session:
|
|||
# boot node services and then start mobility
|
||||
exceptions = self.boot_nodes()
|
||||
if not exceptions:
|
||||
# complete wireless node
|
||||
for node in self.nodes.values():
|
||||
if isinstance(node, WirelessNode):
|
||||
node.post_startup()
|
||||
self.mobility.startup()
|
||||
# notify listeners that instantiation is complete
|
||||
event = EventData(event_type=EventTypes.INSTANTIATION_COMPLETE)
|
||||
|
|
BIN
daemon/core/gui/data/icons/wireless.png
Normal file
BIN
daemon/core/gui/data/icons/wireless.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -53,6 +53,7 @@ class ImageEnum(Enum):
|
|||
LINK = "link"
|
||||
HUB = "hub"
|
||||
WLAN = "wlan"
|
||||
WIRELESS = "wireless"
|
||||
EMANE = "emane"
|
||||
RJ45 = "rj45"
|
||||
TUNNEL = "tunnel"
|
||||
|
|
|
@ -18,7 +18,11 @@ NETWORK_NODES: List["NodeDraw"] = []
|
|||
NODE_ICONS = {}
|
||||
CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, 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}
|
||||
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
|
||||
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
|
||||
|
@ -46,6 +50,7 @@ def setup() -> None:
|
|||
(ImageEnum.HUB, NodeType.HUB, "Hub"),
|
||||
(ImageEnum.SWITCH, NodeType.SWITCH, "Switch"),
|
||||
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "WLAN"),
|
||||
(ImageEnum.WIRELESS, NodeType.WIRELESS, "Wireless"),
|
||||
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
|
||||
(ImageEnum.RJ45, NodeType.RJ45, "RJ45"),
|
||||
(ImageEnum.TUNNEL, NodeType.TUNNEL, "Tunnel"),
|
||||
|
|
|
@ -766,8 +766,9 @@ class CoreNode(CoreNodeBase):
|
|||
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}"
|
||||
# use default iface name for container, if a unique name was not provided
|
||||
if iface.name == name:
|
||||
name = f"eth{iface_id}"
|
||||
self.node_net_client.device_name(iface.name, name)
|
||||
iface.name = name
|
||||
# turn checksums off
|
||||
|
|
240
daemon/core/nodes/wireless.py
Normal file
240
daemon/core/nodes/wireless.py
Normal 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")
|
|
@ -4,7 +4,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper
|
|||
|
||||
import logging
|
||||
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 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.enumerations import EventTypes, MessageFlags
|
||||
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.wireless import WirelessNode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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"
|
||||
NODE_LAYER: str = "CORE::Nodes"
|
||||
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"
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
Helper class for exporting session objects to NRL"s SDT3D.
|
||||
|
@ -67,7 +71,7 @@ class Sdt:
|
|||
:param session: session this manager is tied to
|
||||
"""
|
||||
self.session: "Session" = session
|
||||
self.sock: Optional[IO] = None
|
||||
self.sock: Optional[socket.socket] = None
|
||||
self.connected: bool = False
|
||||
self.url: str = self.DEFAULT_SDT_URL
|
||||
self.address: Optional[Tuple[Optional[str], Optional[int]]] = None
|
||||
|
@ -210,26 +214,23 @@ class Sdt:
|
|||
|
||||
:return: nothing
|
||||
"""
|
||||
nets = []
|
||||
# create layers
|
||||
for layer in CORE_LAYERS:
|
||||
self.cmd(f"layer {layer}")
|
||||
|
||||
with self.session.nodes_lock:
|
||||
for node_id in self.session.nodes:
|
||||
node = self.session.nodes[node_id]
|
||||
if isinstance(node, CoreNetworkBase):
|
||||
nets = []
|
||||
for node in self.session.nodes.values():
|
||||
if isinstance(node, (EmaneNet, WlanNode)):
|
||||
nets.append(node)
|
||||
if not isinstance(node, NodeBase):
|
||||
continue
|
||||
self.add_node(node)
|
||||
|
||||
for link in self.session.link_manager.links():
|
||||
if is_wireless(link.node1) or is_wireless(link.node2):
|
||||
continue
|
||||
link_data = link.get_data(MessageFlags.ADD)
|
||||
self.handle_link_update(link_data)
|
||||
for net in nets:
|
||||
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
|
||||
for link_data in net.links(MessageFlags.ADD):
|
||||
self.handle_link_update(link_data)
|
||||
|
||||
def get_node_position(self, node: NodeBase) -> Optional[str]:
|
||||
|
@ -341,7 +342,7 @@ class Sdt:
|
|||
result = False
|
||||
try:
|
||||
node = self.session.get_node(node_id, NodeBase)
|
||||
result = isinstance(node, (WlanNode, EmaneNet))
|
||||
result = isinstance(node, (WlanNode, EmaneNet, WirelessNode))
|
||||
except CoreError:
|
||||
pass
|
||||
return result
|
||||
|
|
|
@ -7,15 +7,26 @@ from typing import Optional, Tuple
|
|||
import netaddr
|
||||
|
||||
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.network import PtpNet, WlanNode
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
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):
|
||||
name: str = "FRRzebra"
|
||||
group: str = "FRR"
|
||||
|
@ -593,7 +604,7 @@ class FRRBabel(FrrService):
|
|||
|
||||
@classmethod
|
||||
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"
|
||||
else:
|
||||
return " babel wired\n babel split-horizon\n"
|
||||
|
|
|
@ -7,15 +7,26 @@ import netaddr
|
|||
|
||||
from core.emane.nodes import EmaneNet
|
||||
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.network import PtpNet, WlanNode
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
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):
|
||||
name: str = "zebra"
|
||||
group: str = "Quagga"
|
||||
|
@ -431,7 +442,7 @@ class Ospfv3mdr(Ospfv3):
|
|||
@classmethod
|
||||
def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
|
||||
cfg = cls.mtu_check(iface)
|
||||
if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)):
|
||||
if is_wireless(iface.net):
|
||||
return (
|
||||
cfg
|
||||
+ """\
|
||||
|
|
|
@ -16,6 +16,7 @@ from core.nodes.docker import DockerNode
|
|||
from core.nodes.interface import CoreInterface
|
||||
from core.nodes.lxd import LxcNode
|
||||
from core.nodes.network import CtrlNet, GreTapBridge, WlanNode
|
||||
from core.nodes.wireless import WirelessNode
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -542,8 +543,8 @@ class CoreXmlWriter:
|
|||
iface2 = self.create_iface_element("iface2", iface2)
|
||||
link_element.append(iface2)
|
||||
# check for options, don't write for emane/wlan links
|
||||
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet))
|
||||
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet))
|
||||
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet, WirelessNode))
|
||||
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet, WirelessNode))
|
||||
if not (is_node1_wireless or is_node2_wireless):
|
||||
unidirectional = 1 if unidirectional else 0
|
||||
options_element = etree.Element("options")
|
||||
|
|
29
daemon/examples/grpc/wireless.py
Normal file
29
daemon/examples/grpc/wireless.py
Normal 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)
|
46
daemon/examples/python/wireless.py
Normal file
46
daemon/examples/python/wireless.py
Normal 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()
|
|
@ -100,6 +100,12 @@ service CoreApi {
|
|||
rpc WlanLink (wlan.WlanLinkRequest) returns (wlan.WlanLinkResponse) {
|
||||
}
|
||||
|
||||
// wireless rpc
|
||||
rpc WirelessLinked (WirelessLinkedRequest) returns (WirelessLinkedResponse) {
|
||||
}
|
||||
rpc WirelessConfig (WirelessConfigRequest) returns (WirelessConfigResponse) {
|
||||
}
|
||||
|
||||
// emane rpc
|
||||
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
|
||||
}
|
||||
|
@ -697,3 +703,26 @@ message LinkedRequest {
|
|||
|
||||
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 {
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue