Files
smartvpn/rust/src/management.rs

591 lines
23 KiB
Rust

use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::sync::Mutex;
use tracing::{info, error, warn};
use crate::client::{ClientConfig, VpnClient};
use crate::crypto;
use crate::server::{ServerConfig, VpnServer};
use crate::wireguard::{self, WgClient, WgClientConfig, WgPeerConfig, WgServer, WgServerConfig};
// ============================================================================
// IPC protocol types
// ============================================================================
#[derive(Debug, Deserialize)]
pub struct ManagementRequest {
pub id: String,
pub method: String,
#[serde(default)]
pub params: serde_json::Value,
}
#[derive(Debug, Serialize)]
pub struct ManagementResponse {
pub id: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ManagementEvent {
pub event: String,
pub data: serde_json::Value,
}
impl ManagementResponse {
fn ok(id: String, result: serde_json::Value) -> Self {
Self {
id,
success: true,
result: Some(result),
error: None,
}
}
fn err(id: String, message: String) -> Self {
Self {
id,
success: false,
result: None,
error: Some(message),
}
}
}
// ============================================================================
// Stdio management mode
// ============================================================================
fn send_line_stdout(line: &str) {
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let _ = handle.write_all(line.as_bytes());
let _ = handle.write_all(b"\n");
let _ = handle.flush();
}
fn send_response_stdout(response: &ManagementResponse) {
match serde_json::to_string(response) {
Ok(json) => send_line_stdout(&json),
Err(e) => error!("Failed to serialize management response: {}", e),
}
}
fn send_event_stdout(event: &str, data: serde_json::Value) {
let evt = ManagementEvent {
event: event.to_string(),
data,
};
match serde_json::to_string(&evt) {
Ok(json) => send_line_stdout(&json),
Err(e) => error!("Failed to serialize management event: {}", e),
}
}
pub async fn management_loop_stdio(mode: &str) -> Result<()> {
let stdin = BufReader::new(tokio::io::stdin());
let mut lines = stdin.lines();
let mut vpn_client = VpnClient::new();
let mut vpn_server = VpnServer::new();
let mut wg_client = WgClient::new();
let mut wg_server = WgServer::new();
send_event_stdout("ready", serde_json::json!({ "mode": mode }));
loop {
let line = match lines.next_line().await {
Ok(Some(line)) => line,
Ok(None) => {
info!("Management stdin closed, shutting down");
break;
}
Err(e) => {
error!("Error reading management stdin: {}", e);
break;
}
};
let line = line.trim().to_string();
if line.is_empty() {
continue;
}
let request: ManagementRequest = match serde_json::from_str(&line) {
Ok(r) => r,
Err(e) => {
error!("Failed to parse management request: {}", e);
send_response_stdout(&ManagementResponse::err(
"unknown".to_string(),
format!("Failed to parse request: {}", e),
));
continue;
}
};
let response = match mode {
"client" => handle_client_request(&request, &mut vpn_client, &mut wg_client).await,
"server" => handle_server_request(&request, &mut vpn_server, &mut wg_server).await,
_ => ManagementResponse::err(request.id.clone(), format!("Unknown mode: {}", mode)),
};
send_response_stdout(&response);
}
Ok(())
}
// ============================================================================
// Socket management mode
// ============================================================================
pub async fn management_loop_socket(socket_path: &str, mode: &str) -> Result<()> {
let _ = tokio::fs::remove_file(socket_path).await;
let listener = tokio::net::UnixListener::bind(socket_path)?;
info!("Management socket listening on {}", socket_path);
// Shared state behind Mutex for socket mode (multiple connections)
let vpn_client = std::sync::Arc::new(Mutex::new(VpnClient::new()));
let vpn_server = std::sync::Arc::new(Mutex::new(VpnServer::new()));
let wg_client = std::sync::Arc::new(Mutex::new(WgClient::new()));
let wg_server = std::sync::Arc::new(Mutex::new(WgServer::new()));
loop {
match listener.accept().await {
Ok((stream, _addr)) => {
let mode = mode.to_string();
let client = vpn_client.clone();
let server = vpn_server.clone();
let wg_c = wg_client.clone();
let wg_s = wg_server.clone();
tokio::spawn(async move {
if let Err(e) =
handle_socket_connection(stream, &mode, client, server, wg_c, wg_s).await
{
warn!("Socket connection error: {}", e);
}
});
}
Err(e) => {
error!("Failed to accept socket connection: {}", e);
}
}
}
}
async fn handle_socket_connection(
stream: tokio::net::UnixStream,
mode: &str,
vpn_client: std::sync::Arc<Mutex<VpnClient>>,
vpn_server: std::sync::Arc<Mutex<VpnServer>>,
wg_client: std::sync::Arc<Mutex<WgClient>>,
wg_server: std::sync::Arc<Mutex<WgServer>>,
) -> Result<()> {
let (reader, mut writer) = stream.into_split();
let buf_reader = BufReader::new(reader);
let mut lines = buf_reader.lines();
let ready_event = ManagementEvent {
event: "ready".to_string(),
data: serde_json::json!({ "mode": mode }),
};
let ready_json = serde_json::to_string(&ready_event)?;
writer.write_all(ready_json.as_bytes()).await?;
writer.write_all(b"\n").await?;
writer.flush().await?;
loop {
let line = match lines.next_line().await {
Ok(Some(line)) => line,
Ok(None) => {
info!("Socket client disconnected");
break;
}
Err(e) => {
error!("Error reading from socket client: {}", e);
break;
}
};
let line = line.trim().to_string();
if line.is_empty() {
continue;
}
let request: ManagementRequest = match serde_json::from_str(&line) {
Ok(r) => r,
Err(e) => {
let resp = ManagementResponse::err(
"unknown".to_string(),
format!("Failed to parse request: {}", e),
);
let json = serde_json::to_string(&resp)?;
writer.write_all(json.as_bytes()).await?;
writer.write_all(b"\n").await?;
writer.flush().await?;
continue;
}
};
let response = match mode {
"client" => {
let mut client = vpn_client.lock().await;
let mut wg_c = wg_client.lock().await;
handle_client_request(&request, &mut client, &mut wg_c).await
}
"server" => {
let mut server = vpn_server.lock().await;
let mut wg_s = wg_server.lock().await;
handle_server_request(&request, &mut server, &mut wg_s).await
}
_ => ManagementResponse::err(request.id.clone(), format!("Unknown mode: {}", mode)),
};
let json = serde_json::to_string(&response)?;
writer.write_all(json.as_bytes()).await?;
writer.write_all(b"\n").await?;
writer.flush().await?;
}
Ok(())
}
// ============================================================================
// Client command handlers
// ============================================================================
async fn handle_client_request(
request: &ManagementRequest,
vpn_client: &mut VpnClient,
wg_client: &mut WgClient,
) -> ManagementResponse {
let id = request.id.clone();
match request.method.as_str() {
"connect" => {
// Check if transport is "wireguard"
let transport = request.params
.get("config")
.and_then(|c| c.get("transport"))
.and_then(|t| t.as_str())
.unwrap_or("");
if transport == "wireguard" {
let config: WgClientConfig = match serde_json::from_value(
request.params.get("config").cloned().unwrap_or_default(),
) {
Ok(c) => c,
Err(e) => {
return ManagementResponse::err(id, format!("Invalid WG config: {}", e));
}
};
match wg_client.connect(config).await {
Ok(assigned_ip) => {
ManagementResponse::ok(id, serde_json::json!({ "assignedIp": assigned_ip }))
}
Err(e) => ManagementResponse::err(id, format!("WG connect failed: {}", e)),
}
} else {
let config: ClientConfig = match serde_json::from_value(
request.params.get("config").cloned().unwrap_or_default(),
) {
Ok(c) => c,
Err(e) => {
return ManagementResponse::err(id, format!("Invalid config: {}", e));
}
};
match vpn_client.connect(config).await {
Ok(assigned_ip) => {
ManagementResponse::ok(id, serde_json::json!({ "assignedIp": assigned_ip }))
}
Err(e) => ManagementResponse::err(id, format!("Connect failed: {}", e)),
}
}
}
"disconnect" => {
if wg_client.is_running() {
match wg_client.disconnect().await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("WG disconnect failed: {}", e)),
}
} else {
match vpn_client.disconnect().await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Disconnect failed: {}", e)),
}
}
}
"getStatus" => {
if wg_client.is_running() {
ManagementResponse::ok(id, wg_client.get_status().await)
} else {
let status = vpn_client.get_status().await;
ManagementResponse::ok(id, status)
}
}
"getStatistics" => {
if wg_client.is_running() {
ManagementResponse::ok(id, wg_client.get_statistics().await)
} else {
let stats = vpn_client.get_statistics().await;
ManagementResponse::ok(id, stats)
}
}
"getConnectionQuality" => {
match vpn_client.get_connection_quality() {
Some(quality) => {
let health = vpn_client.get_link_health().await;
let interval_secs = match health {
crate::keepalive::LinkHealth::Healthy => 60,
crate::keepalive::LinkHealth::Degraded => 30,
crate::keepalive::LinkHealth::Critical => 10,
};
ManagementResponse::ok(id, serde_json::json!({
"srttMs": quality.srtt_ms,
"jitterMs": quality.jitter_ms,
"minRttMs": quality.min_rtt_ms,
"maxRttMs": quality.max_rtt_ms,
"lossRatio": quality.loss_ratio,
"consecutiveTimeouts": quality.consecutive_timeouts,
"linkHealth": format!("{}", health),
"currentKeepaliveIntervalSecs": interval_secs,
}))
}
None => ManagementResponse::ok(id, serde_json::json!(null)),
}
}
"getMtuInfo" => {
ManagementResponse::ok(id, serde_json::json!({
"tunMtu": 1420,
"effectiveMtu": crate::mtu::TunnelOverhead::default_overhead().effective_tun_mtu(1500),
"linkMtu": 1500,
"overheadBytes": crate::mtu::TunnelOverhead::default_overhead().total(),
"oversizedPacketsDropped": 0,
"icmpTooBigSent": 0,
}))
}
_ => ManagementResponse::err(id, format!("Unknown client method: {}", request.method)),
}
}
// ============================================================================
// Server command handlers
// ============================================================================
async fn handle_server_request(
request: &ManagementRequest,
vpn_server: &mut VpnServer,
wg_server: &mut WgServer,
) -> ManagementResponse {
let id = request.id.clone();
match request.method.as_str() {
"start" => {
// Check if transportMode is "wireguard"
let transport_mode = request.params
.get("config")
.and_then(|c| c.get("transportMode"))
.and_then(|t| t.as_str())
.unwrap_or("");
if transport_mode == "wireguard" {
let config: WgServerConfig = match serde_json::from_value(
request.params.get("config").cloned().unwrap_or_default(),
) {
Ok(c) => c,
Err(e) => {
return ManagementResponse::err(id, format!("Invalid WG config: {}", e));
}
};
match wg_server.start(config).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("WG start failed: {}", e)),
}
} else {
let config: ServerConfig = match serde_json::from_value(
request.params.get("config").cloned().unwrap_or_default(),
) {
Ok(c) => c,
Err(e) => {
return ManagementResponse::err(id, format!("Invalid config: {}", e));
}
};
match vpn_server.start(config).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Start failed: {}", e)),
}
}
}
"stop" => {
if wg_server.is_running() {
match wg_server.stop().await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("WG stop failed: {}", e)),
}
} else {
match vpn_server.stop().await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Stop failed: {}", e)),
}
}
}
"getStatus" => {
if wg_server.is_running() {
ManagementResponse::ok(id, wg_server.get_status())
} else {
let status = vpn_server.get_status();
ManagementResponse::ok(id, status)
}
}
"getStatistics" => {
if wg_server.is_running() {
ManagementResponse::ok(id, wg_server.get_statistics().await)
} else {
let stats = vpn_server.get_statistics().await;
match serde_json::to_value(&stats) {
Ok(v) => ManagementResponse::ok(id, v),
Err(e) => ManagementResponse::err(id, format!("Serialize error: {}", e)),
}
}
}
"listClients" => {
if wg_server.is_running() {
let peers = wg_server.list_peers().await;
match serde_json::to_value(&peers) {
Ok(v) => ManagementResponse::ok(id, serde_json::json!({ "clients": v })),
Err(e) => ManagementResponse::err(id, format!("Serialize error: {}", e)),
}
} else {
let clients = vpn_server.list_clients().await;
match serde_json::to_value(&clients) {
Ok(v) => ManagementResponse::ok(id, serde_json::json!({ "clients": v })),
Err(e) => ManagementResponse::err(id, format!("Serialize error: {}", e)),
}
}
}
"disconnectClient" => {
let client_id = match request.params.get("clientId").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => {
return ManagementResponse::err(id, "Missing clientId parameter".to_string())
}
};
match vpn_server.disconnect_client(&client_id).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Disconnect client failed: {}", e)),
}
}
"setClientRateLimit" => {
let client_id = match request.params.get("clientId").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return ManagementResponse::err(id, "Missing clientId".to_string()),
};
let rate = match request.params.get("rateBytesPerSec").and_then(|v| v.as_u64()) {
Some(r) => r,
None => return ManagementResponse::err(id, "Missing rateBytesPerSec".to_string()),
};
let burst = match request.params.get("burstBytes").and_then(|v| v.as_u64()) {
Some(b) => b,
None => return ManagementResponse::err(id, "Missing burstBytes".to_string()),
};
match vpn_server.set_client_rate_limit(&client_id, rate, burst).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Failed: {}", e)),
}
}
"removeClientRateLimit" => {
let client_id = match request.params.get("clientId").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return ManagementResponse::err(id, "Missing clientId".to_string()),
};
match vpn_server.remove_client_rate_limit(&client_id).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Failed: {}", e)),
}
}
"getClientTelemetry" => {
let client_id = match request.params.get("clientId").and_then(|v| v.as_str()) {
Some(cid) => cid.to_string(),
None => return ManagementResponse::err(id, "Missing clientId".to_string()),
};
let clients = vpn_server.list_clients().await;
match clients.into_iter().find(|c| c.client_id == client_id) {
Some(info) => {
match serde_json::to_value(&info) {
Ok(v) => ManagementResponse::ok(id, v),
Err(e) => ManagementResponse::err(id, format!("Serialize error: {}", e)),
}
}
None => ManagementResponse::err(id, format!("Client {} not found", client_id)),
}
}
"generateKeypair" => match crypto::generate_keypair() {
Ok((public_key, private_key)) => ManagementResponse::ok(
id,
serde_json::json!({
"publicKey": public_key,
"privateKey": private_key,
}),
),
Err(e) => ManagementResponse::err(id, format!("Keypair generation failed: {}", e)),
},
"generateWgKeypair" => {
let (public_key, private_key) = wireguard::generate_wg_keypair();
ManagementResponse::ok(
id,
serde_json::json!({
"publicKey": public_key,
"privateKey": private_key,
}),
)
}
"addWgPeer" => {
if !wg_server.is_running() {
return ManagementResponse::err(id, "WireGuard server not running".to_string());
}
let config: WgPeerConfig = match serde_json::from_value(
request.params.get("peer").cloned().unwrap_or_default(),
) {
Ok(c) => c,
Err(e) => {
return ManagementResponse::err(id, format!("Invalid peer config: {}", e));
}
};
match wg_server.add_peer(config).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Add peer failed: {}", e)),
}
}
"removeWgPeer" => {
if !wg_server.is_running() {
return ManagementResponse::err(id, "WireGuard server not running".to_string());
}
let public_key = match request.params.get("publicKey").and_then(|v| v.as_str()) {
Some(k) => k.to_string(),
None => return ManagementResponse::err(id, "Missing publicKey".to_string()),
};
match wg_server.remove_peer(&public_key).await {
Ok(()) => ManagementResponse::ok(id, serde_json::json!({})),
Err(e) => ManagementResponse::err(id, format!("Remove peer failed: {}", e)),
}
}
"listWgPeers" => {
if !wg_server.is_running() {
return ManagementResponse::err(id, "WireGuard server not running".to_string());
}
let peers = wg_server.list_peers().await;
match serde_json::to_value(&peers) {
Ok(v) => ManagementResponse::ok(id, serde_json::json!({ "peers": v })),
Err(e) => ManagementResponse::err(id, format!("Serialize error: {}", e)),
}
}
_ => ManagementResponse::err(id, format!("Unknown server method: {}", request.method)),
}
}