From 8ab0f49ec48c5c1685bef9e55ea42ca00d05ca64 Mon Sep 17 00:00:00 2001 From: cfal Date: Sun, 1 Nov 2026 00:32:17 +0800 Subject: [PATCH] Fix nested XHTTP SNI and Vision TLS framing (#254) * fix(xhttp): suppress inherited SNI inside the carrier Retain the hop identity for certificate verification while restoring the absent default wire SNI for inner protocols. Extend the TLS identity matrix through the XHTTP builder to cover omitted, null, and explicit SNI. * fix(vision): keep the initial prefix in one TLS record Floor configured Vision client TLS buffers at 21 bytes so peers receive the UUID and first frame header together. Preserve ordinary TLS limits and cover tiny buffers with record-level assertions, UDP exchanges, and sing-box/Xray peer tests. * fix(tls): drain deferred KeyUpdate replies before Vision writes Materialize pending KeyUpdate replies before draining TLS output so they cannot fragment the initial Vision prefix. Preserve queued application data and fatal alerts after session failure, with framing and backpressure regressions. * refactor(tls): clarify identity and Vision regression tests Replace positional identity-path flags with a test-local enum, use the existing message shutdown helper, and document the XHTTP inner SNI boundary. Preserve test coverage and production behavior. * ci: pin Xray test peers to 26.10.31 Replace the unavailable release download with the latest release and its verified checksum. Align source-built peers and fixture version checks with the same release. --- .github/workflows/reality-interop.yml | 4 +- .github/workflows/test.yml | 8 +- .../workflows/vless-encryption-interop.yml | 2 +- src/crypto/crypto_tls_stream.rs | 5 + src/crypto/crypto_tls_stream/tests.rs | 95 +++++++++++++++- src/tcp/tcp_client_handler_factory.rs | 2 +- src/tls_client_handler.rs | 48 ++++++-- src/vless/vision_stream/tests.rs | 106 ++++++++++++++++++ src/xhttp/connector.rs | 4 +- tests/README.md | 2 +- tests/XHTTP.md | 2 +- tests/cases/optional_peer/vision_udp.rs | 7 +- .../cases/optional_peer/wireguard_interop.rs | 2 +- tests/fixtures/vless_encryption/README.md | 4 +- tests/support/src/xray.rs | 2 +- 15 files changed, 260 insertions(+), 33 deletions(-) diff --git a/.github/workflows/reality-interop.yml b/.github/workflows/reality-interop.yml index 71333660..14a3fadd 100644 --- a/.github/workflows/reality-interop.yml +++ b/.github/workflows/reality-interop.yml @@ -10,7 +10,7 @@ jobs: xray: runs-on: ubuntu-latest timeout-minutes: 30 - name: Xray v26.10.29 + name: Xray v26.10.31 env: CARGO_BUILD_JOBS: 1 GOMAXPROCS: 1 @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v7 with: repository: XTLS/Xray-core - ref: 0afec66085a85191e9dd2a4a6646c7c9a94688d1 + ref: a4a3dff5b20c2faae567b742c172c7b95e75c083 path: reality-peer - name: Cache Rust artifacts uses: actions/cache@v6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 237c914c..b02e125a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,11 +15,11 @@ env: ANYTLS_SING_BOX_SHA256: bfc39f643a5ecc1b702c5723ff662e61dde813f12b3af0d0cfce4f629ff74047 MIHOMO_VERSION: 1.19.31 MIHOMO_SHA256: 04cf9f09671704f839ddbee2e93069dc831a4123a75281e725d1d96ab9ac1afc - XRAY_VERSION: 26.10.29 - XRAY_SHA256: 8a5f3e6a1057e4bd0f8a7c57c9d91204b4be0b74222a6e210d070603101163c2 + XRAY_VERSION: 26.10.31 + XRAY_SHA256: e4bd9119f50ae7c41658b1abd0679ae06f98bd864a2907ff33f2f2cf7081b214 SSSERVER_VERSION: 1.25.0 SSSERVER_SHA256: 874f817fcf3e6d7681ec715a1c13c686c6eaae936524d102639b38364f3966ae - XRAY_COMMIT: 0afec66085a85191e9dd2a4a6646c7c9a94688d1 + XRAY_COMMIT: a4a3dff5b20c2faae567b742c172c7b95e75c083 MIHOMO_ALPHA_COMMIT: 94b13aed49b8dc41973e4bb3f721c5fa2c5c5c1f NAIVE_CADDY_VERSION: 2.11.7-naive NAIVE_CADDY_SHA256: ac97057c925bb098eb575e7b0cb8bd547d4c5f845b09a3dedfbe34444aa7ae88 @@ -49,7 +49,7 @@ jobs: env: GOMAXPROCS: 1 WG_SING_BOX_COMMIT: 7054cac5124465ffdb145870d7c01885566711b1 - WG_XRAY_COMMIT: 0afec66085a85191e9dd2a4a6646c7c9a94688d1 + WG_XRAY_COMMIT: a4a3dff5b20c2faae567b742c172c7b95e75c083 steps: - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/vless-encryption-interop.yml b/.github/workflows/vless-encryption-interop.yml index 2d97a066..4bac4864 100644 --- a/.github/workflows/vless-encryption-interop.yml +++ b/.github/workflows/vless-encryption-interop.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v7 with: repository: XTLS/Xray-core - ref: 0afec66085a85191e9dd2a4a6646c7c9a94688d1 + ref: a4a3dff5b20c2faae567b742c172c7b95e75c083 path: peers/xray - uses: actions/checkout@v7 with: diff --git a/src/crypto/crypto_tls_stream.rs b/src/crypto/crypto_tls_stream.rs index a58c8579..d0bb368a 100644 --- a/src/crypto/crypto_tls_stream.rs +++ b/src/crypto/crypto_tls_stream.rs @@ -326,6 +326,11 @@ impl CryptoTlsStream { pub fn poll_drain_tls(&mut self, cx: &mut Context<'_>) -> Poll> { self.register_writer(cx); + if !self.write_raw && self.state.writeable() { + // Deferred KeyUpdate replies must not consume the next plaintext record's budget. + // Failed sessions still need to drain accepted data and fatal alerts. + let _ = self.session.writer().write(&[]); + } self.poll_drain_tls_inner(cx) } diff --git a/src/crypto/crypto_tls_stream/tests.rs b/src/crypto/crypto_tls_stream/tests.rs index 74c72208..09f438fc 100644 --- a/src/crypto/crypto_tls_stream/tests.rs +++ b/src/crypto/crypto_tls_stream/tests.rs @@ -552,9 +552,11 @@ fn raw_flush_retains_prefix_until_transport_flush_completes() { fn key_update_response_precedes_the_next_application_write() { use std::io::Read; - for (session, mut peer) in [ - completed_ordinary_connections(), - crate::reality::established_pair(), + for (drain_first, (session, mut peer)) in [ + (false, completed_ordinary_connections()), + (false, crate::reality::established_pair()), + (true, completed_ordinary_connections()), + (true, crate::reality::established_pair()), ] { let state = Arc::new(Mutex::new(WriteReadiness { allowance: usize::MAX, @@ -583,6 +585,17 @@ fn key_update_response_precedes_the_next_application_write() { assert_eq!((state.write_polls, state.flush_polls), (0, 0)); } + if drain_first { + state.lock().unwrap().allowance = 0; + assert!(stream.poll_drain_tls(&mut cx).is_pending()); + assert!(stream.session.wants_write()); + state.lock().unwrap().allowance = usize::MAX; + assert!(matches!( + stream.poll_drain_tls(&mut cx), + Poll::Ready(Ok(())) + )); + } + assert!(matches!( Pin::new(&mut stream).poll_write(&mut cx, b"reply"), Poll::Ready(Ok(5)) @@ -607,6 +620,74 @@ fn key_update_response_precedes_the_next_application_write() { } } +#[tokio::test] +async fn vision_prefix_stays_in_one_record_after_peer_key_update() { + use crate::address::NetLocation; + use crate::tcp::tcp_client_handler_factory::create_tcp_client_handler; + use futures::future::poll_fn; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + const UUID: [u8; 16] = [0, 0, 0, 0, 0, 0, 0x40, 0, 0x80, 0, 0, 0, 0, 0, 0, 0]; + + tokio::time::timeout(std::time::Duration::from_secs(10), async { + for size in [32, 8, 21, 27, 28, 47, 48, 64] { + let hop = NetLocation::from_str("localhost:443", None).unwrap(); + let handler = create_tcp_client_handler( + serde_json::from_value(serde_json::json!({ + "type": "tls", "verify": false, "vision": true, + "tls_buffer_size": size, + "protocol": { + "type": "vless", "udp_enabled": true, + "user_id": "00000000-0000-4000-8000-000000000000" + } + })) + .unwrap(), + &hop, + Arc::new(crate::resolver::NativeResolver::new()), + ) + .unwrap(); + let (_, server) = ordinary_connections(); + let (left, right) = tokio::io::duplex(65536); + let target = NetLocation::from_str("192.0.2.1:53", None).unwrap(); + let (client, server) = tokio::join!( + handler.setup_client_udp_bidirectional(Box::new(left), target.into()), + CryptoTlsStream::handshake(right, server, TlsReadMode::PreserveRecords, &[]), + ); + let mut client = client.unwrap(); + let mut server = server.unwrap(); + let mut request = [0; 37]; + server.read_exact(&mut request).await.unwrap(); + assert_eq!(request[0], 0); + assert_eq!(&request[1..17], &UUID); + assert_eq!(&request[17..], b"\x12\x0a\x10xtls-rprx-vision\x03"); + + server.session.refresh_traffic_keys().unwrap(); + server.flush().await.unwrap(); + let mut cx = Context::from_waker(noop_waker_ref()); + assert!( + Pin::new(&mut *client) + .poll_read_message(&mut cx, &mut ReadBuf::new(&mut [0; 1024])) + .is_pending() + ); + + poll_fn(|cx| Pin::new(&mut *client).poll_write_message(cx, b"key-update-datagram")) + .await + .unwrap(); + poll_fn(|cx| Pin::new(&mut *client).poll_flush_message(cx)) + .await + .unwrap(); + let mut record = [0; 8192]; + let n = server.read(&mut record).await.unwrap(); + assert!(n >= 21, "buffer {size}: first Vision record has {n} bytes"); + assert!(n <= size.max(21)); + assert_eq!(&record[..16], &UUID); + assert_eq!(record[16], 0); + } + }) + .await + .unwrap(); +} + struct PendingWriteIo; impl AsyncRead for PendingWriteIo { @@ -879,11 +960,12 @@ fn completed_ordinary_connections() -> (TlsConnection, TlsConnection) { } #[tokio::test] -async fn ordinary_tls_shutdown_drains_data_and_fatal_alert_after_backpressure() { +async fn ordinary_tls_drain_and_shutdown_preserve_data_and_fatal_alert_after_backpressure() { + use futures::future::poll_fn; use std::io::Read; use tokio::io::{AsyncReadExt, AsyncWriteExt}; - for fail_server in [false, true] { + for (fail_server, drain_first) in [(false, false), (true, false), (false, true), (true, true)] { let (mut client, mut server) = ordinary_connections(); for _ in 0..8 { let mut wire = Vec::new(); @@ -919,6 +1001,9 @@ async fn ordinary_tls_shutdown_drains_data_and_fatal_alert_after_backpressure() let (shutdown, read) = tokio::time::timeout(std::time::Duration::from_secs(5), async { tokio::join!( async move { + if drain_first { + poll_fn(|cx| stream.poll_drain_tls(cx)).await.unwrap(); + } let result = stream.shutdown().await; drop(stream); result diff --git a/src/tcp/tcp_client_handler_factory.rs b/src/tcp/tcp_client_handler_factory.rs index 65c4a644..bd121e81 100644 --- a/src/tcp/tcp_client_handler_factory.rs +++ b/src/tcp/tcp_client_handler_factory.rs @@ -63,7 +63,7 @@ pub(crate) fn tls_verification_name( rustls::pki_types::ServerName::try_from(identity).map_err(std::io::Error::other) } -fn create_with_hop_context( +pub(crate) fn create_with_hop_context( client_proxy_config: ClientProxyConfig, hop_location: &NetLocation, default_sni_hostname: Option, diff --git a/src/tls_client_handler.rs b/src/tls_client_handler.rs index bc9bfd9f..e8ac1ed4 100644 --- a/src/tls_client_handler.rs +++ b/src/tls_client_handler.rs @@ -27,6 +27,11 @@ impl TlsClientHandler { let mut connection = rustls::ClientConnection::new(self.client_config.clone(), self.server_name.clone())?; if let Some(size) = self.tls_buffer_size { + // Vision peers require the UUID and first frame header in one TLS record. + let size = match self.handler { + TlsInnerClientHandler::VisionVless { .. } => size.max(21), + TlsInnerClientHandler::Default(_) => size, + }; connection.set_buffer_limit(Some(size)); } Ok(connection) @@ -218,17 +223,28 @@ impl TcpClientHandler for TlsClientHandler { mod tests { use super::*; + enum IdentityPath { + Tcp, + Udp, + Xhttp, + } + #[tokio::test] async fn verification_uses_hop_identity_even_without_wire_sni() { - check_hop_identity(false).await; + check_hop_identity(IdentityPath::Tcp).await; } #[tokio::test] async fn udp_verification_uses_hop_identity_even_without_wire_sni() { - check_hop_identity(true).await; + check_hop_identity(IdentityPath::Udp).await; } - async fn check_hop_identity(udp: bool) { + #[tokio::test] + async fn xhttp_inner_tls_keeps_hop_identity_without_default_sni() { + check_hop_identity(IdentityPath::Xhttp).await; + } + + async fn check_hop_identity(path: IdentityPath) { use crate::address::NetLocation; use crate::async_stream::{AsyncFlushMessage, AsyncReadMessage, AsyncWriteMessage}; use crate::tcp::tcp_client_handler_factory::create_tcp_client_handler; @@ -254,6 +270,8 @@ mod tests { } } + let udp = matches!(path, IdentityPath::Udp); + let xhttp = matches!(path, IdentityPath::Xhttp); tokio::time::timeout(std::time::Duration::from_secs(10), async { for nested in [false, true] { for (hop, sni, expected, wire_sni) in [ @@ -261,7 +279,7 @@ mod tests { "localhost:443", "", "localhost", - if nested { None } else { Some("localhost") }, + if nested || xhttp { None } else { Some("localhost") }, ), ("localhost:443", "sni_hostname: null", "localhost", None), ("127.0.0.1:443", "", "127.0.0.1", None), @@ -322,12 +340,22 @@ mod tests { yaml }; let handler = crate::rustls_config_util::with_test_roots(roots, || { - create_tcp_client_handler( - serde_yaml::from_str(&yaml).unwrap(), - &hop, - Arc::new(crate::resolver::NativeResolver::new()), - ) - .unwrap() + let protocol: crate::config::ClientProxyConfig = + serde_yaml::from_str(&yaml).unwrap(); + let resolver = Arc::new(crate::resolver::NativeResolver::new()); + if xhttp { + let config = serde_json::from_value(serde_json::json!({ + "address": hop.to_string(), + "protocol": {"type": "xhttp", "protocol": protocol} + })) + .unwrap(); + crate::xhttp::connector::build(&config, resolver) + .unwrap() + .unwrap() + .1 + } else { + create_tcp_client_handler(protocol, &hop, resolver).unwrap() + } }); let (io, peer) = tokio::io::duplex(65536); let server_task = tokio::spawn(async move { diff --git a/src/vless/vision_stream/tests.rs b/src/vless/vision_stream/tests.rs index 80e98a4c..9258de17 100644 --- a/src/vless/vision_stream/tests.rs +++ b/src/vless/vision_stream/tests.rs @@ -699,6 +699,112 @@ async fn vision_udp_client_uses_mux_and_exchanges_arbitrary_datagrams() { .unwrap(); } +#[tokio::test] +async fn vision_udp_small_tls_buffers_keep_the_initial_prefix_in_one_record() { + use crate::async_stream::{ + AsyncFlushMessage, AsyncReadSessionMessage, AsyncShutdownMessageExt, + AsyncWriteSessionMessage, + }; + use crate::tcp::tcp_client_handler_factory::create_tcp_client_handler; + use crate::xudp::message_stream::XudpMessageStream; + use futures::future::poll_fn; + use tokio::io::AsyncReadExt; + + const UUID: [u8; 16] = [0, 0, 0, 0, 0, 0, 0x40, 0, 0x80, 0, 0, 0, 0, 0, 0, 0]; + + tokio::time::timeout(std::time::Duration::from_secs(10), async { + for backend in [Backend::Tls12, Backend::Tls13] { + for size in [8, 1, 16, 20, 21, 22, 64, 1024] { + let hop = NetLocation::from_str("localhost:443", None).unwrap(); + let target = NetLocation::from_str("192.0.2.1:53", None).unwrap(); + let handler = create_tcp_client_handler( + serde_json::from_value(serde_json::json!({ + "type": "tls", "verify": false, "vision": true, + "tls_buffer_size": size, + "protocol": { + "type": "vless", "udp_enabled": true, + "user_id": "00000000-0000-4000-8000-000000000000" + } + })) + .unwrap(), + &hop, + Arc::new(crate::resolver::NativeResolver::new()), + ) + .unwrap(); + let (_, server) = new_connections(backend); + let (left, right) = tokio::io::duplex(65536); + let (client, server) = tokio::join!( + handler.setup_client_udp_bidirectional(Box::new(left), target.clone().into()), + CryptoTlsStream::handshake(right, server, TlsReadMode::PreserveRecords, &[]), + ); + let mut client = client.unwrap(); + let mut server = server.unwrap(); + let mut request = [0; 37]; + server.read_exact(&mut request).await.unwrap(); + assert_eq!(request[0], 0); + assert_eq!(&request[1..17], &UUID); + assert_eq!(&request[17..], b"\x12\x0a\x10xtls-rprx-vision\x03"); + + let payload = b"small-buffer-datagram"; + poll_fn(|cx| Pin::new(&mut *client).poll_write_message(cx, payload)) + .await + .unwrap(); + poll_fn(|cx| Pin::new(&mut *client).poll_flush_message(cx)) + .await + .unwrap(); + + let mut first_record = [0; 8192]; + let n = server.read(&mut first_record).await.unwrap(); + assert!( + n >= 21, + "{backend:?}, buffer {size}: first record has {n} bytes" + ); + assert!(n <= size.max(21)); + assert_eq!(&first_record[..16], &UUID); + assert_eq!(first_record[16], 0); + let vision = + VisionStream::new_xudp_server(server, UUID, &first_record[..n]).unwrap(); + let mut server = XudpMessageStream::new(Box::new(vision)); + + for exchange in 0..2 { + if exchange > 0 { + poll_fn(|cx| Pin::new(&mut *client).poll_write_message(cx, payload)) + .await + .unwrap(); + poll_fn(|cx| Pin::new(&mut *client).poll_flush_message(cx)) + .await + .unwrap(); + } + let mut storage = [0; 1024]; + let mut buf = ReadBuf::new(&mut storage); + let (id, received_target) = + poll_fn(|cx| Pin::new(&mut server).poll_read_session_message(cx, &mut buf)) + .await + .unwrap(); + assert_eq!(received_target, target); + assert_eq!(buf.filled(), payload); + poll_fn(|cx| { + Pin::new(&mut server).poll_write_session_message(cx, id, payload, &target) + }) + .await + .unwrap(); + poll_fn(|cx| Pin::new(&mut server).poll_flush_message(cx)) + .await + .unwrap(); + buf.clear(); + poll_fn(|cx| Pin::new(&mut *client).poll_read_message(cx, &mut buf)) + .await + .unwrap(); + assert_eq!(buf.filled(), payload); + } + server.shutdown_message().await.unwrap(); + } + } + }) + .await + .unwrap(); +} + #[tokio::test] async fn vision_target_auth_fallback_preserves_all_plaintext() { use crate::client_proxy_selector::ClientProxySelector; diff --git a/src/xhttp/connector.rs b/src/xhttp/connector.rs index cc21e038..d3f054d8 100644 --- a/src/xhttp/connector.rs +++ b/src/xhttp/connector.rs @@ -264,9 +264,11 @@ pub(crate) fn build_with_limits( "XHTTP split modes require at least two request lanes", )); } - let inner = crate::tcp::tcp_client_handler_factory::create_tcp_client_handler( + // Inner protocols keep the hop identity for verification, not default wire SNI. + let inner = crate::tcp::tcp_client_handler_factory::create_with_hop_context( *xhttp.protocol.clone(), &config.address, + None, resolver, )?; let client = Client::new( diff --git a/tests/README.md b/tests/README.md index 9e42765d..329de1c6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -108,7 +108,7 @@ selection. The 1.14 releases require authentication padding to fit in their firs read and cannot handle this fixture's 40,000-byte auth padding. Other sing-box peer tests retain their existing pins. -The SS2022 identity tests use Xray 26.10.29, shadowsocks-rust `ssserver` 1.25.0, +The SS2022 identity tests use Xray 26.10.31, shadowsocks-rust `ssserver` 1.25.0, and sing-box 1.14.1 with multi-user Shadowsocks and SagerNet UoT. They cover both AES key sizes, concurrent users, ordered relays, wrong/missing/reordered identity rejection, large bidirectional transfers, half-close, server-first data, diff --git a/tests/XHTTP.md b/tests/XHTTP.md index ecb9f310..29bc121f 100644 --- a/tests/XHTTP.md +++ b/tests/XHTTP.md @@ -3,7 +3,7 @@ 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: `0afec66085a85191e9dd2a4a6646c7c9a94688d1` (26.10.29). +- Xray-core: `a4a3dff5b20c2faae567b742c172c7b95e75c083` (26.10.31). - Mihomo Alpha: `94b13aed49b8dc41973e4bb3f721c5fa2c5c5c1f`. Use Go 1.27.3, `GOMAXPROCS=1`, and `go build -p 1`. Build Xray's `./main` package. diff --git a/tests/cases/optional_peer/vision_udp.rs b/tests/cases/optional_peer/vision_udp.rs index 91117479..f4e0de7b 100644 --- a/tests/cases/optional_peer/vision_udp.rs +++ b/tests/cases/optional_peer/vision_udp.rs @@ -27,7 +27,7 @@ async fn exercise(xray: bool) -> Result<(), Box> { let private = [42; 32]; let public = aws_lc_rs::agreement::PrivateKey::from_private_key(&aws_lc_rs::agreement::X25519, &private) .unwrap().compute_public_key().unwrap(); - for reality in [false, true] { + for (reality, buffer_size) in [(false, None), (false, Some(8)), (true, None)] { let mut ports = PortHelper::new(); let (_, peer_port) = ports.get_localhost_listener_port(); let (_, shoes_port) = ports.get_localhost_listener_port(); @@ -37,7 +37,8 @@ async fn exercise(xray: bool) -> Result<(), Box> { json!({"type": "reality", "public_key": URL_SAFE_NO_PAD.encode(public.as_ref()), "short_id": "0123456789abcdef", "sni_hostname": "test.local", "vision": true, "protocol": inner}) } else { - json!({"type": "tls", "verify": false, "sni_hostname": "test.local", "vision": true, "protocol": inner}) + json!({"type": "tls", "verify": false, "sni_hostname": "test.local", + "tls_buffer_size": buffer_size, "vision": true, "protocol": inner}) }; let mut shoes = json!([{ "address": format!("0.0.0.0:{shoes_port}"), @@ -95,7 +96,7 @@ async fn exercise(xray: bool) -> Result<(), Box> { let mut data = data.clone(); data.extend_from_slice(b"\x16\x03\x03"); assert_eq!(client.recv_packet(Duration::from_secs(5)).await?, data, - "xray={xray}, reality={reality}"); + "xray={xray}, reality={reality}, tls_buffer_size={buffer_size:?}"); } super::tls_hybrid::echo_through_http(http_port, tcp_echo.local_addr().port()).await?; } diff --git a/tests/cases/optional_peer/wireguard_interop.rs b/tests/cases/optional_peer/wireguard_interop.rs index 7b16fcfb..7634878a 100644 --- a/tests/cases/optional_peer/wireguard_interop.rs +++ b/tests/cases/optional_peer/wireguard_interop.rs @@ -29,7 +29,7 @@ fn singbox_server(port: u16) -> Value { } #[tokio::test] -#[ignore = "Requires Xray 26.10.29; set SHOES_TEST_XRAY_BIN"] +#[ignore = "Requires Xray 26.10.31; set SHOES_TEST_XRAY_BIN"] async fn wireguard_xray_both_roles_tcp_udp_ipv4_ipv6() -> io::Result<()> { tokio::time::timeout(Duration::from_secs(90), async { for shoes_is_server in [false, true] { diff --git a/tests/fixtures/vless_encryption/README.md b/tests/fixtures/vless_encryption/README.md index 0c0ac705..913907d7 100644 --- a/tests/fixtures/vless_encryption/README.md +++ b/tests/fixtures/vless_encryption/README.md @@ -38,8 +38,8 @@ GOMAXPROCS=1 go run -p 1 /path/to/shoes/tests/fixtures/vless_encryption/generate ``` The peer suite uses `XRAY_BINARY`, `MIHOMO_BINARY`, `SHOES_TEST_SING_BOX_BIN`, and -optionally `SHOES_TEST_SHOES_BIN`. CI pins Xray 26.10.29 -(`0afec66085a85191e9dd2a4a6646c7c9a94688d1`), including its Vision TLS-buffer +optionally `SHOES_TEST_SHOES_BIN`. CI pins Xray 26.10.31 +(`a4a3dff5b20c2faae567b742c172c7b95e75c083`), including its Vision TLS-buffer compatibility fix. The committed vectors retain their original provenance above. Missing requested peers fail rather than skip. The wire tap distinguishes fresh from resumed handshakes and checks raw DIRECT lengths. Successful transfers plus diff --git a/tests/support/src/xray.rs b/tests/support/src/xray.rs index c456dcfd..56c27240 100644 --- a/tests/support/src/xray.rs +++ b/tests/support/src/xray.rs @@ -11,7 +11,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::process::ProcessGuard; -pub const VERSION: &str = "26.10.29"; +pub const VERSION: &str = "26.10.31"; pub struct Peer { _process: ProcessGuard,