mirror of
https://github.com/cfal/shoes.git
synced 2026-11-06 08:14:11 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -326,6 +326,11 @@ impl<IO: AsyncStream> CryptoTlsStream<IO> {
|
||||
|
||||
pub fn poll_drain_tls(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
+38
-10
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
@@ -27,7 +27,7 @@ async fn exercise(xray: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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?;
|
||||
}
|
||||
|
||||
@@ -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] {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user