use std::collections::BTreeMap; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; use std::path::Path; use async_stream::try_stream; use color_eyre::eyre::eyre; use futures::stream::BoxStream; use futures::{StreamExt, TryStreamExt}; use imbl_value::InternedString; use ipnet::{IpNet, Ipv4Net, Ipv6Net}; use nix::net::if_::if_nametoindex; use tokio::net::{TcpListener, TcpStream}; use tokio::process::Command; use crate::GatewayId; use crate::db::model::public::{IpInfo, NetworkInterfaceType}; use crate::prelude::*; use crate::util::Invoke; pub async fn load_ip_info() -> Result, Error> { let output = String::from_utf8( Command::new("ip") .arg("-o") .arg("addr") .arg("show") .invoke(crate::ErrorKind::Network) .await?, )?; let err_fn = || { Error::new( eyre!("malformed output from `ip`"), crate::ErrorKind::Network, ) }; let mut res = BTreeMap::::new(); for line in output.lines() { let split = line.split_ascii_whitespace().collect::>(); let iface = GatewayId::from(InternedString::from(*split.get(1).ok_or_else(&err_fn)?)); let subnet: IpNet = split.get(3).ok_or_else(&err_fn)?.parse()?; let ip_info = res.entry(iface.clone()).or_default(); ip_info.name = iface.into(); ip_info.scope_id = split .get(0) .ok_or_else(&err_fn)? .strip_suffix(":") .ok_or_else(&err_fn)? .parse()?; ip_info.subnets.insert(subnet); } for (id, ip_info) in res.iter_mut() { ip_info.device_type = probe_iface_type(id.as_str()).await; } Ok(res) } pub fn ipv6_is_link_local(addr: Ipv6Addr) -> bool { (addr.segments()[0] & 0xffc0) == 0xfe80 } pub fn ipv6_is_local(addr: Ipv6Addr) -> bool { addr.is_loopback() || (addr.segments()[0] & 0xfe00) == 0xfc00 || ipv6_is_link_local(addr) } pub fn is_private_ip(addr: IpAddr) -> bool { match addr { IpAddr::V4(v4) => v4.is_private() || v4.is_loopback() || v4.is_link_local(), IpAddr::V6(v6) => ipv6_is_local(v6), } } fn parse_iface_ip(output: &str) -> Result, Error> { let output = output.trim(); if output.is_empty() { return Ok(Vec::new()); } let mut res = Vec::new(); for line in output.lines() { if let Some(ip) = line.split_ascii_whitespace().nth(3) { res.push(ip) } else { return Err(Error::new( eyre!("malformed output from `ip`"), crate::ErrorKind::Network, )); } } Ok(res) } pub async fn get_iface_ipv4_addr(iface: &str) -> Result, Error> { Ok(parse_iface_ip(&String::from_utf8( Command::new("ip") .arg("-4") .arg("-o") .arg("addr") .arg("show") .arg(iface) .invoke(crate::ErrorKind::Network) .await?, )?)? .into_iter() .map(|s| Ok::<_, Error>((s.split("/").next().unwrap().parse()?, s.parse()?))) .next() .transpose()?) } pub async fn get_iface_ipv6_addr(iface: &str) -> Result, Error> { Ok(parse_iface_ip(&String::from_utf8( Command::new("ip") .arg("-6") .arg("-o") .arg("addr") .arg("show") .arg(iface) .invoke(crate::ErrorKind::Network) .await?, )?)? .into_iter() .find(|ip| !ip.starts_with("fe80::")) .map(|s| Ok::<_, Error>((s.split("/").next().unwrap().parse()?, s.parse()?))) .transpose()?) } pub async fn probe_iface_type(iface: &str) -> Option { match tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("uevent")) .await .ok()? .lines() .find_map(|l| l.strip_prefix("DEVTYPE=")) { Some("wlan") => Some(NetworkInterfaceType::Wireless), Some("bridge") => Some(NetworkInterfaceType::Bridge), Some("wireguard") => Some(NetworkInterfaceType::Wireguard), None if iface_is_physical(iface).await => Some(NetworkInterfaceType::Ethernet), None if iface_is_loopback(iface).await => Some(NetworkInterfaceType::Loopback), _ => None, } } pub async fn iface_is_physical(iface: &str) -> bool { tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("device")) .await .is_ok() } pub async fn iface_is_wireless(iface: &str) -> bool { tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("wireless")) .await .is_ok() } pub async fn iface_is_bridge(iface: &str) -> bool { tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("bridge")) .await .is_ok() } pub async fn iface_is_loopback(iface: &str) -> bool { tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("type")) .await .ok() .map_or(false, |x| x.trim() == "772") } pub fn list_interfaces() -> BoxStream<'static, Result> { try_stream! { let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?; while let Some(iface) = ifaces.next_entry().await? { if let Some(iface) = iface.file_name().into_string().ok() { yield iface; } } } .boxed() } pub async fn find_wifi_iface() -> Result, Error> { let mut ifaces = list_interfaces(); while let Some(iface) = ifaces.try_next().await? { if iface_is_wireless(&iface).await { return Ok(Some(iface)); } } Ok(None) } pub async fn find_eth_iface() -> Result { let mut ifaces = list_interfaces(); while let Some(iface) = ifaces.try_next().await? { if iface_is_physical(&iface).await && !iface_is_wireless(&iface).await { return Ok(iface); } } Err(Error::new( eyre!("Could not detect ethernet interface"), crate::ErrorKind::Network, )) } pub async fn all_socket_addrs_for(port: u16) -> Result, Error> { let mut res = Vec::new(); let raw = String::from_utf8( Command::new("ip") .arg("-o") .arg("addr") .arg("show") .invoke(ErrorKind::ParseSysInfo) .await?, )?; let err = |item: &str, lineno: usize, line: &str| { Error::new( eyre!("failed to parse ip info ({item}[line:{lineno}]) from {line:?}"), ErrorKind::ParseSysInfo, ) }; for (idx, line) in raw .lines() .map(|l| l.trim()) .enumerate() .filter(|(_, l)| !l.is_empty()) { let mut split = line.split_whitespace(); let _num = split.next(); let ifname = split.next().ok_or_else(|| err("ifname", idx, line))?; let _kind = split.next(); let ipnet_str = split.next().ok_or_else(|| err("ipnet", idx, line))?; let ipnet = ipnet_str .parse::() .with_ctx(|_| (ErrorKind::ParseSysInfo, err("ipnet", idx, ipnet_str)))?; match ipnet.addr() { IpAddr::V4(ip4) => res.push((ifname.into(), SocketAddr::new(ip4.into(), port))), IpAddr::V6(ip6) => res.push(( ifname.into(), SocketAddr::V6(SocketAddrV6::new( ip6, port, 0, if_nametoindex(ifname) .with_ctx(|_| (ErrorKind::ParseSysInfo, "reading scope_id"))?, )), )), } } Ok(res) } pub struct TcpListeners { listeners: Vec, } impl TcpListeners { pub fn new(listeners: impl IntoIterator) -> Self { Self { listeners: listeners.into_iter().collect(), } } pub async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> { futures::future::select_all(self.listeners.iter().map(|l| Box::pin(l.accept()))) .await .0 } } // impl hyper::server::accept::Accept for TcpListeners { // type Conn = TcpStream; // type Error = std::io::Error; // fn poll_accept( // self: std::pin::Pin<&mut Self>, // cx: &mut std::task::Context<'_>, // ) -> std::task::Poll>> { // for listener in self.listeners.iter() { // let poll = listener.poll_accept(cx); // if poll.is_ready() { // return poll.map(|a| a.map(|a| a.0)).map(Some); // } // } // std::task::Poll::Pending // } // } // TODO