Advance netctl and networking tools
Red Bear OS Team
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use redbear_netctl_console::backend::{
|
||||
ConsoleBackend, FsBackend, IpMode, Profile, RuntimePaths, SecurityKind,
|
||||
};
|
||||
|
||||
fn env_lock() -> &'static Mutex<()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn temp_root(prefix: &str) -> PathBuf {
|
||||
let nanos = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let root = std::env::temp_dir().join(format!("{prefix}-{nanos}"));
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
root
|
||||
}
|
||||
|
||||
fn runtime_paths(
|
||||
profile_dir: &PathBuf,
|
||||
wifictl_root: &PathBuf,
|
||||
netcfg_root: &PathBuf,
|
||||
dhcpd_command: &str,
|
||||
) -> RuntimePaths {
|
||||
RuntimePaths {
|
||||
profile_dir: profile_dir.clone(),
|
||||
active_profile_path: profile_dir.join("active"),
|
||||
wifictl_root: wifictl_root.clone(),
|
||||
netcfg_root: netcfg_root.clone(),
|
||||
dhcpd_command: dhcpd_command.to_string(),
|
||||
dhcp_wait_timeout: std::time::Duration::from_millis(500),
|
||||
dhcp_poll_interval: std::time::Duration::from_millis(10),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saves_and_loads_profile_using_fake_roots() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-console-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-console-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-console-netcfg");
|
||||
let backend = FsBackend::new(runtime_paths(
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
"/usr/bin/true",
|
||||
));
|
||||
|
||||
let profile = Profile {
|
||||
name: "wifi-open-bounded".to_string(),
|
||||
description: "Wi-Fi bounded".to_string(),
|
||||
interface: "wlan0".to_string(),
|
||||
ssid: "demo-open".to_string(),
|
||||
security: SecurityKind::Open,
|
||||
key: String::new(),
|
||||
ip_mode: IpMode::Bounded,
|
||||
address: String::new(),
|
||||
gateway: String::new(),
|
||||
dns: String::new(),
|
||||
};
|
||||
|
||||
backend.save_profile(&profile).unwrap();
|
||||
let loaded = backend.load_profile("wifi-open-bounded").unwrap();
|
||||
assert_eq!(loaded, profile);
|
||||
assert_eq!(
|
||||
backend.list_wifi_profiles().unwrap(),
|
||||
vec!["wifi-open-bounded".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_writes_bounded_wifictl_flow_nodes() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-console-scan-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-console-scan-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-console-scan-netcfg");
|
||||
fs::create_dir_all(wifictl_root.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(wifictl_root.join("ifaces/wlan0/status"), "scanning\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/transport-init-status"),
|
||||
"transport_init=ok\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/activation-status"),
|
||||
"activation=ok\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/scan-results"),
|
||||
"ssid=demo-open security=open\nssid=demo-secure security=wpa2-psk\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let backend = FsBackend::new(runtime_paths(
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
"/usr/bin/true",
|
||||
));
|
||||
let results = backend.scan("wlan0").unwrap();
|
||||
|
||||
assert_eq!(results.len(), 2);
|
||||
assert_eq!(results[0].ssid, "demo-open");
|
||||
assert_eq!(results[1].ssid, "demo-secure");
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/prepare")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/init-transport")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/activate-nic")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/scan")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_uses_wifictl_and_marks_active_profile() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-console-connect-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-console-connect-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-console-connect-netcfg");
|
||||
fs::create_dir_all(wifictl_root.join("ifaces/wlan0")).unwrap();
|
||||
fs::create_dir_all(netcfg_root.join("ifaces/wlan0/addr")).unwrap();
|
||||
fs::write(
|
||||
netcfg_root.join("ifaces/wlan0/addr/list"),
|
||||
"Not configured\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(wifictl_root.join("ifaces/wlan0/status"), "connected\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/transport-init-status"),
|
||||
"transport_init=ok\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/activation-status"),
|
||||
"activation=ok\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dhcp_script = profile_dir.join("fake-dhcpd.sh");
|
||||
fs::write(
|
||||
&dhcp_script,
|
||||
format!(
|
||||
"#!/usr/bin/env bash\nset -euo pipefail\nprintf '10.0.0.44/24\\n' > '{}/ifaces/wlan0/addr/list'\n",
|
||||
netcfg_root.display()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(&dhcp_script).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&dhcp_script, perms).unwrap();
|
||||
}
|
||||
|
||||
let backend = FsBackend::new(runtime_paths(
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
dhcp_script.to_str().unwrap(),
|
||||
));
|
||||
let profile = Profile {
|
||||
name: "wifi-dhcp".to_string(),
|
||||
description: "Wi-Fi DHCP".to_string(),
|
||||
interface: "wlan0".to_string(),
|
||||
ssid: "demo".to_string(),
|
||||
security: SecurityKind::Wpa2Psk,
|
||||
key: "secret".to_string(),
|
||||
ip_mode: IpMode::Dhcp,
|
||||
address: String::new(),
|
||||
gateway: String::new(),
|
||||
dns: String::new(),
|
||||
};
|
||||
|
||||
let message = backend.connect(&profile).unwrap();
|
||||
assert!(message.contains("applied wifi-dhcp"));
|
||||
assert_eq!(
|
||||
backend.active_profile_name().unwrap().as_deref(),
|
||||
Some("wifi-dhcp")
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/ssid")).unwrap(),
|
||||
"demo\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/security")).unwrap(),
|
||||
"wpa2-psk\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/key")).unwrap(),
|
||||
"secret\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/prepare")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/connect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(netcfg_root.join("ifaces/wlan0/addr/list")).unwrap(),
|
||||
"10.0.0.44/24\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnect_clears_active_profile_when_it_matches() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-console-disconnect-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-console-disconnect-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-console-disconnect-netcfg");
|
||||
fs::create_dir_all(wifictl_root.join("ifaces/wlan0")).unwrap();
|
||||
|
||||
let backend = FsBackend::new(runtime_paths(
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
"/usr/bin/true",
|
||||
));
|
||||
backend.set_active_profile("wifi-dhcp").unwrap();
|
||||
|
||||
let message = backend.disconnect(Some("wifi-dhcp"), "wlan0").unwrap();
|
||||
assert!(message.contains("disconnected wlan0"));
|
||||
assert_eq!(backend.active_profile_name().unwrap(), None);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/disconnect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn temp_root(prefix: &str) -> PathBuf {
|
||||
let stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("{prefix}-{stamp}"));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ncurses_binary_launches_and_quits_on_q() {
|
||||
if Command::new("script")
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("skipping ncurses smoke test: 'script' utility not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let profile_dir = temp_root("rbos-netctl-console-tty-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-console-tty-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-console-tty-netcfg");
|
||||
fs::create_dir_all(wifictl_root.join("ifaces/wlan0")).unwrap();
|
||||
fs::create_dir_all(netcfg_root.join("ifaces/wlan0/addr")).unwrap();
|
||||
fs::write(
|
||||
netcfg_root.join("ifaces/wlan0/addr/list"),
|
||||
"Not configured\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/status"),
|
||||
"device-detected\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(wifictl_root.join("ifaces/wlan0/link-state"), "link=down\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/firmware-status"),
|
||||
"firmware=present\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/transport-status"),
|
||||
"transport=ready\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/transport-init-status"),
|
||||
"transport_init=not-run\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/activation-status"),
|
||||
"activation=not-run\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/connect-result"),
|
||||
"connect_result=not-run\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/disconnect-result"),
|
||||
"disconnect_result=not-run\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut child = Command::new("script")
|
||||
.args([
|
||||
"-qec",
|
||||
env!("CARGO_BIN_EXE_redbear-netctl-console"),
|
||||
"/dev/null",
|
||||
])
|
||||
.env("REDBEAR_NETCTL_PROFILE_DIR", &profile_dir)
|
||||
.env("REDBEAR_WIFICTL_ROOT", &wifictl_root)
|
||||
.env("REDBEAR_NETCFG_ROOT", &netcfg_root)
|
||||
.env("TERM", "xterm")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
child.stdin.as_mut().unwrap().write_all(b"q").unwrap();
|
||||
let output = child.wait_with_output().unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"ncurses console failed: stdout={} stderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user