diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 7d1d3228..edef8a0d 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1493,6 +1493,11 @@ class Session: # initialize distributed tunnels self.distributed.start() + # initialize wlan loss + for node in self.nodes.values(): + if isinstance(node, WlanNode): + node.initialize_loss() + # instantiate will be invoked again upon emane configure if self.emane.startup() == self.emane.NOT_READY: return [] diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 359dba8e..b2e037c9 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -59,6 +59,7 @@ class InterfaceManager: def next_mac(self) -> str: mac = str(self.current_mac) + logging.info("mac(%s) value(%s)", self.current_mac, self.current_mac.value) value = self.current_mac.value + 1 self.current_mac = EUI(value) self.current_mac.dialect = netaddr.mac_unix_expanded diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 05a6ac3e..ddbbdce8 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -363,14 +363,12 @@ class BasicRangeModel(WirelessModel): :return: nothing """ x, y, z = netif.node.position.get() - self._netifslock.acquire() - self._netifs[netif] = (x, y, z) - if x is None or y is None: - self._netifslock.release() - return - for netif2 in self._netifs: - self.calclink(netif, netif2) - self._netifslock.release() + with self._netifslock: + self._netifs[netif] = (x, y, z) + if x is None or y is None: + return + for netif2 in self._netifs: + self.calclink(netif, netif2) position_callback = set_position @@ -407,35 +405,43 @@ class BasicRangeModel(WirelessModel): :param netif2: interface two :return: nothing """ - if netif == netif2: + if not self.range: return + if netif == netif2: + return try: x, y, z = self._netifs[netif] x2, y2, z2 = self._netifs[netif2] - if x2 is None or y2 is None: return - d = self.calcdistance((x, y, z), (x2, y2, z2)) - # ordering is important, to keep the wlan._linked dict organized + # calculate loss + start_point = self.range / 2 + start_value = max(d - start_point, 0) + loss = max(start_value / start_point, 0.001) + loss = min(loss, 1.0) * 100 + self.wlan.change_loss(netif, netif2, loss) + self.wlan.change_loss(netif2, netif, loss) + + # # ordering is important, to keep the wlan._linked dict organized a = min(netif, netif2) b = max(netif, netif2) - with self.wlan._linked_lock: linked = self.wlan.linked(a, b) - if d > self.range: if linked: logging.debug("was linked, unlinking") self.wlan.unlink(a, b) - self.sendlinkmsg(a, b, unlink=True) + self.sendlinkmsg(MessageFlags.DELETE, a, b) else: if not linked: logging.debug("was not linked, linking") self.wlan.link(a, b) - self.sendlinkmsg(a, b) + self.sendlinkmsg(MessageFlags.ADD, a, b, loss=loss) + else: + self.sendlinkmsg(MessageFlags.NONE, a, b, loss=loss) except KeyError: logging.exception("error getting interfaces during calclinkS") @@ -472,7 +478,6 @@ class BasicRangeModel(WirelessModel): self.delay = self._get_config(self.delay, config, "delay") self.loss = self._get_config(self.loss, config, "error") self.jitter = self._get_config(self.jitter, config, "jitter") - self.setlinkparams() def create_link_data( self, @@ -499,22 +504,26 @@ class BasicRangeModel(WirelessModel): ) def sendlinkmsg( - self, netif: CoreInterface, netif2: CoreInterface, unlink: bool = False + self, + message_type: MessageFlags, + netif: CoreInterface, + netif2: CoreInterface, + loss: float = None, ) -> None: """ Send a wireless link/unlink API message to the GUI. + :param message_type: type of link message to send :param netif: interface one :param netif2: interface two - :param unlink: unlink or not + :param loss: link loss value :return: nothing """ - if unlink: - message_type = MessageFlags.DELETE - else: - message_type = MessageFlags.ADD - + label = None + if loss is not None: + label = f"{loss:.2f}%" link_data = self.create_link_data(netif, netif2, message_type) + link_data.label = label self.session.broadcast_link(link_data) def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index f2c16bd0..4eafd8b0 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -288,7 +288,6 @@ class CoreNetwork(CoreNetworkBase): self.has_ebtables_chain = False if start: self.startup() - ebq.startupdateloop(self) def host_cmd( self, @@ -335,16 +334,8 @@ class CoreNetwork(CoreNetworkBase): if not self.up: return - ebq.stopupdateloop(self) - try: self.net_client.delete_bridge(self.brname) - if self.has_ebtables_chain: - cmds = [ - f"{EBTABLES_BIN} -D FORWARD --logical-in {self.brname} -j {self.brname}", - f"{EBTABLES_BIN} -X {self.brname}", - ] - ebtablescmds(self.host_cmd, cmds) except CoreCommandError: logging.exception("error during shutdown") @@ -421,8 +412,6 @@ class CoreNetwork(CoreNetworkBase): return self._linked[netif1][netif2] = False - ebq.ebchange(self) - def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: """ Link two interfaces together, resulting in adding or removing @@ -437,8 +426,6 @@ class CoreNetwork(CoreNetworkBase): return self._linked[netif1][netif2] = True - ebq.ebchange(self) - def linkconfig( self, netif: CoreInterface, @@ -1052,6 +1039,8 @@ class WlanNode(CoreNetwork): # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) self.model = None self.mobility = None + self.loss_maps = {} + self.bands = None def startup(self) -> None: """ @@ -1061,7 +1050,88 @@ class WlanNode(CoreNetwork): """ super().startup() self.net_client.disable_mac_learning(self.brname) - ebq.ebchange(self) + + def initialize_loss(self) -> None: + logging.info("initializing loss") + + # get band count, must be at least 3 + self.bands = len(self.netifs()) + if self.bands < 3: + self.bands = 3 + logging.info("wlan qdisc prio with bands: %s", self.bands) + + # initialize loss rules + for netif in self.netifs(): + node = netif.node + name = netif.localname + logging.info("connected nodes: %s - %s", node.name, name) + + # setup root handler for wlan interface + self.host_cmd( + f"tc qdisc add dev {name} root handle 1: " f"prio bands {self.bands}" + ) + + # setup filter rules and qdisc for each other node + index = 1 + for other_netif in self.netifs(): + if netif == other_netif: + continue + other_name = other_netif.localname + logging.info("setup rules from %s to %s", name, other_name) + + # initialize filter rules for all other nodes and catch all + mac = other_netif.hwaddr + qdisc_index = self._qdisc_index(index) + logging.info( + "setup filter to %s for src mac(%s) index(%s) qdisc(%s)", + other_name, + mac, + index, + qdisc_index, + ) + + # save loss map + loss_map = self.loss_maps.setdefault(name, {}) + loss_map[other_name] = index + self.host_cmd( + f"tc filter add dev {name} protocol all parent 1: " + f"prio 1 u32 match eth src {mac} flowid 1:{index}" + ) + self.host_cmd( + f"tc qdisc add dev {name} parent 1:{index} " + f"handle {qdisc_index}: netem loss 0.1%" + ) + index += 1 + + # setup catch all + self.host_cmd( + f"tc filter add dev {name} protocol all parent 1: " + f"prio 0 u32 match u32 0 0 flowid 1:{index}" + ) + qdisc_index = self._qdisc_index(index) + self.host_cmd( + f"tc qdisc add dev {name} parent 1:{index} handle {qdisc_index}: sfq" + ) + + import pprint + + pretty_map = pprint.pformat(self.loss_maps, indent=4, compact=False) + logging.info("wlan loss map:\n%s", pretty_map) + + def _qdisc_index(self, index: int) -> int: + return self.bands + index + + def change_loss( + self, netif: CoreInterface, netif2: CoreInterface, loss: float + ) -> None: + name = netif.localname + other_name = netif2.localname + index = self.loss_maps[name][other_name] + qdisc_index = self._qdisc_index(index) + self.host_cmd( + f"tc qdisc change dev {name} parent 1:{index}" + f" handle {qdisc_index}: netem loss {loss}%" + ) def attach(self, netif: CoreInterface) -> None: """