(
handler: Arc<H>,
auth: Arc<AuthContext>,
identity_store: Arc<S>,
peer_cert_der: Option<Vec<u8>>,
conn: quinn::Connection,
mut send: quinn::SendStream,
mut recv: quinn::Re
| 244 | /// module docstring for the rationale. |
| 245 | #[allow(clippy::too_many_arguments)] |
| 246 | async fn handle_stream<H: RaftRpcHandler, S: PeerIdentityStore>( |
| 247 | handler: Arc<H>, |
| 248 | auth: Arc<AuthContext>, |
| 249 | identity_store: Arc<S>, |
| 250 | peer_cert_der: Option<Vec<u8>>, |
| 251 | conn: quinn::Connection, |
| 252 | mut send: quinn::SendStream, |
| 253 | mut recv: quinn::RecvStream, |
| 254 | mut shutdown: watch::Receiver<bool>, |
| 255 | ) -> Result<()> { |
| 256 | let work = async { |
| 257 | // 1. Read one envelope. |
| 258 | let envelope = read_envelope(&mut recv).await?; |
| 259 | let (fields, inner_frame) = auth_envelope::parse_envelope(&envelope, &auth.mac_key)?; |
| 260 | |
| 261 | // 2. Replay window — under the advertised from_node_id (MAC-verified). |
| 262 | // Self-addressed frames skip the window: when a node dispatches |
| 263 | // an RPC to itself over the transport, the shared `AuthContext` |
| 264 | // means one window is updated by both the server-side request |
| 265 | // accept (here) and the client-side response accept (in |
| 266 | // `send.rs::parse_inbound`). Skipping when `from == local` |
| 267 | // keeps the two flows from tripping on each other's entries — |
| 268 | // a self-addressed frame can't have been replayed by an |
| 269 | // external attacker by definition. |
| 270 | if fields.from_node_id != auth.local_node_id { |
| 271 | auth.peer_seq_in.accept(fields.from_node_id, fields.seq)?; |
| 272 | } |
| 273 | |
| 274 | // 3b. Peer identity check — binds the MAC-verified node_id to the |
| 275 | // TLS certificate. Self-addressed frames skip the check by the |
| 276 | // same reasoning as the replay window above. |
| 277 | if fields.from_node_id != auth.local_node_id |
| 278 | && let Some(cert_der) = &peer_cert_der |
| 279 | { |
| 280 | let node_info = identity_store.get_node_info(fields.from_node_id); |
| 281 | match node_info { |
| 282 | Some(ref info) => match verify_peer_identity(info, cert_der) { |
| 283 | VerifyOutcome::Accepted { method } => { |
| 284 | debug!( |
| 285 | node_id = fields.from_node_id, |
| 286 | ?method, |
| 287 | "peer identity verified" |
| 288 | ); |
| 289 | } |
| 290 | VerifyOutcome::BootstrapAccepted => { |
| 291 | warn!( |
| 292 | node_id = fields.from_node_id, |
| 293 | "peer identity not pinned — bootstrap window accepted" |
| 294 | ); |
| 295 | } |
| 296 | VerifyOutcome::Rejected => { |
| 297 | warn!( |
| 298 | node_id = fields.from_node_id, |
| 299 | "peer identity mismatch — closing connection" |
| 300 | ); |
| 301 | conn.close(IDENTITY_MISMATCH_QUIC_ERROR, b"peer identity mismatch"); |
| 302 | return Err(ClusterError::Transport { |
| 303 | detail: format!( |
no test coverage detected