crates/wg-appliance-core/tests/psk_kernel_wireguard.rs
//! **The relay's pre-shared keys against the Linux kernel's WireGuard**
//! (2026-10-06, the PQ transport lane) — an independent implementation, so
//! boringtun agreeing with boringtun is not the only witness.
//!
//! A kernel `wireguard` interface (configured with `wg`, as an operator's
//! site hub or any stock peer is) dials the relay with the PSK the relay holds
//! for it: the handshake completes (the kernel's `latest-handshakes`), and its
//! packets reach the relay (`to_hub`). The same interface with another PSK
//! never completes a handshake and delivers nothing.
//!
//! Needs root for the interface (`sudo -n`), so it is `#[ignore]`d:
//! `cargo test -p wg-appliance-core --test psk_kernel_wireguard -- --ignored`.
#![cfg(target_os = "linux")]
use std::net::Ipv4Addr;
use std::process::Command;
use std::time::{Duration, Instant};
use wg_appliance_core::hubrelay::HubRelay;
use wg_appliance_core::keys::{encode_key, generate_keypair, KEY_LEN};
fn sudo(args: &[&str]) -> Result<String, String> {
let o = Command::new("sudo").arg("-n").args(args).output().map_err(|e| e.to_string())?;
if o.status.success() {
Ok(String::from_utf8_lossy(&o.stdout).into_owned())
} else {
Err(format!("sudo {args:?}: {}", String::from_utf8_lossy(&o.stderr)))
}
}
/// A kernel WireGuard interface, deleted on drop.
struct KernelWg(String);
impl Drop for KernelWg {
fn drop(&mut self) {
let _ = sudo(&["ip", "link", "del", &self.0]);
}
}
/// `ifname` at `addr/24`, one peer: the relay at `127.0.0.1:port`, owning the
/// hub's `/32`, with `psk`.
fn kernel_peer(ifname: &str, addr: Ipv4Addr, private: [u8; KEY_LEN], relay_b64: &str, port: u16, hub: Ipv4Addr, psk: [u8; KEY_LEN]) -> KernelWg {
let _ = sudo(&["ip", "link", "del", ifname]);
sudo(&["ip", "link", "add", ifname, "type", "wireguard"]).unwrap();
let wg = KernelWg(ifname.to_string());
// The keys never go through an argument. Ubuntu's AppArmor profile for `wg`
// lets it read key files under /etc/wireguard ONLY (not our pipe, not a file of
// ours): root writes them there from our stdin, `wg` reads them, and the
// directory is gone before the test goes on.
let dir = sudo(&["mktemp", "-d", "-p", "/etc/wireguard", "psk-test.XXXXXX"]).unwrap().trim().to_string();
let (k, p) = (format!("{dir}/key"), format!("{dir}/psk"));
sudo_in(&["install", "-m", "600", "/dev/stdin", &k], &encode_key(&private)).unwrap();
sudo_in(&["install", "-m", "600", "/dev/stdin", &p], &encode_key(&psk)).unwrap();
let set = sudo(&[
"wg", "set", ifname, "private-key", &k, "peer", relay_b64, "preshared-key", &p,
"endpoint", &format!("127.0.0.1:{port}"), "allowed-ips", &format!("{hub}/32"), "persistent-keepalive", "1",
]);
let _ = sudo(&["rm", "-rf", &dir]);
set.unwrap();
sudo(&["ip", "addr", "add", &format!("{addr}/24"), "dev", ifname]).unwrap();
sudo(&["ip", "link", "set", ifname, "up"]).unwrap();
wg
}
fn sudo_in(args: &[&str], input: &str) -> Result<(), String> {
use std::io::Write as _;
let mut c = Command::new("sudo").arg("-n").args(args).stdin(std::process::Stdio::piped()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::piped()).spawn().map_err(|e| e.to_string())?;
c.stdin.take().unwrap().write_all(input.as_bytes()).map_err(|e| e.to_string())?;
let o = c.wait_with_output().map_err(|e| e.to_string())?;
if o.status.success() {
Ok(())
} else {
Err(format!("sudo {:?}: {}", args.first(), String::from_utf8_lossy(&o.stderr)))
}
}
/// The kernel's last handshake with its one peer, seconds since the epoch (0 = never).
fn latest_handshake(ifname: &str) -> u64 {
sudo(&["wg", "show", ifname, "latest-handshakes"]).ok().and_then(|s| s.split_whitespace().nth(1).and_then(|t| t.parse().ok())).unwrap_or(0)
}
fn ping(to: Ipv4Addr) {
let _ = Command::new("ping").args(["-c", "3", "-i", "0.3", "-W", "1", &to.to_string()]).output();
}
#[test]
#[ignore = "needs root (sudo -n) for a kernel WireGuard interface"]
fn the_kernels_wireguard_pairs_with_the_relay_on_the_psk_it_holds_and_not_on_another() {
assert!(sudo(&["true"]).is_ok(), "this test needs passwordless sudo");
let hub = Ipv4Addr::new(10, 78, 0, 1);
let (hub_priv, _) = generate_keypair();
let relay = HubRelay::spawn(0, hub_priv, hub).unwrap();
let psk = [0x42u8; KEY_LEN];
// The right PSK: a handshake, and its packets reach the relay.
let (k_priv, k_pub) = generate_keypair();
let box_addr = Ipv4Addr::new(10, 78, 0, 2);
relay.add_named_peer_with_psk(k_pub, Some(psk), box_addr, "kernel").unwrap();
let ifname = format!("wgpq{}", std::process::id() % 10000);
let wg = kernel_peer(&ifname, box_addr, k_priv, &relay.public_b64, relay.port(), hub, psk);
ping(hub);
let v = relay.wait_for(Duration::from_secs(10), |v| v.to_hub >= 1 && v.peer(box_addr).is_some_and(|c| c.rx_packets >= 1));
assert!(latest_handshake(&ifname) > 0, "the kernel completed a handshake: {}", wg_appliance_core::hubrelay::describe(&v));
assert!(v.to_hub >= 1 && v.peer(box_addr).unwrap().rx_packets >= 1, "its packets reached the relay: {}", wg_appliance_core::hubrelay::describe(&v));
assert_eq!(v.with_psk, vec![box_addr]);
drop(wg);
// Another PSK on the same key: the kernel never completes a handshake.
let (w_priv, w_pub) = generate_keypair();
let wrong_addr = Ipv4Addr::new(10, 78, 0, 3);
relay.add_named_peer_with_psk(w_pub, Some([0x24u8; KEY_LEN]), wrong_addr, "wrong").unwrap();
let ifname = format!("wgpw{}", std::process::id() % 10000);
let _wg = kernel_peer(&ifname, wrong_addr, w_priv, &relay.public_b64, relay.port(), hub, psk);
let t0 = Instant::now();
while t0.elapsed() < Duration::from_secs(4) {
ping(hub);
}
assert_eq!(latest_handshake(&ifname), 0, "no handshake on a PSK the relay does not hold");
assert_eq!(relay.verdict().peer(wrong_addr).unwrap().rx_packets, 0, "nothing it sent was opened");
}