Reject flood trace and control packets

This commit is contained in:
mikecarper committed 2026-09-17 17:30:43 -07:00
1 parent eb9d41ee75
commit e4db57dbef
6 files changed
+160 -19

No files matched your search

+26 -14
View File
@@ -715,23 +715,32 @@ void Dispatcher::checkRecv() {
#endif
snr = _radio->getLastSNR();
rssi = _radio->getLastRSSI();
logRxRaw(snr, rssi, raw, len);
pkt = _mgr->allocNew();
if (pkt == NULL) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime());
// TRACE and CONTROL are direct-only. Reject a flood framing before
// raw hooks can expose it to USB logs, MQTT, ESP-NOW, or another bridge.
// The header check is deliberate: it also holds when the packet pool is
// exhausted, so an invalid frame never slips out through a raw sink.
if (Packet::violatesRoutePolicy(raw[0])) {
pkt = NULL;
} else {
if (tryParsePacket(pkt, raw, len)) {
last_meshcore_recv_millis = _ms->getMillis();
if (auto* p = _radio->profiles()) ++p->rx_packets[pkt->radio_profile];
pkt->_snr = snr * 4.0f;
pkt->_rssi = (int16_t)rssi;
score = _radio->packetScore(snr, len);
air_time = _radio->getEstAirtimeFor(len);
rx_air_time += air_time;
logRxRaw(snr, rssi, raw, len);
pkt = _mgr->allocNew();
if (pkt == NULL) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime());
} else {
_mgr->free(pkt); // put back into pool
pkt = NULL;
if (tryParsePacket(pkt, raw, len)) {
last_meshcore_recv_millis = _ms->getMillis();
if (auto* p = _radio->profiles()) ++p->rx_packets[pkt->radio_profile];
pkt->_snr = snr * 4.0f;
pkt->_rssi = (int16_t)rssi;
score = _radio->packetScore(snr, len);
air_time = _radio->getEstAirtimeFor(len);
rx_air_time += air_time;
} else {
_mgr->free(pkt); // put back into pool
pkt = NULL;
}
}
}
} else {
@@ -978,6 +987,9 @@ void Dispatcher::releasePacket(Packet* packet) {
}
bool Dispatcher::queueOutboundPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
if (packet->violatesRoutePolicy()) {
return false;
}
if (!radio_available) {
MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): radio unavailable", getLogDateTime());
return false;
+9 -4
View File
@@ -918,6 +918,11 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
}
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
// Trace and control payloads are direct-only. Dispatcher enforces this for
// RF input before any logging/bridge hook; retain the core check for packets
// arriving through another transport implementation.
if (pkt->violatesRoutePolicy()) return ACTION_RELEASE;
observeForwardedAdvertEcho(pkt);
if (pkt->isRouteDirect()) {
cancelDirectRetryOnEcho(pkt);
@@ -3015,8 +3020,8 @@ bool Mesh::sendManagementData(Packet* packet, bool flood, const uint8_t* path,
}
bool Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_size) {
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
if (packet == NULL) return false;
if (Packet::isDirectOnlyPayloadType(packet->getPayloadType())) {
releasePacket(packet);
return false;
}
@@ -3047,8 +3052,8 @@ bool Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
}
bool Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis, uint8_t path_hash_size) {
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
if (packet == NULL) return false;
if (Packet::isDirectOnlyPayloadType(packet->getPayloadType())) {
releasePacket(packet);
return false;
}
+14
View File
@@ -84,6 +84,20 @@ public:
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; }
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
// TRACE gathers per-hop state and CONTROL can carry local discovery state.
// Neither has flood semantics. Keep this header-only so raw receive sinks
// can reject a prohibited packet before exporting it to USB/MQTT/bridges.
static bool isDirectOnlyPayloadType(uint8_t payload_type) {
return payload_type == PAYLOAD_TYPE_TRACE || payload_type == PAYLOAD_TYPE_CONTROL;
}
static bool violatesRoutePolicy(uint8_t packet_header) {
const uint8_t route = packet_header & PH_ROUTE_MASK;
const uint8_t payload_type = (packet_header >> PH_TYPE_SHIFT) & PH_TYPE_MASK;
return (route == ROUTE_TYPE_FLOOD || route == ROUTE_TYPE_TRANSPORT_FLOOD)
&& isDirectOnlyPayloadType(payload_type);
}
bool violatesRoutePolicy() const { return violatesRoutePolicy(header); }
bool hasTransportCodes() const { return getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
/**
+4 -1
View File
@@ -11,7 +11,10 @@ protected:
// Called on the mesh loop, before copying/marking/queuing a packet. Never
// invoke the policy from a WiFi callback or the MQTT publishing task.
bool allowsPacket(const mesh::Packet* packet) const {
return packet && (!_packet_filter
// Route policy is transport-independent: a flood-framed TRACE or CONTROL
// must never be relayed through a bridge even if the role has no custom
// bridge filter configured.
return packet && !packet->violatesRoutePolicy() && (!_packet_filter
|| _packet_filter(_packet_filter_context, packet));
}