diff --git a/packages/client-api/src/providers/network/create-network-provider.ts b/packages/client-api/src/providers/network/create-network-provider.ts index cda77780..3d854538 100644 --- a/packages/client-api/src/providers/network/create-network-provider.ts +++ b/packages/client-api/src/providers/network/create-network-provider.ts @@ -4,11 +4,52 @@ import type { NetworkProviderConfig } from '~/user-config'; import { createProviderListener } from '../create-provider-listener'; export interface NetworkVariables { - defaultInterface: NetworkInterface; - defaultGateway: Gateway; + defaultInterface: NetworkInterface | null; + defaultGateway: NetworkGateway | null; interfaces: NetworkInterface[]; } +export interface NetworkInterface { + name: string; + friendlyName: string | null; + description: string | null; + type: InterfaceType; + ipv4Addresses: string[]; + ipv6Addresses: string[]; + macAddress: string | null; + transmitSeed: number | null; + receiveSpeed: number | null; + dnsServers: string[]; + isDefault: boolean; +} + +export interface NetworkGateway { + macAddress: string; + ipv4Addresses: string[]; + ipv6Addresses: string[]; + ssid: string | null; + signalStrength: number | null; +} + +export enum InterfaceType { + UNKNOWN = 'unknown', + ETHERNET = 'ethernet', + TOKEN_RING = 'token_ring', + FDDI = 'fddi', + PPP = 'ppp', + LOOPBACK = 'loopback', + SLIP = 'slip', + ATM = 'atm', + GENERIC_MODEM = 'generic_modem', + ISDN = 'isdn', + WIFI = 'wifi', + DSL = 'dsl', + TUNNEL = 'tunnel', + HIGH_PERFORMANCE_SERIAL_BUS = 'high_performance_serial_bus', + MOBILE_BROADBAND = 'mobile_broadband', + BRIDGE = 'bridge', +} + export async function createNetworkProvider( config: NetworkProviderConfig, owner: Owner, @@ -30,82 +71,3 @@ export async function createNetworkProvider( }, }; } - -export interface NetworkInterface { - name: string; - friendlName: string; - description: string; - interfaceType: InterfaceType; - ipv4: Ipv4Net; - ipv6: Ipv6Net; - macAddress: MacAddress; - transmitSeed: number; - receiveSpeed: number; - dnsServers: (Ipv4Addr | Ipv6Addr)[]; - default: boolean; -} - -export interface Gateway { - macAddress: MacAddress; - ipv4: Ipv4Addr[]; - ipv6: Ipv6Addr[]; - ssid: string; - signal_strength: number; - connected: boolean; -} - -enum InterfaceType { - Unknown, - Ethernet, - TokenRing, - Fddi, - BasicIsdn, - PrimaryIsdn, - Ppp, - Loopback, - Ethernet3Megabit, - Slip, - Atm, - GenericModem, - FastEthernetT, - Isdn, - FastEthernetFx, - Wireless80211, - AsymmetricDsl, - RateAdaptDsl, - SymmetricDsl, - VeryHighSpeedDsl, - IPOverAtm, - GigabitEthernet, - Tunnel, - MultiRateSymmetricDsl, - HighPerformanceSerialBus, - Wman, - Wwanpp, - Wwanpp2, - Bridge, -} - -interface Ipv4Net { - addr: Ipv4Addr; - netmask: Ipv4Addr; - prefixLength: number; -} - -interface Ipv6Net { - addr: Ipv6Addr; - netmask: Ipv6Addr; - prefixLength: number; -} - -interface Ipv6Addr { - octects: number[]; -} - -interface Ipv4Addr { - octects: number[]; -} - -interface MacAddress { - octects: number[]; -} diff --git a/packages/desktop/resources/sample-config.yaml b/packages/desktop/resources/sample-config.yaml index a3b35b15..5934a70c 100644 --- a/packages/desktop/resources/sample-config.yaml +++ b/packages/desktop/resources/sample-config.yaml @@ -108,18 +108,19 @@ window/bar: template/network: providers: ['network'] template: | - - @if (network.defaultInterface.type === 'Ethernet') { - @if (network.defaultGateway.isConnected !== true) { } - @else { } + @if (network.defaultInterface?.type === 'ethernet') { + + } @else if (network.defaultInterface?.type === 'wifi') { + @if (network.defaultGateway?.signalStrength >= 80) { } + @else if (network.defaultGateway?.signalStrength >= 65) { } + @else if (network.defaultGateway?.signalStrength >= 40) { } + @else if (network.defaultGateway?.signalStrength >= 25) { } + @else { } + {{ network.defaultGateway?.ssid }} + } @else { + } - @else if (network.defaultGateway.isConnected !== true) { } - @else if (network.defaultGateway.signalStrengthPercent >= 80) { } - @else if (network.defaultGateway.signalStrengthPercent >= 65) { } - @else if (network.defaultGateway.signalStrengthPercent >= 40) { } - @else if (network.defaultGateway.signalStrengthPercent >= 25) { } - @else { } template/memory: providers: ['memory'] diff --git a/packages/desktop/src/providers/network/mod.rs b/packages/desktop/src/providers/network/mod.rs index a4831acf..a4303490 100644 --- a/packages/desktop/src/providers/network/mod.rs +++ b/packages/desktop/src/providers/network/mod.rs @@ -1,7 +1,7 @@ mod config; mod provider; mod variables; -mod parse_netsh; +mod wifi_hotspot; pub use config::*; pub use provider::*; diff --git a/packages/desktop/src/providers/network/parse_netsh.rs b/packages/desktop/src/providers/network/parse_netsh.rs deleted file mode 100644 index 6b808db2..00000000 --- a/packages/desktop/src/providers/network/parse_netsh.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anyhow::Context; -use regex::Regex; - -// Runs the netsh command and parses the output -// to get the primary interface's SSID and signal strength. -// -// Unclear if the primary interface is always the default interface -// returned by the netdev/defaultnet crate - requires more testing. - -#[derive(Debug)] -pub struct NetshInfo { - pub ssid: Option, - pub signal: Option, - pub connected: bool, -} - -#[cfg(target_os = "windows")] -pub fn get_primary_interface_ssid_and_strength( -) -> anyhow::Result { - let ssid_match = Regex::new(r"(?m)^\s*SSID\s*:\s*(.*?)\r?$").unwrap(); - - let signal_match = - Regex::new(r"(?m)^\s*Signal\s*:\s*(.*?)\r?$").unwrap(); - - let signal_strip = Regex::new(r"(\d+)%").unwrap(); - - let connected_match = - Regex::new(r"(?m)^\s*State\s*:\s*(.*?)\r?$").unwrap(); - - let output = std::process::Command::new("netsh") - .args(&["wlan", "show", "interfaces"]) - .output() - .context("could not run netsh")?; - - let output = String::from_utf8_lossy(&output.stdout); - - let ssid = ssid_match - .captures(&output) - .map(|m| m.get(1).unwrap().as_str().to_string()); - let signal_str: Option<&str> = signal_match - .captures(&output) - .map(|m| m.get(1).unwrap().as_str()); - let signal: Option = signal_str - .and_then(|s| signal_strip.captures(s)) - .map(|m| m.get(1).unwrap().as_str().parse().unwrap()); - let connected: bool = connected_match - .captures(&output) - .map(|m| m.get(1).unwrap().as_str().to_string()) == Some("connected".to_string()); - - return Ok(NetshInfo { ssid, signal, connected }); -} - -#[cfg(not(target_os = "windows"))] -pub fn get_primary_interface_ssid_and_strength( -) -> anyhow::Result { - Ok(NetshInfo { - ssid: None, - signal: None, - connected: false, - }) -} diff --git a/packages/desktop/src/providers/network/provider.rs b/packages/desktop/src/providers/network/provider.rs index 38fa20d5..a55887bb 100644 --- a/packages/desktop/src/providers/network/provider.rs +++ b/packages/desktop/src/providers/network/provider.rs @@ -2,19 +2,16 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; -use netdev::interface::{ - get_default_interface, get_interfaces, -}; use tokio::{sync::Mutex, task::AbortHandle}; use crate::providers::{ - interval_provider::IntervalProvider, - network::parse_netsh::get_primary_interface_ssid_and_strength, - variables::ProviderVariables, + interval_provider::IntervalProvider, variables::ProviderVariables, }; use super::{ - Gateway, NetworkInterface, NetworkProviderConfig, NetworkVariables, + wifi_hotspot::{default_gateway_wifi, WifiHotstop}, + InterfaceType, NetworkGateway, NetworkInterface, NetworkProviderConfig, + NetworkVariables, }; pub struct NetworkProvider { @@ -31,6 +28,57 @@ impl NetworkProvider { _state: Arc::new(Mutex::new(())), } } + + fn transform_interface( + interface: &netdev::Interface, + ) -> NetworkInterface { + NetworkInterface { + name: interface.name.to_string(), + friendly_name: interface.friendly_name.clone(), + description: interface.description.clone(), + interface_type: InterfaceType::from(interface.if_type), + ipv4_addresses: interface + .ipv4 + .iter() + .map(|ip| ip.to_string()) + .collect(), + ipv6_addresses: interface + .ipv6 + .iter() + .map(|ip| ip.to_string()) + .collect(), + mac_address: interface.mac_addr.map(|mac| mac.to_string()), + transmit_speed: interface.transmit_speed, + receive_speed: interface.receive_speed, + dns_servers: interface + .dns_servers + .iter() + .map(|ip| ip.to_string()) + .collect(), + is_default: interface.default, + } + } + + fn transform_gateway( + gateway: &netdev::NetworkDevice, + wifi_hotspot: WifiHotstop, + ) -> NetworkGateway { + NetworkGateway { + mac_address: gateway.mac_addr.address(), + ipv4_addresses: gateway + .ipv4 + .iter() + .map(|ip| ip.to_string()) + .collect(), + ipv6_addresses: gateway + .ipv6 + .iter() + .map(|ip| ip.to_string()) + .collect(), + ssid: wifi_hotspot.ssid, + signal_strength: wifi_hotspot.signal_strength, + } + } } #[async_trait] @@ -58,67 +106,24 @@ impl IntervalProvider for NetworkProvider { _: &NetworkProviderConfig, _state: &(), ) -> Result { - let default_interface = get_default_interface().unwrap(); - - let interfaces = get_interfaces(); + let interfaces = netdev::get_interfaces(); - let default_gateway_ssid_and_strength = - get_primary_interface_ssid_and_strength()?; // Returns ssid = None, signal = None, connected = false if not on Windows for now + let default_interface = netdev::get_default_interface().ok(); let variables = NetworkVariables { - default_interface: NetworkInterface { - name: default_interface.name.clone(), - friendly_name: default_interface.friendly_name.clone(), - description: default_interface.description.clone(), - interface_type: default_interface.if_type, - ipv4: default_interface.ipv4.clone(), - ipv6: default_interface.ipv6.clone(), - mac_address: default_interface.mac_addr.unwrap(), - transmit_speed: default_interface.transmit_speed, - receive_speed: default_interface.receive_speed, - dns_servers: default_interface.dns_servers.clone(), - is_default: default_interface.default, - }, - default_gateway: Gateway { - mac_address: default_interface - .gateway - .as_ref() - .unwrap() - .mac_addr - .clone(), - ipv4_addresses: default_interface - .gateway - .as_ref() - .unwrap() - .ipv4 - .clone(), - ipv6_addresses: default_interface - .gateway - .as_ref() - .unwrap() - .ipv6 - .clone(), - ssid: default_gateway_ssid_and_strength.ssid.unwrap(), - signal_strength: default_gateway_ssid_and_strength - .signal - .unwrap(), - is_connected: default_gateway_ssid_and_strength.connected, - }, + default_interface: default_interface + .as_ref() + .map(Self::transform_interface), + default_gateway: default_interface + .and_then(|interface| interface.gateway) + .and_then(|gateway| { + default_gateway_wifi() + .map(|wifi| Self::transform_gateway(&gateway, wifi)) + .ok() + }), interfaces: interfaces .iter() - .map(|iface| NetworkInterface { - name: iface.name.clone(), - friendly_name: iface.friendly_name.clone(), - description: iface.description.clone(), - interface_type: iface.if_type.clone(), - ipv4: iface.ipv4.clone(), - ipv6: iface.ipv6.clone(), - mac_address: iface.mac_addr.unwrap().clone(), - transmit_speed: iface.transmit_speed, - receive_speed: iface.receive_speed, - dns_servers: iface.dns_servers.clone(), - is_default: iface.default, - }) + .map(Self::transform_interface) .collect(), }; diff --git a/packages/desktop/src/providers/network/variables.rs b/packages/desktop/src/providers/network/variables.rs index 1d9042aa..f8070fe3 100644 --- a/packages/desktop/src/providers/network/variables.rs +++ b/packages/desktop/src/providers/network/variables.rs @@ -1,14 +1,11 @@ use netdev::interface::InterfaceType as NdInterfaceType; -use netdev::ip::{Ipv4Net as NdIpv4Net, Ipv6Net as NdIpv6Net}; -use netdev::mac::MacAddr as NdMacAddr; -use serde::{Serialize, Serializer}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use serde::Serialize; #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct NetworkVariables { - pub default_interface: NetworkInterface, - pub default_gateway: Gateway, + pub default_interface: Option, + pub default_gateway: Option, pub interfaces: Vec, } @@ -18,203 +15,83 @@ pub struct NetworkInterface { pub name: String, pub friendly_name: Option, pub description: Option, - #[serde(serialize_with = "interfacetype_ser")] #[serde(rename = "type")] - pub interface_type: NdInterfaceType, - #[serde(serialize_with = "ipv4_ser")] - pub ipv4: Vec, - #[serde(serialize_with = "ipv6_ser")] - pub ipv6: Vec, - #[serde(serialize_with = "macaddr_ser")] - pub mac_address: NdMacAddr, + pub interface_type: InterfaceType, + pub ipv4_addresses: Vec, + pub ipv6_addresses: Vec, + pub mac_address: Option, pub transmit_speed: Option, pub receive_speed: Option, - pub dns_servers: Vec, + pub dns_servers: Vec, pub is_default: bool, } #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub struct Gateway { - #[serde(serialize_with = "macaddr_ser")] - pub mac_address: NdMacAddr, - pub ipv4_addresses: Vec, - pub ipv6_addresses: Vec, - pub ssid: String, - pub signal_strength: u32, - pub is_connected: bool, +pub struct NetworkGateway { + pub mac_address: String, + pub ipv4_addresses: Vec, + pub ipv6_addresses: Vec, + pub ssid: Option, + pub signal_strength: Option, } #[derive(Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub enum InterfaceType { Unknown, Ethernet, TokenRing, Fddi, - BasicIsdn, - PrimaryIsdn, Ppp, Loopback, - Ethernet3Megabit, Slip, Atm, GenericModem, - FastEthernetT, Isdn, - FastEthernetFx, - Wireless80211, - AsymmetricDsl, - RateAdaptDsl, - SymmetricDsl, - VeryHighSpeedDsl, - IPOverAtm, - GigabitEthernet, + Wifi, + Dsl, Tunnel, - MultiRateSymmetricDsl, HighPerformanceSerialBus, - Wman, - Wwanpp, - Wwanpp2, + MobileBroadband, Bridge, } -fn itype_to_local(itype: &NdInterfaceType) -> InterfaceType { - match itype { - NdInterfaceType::Unknown => InterfaceType::Unknown, - NdInterfaceType::Ethernet => InterfaceType::Ethernet, - NdInterfaceType::TokenRing => InterfaceType::TokenRing, - NdInterfaceType::Fddi => InterfaceType::Fddi, - NdInterfaceType::BasicIsdn => InterfaceType::BasicIsdn, - NdInterfaceType::PrimaryIsdn => InterfaceType::PrimaryIsdn, - NdInterfaceType::Ppp => InterfaceType::Ppp, - NdInterfaceType::Loopback => InterfaceType::Loopback, - NdInterfaceType::Ethernet3Megabit => InterfaceType::Ethernet3Megabit, - NdInterfaceType::Slip => InterfaceType::Slip, - NdInterfaceType::Atm => InterfaceType::Atm, - NdInterfaceType::GenericModem => InterfaceType::GenericModem, - NdInterfaceType::FastEthernetT => InterfaceType::FastEthernetT, - NdInterfaceType::Isdn => InterfaceType::Isdn, - NdInterfaceType::FastEthernetFx => InterfaceType::FastEthernetFx, - NdInterfaceType::Wireless80211 => InterfaceType::Wireless80211, - NdInterfaceType::AsymmetricDsl => InterfaceType::AsymmetricDsl, - NdInterfaceType::RateAdaptDsl => InterfaceType::RateAdaptDsl, - NdInterfaceType::SymmetricDsl => InterfaceType::SymmetricDsl, - NdInterfaceType::VeryHighSpeedDsl => InterfaceType::VeryHighSpeedDsl, - NdInterfaceType::IPOverAtm => InterfaceType::IPOverAtm, - NdInterfaceType::GigabitEthernet => InterfaceType::GigabitEthernet, - NdInterfaceType::Tunnel => InterfaceType::Tunnel, - NdInterfaceType::MultiRateSymmetricDsl => { - InterfaceType::MultiRateSymmetricDsl - } - NdInterfaceType::HighPerformanceSerialBus => { - InterfaceType::HighPerformanceSerialBus - } - NdInterfaceType::Wman => InterfaceType::Wman, - NdInterfaceType::Wwanpp => InterfaceType::Wwanpp, - NdInterfaceType::Wwanpp2 => InterfaceType::Wwanpp2, - NdInterfaceType::Bridge => InterfaceType::Bridge, - } -} - -fn interfacetype_ser( - itype: &NdInterfaceType, - serializer: S, -) -> Result { - let local_itype = itype_to_local(itype); - local_itype.serialize(serializer) -} - -#[derive(Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Ipv4Net { - pub address: Ipv4Addr, - pub prefix_length: u8, - pub netmask: Ipv4Addr, -} - -fn ipv4net_to_local(ipv4net: &Vec) -> Vec { - let mut result = Vec::new(); - for net in ipv4net { - match net { - NdIpv4Net { - addr, - prefix_len, - netmask, - } => { - result.push(Ipv4Net { - address: *addr, - prefix_length: *prefix_len, - netmask: *netmask, - }); +impl From for InterfaceType { + fn from(layout: NdInterfaceType) -> Self { + match layout { + NdInterfaceType::Unknown => InterfaceType::Unknown, + NdInterfaceType::Ethernet + | NdInterfaceType::Ethernet3Megabit + | NdInterfaceType::FastEthernetFx + | NdInterfaceType::FastEthernetT + | NdInterfaceType::GigabitEthernet => InterfaceType::Ethernet, + NdInterfaceType::TokenRing => InterfaceType::TokenRing, + NdInterfaceType::Fddi => InterfaceType::Fddi, + NdInterfaceType::Ppp => InterfaceType::Ppp, + NdInterfaceType::Loopback => InterfaceType::Loopback, + NdInterfaceType::Slip => InterfaceType::Slip, + NdInterfaceType::Atm | NdInterfaceType::IPOverAtm => { + InterfaceType::Atm } - } - } - result -} - -fn ipv4_ser( - ipv4net: &Vec, - serializer: S, -) -> Result { - let local_ipv4net = ipv4net_to_local(ipv4net); - local_ipv4net.serialize(serializer) -} - -#[derive(Serialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -struct Ipv6Net { - pub address: Ipv6Addr, - pub prefix_length: u8, - pub netmask: Ipv6Addr, -} - -fn ipv6net_to_local(ipv6net: &Vec) -> Vec { - let mut result = Vec::new(); - for net in ipv6net { - match net { - NdIpv6Net { - addr, - prefix_len, - netmask, - } => { - result.push(Ipv6Net { - address: *addr, - prefix_length: *prefix_len, - netmask: *netmask, - }); + NdInterfaceType::GenericModem => InterfaceType::GenericModem, + NdInterfaceType::Isdn + | NdInterfaceType::BasicIsdn + | NdInterfaceType::PrimaryIsdn => InterfaceType::Isdn, + NdInterfaceType::Wireless80211 => InterfaceType::Wifi, + NdInterfaceType::AsymmetricDsl + | NdInterfaceType::RateAdaptDsl + | NdInterfaceType::SymmetricDsl + | NdInterfaceType::VeryHighSpeedDsl + | NdInterfaceType::MultiRateSymmetricDsl => InterfaceType::Dsl, + NdInterfaceType::Tunnel => InterfaceType::Tunnel, + NdInterfaceType::HighPerformanceSerialBus => { + InterfaceType::HighPerformanceSerialBus } + NdInterfaceType::Wman + | NdInterfaceType::Wwanpp + | NdInterfaceType::Wwanpp2 => InterfaceType::MobileBroadband, + NdInterfaceType::Bridge => InterfaceType::Bridge, } } - result -} - -fn ipv6_ser( - ipv6net: &Vec, - serializer: S, -) -> Result { - let local_ipv6net = ipv6net_to_local(ipv6net); - local_ipv6net.serialize(serializer) -} - -#[derive(Serialize, Debug, Clone)] -pub struct MacAddr(pub u8, pub u8, pub u8, pub u8, pub u8, pub u8); - -fn macaddr_to_local(macaddr: &NdMacAddr) -> Option { - match macaddr { - NdMacAddr { .. } => { - let octets = macaddr.octets(); - Some(MacAddr( - octets[0], octets[1], octets[2], octets[3], octets[4], octets[5], - )) - } - } -} - -fn macaddr_ser( - macaddr: &NdMacAddr, - serializer: S, -) -> Result { - let local_macaddr = macaddr_to_local(macaddr); - local_macaddr.serialize(serializer) } diff --git a/packages/desktop/src/providers/network/wifi_hotspot.rs b/packages/desktop/src/providers/network/wifi_hotspot.rs new file mode 100644 index 00000000..a95d2a22 --- /dev/null +++ b/packages/desktop/src/providers/network/wifi_hotspot.rs @@ -0,0 +1,53 @@ +use std::process::Command; + +use anyhow::{Context, Result}; +use regex::Regex; + +#[derive(Debug)] +pub struct WifiHotstop { + pub ssid: Option, + pub signal_strength: Option, +} + +/// Runs the netsh command and parses the output to get the primary +/// interface's SSID and signal strength. +// +/// Unclear if the primary interface is always the default interface +/// returned by the netdev/defaultnet crate - requires more testing. +pub fn default_gateway_wifi() -> Result { + if cfg!(not(target_os = "windows")) { + return Ok(WifiHotstop { + ssid: None, + signal_strength: None, + }); + } + + let ssid_match = Regex::new(r"(?m)^\s*SSID\s*:\s*(.*?)\r?$").unwrap(); + + let signal_match = + Regex::new(r"(?m)^\s*Signal\s*:\s*(.*?)\r?$").unwrap(); + + let signal_strip = Regex::new(r"(\d+)%").unwrap(); + + let output = Command::new("netsh") + .args(&["wlan", "show", "interfaces"]) + .output() + .context("Could not run netsh.")?; + + let output = String::from_utf8_lossy(&output.stdout); + + let ssid = ssid_match + .captures(&output) + .map(|m| m.get(1).unwrap().as_str().to_string()); + let signal_str: Option<&str> = signal_match + .captures(&output) + .map(|m| m.get(1).unwrap().as_str()); + let signal: Option = signal_str + .and_then(|s| signal_strip.captures(s)) + .map(|m| m.get(1).unwrap().as_str().parse().unwrap()); + + return Ok(WifiHotstop { + ssid, + signal_strength: signal, + }); +}