feat: firewalled peer connections via server-brokered connect
Implement the FIREWALLED connection mode: when a direct outbound connection to a peer fails (peer unreachable), fall back to asking the server to broker it. - Add MessageFactory::build_connect_to_peer (server code 18). - A pre-established outbound failure now emits ClientOperation::PeerConnectFailed instead of a normal disconnect; the client registers a correlation token and sends ConnectToPeer so the peer connects back. - The listener accepts the peer's PierceFirewall (peer code 0) connect-back, matches the pending token, registers it as the P control connection, and fires PeerConnected to flush the queued download. Fix a latent aliasing bug this surfaced: a peer actor could emit two terminal notifications (a connect-failure, then a disconnect when later replaced and stopped), and the second removed the *newer* actor registered under the same username, killing the brokered connection mid-transfer. An actor now reports a terminal outcome at most once (disconnect_reported guard). Verified end to end against soulfind: the mock advertises an unlistened port so the direct connect is refused, the server brokers the connection, the mock pierces back, and the file transfers — bytes match. A companion test (soulfind_brokers_connect_to_peer_between_users) pins the server behaviour the mode relies on.
This commit is contained in:
@@ -53,6 +53,14 @@ pub struct PeerActor {
|
||||
/// True when we initiated this connection (no stream supplied at
|
||||
/// construction), so we must send a `PeerInit` once connected.
|
||||
outbound: bool,
|
||||
/// Set once the connection is up. A pre-established failure on an outbound
|
||||
/// connection means the peer is unreachable (likely firewalled).
|
||||
established: bool,
|
||||
/// Set once this actor has reported a terminal outcome (disconnect or
|
||||
/// connect-failure). Guards against a second notification — e.g. when a
|
||||
/// replaced actor is later stopped — which would otherwise alias and evict
|
||||
/// a newer actor registered under the same username.
|
||||
disconnect_reported: bool,
|
||||
}
|
||||
|
||||
impl PeerActor {
|
||||
@@ -83,6 +91,8 @@ impl PeerActor {
|
||||
queued_messages: Vec::new(),
|
||||
own_username,
|
||||
outbound,
|
||||
established: false,
|
||||
disconnect_reported: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,9 +439,22 @@ impl PeerActor {
|
||||
|
||||
self.stream.take();
|
||||
|
||||
if let Err(e) = self.client_channel.send(
|
||||
ClientOperation::PeerDisconnected(username, Some(error.into())),
|
||||
) {
|
||||
if self.disconnect_reported {
|
||||
return;
|
||||
}
|
||||
self.disconnect_reported = true;
|
||||
|
||||
// A direct outbound connection that never established means the peer is
|
||||
// unreachable (likely firewalled): signal a connect failure so the
|
||||
// client can fall back to server-brokered connect. Anything else is a
|
||||
// normal disconnect.
|
||||
let op = if self.outbound && !self.established {
|
||||
ClientOperation::PeerConnectFailed(username)
|
||||
} else {
|
||||
ClientOperation::PeerDisconnected(username, Some(error.into()))
|
||||
};
|
||||
|
||||
if let Err(e) = self.client_channel.send(op) {
|
||||
error!("Failed to send disconnect notification: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -441,6 +464,11 @@ impl PeerActor {
|
||||
|
||||
self.stream.take();
|
||||
|
||||
if self.disconnect_reported {
|
||||
return;
|
||||
}
|
||||
self.disconnect_reported = true;
|
||||
|
||||
if let Err(e) = self
|
||||
.client_channel
|
||||
.send(ClientOperation::PeerDisconnected(username, None))
|
||||
@@ -552,6 +580,8 @@ impl PeerActor {
|
||||
}
|
||||
};
|
||||
|
||||
self.established = true;
|
||||
|
||||
let Some(ref mut stream) = self.stream else {
|
||||
return;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user