Files
wgcloak/tests/direct_udp.rs
T

296 lines
11 KiB
Rust

#[path = "../src/config.rs"]
mod config;
#[path = "../src/datagram.rs"]
mod datagram;
#[path = "../src/protocol/mod.rs"]
mod protocol;
#[path = "../src/stats.rs"]
mod stats;
#[path = "../src/transport/mod.rs"]
mod transport;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use base64::Engine;
use bytes::Bytes;
use config::Config;
use tokio::{net::UdpSocket, time::timeout};
use transport::{
direct_udp::{DirectUdpServer, DirectUdpSession},
TransportError, TransportSession,
};
const SECRET: &str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
fn configs(port: u16, backend: u16) -> (config::ServerConfig, config::ClientConfig) {
let server = Config::from_str(&format!(
"[server]\nlisten = \"127.0.0.1\"\n[backend]\ntarget = \"127.0.0.1:{backend}\"\n[transports.direct_udp]\nport = {port}\n[[auth.clients]]\nid = \"test\"\nsecret = \"{SECRET}\"\n"
)).unwrap().into_server().unwrap();
let client = Config::from_str(&format!(
"[client]\nserver = \"127.0.0.1\"\nid = \"test\"\nsecret = \"{SECRET}\"\n[transport.direct_udp]\nport = {port}\n[network]\nmax_datagram_size = 1200\n"
)).unwrap().into_client().unwrap();
(server, client)
}
fn multi_configs(
port: u16,
backend: u16,
) -> (
config::ServerConfig,
config::ClientConfig,
config::ClientConfig,
) {
let server = Config::from_str(&format!(
"[server]\nlisten = \"127.0.0.1\"\n[backend]\ntarget = \"127.0.0.1:{backend}\"\n[transports.direct_udp]\nport = {port}\n[[auth.clients]]\nid = \"one\"\nsecret = \"{SECRET}\"\n[[auth.clients]]\nid = \"two\"\nsecret = \"{SECRET}\"\n"
)).unwrap().into_server().unwrap();
let client = |id: &str| {
Config::from_str(&format!(
"[client]\nserver = \"127.0.0.1\"\nid = \"{id}\"\nsecret = \"{SECRET}\"\n[transport.direct_udp]\nport = {port}\n[network]\nmax_datagram_size = 1200\n"
)).unwrap().into_client().unwrap()
};
(server, client("one"), client("two"))
}
#[tokio::test]
async fn direct_udp_round_trip_and_auth_rejection() {
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let backend_addr = backend.local_addr().unwrap();
let port = 45781;
let (server_config, client_config) = configs(port, backend_addr.port());
let server = DirectUdpServer::bind(server_config).await.unwrap();
let stats = server.stats();
let server_task = tokio::spawn(server.run());
let backend_task = tokio::spawn(async move {
let mut buf = [0u8; 2048];
let (len, peer) = backend.recv_from(&mut buf).await.unwrap();
backend.send_to(&buf[..len], peer).await.unwrap();
});
let session = DirectUdpSession::connect(&client_config).await.unwrap();
session
.send_datagram(Bytes::from_static(b"opaque"))
.await
.unwrap();
assert_eq!(
timeout(Duration::from_secs(2), session.recv_datagram())
.await
.unwrap()
.unwrap(),
Bytes::from_static(b"opaque")
);
let mut wrong = client_config.clone();
wrong.client.secret.secret =
Some("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_owned());
let result = DirectUdpSession::connect(&wrong).await;
assert!(matches!(
result,
Err(TransportError::AuthenticationRejected(_))
));
session.close().await.unwrap();
backend_task.await.unwrap();
tokio::time::sleep(Duration::from_millis(20)).await;
assert_eq!(stats.snapshot().expired_sessions, 0);
server_task.abort();
}
#[tokio::test]
async fn direct_udp_enforces_payload_limit_and_expires_idle_session() {
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let backend_addr = backend.local_addr().unwrap();
let (mut server_config, client_config) = configs(45782, backend_addr.port());
server_config.session.idle_timeout_secs = 1;
let server = DirectUdpServer::bind(server_config).await.unwrap();
let stats = server.stats();
let server_task = tokio::spawn(server.run());
let session = DirectUdpSession::connect(&client_config).await.unwrap();
assert!(matches!(
session.send_datagram(Bytes::from(vec![0u8; 1201])).await,
Err(TransportError::Oversized { maximum: 1200 })
));
tokio::time::sleep(Duration::from_secs(3)).await;
assert_eq!(stats.snapshot().expired_sessions, 1);
session.close().await.unwrap();
server_task.abort();
}
#[tokio::test]
async fn concurrent_clients_use_independent_backend_sockets() {
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let backend_addr = backend.local_addr().unwrap();
let (server_config, mut client_one, mut client_two) = multi_configs(45783, backend_addr.port());
let server = DirectUdpServer::bind(server_config).await.unwrap();
let stats = server.stats();
let server_task = tokio::spawn(server.run());
client_one.transport.direct_udp.port = 45783;
client_two.transport.direct_udp.port = 45783;
let backend_task = tokio::spawn(async move {
let mut peers = Vec::new();
let mut buf = [0u8; 2048];
for _ in 0..2 {
let (len, peer) = backend.recv_from(&mut buf).await.unwrap();
peers.push(peer);
backend.send_to(&buf[..len], peer).await.unwrap();
}
peers
});
let first = DirectUdpSession::connect(&client_one).await.unwrap();
let second = DirectUdpSession::connect(&client_two).await.unwrap();
assert!(stats.snapshot().handshake_attempts >= 2);
first
.send_datagram(Bytes::from_static(b"first"))
.await
.unwrap();
second
.send_datagram(Bytes::from_static(b"second"))
.await
.unwrap();
assert_eq!(
timeout(Duration::from_secs(2), first.recv_datagram())
.await
.unwrap()
.unwrap(),
Bytes::from_static(b"first")
);
assert_eq!(
timeout(Duration::from_secs(2), second.recv_datagram())
.await
.unwrap()
.unwrap(),
Bytes::from_static(b"second")
);
let peers = backend_task.await.unwrap();
assert_ne!(
peers[0], peers[1],
"each authenticated client must have its own backend socket"
);
first.close().await.unwrap();
second.close().await.unwrap();
server_task.abort();
}
#[tokio::test]
async fn malformed_and_unauthenticated_packets_do_not_kill_server() {
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let backend_addr = backend.local_addr().unwrap();
let (server_config, client_config) = configs(45784, backend_addr.port());
let server = DirectUdpServer::bind(server_config).await.unwrap();
let stats = server.stats();
let server_task = tokio::spawn(server.run());
let raw = UdpSocket::bind("127.0.0.1:0").await.unwrap();
raw.send_to(&[1, 2, 3], "127.0.0.1:45784").await.unwrap();
raw.send_to(&[0u8; 64], "127.0.0.1:45784").await.unwrap();
let mut wrong = client_config.clone();
wrong.client.secret.secret = Some("B".repeat(64));
assert!(matches!(
DirectUdpSession::connect(&wrong).await,
Err(TransportError::AuthenticationRejected(_))
));
let backend_task = tokio::spawn(async move {
let mut buf = [0u8; 2048];
let (len, peer) = backend.recv_from(&mut buf).await.unwrap();
backend.send_to(&buf[..len], peer).await.unwrap();
});
let session = DirectUdpSession::connect(&client_config).await.unwrap();
session
.send_datagram(Bytes::from_static(b"still-alive"))
.await
.unwrap();
assert_eq!(
timeout(Duration::from_secs(2), session.recv_datagram())
.await
.unwrap()
.unwrap(),
Bytes::from_static(b"still-alive")
);
assert!(stats.snapshot().datagrams_dropped >= 3);
session.close().await.unwrap();
backend_task.await.unwrap();
server_task.abort();
}
#[tokio::test]
async fn authenticated_source_port_change_updates_return_peer() {
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let backend_addr = backend.local_addr().unwrap();
let (server_config, _) = configs(45785, backend_addr.port());
let server = DirectUdpServer::bind(server_config).await.unwrap();
let server_task = tokio::spawn(server.run());
let first = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let second = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let secret = base64::engine::general_purpose::STANDARD
.decode(SECRET)
.unwrap();
let mut nonce = [0u8; 16];
nonce[0] = 1;
let hello = protocol::HelloPacket::authenticated(
&secret,
Bytes::from_static(b"test"),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
nonce,
)
.unwrap();
first
.send_to(&hello.encode().unwrap(), "127.0.0.1:45785")
.await
.unwrap();
let mut wire = [0u8; 2048];
let (len, _) = timeout(Duration::from_secs(2), first.recv_from(&mut wire))
.await
.unwrap()
.unwrap();
let accept = match protocol::parse_packet(&wire[..len]).unwrap() {
protocol::Packet::Accept(packet) => packet,
other => panic!("unexpected handshake response: {other:?}"),
};
let key =
protocol::derive_session_key(&secret, &nonce, &accept.server_nonce, accept.session_id);
let data = protocol::DataPacket::authenticated(
&key,
accept.session_id,
1,
Bytes::from_static(b"rebound"),
);
second
.send_to(&data.encode().unwrap(), "127.0.0.1:45785")
.await
.unwrap();
let backend_task = tokio::spawn(async move {
let mut buf = [0u8; 2048];
let (len, peer) = backend.recv_from(&mut buf).await.unwrap();
backend.send_to(&buf[..len], peer).await.unwrap();
});
let (len, _) = timeout(Duration::from_secs(2), second.recv_from(&mut wire))
.await
.unwrap()
.unwrap();
match protocol::parse_packet(&wire[..len]).unwrap() {
protocol::Packet::Data(packet) => {
assert_eq!(packet.payload, Bytes::from_static(b"rebound"))
}
other => panic!("unexpected rebound response: {other:?}"),
}
backend_task.await.unwrap();
server_task.abort();
}
#[tokio::test]
async fn unavailable_backend_does_not_terminate_listener() {
let (server_config, client_config) = configs(45786, 9);
let server = DirectUdpServer::bind(server_config).await.unwrap();
let server_task = tokio::spawn(server.run());
let session = DirectUdpSession::connect(&client_config).await.unwrap();
session
.send_datagram(Bytes::from_static(b"backend-may-be-down"))
.await
.unwrap();
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(
!server_task.is_finished(),
"backend failure must not terminate the public listener"
);
session.close().await.unwrap();
server_task.abort();
}