Skip to content

Commit b0408be

Browse files
maxholmanclaude
andcommitted
fix(peers): derive peer role from capabilities instead of hardcoding Entry
Every registration site (exit accept/connect, auto exit accept/connect, relay connector) was hardcoding NodeRole::Entry for the peer. Now uses peer_role_from_capabilities() which checks the handshake: both listen+connect → Relay, tun_capable → Entry, otherwise → Exit. Moved role_from_capabilities to mode/mod.rs as peer_role_from_capabilities so all modes share it. Relay connector now resolves the peer handshake to get the peer's name and capabilities — no more showing raw address as peer name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8824fc2 commit b0408be

5 files changed

Lines changed: 69 additions & 33 deletions

File tree

crates/daemon/src/mode/auto.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,9 @@ async fn run_auto_connect_session_dispatch(
465465
peer_hs.name,
466466
);
467467
node_state.update_role(NodeRole::Exit);
468+
let peer_role = peer_hs
469+
.capabilities
470+
.map_or(NodeRole::Exit, super::peer_role_from_capabilities);
468471
let peer_name = if peer_hs.name.is_empty() {
469472
peer_addr.to_string()
470473
} else {
@@ -495,6 +498,7 @@ async fn run_auto_connect_session_dispatch(
495498
instructions_rx,
496499
responses_tx,
497500
heartbeat,
501+
peer_role,
498502
&peer_name,
499503
peer_addr,
500504
&metrics,
@@ -551,6 +555,7 @@ async fn run_auto_exit_session_inner(
551555
instructions_rx: tokio::sync::mpsc::Receiver<wallhack_wire::data::EntryNodeInstruction>,
552556
responses_tx: tokio::sync::mpsc::Sender<wallhack_wire::data::ExitNodeResponse>,
553557
_heartbeat: tokio::task::JoinHandle<()>,
558+
peer_role: NodeRole,
554559
peer_name: &str,
555560
peer_addr: &str,
556561
metrics: &Arc<Metrics>,
@@ -559,7 +564,7 @@ async fn run_auto_exit_session_inner(
559564
peers.register(
560565
peer_name.to_string(),
561566
peer_addr.to_string(),
562-
NodeRole::Entry,
567+
peer_role,
563568
ConnectionSide::Connect,
564569
);
565570

@@ -1037,12 +1042,15 @@ async fn run_auto_accept_session_inner(
10371042
} else {
10381043
peer_hs.name.clone()
10391044
};
1040-
// The peer connected to us (we accepted), so side=Accept.
1041-
// We are exit, so the peer that connected is entry.
1045+
let peer_role = peer_hs
1046+
.capabilities
1047+
.as_ref()
1048+
.copied()
1049+
.map_or(NodeRole::Exit, super::peer_role_from_capabilities);
10421050
peers.register(
10431051
peer_name.clone(),
10441052
peer_addr.clone(),
1045-
NodeRole::Entry,
1053+
peer_role,
10461054
ConnectionSide::Accept,
10471055
);
10481056

crates/daemon/src/mode/entry.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -805,17 +805,7 @@ struct PeerIdentity {
805805
role: NodeRole,
806806
}
807807

808-
/// Derive the peer's role from its advertised capabilities.
809-
///
810-
/// A peer that both listens and connects is a relay; otherwise it is an exit
811-
/// node (entry nodes do not connect to other entry nodes in this topology).
812-
fn role_from_capabilities(caps: wallhack_wire::data::Capabilities) -> NodeRole {
813-
if caps.listening && caps.connecting {
814-
NodeRole::Relay
815-
} else {
816-
NodeRole::Exit
817-
}
818-
}
808+
// Uses super::peer_role_from_capabilities
819809

820810
/// Validate the peer's handshake (PSK proof + identity) for generic `AcceptResult`.
821811
fn validate_handshake<T: wallhack_core::transport::Transport>(
@@ -842,7 +832,7 @@ fn validate_handshake<T: wallhack_core::transport::Transport>(
842832
}
843833

844834
let capabilities = hs.capabilities.unwrap_or_default();
845-
let role = role_from_capabilities(capabilities);
835+
let role = super::peer_role_from_capabilities(capabilities);
846836

847837
if hs.name.is_empty() {
848838
tracing::debug!("Peer identified with empty name (v{})", hs.version);

crates/daemon/src/mode/exit.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,19 @@ where
312312
Ok(Some(mut accept_result)) => {
313313
let peer_addr = accept_result.peer_addr().to_string();
314314

315-
// Register the connecting peer using handshake name if available.
316-
let peer_name = accept_result
317-
.peer_handshake()
315+
// Register the connecting peer using handshake name and capabilities.
316+
let peer_hs = accept_result.peer_handshake();
317+
let peer_name = peer_hs
318318
.filter(|h| !h.name.is_empty())
319319
.map_or_else(|| peer_addr.clone(), |h| h.name.clone());
320+
let peer_role = peer_hs
321+
.and_then(|h| h.capabilities)
322+
.map_or(NodeRole::Exit, super::peer_role_from_capabilities);
320323
tracing::info!("Peer connected: name={peer_name} addr={peer_addr}");
321-
// The entry peer connected to us (we accepted), so side=Accept.
322324
ctx.peers.register(
323325
peer_name.clone(),
324326
peer_addr,
325-
NodeRole::Entry,
327+
peer_role,
326328
ConnectionSide::Accept,
327329
);
328330

@@ -442,19 +444,18 @@ async fn run_exit_loop_inner(
442444
.filter(|h| !h.name.is_empty())
443445
.map_or_else(|| peer_addr.to_string(), |h| h.name.clone());
444446

445-
// We connected to the entry peer, so side=Connect.
447+
// Derive peer role from capabilities, then register.
448+
let peer_capabilities = peer_handshake
449+
.as_ref()
450+
.and_then(|h| h.capabilities)
451+
.unwrap_or_default();
452+
let peer_role = super::peer_role_from_capabilities(peer_capabilities);
446453
ctx.peers.register(
447454
peer_name.clone(),
448455
peer_addr.to_string(),
449-
NodeRole::Entry,
456+
peer_role,
450457
ConnectionSide::Connect,
451458
);
452-
453-
// Apply the peer's advertised capabilities from the handshake.
454-
let peer_capabilities = peer_handshake
455-
.as_ref()
456-
.and_then(|h| h.capabilities)
457-
.unwrap_or_default();
458459
ctx.peers
459460
.update_capabilities(&peer_name, &peer_capabilities);
460461

crates/daemon/src/mode/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ pub(crate) struct NodeResources {
6363
pub node_state: SharedNodeState,
6464
}
6565

66+
/// Derive a peer's role from its advertised capabilities.
67+
///
68+
/// A peer that both listens and connects is a relay; a peer with TUN
69+
/// capability is an entry; otherwise it is an exit node.
70+
pub(crate) fn peer_role_from_capabilities(
71+
caps: wallhack_wire::data::Capabilities,
72+
) -> wallhack_core::NodeRole {
73+
if caps.listening && caps.connecting {
74+
wallhack_core::NodeRole::Relay
75+
} else if caps.tun_capable {
76+
wallhack_core::NodeRole::Entry
77+
} else {
78+
wallhack_core::NodeRole::Exit
79+
}
80+
}
81+
6682
/// Inject a Ping message into the control stream.
6783
pub(crate) async fn send_ping(
6884
control_tx: &tokio::sync::mpsc::Sender<wallhack_wire::control::ControlMessage>,

crates/daemon/src/mode/relay.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ pub async fn run(
145145
e.channels,
146146
e.tasks,
147147
e.control_tx,
148+
e.peer_handshake_rx,
148149
&global,
149150
&listen_spec,
150151
addr,
@@ -197,6 +198,7 @@ pub async fn run(
197198
e.channels,
198199
e.tasks,
199200
e.control_tx,
201+
e.peer_handshake_rx,
200202
&global,
201203
&listen_spec,
202204
addr,
@@ -223,7 +225,7 @@ pub async fn run(
223225
/// Starts the listener, bridges channels, and returns `Ok(())` when the
224226
/// source peer disconnects so `connect_loop` reconnects.
225227
/// Non-generic relay loop: monomorphized once regardless of transport type.
226-
#[allow(clippy::too_many_arguments)]
228+
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
227229
async fn run_relay_loop_inner(
228230
peer_addr: String,
229231
transport: std::sync::Arc<dyn wallhack_core::transport::ErasedTransport>,
@@ -232,6 +234,7 @@ async fn run_relay_loop_inner(
232234
// Retain control_tx for the full session lifetime — dropping it kills the
233235
// control stream and causes the source to see the relay as disconnected.
234236
_source_control_tx: tokio::sync::mpsc::Sender<wallhack_wire::control::ControlMessage>,
237+
peer_handshake_rx: Option<tokio::sync::oneshot::Receiver<wallhack_wire::data::Handshake>>,
235238
global: &GlobalConfig,
236239
listen_spec: &AddressSpec,
237240
addr: std::net::SocketAddr,
@@ -244,11 +247,29 @@ async fn run_relay_loop_inner(
244247

245248
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(());
246249

250+
// Resolve peer handshake for name and capabilities.
251+
let peer_handshake = if let Some(rx) = peer_handshake_rx {
252+
match tokio::time::timeout(std::time::Duration::from_secs(10), rx).await {
253+
Ok(Ok(hs)) => Some(hs),
254+
_ => None,
255+
}
256+
} else {
257+
None
258+
};
259+
let peer_name = peer_handshake
260+
.as_ref()
261+
.filter(|h| !h.name.is_empty())
262+
.map_or_else(|| peer_addr.clone(), |h| h.name.clone());
263+
let peer_role = peer_handshake
264+
.as_ref()
265+
.and_then(|h| h.capabilities)
266+
.map_or(NodeRole::Exit, super::peer_role_from_capabilities);
267+
247268
// Register the source peer so it appears in `wallhack peers`.
248269
peers.register(
270+
peer_name.clone(),
249271
peer_addr.clone(),
250-
peer_addr.clone(),
251-
NodeRole::Entry,
272+
peer_role,
252273
ConnectionSide::Connect,
253274
);
254275

@@ -357,7 +378,7 @@ async fn run_relay_loop_inner(
357378
// Dropping shutdown_tx wakes all bridge tasks holding a shutdown_rx clone.
358379
drop(shutdown_tx);
359380

360-
peers.unregister(&peer_addr);
381+
peers.unregister(&peer_name);
361382

362383
Ok(())
363384
}

0 commit comments

Comments
 (0)