mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-19 01:41:30 +10:00
Block coordinate traffic on configured event channels (#11045)
* Block coordinate traffic on configured event channels Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Suppress event coordinates in reliable relay paths Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Reject blocked phone coordinates before rate limiting Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Prevent event coordinates from reaching MQTT Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Add event coordinate policy preference Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Test event coordinate policy in native CI Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Make event policy test tolerate a full NodeDB Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Test Router event coordinate enforcement Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Test PhoneAPI event coordinate retry handling Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Test reliable event coordinate suppression Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Test MQTT event coordinate suppression Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * Run event policy behavioral suites in native CI Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * tests: address CodeRabbit review feedback - test_event_channel_phone_api: complete the setUp/tearDown save-restore pair. GlobalState now carries cryptLock and myNodeInfo; setUp() nulls cryptLock before constructing MockRouter (Router's ctor asserts it is unset), and tearDown() restores both so the suite leaves no global mutated. Not reachable today - the globals start null in this binary - but the pair was asymmetric. - Replace the strcpy calls this branch added on Channel.settings.name (char[12]) with the bounded form the rest of the test tree already uses, strncpy(dst, src, sizeof(dst) - 1). Covers the flagged site in test_nexthop_routing plus the six equivalents in test_event_channel_phone_api, test_mqtt and test_position_precision, which trip the same ast-grep dangerous-buffer-functions-cpp rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Sisyphus
Claude Opus 5
Ben Meadors
parent
fb17f6ddbe
commit
546b9d9e40
@@ -497,6 +497,21 @@ bool Channels::isWellKnownChannel(ChannelIndex chIndex)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Channels::isEventChannel(ChannelIndex chIndex)
|
||||
{
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL && defined(USERPREFS_CHANNEL_0_PSK)
|
||||
static const uint8_t configuredEventPsk[] = USERPREFS_CHANNEL_0_PSK;
|
||||
static_assert(sizeof(configuredEventPsk) == 16 || sizeof(configuredEventPsk) == 32,
|
||||
"USERPREFS_CHANNEL_0_PSK must be an AES-128 or AES-256 key");
|
||||
CryptoKey effectiveKey = getKey(chIndex);
|
||||
return effectiveKey.length == sizeof(configuredEventPsk) &&
|
||||
memcmp(effectiveKey.bytes, configuredEventPsk, sizeof(configuredEventPsk)) == 0;
|
||||
#else
|
||||
(void)chIndex;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Channels::hasDefaultChannel()
|
||||
{
|
||||
// If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel
|
||||
|
||||
+12
-1
@@ -5,6 +5,14 @@
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
#define USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL USERPREFS_EVENT_MODE
|
||||
#endif
|
||||
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL && !defined(USERPREFS_CHANNEL_0_PSK)
|
||||
#error "USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL requires USERPREFS_CHANNEL_0_PSK"
|
||||
#endif
|
||||
|
||||
/** A channel number (index into the channel table)
|
||||
*/
|
||||
typedef uint8_t ChannelIndex;
|
||||
@@ -95,6 +103,9 @@ class Channels
|
||||
// matches the current preset's name and PSK byte 1.
|
||||
bool isWellKnownChannel(ChannelIndex chIndex);
|
||||
|
||||
// Returns true if this channel's effective key matches USERPREFS_CHANNEL_0_PSK.
|
||||
bool isEventChannel(ChannelIndex chIndex);
|
||||
|
||||
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
|
||||
bool hasDefaultChannel();
|
||||
|
||||
@@ -164,4 +175,4 @@ bool channelFileUsesPublicKey(const meshtastic_ChannelFile &cf, ChannelIndex chI
|
||||
|
||||
static const uint8_t eventpsk[] = {0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36,
|
||||
0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74,
|
||||
0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1};
|
||||
0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1};
|
||||
|
||||
@@ -113,7 +113,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
||||
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
|
||||
if (isRepeated) {
|
||||
if (!findInTxQueue(p->from, p->id)) {
|
||||
if (reprocessPacket(p) && !perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
|
||||
if (reprocessPacket(p) && !isBlockedEventCoordinatePacket(p) && !perhapsRebroadcast(p) && isToUs(p) &&
|
||||
p->want_ack) {
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
}
|
||||
}
|
||||
@@ -190,6 +191,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
/* Check if we should be rebroadcasting this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
// Never relay coordinate-bearing packets on the event ("everyone") channel.
|
||||
// Closes the reliable-retransmit-dupe path that runs before handleReceived().
|
||||
if (isBlockedEventCoordinatePacket(p)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check if traffic management wants to exhaust this packet's hops
|
||||
bool exhaustHops = false;
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
|
||||
@@ -1812,6 +1812,16 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Reject before recording duplicate or per-port cooldown state, so a blocked
|
||||
// attempt cannot throttle a valid private-channel position retry.
|
||||
if (isBlockedEventCoordinatePacket(&p)) {
|
||||
LOG_DEBUG("Suppress phone coordinate send on event (everyone) channel");
|
||||
meshtastic_QueueStatus qs = router->getQueueStatus();
|
||||
service->sendQueueStatusToPhone(qs, 0, p.id);
|
||||
sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Location sharing is disabled on this channel");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
// For use with the simulator, we should not ignore duplicate packets from the phone
|
||||
if (SimRadio::instance == nullptr)
|
||||
|
||||
@@ -16,6 +16,10 @@ uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel)
|
||||
|
||||
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
|
||||
{
|
||||
// Event-channel privacy takes precedence over every stored precision and key policy.
|
||||
if (channels.isEventChannel(channelIndex))
|
||||
return 0;
|
||||
|
||||
const meshtastic_Channel &ch = channels.getByIndex(channelIndex);
|
||||
if (ch.role == meshtastic_Channel_Role_DISABLED)
|
||||
return 0;
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
*/
|
||||
ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (isBlockedEventCoordinatePacket(p)) {
|
||||
LOG_DEBUG("Suppress reliable coordinate send on event (everyone) channel");
|
||||
packetPool.release(p);
|
||||
return meshtastic_Routing_Error_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
const GlobalPacketId key(p);
|
||||
const bool retransmitting = p->want_ack;
|
||||
|
||||
|
||||
+70
-4
@@ -69,6 +69,52 @@ Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
|
||||
|
||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
|
||||
|
||||
static ChannelIndex getEffectiveChannelIndex(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
ChannelIndex chIndex = p->channel;
|
||||
if (nodeDB && isFromUs(p) && !chIndex && !p->pki_encrypted && !isBroadcast(p->to)) {
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
|
||||
if (node)
|
||||
chIndex = node->channel;
|
||||
}
|
||||
return chIndex;
|
||||
}
|
||||
|
||||
bool isBlockedEventCoordinatePacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
if (p->pki_encrypted || willUsePki(p)) {
|
||||
return false;
|
||||
}
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
return isCoordinatePortnum(p->decoded.portnum) && channels.isEventChannel(getEffectiveChannelIndex(p));
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
(void)p;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool willUsePki(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
if (p->which_payload_variant != meshtastic_MeshPacket_decoded_tag || !isFromUs(p))
|
||||
return false;
|
||||
bool haveDestKey = false;
|
||||
if (p->decoded.portnum == meshtastic_PortNum_KEY_VERIFICATION_APP) {
|
||||
meshtastic_NodeInfoLite_public_key_t destKey = {0, {0}};
|
||||
haveDestKey = nodeDB->copyPublicKey(p->to, destKey);
|
||||
if (!haveDestKey && p->pki_encrypted)
|
||||
haveDestKey = crypto->getPendingPublicKey(p->to, destKey);
|
||||
}
|
||||
return wouldEncryptWithPKC(p, getEffectiveChannelIndex(p), haveDestKey);
|
||||
#else
|
||||
(void)p;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct RoutingAuthCache {
|
||||
bool valid = false;
|
||||
// Deliberately NOT initialized in-class as this eats flash space.
|
||||
@@ -141,7 +187,6 @@ void resetRoutingAuthEvaluationCount()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -360,9 +405,9 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
|
||||
|
||||
// don't override if a channel was requested and no need to set it when PKI is enforced
|
||||
if (!p->channel && !p->pki_encrypted && !isBroadcast(p->to)) {
|
||||
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->to);
|
||||
if (node) {
|
||||
p->channel = node->channel;
|
||||
ChannelIndex chIndex = getEffectiveChannelIndex(p);
|
||||
if (chIndex) {
|
||||
p->channel = chIndex;
|
||||
LOG_DEBUG("localSend to channel %d", p->channel);
|
||||
}
|
||||
}
|
||||
@@ -473,6 +518,12 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
fixPriority(p); // Before encryption, fix the priority if it's unset
|
||||
// Position precision is an originator-only privacy policy. Relays keep
|
||||
// p->from as the original sender, so do not rewrite their POSITION_APP payload.
|
||||
if (isBlockedEventCoordinatePacket(p)) {
|
||||
LOG_DEBUG("Suppress coordinate send on event (everyone) channel");
|
||||
packetPool.release(p);
|
||||
return meshtastic_Routing_Error_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
if (isFromUs(p)) {
|
||||
if (!applyPositionPrecisionForChannel(*p, p->channel)) {
|
||||
LOG_ERROR("Drop malformed position packet before send");
|
||||
@@ -959,6 +1010,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
return DecodeState::DECODE_POLICY_REJECT;
|
||||
#endif
|
||||
|
||||
if (isBlockedEventCoordinatePacket(p)) {
|
||||
LOG_DEBUG("Decoded coordinate packet on event channel; suppress payload logging");
|
||||
return DecodeState::DECODE_SUCCESS;
|
||||
}
|
||||
|
||||
if (p->decoded.has_bitfield)
|
||||
p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK;
|
||||
|
||||
@@ -1436,6 +1492,16 @@ void Router::dispatchReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
}
|
||||
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
// Discard coordinate-bearing packets that arrive on the event ("everyone")
|
||||
// channel: don't process, store in NodeDB, or rebroadcast them.
|
||||
if (!skipHandle && isBlockedEventCoordinatePacket(p)) {
|
||||
LOG_DEBUG("Drop coordinate packet on event (everyone) channel");
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
printPacket("packet decoding failed or skipped (no PSK?)", p);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
#include "concurrency/OSThread.h"
|
||||
#include <memory>
|
||||
|
||||
inline bool isCoordinatePortnum(meshtastic_PortNum portnum)
|
||||
{
|
||||
return portnum == meshtastic_PortNum_POSITION_APP || portnum == meshtastic_PortNum_WAYPOINT_APP ||
|
||||
portnum == meshtastic_PortNum_MAP_REPORT_APP;
|
||||
}
|
||||
|
||||
bool isBlockedEventCoordinatePacket(const meshtastic_MeshPacket *p);
|
||||
bool willUsePki(const meshtastic_MeshPacket *p);
|
||||
|
||||
/// rx_time/has_rx_time for "now": a real epoch when the clock is trustworthy, else a
|
||||
/// Time::getMillis() placeholder with valid=false.
|
||||
struct RxTimeStamp {
|
||||
|
||||
@@ -700,6 +700,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
|
||||
{
|
||||
if (mp_encrypted.via_mqtt)
|
||||
return; // Don't send messages that came from MQTT back into MQTT
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
if (isBlockedEventCoordinatePacket(&mp_decoded)) {
|
||||
LOG_DEBUG("MQTT onSend - Suppress coordinate packet on event channel");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
bool uplinkEnabled = false;
|
||||
for (int i = 0; i <= 7; i++) {
|
||||
if (channels.getByIndex(i).settings.uplink_enabled)
|
||||
@@ -777,6 +783,12 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
|
||||
|
||||
void MQTT::perhapsReportToMap()
|
||||
{
|
||||
#if USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL
|
||||
if (channels.isEventChannel(channels.getPrimaryIndex())) {
|
||||
LOG_DEBUG("Suppress MQTT map report on event (everyone) channel");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (!moduleConfig.mqtt.map_reporting_enabled || !moduleConfig.mqtt.map_report_settings.should_report_location ||
|
||||
!(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user