Skip to content

Commit 59378ea

Browse files
committed
test(icaptcha): use [::1] for the blackhole tripwire fixture
The tripwire bound a hard-coded 127.0.0.2 alias, which macOS does not route by default (only 127.0.0.1), so the test could not run on apple-darwin targets. Switch the fixture to [::1] (IPv6 loopback), which is present by default on Linux, macOS, and Windows and is NOT covered by NO_PROXY (127.0.0.1, localhost). The negative control still holds: hyper-util's proxy matcher has no implicit loopback bypass — a host is only proxied-around when literally listed in NO_PROXY (verified in the pinned hyper-util source). Both the positive control (direct reach of [::1]) and negative control (proxy intercept) still pass.
1 parent f741c72 commit 59378ea

1 file changed

Lines changed: 31 additions & 26 deletions

File tree

‎crates/icaptcha-client/tests/icaptcha_blackhole_tripwire.rs‎

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,33 @@
77
//! during the normal mock-consumed run because the mock is on loopback and
88
//! `NO_PROXY` covers it — the blackhole isn't even exercised).
99
//!
10-
//! This test asserts that a non-loopback destination fails *while the blackhole
11-
//! is armed*. The destination is a local HTTP server on `127.0.0.2`, a loopback
12-
//! alias that `NO_PROXY` does NOT cover. With the blackhole active the proxy
13-
//! intercepts the connection and blocks it; without the blackhole the request
14-
//! would reach the server directly and succeed, turning this test RED.
10+
//! This test asserts that a destination the blackhole's `NO_PROXY` does NOT
11+
//! cover fails *while the blackhole is armed*. The destination is a local HTTP
12+
//! server on `[::1]` (IPv6 loopback), which `NO_PROXY` (`127.0.0.1, localhost`)
13+
//! does not list. With the blackhole active the proxy intercepts the
14+
//! connection and blocks it; without the blackhole the request would reach the
15+
//! server directly and succeed, turning this test RED.
1516
//!
1617
//! A positive control (disarmed blackhole) runs first, proving the fixture is
17-
//! valid and `127.0.0.2` is reachable.
18+
//! valid and `[::1]` is reachable.
1819
//!
1920
//! Design constraints (see issue #211):
2021
//! - An unresolvable host would keep the request failing even when the guard
2122
//! is disarmed (DNS failure masquerading as the blackhole), so we must use
2223
//! a reachable address.
2324
//! - A real external host would make a live network call the moment the guard
2425
//! disarms, and on any runner that cannot reach that host the connect error
25-
//! again masquerades as the blackhole. A local loopback alias avoids both
26+
//! again masquerades as the blackhole. A local loopback address avoids both
2627
//! problems — it is always reachable when unblocked and never reaches the
2728
//! real network.
28-
//! - This depends on reqwest's `NO_PROXY` matching by exact IP (holds in the
29-
//! pinned reqwest/hyper-util; recheck on upgrade) and on the OS routing
30-
//! `127/8` as loopback (default on Linux; macOS needs an explicit alias —
31-
//! the test fails loudly, never skips, if `127.0.0.2` cannot be bound).
29+
//! - The address must NOT be covered by `NO_PROXY`, yet must exist on every
30+
//! platform. An arbitrary `127/8` alias is not portable: macOS only binds
31+
//! `127.0.0.1` by default. `[::1]` (IPv6 loopback) is present by default
32+
//! on Linux, macOS, and Windows, and `NO_PROXY` only lists `127.0.0.1` and
33+
//! `localhost`, so the blackhole still intercepts it.
34+
//! - This depends on hyper-util's proxy matcher having no implicit loopback
35+
//! bypass — a host is only proxied-around when literally listed in
36+
//! `NO_PROXY` (verified in the pinned hyper-util; recheck on upgrade).
3237
3338
use std::io::{Read, Write};
3439
use std::net::TcpListener;
@@ -39,7 +44,7 @@ use icaptcha_client::{obtain_proof, Challenge, IcaptchaCfg};
3944
mod support;
4045

4146
/// A minimal HTTP server that responds to iCaptcha challenge and answer
42-
/// requests on a loopback alias NOT covered by NO_PROXY.
47+
/// requests on the IPv6 loopback address NOT covered by NO_PROXY.
4348
///
4449
/// When the blackhole is working, the proxy should intercept these requests
4550
/// and `obtain_proof` should fail. When the blackhole is disarmed, the
@@ -92,21 +97,21 @@ fn make_cfg(url: &str) -> IcaptchaCfg {
9297

9398
#[test]
9499
fn obtain_proof_blackhole_tripwire() {
95-
// Bind to 127.0.0.2, a loopback alias NOT covered by NO_PROXY. Linux
96-
// routes the whole 127/8 block as loopback; macOS does not alias it by
97-
// default (add one with `sudo ifconfig lo0 alias 127.0.0.2`). Fail
98-
// loudly on platforms that cannot bind it: a silent skip would report
99-
// this tripwire as green while exercising none of the negative control,
100-
// recreating the very gap it exists to close.
101-
let listener = TcpListener::bind("127.0.0.2:0").expect(
102-
"cannot bind 127.0.0.2 for the blackhole tripwire. Linux routes \
103-
127/8 as loopback; on macOS add the alias first \
104-
(`sudo ifconfig lo0 alias 127.0.0.2`). This test fails rather than \
105-
silently skipping so a platform without the alias cannot count the \
106-
negative control as passed.",
100+
// Bind to [::1], IPv6 loopback, which NO_PROXY (127.0.0.1, localhost)
101+
// does not cover. Unlike an arbitrary 127/8 alias, IPv6 loopback is
102+
// present by default on Linux, macOS, and Windows, so the fixture is
103+
// portable across platforms. Fail loudly if it cannot be bound: a
104+
// silent skip would report this tripwire as green while exercising
105+
// none of the negative control, recreating the very gap it exists to
106+
// close.
107+
let listener = TcpListener::bind(("::1", 0)).expect(
108+
"cannot bind [::1] for the blackhole tripwire. IPv6 loopback (::1) \
109+
is present by default on Linux, macOS, and Windows. This test fails \
110+
rather than silently skipping so a platform without it cannot count \
111+
the negative control as passed.",
107112
);
108113
let port = listener.local_addr().unwrap().port();
109-
let url = format!("http://127.0.0.2:{port}");
114+
let url = format!("http://[::1]:{port}");
110115
serve_icaptcha(listener);
111116

112117
// ── Positive control: disarmed blackhole → obtain_proof succeeds ──────
@@ -128,7 +133,7 @@ fn obtain_proof_blackhole_tripwire() {
128133
let result = obtain_proof(&cfg, Some(solve));
129134

130135
let err = result.expect_err(
131-
"blackhole tripwire: obtain_proof succeeded against 127.0.0.2, \
136+
"blackhole tripwire: obtain_proof succeeded against [::1], \
132137
meaning the proxy blackhole did not intercept the request; \
133138
the no-live-call guard is disarmed",
134139
);

0 commit comments

Comments
 (0)