diff --git a/core/src/context/rpc.rs b/core/src/context/rpc.rs index 204b000b5..dbb886f3c 100644 --- a/core/src/context/rpc.rs +++ b/core/src/context/rpc.rs @@ -35,7 +35,6 @@ use crate::lxc::LxcManager; use crate::net::gateway::WildcardListener; use crate::net::net_controller::{NetController, NetService}; use crate::net::socks::DEFAULT_SOCKS_LISTEN; -use crate::net::utils::{find_eth_iface, find_wifi_iface}; use crate::net::web_server::WebServerAcceptorSetter; use crate::net::wifi::WpaCli; use crate::prelude::*; @@ -55,8 +54,6 @@ use crate::{DATA_DIR, PLATFORM, PackageId}; pub struct RpcContextSeed { is_closed: AtomicBool, pub os_partitions: OsPartitionInfo, - pub wifi_interface: Option, - pub ethernet_interface: String, pub disk_guid: InternedString, pub ephemeral_sessions: SyncMutex, pub db: TypedPatchDb, @@ -73,7 +70,7 @@ pub struct RpcContextSeed { pub open_authed_continuations: OpenAuthedContinuations>, pub rpc_continuations: RpcContinuations, pub callbacks: Arc, - pub wifi_manager: Arc>>, + pub wifi_manager: RwLock>, pub current_secret: Arc, pub client: Client, pub start_time: Instant, @@ -323,13 +320,9 @@ impl RpcContext { }); } - let wifi_interface = find_wifi_iface().await?; - let seed = Arc::new(RpcContextSeed { is_closed: AtomicBool::new(false), os_partitions: OsPartitionInfo::from_fstab().await?, - wifi_interface: wifi_interface.clone(), - ethernet_interface: find_eth_iface().await?, disk_guid, ephemeral_sessions: SyncMutex::new(Sessions::new()), sync_db: watch::Sender::new(db.sequence().await), @@ -350,7 +343,7 @@ impl RpcContext { shutdown, lxc_manager: Arc::new(LxcManager::new()), open_authed_continuations: OpenAuthedContinuations::new(), - wifi_manager: Arc::new(RwLock::new(wifi_interface.clone().map(|i| WpaCli::init(i)))), + wifi_manager: RwLock::new(None), current_secret: Arc::new( Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).map_err(|e| { tracing::debug!("{:?}", e); diff --git a/core/src/db/model/public.rs b/core/src/db/model/public.rs index 9477dac82..11b68c226 100644 --- a/core/src/db/model/public.rs +++ b/core/src/db/model/public.rs @@ -98,7 +98,7 @@ impl Public { port_forwards: BTreeSet::new(), }, wifi: WifiInfo { - enabled: true, + enabled: false, ..Default::default() }, gateways: OrdMap::new(), @@ -378,7 +378,7 @@ pub struct ServerStatus { #[ts(export)] pub struct WifiInfo { pub enabled: bool, - pub interface: Option, + pub interface: Option, pub ssids: BTreeSet, pub selected: Option, #[ts(type = "string | null")] diff --git a/core/src/init.rs b/core/src/init.rs index ce489c0e9..eb74d6a19 100644 --- a/core/src/init.rs +++ b/core/src/init.rs @@ -23,7 +23,6 @@ use crate::middleware::auth::local::LocalAuthContext; use crate::net::gateway::WildcardListener; use crate::net::net_controller::{NetController, NetService}; use crate::net::socks::DEFAULT_SOCKS_LISTEN; -use crate::net::utils::find_wifi_iface; use crate::net::web_server::WebServerAcceptorSetter; use crate::prelude::*; use crate::progress::{ @@ -280,20 +279,17 @@ pub async fn init( load_ca_cert.complete(); load_wifi.start(); - let wifi_interface = find_wifi_iface().await?; - let wifi = db - .mutate(|db| { - let wifi = db - .as_public_mut() - .as_server_info_mut() - .as_network_mut() - .as_wifi_mut(); - wifi.as_interface_mut().ser(&wifi_interface)?; - wifi.de() - }) - .await - .result?; - crate::net::wifi::synchronize_network_manager(MAIN_DATA, &wifi).await?; + crate::net::wifi::synchronize_network_manager( + MAIN_DATA, + &peek + .as_public() + .as_server_info() + .as_network() + .as_wifi() + .de() + .unwrap_or_default(), + ) + .await?; load_wifi.complete(); init_tmp.start(); diff --git a/core/src/net/gateway.rs b/core/src/net/gateway.rs index 91a012df1..a4959b796 100644 --- a/core/src/net/gateway.rs +++ b/core/src/net/gateway.rs @@ -34,6 +34,7 @@ use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceTyp use crate::net::forward::START9_BRIDGE_IFACE; use crate::net::gateway::device::DeviceProxy; use crate::net::host::all_hosts; +use crate::net::utils::find_wifi_iface; use crate::net::web_server::{Accept, AcceptStream, MetadataVisitor, TcpMetadata}; use crate::prelude::*; use crate::util::Invoke; @@ -775,7 +776,9 @@ async fn watcher( async fn get_wan_ipv4(iface: &str, base_url: &Url) -> Result, Error> { let client = reqwest::Client::builder(); #[cfg(target_os = "linux")] - let client = client.interface(iface); + let client = client + .interface(iface) + .local_address(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); let url = base_url.join("/ip").with_kind(ErrorKind::ParseUrl)?; let text = client .build()? @@ -1289,14 +1292,15 @@ async fn poll_ip_info( }; let mut wan_ip = None; for echoip_url in echoip_urls { - let wan_ip = if echoip_ratelimit_state + if echoip_ratelimit_state .get(&echoip_url) .map_or(true, |i| i.elapsed() > Duration::from_secs(300)) && !subnets.is_empty() && !matches!( device_type, Some(NetworkInterfaceType::Bridge | NetworkInterfaceType::Loopback) - ) { + ) + { match get_wan_ipv4(iface.as_str(), &echoip_url).await { Ok(a) => { wan_ip = a; @@ -1458,12 +1462,27 @@ impl NetworkInterfaceController { ) -> Result<(), Error> { tracing::debug!("syncronizing {info:?} to db"); + let mut wifi_iface = info + .iter() + .find(|(_, info)| { + info.ip_info.as_ref().map_or(false, |i| { + i.device_type == Some(NetworkInterfaceType::Wireless) + }) + }) + .map(|(id, _)| id.clone()); + if wifi_iface.is_none() { + wifi_iface = find_wifi_iface() + .await + .ok() + .and_then(|a| a) + .map(InternedString::from) + .map(GatewayId::from); + } + db.mutate(|db| { - db.as_public_mut() - .as_server_info_mut() - .as_network_mut() - .as_gateways_mut() - .ser(info)?; + let network = db.as_public_mut().as_server_info_mut().as_network_mut(); + network.as_gateways_mut().ser(info)?; + network.as_wifi_mut().as_interface_mut().ser(&wifi_iface)?; let hostname = crate::hostname::ServerHostname::load(db.as_public().as_server_info())?; let ports = db.as_private().as_available_ports().de()?; for host in all_hosts(db) { diff --git a/core/src/net/wifi.rs b/core/src/net/wifi.rs index 57ccbd107..cead621bb 100644 --- a/core/src/net/wifi.rs +++ b/core/src/net/wifi.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::path::Path; -use std::sync::Arc; use std::time::Duration; use clap::Parser; @@ -11,30 +10,100 @@ use regex::Regex; use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async}; use serde::{Deserialize, Serialize}; use tokio::process::Command; -use tokio::sync::RwLock; +use tokio::sync::{RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard}; use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::db::model::Database; -use crate::db::model::public::WifiInfo; +use crate::db::model::public::{NetworkInterfaceType, WifiInfo}; use crate::prelude::*; use crate::util::Invoke; use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable}; -use crate::{Error, ErrorKind}; +use crate::{Error, ErrorKind, GatewayId}; -type WifiManager = Arc>>; +impl RpcContext { + async fn read_wifi_manager(&self) -> Result, Error> { + let err = || { + Error::new( + color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), + ErrorKind::Wifi, + ) + }; + let Some(interface) = self + .db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_wifi() + .as_interface() + .de()? + else { + return Err(err()); + }; + let mut cli = RwLockReadGuard::try_map(self.wifi_manager.read().await, |c| c.as_ref()).ok(); + while cli.as_ref().map_or(true, |c| c.interface != interface) { + drop(cli.take()); + let mut guard = self.wifi_manager.write().await; + *guard = Some(WpaCli::new(interface.clone())); + drop(guard); + cli = RwLockReadGuard::try_map(self.wifi_manager.read().await, |c| c.as_ref()).ok(); + } + cli.ok_or_else(err) + } -// pub fn wifi_manager(ctx: &RpcContext) -> Result<&WifiManager, Error> { -// if let Some(wifi_manager) = ctx.wifi_manager.as_ref() { -// Ok(wifi_manager) -// } else { -// Err(Error::new( -// color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), -// ErrorKind::Wifi, -// )) -// } -// } + async fn write_wifi_manager(&self) -> Result, Error> { + let err = || { + Error::new( + color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), + ErrorKind::Wifi, + ) + }; + let Some(interface) = self + .db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_wifi() + .as_interface() + .de()? + else { + return Err(err()); + }; + let mut cli = self.wifi_manager.write().await; + if cli.as_ref().map_or(true, |c| c.interface != interface) { + *cli = Some(WpaCli::new(interface)); + } + RwLockWriteGuard::try_map(cli, |c| c.as_mut()).map_err(|_| err()) + } + + async fn ethernet_interface_connected(&self) -> Result { + for (iface, info) in self + .db + .peek() + .await + .as_public() + .as_server_info() + .as_network() + .as_gateways() + .as_entries()? + { + let Some(info) = info.as_ip_info().transpose_ref() else { + continue; + }; + if info.as_deref().as_device_type().de()? == Some(NetworkInterfaceType::Ethernet) { + if interface_connected(iface.as_str()).await? { + return Ok(true); + } + } + } + Ok(false) + } +} pub fn wifi() -> ParentHandler { ParentHandler::new() @@ -165,55 +234,35 @@ pub async fn add( ctx: RpcContext, WifiAddParams { ssid, password }: WifiAddParams, ) -> Result<(), Error> { - let wifi_manager = ctx.wifi_manager.clone(); + let mut wpa_supplicant = ctx.write_wifi_manager().await?; if !ssid.is_ascii() { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")), ErrorKind::Wifi, )); } + let ssid = Ssid(ssid); if !password.is_ascii() { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.password-no-special-characters")), ErrorKind::Wifi, )); } - async fn add_procedure( - db: TypedPatchDb, - wifi_manager: WifiManager, - ssid: &Ssid, - password: &Psk, - ) -> Result<(), Error> { - tracing::info!("{}", t!("net.wifi.adding-network", ssid = &ssid.0)); - let mut wpa_supplicant = wifi_manager.write_owned().await; - let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; - wpa_supplicant.add_network(db, ssid, password).await?; - Ok(()) - } - if let Err(err) = add_procedure( - ctx.db.clone(), - wifi_manager.clone(), - &Ssid(ssid.clone()), - &Psk(password.clone()), - ) - .await + if let Err(err) = wpa_supplicant + .add_network(ctx.db.clone(), &ssid, &Psk(password)) + .await { tracing::error!( "{}", t!( "net.wifi.failed-to-add-network", - ssid = &ssid, + ssid = &ssid.0, error = err.to_string() ) ); tracing::debug!("{:?}", err); return Err(Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.failed-adding", ssid = &ssid)), + color_eyre::eyre::eyre!("{}", t!("net.wifi.failed-adding", ssid = &ssid.0)), ErrorKind::Wifi, )); } @@ -225,7 +274,7 @@ pub async fn add( .as_wifi_mut() .as_ssids_mut() .mutate(|s| { - s.insert(ssid); + s.insert(ssid.0); Ok(()) }) }) @@ -247,45 +296,36 @@ pub async fn connect( ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams, ) -> Result<(), Error> { - let wifi_manager = ctx.wifi_manager.clone(); + let mut wpa_supplicant = ctx.write_wifi_manager().await?; if !ssid.is_ascii() { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")), ErrorKind::Wifi, )); } - async fn connect_procedure( - db: TypedPatchDb, - wifi_manager: WifiManager, - ssid: &Ssid, - ) -> Result<(), Error> { - let mut wpa_supplicant = wifi_manager.write_owned().await; - let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; + if let Err(err) = async { let current = wpa_supplicant.get_current_network().await?; - let connected = wpa_supplicant.select_network(db.clone(), ssid).await?; + let connected = wpa_supplicant + .select_network(ctx.db.clone(), &Ssid(ssid.clone())) + .await?; if connected { - tracing::info!("{}", t!("net.wifi.connected-successfully", ssid = &ssid.0)); + tracing::info!("{}", t!("net.wifi.connected-successfully", ssid = &ssid)); } else { - tracing::info!("{}", t!("net.wifi.connection-failed", ssid = &ssid.0)); + tracing::info!("{}", t!("net.wifi.connection-failed", ssid = &ssid)); match current { None => { tracing::info!("{}", t!("net.wifi.no-wifi-to-revert")); } Some(current) => { - wpa_supplicant.select_network(db, ¤t).await?; + wpa_supplicant + .select_network(ctx.db.clone(), ¤t) + .await?; } } } - Ok(()) + Ok::<_, Error>(()) } - - if let Err(err) = - connect_procedure(ctx.db.clone(), wifi_manager.clone(), &Ssid(ssid.clone())).await + .await { tracing::error!( "{}", @@ -321,26 +361,18 @@ pub async fn connect( #[instrument(skip_all)] pub async fn remove(ctx: RpcContext, WifiSsidParams { ssid }: WifiSsidParams) -> Result<(), Error> { - let wifi_manager = ctx.wifi_manager.clone(); + let mut wpa_supplicant = ctx.write_wifi_manager().await?; if !ssid.is_ascii() { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.ssid-no-special-characters")), ErrorKind::Wifi, )); } - - let mut wpa_supplicant = wifi_manager.write_owned().await; - let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; let current = wpa_supplicant.get_current_network().await?; let ssid = Ssid(ssid); let is_current_being_removed = matches!(current, Some(current) if current == ssid); let is_current_removed_and_no_hardwire = - is_current_being_removed && !interface_connected(&ctx.ethernet_interface).await?; + is_current_being_removed && !ctx.ethernet_interface_connected().await?; if is_current_removed_and_no_hardwire { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.forbidden-delete-would-disconnect")), @@ -487,19 +519,12 @@ fn display_wifi_list(params: WithIoFormat, info: Vec) -> Res // #[command(display(display_wifi_info))] #[instrument(skip_all)] pub async fn get(ctx: RpcContext, _: Empty) -> Result { - let wifi_manager = ctx.wifi_manager.clone(); - let wpa_supplicant = wifi_manager.read_owned().await; - let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; + let wpa_supplicant = ctx.read_wifi_manager().await?; let (list_networks, current_res, country_res, ethernet_res, signal_strengths) = tokio::join!( wpa_supplicant.list_networks_low(), wpa_supplicant.get_current_network(), wpa_supplicant.get_country_low(), - interface_connected(&ctx.ethernet_interface), + ctx.ethernet_interface_connected(), wpa_supplicant.list_wifi_low() ); let signal_strengths = signal_strengths?; @@ -541,14 +566,7 @@ pub async fn get(ctx: RpcContext, _: Empty) -> Result { #[instrument(skip_all)] pub async fn get_available(ctx: RpcContext, _: Empty) -> Result, Error> { - let wifi_manager = ctx.wifi_manager.clone(); - let wpa_supplicant = wifi_manager.read_owned().await; - let wpa_supplicant = wpa_supplicant.as_ref().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; + let wpa_supplicant = ctx.read_wifi_manager().await?; let (wifi_list, network_list) = tokio::join!( wpa_supplicant.list_wifi_low(), wpa_supplicant.list_networks_low() @@ -584,20 +602,13 @@ pub async fn set_country( ctx: RpcContext, SetCountryParams { country }: SetCountryParams, ) -> Result<(), Error> { - let wifi_manager = ctx.wifi_manager.clone(); - if !interface_connected(&ctx.ethernet_interface).await? { + if !ctx.ethernet_interface_connected().await? { return Err(Error::new( color_eyre::eyre::eyre!("{}", t!("net.wifi.wont-change-country-without-ethernet")), crate::ErrorKind::Wifi, )); } - let mut wpa_supplicant = wifi_manager.write_owned().await; - let wpa_supplicant = wpa_supplicant.as_mut().ok_or_else(|| { - Error::new( - color_eyre::eyre::eyre!("{}", t!("net.wifi.no-interface-available")), - ErrorKind::Wifi, - ) - })?; + let mut wpa_supplicant = ctx.write_wifi_manager().await?; wpa_supplicant.set_country_low(country.alpha2()).await?; for (network_id, _wifi_info) in wpa_supplicant.list_networks_low().await? { wpa_supplicant.remove_network_low(network_id).await?; @@ -611,7 +622,7 @@ pub async fn set_country( #[derive(Debug)] pub struct WpaCli { - interface: String, + interface: GatewayId, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct NetworkId(String); @@ -661,7 +672,7 @@ pub struct WifiInfoLow { #[derive(Clone, Debug)] pub struct Psk(String); impl WpaCli { - pub fn init(interface: String) -> Self { + pub fn new(interface: GatewayId) -> Self { WpaCli { interface } } @@ -709,7 +720,7 @@ impl WpaCli { .arg("modify") .arg(&ssid.0) .arg("ifname") - .arg(&self.interface) + .arg(self.interface.as_str()) .invoke(ErrorKind::Wifi) .await .map(|_| ()) @@ -951,7 +962,7 @@ impl WpaCli { #[instrument(skip_all)] pub async fn get_current_network(&self) -> Result, Error> { let r = Command::new("iwgetid") - .arg(&self.interface) + .arg(self.interface.as_str()) .arg("--raw") .invoke(ErrorKind::Wifi) .await?; @@ -1055,6 +1066,12 @@ pub async fn synchronize_network_manager>( .arg("all") .invoke(ErrorKind::Wifi) .await?; + } else { + Command::new("rfkill") + .arg("unblock") + .arg("all") + .invoke(ErrorKind::Wifi) + .await?; } Command::new("ip") @@ -1093,7 +1110,7 @@ pub async fn synchronize_network_manager>( }; Command::new("ifconfig") - .arg(wifi_iface) + .arg(wifi_iface.as_str()) .arg("up") .invoke(ErrorKind::Wifi) .await?;