mod common;
use std::io::ErrorKind;
use std::str::FromStr;
use std::time::Duration;
use common::{get_available_port, start_tunnel, TEST_TIMEOUT};
use rusnel::common::remote::RemoteRequest;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::time::timeout;
#[allow(deprecated)]
fn rst_close(stream: TcpStream) {
let _ = stream.set_linger(Some(Duration::ZERO));
drop(stream);
}
#[tokio::test]
async fn test_tcp_forward_target_rst_mid_stream() {
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 mut app = TcpStream::connect(format!("127.0.0.1:{local_port}"))
.await
.unwrap();
let (mut target, _) = target_listener.accept().await.unwrap();
app.write_all(b"hello").await.unwrap();
let mut buf = [0u8; 5];
target.read_exact(&mut buf).await.unwrap();
rst_close(target);
let mut buf = [0u8; 16];
let result = timeout(Duration::from_secs(5), app.read(&mut buf))
.await
.expect("app never observed target RST as connection end");
match result {
Ok(0) => {} Ok(n) => panic!("unexpected {n} trailing bytes after target RST"),
Err(e) => assert!(
matches!(
e.kind(),
ErrorKind::ConnectionReset
| ErrorKind::BrokenPipe
| ErrorKind::UnexpectedEof
| ErrorKind::ConnectionAborted
),
"unexpected error kind {:?}",
e.kind()
),
}
})
.await
.expect("test_tcp_forward_target_rst_mid_stream timed out");
}
#[tokio::test]
async fn test_tcp_forward_app_rst_mid_stream() {
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 mut app = TcpStream::connect(format!("127.0.0.1:{local_port}"))
.await
.unwrap();
let (mut target, _) = target_listener.accept().await.unwrap();
app.write_all(b"abc").await.unwrap();
let mut buf = [0u8; 3];
target.read_exact(&mut buf).await.unwrap();
rst_close(app);
let mut buf = [0u8; 16];
let result = timeout(Duration::from_secs(5), target.read(&mut buf))
.await
.expect("target never observed app RST");
match result {
Ok(0) => {}
Ok(n) => panic!("unexpected {n} trailing bytes after app RST"),
Err(e) => assert!(
matches!(
e.kind(),
ErrorKind::ConnectionReset
| ErrorKind::BrokenPipe
| ErrorKind::UnexpectedEof
| ErrorKind::ConnectionAborted
),
"unexpected error kind {:?}",
e.kind()
),
}
})
.await
.expect("test_tcp_forward_app_rst_mid_stream timed out");
}
#[tokio::test]
async fn test_tcp_reverse_app_rst_mid_stream() {
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 mut app = TcpStream::connect(format!("127.0.0.1:{listen_port}"))
.await
.unwrap();
let (mut target, _) = target_listener.accept().await.unwrap();
app.write_all(b"abc").await.unwrap();
let mut buf = [0u8; 3];
target.read_exact(&mut buf).await.unwrap();
rst_close(app);
let mut buf = [0u8; 16];
let result = timeout(Duration::from_secs(5), target.read(&mut buf))
.await
.expect("target never observed app RST on reverse path");
match result {
Ok(0) => {}
Ok(n) => panic!("unexpected {n} trailing bytes after app RST (reverse)"),
Err(e) => assert!(
matches!(
e.kind(),
ErrorKind::ConnectionReset
| ErrorKind::BrokenPipe
| ErrorKind::UnexpectedEof
| ErrorKind::ConnectionAborted
),
"unexpected error kind {:?}",
e.kind()
),
}
})
.await
.expect("test_tcp_reverse_app_rst_mid_stream timed out");
}
#[tokio::test]
async fn test_tcp_reverse_dead_target_closes_connection() {
timeout(TEST_TIMEOUT, async {
let server_port = get_available_port();
let listen_port = get_available_port();
let dead_target_port = get_available_port();
let remote = RemoteRequest::from_str(&format!(
"R:127.0.0.1:{listen_port}:127.0.0.1:{dead_target_port}"
))
.unwrap();
let _env = start_tunnel(server_port, true, vec![remote]).await;
let mut app = TcpStream::connect(format!("127.0.0.1:{listen_port}"))
.await
.unwrap();
let _ = app.write_all(b"x").await;
let mut buf = [0u8; 16];
let n = timeout(Duration::from_secs(5), app.read(&mut buf))
.await
.expect("client never observed reverse-tunnel teardown for dead target")
.unwrap_or(0);
assert_eq!(
n, 0,
"expected EOF from reverse tunnel pointing at dead target"
);
})
.await
.expect("test_tcp_reverse_dead_target_closes_connection timed out");
}