feat(cli): implement Wave 4 — aura binary (PKI, server/client, admin, bench)

aura-cli: clap command tree (pki init/issue-server/issue-client/revoke/list,
server, client, route add/list/remove, status, bench-crypto); TOML config with
~ expansion and split-tunnel rules -> RouteTable; JSON-over-Unix-socket admin
IPC; server/client data paths wiring transport + tunnel (TUN run needs root).
config/{server,client}.toml.example. 15 tests (pki roundtrip, config parse,
admin-socket roundtrip, loopback connection). Verified the real binary: --help,
bench-crypto, and a full CA->server->client cert workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xah30
2026-05-25 18:36:13 +03:00
parent c19a6c5586
commit cb89312a27
15 changed files with 2379 additions and 3 deletions
+147
View File
@@ -0,0 +1,147 @@
//! Admin socket roundtrip: start the admin listener on a temp Unix socket over a shared
//! [`RouteTable`], connect a client, send `route_add` / `route_list` / `route_remove` / `status`,
//! and assert the table changed and the responses are correct.
//!
//! Runs without root or network (an `AF_UNIX` socket in the temp dir).
#![cfg(unix)]
use std::path::PathBuf;
use std::sync::Arc;
use aura_cli::admin::{self, AdminState, Request, Stats};
use aura_tunnel::{RouteAction, RouteTable};
use tokio::sync::RwLock;
/// A unique socket path for this test (Unix socket paths are length-limited; temp dir keeps it
/// short enough on macOS/Linux).
fn socket_path() -> PathBuf {
let mut p = std::env::temp_dir();
p.push(format!(
"aura-admin-{}-{}.sock",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
p
}
#[tokio::test]
async fn admin_socket_route_roundtrip() {
let routes = Arc::new(RwLock::new(RouteTable::new(RouteAction::Vpn)));
let stats = Arc::new(Stats::new());
stats.set_peer_id(Some("client-test".to_string()));
let state = AdminState::new(
Arc::clone(&routes),
Arc::clone(&stats),
std::iter::empty(),
std::iter::empty(),
);
let path = socket_path();
let path_str = path.to_string_lossy().to_string();
// Spawn the listener.
let serve_path = path_str.clone();
let listener = tokio::spawn(async move {
let _ = admin::serve(&serve_path, state).await;
});
// Wait until the socket file exists (the listener binds before serving).
for _ in 0..200 {
if path.exists() {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
}
assert!(path.exists(), "admin socket was not created");
// route_add (cidr, direct).
let resp = admin::request(
&path_str,
&Request::RouteAdd {
cidr: Some("8.8.8.0/24".into()),
domain: None,
action: "direct".into(),
},
)
.await
.expect("route_add request");
assert!(resp.ok, "route_add ok: {:?}", resp.error);
// The shared table actually changed.
assert_eq!(
routes.read().await.classify("8.8.8.8".parse().unwrap()),
RouteAction::Direct
);
// route_add (domain, vpn).
let resp = admin::request(
&path_str,
&Request::RouteAdd {
cidr: None,
domain: Some("example.com".into()),
action: "vpn".into(),
},
)
.await
.expect("route_add domain");
assert!(resp.ok);
// route_list reflects both rules and the default.
let resp = admin::request(&path_str, &Request::RouteList)
.await
.expect("route_list");
assert!(resp.ok);
assert_eq!(resp.default.as_deref(), Some("vpn"));
let cidrs = resp.cidrs.expect("cidrs present");
assert_eq!(cidrs.len(), 1);
assert_eq!(cidrs[0].cidr, "8.8.8.0/24");
assert_eq!(cidrs[0].action, "direct");
let domains = resp.domains.expect("domains present");
assert_eq!(domains.len(), 1);
assert_eq!(domains[0].domain, "example.com");
// status reflects peer id + default + rule count.
let resp = admin::request(&path_str, &Request::Status)
.await
.expect("status");
assert!(resp.ok);
assert_eq!(resp.peer_id.as_deref(), Some("client-test"));
assert_eq!(resp.default.as_deref(), Some("vpn"));
assert_eq!(resp.rules, Some(2));
// route_remove the CIDR; classification falls back to default VPN.
let resp = admin::request(
&path_str,
&Request::RouteRemove {
cidr: "8.8.8.0/24".into(),
},
)
.await
.expect("route_remove");
assert_eq!(resp.removed, Some(true));
assert_eq!(
routes.read().await.classify("8.8.8.8".parse().unwrap()),
RouteAction::Vpn
);
// A malformed CIDR yields an error response (not a panic / disconnect).
let resp = admin::request(
&path_str,
&Request::RouteAdd {
cidr: Some("nonsense".into()),
domain: None,
action: "vpn".into(),
},
)
.await
.expect("route_add bad cidr");
assert!(!resp.ok);
assert!(resp.error.is_some());
listener.abort();
let _ = std::fs::remove_file(&path);
}