fix(traceroute): always send on channel 0 (Primary) — fixes #3696#3699
Conversation
Traceroutes were being sent on the node's stored `channel` field, which is updated from every incoming packet. If the last packet from the target arrived on a secondary encrypted channel (e.g. channel 2), the next traceroute was also sent on that channel. Intermediate nodes that don't have that channel can't decrypt the payload and appear as "Unknown" in the route. Pass explicit channel 0 to resolveDestinationChannel for traceroutes. Channel 0 (Primary) is the only channel guaranteed to be readable by all nodes in the mesh, which is required for traceroute to function correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MC5zHztyQEcdUw2ehM8k7x
|
Claude finished @Yeraze's task —— View job PR Review: fix(traceroute): always send on channel 0 (Primary)
SummaryThis is a clean, minimal fix for a real usability bug. The root cause and fix are both well understood, and the code is correct. A few minor observations below. Code Quality
One small observation: The redundant parentheses on Potential IssuesMinor:
Option 2 is arguably better because it keeps the call site consistent with the other endpoints and leverages the validation/clamping logic in Test CoverageThe updated test at line 68–76 is good: it('always sends traceroute on channel 0 (Primary) regardless of node stored channel', async () => {
// node.channel is 2 from the beforeEach mock, but traceroutes must use
// channel 0 so intermediate nodes can read the unencrypted packet (issue #3696).
mockManager.sendTraceroute.mockResolvedValue(undefined);
const res = await request(app).post('/traceroute').send({ destination: '!12345678' });
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
expect(mockManager.sendTraceroute).toHaveBeenCalledWith(0x12345678, 0);
});
One missing test case: There's no test verifying that the DB lookup ( Security / PerformanceNo concerns. The fix reduces a DB call (the VerdictApprove. The fix is correct, the test is solid, and the comment block in the route handler clearly explains the reasoning. No blocking issues. |
Summary
Traceroutes were being sent on whatever channel the destination node was last heard on, rather than the Primary channel (index 0). When a user had exchanged messages with a node on a secondary encrypted channel, the node's stored
channelfield reflected that secondary channel — and subsequent traceroutes were sent on it. Intermediate nodes that don't have the encrypted channel can't decode the payload and appear as "Unknown" in the route, making traceroutes from MeshMonitor useless.This fix forces traceroutes to always use channel 0 by passing it as the explicit channel to
resolveDestinationChannel. Channel 0 (Primary/LongFast) is the only channel guaranteed to be present on all nodes, which is a prerequisite for traceroute to function correctly.Root Cause
resolveDestinationChannelwithout an explicit channel falls back tonode.channelfrom the database, which is updated on every incoming packet regardless of channel index. Receiving a DM on channel 2 setsnode.channel = 2, poisoning all subsequent traceroutes to that node.Position requests already accepted an explicit caller-supplied channel (and the frontend's channel-picker widget overrides it). Traceroutes had no such override, so the poisoned value always won.
Changes
src/server/routes/meshRequestRoutes.ts: pass0as the explicit channel argument when callingresolveDestinationChannelfor traceroutessrc/server/routes/meshRequestRoutes.test.ts: update test name and assertion to reflect the corrected behavior (channel 0, not the node's stored channel)Issues Resolved
Fixes #3696
Documentation Updates
No documentation changes needed — this corrects existing behavior to match user expectations.
Testing
tsc -p tsconfig.server.json --noEmit)🤖 Generated with Claude Code
Generated by Claude Code