review-003: the assembly-layer full review — F-1/F-2 fixed, the residue dispositioned
- Review 003 written (docs/reviews/003-assembly-layer-implementation- review.md): the batch-2 assembly layer post-landing, the review-002 template — correctness/security/smells/coverage over the five batch-2 modules + the batch-2 consumer/producer diff surface. Baseline: 375 default / 620 all-features green, clippy/fmt/doc clean, both wasm checks clean, publish dry-run completes, llvm-cov 93.68% lines / 91.01% functions. - F-1 (major, fixed): the cred-bearing door swallowed every in-band refusal's mapped reply code (the CONNECT/BIND session-open errors and the accept-stage Err were bare closes) — a vanilla client got no reply where ADR-007 pins a reply-code-shaped refusal. Fixed with a write_refusal_reply helper (the door's boundary codec, RFC placeholder BND.ADDR) at all three sites, pinned by cred_door_in_band_refusal_surfaces_the_reply_code. - F-2 (major, fixed): UdpFrontDoor::serve_on returned Ok(()) on a fatal relay-socket I/O failure against its own doc contract — the Io exit now surfaces as Err; the idle/association/session exits stay clean. - Findings recorded, not fixed (the release-polish round's residue): D-1 (associate_with_token 0% covered), D-2 (the composed-egress failure matrix), D-3 (the io-kind refusal-table arms), S-1 (the UDP door's learned client address), N-1..N-8 (doc lines, posture notes, coverage residue). Verification: cargo test (375) + --all-features (620) green, clippy --all-targets --all-features -D warnings clean, fmt --check clean, doc --no-deps clean, both wasm checks clean, publish dry-run completes.
This commit is contained in:
1 parent
04c76e3a41
commit
2db05172ef
3 files changed
+797
-8
No files matched your search
+42
-8
@@ -480,7 +480,14 @@ async fn cred_bearing(
|
||||
.await
|
||||
{
|
||||
Ok(session) => session,
|
||||
Err(err) => return DoorExit::Open(err.to_string()),
|
||||
Err(err) => {
|
||||
// The in-band refusal's mapped code is re-encoded to
|
||||
// the vanilla client (the RFC codec at the door
|
||||
// boundary — ADR-003): a dial refusal is reply#1's
|
||||
// code, never a bare EOF.
|
||||
write_refusal_reply(&mut stream, &err).await;
|
||||
return DoorExit::Open(err.to_string());
|
||||
}
|
||||
};
|
||||
pipe_connect_plane(stream, session).await
|
||||
}
|
||||
@@ -490,7 +497,10 @@ async fn cred_bearing(
|
||||
.await
|
||||
{
|
||||
Ok(session) => session,
|
||||
Err(err) => return DoorExit::Open(err.to_string()),
|
||||
Err(err) => {
|
||||
write_refusal_reply(&mut stream, &err).await;
|
||||
return DoorExit::Open(err.to_string());
|
||||
}
|
||||
};
|
||||
pipe_bind_plane(stream, session).await
|
||||
}
|
||||
@@ -663,11 +673,13 @@ async fn bind_quiet_and_pipe(
|
||||
let plane = match session.await_inbound().await {
|
||||
Ok(Some(plane)) => plane,
|
||||
Ok(None) => return DoorExit::PlaneEnded,
|
||||
// An accept-stage refusal (reply#2's mapped code was written
|
||||
// in-band; the producer's pump ended the session): the door
|
||||
// closes the vanilla connection — the conversation ends where
|
||||
// the in-band one did.
|
||||
Err(_) => {
|
||||
// An accept-stage refusal (the mapped reply#2 code): the
|
||||
// vanilla client gets that code re-encoded door-side (the RFC
|
||||
// codec at the boundary — never a bare EOF), then the
|
||||
// connection closes — the conversation ends where the in-band
|
||||
// one did.
|
||||
Err(err) => {
|
||||
write_refusal_reply(&mut stream, &err).await;
|
||||
session.close().await;
|
||||
return DoorExit::PlaneEnded;
|
||||
}
|
||||
@@ -699,6 +711,22 @@ async fn write_vanilla_reply(stream: &mut TcpStream, reply: &[u8]) -> std::io::R
|
||||
stream.flush().await
|
||||
}
|
||||
|
||||
/// Re-encode an in-band session error's refusal to the vanilla client
|
||||
/// (the door's RFC codec at the boundary): an error carrying an RFC
|
||||
/// reply code gets that code as the command's reply (`Succeeded`-form
|
||||
/// encoding with the error's code + the placeholder BND.ADDR — a
|
||||
/// refused reply carries a zero address per the RFC); any other error
|
||||
/// shape (open/transport/I-O) stays a bare close (nothing to encode).
|
||||
/// Best-effort: a failed write means the client is gone either way.
|
||||
async fn write_refusal_reply(stream: &mut TcpStream, err: &SocksSessionError) {
|
||||
let code = match err {
|
||||
SocksSessionError::Rfc(SocksError::ReplyError(code)) => *code,
|
||||
_ => return,
|
||||
};
|
||||
let reply = new_reply(&code, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0));
|
||||
let _ = write_vanilla_reply(stream, &reply).await;
|
||||
}
|
||||
|
||||
/// The UDP front door (feature `local`, the POC #2 test-8 shape): the
|
||||
/// ASSOCIATE door — a real UDP relay socket bridged onto one in-band
|
||||
/// association. See the module docs for the shape and the codec
|
||||
@@ -908,7 +936,13 @@ impl UdpFrontDoor {
|
||||
};
|
||||
tracing::debug!("udp front door: association ended: {exit}");
|
||||
let _ = association.shutdown().await;
|
||||
Ok(())
|
||||
// A socket I/O failure is the door's fatal outcome (surfaced to
|
||||
// the caller); the idle bound, the association's end, and a
|
||||
// session error are the association's own ends — a clean return.
|
||||
match exit {
|
||||
UdpDoorExit::Io(err) => Err(err),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user