Skip to content

Conversation

@sanity
Copy link
Collaborator

@sanity sanity commented Nov 25, 2025

Problem

When a joiner behind NAT sends a ConnectRequest to join the network, the gateway/relay observes the joiner's public external address. However, when sending the ConnectResponse back to the joiner, the code was incorrectly using the original from parameter (the joiner's private/NAT address) instead of the updated address with the observed external socket.

This caused ConnectResponse messages to be sent to unreachable private addresses (e.g., 192.168.x.x), preventing NAT users from successfully joining the network.

Root Cause

In process_message, when handling ConnectMsg::Request:

  • The actions.observed_address correctly captures the updated joiner address
  • But line ~824 used target: from.clone() where from is the original request sender

The observed_address field in RelayActions is only set once (guarded by !self.observed_sent), so using it directly would fail on subsequent calls. We needed a dedicated field that persists the response target.

Solution

  1. Added response_target: Option<PeerKeyLocation> to RelayActions struct
  2. Set response_target to the updated self.request.joiner when accepting a connection (after observed address processing)
  3. Use response_target.unwrap_or_else(|| from.clone()) when creating the ConnectResponse

This ensures the response is sent to the joiner's observed external address when available, falling back to the original address for backward compatibility.

Testing

Added connect_response_uses_observed_address_not_private test that:

  • Creates a joiner with a private address (192.168.1.100)
  • Sets an observed public address (203.0.113.50)
  • Verifies response_target contains the observed public address, not private

All existing tests pass.

Fixes #2141

[AI-assisted - Claude]

ConnectResponse was being sent to the joiner's original (possibly
private/NAT) address instead of the observed external address. This
prevented NAT users from joining the network.

Added response_target field to RelayActions that captures the joiner's
updated address after observed_addr processing. The response now uses
this target address when sending acceptance back to the joiner.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sanity sanity enabled auto-merge November 25, 2025 04:26
@sanity sanity added this pull request to the merge queue Nov 25, 2025
Merged via the queue into main with commit 9b3e29a Nov 25, 2025
11 checks passed
@sanity sanity deleted the fix-connect-response-addr branch November 25, 2025 04:51
sanity added a commit that referenced this pull request Nov 25, 2025
The previous self-connection guard only checked peer_key, which can be
None early during initialization. This caused self-connections to slip
through when combined with PR #2142's response_target change.

The fix adds a secondary check that compares pub_key directly. Since
pub_key is always set in ConnectionManager, this catches self-connections
even when peer_key is not yet initialized.

This fixes the test_three_node_network_connectivity failure in the
release/v0.1.39 CI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
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.

ConnectResponse sent to joiner's private address instead of observed external address

2 participants