use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
use tokio::sync::Notify;
use tokio::time::timeout;
const IMMEDIATE: Duration = Duration::from_millis(50);
const NOT_YET: Duration = Duration::from_millis(50);
#[tokio::test]
async fn defect_socks5_success_reply_arrives_before_route() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut header = [0u8; 2];
stream.read_exact(&mut header).await.unwrap();
let nmethods = header[1] as usize;
let mut methods = vec![0u8; nmethods];
stream.read_exact(&mut methods).await.unwrap();
stream.write_all(&[0x05, 0x00]).await.unwrap();
stream.flush().await.unwrap();
let mut req = [0u8; 4];
stream.read_exact(&mut req).await.unwrap();
assert_eq!(req[0], 0x05);
assert_eq!(req[1], 0x01);
match req[3] {
0x01 => {
let mut buf = [0u8; 6];
stream.read_exact(&mut buf).await.unwrap();
}
0x03 => {
let mut len = [0u8; 1];
stream.read_exact(&mut len).await.unwrap();
let mut domain = vec![0u8; len[0] as usize];
stream.read_exact(&mut domain).await.unwrap();
let mut port = [0u8; 2];
stream.read_exact(&mut port).await.unwrap();
}
0x04 => {
let mut buf = [0u8; 18];
stream.read_exact(&mut buf).await.unwrap();
}
_ => panic!("unexpected atyp"),
}
let reply: [u8; 10] = [
0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
stream.write_all(&reply).await.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
let mut method_resp = [0u8; 2];
client.read_exact(&mut method_resp).await.unwrap();
assert_eq!(method_resp, [0x05, 0x00]);
client
.write_all(&[0x05, 0x01, 0x00, 0x01, 198, 51, 100, 1])
.await
.unwrap();
client.write_all(&443u16.to_be_bytes()).await.unwrap();
let mut reply_buf = [0u8; 10];
let result = timeout(IMMEDIATE, client.read_exact(&mut reply_buf)).await;
assert!(
result.is_ok(),
"SOCKS5 success reply is immediately available — defect: reply sent before route"
);
let inner = result.unwrap();
assert!(inner.is_ok());
assert_eq!(reply_buf[0], 0x05, "version must be 5");
assert_eq!(reply_buf[1], 0x00, "REP must be 0x00 (success)");
server.await.unwrap();
}
#[tokio::test]
async fn defect_socks4_granted_reply_arrives_before_route() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut header = [0u8; 8];
stream.read_exact(&mut header).await.unwrap();
assert_eq!(header[0], 0x04); assert_eq!(header[1], 0x01);
loop {
let mut byte = [0u8; 1];
stream.read_exact(&mut byte).await.unwrap();
if byte[0] == 0x00 {
break;
}
}
let reply: [u8; 8] = [
0x00, 90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
stream.write_all(&reply).await.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client
.write_all(&[0x04, 0x01, 0x00, 80, 10, 0, 0, 1])
.await
.unwrap();
client.write_all(b"testuser").await.unwrap();
client.write_all(&[0x00]).await.unwrap();
let mut reply_buf = [0u8; 8];
let result = timeout(IMMEDIATE, client.read_exact(&mut reply_buf)).await;
assert!(
result.is_ok(),
"SOCKS4 granted reply is immediately available — defect: reply sent before route"
);
let inner = result.unwrap();
assert!(inner.is_ok());
assert_eq!(reply_buf[0], 0x00, "VN must be 0x00 for reply");
assert_eq!(reply_buf[1], 90, "CD must be 90 (granted)");
server.await.unwrap();
}
#[tokio::test]
async fn defect_http_connect_200_reply_arrives_before_route() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut head = Vec::with_capacity(1024);
loop {
let mut byte = [0u8; 1];
stream.read_exact(&mut byte).await.unwrap();
head.push(byte[0]);
if head.len() >= 4 && head[head.len() - 4..] == *b"\r\n\r\n" {
break;
}
}
let head_str = String::from_utf8_lossy(&head);
assert!(head_str.starts_with("CONNECT "));
stream
.write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
.await
.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client
.write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
.await
.unwrap();
let mut resp_buf = [0u8; 4096];
let result = timeout(IMMEDIATE, client.read(&mut resp_buf)).await;
assert!(
result.is_ok(),
"HTTP 200 reply is immediately available — defect: reply sent before route"
);
let n = result.unwrap().unwrap();
let resp = String::from_utf8_lossy(&resp_buf[..n]);
assert!(
resp.starts_with("HTTP/1.1 200"),
"expected HTTP 200, got: {}",
&resp[..resp.len().min(40)]
);
server.await.unwrap();
}
#[tokio::test]
async fn defect_socks5_no_failure_reply_when_route_fails() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut header = [0u8; 2];
stream.read_exact(&mut header).await.unwrap();
let nmethods = header[1] as usize;
let mut methods = vec![0u8; nmethods];
stream.read_exact(&mut methods).await.unwrap();
stream.write_all(&[0x05, 0x00]).await.unwrap();
stream.flush().await.unwrap();
let mut req = [0u8; 4];
stream.read_exact(&mut req).await.unwrap();
match req[3] {
0x01 => {
let mut buf = [0u8; 6];
stream.read_exact(&mut buf).await.unwrap();
}
0x03 => {
let mut len = [0u8; 1];
stream.read_exact(&mut len).await.unwrap();
let mut domain = vec![0u8; len[0] as usize];
stream.read_exact(&mut domain).await.unwrap();
let mut port = [0u8; 2];
stream.read_exact(&mut port).await.unwrap();
}
_ => panic!("unexpected atyp"),
}
drop(stream);
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
let mut method_resp = [0u8; 2];
client.read_exact(&mut method_resp).await.unwrap();
client
.write_all(&[0x05, 0x01, 0x00, 0x01, 198, 51, 100, 1])
.await
.unwrap();
client.write_all(&443u16.to_be_bytes()).await.unwrap();
let mut reply_buf = [0u8; 10];
let result = timeout(
Duration::from_millis(200),
client.read_exact(&mut reply_buf),
)
.await;
assert!(
result.is_err() || result.unwrap().is_err(),
"client sees connection close with no failure reply — missing error handling"
);
server.await.unwrap();
}
#[tokio::test]
async fn correct_socks5_reply_after_route_ready() {
let route_ready = Arc::new(Notify::new());
let route_ready_clone = route_ready.clone();
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut header = [0u8; 2];
stream.read_exact(&mut header).await.unwrap();
let nmethods = header[1] as usize;
let mut methods = vec![0u8; nmethods];
stream.read_exact(&mut methods).await.unwrap();
stream.write_all(&[0x05, 0x00]).await.unwrap();
stream.flush().await.unwrap();
let mut req = [0u8; 4];
stream.read_exact(&mut req).await.unwrap();
match req[3] {
0x01 => {
let mut buf = [0u8; 6];
stream.read_exact(&mut buf).await.unwrap();
}
0x03 => {
let mut len = [0u8; 1];
stream.read_exact(&mut len).await.unwrap();
let mut domain = vec![0u8; len[0] as usize];
stream.read_exact(&mut domain).await.unwrap();
let mut port = [0u8; 2];
stream.read_exact(&mut port).await.unwrap();
}
_ => panic!("unexpected atyp"),
}
tokio::time::timeout(Duration::from_secs(5), route_ready_clone.notified())
.await
.expect("route_ready signal should arrive");
let reply: [u8; 10] = [0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&reply).await.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client.write_all(&[0x05, 0x01, 0x00]).await.unwrap();
let mut method_resp = [0u8; 2];
client.read_exact(&mut method_resp).await.unwrap();
client
.write_all(&[0x05, 0x01, 0x00, 0x01, 198, 51, 100, 1])
.await
.unwrap();
client.write_all(&443u16.to_be_bytes()).await.unwrap();
let mut reply_buf = [0u8; 10];
let result = timeout(NOT_YET, client.read_exact(&mut reply_buf)).await;
assert!(
result.is_err(),
"reply should not be available before route is established"
);
route_ready.notify_one();
let result = timeout(Duration::from_secs(5), client.read_exact(&mut reply_buf)).await;
result
.expect("timed out waiting for reply")
.expect("read failed");
assert_eq!(reply_buf[0], 0x05);
assert_eq!(reply_buf[1], 0x00);
server.await.unwrap();
}
#[tokio::test]
async fn correct_socks4_reply_after_route_ready() {
let route_ready = Arc::new(Notify::new());
let route_ready_clone = route_ready.clone();
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut header = [0u8; 8];
stream.read_exact(&mut header).await.unwrap();
loop {
let mut byte = [0u8; 1];
stream.read_exact(&mut byte).await.unwrap();
if byte[0] == 0x00 {
break;
}
}
tokio::time::timeout(Duration::from_secs(5), route_ready_clone.notified())
.await
.expect("route_ready signal should arrive");
let reply: [u8; 8] = [0x00, 90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream.write_all(&reply).await.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client
.write_all(&[0x04, 0x01, 0x00, 80, 10, 0, 0, 1])
.await
.unwrap();
client.write_all(b"testuser").await.unwrap();
client.write_all(&[0x00]).await.unwrap();
let mut reply_buf = [0u8; 8];
let result = timeout(NOT_YET, client.read_exact(&mut reply_buf)).await;
assert!(
result.is_err(),
"reply should not be available before route is established"
);
route_ready.notify_one();
let result = timeout(Duration::from_secs(5), client.read_exact(&mut reply_buf)).await;
result
.expect("timed out waiting for reply")
.expect("read failed");
assert_eq!(reply_buf[0], 0x00);
assert_eq!(reply_buf[1], 90);
server.await.unwrap();
}
#[tokio::test]
async fn correct_http_connect_reply_after_route_ready() {
let route_ready = Arc::new(Notify::new());
let route_ready_clone = route_ready.clone();
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let mut head = Vec::with_capacity(1024);
loop {
let mut byte = [0u8; 1];
stream.read_exact(&mut byte).await.unwrap();
head.push(byte[0]);
if head.len() >= 4 && head[head.len() - 4..] == *b"\r\n\r\n" {
break;
}
}
tokio::time::timeout(Duration::from_secs(5), route_ready_clone.notified())
.await
.expect("route_ready signal should arrive");
stream
.write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
.await
.unwrap();
stream.flush().await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
client
.write_all(b"CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n")
.await
.unwrap();
let mut resp_buf = [0u8; 4096];
let result = timeout(NOT_YET, client.read(&mut resp_buf)).await;
assert!(
result.is_err(),
"reply should not be available before route is established"
);
route_ready.notify_one();
let result = timeout(Duration::from_secs(5), client.read(&mut resp_buf)).await;
let n = result.expect("timed out").expect("read failed");
let resp = String::from_utf8_lossy(&resp_buf[..n]);
assert!(resp.starts_with("HTTP/1.1 200"));
server.await.unwrap();
}