mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 15:42:29 +00:00
Reject flood trace and control packets
This commit is contained in:
1 parent
eb9d41ee75
commit
e4db57dbef
6 files changed
+160
-19
No files matched your search
+26
-14
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,12 @@ protected:
|
||||
completed_packet = packet;
|
||||
completed_packets++;
|
||||
}
|
||||
void logRxRaw(float, float, const uint8_t[], int) override {
|
||||
raw_receive_logs++;
|
||||
}
|
||||
void logRx(mesh::Packet*, int, float) override {
|
||||
parsed_receive_logs++;
|
||||
}
|
||||
|
||||
public:
|
||||
mesh::Packet* failed_packet = nullptr;
|
||||
@@ -109,6 +115,8 @@ public:
|
||||
int completed_packets = 0;
|
||||
int free_count_during_failure = -1;
|
||||
int received_packets = 0;
|
||||
int raw_receive_logs = 0;
|
||||
int parsed_receive_logs = 0;
|
||||
int forced_rx_delay = -1;
|
||||
bool bypass_rx_delay = false;
|
||||
bool configured_cad_enabled = false;
|
||||
@@ -248,6 +256,85 @@ TEST(Dispatcher, FloodPacketWaitsForConfiguredRxDelayWithoutBypass) {
|
||||
EXPECT_EQ(1, dispatcher.received_packets);
|
||||
}
|
||||
|
||||
TEST(Dispatcher, FloodTraceAndControlAreDroppedBeforeLogsAndRouting) {
|
||||
RxReservePacketManager manager(4, 1);
|
||||
TestClock clock;
|
||||
TestRadio radio;
|
||||
TestDispatcher dispatcher(radio, clock, manager);
|
||||
dispatcher.begin();
|
||||
|
||||
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
|
||||
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
|
||||
uint8_t raw[7] = {};
|
||||
int raw_len = 0;
|
||||
raw[raw_len++] = route | (type << PH_TYPE_SHIFT);
|
||||
if (route == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
raw_len += 4; // transport codes
|
||||
}
|
||||
raw[raw_len++] = 0; // zero-hop path
|
||||
raw[raw_len++] = 0x42;
|
||||
|
||||
clock.now++;
|
||||
radio.queueRx(raw, raw_len);
|
||||
dispatcher.loop();
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, dispatcher.raw_receive_logs);
|
||||
EXPECT_EQ(0, dispatcher.parsed_receive_logs);
|
||||
EXPECT_EQ(0, dispatcher.received_packets);
|
||||
EXPECT_EQ(0U, dispatcher.getLastMeshCoreRecvMillis());
|
||||
EXPECT_EQ(0U, dispatcher.getNumRecvFlood());
|
||||
EXPECT_EQ(4, manager.getFreeCount());
|
||||
}
|
||||
|
||||
TEST(Dispatcher, DirectTraceAndControlRemainValid) {
|
||||
RxReservePacketManager manager(4, 1);
|
||||
TestClock clock;
|
||||
TestRadio radio;
|
||||
TestDispatcher dispatcher(radio, clock, manager);
|
||||
dispatcher.begin();
|
||||
|
||||
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
|
||||
const uint8_t raw[] = {
|
||||
static_cast<uint8_t>(ROUTE_TYPE_DIRECT | (type << PH_TYPE_SHIFT)), 0, 0x42
|
||||
};
|
||||
clock.now++;
|
||||
radio.queueRx(raw, sizeof(raw));
|
||||
dispatcher.loop();
|
||||
}
|
||||
|
||||
EXPECT_EQ(2, dispatcher.raw_receive_logs);
|
||||
EXPECT_EQ(2, dispatcher.parsed_receive_logs);
|
||||
EXPECT_EQ(2, dispatcher.received_packets);
|
||||
EXPECT_EQ(2U, dispatcher.getLastMeshCoreRecvMillis());
|
||||
EXPECT_EQ(2U, dispatcher.getNumRecvDirect());
|
||||
}
|
||||
|
||||
TEST(Dispatcher, FloodTraceAndControlCannotBeQueuedForTransmit) {
|
||||
RxReservePacketManager manager(4, 1);
|
||||
TestClock clock;
|
||||
TestRadio radio;
|
||||
TestDispatcher dispatcher(radio, clock, manager);
|
||||
dispatcher.begin();
|
||||
|
||||
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
|
||||
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
|
||||
mesh::Packet* packet = dispatcher.obtainNewPacket();
|
||||
ASSERT_NE(packet, nullptr);
|
||||
packet->header = route | (type << PH_TYPE_SHIFT);
|
||||
packet->setPathHashSizeAndCount(1, 0);
|
||||
packet->payload_len = 1;
|
||||
packet->payload[0] = 0x42;
|
||||
EXPECT_FALSE(dispatcher.sendPacket(packet, 0));
|
||||
EXPECT_FALSE(dispatcher.hasOutbound());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(4, manager.getFreeCount());
|
||||
EXPECT_EQ(0, radio.send_starts);
|
||||
}
|
||||
|
||||
TEST(Dispatcher, RejectedRadioPacketsCannotFeedMeshCoreRxWatchdog) {
|
||||
RxReservePacketManager manager(4, 1);
|
||||
TestClock clock;
|
||||
|
||||
@@ -705,6 +705,26 @@ TEST(MeshReceiveHooks, GroupPacketIsObservedWhenForwardingIsDisabled) {
|
||||
EXPECT_TRUE(node.groupPacketObserved);
|
||||
}
|
||||
|
||||
TEST(MeshReceiveHooks, FloodTraceAndControlAreNeverForwarded) {
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
TraceTestRadio radio;
|
||||
ForwardingTestTables tables;
|
||||
StaticPoolPacketManager manager(12);
|
||||
TraceTestMesh node(radio, clock, rng, rtc, manager, tables);
|
||||
node.forwardFloods = true;
|
||||
|
||||
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
|
||||
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
|
||||
mesh::Packet packet = makeFloodPacket(type);
|
||||
packet.header = route | (type << PH_TYPE_SHIFT);
|
||||
EXPECT_EQ(ACTION_RELEASE, node.receivePacket(&packet));
|
||||
EXPECT_EQ(0, tables.mark_seen_calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mesh::Packet* makeTrace(TraceTestMesh& node, uint32_t tag, uint32_t auth,
|
||||
const uint8_t* route, uint8_t route_len) {
|
||||
mesh::Packet* packet = node.createTrace(tag, auth, 0);
|
||||
|
||||
Reference in new issue
Block a user