-
-
Notifications
You must be signed in to change notification settings - Fork 107
Description
Summary
When a peer behind NAT joins the network, the gateway correctly observes their external address from the UDP socket, but sends the ConnectResponse to the joiner's self-reported private address (e.g., 127.0.0.1) instead of the observed external address. This causes the join handshake to never complete, leaving the peer without a ring location and unable to participate in DHT operations.
Impact
- Peers behind NAT cannot complete the join process
own_locationstays unassigned (-1)- All PUT operations are cached locally with warning:
"Not forwarding PUT – own ring location not assigned yet" - GET/Subscribe operations fail because peer can't route through DHT
- River and other Freenet applications are completely non-functional for users behind NAT
Root Cause
File: crates/core/src/operations/connect.rs, line 824
When handling a ConnectRequest, the relay correctly updates the joiner's address with the observed external address at line 282:
// Line 282 - CORRECT: Updates joiner address with observed external address
self.request.joiner.peer.addr = joiner_addr;And uses the updated address when sending ObservedAddress at lines 784-792:
// Lines 784-792 - CORRECT: Uses updated target from actions
if let Some((target, address)) = actions.observed_address {
let msg = ConnectMsg::ObservedAddress { ... target: target.clone(), ... };
network_bridge.send(&target.peer, ...).await?; // Sends to correct address
}But when sending ConnectResponse, it uses the original from parameter which still has the private address:
// Lines 820-830 - BUG: Uses original 'from' instead of updated joiner
if let Some(response) = actions.accept_response {
let response_msg = ConnectMsg::Response {
id: self.id,
sender: env.self_location().clone(),
target: from.clone(), // <-- BUG: from.peer.addr is still 127.0.0.1
payload: response,
};Evidence from Logs
Gateway logs show it trying to send ConnectResponse to 127.0.0.1:43227:
Sending outbound message to peer, tx: 01KAWDVAY8...,
msg_type: Message {ConnectResponse { sender: v6MWKgqHiBMNcGtG, target: v6MWKgqKEpQJPprk, ... }},
target_peer: v6MWKgqKEpQJPprk
WARN: No existing outbound connection, establishing connection first,
id: 01KAWDVAY8..., target: v6MWKgqKEpQJPprk
NodeEvent::ConnectPeer received, tx: 01KAWDVAY8...,
remote: v6MWKgqKEpQJPprk,
remote_addr: 127.0.0.1:43227 <-- WRONG ADDRESS
OutboundFailed { transaction: 01KAWDVAY8..., peer: v6MWKgqKEpQJPprk,
error: TransportError("failed while establishing connection, reason: max connection attempts reached") }
Meanwhile, ObservedAddress was sent to the correct address and arrived successfully:
# Gateway sends to correct address:
Sending outbound message to peer, tx: 01KAWDVAY8...,
msg_type: Message {ObservedAddress { target: v6MWKgqKEpQJPprk (@ 0.409...), address: 136.62.52.28:43227 }}
Message successfully sent to peer connection
# Local peer receives it:
Received inbound message from peer - processing, tx: 01KAWDVAY8...,
msg_type: Message {ObservedAddress { target: v6MWKgqKEpQJPprk (@ 0.409...), address: 136.62.52.28:43227 }}
Suggested Fix
Line 824 should use the updated joiner address from actions.observed_address instead of the original from:
if let Some(response) = actions.accept_response {
// Use updated joiner address if available, otherwise fall back to from
let response_target = actions.observed_address
.as_ref()
.map(|(target, _)| target.clone())
.unwrap_or_else(|| from.clone());
let response_msg = ConnectMsg::Response {
id: self.id,
sender: env.self_location().clone(),
target: response_target, // Uses correct external address
payload: response,
};
return Ok(store_operation_state_with_msg(&mut self, Some(response_msg)));
}Alternatively, add a response_target field to RelayActions that's always set to the correct joiner address.
Why Tests Don't Catch This
- Tests run on localhost where all addresses are
127.0.0.1- packets to loopback still arrive - The existing test
relay_emits_observed_address_for_private_joinervalidates thatObservedAddressuses the correct address, but doesn't testConnectResponse - Test network configurations often pre-set locations via
location: Some(...), bypassing the need for the join handshake to complete
Test Case Needed
Add a test that verifies ConnectResponse is sent to the observed external address, not the joiner's self-reported address:
#[test]
fn connect_response_uses_observed_address_not_private() {
// Setup: joiner with private address 127.0.0.1:5050
// Gateway observes external address 203.0.113.10:5050
// Verify: ConnectResponse target has 203.0.113.10:5050, not 127.0.0.1:5050
}Reproduction Steps
- Run a Freenet peer behind NAT (or with a private IP that differs from external)
- Configure it to connect to a public gateway
- Observe in logs:
- Peer sends
ConnectRequestwith private address injoinerfield - Gateway sends
ObservedAddresswith correct external address (peer receives this) - Gateway sends
ConnectResponseto private address (peer never receives this) - Peer logs show:
"Not forwarding PUT – own ring location not assigned yet"
- Peer sends
Related Files
crates/core/src/operations/connect.rs- Main bug location (line 824)crates/core/src/operations/connect.rs:277-289- Where joiner address is correctly updatedcrates/core/src/node/network_bridge/p2p_protoc.rs:333-335- Where observed_addr is set from UDP socket
Metadata
Metadata
Assignees
Labels
Type
Projects
Status