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:
parent
e4a6ecf3c2
commit
84acb82c18
19 changed files with 399 additions and 90 deletions
|
@ -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
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
"""
|
||||
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 LinkOptions
|
||||
from core.emulator.links import CoreLink
|
||||
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
|
||||
from core.nodes.network import PtpNet
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emulator.session import Session
|
||||
|
@ -16,6 +22,24 @@ if TYPE_CHECKING:
|
|||
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,
|
||||
|
@ -26,7 +50,8 @@ class WirelessNode(CoreNetworkBase):
|
|||
):
|
||||
super().__init__(session, _id, name, server)
|
||||
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:
|
||||
if self.up:
|
||||
|
@ -39,10 +64,8 @@ class WirelessNode(CoreNetworkBase):
|
|||
self.net_client.delete_bridge(bridge_name)
|
||||
self.host_cmd(f"{NFTABLES} delete table bridge {bridge_name}")
|
||||
while self.links:
|
||||
key, core_link = self.links.popitem()
|
||||
core_link.iface1.shutdown()
|
||||
core_link.iface2.shutdown()
|
||||
core_link.ptp.shutdown()
|
||||
_, link = self.links.popitem()
|
||||
link.iface.shutdown()
|
||||
self.up = False
|
||||
|
||||
def attach(self, iface: CoreInterface) -> None:
|
||||
|
@ -90,26 +113,20 @@ class WirelessNode(CoreNetworkBase):
|
|||
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}"
|
||||
iface1 = CoreInterface(0, name1, f"{name1}p", self.session.use_ovs())
|
||||
iface1.startup()
|
||||
iface2 = CoreInterface(0, name2, f"{name2}p", self.session.use_ovs())
|
||||
iface2.startup()
|
||||
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
|
||||
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, iface1.name)
|
||||
self.net_client.set_iface_master(bridge2, iface2.name)
|
||||
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)
|
||||
|
@ -130,41 +147,94 @@ class WirelessNode(CoreNetworkBase):
|
|||
|
||||
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)
|
||||
core_link = self.links.get(key)
|
||||
if not core_link:
|
||||
link = self.links.get(key)
|
||||
if not link:
|
||||
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
|
||||
ptp = core_link.ptp
|
||||
iface1, iface2 = core_link.iface1, core_link.iface2
|
||||
bridge1, bridge2 = link.bridge1, link.bridge2
|
||||
iface = link.iface
|
||||
if linked:
|
||||
ptp.attach(iface1)
|
||||
ptp.attach(iface2)
|
||||
self.net_client.set_iface_master(bridge1, iface.name)
|
||||
self.net_client.set_iface_master(bridge2, iface.localname)
|
||||
message_type = MessageFlags.ADD
|
||||
else:
|
||||
ptp.detach(iface1)
|
||||
ptp.detach(iface2)
|
||||
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,
|
||||
iface1_options: LinkOptions,
|
||||
iface2_options: LinkOptions,
|
||||
message_type: MessageFlags,
|
||||
label: str = None,
|
||||
) -> None:
|
||||
key = (node1_id, node2_id) if node1_id < node2_id else (node2_id, node1_id)
|
||||
core_link = self.links.get(key)
|
||||
if not core_link:
|
||||
raise CoreError(f"invalid node links node1({node1_id}) node2({node2_id})")
|
||||
iface1, iface2 = core_link.iface1, core_link.iface2
|
||||
iface1.options.update(iface1_options)
|
||||
iface1.set_config()
|
||||
iface2.options.update(iface2_options)
|
||||
iface2.set_config()
|
||||
"""
|
||||
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:
|
||||
logger.info(
|
||||
"received position callback for node(%s) iface(%s)",
|
||||
iface.node.name,
|
||||
iface.name,
|
||||
)
|
||||
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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue