checkpoint: integrate authenticated transports
This commit is contained in:
1 parent
313ab00765
commit
586ff8f223
42 files changed
+6277
-771
No files matched your search
@@ -0,0 +1,161 @@
|
||||
use std::{path::Path, time::Duration};
|
||||
|
||||
use tempfile::tempdir;
|
||||
use tokio::{
|
||||
net::UdpSocket,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
use wgcloak::{
|
||||
client,
|
||||
config::{self, Config},
|
||||
control, server,
|
||||
};
|
||||
|
||||
const SECRET: &str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
|
||||
fn free_udp_port() -> u16 {
|
||||
let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
socket.local_addr().unwrap().port()
|
||||
}
|
||||
|
||||
fn server_config(port: u16, backend: std::net::SocketAddr) -> config::ServerConfig {
|
||||
Config::from_str(&format!(
|
||||
"[server]\nlisten='127.0.0.1'\n[backend]\ntarget='{backend}'\n[transports.direct_udp]\nenabled=true\nport={port}\n[transports.quic]\nenabled=false\n[transports.http3]\nenabled=false\n[transports.tls_tcp]\nenabled=false\n[[auth.clients]]\nid='reconnect-test'\nsecret='{SECRET}'\n"
|
||||
))
|
||||
.unwrap()
|
||||
.into_server()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn client_config(port: u16, local_port: u16) -> config::ClientConfig {
|
||||
Config::from_str(&format!(
|
||||
"[client]\nserver='127.0.0.1'\nid='reconnect-test'\nsecret='{SECRET}'\n[local]\nlisten='127.0.0.1:{local_port}'\n[transport]\nmode='direct_udp'\n[transport.direct_udp]\nport={port}\n[transport.auto]\nprobe_timeout_ms=150\nretries=1\n"
|
||||
))
|
||||
.unwrap()
|
||||
.into_client()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn wait_for_contains(path: &Path, command: &str, needle: &str) -> String {
|
||||
timeout(Duration::from_secs(10), async {
|
||||
loop {
|
||||
if let Ok(response) = control::send_command(path, command).await {
|
||||
if response.contains(needle) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("control {command} did not contain {needle:?}"))
|
||||
}
|
||||
|
||||
async fn wait_for_reconnected_stats(path: &Path) -> String {
|
||||
timeout(Duration::from_secs(10), async {
|
||||
loop {
|
||||
if let Ok(response) = control::send_command(path, "stats").await {
|
||||
if response.contains("\"state\":\"connected\"")
|
||||
&& response.contains("\"reconnections\":1")
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("client did not publish its connected reconnect state")
|
||||
}
|
||||
|
||||
async fn wait_for_echo(app: &UdpSocket, local_port: u16, payload: &[u8]) {
|
||||
let mut response = [0u8; 128];
|
||||
app.send_to(payload, ("127.0.0.1", local_port))
|
||||
.await
|
||||
.unwrap();
|
||||
let (len, _) = timeout(Duration::from_secs(5), app.recv_from(&mut response))
|
||||
.await
|
||||
.expect("application echo timed out")
|
||||
.unwrap();
|
||||
assert_eq!(&response[..len], payload);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_recovers_through_backoff_after_server_restart() {
|
||||
let directory = tempdir().unwrap();
|
||||
let backend = UdpSocket::bind("127.0.0.1:0").await.unwrap();
|
||||
let backend_addr = backend.local_addr().unwrap();
|
||||
let backend_task = tokio::spawn(async move {
|
||||
let mut packet = vec![0u8; 2048];
|
||||
while let Ok((len, peer)) = backend.recv_from(&mut packet).await {
|
||||
let _ = backend.send_to(&packet[..len], peer).await;
|
||||
}
|
||||
});
|
||||
|
||||
let port = free_udp_port();
|
||||
let local_port = free_udp_port();
|
||||
let runtime_dir = directory.path().join("runtime");
|
||||
let server_control = runtime_dir.join("server.sock");
|
||||
let client_control = runtime_dir.join("client.sock");
|
||||
let server_task = tokio::spawn(server::run_with_control_path(
|
||||
server_config(port, backend_addr),
|
||||
server_control.clone(),
|
||||
));
|
||||
wait_for_contains(&server_control, "status", "transports=direct_udp").await;
|
||||
|
||||
let client_task = tokio::spawn(client::run_with_http3_connector_and_control_path(
|
||||
client_config(port, local_port),
|
||||
client_control.clone(),
|
||||
None,
|
||||
));
|
||||
wait_for_contains(&client_control, "status", "connected; server=127.0.0.1").await;
|
||||
let app = UdpSocket::bind("127.0.0.1:0").await.unwrap();
|
||||
wait_for_echo(&app, local_port, b"before-restart").await;
|
||||
|
||||
let stopped = control::send_command(&server_control, "shutdown")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(stopped.contains("\"ok\":true"));
|
||||
timeout(Duration::from_secs(5), server_task)
|
||||
.await
|
||||
.expect("first server instance should stop")
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let requested = control::send_command(&client_control, "reconnect")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(requested.contains("\"ok\":true"));
|
||||
// Wait until the client has made a failed connection attempt and entered
|
||||
// jittered backoff before starting the replacement server. This exercises
|
||||
// the daemon loop without relying on a guessed sleep duration.
|
||||
wait_for_contains(&client_control, "status", "error=").await;
|
||||
|
||||
let replacement = tokio::spawn(server::run_with_control_path(
|
||||
server_config(port, backend_addr),
|
||||
server_control.clone(),
|
||||
));
|
||||
wait_for_contains(&server_control, "status", "transports=direct_udp").await;
|
||||
let _stats = wait_for_reconnected_stats(&client_control).await;
|
||||
wait_for_echo(&app, local_port, b"after-restart").await;
|
||||
|
||||
let stopped = control::send_command(&server_control, "shutdown")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(stopped.contains("\"ok\":true"));
|
||||
timeout(Duration::from_secs(5), replacement)
|
||||
.await
|
||||
.expect("replacement server should stop")
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let stopped = control::send_command(&client_control, "shutdown")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(stopped.contains("\"ok\":true"));
|
||||
timeout(Duration::from_secs(5), client_task)
|
||||
.await
|
||||
.expect("client should stop")
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
backend_task.abort();
|
||||
}
|
||||
Reference in new issue
Block a user