Advance netctl and networking tools
Red Bear OS Team
This commit is contained in:
@@ -3,10 +3,9 @@ name = "redbear-netctl"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "netctl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-netctl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
redbear-netctl-console = { path = "../../redbear-netctl-console/source" }
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{self, Command};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn program_name() -> String {
|
||||
env::args()
|
||||
@@ -16,13 +18,14 @@ fn program_name() -> String {
|
||||
|
||||
fn usage() -> String {
|
||||
format!(
|
||||
"Usage: {} [--boot|list|status [profile]|start <profile>|stop <profile>|enable <profile>|disable [profile]|is-enabled [profile]]",
|
||||
"Usage: {} [--boot|list|status [profile]|scan <profile|iface>|retry <profile|iface>|start <profile>|stop <profile>|enable <profile>|disable [profile]|is-enabled [profile]]",
|
||||
program_name()
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ProfileIpMode {
|
||||
Bounded,
|
||||
Dhcp,
|
||||
Static {
|
||||
address: String,
|
||||
@@ -31,11 +34,29 @@ enum ProfileIpMode {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum WifiSecurity {
|
||||
Open,
|
||||
Wpa2Psk { key: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct WifiSettings {
|
||||
ssid: String,
|
||||
security: WifiSecurity,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ConnectionMode {
|
||||
Ethernet,
|
||||
Wifi(WifiSettings),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Profile {
|
||||
name: String,
|
||||
interface: String,
|
||||
connection: String,
|
||||
connection: ConnectionMode,
|
||||
ip_mode: ProfileIpMode,
|
||||
}
|
||||
|
||||
@@ -56,6 +77,8 @@ fn run() -> Result<(), String> {
|
||||
"--boot" => run_boot_profile(),
|
||||
"list" => list_profiles(),
|
||||
"status" => status(args.next().as_deref()),
|
||||
"scan" => scan_wifi(&required_profile(args.next())?),
|
||||
"retry" => retry_wifi(&required_profile(args.next())?),
|
||||
"start" => start_profile(&required_profile(args.next())?, false),
|
||||
"stop" => stop_profile(&required_profile(args.next())?),
|
||||
"enable" => enable_profile(&required_profile(args.next())?),
|
||||
@@ -92,20 +115,69 @@ fn list_profiles() -> Result<(), String> {
|
||||
fn status(profile: Option<&str>) -> Result<(), String> {
|
||||
let active = active_profile_name()?;
|
||||
let selected = profile.map(str::to_string).or(active.clone());
|
||||
let address = current_addr().unwrap_or_else(|| "unconfigured".into());
|
||||
|
||||
match selected {
|
||||
Some(name) => {
|
||||
let loaded = load_profile(&name)?;
|
||||
let enabled = active.as_deref() == Some(name.as_str());
|
||||
println!(
|
||||
"profile={} enabled={} address={}",
|
||||
name,
|
||||
if enabled { "yes" } else { "no" },
|
||||
address
|
||||
);
|
||||
let address = current_addr(&loaded.interface).unwrap_or_else(|| "unconfigured".into());
|
||||
let connection = connection_name(&loaded.connection);
|
||||
match &loaded.connection {
|
||||
ConnectionMode::Wifi(_) => {
|
||||
let wifi_status = read_wifictl_value(&loaded.interface, "status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let link_state = read_wifictl_value(&loaded.interface, "link-state")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let firmware_status = read_wifictl_value(&loaded.interface, "firmware-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let transport_status =
|
||||
read_wifictl_value(&loaded.interface, "transport-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let transport_init_status =
|
||||
read_wifictl_value(&loaded.interface, "transport-init-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let activation_status =
|
||||
read_wifictl_value(&loaded.interface, "activation-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let connect_result = read_wifictl_value(&loaded.interface, "connect-result")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let disconnect_result =
|
||||
read_wifictl_value(&loaded.interface, "disconnect-result")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let last_error = read_wifictl_value(&loaded.interface, "last-error")
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
println!(
|
||||
"profile={} enabled={} connection={} interface={} address={} wifi_status={} link_state={} firmware_status={} transport_status={} transport_init_status={} activation_status={} connect_result={} disconnect_result={} last_error={}",
|
||||
name,
|
||||
if enabled { "yes" } else { "no" },
|
||||
connection,
|
||||
loaded.interface,
|
||||
address,
|
||||
wifi_status,
|
||||
link_state,
|
||||
firmware_status,
|
||||
transport_status,
|
||||
transport_init_status,
|
||||
activation_status,
|
||||
connect_result,
|
||||
disconnect_result,
|
||||
last_error
|
||||
);
|
||||
}
|
||||
ConnectionMode::Ethernet => {
|
||||
println!(
|
||||
"profile={} enabled={} connection={} interface={} address={}",
|
||||
name,
|
||||
if enabled { "yes" } else { "no" },
|
||||
connection,
|
||||
loaded.interface,
|
||||
address
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("profile=none enabled=no address={address}");
|
||||
println!("profile=none enabled=no address=unconfigured");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +185,6 @@ fn status(profile: Option<&str>) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn start_profile(name: &str, boot: bool) -> Result<(), String> {
|
||||
ensure_runtime_surfaces()?;
|
||||
let profile = load_profile(name)?;
|
||||
apply_profile(&profile, boot)?;
|
||||
println!("started {}", profile.name);
|
||||
@@ -121,6 +192,11 @@ fn start_profile(name: &str, boot: bool) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn stop_profile(name: &str) -> Result<(), String> {
|
||||
if let Ok(profile) = load_profile(name) {
|
||||
if let ConnectionMode::Wifi(_) = profile.connection {
|
||||
write_wifictl(&profile.interface, "disconnect", "1")?;
|
||||
}
|
||||
}
|
||||
if active_profile_name()?.as_deref() == Some(name) {
|
||||
let _ = fs::remove_file(active_profile_path());
|
||||
}
|
||||
@@ -161,28 +237,23 @@ fn is_enabled(profile: Option<&str>) -> Result<(), String> {
|
||||
}
|
||||
|
||||
fn apply_profile(profile: &Profile, boot: bool) -> Result<(), String> {
|
||||
if profile.connection != "ethernet" {
|
||||
return Err(format!(
|
||||
"unsupported Connection={} (only ethernet is supported)",
|
||||
profile.connection
|
||||
));
|
||||
}
|
||||
if profile.interface != "eth0" {
|
||||
return Err(format!(
|
||||
"unsupported Interface={} (only eth0 is supported)",
|
||||
profile.interface
|
||||
));
|
||||
match &profile.connection {
|
||||
ConnectionMode::Ethernet => {}
|
||||
ConnectionMode::Wifi(wifi) => apply_wifi_profile(&profile.interface, wifi)?,
|
||||
}
|
||||
|
||||
match &profile.ip_mode {
|
||||
ProfileIpMode::Bounded => {}
|
||||
ProfileIpMode::Dhcp => {
|
||||
if boot
|
||||
|| current_addr().as_deref() == Some("Not configured")
|
||||
|| current_addr().is_none()
|
||||
|| current_addr(&profile.interface).as_deref() == Some("Not configured")
|
||||
|| current_addr(&profile.interface).is_none()
|
||||
{
|
||||
let _child = Command::new("dhcpd")
|
||||
let _child = Command::new(dhcpd_command())
|
||||
.arg(&profile.interface)
|
||||
.spawn()
|
||||
.map_err(|err| format!("failed to spawn dhcpd: {err}"))?;
|
||||
wait_for_address(&profile.interface)?;
|
||||
}
|
||||
}
|
||||
ProfileIpMode::Static {
|
||||
@@ -190,7 +261,7 @@ fn apply_profile(profile: &Profile, boot: bool) -> Result<(), String> {
|
||||
gateway,
|
||||
dns,
|
||||
} => {
|
||||
write_netcfg("ifaces/eth0/addr/set", address)?;
|
||||
write_netcfg(&format!("ifaces/{}/addr/set", profile.interface), address)?;
|
||||
if let Some(gateway) = gateway {
|
||||
write_netcfg("route/add", &format!("default via {gateway}"))?;
|
||||
}
|
||||
@@ -209,17 +280,181 @@ fn apply_profile(profile: &Profile, boot: bool) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_runtime_surfaces() -> Result<(), String> {
|
||||
let addr_path = format!("{}/ifaces/eth0/addr/list", netcfg_root().display());
|
||||
fn ensure_runtime_surfaces_for(interface: &str) -> Result<(), String> {
|
||||
let addr_path = format!("{}/ifaces/{interface}/addr/list", netcfg_root().display());
|
||||
fs::read_to_string(&addr_path)
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("failed to access {addr_path}: {err}"))
|
||||
}
|
||||
|
||||
fn current_addr() -> Option<String> {
|
||||
fs::read_to_string(format!("{}/ifaces/eth0/addr/list", netcfg_root().display()))
|
||||
fn current_addr(interface: &str) -> Option<String> {
|
||||
fs::read_to_string(format!(
|
||||
"{}/ifaces/{interface}/addr/list",
|
||||
netcfg_root().display()
|
||||
))
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
}
|
||||
|
||||
fn connection_name(connection: &ConnectionMode) -> &'static str {
|
||||
match connection {
|
||||
ConnectionMode::Ethernet => "ethernet",
|
||||
ConnectionMode::Wifi(_) => "wifi",
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_wifi(target: &str) -> Result<(), String> {
|
||||
let interface = match load_profile(target) {
|
||||
Ok(profile) => match profile.connection {
|
||||
ConnectionMode::Wifi(_) => profile.interface,
|
||||
ConnectionMode::Ethernet => {
|
||||
return Err(format!("profile {target} is not a Wi-Fi profile"));
|
||||
}
|
||||
},
|
||||
Err(_) => target.to_string(),
|
||||
};
|
||||
|
||||
write_wifictl(&interface, "prepare", "1")?;
|
||||
if read_wifictl_value(&interface, "status").as_deref() == Some("failed") {
|
||||
let last_error = read_wifictl_value(&interface, "last-error")
|
||||
.unwrap_or_else(|| "prepare failed".to_string());
|
||||
return Err(format!("wifictl prepare failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(&interface, "init-transport", "1")?;
|
||||
if read_wifictl_value(&interface, "transport-init-status").as_deref()
|
||||
== Some("transport_init=failed")
|
||||
|| read_wifictl_value(&interface, "status").as_deref() == Some("failed")
|
||||
{
|
||||
let last_error = read_wifictl_value(&interface, "last-error")
|
||||
.unwrap_or_else(|| "transport init failed".to_string());
|
||||
return Err(format!("wifictl init-transport failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(&interface, "activate-nic", "1")?;
|
||||
if read_wifictl_value(&interface, "activation-status").as_deref() == Some("activation=failed")
|
||||
|| read_wifictl_value(&interface, "status").as_deref() == Some("failed")
|
||||
{
|
||||
let last_error = read_wifictl_value(&interface, "last-error")
|
||||
.unwrap_or_else(|| "activation failed".to_string());
|
||||
return Err(format!("wifictl activate-nic failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(&interface, "scan", "1")?;
|
||||
let results = read_wifictl_value(&interface, "scan-results").unwrap_or_default();
|
||||
let status = read_wifictl_value(&interface, "status").unwrap_or_else(|| "unknown".to_string());
|
||||
let firmware_status =
|
||||
read_wifictl_value(&interface, "firmware-status").unwrap_or_else(|| "unknown".to_string());
|
||||
let transport_status =
|
||||
read_wifictl_value(&interface, "transport-status").unwrap_or_else(|| "unknown".to_string());
|
||||
let transport_init_status = read_wifictl_value(&interface, "transport-init-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let activation_status = read_wifictl_value(&interface, "activation-status")
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
println!(
|
||||
"interface={} status={} firmware_status={} transport_status={} transport_init_status={} activation_status={} scan_results={}",
|
||||
interface,
|
||||
status,
|
||||
firmware_status,
|
||||
transport_status,
|
||||
transport_init_status,
|
||||
activation_status,
|
||||
if results.is_empty() {
|
||||
"none".to_string()
|
||||
} else {
|
||||
results
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn retry_wifi(target: &str) -> Result<(), String> {
|
||||
let interface = match load_profile(target) {
|
||||
Ok(profile) => match profile.connection {
|
||||
ConnectionMode::Wifi(_) => profile.interface,
|
||||
ConnectionMode::Ethernet => {
|
||||
return Err(format!("profile {target} is not a Wi-Fi profile"));
|
||||
}
|
||||
},
|
||||
Err(_) => target.to_string(),
|
||||
};
|
||||
|
||||
write_wifictl(&interface, "retry", "1")?;
|
||||
let status = read_wifictl_value(&interface, "status").unwrap_or_else(|| "unknown".to_string());
|
||||
let link_state =
|
||||
read_wifictl_value(&interface, "link-state").unwrap_or_else(|| "unknown".to_string());
|
||||
let last_error =
|
||||
read_wifictl_value(&interface, "last-error").unwrap_or_else(|| "none".to_string());
|
||||
println!(
|
||||
"interface={} status={} link_state={} last_error={}",
|
||||
interface, status, link_state, last_error
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_wifi_profile(interface: &str, wifi: &WifiSettings) -> Result<(), String> {
|
||||
let root = wifictl_root();
|
||||
let iface_root = root.join("ifaces").join(interface);
|
||||
fs::create_dir_all(&iface_root)
|
||||
.map_err(|err| format!("failed to prepare {}: {err}", iface_root.display()))?;
|
||||
|
||||
write_wifictl(interface, "ssid", &wifi.ssid)?;
|
||||
match &wifi.security {
|
||||
WifiSecurity::Open => {
|
||||
write_wifictl(interface, "security", "open")?;
|
||||
}
|
||||
WifiSecurity::Wpa2Psk { key } => {
|
||||
write_wifictl(interface, "security", "wpa2-psk")?;
|
||||
write_wifictl(interface, "key", key)?;
|
||||
}
|
||||
}
|
||||
write_wifictl(interface, "prepare", "1")?;
|
||||
if read_wifictl_value(interface, "status").as_deref() == Some("failed") {
|
||||
let last_error = read_wifictl_value(interface, "last-error")
|
||||
.unwrap_or_else(|| "prepare failed".to_string());
|
||||
return Err(format!("wifictl prepare failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(interface, "init-transport", "1")?;
|
||||
if read_wifictl_value(interface, "transport-init-status").as_deref()
|
||||
== Some("transport_init=failed")
|
||||
|| read_wifictl_value(interface, "status").as_deref() == Some("failed")
|
||||
{
|
||||
let last_error = read_wifictl_value(interface, "last-error")
|
||||
.unwrap_or_else(|| "transport init failed".to_string());
|
||||
return Err(format!("wifictl init-transport failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(interface, "activate-nic", "1")?;
|
||||
if read_wifictl_value(interface, "activation-status").as_deref() == Some("activation=failed")
|
||||
|| read_wifictl_value(interface, "status").as_deref() == Some("failed")
|
||||
{
|
||||
let last_error = read_wifictl_value(interface, "last-error")
|
||||
.unwrap_or_else(|| "activation failed".to_string());
|
||||
return Err(format!("wifictl activate-nic failed: {last_error}"));
|
||||
}
|
||||
write_wifictl(interface, "connect", "1")?;
|
||||
if read_wifictl_value(interface, "status").as_deref() == Some("failed") {
|
||||
let last_error = read_wifictl_value(interface, "last-error")
|
||||
.unwrap_or_else(|| "connect failed".to_string());
|
||||
return Err(format!("wifictl connect failed: {last_error}"));
|
||||
}
|
||||
ensure_runtime_surfaces_for(interface)
|
||||
}
|
||||
|
||||
fn write_wifictl(interface: &str, node: &str, value: &str) -> Result<(), String> {
|
||||
let path = wifictl_root().join("ifaces").join(interface).join(node);
|
||||
fs::write(&path, format!("{}\n", value.trim()))
|
||||
.map_err(|err| format!("failed to write {}: {err}", path.display()))
|
||||
}
|
||||
|
||||
fn wifictl_root() -> PathBuf {
|
||||
env::var_os("REDBEAR_WIFICTL_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/scheme/wifictl"))
|
||||
}
|
||||
|
||||
fn read_wifictl_value(interface: &str, node: &str) -> Option<String> {
|
||||
fs::read_to_string(wifictl_root().join("ifaces").join(interface).join(node))
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn write_netcfg(node: &str, value: &str) -> Result<(), String> {
|
||||
@@ -298,6 +533,44 @@ fn netcfg_root() -> PathBuf {
|
||||
.unwrap_or_else(|| PathBuf::from("/scheme/netcfg"))
|
||||
}
|
||||
|
||||
fn dhcpd_command() -> String {
|
||||
env::var("REDBEAR_DHCPD_CMD").unwrap_or_else(|_| "dhcpd".to_string())
|
||||
}
|
||||
|
||||
fn dhcp_wait_timeout() -> Duration {
|
||||
env::var("REDBEAR_DHCPD_WAIT_MS")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.map(Duration::from_millis)
|
||||
.unwrap_or_else(|| Duration::from_millis(1000))
|
||||
}
|
||||
|
||||
fn dhcp_poll_interval() -> Duration {
|
||||
env::var("REDBEAR_DHCPD_POLL_MS")
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.map(Duration::from_millis)
|
||||
.unwrap_or_else(|| Duration::from_millis(50))
|
||||
}
|
||||
|
||||
fn wait_for_address(interface: &str) -> Result<(), String> {
|
||||
let deadline = Instant::now() + dhcp_wait_timeout();
|
||||
let poll = dhcp_poll_interval();
|
||||
|
||||
loop {
|
||||
match current_addr(interface).as_deref() {
|
||||
Some(addr) if addr != "Not configured" && !addr.is_empty() => return Ok(()),
|
||||
_ if Instant::now() >= deadline => {
|
||||
return Err(format!(
|
||||
"timed out waiting for DHCP address on {}",
|
||||
interface
|
||||
));
|
||||
}
|
||||
_ => thread::sleep(poll),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_profile(name: &str, content: &str) -> Result<Profile, String> {
|
||||
let mut interface = None;
|
||||
let mut connection = None;
|
||||
@@ -305,6 +578,9 @@ fn parse_profile(name: &str, content: &str) -> Result<Profile, String> {
|
||||
let mut address = None;
|
||||
let mut gateway = None;
|
||||
let mut dns = None;
|
||||
let mut ssid = None;
|
||||
let mut security = None;
|
||||
let mut wifi_key = None;
|
||||
|
||||
for raw_line in content.lines() {
|
||||
let line = raw_line.trim();
|
||||
@@ -326,17 +602,43 @@ fn parse_profile(name: &str, content: &str) -> Result<Profile, String> {
|
||||
"Address" => address = parse_first_array_item(value),
|
||||
"Gateway" => gateway = Some(parse_scalar(value)),
|
||||
"DNS" => dns = parse_first_array_item(value),
|
||||
"SSID" => ssid = Some(parse_scalar(value)),
|
||||
"Security" => security = Some(parse_scalar(value)),
|
||||
"Key" | "Passphrase" => wifi_key = Some(parse_scalar(value)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let interface = interface.ok_or_else(|| format!("profile {name} is missing Interface="))?;
|
||||
let connection = connection.ok_or_else(|| format!("profile {name} is missing Connection="))?;
|
||||
let connection = match connection
|
||||
.ok_or_else(|| format!("profile {name} is missing Connection="))?
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"ethernet" => ConnectionMode::Ethernet,
|
||||
"wifi" => {
|
||||
let ssid = ssid.ok_or_else(|| format!("profile {name} is missing SSID="))?;
|
||||
let security = match security
|
||||
.ok_or_else(|| format!("profile {name} is missing Security="))?
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"open" => WifiSecurity::Open,
|
||||
"wpa2-psk" => WifiSecurity::Wpa2Psk {
|
||||
key: wifi_key.ok_or_else(|| format!("profile {name} is missing Key="))?,
|
||||
},
|
||||
other => return Err(format!("unsupported Security={other}")),
|
||||
};
|
||||
ConnectionMode::Wifi(WifiSettings { ssid, security })
|
||||
}
|
||||
other => return Err(format!("unsupported Connection={other}")),
|
||||
};
|
||||
let ip_mode = match ip
|
||||
.ok_or_else(|| format!("profile {name} is missing IP="))?
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"bounded" | "none" => ProfileIpMode::Bounded,
|
||||
"dhcp" => ProfileIpMode::Dhcp,
|
||||
"static" => ProfileIpMode::Static {
|
||||
address: address.ok_or_else(|| format!("profile {name} is missing Address="))?,
|
||||
@@ -349,11 +651,379 @@ fn parse_profile(name: &str, content: &str) -> Result<Profile, String> {
|
||||
Ok(Profile {
|
||||
name: name.to_string(),
|
||||
interface,
|
||||
connection: connection.to_ascii_lowercase(),
|
||||
connection,
|
||||
ip_mode,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn env_lock() -> &'static Mutex<()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
}
|
||||
|
||||
fn temp_root(prefix: &str) -> PathBuf {
|
||||
let stamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = env::temp_dir().join(format!("{prefix}-{stamp}"));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_wifi_profile() {
|
||||
let profile = parse_profile(
|
||||
"wifi-dhcp",
|
||||
"Description='Wi-Fi'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=wpa2-psk\nKey='secret'\nIP=dhcp\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(profile.interface, "wlan0");
|
||||
match profile.connection {
|
||||
ConnectionMode::Wifi(wifi) => {
|
||||
assert_eq!(wifi.ssid, "test-ssid");
|
||||
match wifi.security {
|
||||
WifiSecurity::Wpa2Psk { key } => assert_eq!(key, "secret"),
|
||||
_ => panic!("expected WPA2 profile"),
|
||||
}
|
||||
}
|
||||
_ => panic!("expected wifi connection"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_bounded_wifi_profile() {
|
||||
let profile = parse_profile(
|
||||
"wifi-open-bounded",
|
||||
"Description='Wi-Fi bounded'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=open\nIP=bounded\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(profile.interface, "wlan0");
|
||||
match profile.connection {
|
||||
ConnectionMode::Wifi(wifi) => {
|
||||
assert_eq!(wifi.ssid, "test-ssid");
|
||||
assert!(matches!(wifi.security, WifiSecurity::Open));
|
||||
}
|
||||
_ => panic!("expected wifi connection"),
|
||||
}
|
||||
assert!(matches!(profile.ip_mode, ProfileIpMode::Bounded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_wifi_profile_to_fake_roots() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let netcfg = temp_root("rbos-netcfg");
|
||||
let wifictl = temp_root("rbos-wifictl");
|
||||
let dhcp_script = temp_root("rbos-dhcp-script").join("fake-dhcpd.sh");
|
||||
fs::create_dir_all(netcfg.join("ifaces/wlan0/addr")).unwrap();
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(netcfg.join("ifaces/wlan0/addr/list"), "Not configured\n").unwrap();
|
||||
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.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();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_NETCFG_ROOT", &netcfg);
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
env::set_var("REDBEAR_DHCPD_CMD", &dhcp_script);
|
||||
env::set_var("REDBEAR_DHCPD_WAIT_MS", "500");
|
||||
env::set_var("REDBEAR_DHCPD_POLL_MS", "10");
|
||||
}
|
||||
|
||||
let profile = parse_profile(
|
||||
"wifi-dhcp",
|
||||
"Description='Wi-Fi'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=wpa2-psk\nKey='secret'\nIP=dhcp\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
apply_profile(&profile, false).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(netcfg.join("ifaces/wlan0/addr/list")).unwrap(),
|
||||
"10.0.0.44/24\n"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/ssid")).unwrap(),
|
||||
"test-ssid\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/security")).unwrap(),
|
||||
"wpa2-psk\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/key")).unwrap(),
|
||||
"secret\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/prepare")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/init-transport")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/connect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_bounded_wifi_profile_without_dhcp_handoff() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let netcfg = temp_root("rbos-netcfg-bounded");
|
||||
let wifictl = temp_root("rbos-wifictl-bounded");
|
||||
let dhcp_log = temp_root("rbos-dhcp-bounded").join("dhcp.log");
|
||||
let dhcp_script = temp_root("rbos-dhcp-script-bounded").join("fake-dhcpd.sh");
|
||||
fs::create_dir_all(netcfg.join("ifaces/wlan0/addr")).unwrap();
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(netcfg.join("ifaces/wlan0/addr/list"), "Not configured\n").unwrap();
|
||||
fs::write(
|
||||
&dhcp_script,
|
||||
format!(
|
||||
"#!/usr/bin/env bash\nset -euo pipefail\nprintf 'called\n' > '{}'\n",
|
||||
dhcp_log.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();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_NETCFG_ROOT", &netcfg);
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
env::set_var("REDBEAR_DHCPD_CMD", &dhcp_script);
|
||||
}
|
||||
|
||||
let profile = parse_profile(
|
||||
"wifi-open-bounded",
|
||||
"Description='Wi-Fi bounded'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=open\nIP=bounded\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
apply_profile(&profile, false).unwrap();
|
||||
|
||||
assert!(!dhcp_log.exists());
|
||||
assert_eq!(
|
||||
fs::read_to_string(netcfg.join("ifaces/wlan0/addr/list")).unwrap(),
|
||||
"Not configured\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/connect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_wifi_state_values() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let wifictl = temp_root("rbos-wifictl-state");
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(wifictl.join("ifaces/wlan0/status"), "firmware-ready\n").unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/firmware-status"),
|
||||
"firmware=present family=intel-bz-arrow-lake\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/transport-status"),
|
||||
"transport=pci memory_enabled=yes bus_master=yes bar0_present=yes irq_pin_present=yes\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/transport-init-status"),
|
||||
"transport_init=stub\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/activation-status"),
|
||||
"activation=stub\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/connect-result"),
|
||||
"connect_result=bounded-associated ssid=demo security=wpa2-psk\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
read_wifictl_value("wlan0", "status").as_deref(),
|
||||
Some("firmware-ready")
|
||||
);
|
||||
assert!(read_wifictl_value("wlan0", "firmware-status")
|
||||
.unwrap()
|
||||
.contains("intel-bz-arrow-lake"));
|
||||
assert!(read_wifictl_value("wlan0", "transport-status")
|
||||
.unwrap()
|
||||
.contains("memory_enabled=yes"));
|
||||
assert_eq!(
|
||||
read_wifictl_value("wlan0", "transport-init-status").as_deref(),
|
||||
Some("transport_init=stub")
|
||||
);
|
||||
assert_eq!(
|
||||
read_wifictl_value("wlan0", "activation-status").as_deref(),
|
||||
Some("activation=stub")
|
||||
);
|
||||
assert_eq!(
|
||||
read_wifictl_value("wlan0", "connect-result").as_deref(),
|
||||
Some("connect_result=bounded-associated ssid=demo security=wpa2-psk")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_uses_wifi_profile_or_interface() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-scan-profile");
|
||||
let wifictl = temp_root("rbos-netctl-scan-wifictl");
|
||||
fs::create_dir_all(&profile_dir).unwrap();
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(
|
||||
profile_dir.join("wifi-dhcp"),
|
||||
"Description='Wi-Fi'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=open\nIP=dhcp\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(wifictl.join("ifaces/wlan0/status"), "scanning\n").unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/firmware-status"),
|
||||
"firmware=present family=intel-bz-arrow-lake prepared=yes\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/transport-status"),
|
||||
"transport=pci memory_enabled=yes bus_master=yes\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/transport-init-status"),
|
||||
"transport_init=stub\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/activation-status"),
|
||||
"activation=stub\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/scan-results"),
|
||||
"demo-ssid\ndemo-open\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_NETCTL_PROFILE_DIR", &profile_dir);
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
}
|
||||
|
||||
scan_wifi("wifi-dhcp").unwrap();
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/prepare")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/init-transport")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/activate-nic")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/scan")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_uses_wifi_profile_or_interface() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let profile_dir = temp_root("rbos-netctl-retry-profile");
|
||||
let wifictl = temp_root("rbos-netctl-retry-wifictl");
|
||||
fs::create_dir_all(&profile_dir).unwrap();
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(
|
||||
profile_dir.join("wifi-dhcp"),
|
||||
"Description='Wi-Fi'\nInterface=wlan0\nConnection=wifi\nSSID='test-ssid'\nSecurity=open\nIP=dhcp\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(wifictl.join("ifaces/wlan0/status"), "device-detected\n").unwrap();
|
||||
fs::write(wifictl.join("ifaces/wlan0/link-state"), "link=retrying\n").unwrap();
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_NETCTL_PROFILE_DIR", &profile_dir);
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
}
|
||||
|
||||
retry_wifi("wifi-dhcp").unwrap();
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl.join("ifaces/wlan0/retry")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_wifi_last_error() {
|
||||
let _guard = env_lock()
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let wifictl = temp_root("rbos-wifictl-error");
|
||||
fs::create_dir_all(wifictl.join("ifaces/wlan0")).unwrap();
|
||||
fs::write(
|
||||
wifictl.join("ifaces/wlan0/last-error"),
|
||||
"missing firmware\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
unsafe {
|
||||
env::set_var("REDBEAR_WIFICTL_ROOT", &wifictl);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
read_wifictl_value("wlan0", "last-error").as_deref(),
|
||||
Some("missing firmware")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_scalar(value: &str) -> String {
|
||||
let trimmed = value.trim();
|
||||
trimmed
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use redbear_netctl_console::backend::{
|
||||
ConsoleBackend, FsBackend, IpMode, Profile, RuntimePaths, SecurityKind,
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn run_netctl(
|
||||
args: &[&str],
|
||||
profile_dir: &PathBuf,
|
||||
wifictl_root: &PathBuf,
|
||||
netcfg_root: &PathBuf,
|
||||
) -> String {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_redbear-netctl"))
|
||||
.args(args)
|
||||
.env("REDBEAR_NETCTL_PROFILE_DIR", profile_dir)
|
||||
.env("REDBEAR_WIFICTL_ROOT", wifictl_root)
|
||||
.env("REDBEAR_NETCFG_ROOT", netcfg_root)
|
||||
.env("REDBEAR_DHCPD_CMD", "/usr/bin/true")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"command {:?} failed: {}",
|
||||
args,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
}
|
||||
|
||||
fn console_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 cli_start_wifi_profile_writes_connect_path() {
|
||||
let profile_dir = temp_root("rbos-netctl-cli-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-cli-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-cli-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(
|
||||
profile_dir.join("wifi-dhcp"),
|
||||
"Description='Wi-Fi'\nInterface=wlan0\nConnection=wifi\nSSID='demo'\nSecurity=wpa2-psk\nKey='secret'\nIP=dhcp\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
fs::write(wifictl_root.join("ifaces/wlan0/status"), "connected\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/link-state"),
|
||||
"link=connected\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=active\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/connect-result"),
|
||||
"connect_result=bounded-associated ssid=demo security=wpa2-psk\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/disconnect-result"),
|
||||
"disconnect_result=bounded-disconnected\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let dhcp_log = profile_dir.join("dhcp.log");
|
||||
let dhcp_script = profile_dir.join("fake-dhcpd.sh");
|
||||
fs::write(
|
||||
&dhcp_script,
|
||||
format!(
|
||||
"#!/usr/bin/env bash\nset -euo pipefail\nprintf '%s\\n' \"$1\" > '{}'\nprintf '10.0.0.44/24\\n' > '{}/ifaces/wlan0/addr/list'\n",
|
||||
dhcp_log.display(),
|
||||
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 output = Command::new(env!("CARGO_BIN_EXE_redbear-netctl"))
|
||||
.args(["start", "wifi-dhcp"])
|
||||
.env("REDBEAR_NETCTL_PROFILE_DIR", &profile_dir)
|
||||
.env("REDBEAR_WIFICTL_ROOT", &wifictl_root)
|
||||
.env("REDBEAR_NETCFG_ROOT", &netcfg_root)
|
||||
.env("REDBEAR_DHCPD_CMD", &dhcp_script)
|
||||
.env("REDBEAR_DHCPD_WAIT_MS", "500")
|
||||
.env("REDBEAR_DHCPD_POLL_MS", "10")
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"command {:?} failed: {}",
|
||||
["start", "wifi-dhcp"],
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let started = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(started.contains("started wifi-dhcp"));
|
||||
assert_eq!(fs::read_to_string(&dhcp_log).unwrap(), "wlan0\n");
|
||||
assert_eq!(
|
||||
fs::read_to_string(netcfg_root.join("ifaces/wlan0/addr/list")).unwrap(),
|
||||
"10.0.0.44/24\n"
|
||||
);
|
||||
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/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/connect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
|
||||
let status = run_netctl(
|
||||
&["status", "wifi-dhcp"],
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
);
|
||||
assert!(status.contains("address=10.0.0.44/24"));
|
||||
assert!(status.contains("wifi_status=connected"));
|
||||
assert!(status.contains("connect_result=bounded-associated ssid=demo security=wpa2-psk"));
|
||||
assert!(status.contains("disconnect_result=bounded-disconnected"));
|
||||
|
||||
let stopped = run_netctl(
|
||||
&["stop", "wifi-dhcp"],
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
);
|
||||
assert!(stopped.contains("stopped wifi-dhcp"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/disconnect")).unwrap(),
|
||||
"1\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_status_reports_pending_wifi_link_honestly() {
|
||||
let profile_dir = temp_root("rbos-netctl-cli-pending-profiles");
|
||||
let wifictl_root = temp_root("rbos-netctl-cli-pending-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-cli-pending-netcfg");
|
||||
fs::create_dir_all(wifictl_root.join("ifaces/wlan0")).unwrap();
|
||||
fs::create_dir_all(netcfg_root.join("ifaces/wlan0/addr")).unwrap();
|
||||
fs::write(
|
||||
profile_dir.join("wifi-open-bounded"),
|
||||
"Description='Wi-Fi bounded'\nInterface=wlan0\nConnection=wifi\nSSID='demo'\nSecurity=wpa2-psk\nKey='secret'\nIP=bounded\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(profile_dir.join("active"), "wifi-open-bounded\n").unwrap();
|
||||
|
||||
fs::write(wifictl_root.join("ifaces/wlan0/status"), "associating\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/link-state"),
|
||||
"link=associating\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=active\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/connect-result"),
|
||||
"connect_result=host-bounded-pending ssid=demo security=wpa2-psk\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/disconnect-result"),
|
||||
"disconnect_result=bounded-disconnected\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
netcfg_root.join("ifaces/wlan0/addr/list"),
|
||||
"Not configured\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let status = run_netctl(
|
||||
&["status", "wifi-open-bounded"],
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
);
|
||||
assert!(status.contains("wifi_status=associating"));
|
||||
assert!(status.contains("link_state=link=associating"));
|
||||
assert!(status.contains("connect_result=host-bounded-pending ssid=demo security=wpa2-psk"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_start_consumes_console_written_wifi_profile() {
|
||||
let profile_dir = temp_root("rbos-netctl-cli-console-profile");
|
||||
let wifictl_root = temp_root("rbos-netctl-cli-console-wifictl");
|
||||
let netcfg_root = temp_root("rbos-netctl-cli-console-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"), "associating\n").unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/link-state"),
|
||||
"link=associating\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/connect-result"),
|
||||
"connect_result=host-bounded-pending ssid=console-demo security=wpa2-psk\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wifictl_root.join("ifaces/wlan0/disconnect-result"),
|
||||
"disconnect_result=not-run\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let console_backend = FsBackend::new(console_runtime_paths(
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
"/usr/bin/true",
|
||||
));
|
||||
let profile = Profile {
|
||||
name: "wifi-console-bounded".to_string(),
|
||||
description: "Console written Wi-Fi profile".to_string(),
|
||||
interface: "wlan0".to_string(),
|
||||
ssid: "console-demo".to_string(),
|
||||
security: SecurityKind::Wpa2Psk,
|
||||
key: "secret".to_string(),
|
||||
ip_mode: IpMode::Bounded,
|
||||
address: String::new(),
|
||||
gateway: String::new(),
|
||||
dns: String::new(),
|
||||
};
|
||||
console_backend.save_profile(&profile).unwrap();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_redbear-netctl"))
|
||||
.args(["start", "wifi-console-bounded"])
|
||||
.env("REDBEAR_NETCTL_PROFILE_DIR", &profile_dir)
|
||||
.env("REDBEAR_WIFICTL_ROOT", &wifictl_root)
|
||||
.env("REDBEAR_NETCFG_ROOT", &netcfg_root)
|
||||
.env("REDBEAR_DHCPD_CMD", "/usr/bin/true")
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"command {:?} failed: {}",
|
||||
["start", "wifi-console-bounded"],
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let started = String::from_utf8(output.stdout).unwrap();
|
||||
assert!(started.contains("started wifi-console-bounded"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(wifictl_root.join("ifaces/wlan0/ssid")).unwrap(),
|
||||
"console-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"
|
||||
);
|
||||
|
||||
let status = run_netctl(
|
||||
&["status", "wifi-console-bounded"],
|
||||
&profile_dir,
|
||||
&wifictl_root,
|
||||
&netcfg_root,
|
||||
);
|
||||
assert!(status.contains("profile=wifi-console-bounded"));
|
||||
assert!(status.contains("wifi_status=associating"));
|
||||
assert!(status.contains("link_state=link=associating"));
|
||||
assert!(
|
||||
status.contains("connect_result=host-bounded-pending ssid=console-demo security=wpa2-psk")
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user