mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
peer: drive the connection-oriented outbound dial through the machine
Route the connection-oriented (TCP/Tor) outbound path through the peer state machine, matching how the connectionless path already works. initiate_connection's oriented branch now drives PeerEvent::Dial with connection_oriented=true; the machine parks in Connecting and emits OpenTransport, whose executor arm performs the non-blocking transport.connect and pushes the PendingConnect. When the connect resolves, poll_pending_connects prepares msg1 in the shell and then drives PeerEvent::TransportConnected, which sends msg1 via the machine's SendHandshake arm. The msg1 prepare (index allocation, Noise leaf, wire arming) MUST run in the shell before the TransportConnected drive: send_stored_msg1 only transmits an already-armed wire, so a drive-only path would silently send nothing. The machine's our_index stays unset; the connect-failure path keeps its direct handshake-timeout handling (TransportFailed stays dormant). The now-unused Node::start_handshake helper is removed. Behavior-neutral: same transport.connect, same PendingConnect, same msg1 send and failure teardown as the removed inline path -- only the driver changes from inline shell code to the state machine.
This commit is contained in:
@@ -10,15 +10,17 @@
|
||||
//!
|
||||
//! The executor is wired incrementally. Live today: the inbound establish
|
||||
//! (`handle_msg1` → `step(InboundMsg1)`), the outbound msg2 promote
|
||||
//! (`handle_msg2` looks up the dial-persisted machine), and the connectionless
|
||||
//! (`handle_msg2` looks up the dial-persisted machine), the connectionless
|
||||
//! outbound msg1 send (`SendHandshake` with `their_index == None` →
|
||||
//! `send_stored_msg1`, driven from `initiate_connection`).
|
||||
//! `send_stored_msg1`, driven from `initiate_connection`), and the
|
||||
//! connection-oriented dial (`OpenTransport` performs the non-blocking
|
||||
//! `transport.connect`; `TransportConnected` drives the connect-resolution msg1
|
||||
//! send from `poll_pending_connects`).
|
||||
//!
|
||||
//! Not yet driven, so their arms stay inert stubs: `OpenTransport` (the
|
||||
//! connection-oriented dial), rekey/crypto installs, link-control frames, the
|
||||
//! timers (`SetTimer`/`CancelTimer` — the legacy tick still runs them), and the
|
||||
//! connected-UDP plane. `RegisterDecryptSession` is a deliberate no-op — see its
|
||||
//! arm for the note.
|
||||
//! Not yet driven, so their arms stay inert stubs: rekey/crypto installs,
|
||||
//! link-control frames, the timers (`SetTimer`/`CancelTimer` — the legacy tick
|
||||
//! still runs them), and the connected-UDP plane. `RegisterDecryptSession` is a
|
||||
//! deliberate no-op — see its arm for the note.
|
||||
|
||||
use crate::PeerIdentity;
|
||||
use crate::node::Node;
|
||||
@@ -109,10 +111,45 @@ impl Node {
|
||||
let mut queue: VecDeque<PeerAction> = actions.into();
|
||||
while let Some(action) = queue.pop_front() {
|
||||
match action {
|
||||
PeerAction::OpenTransport { .. } => {
|
||||
// Outbound dial (`initiate_connection`,
|
||||
// `lifecycle/mod.rs:470`). Outbound establish is not cut over
|
||||
// yet; inert in the shadow-only skeleton.
|
||||
PeerAction::OpenTransport {
|
||||
transport_id,
|
||||
remote_addr,
|
||||
} => {
|
||||
// Outbound connection-oriented dial. `initiate_connection`'s
|
||||
// oriented branch drove the machine to `Connecting`, which
|
||||
// emitted this action. Perform the non-blocking
|
||||
// `transport.connect` and, on success, push the
|
||||
// `PendingConnect` for `poll_pending_connects` to resolve. On
|
||||
// connect error, tear down the dial-window state (link,
|
||||
// reverse map, control machine) and abort the queue — the
|
||||
// executor-local mirror of the old inline
|
||||
// `initiate_connection` connect+push.
|
||||
if let Some(transport) = self.transports.get(&transport_id) {
|
||||
match transport.connect(&remote_addr).await {
|
||||
Ok(()) => {
|
||||
debug!(
|
||||
transport_id = %transport_id,
|
||||
remote_addr = %remote_addr,
|
||||
link_id = %link,
|
||||
"Transport connect initiated (non-blocking)"
|
||||
);
|
||||
self.peering
|
||||
.pending_connects
|
||||
.push(crate::node::PendingConnect {
|
||||
link_id: link,
|
||||
transport_id,
|
||||
remote_addr,
|
||||
peer_identity: ambient.verified_identity,
|
||||
});
|
||||
}
|
||||
Err(_e) => {
|
||||
self.links.remove(&link);
|
||||
self.addr_to_link.remove(&(transport_id, remote_addr));
|
||||
self.peer_machines.remove(&link);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PeerAction::SendHandshake { bytes } => {
|
||||
// Two outbound directions share this action, discriminated by
|
||||
|
||||
@@ -1141,10 +1141,10 @@ impl Node {
|
||||
// net-new arm — no ordering constraint, lowest risk.
|
||||
//
|
||||
// Look up the outbound machine persisted at DIAL, and step `Msg2 →
|
||||
// [PromoteToActive]` in place. It is in `Discovered` (connection-oriented
|
||||
// dial, not yet cut over) or `Handshaking{SentMsg1}` (connectionless dial,
|
||||
// which drives the machine to send msg1) — either way `on_msg2` is
|
||||
// state-independent: it decides from the outbound snapshot, not the
|
||||
// [PromoteToActive]` in place. Both dial paths reach msg2 in
|
||||
// `Handshaking{SentMsg1}` — each drives the machine to send msg1 before
|
||||
// msg2 — and `on_msg2` is state-independent regardless: it decides from
|
||||
// the outbound snapshot, not the
|
||||
// machine's state, and reads the machine's unset `conn.our_index`
|
||||
// as `None`, so stepping the persisted (vs the former transient) machine
|
||||
// is byte-identical: same `Promote` decision (`has_existing_peer == false`),
|
||||
|
||||
+67
-59
@@ -439,8 +439,6 @@ impl Node {
|
||||
remote_addr: TransportAddr,
|
||||
peer_identity: PeerIdentity,
|
||||
) -> Result<(), NodeError> {
|
||||
let peer_node_addr = *peer_identity.node_addr();
|
||||
|
||||
self.authorize_peer(
|
||||
&peer_identity,
|
||||
PeerAclContext::OutboundConnect,
|
||||
@@ -494,33 +492,35 @@ impl Node {
|
||||
self.peer_machines.insert(link_id, machine);
|
||||
|
||||
if is_connection_oriented {
|
||||
// Connection-oriented: start non-blocking connect, defer handshake
|
||||
if let Some(transport) = self.transports.get(&transport_id) {
|
||||
match transport.connect(&remote_addr).await {
|
||||
Ok(()) => {
|
||||
debug!(
|
||||
peer = %self.peer_display_name(&peer_node_addr),
|
||||
transport_id = %transport_id,
|
||||
remote_addr = %remote_addr,
|
||||
link_id = %link_id,
|
||||
"Transport connect initiated (non-blocking)"
|
||||
);
|
||||
self.peering.pending_connects.push(super::PendingConnect {
|
||||
link_id,
|
||||
transport_id,
|
||||
remote_addr,
|
||||
peer_identity,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
// Clean up link and the dial-time control machine
|
||||
self.links.remove(&link_id);
|
||||
self.addr_to_link.remove(&(transport_id, remote_addr));
|
||||
self.peer_machines.remove(&link_id);
|
||||
return Err(NodeError::TransportError(e.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Connection-oriented: drive the machine to open the transport. The
|
||||
// machine parks in `Connecting` and emits one `OpenTransport`; the
|
||||
// executor performs the non-blocking `transport.connect` and pushes
|
||||
// the `PendingConnect`. No index alloc or wire-arm happens here —
|
||||
// that is deferred to `prepare_outbound_msg1` at connect-resolution
|
||||
// time (`poll_pending_connects`), so `our_index` stays `None` on both
|
||||
// the (not-yet-built) connection and the machine.
|
||||
let now = Self::now_ms();
|
||||
let ambient = PeerActionCtx {
|
||||
verified_identity: peer_identity,
|
||||
transport_id,
|
||||
remote_addr: remote_addr.clone(),
|
||||
our_index: None,
|
||||
their_index: None,
|
||||
now_ms: now,
|
||||
is_outbound: true,
|
||||
};
|
||||
self.advance_peer_machine(
|
||||
link_id,
|
||||
PeerEvent::Dial {
|
||||
transport_id,
|
||||
remote_addr,
|
||||
peer_identity,
|
||||
connection_oriented: true,
|
||||
},
|
||||
now,
|
||||
&ambient,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
} else {
|
||||
// Connectionless: no connect step. Prepare msg1 in the shell — the
|
||||
@@ -557,24 +557,6 @@ impl Node {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the Noise handshake on a link and send msg1.
|
||||
///
|
||||
/// Called after a connection-oriented transport connects. (Connectionless
|
||||
/// dials `prepare_outbound_msg1` + drive the machine to send in
|
||||
/// `initiate_connection`.)
|
||||
pub(super) async fn start_handshake(
|
||||
&mut self,
|
||||
link_id: LinkId,
|
||||
transport_id: TransportId,
|
||||
remote_addr: TransportAddr,
|
||||
peer_identity: PeerIdentity,
|
||||
) -> Result<(), NodeError> {
|
||||
self.prepare_outbound_msg1(link_id, transport_id, &remote_addr, peer_identity)?;
|
||||
self.send_stored_msg1(link_id, transport_id, &remote_addr)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Prepare an outbound Noise msg1 at dial: allocate the session index, run
|
||||
/// the Noise leaf, frame the wire, arm the shell-side resend, track
|
||||
/// `pending_outbound`, and persist the connection. Returns `Err` on
|
||||
@@ -659,8 +641,9 @@ impl Node {
|
||||
/// Send the msg1 wire that `prepare_outbound_msg1` armed on the connection.
|
||||
/// On send error, marks the connection failed and RETAINS it (the legacy
|
||||
/// resend tick retries); a missing wire or transport is a no-op. This is the
|
||||
/// body of the executor's `SendHandshake` msg1 action and the send tail of
|
||||
/// the connection-oriented `start_handshake`.
|
||||
/// body of the executor's `SendHandshake` msg1 action — reached on both the
|
||||
/// connectionless dial and the connection-oriented connect-resolution path,
|
||||
/// after `prepare_outbound_msg1` has armed the wire.
|
||||
pub(in crate::node) async fn send_stored_msg1(
|
||||
&mut self,
|
||||
link_id: LinkId,
|
||||
@@ -1223,16 +1206,18 @@ impl Node {
|
||||
"Transport connected, starting handshake"
|
||||
);
|
||||
|
||||
// Start the handshake now that the transport is connected
|
||||
if let Err(e) = self
|
||||
.start_handshake(
|
||||
pending.link_id,
|
||||
pending.transport_id,
|
||||
pending.remote_addr.clone(),
|
||||
pending.peer_identity,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// Prepare msg1 now that the transport is connected, then drive
|
||||
// the machine to send it. The prepare (index alloc, Noise leaf,
|
||||
// wire arm) MUST run BEFORE the `TransportConnected` drive: the
|
||||
// executor's `SendHandshake` msg1 branch only transmits the wire
|
||||
// this armed on the connection — a drive-only path would find no
|
||||
// armed wire and silently send nothing.
|
||||
if let Err(e) = self.prepare_outbound_msg1(
|
||||
pending.link_id,
|
||||
pending.transport_id,
|
||||
&pending.remote_addr,
|
||||
pending.peer_identity,
|
||||
) {
|
||||
warn!(
|
||||
link_id = %pending.link_id,
|
||||
error = %e,
|
||||
@@ -1241,6 +1226,29 @@ impl Node {
|
||||
// Clean up link and dial-time machine on handshake failure
|
||||
self.remove_link(&pending.link_id);
|
||||
self.peer_machines.remove(&pending.link_id);
|
||||
} else {
|
||||
// Drive the dial-persisted machine: `Connecting` →
|
||||
// `on_transport_connected` → `start_outbound_handshake`,
|
||||
// emitting `SendHandshake` (msg1) whose executor arm sends the
|
||||
// wire just armed. `our_index`/`their_index` stay `None` so the
|
||||
// executor takes the `their_index == None` msg1 branch.
|
||||
let now = Self::now_ms();
|
||||
let ambient = PeerActionCtx {
|
||||
verified_identity: pending.peer_identity,
|
||||
transport_id: pending.transport_id,
|
||||
remote_addr: pending.remote_addr.clone(),
|
||||
our_index: None,
|
||||
their_index: None,
|
||||
now_ms: now,
|
||||
is_outbound: true,
|
||||
};
|
||||
self.advance_peer_machine(
|
||||
pending.link_id,
|
||||
PeerEvent::TransportConnected,
|
||||
now,
|
||||
&ambient,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
let reason = reason.unwrap_or_default();
|
||||
|
||||
Reference in New Issue
Block a user