Skip to content

fix(traceroute): always send on channel 0 (Primary) — fixes #3696#3699

Merged
Yeraze merged 1 commit into
mainfrom
claude/great-dijkstra-v0i0ca
Jun 24, 2026
Merged

fix(traceroute): always send on channel 0 (Primary) — fixes #3696#3699
Yeraze merged 1 commit into
mainfrom
claude/great-dijkstra-v0i0ca

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 24, 2026

Copy link
Copy Markdown
Owner

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 channel field 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

resolveDestinationChannel without an explicit channel falls back to node.channel from the database, which is updated on every incoming packet regardless of channel index. Receiving a DM on channel 2 sets node.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: pass 0 as the explicit channel argument when calling resolveDestinationChannel for traceroutes
  • src/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

  • Unit tests pass (18 tests in meshRequestRoutes.test.ts, full suite running in CI)
  • TypeScript compiles cleanly (tsc -p tsconfig.server.json --noEmit)
  • Manual: create a secondary encrypted channel, exchange messages on it with another node, then request a traceroute — route should now show intermediate nodes correctly instead of "Unknown"

🤖 Generated with Claude Code


Generated by Claude Code

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
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: fix(traceroute): always send on channel 0 (Primary)

  • Read changed files
  • Review code quality and correctness
  • Check test coverage
  • Post findings

Summary

This 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

meshRequestRoutes.ts:29 — The fix is correct. Passing 0 as explicitChannel to resolveDestinationChannel causes it to return 0 immediately via the isValidChannelIndex fast path (line 52–54 of resolveDestinationChannel.ts), so the database lookup is skipped entirely. This is both correct and efficient.

One small observation: The redundant parentheses on resolveSourceManager(...) calls (lines 28, 63, 141, 221, 304) appear to be pre-existing style — this PR doesn't introduce them and they're harmless, but they look slightly unusual.


Potential Issues

Minor: resolveDestinationChannel is still called even though its result is predetermined. Passing 0 as the explicit channel causes resolveDestinationChannel to short-circuit immediately and return 0 without hitting the database. While this works correctly and efficiently, the intent could be made clearer by either:

  1. Assigning const channel = 0; directly and skipping the call entirely, or
  2. Adding a brief inline comment explaining why 0 is passed (the existing block comment at lines 24–28 does this well, though it could reference that the DB lookup is intentionally skipped).

Option 2 is arguably better because it keeps the call site consistent with the other endpoints and leverages the validation/clamping logic in resolveDestinationChannel as a safety net if the argument ever changes. The current approach is fine.


Test Coverage

The 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);
});
  • The beforeEach sets node.channel: 2, so the test correctly proves the stored channel is ignored.
  • The assertion toHaveBeenCalledWith(0x12345678, 0) directly verifies the bug fix.
  • Test name is descriptive and references the issue.

One missing test case: There's no test verifying that the DB lookup (getNode) is not called during a traceroute, or alternatively a test passing a channel in the request body to confirm it's also ignored (unlike position requests). Both are low priority since the existing assertion already proves channel 0 is used regardless of the mock's stored value.


Security / Performance

No concerns. The fix reduces a DB call (the getNode lookup is skipped because isValidChannelIndex(0) returns true immediately), so it's a minor perf improvement too.


Verdict

Approve. The fix is correct, the test is solid, and the comment block in the route handler clearly explains the reasoning. No blocking issues.

@Yeraze Yeraze merged commit c5c8799 into main Jun 24, 2026
19 checks passed
@Yeraze Yeraze deleted the claude/great-dijkstra-v0i0ca branch June 24, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Traceroutes are sent over secondary encrypted channel by default

2 participants