mod common;
use std::collections::HashSet;
use std::str::FromStr;
use std::time::Duration;
use common::{
get_available_port, get_available_udp_port, socks5_connect_ipv4, start_tunnel, TEST_TIMEOUT,
};
use rusnel::common::remote::RemoteRequest;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream, UdpSocket};
use tokio::time::timeout;
const NUM_CONNS: usize = 10;
#[tokio::test]
async fn test_tcp_forward_many_concurrent_connections() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let local_port = get_available_port();
let remote_port = get_available_port();
let target_listener = TcpListener::bind(format!("127.0.0.1:{remote_port}"))
.await
.unwrap();
let remote =
RemoteRequest::from_str(&format!("127.0.0.1:{local_port}:127.0.0.1:{remote_port}"))
.unwrap();
let _env = start_tunnel(server_port, false, vec![remote]).await;
let acceptor = tokio::spawn(async move {
let mut accept_handles = Vec::with_capacity(NUM_CONNS);
for _ in 0..NUM_CONNS {
let (stream, _) = target_listener.accept().await.unwrap();
accept_handles.push(tokio::spawn(async move {
let (mut r, mut w) = stream.into_split();
let mut buf = vec![0u8; 4096];
loop {
let n = r.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
w.write_all(&buf[..n]).await.unwrap();
}
w.shutdown().await.unwrap();
}));
}
for h in accept_handles {
h.await.unwrap();
}
});
let mut client_handles = Vec::with_capacity(NUM_CONNS);
for i in 0..NUM_CONNS {
let local_port = local_port;
client_handles.push(tokio::spawn(async move {
let payload = format!("conn-{i}-payload-XXXXXXX-{i:04}").into_bytes();
let mut conn = TcpStream::connect(format!("127.0.0.1:{local_port}"))
.await
.unwrap();
let (mut r, mut w) = conn.split();
let payload_clone = payload.clone();
let writer = async move {
w.write_all(&payload_clone).await.unwrap();
w.shutdown().await.unwrap();
};
let expected = payload.clone();
let reader = async move {
let mut got = Vec::new();
r.read_to_end(&mut got).await.unwrap();
assert_eq!(got, expected, "echo mismatch on connection {i}");
};
tokio::join!(writer, reader);
}));
}
for h in client_handles {
h.await.unwrap();
}
acceptor.await.unwrap();
})
.await
.expect("test_tcp_forward_many_concurrent_connections timed out");
}
#[tokio::test]
async fn test_tcp_reverse_many_concurrent_connections() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let listen_port = get_available_port();
let target_port = get_available_port();
let target_listener = TcpListener::bind(format!("127.0.0.1:{target_port}"))
.await
.unwrap();
let remote = RemoteRequest::from_str(&format!(
"R:127.0.0.1:{listen_port}:127.0.0.1:{target_port}"
))
.unwrap();
let _env = start_tunnel(server_port, true, vec![remote]).await;
let acceptor = tokio::spawn(async move {
let mut accept_handles = Vec::with_capacity(NUM_CONNS);
for _ in 0..NUM_CONNS {
let (stream, _) = target_listener.accept().await.unwrap();
accept_handles.push(tokio::spawn(async move {
let (mut r, mut w) = stream.into_split();
let mut buf = vec![0u8; 4096];
loop {
let n = r.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
w.write_all(&buf[..n]).await.unwrap();
}
w.shutdown().await.unwrap();
}));
}
for h in accept_handles {
h.await.unwrap();
}
});
let mut client_handles = Vec::with_capacity(NUM_CONNS);
for i in 0..NUM_CONNS {
client_handles.push(tokio::spawn(async move {
let payload = format!("rev-conn-{i}-data-{i:08}").into_bytes();
let mut conn = TcpStream::connect(format!("127.0.0.1:{listen_port}"))
.await
.unwrap();
let (mut r, mut w) = conn.split();
let payload_clone = payload.clone();
let writer = async move {
w.write_all(&payload_clone).await.unwrap();
w.shutdown().await.unwrap();
};
let expected = payload.clone();
let reader = async move {
let mut got = Vec::new();
r.read_to_end(&mut got).await.unwrap();
assert_eq!(got, expected, "reverse echo mismatch on connection {i}");
};
tokio::join!(writer, reader);
}));
}
for h in client_handles {
h.await.unwrap();
}
acceptor.await.unwrap();
})
.await
.expect("test_tcp_reverse_many_concurrent_connections timed out");
}
#[tokio::test]
async fn test_socks5_many_concurrent_targets() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let socks_port = get_available_port();
let remote = RemoteRequest::from_str(&format!("127.0.0.1:{socks_port}:socks")).unwrap();
let _env = start_tunnel(server_port, false, vec![remote]).await;
let mut target_ports = Vec::with_capacity(NUM_CONNS);
let mut target_tasks = Vec::with_capacity(NUM_CONNS);
for i in 0..NUM_CONNS {
let target_port = get_available_port();
let listener = TcpListener::bind(format!("127.0.0.1:{target_port}"))
.await
.unwrap();
target_ports.push(target_port);
target_tasks.push(tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let (mut r, mut w) = stream.into_split();
let mut buf = vec![0u8; 4096];
let mut all = Vec::new();
loop {
let n = r.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
all.extend_from_slice(&buf[..n]);
w.write_all(&buf[..n]).await.unwrap();
}
w.shutdown().await.unwrap();
(i, all)
}));
}
let mut client_handles = Vec::with_capacity(NUM_CONNS);
for (i, &target_port) in target_ports.iter().enumerate() {
let socks_addr = format!("127.0.0.1:{socks_port}");
client_handles.push(tokio::spawn(async move {
let mut conn = socks5_connect_ipv4(&socks_addr, [127, 0, 0, 1], target_port).await;
let payload = format!("socks-{i}-payload-{i:06}").into_bytes();
let (mut r, mut w) = conn.split();
let payload_clone = payload.clone();
let writer = async move {
w.write_all(&payload_clone).await.unwrap();
w.shutdown().await.unwrap();
};
let expected = payload.clone();
let reader = async move {
let mut got = Vec::new();
r.read_to_end(&mut got).await.unwrap();
assert_eq!(got, expected, "socks echo mismatch on conn {i}");
};
tokio::join!(writer, reader);
}));
}
for h in client_handles {
h.await.unwrap();
}
let mut seen_indices = HashSet::new();
for h in target_tasks {
let (i, all) = h.await.unwrap();
let expected = format!("socks-{i}-payload-{i:06}").into_bytes();
assert_eq!(all, expected, "target {i} received unexpected bytes");
seen_indices.insert(i);
}
assert_eq!(seen_indices.len(), NUM_CONNS);
})
.await
.expect("test_socks5_many_concurrent_targets timed out");
}
#[tokio::test]
async fn test_tcp_forward_sequential_reuse() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let local_port = get_available_port();
let remote_port = get_available_port();
let target_listener = TcpListener::bind(format!("127.0.0.1:{remote_port}"))
.await
.unwrap();
let remote =
RemoteRequest::from_str(&format!("127.0.0.1:{local_port}:127.0.0.1:{remote_port}"))
.unwrap();
let _env = start_tunnel(server_port, false, vec![remote]).await;
let acceptor = tokio::spawn(async move {
for i in 0..5 {
let (mut stream, _) = target_listener.accept().await.unwrap();
let mut buf = vec![0u8; 1024];
let n = stream.read(&mut buf).await.unwrap();
let expected = format!("seq-{i}").into_bytes();
assert_eq!(&buf[..n], &expected[..]);
stream.write_all(&buf[..n]).await.unwrap();
stream.shutdown().await.unwrap();
}
});
for i in 0..5 {
let mut conn = TcpStream::connect(format!("127.0.0.1:{local_port}"))
.await
.unwrap();
let payload = format!("seq-{i}");
conn.write_all(payload.as_bytes()).await.unwrap();
conn.shutdown().await.unwrap();
let mut got = Vec::new();
conn.read_to_end(&mut got).await.unwrap();
assert_eq!(got, payload.as_bytes());
}
acceptor.await.unwrap();
})
.await
.expect("test_tcp_forward_sequential_reuse timed out");
}
#[tokio::test]
async fn test_udp_forward_multiple_senders() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let local_port = get_available_udp_port();
let remote_port = get_available_udp_port();
let target_socket = UdpSocket::bind(format!("127.0.0.1:{remote_port}"))
.await
.unwrap();
let remote = RemoteRequest::from_str(&format!(
"127.0.0.1:{local_port}:127.0.0.1:{remote_port}/udp"
))
.unwrap();
let _env = start_tunnel(server_port, false, vec![remote]).await;
const NUM_SENDERS: usize = 4;
let recv_task = tokio::spawn(async move {
let mut seen = HashSet::new();
let mut buf = vec![0u8; 4096];
while seen.len() < NUM_SENDERS {
match timeout(Duration::from_secs(2), target_socket.recv(&mut buf)).await {
Ok(Ok(n)) => {
let payload = std::str::from_utf8(&buf[..n]).unwrap().to_string();
seen.insert(payload);
}
Ok(Err(e)) => panic!("recv error: {e}"),
Err(_) => break,
}
}
seen
});
let mut sender_handles = Vec::with_capacity(NUM_SENDERS);
for i in 0..NUM_SENDERS {
sender_handles.push(tokio::spawn(async move {
let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let payload = format!("from-sender-{i}");
sock.send_to(payload.as_bytes(), format!("127.0.0.1:{local_port}"))
.await
.unwrap();
}));
}
for h in sender_handles {
h.await.unwrap();
}
let seen = recv_task.await.unwrap();
let mut missing = Vec::new();
for i in 0..NUM_SENDERS {
let expected = format!("from-sender-{i}");
if !seen.contains(&expected) {
missing.push(expected);
}
}
assert!(
missing.is_empty(),
"missing UDP payloads from senders: {missing:?}; seen: {seen:?}"
);
})
.await
.expect("test_udp_forward_multiple_senders timed out");
}