147 lines
5.1 KiB
Rust
147 lines
5.1 KiB
Rust
use std::{os::unix::fs::PermissionsExt, path::Path};
|
|
use tempfile::tempdir;
|
|
use tokio::time::Duration;
|
|
use tokio::{net::UdpSocket, time::timeout};
|
|
use wgcloak::{
|
|
config::Config,
|
|
control, server,
|
|
transport::{direct_udp::DirectUdpSession, TransportSession},
|
|
};
|
|
|
|
const DIRECT_SECRET: &str = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
|
|
fn private_runtime_dir(root: &Path) -> std::path::PathBuf {
|
|
let path = root.join("runtime");
|
|
std::fs::create_dir(&path).unwrap();
|
|
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700)).unwrap();
|
|
path
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn control_commands_drive_only_supported_runtime_actions() {
|
|
let dir = tempdir().unwrap();
|
|
let path = private_runtime_dir(dir.path()).join("runtime.sock");
|
|
let state = control::ControlState::default();
|
|
state.enable_reconnect();
|
|
let server = control::ControlServer::bind(&path, state.clone())
|
|
.await
|
|
.unwrap();
|
|
let task = tokio::spawn(server.run());
|
|
|
|
state.set_status("connected; transport=direct_udp").await;
|
|
state
|
|
.set_stats_json("{\"datagrams_sent\":7,\"datagrams_dropped\":1}")
|
|
.await;
|
|
let status = control::send_command(&path, "status").await.unwrap();
|
|
assert!(status.contains("connected; transport=direct_udp"));
|
|
let stats = control::send_command(&path, "stats").await.unwrap();
|
|
assert!(stats.contains("\"datagrams_sent\":7"));
|
|
|
|
let reconnect = state.reconnect.notified();
|
|
tokio::pin!(reconnect);
|
|
let response = control::send_command(&path, "reconnect").await.unwrap();
|
|
assert!(response.contains("\"ok\":true"));
|
|
timeout(Duration::from_secs(1), &mut reconnect)
|
|
.await
|
|
.expect("reconnect should reach the running client loop");
|
|
|
|
let response = control::send_command(&path, "reload").await.unwrap();
|
|
assert!(response.contains("\"ok\":false"));
|
|
assert!(response.contains("not supported"));
|
|
|
|
let response = control::send_command(&path, "shutdown").await.unwrap();
|
|
assert!(response.contains("\"ok\":true"));
|
|
timeout(Duration::from_secs(1), task)
|
|
.await
|
|
.expect("control server should stop on shutdown")
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn server_control_rejects_client_only_reconnect() {
|
|
let dir = tempdir().unwrap();
|
|
let path = private_runtime_dir(dir.path()).join("server.sock");
|
|
let state = control::ControlState::default();
|
|
let server = control::ControlServer::bind(&path, state.clone())
|
|
.await
|
|
.unwrap();
|
|
let task = tokio::spawn(server.run());
|
|
|
|
let response = control::send_command(&path, "reconnect").await.unwrap();
|
|
assert!(response.contains("\"ok\":false"));
|
|
assert!(response.contains("not supported"));
|
|
|
|
state.shutdown.cancel();
|
|
timeout(Duration::from_secs(1), task)
|
|
.await
|
|
.expect("control server should stop")
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn direct_server_control_stats_track_authenticated_session_lifecycle() {
|
|
let probe = UdpSocket::bind("127.0.0.1:0").await.unwrap();
|
|
let port = probe.local_addr().unwrap().port();
|
|
drop(probe);
|
|
|
|
let config = Config::from_str(&format!(
|
|
"[server]\nlisten='127.0.0.1'\n[backend]\ntarget='127.0.0.1:9'\n[transports.direct_udp]\nport={port}\n[[auth.clients]]\nid='runtime-test'\nsecret='{DIRECT_SECRET}'\n"
|
|
))
|
|
.unwrap()
|
|
.into_server()
|
|
.unwrap();
|
|
let client_config = Config::from_str(&format!(
|
|
"[client]\nserver='127.0.0.1'\nid='runtime-test'\nsecret='{DIRECT_SECRET}'\n[transport.direct_udp]\nport={port}\n"
|
|
))
|
|
.unwrap()
|
|
.into_client()
|
|
.unwrap();
|
|
|
|
let dir = tempdir().unwrap();
|
|
let control_path = private_runtime_dir(dir.path()).join("direct-runtime.sock");
|
|
let server_task = tokio::spawn(server::run_with_control_path(config, control_path.clone()));
|
|
|
|
let session = timeout(
|
|
Duration::from_secs(3),
|
|
DirectUdpSession::connect(&client_config),
|
|
)
|
|
.await
|
|
.expect("direct handshake should complete")
|
|
.unwrap();
|
|
|
|
wait_for_active_count(&control_path, 1).await;
|
|
let status = control::send_command(&control_path, "status")
|
|
.await
|
|
.unwrap();
|
|
assert!(status.contains("active_sessions=1"));
|
|
session.close().await.unwrap();
|
|
wait_for_active_count(&control_path, 0).await;
|
|
|
|
let response = control::send_command(&control_path, "shutdown")
|
|
.await
|
|
.unwrap();
|
|
assert!(response.contains("\"ok\":true"));
|
|
timeout(Duration::from_secs(3), server_task)
|
|
.await
|
|
.expect("server runtime should stop")
|
|
.unwrap()
|
|
.unwrap();
|
|
}
|
|
|
|
async fn wait_for_active_count(path: &std::path::Path, expected: usize) {
|
|
timeout(Duration::from_secs(4), async {
|
|
loop {
|
|
if let Ok(response) = control::send_command(path, "stats").await {
|
|
if response.contains(&format!("\"active_sessions\":{expected}")) {
|
|
return;
|
|
}
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
}
|
|
})
|
|
.await
|
|
.unwrap_or_else(|_| panic!("server control stats did not report {expected} active sessions"));
|
|
}
|