Commit 95f4cd7
flint-fronted: meek polling client + vantage-point scanner (Shir-o-Khorshid CDN-fronting) (#10)
* flint-fronted: meek polling client + vantage-point scanner (Shir-o-Khorshid CDN-fronting)
Add a self-bootstrapping, domain-fronted meek POLLING transport. This is distinct
from the existing h2-streaming MeekStream (a different wire protocol the deployed
meek-server does not speak); only the fronting/TLS/config layer is reused.
- meek_poll: MeekPollConn (AsyncRead+AsyncWrite) porting lantern-box protocol/meek
PR #282 — discrete POSTs keyed by X-Session-Id + a monotonic server-deduped
X-Meek-Seq (lost request/response replays, no gap/dup), 256KB negotiated body
(X-Meek-Max-Body), retriable polls. Both h1 and h2 backends. FrontedMeekPollDialer
+ open_meek_poll glue; app<->poll-task bridged via tokio::io::duplex.
- sys_dns: SystemResolver (getaddrinfo) — local/ISP DNS for Akamai-edge discovery.
A censor returns truthful, geo-local edge IPs; flint's DoH resolver would bypass
exactly the local answers we want.
- scanner: vantage-point front discovery — Akamai edge local-DNS resolution +
CloudFront & Aliyun/Alibaba prefix sampling, two-stage ranked probe.
- socks5: minimal SOCKS5 CONNECT over the tunnel (meek-server upstream is microsocks).
- dial_fronts: race scanner-discovered fronts without a Config.
37 hermetic tests pass (incl. multi-poll reassembly + retry-replay-without-gap/dup).
Live test (tests/meek_live.rs, run with --features boring --ignored): a real Akamai
edge (system-resolved) -> meek over h1 -> the deployed meek-server -> SOCKS5 ->
example.com -> 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint-dial/fronted: surface negotiated ALPN; meek auto-selects h1/h2
The boring Chrome dial offers h2,http/1.1 and the CDN edge picks one, but boxing
the SslStream into BoxedTlsStream erased which. Capture it and thread it up so the
meek client speaks whatever was negotiated per connection instead of a fixed guess.
- flint-dial: new AlpnStream (wraps BoxedTlsStream + the negotiated ALPN) and
dial_alpn / dial_over_alpn; dial / dial_over keep their old signatures (thin
wrappers that drop the ALPN). dial_boring captures
ssl().selected_alpn_protocol() before boxing.
- flint-fronted: dial_fronts_alpn (race scanner fronts, keep the ALPN) and
open_meek_poll_auto (set MeekPollConfig.http_version = h2 iff the edge negotiated
h2, else h1).
- meek_live test: drive the full auto path (scan -> dial_fronts_alpn ->
open_meek_poll_auto). Live against the deployed server it logs negotiated
ALPN "http/1.1" and auto-selects h1; SOCKS5 -> example.com -> 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: address review (timeouts, h1 framing/CRLF hardening, drop-abort, honest retry)
Review fixes from CodeRabbit + Copilot on the fronted-meek PR:
- MeekPollConn aborts its poll task on Drop (was detached, could outlive the conn).
- Per-poll request timeout (MeekPollConfig.request_timeout) wraps each roundtrip;
an elapsed request is retryable so a stalled response can't block forever.
- Retry semantics made honest: removed the no-op Backend::reconnect; retry only
in-place when safe (h2: fresh stream + server seq-dedupe), h1 ends the session
on failure (single keep-alive connection a failed request leaves desynced).
- h1 hardening: reject CR/LF/control chars in inner_host/path (raw request line);
require Content-Length on keep-alive and error on over-cap instead of leaving
unread bytes; chunked decode now parses the size token before `;extensions`,
drains all trailers to the blank line, and keeps draining framing past the cap
(stops appending only) so the connection stays aligned.
- h2: error on an over-cap response instead of truncating + advancing seq.
- SystemResolver: bound getaddrinfo with a 5s timeout (a stuck resolver can't hang
the scan).
- meek_live: timeout the SOCKS5 handshake + request write too.
- cargo fmt (unblocks CI); fix a stale MeekHttpVersion doc (ALPN is now exposed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* meek_live: assert on the HTTP status line + body marker, not a bare "200"
Review (Copilot): "200" could appear anywhere in the bytes. Assert the response
starts with "HTTP/1.1 200" and contains "Example Domain".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: review round 3 (stricter host validation, framing checks)
- Reject space/non-ASCII (not just CR/LF/control) in the h1 inner_host/path, which
would otherwise break the request-target / Host tokenization.
- Validate the chunk-terminating CRLF in chunked decode (error on malformed
framing instead of silently desyncing the keep-alive connection).
- Validate the SOCKS5 reply RSV byte is 0x00.
- sys_dns test: bind the resolver to a local before awaiting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: review round 4 (bound chunk allocation, accurate Front.domain)
- read_chunked: reject a chunk whose size exceeds max_body_bytes before
allocating, so an attacker-controlled chunk-size line can't force an OOM
allocation (the server caps responses at the advertised max-body anyway).
- Candidate::to_front sets Front.domain to the edge's verify hostname (e.g.
a248.e.akamai.net) rather than duplicating the inner fronted_host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: review round 5 (chunked over-cap errors; strict inner_host authority)
- read_chunked errors when the total exceeds max_body_bytes (matching the
non-chunked + h2 paths) instead of silently truncating and advancing the seq.
- inner_host is validated as a bare authority — also reject `/ ? # @ \` (not just
control/space/non-ASCII), which would break the Host header / request target.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: review round 6 (h2 driver teardown; provider verify hostnames)
- H2Backend holds the h2 connection-driver JoinHandle and aborts it on Drop, so
the driver (and the TLS stream it holds) can't outlive the backend — matching
MeekPollConn's drop semantics.
- ScanTargets gains cloudfront_verify_hostname / aliyun_verify_hostname: prefix-
sampled (empty-SNI) candidates verify against the edge's cert identity rather
than always the inner fronted_host (which only matches for an Akamai endpoint).
Defaults preserve current behavior; documented for real CloudFront/Aliyun meek.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* socks5: flush after each write so buffered writers don't deadlock
Flush after the method-select greeting and the CONNECT request; a buffered
AsyncWrite could otherwise hold the bytes while we block on read_exact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: review round 8 (hermetic h1 test; normalize system DNS results)
- Add a hermetic HTTP/1.1 meek echo test so the h1 backend's Content-Length path
has CI coverage (previously only the live test exercised h1).
- SystemResolver short-circuits IP literals and sorts+dedups results, matching
FlintDnsResolver and avoiding redundant scan/dial attempts.
(Skipped the dial() ALPN-Vec micro-alloc nit — negligible, and dial() is not the
meek hot path; the race uses dial_alpn.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
* flint: fix clippy (derivable_impls) — derive Default for MeekHttpVersion
CI runs `cargo clippy --all-targets -- -D warnings`; the manual Default impl
tripped clippy::derivable_impls. Replace with #[derive(Default)] + #[default] on
H2 (unchanged behavior). Ran the exact CI clippy command locally to confirm clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9beSsYGzUaBhRK5ULmtGr
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent 76c5cd3 commit 95f4cd7
8 files changed
Lines changed: 2102 additions & 9 deletions
File tree
- crates
- flint-dial/src
- flint-fronted
- src
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
17 | 23 | | |
18 | 24 | | |
19 | 25 | | |
20 | 26 | | |
21 | | - | |
| 27 | + | |
22 | 28 | | |
23 | 29 | | |
24 | 30 | | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
28 | 42 | | |
29 | 43 | | |
30 | 44 | | |
31 | 45 | | |
32 | 46 | | |
33 | | - | |
| 47 | + | |
34 | 48 | | |
35 | 49 | | |
36 | 50 | | |
37 | 51 | | |
38 | 52 | | |
39 | 53 | | |
40 | | - | |
| 54 | + | |
| 55 | + | |
41 | 56 | | |
42 | 57 | | |
43 | 58 | | |
| |||
70 | 85 | | |
71 | 86 | | |
72 | 87 | | |
73 | | - | |
| 88 | + | |
74 | 89 | | |
75 | 90 | | |
76 | 91 | | |
77 | 92 | | |
78 | | - | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
79 | 97 | | |
80 | 98 | | |
81 | 99 | | |
| |||
85 | 103 | | |
86 | 104 | | |
87 | 105 | | |
88 | | - | |
| 106 | + | |
89 | 107 | | |
90 | 108 | | |
91 | 109 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
21 | 25 | | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
26 | | - | |
| 30 | + | |
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
| |||
36 | 40 | | |
37 | 41 | | |
38 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
35 | 49 | | |
36 | 50 | | |
37 | 51 | | |
| |||
1481 | 1495 | | |
1482 | 1496 | | |
1483 | 1497 | | |
| 1498 | + | |
| 1499 | + | |
| 1500 | + | |
| 1501 | + | |
| 1502 | + | |
| 1503 | + | |
| 1504 | + | |
| 1505 | + | |
| 1506 | + | |
| 1507 | + | |
| 1508 | + | |
| 1509 | + | |
| 1510 | + | |
| 1511 | + | |
| 1512 | + | |
| 1513 | + | |
| 1514 | + | |
| 1515 | + | |
| 1516 | + | |
| 1517 | + | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
| 1521 | + | |
| 1522 | + | |
| 1523 | + | |
| 1524 | + | |
| 1525 | + | |
| 1526 | + | |
| 1527 | + | |
1484 | 1528 | | |
1485 | 1529 | | |
1486 | 1530 | | |
| |||
0 commit comments