From 1fc5c92039349435ebce107ba7235667af3dd4ba Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:57:07 -0700 Subject: [PATCH 1/2] initial work towards trying to model loss between wlan nodes using tc filters --- daemon/core/emulator/session.py | 5 ++ daemon/core/gui/interface.py | 1 + daemon/core/location/mobility.py | 57 +++++++++++-------- daemon/core/nodes/network.py | 98 +++++++++++++++++++++++++++----- 4 files changed, 123 insertions(+), 38 deletions(-) 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: """ From 914cca589a7e760f206b0d59c770b6f1eeb701b8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 27 Apr 2020 12:07:13 -0700 Subject: [PATCH 2/2] updates to leverage htb as the means for managing loss between more nodes, added in updates to rate configurations and delay/jitter settings --- daemon/core/emulator/session.py | 5 -- daemon/core/nodes/network.py | 89 ++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index edef8a0d..7d1d3228 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1493,11 +1493,6 @@ 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/nodes/network.py b/daemon/core/nodes/network.py index 4eafd8b0..6ed62a36 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1037,6 +1037,7 @@ class WlanNode(CoreNetwork): """ super().__init__(session, _id, name, start, server, policy) # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) + self.initialized = False self.model = None self.mobility = None self.loss_maps = {} @@ -1051,14 +1052,12 @@ class WlanNode(CoreNetwork): super().startup() self.net_client.disable_mac_learning(self.brname) - def initialize_loss(self) -> None: - logging.info("initializing loss") + def _initialize_tc(self) -> None: + logging.info("setting wlan configuration: %s", self.model.bw) - # get band count, must be at least 3 + # initial settings self.bands = len(self.netifs()) - if self.bands < 3: - self.bands = 3 - logging.info("wlan qdisc prio with bands: %s", self.bands) + self.loss_maps.clear() # initialize loss rules for netif in self.netifs(): @@ -1068,58 +1067,71 @@ class WlanNode(CoreNetwork): # setup root handler for wlan interface self.host_cmd( - f"tc qdisc add dev {name} root handle 1: " f"prio bands {self.bands}" + f"tc qdisc add dev {name} root handle 1: htb default {self.bands}" ) # setup filter rules and qdisc for each other node - index = 1 + index = 2 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, + "tc filter from(%s) to(%s) for src mac(%s) index(%s)", + node.name, + other_netif.node.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%" - ) + self._tc_filter(name, index, mac) 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" - ) + loss_map = self.loss_maps.setdefault(name, {}) + loss_map["catch"] = index + self._tc_catch_all(name, index) 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 _tc_update_rate(self): + for name, loss_map in self.loss_maps.items(): + for index in loss_map.values(): + self.host_cmd( + f"tc class change dev {name} parent 1: classid 1:{index} " + f"htb rate {self.model.bw}" + ) + + def _tc_catch_all(self, name: str, index: int) -> None: + self.host_cmd( + f"tc class add dev {name} parent 1: classid 1:{index} " + f"htb rate {self.model.bw}" + ) + self.host_cmd( + f"tc filter add dev {name} protocol all parent 1: " + f"prio 0 u32 match u32 0 0 flowid 1:{index}" + ) + self.host_cmd(f"tc qdisc add dev {name} parent 1:{index} handle {index}: sfq") + + def _tc_filter(self, name: str, index: int, mac: str) -> None: + self.host_cmd( + f"tc class add dev {name} parent 1: classid 1:{index} " + f"htb rate {self.model.bw}" + ) + 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 {index}: netem loss 0.01%" + ) def change_loss( self, netif: CoreInterface, netif2: CoreInterface, loss: float @@ -1127,10 +1139,10 @@ class WlanNode(CoreNetwork): 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}%" + f" handle {index}: netem loss {loss}%" + f" delay {self.model.delay}us {self.model.jitter}us 25%" ) def attach(self, netif: CoreInterface) -> None: @@ -1176,6 +1188,11 @@ class WlanNode(CoreNetwork): "node(%s) updating model(%s): %s", self.id, self.model.name, config ) self.model.update_config(config) + if not self.initialized: + self.initialized = True + self._initialize_tc() + else: + self._tc_update_rate() for netif in self.netifs(): netif.setposition()