-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Traffic Management Module for packet forwarding logic #9358
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
Draft
h3lix1
wants to merge
18
commits into
meshtastic:develop
Choose a base branch
from
h3lix1:traffic_module
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 tasks
629967e to
1449510
Compare
27759c2 to
9beb6db
Compare
… was a router. Also works for CLIENT_BASE.
… 1 so it will be 0 when sent.
…cks later in the code.
…tal using 8 bit hashes with 0.4% collision. Probably ok. Adding portduino to the platforms that don't need to worry about memory as much.
…ement.nodeinfo_direct_response_max_hops
…for some values, reduced a couple bytes per entry by using a resolution-scale time selection based on configuration value.
… into traffic_module
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
New Module: TrafficManagementModule
Architecture
classDiagram class MeshModule { +handleReceived(MeshPacket) ProcessMessage +alterReceived(MeshPacket) +wantPacket(MeshPacket) bool #isPromiscuous bool #encryptedOk bool } class OSThread { +runOnce() int32_t #setIntervalFromNow(ms) } class TrafficManagementModule { -cacheLock Lock -cache UnifiedCacheEntry* -stats TrafficManagementStats -cacheEpochMs uint32_t +getStats() TrafficManagementStats +resetStats() +recordRouterHopPreserved() -shouldDropPosition() bool -shouldDropUnknown() bool -isRateLimited() bool -shouldRespondToNodeInfo() bool -computePositionHash()$ uint8_t } MeshModule <|-- TrafficManagementModule OSThread <|-- TrafficManagementModule TrafficManagementModule --> Router : allocForSending TrafficManagementModule --> MeshService : sendToMeshMeshModule(packet handling) +concurrency::OSThread(periodic maintenance)src/modules/TrafficManagementModule.cppandsrc/modules/TrafficManagementModule.hkMaintenanceIntervalMs)isPromiscuous = trueallows inspection of all packets, not just those addressed to this nodeencryptedOk = truepermits handling undecryptable packets for unknown-packet trackingCompile-Time Gating
Module instantiation in
setupModules()requires all three conditions:flowchart TD A[setupModules called] --> B{HAS_TRAFFIC_MANAGEMENT<br/>== 1?} B -->|No| Z[Module not compiled] B -->|Yes| C{MESHTASTIC_EXCLUDE_<br/>TRAFFIC_MANAGEMENT?} C -->|Yes| Z C -->|No| D{moduleConfig.<br/>traffic_management.<br/>enabled?} D -->|No| Y[Module compiled but<br/>not instantiated] D -->|Yes| E[new TrafficManagementModule] E --> F[trafficManagementModule = instance]HAS_TRAFFIC_MANAGEMENTdefined as 1 (variant-level)!MESHTASTIC_EXCLUDE_TRAFFIC_MANAGEMENT(build exclusion flag)moduleConfig.traffic_management.enabled(runtime config)Memory Management for Embedded Devices
Unified Cache with Cuckoo Hashing
The module uses a unified cache combining all per-node tracking into a single 10-byte structure with cuckoo hashing for O(1) lookups. This provides:
UnifiedCacheEntry Structure (10 bytes) - All Platforms
A single compact structure used across ESP32, NRF52, and all other platforms:
Position Fingerprinting
Instead of storing full coordinates (8 bytes) or using a computed hash, the module uses a deterministic 8-bit fingerprint derived from the truncated lat/lon:
fingerprint = (lat_low4 << 4) | lon_low4Where
lat_low4andlon_low4are the lower 4 significant bits of each truncated coordinate.Benefits over hash-based approach:
Guard for low precision: If
position_precision_bits < 4, usesmin(precision, 4)bits from each coordinate.Adaptive Timestamp Resolution
All timestamps use 8-bit values with adaptive resolution calculated from config at startup:
Since config changes require reboot, resolution is calculated once in the constructor.
Memory Efficiency vs NodeDB:
The traffic management cache is dramatically more memory-efficient than full NodeDB entries:
meshtastic_NodeInfoLiteUnifiedCacheEntryThe unified cache achieves ~20x memory reduction compared to duplicating NodeDB data because it stores only the minimal state needed for traffic decisions:
Cuckoo Hashing Algorithm
Cuckoo hashing provides O(1) worst-case lookup using two hash functions:
flowchart TD A[lookup node] --> B{cache h1 node == node?} B -->|Yes| C[Return entry at h1] B -->|No| D{cache h2 node == node?} D -->|Yes| E[Return entry at h2] D -->|No| F[Not found] G[insert node] --> H{slot h1 empty?} H -->|Yes| I[Insert at h1] H -->|No| J{slot h2 empty?} J -->|Yes| K[Insert at h2] J -->|No| L[Kick entry from h1] L --> M[Place kicked entry at its alternate] M --> N{Max kicks reached?} N -->|No| L N -->|Yes| O[Evict oldest entry]Hash functions:
Relative Timestamp Compression
Timestamps use relative offsets from a rolling epoch, with different granularity by platform:
Full mode (ESP32 + PSRAM):
Compact mode (NRF52, non-PSRAM ESP32):
The shorter epoch reset interval in compact mode (3.5h vs 17h) is acceptable since it only invalidates cached position data, not ongoing traffic management functionality.
Platform-Specific Allocation
flowchart TD subgraph ESP32_PSRAM["ESP32 + PSRAM"] A1[Constructor] --> A2{ps_calloc success?} A2 -->|Yes| A3[Full structure 20B<br/>in PSRAM] A2 -->|No| A4[Fallback to heap] end subgraph NATIVE["Native/Portduino"] D1[Constructor] --> D2[Full structure 20B<br/>in heap] end subgraph ESP32_NO_PSRAM["ESP32 without PSRAM"] B1[Constructor] --> B2[Compact structure 12B<br/>in heap] end subgraph NRF52["NRF52"] C1[Constructor] --> C2[Compact structure 12B<br/>in heap] endPlatform Detection: At compile time,
#if (defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)selects the full 20-byte structure with coordinate storage. Memory-constrained platforms (NRF52, non-PSRAM ESP32) use the compact 12-byte structure with 8-bit position hashing.Cache Sizing
Cache size rounds up to power-of-2 for efficient cuckoo hash indexing:
Memory Budget Calculations
Cache memory at 2048 entries (unified 10-byte structure):
Note:
TRAFFIC_MANAGEMENT_CACHE_SIZEcan be overridden per-variant. The value rounds up to the next power of 2 for efficient cuckoo hash indexing (e.g., 1000 → 1024, 2000 → 2048).Typical available RAM by platform:
Risk assessment with unified 10-byte structure (2048 entries = 20KB):
Recommended Cache Sizes by Platform
Example variant enablement:
Current Enablement Status
Traffic management is enabled on:
Both use
TRAFFIC_MANAGEMENT_CACHE_SIZE=2048(~20KB with unified 10-byte entries).Other variants can enable via
HAS_TRAFFIC_MANAGEMENT=1in theirvariant.h.Cuckoo Hash Lookup and Insertion
Cuckoo hashing provides O(1) lookup by checking exactly two locations:
flowchart TD subgraph Lookup["O(1) Lookup"] L1[findEntry node] --> L2{cache h1 == node?} L2 -->|Yes| L3[Return entry] L2 -->|No| L4{cache h2 == node?} L4 -->|Yes| L3 L4 -->|No| L5[Return null] end subgraph Insert["O(1) Amortized Insert"] I1[findOrCreate node] --> I2[Check h1 and h2] I2 --> I3{Found existing?} I3 -->|Yes| I4[Return existing] I3 -->|No| I5{Empty slot?} I5 -->|Yes| I6[Insert and return] I5 -->|No| I7[Cuckoo displacement] I7 --> I8{Max kicks<br/>exceeded?} I8 -->|No| I9[Kick entry to<br/>alternate slot] I9 --> I7 I8 -->|Yes| I10[Evict oldest<br/>globally] I10 --> I6 endCuckoo displacement algorithm:
This O(1) lookup is critical for packet processing performance:
Performance comparison:
Thread Safety
Lock Architecture
The module uses a single
mutable concurrency::Lock cacheLockmember to protect all cache and statistics operations.Codebase Lock Pattern Comparison:
extern Lock *spiLock;LockGuard g(spiLock);Lock mutex;LockGuard g(&mutex);mutable Lock cacheLock;LockGuard g(&cacheLock);Why
mutable? ThegetStats()method isconstbut must acquire a lock. Declaring the lock asmutableallows locking in const methods without requiring a dangerousconst_cast. This is semantically correct: locking doesn't affect the logical const-ness of the object.Compare to
ScanI2CTwoWirewhich uses a non-mutable lock and requires casting in const methods:FreeRTOS Implementation Details
The underlying
concurrency::Lockuses FreeRTOS binary semaphores:On non-FreeRTOS platforms, locking is a no-op (single-threaded execution).
Critical Sections Analysis
getStats()resetStats()incrementStat()runOnce()shouldDropPosition()isRateLimited()shouldDropUnknown()Variable Naming Convention
Codebase uses both
gandguardfor lock guard variables:g- SafeFile, FSCommon, Router, ContentHandler, TFTDisplayguard- PhoneAPI, ScanI2CTwoWire, FlashData, MessageStoreTrafficManagementModule uses
guard, consistent with PhoneAPI and modern modules.Statistics Counters
incrementStat()with lock guardmeshtastic_TrafficManagementStats) is 32 bytes (8 x uint32_t)getStats()returns copy under lock to prevent torn reads across multiple countersPacket Handling Flow
handleReceived() Processing Order
flowchart TD A[Packet Received] --> B{Module Enabled?} B -->|No| Z[Return CONTINUE] B -->|Yes| C[Increment packets_inspected] C --> D{Undecoded Packet?} D -->|Yes| E{drop_unknown enabled?} E -->|Yes| F{Threshold exceeded?} F -->|Yes| G[/"Drop: unknown_packet_drops++<br/>Return STOP"/] F -->|No| H[Track in UnknownTracker] E -->|No| H H --> Z D -->|No| I{NodeInfo Request?} I -->|Yes| J{nodeinfo_direct_response<br/>enabled?} J -->|Yes| K{want_response &&<br/>!broadcast && !toUs?} K -->|Yes| L{hopsAway <= maxHops?<br/>Router: 3, Client: 0} L -->|Yes| M[/"Send cached User<br/>hop_limit=0<br/>nodeinfo_cache_hits++<br/>Return STOP"/] L -->|No - too far| N[Continue to next check] K -->|No| N J -->|No| N I -->|No| N N --> O{Position Packet?<br/>Not from us?} O -->|Yes| P{position_dedup_enabled?} P -->|Yes| Q[Decode & truncate lat/lon] Q --> R{Duplicate within<br/>min_interval?} R -->|Yes| S[/"Drop: position_dedup_drops++<br/>Return STOP"/] R -->|No| T[Update cache] T --> U P -->|No| U O -->|No| U U{Rate limit check?<br/>Not from us?<br/>Not ROUTING/ADMIN?} U -->|Yes| V{rate_limit_enabled?} V -->|Yes| W{Limit exceeded?} W -->|Yes| X[/"Drop: rate_limit_drops++<br/>Return STOP"/] W -->|No| Y[Increment counter] Y --> Z V -->|No| Z U -->|No| Z Z[Return CONTINUE]NodeInfo Direct Response - Role-Based Limits
Both routers and clients use maxHops logic: respond when
hopsAway <= threshold.Role determines the enforced maximum (not just a default).
Examples:
Note:
nodeinfo_direct_responsemust be enabled separately for this feature to work.Adjustable constants in
TrafficManagementModule.cpp:kRouterDefaultMaxHops = 3- Router limit: max 3 hopskClientDefaultMaxHops = 0- Client limit: direct only (cannot increase)alterReceived() - Local Broadcast Hop Exhaustion
For locally-originated broadcasts (
isFromUs(&mp) && isBroadcast(mp.to)):exhaust_hop_telemetryand port isTELEMETRY_APP: sethop_limit = 0exhaust_hop_positionand port isPOSITION_APP: sethop_limit = 0Dry Run Mode
When
cfg.dry_run == true:dry_run_would_dropcounter incremented instead of action counter[TM]prefix for analysisPosition Truncation Algorithm
Coordinates are truncated to configurable precision for deduplication:
Precision examples (approximate):
Default Values
Defaults are defined in
src/mesh/Default.hand applied at runtime viaDefault::getConfiguredOrDefault():position_precision_bitsposition_min_interval_secsThese defaults provide aggressive deduplication suitable for reducing network traffic while still allowing meaningful position updates. Users can adjust via app settings.
Router Hop Preservation
Router.cpp Changes
When
traffic_management.router_preserve_hopsis enabled and the local device is a router role (ROUTER, ROUTER_LATE, or CLIENT_BASE), hops are unconditionally preserved. This is a "best effort" approach that does not attempt to identify whether the previous relay was also a router, since therelay_nodefield only contains the last byte of the node ID (8 bits), making reliable identification impossible due to collision risk.The feature simply preserves hop budget when router-role devices relay packets, extending effective mesh range through infrastructure routers.
Protocol Buffer Definitions
TrafficManagementConfig (module_config.proto)
15 configuration fields using nanopb STATIC encoding:
Generated C struct:
meshtastic_ModuleConfig_TrafficManagementConfig(~36 bytes)TrafficManagementStats (telemetry.proto)
8 counter fields for telemetry reporting:
Generated C struct:
meshtastic_TrafficManagementStats(32 bytes)Admin/Phone API Integration
AdminModule.cpp Changes
Get handler (
handleGetModuleConfig):Set handler (
handleSetModuleConfig):PhoneAPI.cpp Changes
Traffic management config included in module config enumeration sent to connected clients.
Periodic Maintenance Thread
The
runOnce()method executes every 60 seconds to garbage-collect stale cache entries:This prevents cache pollution from inactive nodes and ensures memory is reclaimed.
Build/Variant Enablement
mesh-pb-constants.h Additions
Variant Example: Heltec V4
variants/esp32s3/heltec_v4/variant.h: