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
|
//! The executor is wired incrementally. Live today: the inbound establish
|
||||||
//! (`handle_msg1` → `step(InboundMsg1)`), the outbound msg2 promote
|
//! (`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` →
|
//! 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
|
//! Not yet driven, so their arms stay inert stubs: rekey/crypto installs,
|
||||||
//! connection-oriented dial), rekey/crypto installs, link-control frames, the
|
//! link-control frames, the timers (`SetTimer`/`CancelTimer` — the legacy tick
|
||||||
//! timers (`SetTimer`/`CancelTimer` — the legacy tick still runs them), and the
|
//! still runs them), and the connected-UDP plane. `RegisterDecryptSession` is a
|
||||||
//! connected-UDP plane. `RegisterDecryptSession` is a deliberate no-op — see its
|
//! deliberate no-op — see its arm for the note.
|
||||||
//! arm for the note.
|
|
||||||
|
|
||||||
use crate::PeerIdentity;
|
use crate::PeerIdentity;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
@@ -109,10 +111,45 @@ impl Node {
|
|||||||
let mut queue: VecDeque<PeerAction> = actions.into();
|
let mut queue: VecDeque<PeerAction> = actions.into();
|
||||||
while let Some(action) = queue.pop_front() {
|
while let Some(action) = queue.pop_front() {
|
||||||
match action {
|
match action {
|
||||||
PeerAction::OpenTransport { .. } => {
|
PeerAction::OpenTransport {
|
||||||
// Outbound dial (`initiate_connection`,
|
transport_id,
|
||||||
// `lifecycle/mod.rs:470`). Outbound establish is not cut over
|
remote_addr,
|
||||||
// yet; inert in the shadow-only skeleton.
|
} => {
|
||||||
|
// 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 } => {
|
PeerAction::SendHandshake { bytes } => {
|
||||||
// Two outbound directions share this action, discriminated by
|
// Two outbound directions share this action, discriminated by
|
||||||
|
|||||||
@@ -1141,10 +1141,10 @@ impl Node {
|
|||||||
// net-new arm — no ordering constraint, lowest risk.
|
// net-new arm — no ordering constraint, lowest risk.
|
||||||
//
|
//
|
||||||
// Look up the outbound machine persisted at DIAL, and step `Msg2 →
|
// Look up the outbound machine persisted at DIAL, and step `Msg2 →
|
||||||
// [PromoteToActive]` in place. It is in `Discovered` (connection-oriented
|
// [PromoteToActive]` in place. Both dial paths reach msg2 in
|
||||||
// dial, not yet cut over) or `Handshaking{SentMsg1}` (connectionless dial,
|
// `Handshaking{SentMsg1}` — each drives the machine to send msg1 before
|
||||||
// which drives the machine to send msg1) — either way `on_msg2` is
|
// msg2 — and `on_msg2` is state-independent regardless: it decides from
|
||||||
// state-independent: it decides from the outbound snapshot, not the
|
// the outbound snapshot, not the
|
||||||
// machine's state, and reads the machine's unset `conn.our_index`
|
// machine's state, and reads the machine's unset `conn.our_index`
|
||||||
// as `None`, so stepping the persisted (vs the former transient) machine
|
// as `None`, so stepping the persisted (vs the former transient) machine
|
||||||
// is byte-identical: same `Promote` decision (`has_existing_peer == false`),
|
// is byte-identical: same `Promote` decision (`has_existing_peer == false`),
|
||||||
|
|||||||
+67
-59
@@ -439,8 +439,6 @@ impl Node {
|
|||||||
remote_addr: TransportAddr,
|
remote_addr: TransportAddr,
|
||||||
peer_identity: PeerIdentity,
|
peer_identity: PeerIdentity,
|
||||||
) -> Result<(), NodeError> {
|
) -> Result<(), NodeError> {
|
||||||
let peer_node_addr = *peer_identity.node_addr();
|
|
||||||
|
|
||||||
self.authorize_peer(
|
self.authorize_peer(
|
||||||
&peer_identity,
|
&peer_identity,
|
||||||
PeerAclContext::OutboundConnect,
|
PeerAclContext::OutboundConnect,
|
||||||
@@ -494,33 +492,35 @@ impl Node {
|
|||||||
self.peer_machines.insert(link_id, machine);
|
self.peer_machines.insert(link_id, machine);
|
||||||
|
|
||||||
if is_connection_oriented {
|
if is_connection_oriented {
|
||||||
// Connection-oriented: start non-blocking connect, defer handshake
|
// Connection-oriented: drive the machine to open the transport. The
|
||||||
if let Some(transport) = self.transports.get(&transport_id) {
|
// machine parks in `Connecting` and emits one `OpenTransport`; the
|
||||||
match transport.connect(&remote_addr).await {
|
// executor performs the non-blocking `transport.connect` and pushes
|
||||||
Ok(()) => {
|
// the `PendingConnect`. No index alloc or wire-arm happens here —
|
||||||
debug!(
|
// that is deferred to `prepare_outbound_msg1` at connect-resolution
|
||||||
peer = %self.peer_display_name(&peer_node_addr),
|
// time (`poll_pending_connects`), so `our_index` stays `None` on both
|
||||||
transport_id = %transport_id,
|
// the (not-yet-built) connection and the machine.
|
||||||
remote_addr = %remote_addr,
|
let now = Self::now_ms();
|
||||||
link_id = %link_id,
|
let ambient = PeerActionCtx {
|
||||||
"Transport connect initiated (non-blocking)"
|
verified_identity: peer_identity,
|
||||||
);
|
transport_id,
|
||||||
self.peering.pending_connects.push(super::PendingConnect {
|
remote_addr: remote_addr.clone(),
|
||||||
link_id,
|
our_index: None,
|
||||||
transport_id,
|
their_index: None,
|
||||||
remote_addr,
|
now_ms: now,
|
||||||
peer_identity,
|
is_outbound: true,
|
||||||
});
|
};
|
||||||
}
|
self.advance_peer_machine(
|
||||||
Err(e) => {
|
link_id,
|
||||||
// Clean up link and the dial-time control machine
|
PeerEvent::Dial {
|
||||||
self.links.remove(&link_id);
|
transport_id,
|
||||||
self.addr_to_link.remove(&(transport_id, remote_addr));
|
remote_addr,
|
||||||
self.peer_machines.remove(&link_id);
|
peer_identity,
|
||||||
return Err(NodeError::TransportError(e.to_string()));
|
connection_oriented: true,
|
||||||
}
|
},
|
||||||
}
|
now,
|
||||||
}
|
&ambient,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// Connectionless: no connect step. Prepare msg1 in the shell — the
|
// 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
|
/// Prepare an outbound Noise msg1 at dial: allocate the session index, run
|
||||||
/// the Noise leaf, frame the wire, arm the shell-side resend, track
|
/// the Noise leaf, frame the wire, arm the shell-side resend, track
|
||||||
/// `pending_outbound`, and persist the connection. Returns `Err` on
|
/// `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.
|
/// Send the msg1 wire that `prepare_outbound_msg1` armed on the connection.
|
||||||
/// On send error, marks the connection failed and RETAINS it (the legacy
|
/// 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
|
/// 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
|
/// body of the executor's `SendHandshake` msg1 action — reached on both the
|
||||||
/// the connection-oriented `start_handshake`.
|
/// 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(
|
pub(in crate::node) async fn send_stored_msg1(
|
||||||
&mut self,
|
&mut self,
|
||||||
link_id: LinkId,
|
link_id: LinkId,
|
||||||
@@ -1223,16 +1206,18 @@ impl Node {
|
|||||||
"Transport connected, starting handshake"
|
"Transport connected, starting handshake"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start the handshake now that the transport is connected
|
// Prepare msg1 now that the transport is connected, then drive
|
||||||
if let Err(e) = self
|
// the machine to send it. The prepare (index alloc, Noise leaf,
|
||||||
.start_handshake(
|
// wire arm) MUST run BEFORE the `TransportConnected` drive: the
|
||||||
pending.link_id,
|
// executor's `SendHandshake` msg1 branch only transmits the wire
|
||||||
pending.transport_id,
|
// this armed on the connection — a drive-only path would find no
|
||||||
pending.remote_addr.clone(),
|
// armed wire and silently send nothing.
|
||||||
pending.peer_identity,
|
if let Err(e) = self.prepare_outbound_msg1(
|
||||||
)
|
pending.link_id,
|
||||||
.await
|
pending.transport_id,
|
||||||
{
|
&pending.remote_addr,
|
||||||
|
pending.peer_identity,
|
||||||
|
) {
|
||||||
warn!(
|
warn!(
|
||||||
link_id = %pending.link_id,
|
link_id = %pending.link_id,
|
||||||
error = %e,
|
error = %e,
|
||||||
@@ -1241,6 +1226,29 @@ impl Node {
|
|||||||
// Clean up link and dial-time machine on handshake failure
|
// Clean up link and dial-time machine on handshake failure
|
||||||
self.remove_link(&pending.link_id);
|
self.remove_link(&pending.link_id);
|
||||||
self.peer_machines.remove(&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 {
|
} else {
|
||||||
let reason = reason.unwrap_or_default();
|
let reason = reason.unwrap_or_default();
|
||||||
|
|||||||
Reference in New Issue
Block a user