Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next Hop-based routing with fallback to flooding #2856

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open

Conversation

GUVWAF
Copy link
Member

@GUVWAF GUVWAF commented Oct 2, 2023

Description

This adds a NextHopRouter for direct messages, which only relays if it is the next hop for a packet. The next hop is set by the current relayer of a packet, which bases this on information from a previous successful delivery to the destination via flooding.
Namely, in the PacketHistory, we keep track of (up to 3) relayers of a packet. When the ACK is delivered back to us via a node that also relayed the original packet, we use that node as next hop for the destination from then on. This makes sure that only when there’s a two-way connection, we assign a next hop. Both the ReliableRouter and NextHopRouter will do retransmissions (the NextHopRouter only 1 time). For the final retry, if no one actually relayed the packet, it will reset the next hop in order to fall back to the FloodingRouter again. Note that thus also intermediate hops will do a single retransmission if the intended next-hop didn’t relay, in order to fix changes in the middle of the route.

It is backwards compatible with all 2.x versions of Meshtastic, because for those nodes it will always fall back to flooding.

Implementation details

The next_hop and relay_node are added to the unencrypted PacketHeader. Since we only used 14 bytes out of the 16-byte header (it got this size due to memory alignment), the two unused bytes are used to save the last byte of the NodeNum of the next hop and current relayer. When hop_start was added (version 2.3), these bytes were set to 0, so we can use these bytes safely when hop_start is set.
The re-use of header space means that there is no additional over-the-air overhead, except that up to 2 retransmissions are needed when a change of next hop occurs. So, the benefit will be most pronounce for rather static meshes, but since the next hop is only set after a successful two-way connection is set up, and it falls back to flooding rather quickly, even for dynamic meshes there is likely a benefit to using this.

In terms of memory overhead, the next_hop has to be stored in the NodeDB (only 1 byte), and there are 4 additional bytes required per packet in the PacketHistory to store the next_hop and three relayers.

Using only 1 byte for the next_hop means that there is a chance that the last byte of two nodes match, and then they will both try to relay. However, that’s not a big issue as that would be similar to flooding. The chance that only the intended next hop relays depends on the amount of nodes that can hear the packet. If that are 10 nodes (that would be a lot), the chance that only the next hop relays is 83.7% ((255/256*254/256*253/256 ... 247/256*)*100), for 5 nodes the chance is 96.1% ((255/256*254/256*253/256*252/256)*100).

Examples

With this, you get rid of the unnecessary rebroadcasts caused by flooding like the one from node 0 as shown below. (Note that an arrow to a node means that it received it (so there might be multiple arrows for one packet), but not necessarily that it was addressed.)
image
With the NextHopRouter, node 0 doesn't try to relay:
image

Furthermore, it solves this issue of the current implementation where the wrong node (1, because it has the lowest SNR) is relaying:
image
With the NextHopRouter, due to randomness in the order of rebroadcasting, at some point the route will succeed and 2 will be set as next hop from then on.
image

Notes for reviewers

NextHopRouter inherits from the FloodingRouter. Since it also requires retransmissions, this logic is now moved from the ReliableRouter to the NextHopRouter, and the ReliableRouter inherits from the NextHopRouter.
Also, since the Router and NextHopRouter need to have access to the PacketHistory as well, the Router now inherits from it instead of only the FloodingRouter.

@loodydo
Copy link
Contributor

loodydo commented Oct 2, 2023

I have started looking. May take me some to to finish. I am not a statistician so I asked ChatGPT about the probability that the last byte of two nodes match. It came up with the result of 16.31%. Considering that this is a non breaking change I would think that the risk of two nodes broadcasting is still worth it.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2023

🤖 Pull request artifacts

file commit
pr2856-firmware-2.2.10.44dc270.zip 44dc270

thebentern added a commit to meshtastic/artifacts that referenced this pull request Oct 3, 2023
@GUVWAF
Copy link
Member Author

GUVWAF commented Oct 3, 2023

I am not a statistician so I asked ChatGPT about the probability that the last byte of two nodes match. It came up with the result of 16.31%. Considering that this is a non breaking change I would think that the risk of two nodes broadcasting is still worth it.

It depends on the amount of nodes that can hear the packet (so the amount of immediate neighbors). But indeed, the chance that then only the intended next hop relays is a bit different than I presented before. I've updated the description with two calculations.

src/mesh/RadioInterface.h Outdated Show resolved Hide resolved
src/mesh/NextHopRouter.cpp Outdated Show resolved Hide resolved
src/mesh/NextHopRouter.cpp Outdated Show resolved Hide resolved
src/mesh/NextHopRouter.cpp Outdated Show resolved Hide resolved
src/mesh/NextHopRouter.cpp Outdated Show resolved Hide resolved
@GUVWAF
Copy link
Member Author

GUVWAF commented Oct 13, 2023

I pushed a new commit to resolve @loodydo’s comments. Also added the hop limit setting of the original transmitter to the header flags (using 3 of the 4 currently unused bits), such that we can determine the amount of hops the packet has already traveled. This comes in handy to set the next hop for immediate neighbors, and I think it’s also useful for displaying in the apps. Currently it will always show the SNR/RSSI of nodes in the list, but this is actually the SNR/RSSI to the last relayer. In cases that a node is not an immediate neighbor, we could display the hops towards that node.

I also changed the way how the next_hop and relay_node are stored in the MeshPacket and NodeDB. Now only the last byte is stored, since only that byte is sent over the air. Besides, it mitigates the case where you might assign the wrong NodeNum if you happen to have multiple nodes with the same last byte in the NodeDB.

thebentern added a commit to meshtastic/artifacts that referenced this pull request Oct 13, 2023
@GUVWAF
Copy link
Member Author

GUVWAF commented Nov 26, 2023

I think this PR is ready to merge, except that it’s a breaking change unfortunately.
I did a test and it seems that the two unused bytes are not set to zero with current firmware. Meaning with this PR it would think someone set the next hop for a specific node and no one will relay the message.
We’ll have to wait for 3.0 with this.

Edit: The unused bytes are set to zero from 2.3 on, which is also when hop_start was introduced. So, if hop_start is set, we can safely use the bytes.

src/mesh/Router.cpp Outdated Show resolved Hide resolved
@GUVWAF GUVWAF added pinned Exclude from stale processing and removed 3.0 Planned for next major release labels Nov 11, 2024
@GUVWAF GUVWAF marked this pull request as ready for review November 11, 2024 12:43
@GUVWAF GUVWAF added the requires-protos A change in device that requires changes to protobufs label Nov 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pinned Exclude from stale processing requires-protos A change in device that requires changes to protobufs
Projects
None yet
Development

Successfully merging this pull request may close these issues.