Files
cfal b10c100288 Simplify XHTTP rejections and allow matching_headers value lists (#208)
XHTTP required-header support still carried leftovers from its earlier connection-closing design. This removes `close_on_http1_error` entirely (configs that set it now fail to load), so header and path mismatches return the same plain 404 on every HTTP version, and makes every XHTTP error response a bare status with an empty body. `matching_headers` values can now be lists so secrets can rotate without downtime, and client `headers` now reject names that differ only in case and values with leading or trailing whitespace, neither of which could ever match. Redundant H2/H3 reset handling is removed while the ordering and H3 upload-reset handling that prevent session closures and held request slots are kept, documented and covered by a regression test; new interop tests cover Xray and Mihomo clients sending required headers, and the README explains that Xray clients using `extra` must put `headers` inside it.
2026-10-22 04:41:27 -04:00

9.8 KiB

XHTTP Interoperability Tests

The xhttp_interop integration target runs real Shoes processes against Shoes, Xray-core, and Mihomo Alpha. CI builds and caches these peers from pinned source:

  • Xray-core: 1b1647cd9ddf0abc34b981d4611fc03bcac1ca74 (26.10.21).
  • Mihomo Alpha: 94b13aed49b8dc41973e4bb3f721c5fa2c5c5c1f.

Use Go 1.27.3, GOMAXPROCS=1, and go build -p 1. Build Xray's ./main package. Build Mihomo's root package with CGO_ENABLED=0 and -ldflags '-X github.com/metacubex/mihomo/constant.Version=alpha-94b13aed'. The full peer setup is in .github/workflows/test.yml; the stable Mihomo peer used by optional_peer is deliberately unchanged.

export SHOES_TEST_XRAY_BIN=/absolute/path/to/xray
export SHOES_TEST_MIHOMO_BIN=/absolute/path/to/mihomo
CARGO_BUILD_JOBS=1 cargo test --locked --features integration-tests \
  --test xhttp_interop -- --test-threads=1

Missing or incorrectly versioned peers fail the tests rather than skipping them. Use the shoes_to_shoes, shoes_backpressure, or wire:: test filters to run local-only coverage without external peers.

Coverage

  • VLESS in both external-peer directions: cleartext H1/H2C, TLS H1/H2/H3, and REALITY/H2 where supported; all three explicit modes plus representative auto cases. Xray also covers Trojan. Shoes covers default HTTP selection.
  • Fixed 32 KiB packet limits and offset/session-dependent binary payloads force fragmentation. Concurrent sessions, sequential reuse, and two UDP destinations check integrity and routing, not just successful handshakes. Dedicated UDP cases use 1 KiB POST limits and concurrent associations so 4 KiB datagrams cross POST boundaries without exceeding the external peers' UDP buffer limits.
  • H2/H3 packet-up transfers pause consumers with 16 MiB uploads and 64 MiB downloads, for at least twice the measured unpaused transfer duration (minimum one second), then resume and verify exact bytes and continued session progress. Shoes uses 256 KiB per-session and 1 MiB global byte budgets, with the default 30-packet reorder window and unlimited per-session requests. Test-owned sending and receiving sockets use 64 KiB buffers to keep kernel autotuning from absorbing the entire paused transfer. Downloads exceed the combined peer transport windows and intermediate TCP buffers; a 16 MiB download can fit in those buffers without backpressuring the source. Cancellation and connection churn use the same processes.
  • Separate Xray cases use the default unlimited aggregate and per-session byte and request budgets with 32 KiB POSTs and 30 ms pacing. H2 and H3 recover after a ten-second destination pause while download heartbeats continue on that same session. An additional H2 variant explicitly caps live packet requests at 128 to exercise refusal and Xray transport replay without closing the logical session. The per-connection request limit remains 250 in these cases. A separate H2 case uses a two-second packet inactivity timeout to check permanent-stall containment and subsequent session availability.
  • Mihomo server recovery is also checked with the default Shoes client byte limits. The client sequence window prevents ACKs for later packets from advancing past a stalled earlier packet and overflowing the peer's reorder queue. H1's single-POST client path undergoes the same stall/resume checks against both peers.
  • Raw H1 requests exercise reordering, matching/conflicting duplicates, exclusive download ownership, wrong host/path, missing padding, oversized packets, reorder-window rejection, and recovery after rejected requests.
  • A single POST trickles at about 5 KiB/s for four seconds with a two-second inactivity timeout. Stalled bodies must time out and release admission.
  • Wrong UUIDs must fail without dialing the destination; matching controls pass. Xray and Mihomo client headers satisfy Shoes matching_headers in all three modes on H2 and in H1/H3 packet-up; missing or wrong values fail the same way. Existing TLS/QUIC allowlist and REALITY ML-DSA verification cases remain enabled.
  • SOCKS -> XHTTP and QUIC XHTTP -> VLESS chains carry TCP/UDP and fail closed when a hop disappears.

Boundaries

Mihomo has no XHTTP H3 listener or Trojan/XHTTP adapter. Its client supports H3; its REALITY fixture permits classical X25519 as well as hybrid key exchange. Mihomo uses a simple path because its normalization treats query syntax as path text. Shoes/Xray retain the escaped-path/query interoperability fixture. The XHTTP fixture disables Mihomo's loopback detector only in its peer subprocess. The detector compares inbound source IP/port against outbound local IP/port without considering the destination; independent connections in this all-loopback topology can share that pair and trigger a false routing-loop rejection during churn. Other Mihomo fixtures retain their existing environment and loop detection.

Xray client H2C is not included. Packet-up does not signal upload FIN. A completely stalled session can retain shared-carrier flow-control credit, so the pressure tests require recovery after consumption resumes, not independent progress while the shared window is exhausted. Attached out-of-window POSTs wait for their predecessors subject to packet inactivity. Normal pressure fixtures leave aggregate and per-session request caps unlimited; client pipeline depth, transport flow control and the 250-request per-connection limit still constrain concurrency. The explicit 128-request Xray H2 case covers per-session refusal and recovery. Deterministic GOAWAY, credit starvation, timeout races, and memory-release ordering remain covered by the XHTTP library tests.

Throughput And Stall Qualification

The ignored packet_up_throughput_and_stall_qualification test compares Xray-to-Xray, Xray-to-Shoes and Shoes-to-Xray using 16 MiB in each direction per session. A bounded TCP relay delays each byte batch by half the requested RTT without serializing a whole RTT per batch. It models latency, not loss or congestion. Linux /proc sampling records aggregate peer RSS; debug resource logs record application byte and request-budget peaks. Neither measurement includes relay RSS in the peer figure or equates application byte accounting with total RSS.

XHTTP_BENCH_RTT_MS=0,50,150,300 \
XHTTP_BENCH_PAUSE_SECS=0,5,10,30,60 \
XHTTP_BENCH_POST_BYTES=32768,1000000 \
XHTTP_BENCH_REQUEST_CAP=128,1024 \
XHTTP_BENCH_SESSIONS=1,4 \
CARGO_BUILD_JOBS=1 cargo test --locked --features integration-tests \
  --test xhttp_interop packet_up_throughput_and_stall_qualification \
  -- --ignored --nocapture --test-threads=1

Each variable accepts a comma-separated list. Defaults run RTT 0/150 ms, no stall, 32 KiB/1 MB packets, cap 128 and one session. XHTTP_QUALIFICATION JSON records integrity success, elapsed time, aggregate duplex throughput and sampled peak RSS. The server-request-cap override applies only to the Shoes server, not Xray. Set XHTTP_BENCH_H2_WINDOW_BYTES=262144 to compare the previous fixed H2 receive windows with the new defaults without changing the binary. XHTTP_BENCH_DIRECTIONS selects comma-separated xray-xray, xray-shoes, or shoes-xray cases. Set SHOES_TEST_SHOES_BIN to a release binary for throughput comparisons; debug builds are useful for correctness, not production rate claims. This extended sweep is opt-in; normal CI runs the focused recovery/containment cases.

Local Stall Qualification

On 2026-10-16, the then-pinned Xray 26.10.10 (701af607) client completed all six 16 MiB duplex cases against Shoes with 32 KiB POSTs, 30 ms pacing, no added RTT, and destination pauses of 10/30/60 seconds. These correctness runs used a debug Shoes binary; their rates are not production throughput measurements.

Server request cap Total duration, 10/30/60 s pause Peak service handlers Peak application bytes Combined peer RSS range
128 16.2 / 47.8 / 83.1 s 133 1.03 MiB 89.4-103.8 MiB
1024 16.3 / 31.0 / 61.1 s 397 1.03 MiB 93.4-117.0 MiB

Service handlers include the GET and briefly rejected requests, not just admitted packet requests. RSS includes both Shoes and Xray and excludes the test harness. The then-default 128 cap survived every tested pause, but Xray's retry backoff delayed completion after longer pauses. A larger cap reduced that delay while retaining more requests. These samples qualify this workload, not arbitrary stalls, packet sizes, transport-credit contention, or overload. These measurements predate the unlimited admission defaults.

A release-build run with four concurrent sessions, 150 ms added RTT, 32 KiB POSTs and a ten-second destination pause also passed all three directions. Total times were 19.2 seconds for Xray-to-Xray, 19.2 seconds for Xray-to-Shoes and 25.0 seconds for Shoes-to-Xray. Each session resumed with exact upload and download bytes.

Local Throughput Qualification

Using the then-pinned Xray 26.10.10 (701af607), the release Shoes binary passed all 24 combinations of 0/50/150/300 ms added RTT, 32 KiB/1,000,000-byte POST limits, and the three peer directions. All used 30 ms pacing and exact 16 MiB transfers each way; Shoes used its default client pipeline depth of 30. Representative aggregate duplex rates for 1,000,000-byte POSTs:

Direction 150 ms RTT 300 ms RTT
Xray to Xray 8.22 MiB/s 4.84 MiB/s
Xray to Shoes 14.31 MiB/s 10.14 MiB/s
Shoes to Xray 7.62 MiB/s 4.62 MiB/s

With 32 KiB POSTs, pacing dominated: all directions were within about 3% of the reference across these RTTs. At 150 ms RTT, overriding only the Shoes H2 windows to the former 256 KiB values reduced the 1,000,000-byte cases to 2.31 MiB/s inbound and 2.28 MiB/s outbound. This supports larger bounded windows rather than an unbounded request pipeline. These are single local samples, including session setup and harness overhead, not link-capacity guarantees or statistical benchmarks.