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

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ void setup()
}
nodeStatus->observe(&nodeDB.newStatus);

config.lora.next_hop_routing = true; // FIXME - remove this before merging
LOG_INFO("USING NEXT-HOP ROUTING\n");

service.init();

// Now that the mesh service is created, create any modules
Expand Down
2 changes: 1 addition & 1 deletion src/mesh/FloodingRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
}
// handle the packet as normal
Router::sniffReceived(p, c);
}
}
106 changes: 106 additions & 0 deletions src/mesh/NextHopRouter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "NextHopRouter.h"

NextHopRouter::NextHopRouter() {}

/**
* Send a packet
*/
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
{
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
wasSeenRecently(p); // FIXME, move this to a sniffSent method

p->next_hop = getNextHop(p->to, p->current_relayer); // set the next hop
LOG_DEBUG("Setting next hop for packet with dest %x to %x\n", p->to, p->next_hop);

return Router::send(p);
}

bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
if (p->next_hop == getNodeNum()) {
LOG_DEBUG("Ignoring incoming msg, because we've already seen it.\n");
} else {
LOG_DEBUG("Ignoring incoming msg, because we've already seen it and cancel any outgoing packets.\n");
Router::cancelSending(p->from, p->id);
}
return true;
}

return Router::shouldFilterReceived(p);
}

void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
bool isAck =
((c && c->error_reason == meshtastic_Routing_Error_NONE)); // consider only ROUTING_APP message without error as ACK
if (isAck) {
// Update next-hop of this successful transmission to current relayer, but ONLY if "from" is not 0 or ourselves (means
// implicit ACK or someone is relaying our ACK)
if (p->from != 0 && p->from != getNodeNum()) {
if (p->current_relayer) {
GUVWAF marked this conversation as resolved.
Show resolved Hide resolved
meshtastic_NodeInfoLite *sentTo = nodeDB.getMeshNode(p->from);
if (sentTo) {
LOG_DEBUG("Update next hop of %x to %x based on received ACK.\n", p->from, p->current_relayer);
sentTo->next_hop = p->current_relayer;
}
}
}
}

if (config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
if ((p->to != getNodeNum()) && (getFrom(p) != getNodeNum())) {
if (p->next_hop == getNodeNum()) {
GUVWAF marked this conversation as resolved.
Show resolved Hide resolved
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Relaying received next-hop message coming from %x\n", p->current_relayer);

if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
// If it is a traceRoute request, update the route that it went via me
if (traceRouteModule && traceRouteModule->wantPacket(tosend))
traceRouteModule->updateRoute(tosend);
// If it is a neighborInfo packet, update last_sent_by_id
if (neighborInfoModule && neighborInfoModule->wantPacket(tosend))
neighborInfoModule->updateLastSentById(tosend);
}

tosend->hop_limit--; // bump down the hop count
NextHopRouter::send(tosend);
} else if (p->next_hop == 0) {
GUVWAF marked this conversation as resolved.
Show resolved Hide resolved
// No preference for next hop, use FloodingRouter
LOG_DEBUG("No preference for next hop, using FloodingRouter\n");
FloodingRouter::sniffReceived(p, c);
} else if (p->to == NODENUM_BROADCAST) {
GUVWAF marked this conversation as resolved.
Show resolved Hide resolved
// TODO how to handle broadcast messages?
LOG_DEBUG("TODO: Broadcast next-hop message\n");
FloodingRouter::sniffReceived(p, c);
}
}
} else {
LOG_DEBUG("Not rebroadcasting. Role = Role_ClientMute\n");
}
// handle the packet as normal
Router::sniffReceived(p, c);
}

/**
* Get the next hop for a destination, given the current relayer
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint32_t NextHopRouter::getNextHop(NodeNum to, NodeNum current_relayer)
{
meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(to);
if (node) {
// We are careful not to return the current relayer as the next hop
if (node->next_hop != current_relayer) {
LOG_DEBUG("Next hop for %x is %x\n", to, node->next_hop);
return node->next_hop;
} else {
LOG_WARN("Next hop for %x is %x, which is the same as current relayer; setting as no preference\n", to,
node->next_hop);
return 0;
}
} else {
return 0;
}
}
49 changes: 49 additions & 0 deletions src/mesh/NextHopRouter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include "FloodingRouter.h"

/*
Router 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 either the NeighborInfoModule, or a
previous successful delivery via flooding. It is only used for DMs and not used for broadcasts. Using the NeighborInfoModule, it
can derive the next hop of neighbors and that of neighbors of neighbors. For others, it has no information in the beginning,
which results into falling back to the FloodingRouter. Upon successful delivery via flooding, it updates the next hop of the
recipient to the node that last relayed the ACK to us. When the ReliableRouter is doing retransmissions, at the last retry, it
will reset the next hop, in order to fall back to the FloodingRouter.
*/
class NextHopRouter : public FloodingRouter
{
public:
/**
* Constructor
*
*/
NextHopRouter();

/**
* Send a packet
* @return an error code
*/
virtual ErrorCode send(meshtastic_MeshPacket *p) override;

protected:
/**
* Should this incoming filter be dropped?
*
* Called immediately on reception, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;

/**
* Look for packets we need to relay
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;

private:
/**
* Get the next hop for a destination, given the current relayer
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint32_t getNextHop(NodeNum to, NodeNum current_relayer);
};
10 changes: 10 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,16 @@ meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
return NULL;
}

// Find a node in the database that matches the last byte, return 0 if not found
NodeNum NodeDB::findMatchingNodeNum(uint8_t last_byte)
{
for (int i = 0; i < *numMeshNodes; i++)
if ((uint8_t)(meshNodes[i].num & 0xFF) == last_byte)
return meshNodes[i].num;

return 0;
}

/// Find a node in our DB, create an empty NodeInfo if missing
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
{
Expand Down
3 changes: 2 additions & 1 deletion src/mesh/NodeDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class NodeDB
}

meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
NodeNum findMatchingNodeNum(uint8_t last_byte);
size_t getNumMeshNodes() { return *numMeshNodes; }

private:
Expand Down Expand Up @@ -242,4 +243,4 @@ extern uint32_t error_address;
#define Module_Config_size \
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
4 changes: 3 additions & 1 deletion src/mesh/RadioInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
p->hop_limit = HOP_RELIABLE;
}
h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0);
h->next_hop = (p->next_hop & 0xFF); // set last byte of next_hop
h->current_relayer = (p->current_relayer & 0xFF); // set last byte of current_relayer

// if the sender nodenum is zero, that means uninitialized
assert(h->from);
Expand All @@ -546,4 +548,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)

sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
}
}
8 changes: 7 additions & 1 deletion src/mesh/RadioInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ typedef struct {

/** The channel hash - used as a hint for the decoder to limit which channels we consider */
uint8_t channel;

// Last byte of the NodeNum of the next-hop for this packet
uint8_t next_hop;

// Last byte of the NodeNum of the current relayer of this packet
uint8_t current_relayer;
GUVWAF marked this conversation as resolved.
Show resolved Hide resolved
} PacketHeader;

