mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-27 00:35:45 -04:00
UdpMulticastHandler clears transport_mechanism, pki_encrypted and public_key on arrival but leaves priority, via_mqtt and tx_after as the sender set them. All three are local-only, and the reasoning the BLE ingress already carries applies unchanged here: multicast carries the full proto, so the sender chooses. priority is the one that bites. It is not in the LoRa header, so fixPriority derives it locally for a radio arrival and nothing on that path can be chosen by a stranger. Over UDP a crafted packet can ask for MAX, which outranks the ceiling fixPriority assigns, and replaceLowerPriorityPacket then evicts one of our own frames to make room for it once perhapsRebroadcast queues it. via_mqtt suppresses our uplink for that packet and tx_after schedules our transmit. None of it needs a key, and multicast reaches anyone on the LAN. develop has the same gap, so this is not something the BLE spike introduced - the spike's own bearer was simply hardened and the shipped one was not. Worth its own PR against develop. Also restores two firmware anchors a comment sweep dropped from the BLE priority guard: perhapsRebroadcast is what copies the crafted value into the TX queue and replaceLowerPriorityPacket is what evicts. Native suite 1430/1430.
186 lines
7.3 KiB
C++
186 lines
7.3 KiB
C++
#include "FloodingRouter.h"
|
|
#include "MeshTransportBase.h"
|
|
#include "MeshTypes.h"
|
|
#include "NodeDB.h"
|
|
#include "configuration.h"
|
|
#include "mesh-pb-constants.h"
|
|
#include "meshUtils.h"
|
|
#include "modules/TextMessageModule.h"
|
|
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
|
#include "modules/TraceRouteModule.h"
|
|
#endif
|
|
|
|
FloodingRouter::FloodingRouter() {}
|
|
|
|
/**
|
|
* Send a packet on a suitable interface. This routine will
|
|
* later free() the packet to pool. This routine is not allowed to stall.
|
|
* If the txmit queue is full it might return an error
|
|
*/
|
|
ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
|
|
{
|
|
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
|
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
|
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
|
|
|
return Router::send(p);
|
|
}
|
|
|
|
bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
|
{
|
|
bool wasUpgraded = false;
|
|
bool seenRecently =
|
|
wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected
|
|
|
|
// Handle hop_limit upgrade scenario for rebroadcasters
|
|
if (wasUpgraded && perhapsHandleUpgradedPacket(p)) {
|
|
return true; // we handled it, so stop processing
|
|
}
|
|
|
|
if (!seenRecently && !wasUpgraded && textMessageModule) {
|
|
seenRecently = textMessageModule->recentlySeen(p->id);
|
|
}
|
|
|
|
if (seenRecently) {
|
|
printPacket("Ignore dupe incoming msg", p);
|
|
rxDupe++;
|
|
|
|
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
|
|
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
|
|
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
|
if (isRepeated) {
|
|
LOG_DEBUG("Repeated reliable tx");
|
|
// Check if it's still in the Tx queue, if not, we have to relay it again
|
|
if (!findInTxQueue(p->from, p->id)) {
|
|
if (reprocessPacket(p))
|
|
perhapsRebroadcast(p);
|
|
}
|
|
} else {
|
|
perhapsCancelDupe(p);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return Router::shouldFilterReceived(p);
|
|
}
|
|
|
|
bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p)
|
|
{
|
|
// isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages
|
|
if (isRebroadcaster() && iface && p->hop_limit > 0) {
|
|
// Verify the replacement before deleting the valid lower-hop copy waiting in the TX queue.
|
|
// This is intentionally redundant with ReliableRouter's ingress gate: it keeps this helper
|
|
// safe if another caller is introduced later.
|
|
if (passesRoutingAuthGate(const_cast<meshtastic_MeshPacket *>(p)) != RoutingAuthVerdict::ACCEPT)
|
|
return true;
|
|
|
|
// If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to
|
|
// rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead.
|
|
uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining
|
|
if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) {
|
|
LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id,
|
|
p->hop_limit, dropThreshold);
|
|
|
|
if (!reprocessPacket(p))
|
|
return true;
|
|
perhapsRebroadcast(p);
|
|
|
|
rxDupe++;
|
|
// We already enqueued the improved copy, so make sure the incoming packet stops here.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
|
|
{
|
|
if (p->which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
|
|
auto decodedState = perhapsDecode(const_cast<meshtastic_MeshPacket *>(p));
|
|
if (decodedState != DecodeState::DECODE_SUCCESS && decodedState != DecodeState::DECODE_OPAQUE)
|
|
return false;
|
|
}
|
|
|
|
if (nodeDB)
|
|
nodeDB->updateFrom(*p);
|
|
|
|
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
|
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
|
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) {
|
|
traceRouteModule->processUpgradedPacket(*p);
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
|
|
{
|
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
|
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
|
|
// ROUTER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
|
|
// even if we've heard another station rebroadcast it already.
|
|
return false;
|
|
}
|
|
|
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
|
// CLIENT_BASE: if the packet is from or to a favorited node,
|
|
// we should act like a ROUTER and should never cancel a rebroadcast (i.e. we should always rebroadcast),
|
|
// even if we've heard another station rebroadcast it already.
|
|
return !nodeDB->isFromOrToFavoritedNode(*p);
|
|
}
|
|
|
|
// All other roles (such as CLIENT) should cancel a rebroadcast if they hear another station's rebroadcast.
|
|
return true;
|
|
}
|
|
|
|
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
|
|
{
|
|
if (roleAllowsCancelingDupe(p)) {
|
|
// Strictly same-medium: an overhear on one medium is no evidence about who heard this node
|
|
// on another.
|
|
switch (p->transport_mechanism) {
|
|
case meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA:
|
|
if (Router::cancelSending(p->from, p->id))
|
|
txRelayCanceled++;
|
|
break;
|
|
case meshtastic_MeshPacket_TransportMechanism_TRANSPORT_BLE_ADV:
|
|
if (MeshTransportBase::cancelTransportsOn(p->transport_mechanism, p->from, p->id))
|
|
txRelayCanceled++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
|
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
|
}
|
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE && iface && nodeDB &&
|
|
nodeDB->isFromOrToFavoritedNode(*p)) {
|
|
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
|
}
|
|
}
|
|
|
|
bool FloodingRouter::isRebroadcaster()
|
|
{
|
|
return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
|
|
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
|
|
}
|
|
|
|
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
|
{
|
|
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
|
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
|
if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
|
|
// do not flood direct message that is ACKed or replied to
|
|
LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast");
|
|
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
|
|
}
|
|
|
|
perhapsRebroadcast(p);
|
|
|
|
// handle the packet as normal
|
|
Router::sniffReceived(p, c);
|
|
}
|