Appearance
Traceroute, RSSI, SNR, and per-hop link budget
LowMesh includes mesh traceroute: an originator sends TRACEROUTE_REQ (payloadType = 0x0D); relays toward targetId record per-hop RF metrics; the destination (or last reachable hop) returns TRACEROUTE_RSP (0x0E) with a fixed-size hop report list — same types referenced in Payloads and IoT.
Diagnostics frames let operators and firmware measure multi-hop paths and RF quality toward each relay.
Metrics definitions (per reception)
| Metric | Typical source | Unit |
|---|---|---|
| RSSI | LoRa modem / RSSI after packet | dBm (often reported as negative integer) |
| SNR | LoRa packet SNR | dB (can be negative) |
| Link margin | Implementation-defined estimate | dB — headroom above required SNR for current SF |
Per-hop entry records the receiver’s measurement of the inbound transmission from the previous hop (not the forward TX).
TRACEROUTE_REQ (0x0D)
Fixed body only — no mutable traces[] accumulated in the request. Relays forward the same inner body toward targetId; hop data is collected only in the response.
| Field | Size | Description |
|---|---|---|
traceId | 2 | LE — originator picks; echoed in TRACEROUTE_RSP |
targetId | 4 | Destination nodeId32 (LE uint32) |
maxHops | 1 | Upper bound on hop reports (1–63) |
requestFlags | 1 | Reserved 0 on TX unless defined later |
innerBodyLen MUST be 8.
Forwarding: Unicast or flood toward targetId. Each relay decrements ttlRaw per Routing. When the probe reaches targetId, that node (or policy-designated responder) builds TRACEROUTE_RSP using the same traceId and measured path. Long paths MAY use fragmentation for the response body only.
TRACEROUTE_RSP (0x0E)
Returned toward the originator (unicast when addressed; otherwise flood toward the request’s srcId with bounded ttlRaw per policy).
Response body (after mesh header + decryption)
| Field | Size | Description |
|---|---|---|
traceId | 2 | LE — MUST match the request |
hopCount | 1 | Number of HopReport records following (0–63; 0 = error / no data) |
hopReports | hopCount × 16 | Fixed 16-byte records — see below |
If the path is incomplete (target unreachable), implementations MAY return a partial list with hopCount reflecting how far the probe got, or hopCount = 0 with a product-defined error extension.
Hop record (HopReport, 16 bytes fixed)
| Field | Size | Description |
|---|---|---|
nodeId | 4 | Relay nodeId32 that received from previous hop (LE uint32) |
rssiDbm | 1 | int8 measured at this hop’s RX; 0x80 = unknown |
rxSnrQ4 | 1 | int8 SNR in quarter-dB units (decodedSnrDb = rxSnrQ4 / 4.0); 0x80 = unknown (same encoding as ACK_BASIC) |
rxMarginDb | 2 | int16 LE link margin estimate |
txPowerDbm | 1 | int8 forward TX power for outbound leg (if known); 0x80 = unknown |
radioProfileId | 1 | uint8 |
relayDelayMs | 2 | uint16 LE queue/airtime delay at this hop; 0xFFFF = unknown |
reserved | 4 | MUST be 0 on TX |
Origin is hop 0 (source). Maximum records = path length, capped by maxHops in the request.
Link budget summary
Implementations SHOULD compute a scalar score for UI:
hopQualityDb = snrDb - snrRequiredForSf(sf) // simplified
pathScore = min(hopQualityDb across hops) // bottleneck hopDisplay per-hop RSSI/SNR in a table; optional sparkline for path.
Example: 3-hop trace (conceptual)
| Hop | nodeId | RSSI | SNR | margin |
|---|---|---|---|---|
| 1 | R1 | -95 | +4 | 6 |
| 2 | R2 | -102 | +2 | 3 |
| 3 | G | -108 | -1 | 1 |
The bottleneck is hop 3 — user may relocate R2 or switch profile.
Neighbor table (runtime)
Even without traceroute, every node stores last RSSI/SNR per neighbor from any decoded frame:
| Field | Description |
|---|---|
neighborId | nodeId32 (4 bytes LE) |
lastRssi | int16 |
lastSnr | int16 |
lastSeenMs | monotonic clock |
ewmaSnr | smoothed |
This feeds path-quality estimates for policy (gateway pick, TTL tuning) in Routing — not mesh-wide predictive tables.
C++ API: a documented RAM row shape is lowmesh::runtime::Neighbor in Embedded storage (nodeId, lastRssiDbm, lastSnrDb, lastSeenMs, ewmaSnrDb, …).
Security
Traceroute reveals topology. On the default community channel (channelHash == 0x0000), ciphertext hides inner payloads, but participation patterns may still leak. On other channels, only members who hold the key can decrypt responses — still operator-sensitive.