/**
Expand Down Expand Up @@ -223,4 +229,4 @@ class RadioInterface
};

/// Debug printing for packets
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
void printPacket(const char *prefix, const meshtastic_MeshPacket *p);
2 changes: 2 additions & 0 deletions src/mesh/RadioLibInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ void RadioLibInterface::handleReceiveInterrupt()
assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
mp->next_hop = nodeDB.findMatchingNodeNum(h->next_hop);
mp->current_relayer = nodeDB.findMatchingNodeNum(h->current_relayer);

addReceiveMetadata(mp);

Expand Down
26 changes: 19 additions & 7 deletions src/mesh/ReliableRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
}
}

return FloodingRouter::send(p);
return config.lora.next_hop_routing ? NextHopRouter::send(p) : FloodingRouter::send(p);
}

bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
Expand Down Expand Up @@ -71,19 +71,19 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
i->second.nextTxMsec += iface->getPacketTime(p);
}

/* Resend implicit ACKs for repeated packets (assuming the original packet was sent with HOP_RELIABLE)
/* Resend implicit ACKs for repeated packets (current relayer is the same as original transmitter)
* this way if an implicit ACK is dropped and a packet is resent we'll rebroadcast again.
* Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and
* flooding this ACK back to the original sender already adds redundancy. */
if (wasSeenRecently(p, false) && p->hop_limit == HOP_RELIABLE && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
if (wasSeenRecently(p, false) && p->current_relayer == p->from && !MeshModule::currentReply && p->to != nodeDB.getNodeNum()) {
// retransmission on broadcast has hop_limit still equal to HOP_RELIABLE
LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n");
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p);
tosend->hop_limit--; // bump down the hop count
Router::send(tosend);
}

return FloodingRouter::shouldFilterReceived(p);
return config.lora.next_hop_routing ? NextHopRouter::shouldFilterReceived(p) : FloodingRouter::shouldFilterReceived(p);
}

/**
Expand Down Expand Up @@ -133,7 +133,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
}

// handle the packet as normal
FloodingRouter::sniffReceived(p, c);
config.lora.next_hop_routing ? NextHopRouter::sniffReceived(p, c) : FloodingRouter::sniffReceived(p, c);
}

#define NUM_RETRANSMISSIONS 3
Expand Down Expand Up @@ -222,9 +222,21 @@ int32_t ReliableRouter::doRetransmissions()
LOG_DEBUG("Sending reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d\n", p.packet->from,
p.packet->to, p.packet->id, p.numRetransmissions);

if (config.lora.next_hop_routing && p.numRetransmissions == 1) {
// Last retransmission, reset next_hop (fallback to FloodingRouter)
p.packet->next_hop = 0;
// Also reset it in the nodeDB
meshtastic_NodeInfoLite *sentTo = nodeDB.getMeshNode(p.packet->to);
if (sentTo) {
LOG_DEBUG("Resetting next hop for packet with dest %x\n", p.packet->to);
sentTo->next_hop = 0;
}
}

// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
config.lora.next_hop_routing ? NextHopRouter::send(packetPool.allocCopy(*p.packet))
: FloodingRouter::send(packetPool.allocCopy(*p.packet));

// Queue again
--p.numRetransmissions;
Expand All @@ -251,4 +263,4 @@ void ReliableRouter::setNextTx(PendingPacket *pending)
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
}
}
6 changes: 3 additions & 3 deletions src/mesh/ReliableRouter.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "FloodingRouter.h"
#include "NextHopRouter.h"
#include <unordered_map>

/**
Expand Down Expand Up @@ -51,7 +51,7 @@ class GlobalPacketIdHashFunction
/**
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
*/
class ReliableRouter : public FloodingRouter
class ReliableRouter : public NextHopRouter
{
private:
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
Expand Down Expand Up @@ -120,4 +120,4 @@ class ReliableRouter : public FloodingRouter
int32_t doRetransmissions();

void setNextTx(PendingPacket *pending);
};
};
2 changes: 2 additions & 0 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// the lora we need to make sure we have replaced it with our local address
p->from = getFrom(p);

p->current_relayer = getNodeNum(); // set the current relayer to us

// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)

assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ||
Expand Down
2 changes: 1 addition & 1 deletion src/mesh/generated/meshtastic/apponly.pb.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
#define meshtastic_ChannelSet_fields &meshtastic_ChannelSet_msg

/* Maximum encoded size of messages (where known) */
#define meshtastic_ChannelSet_size 591
#define meshtastic_ChannelSet_size 593

#ifdef __cplusplus
} /* extern "C" */
Expand Down
Loading
Loading