1use neli::attr::Attribute as _;
2use neli::consts::{nl::*, rtnl::*, socket::*};
3use neli::nl::{NlPayload, Nlmsghdr};
4use neli::rtnl::{Ifaddrmsg, Ifinfomsg, Rtmsg};
5use neli::socket::{NlSocketHandle, tokio::NlSocket};
6use neli::types::RtBuffer;
7
8use regex::Regex;
9
10use libc::c_uchar;
11
12use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
13use std::ops;
14use std::path::Path;
15
16use crate::errors::*;
17use crate::util;
18
19const RT_SCOPE_HOST: c_uchar = 254;
21
22#[derive(Debug)]
23pub struct NetDevice {
24 pub iface: Interface,
25 pub wifi_info: Option<WifiInfo>,
26 pub ip: Option<Ipv4Addr>,
27 pub ipv6: Option<Ipv6Addr>,
28 pub icon: &'static str,
29 pub tun_wg_ppp: bool,
30 pub nameservers: Vec<IpAddr>,
31}
32
33#[derive(Debug, Default)]
34pub struct WifiInfo {
35 pub ssid: Option<String>,
36 pub signal: Option<f64>,
37 pub frequency: Option<f64>,
38 pub bitrate: Option<f64>,
39}
40
41impl NetDevice {
42 pub async fn new(iface_re: Option<&Regex>) -> Result<Option<Self>> {
43 let mut sock = NlSocket::new(
44 NlSocketHandle::connect(NlFamily::Route, None, &[]).error("Socket error")?,
45 )
46 .error("Socket error")?;
47
48 let mut ifaces = get_interfaces(&mut sock, iface_re)
49 .await
50 .map_err(BoxErrorWrapper)
51 .error("Failed to fetch interfaces")?;
52 if ifaces.is_empty() {
53 return Ok(None);
54 }
55
56 let default_iface = get_default_interface(&mut sock)
57 .await
58 .map_err(BoxErrorWrapper)
59 .error("Failed to get default interface")?;
60
61 let iface_position = ifaces
62 .iter()
63 .position(|i| i.index == default_iface)
64 .or_else(|| ifaces.iter().position(|i| i.operstate == Operstate::Up))
65 .unwrap_or(0);
66
67 let iface = ifaces.swap_remove(iface_position);
68 let wifi_info = WifiInfo::new(iface.index).await?;
69 let ip = ipv4(&mut sock, iface.index).await?;
70 let ipv6 = ipv6(&mut sock, iface.index).await?;
71 let nameservers = read_nameservers()
72 .await
73 .error("Failed to read nameservers")?;
74
75 let path = Path::new("/sys/class/net").join(&iface.name);
78 let tun = iface.name.starts_with("tun")
79 || iface.name.starts_with("tap")
80 || path.join("tun_flags").exists();
81 let (wg, ppp) = util::read_file(path.join("uevent"))
82 .await
83 .map_or((false, false), |c| {
84 (c.contains("wireguard"), c.contains("ppp"))
85 });
86
87 let icon = if wifi_info.is_some() {
88 "net_wireless"
89 } else if tun || wg || ppp {
90 "net_vpn"
91 } else if iface.name == "lo" {
92 "net_loopback"
93 } else {
94 "net_wired"
95 };
96
97 Ok(Some(Self {
98 iface,
99 wifi_info,
100 ip,
101 ipv6,
102 icon,
103 tun_wg_ppp: tun | wg | ppp,
104 nameservers,
105 }))
106 }
107
108 pub fn is_up(&self) -> bool {
109 self.tun_wg_ppp
110 || self.iface.operstate == Operstate::Up
111 || (self.iface.operstate == Operstate::Unknown
112 && (self.ip.is_some() || self.ipv6.is_some()))
113 }
114
115 pub fn ssid(&self) -> Option<String> {
116 self.wifi_info.as_ref()?.ssid.clone()
117 }
118
119 pub fn frequency(&self) -> Option<f64> {
120 self.wifi_info.as_ref()?.frequency
121 }
122
123 pub fn bitrate(&self) -> Option<f64> {
124 self.wifi_info.as_ref()?.bitrate
125 }
126
127 pub fn signal(&self) -> Option<f64> {
128 self.wifi_info.as_ref()?.signal
129 }
130}
131
132impl WifiInfo {
133 async fn new(if_index: i32) -> Result<Option<Self>> {
134 fn signal_percents(raw: f64) -> f64 {
136 const MAX_LEVEL: f64 = -20.;
137 const MIN_LEVEL: f64 = -85.;
138 const DIFF: f64 = MAX_LEVEL - MIN_LEVEL;
139 (100. - (MAX_LEVEL - raw) * (15. * DIFF + 62. * (MAX_LEVEL - raw)) / (DIFF * DIFF))
140 .clamp(0., 100.)
141 }
142
143 fn ssid_from_bss_info_elements(mut bytes: &[u8]) -> Option<String> {
144 while bytes.len() > 2 && bytes[0] != 0 {
145 bytes = &bytes[(bytes[1] as usize + 2)..];
146 }
147
148 if bytes.len() < 2 || bytes.len() < bytes[1] as usize + 2 {
149 return None;
150 };
151
152 let ssid_len = bytes[1] as usize;
153 let raw_ssid = &bytes[2..][..ssid_len];
154
155 Some(String::from_utf8_lossy(raw_ssid).into_owned())
156 }
157
158 let Ok(mut socket) = neli_wifi::AsyncSocket::connect() else {
160 return Ok(None);
161 };
162
163 let interfaces = socket
164 .get_interfaces_info()
165 .await
166 .error("Failed to get nl80211 interfaces")?;
167
168 for interface in interfaces {
169 if let Some(index) = interface.index {
170 if index != if_index {
171 continue;
172 }
173
174 let Ok(ap) = socket.get_station_info(index).await else {
175 continue;
176 };
177
178 let Some(ap) = ap.into_iter().next() else {
180 continue;
181 };
182
183 let bss = socket
184 .get_bss_info(index)
185 .await
186 .unwrap_or_default()
187 .into_iter()
188 .find(|bss| bss.status == Some(1));
189
190 let raw_signal = match ap.signal {
191 Some(signal) => Some(signal),
192 None => bss
193 .as_ref()
194 .and_then(|bss| bss.signal)
195 .map(|s| (s / 100) as i8),
196 };
197
198 let ssid = interface
199 .ssid
200 .as_deref()
201 .map(|ssid| String::from_utf8_lossy(ssid).into_owned())
202 .or_else(|| {
203 bss.as_ref()
204 .and_then(|bss| bss.information_elements.as_deref())
205 .and_then(ssid_from_bss_info_elements)
206 });
207
208 return Ok(Some(Self {
209 ssid,
210 frequency: interface.frequency.map(|f| f as f64 * 1e6),
211 signal: raw_signal.map(|s| signal_percents(s as f64)),
212 bitrate: ap.tx_bitrate.map(|b| b as f64 * 1e5), }));
214 }
215 }
216 Ok(None)
217 }
218}
219
220#[derive(Debug, Default, Clone, Copy)]
221pub struct InterfaceStats {
222 pub rx_bytes: u64,
223 pub tx_bytes: u64,
224}
225
226impl ops::Sub for InterfaceStats {
227 type Output = Self;
228
229 fn sub(mut self, rhs: Self) -> Self::Output {
230 self.rx_bytes = self.rx_bytes.saturating_sub(rhs.rx_bytes);
231 self.tx_bytes = self.tx_bytes.saturating_sub(rhs.tx_bytes);
232 self
233 }
234}
235
236impl InterfaceStats {
237 fn from_stats64(stats: &[u8]) -> Self {
238 assert!(stats.len() >= 8 * 4);
249 Self {
250 rx_bytes: u64::from_ne_bytes(stats[16..24].try_into().unwrap()),
251 tx_bytes: u64::from_ne_bytes(stats[24..32].try_into().unwrap()),
252 }
253 }
254}
255
256#[derive(Debug)]
257pub struct Interface {
258 pub index: i32,
259 pub operstate: Operstate,
260 pub name: String,
261 pub stats: Option<InterfaceStats>,
262}
263
264macro_rules! recv_until_done {
265 ($sock:ident, $payload:ident: $payload_type:ty => $($code:tt)*) => {
266 let mut buf = Vec::new();
267 'recv: loop {
268 let msgs = $sock.recv::<u16, $payload_type>(&mut buf).await?;
269 for msg in msgs {
270 if msg.nl_type == libc::NLMSG_DONE as u16 {
271 break 'recv;
272 }
273 if let NlPayload::Payload($payload) = msg.nl_payload {
274 $($code)*
275 }
276 }
277 }
278 };
279}
280
281async fn get_interfaces(
282 sock: &mut NlSocket,
283 filter: Option<&Regex>,
284) -> Result<Vec<Interface>, Box<dyn StdError + Send + Sync + 'static>> {
285 sock.send(&Nlmsghdr::new(
286 None,
287 Rtm::Getlink,
288 NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
289 None,
290 None,
291 NlPayload::Payload(Ifinfomsg::new(
292 RtAddrFamily::Unspecified,
293 Arphrd::None,
294 0,
295 IffFlags::empty(),
296 IffFlags::empty(),
297 RtBuffer::new(),
298 )),
299 ))
300 .await?;
301
302 let mut interfaces = Vec::new();
303
304 recv_until_done!(sock, msg: Ifinfomsg => {
305 let mut name = None;
306 let mut stats = None;
307 let mut operstate = Operstate::Unknown;
308 for attr in msg.rtattrs.iter() {
309 match attr.rta_type {
310 Ifla::Ifname => name = Some(attr.get_payload_as_with_len()?),
311 Ifla::Stats64 => stats = Some(InterfaceStats::from_stats64(attr.payload().as_ref())),
312 Ifla::Operstate => operstate = attr.get_payload_as::<u8>()?.into(),
313 _ => (),
314 }
315 }
316 let name: String = name.unwrap();
317 if filter.is_none_or(|f| f.is_match(&name)) {
318 interfaces.push(Interface {
319 index: msg.ifi_index,
320 operstate,
321 name,
322 stats,
323 });
324 }
325 });
326
327 Ok(interfaces)
328}
329
330async fn get_default_interface(
331 sock: &mut NlSocket,
332) -> Result<i32, Box<dyn StdError + Send + Sync + 'static>> {
333 sock.send(&Nlmsghdr::new(
334 None,
335 Rtm::Getroute,
336 NlmFFlags::new(&[NlmF::Request, NlmF::Dump]),
337 None,
338 None,
339 NlPayload::Payload(Rtmsg {
340 rtm_family: RtAddrFamily::Inet,
341 rtm_dst_len: 0,
342 rtm_src_len: 0,
343 rtm_tos: 0,
344 rtm_table: RtTable::Unspec,
345 rtm_protocol: Rtprot::Unspec,
346 rtm_scope: RtScope::Universe,
347 rtm_type: Rtn::Unspec,
348 rtm_flags: RtmFFlags::empty(),
349 rtattrs: RtBuffer::new(),
350 }),
351 ))
352 .await?;
353
354 let mut default_index = 0;
355
356 recv_until_done!(sock, msg: Rtmsg => {
357 if msg.rtm_type != Rtn::Unicast {
358 continue;
359 }
360 let mut index = None;
361 let mut is_default = false;
362 for attr in msg.rtattrs.iter() {
363 match attr.rta_type {
364 Rta::Oif => index = Some(attr.get_payload_as::<i32>()?),
365 Rta::Gateway => is_default = true,
366 _ => (),
367 }
368 }
369 if is_default && default_index == 0 {
370 default_index = index.unwrap();
371 }
372 });
373
374 Ok(default_index)
375}
376
377async fn ip_payload<const BYTES: usize>(
378 sock: &mut NlSocket,
379 ifa_family: RtAddrFamily,
380 ifa_index: i32,
381) -> Result<Option<[u8; BYTES]>, Box<dyn StdError + Send + Sync + 'static>> {
382 sock.send(&Nlmsghdr::new(
383 None,
384 Rtm::Getaddr,
385 NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
386 None,
387 None,
388 NlPayload::Payload(Ifaddrmsg {
389 ifa_family,
390 ifa_prefixlen: 0,
391 ifa_flags: IfaFFlags::empty(),
392 ifa_scope: 0,
393 ifa_index: 0,
394 rtattrs: RtBuffer::new(),
395 }),
396 ))
397 .await?;
398
399 let mut payload = None;
400
401 recv_until_done!(sock, msg: Ifaddrmsg => {
402 if msg.ifa_index != ifa_index || msg.ifa_scope >= RT_SCOPE_HOST || payload.is_some() {
403 continue;
404 }
405
406 let attr_handle = msg.rtattrs.get_attr_handle();
407
408 let Some(attr) = attr_handle.get_attribute(Ifa::Local)
409 .or_else(|| attr_handle.get_attribute(Ifa::Address))
410 else { continue };
411
412 if let Ok(p) = attr.rta_payload.as_ref().try_into() {
413 payload = Some(p);
414 }
415 });
416
417 Ok(payload)
418}
419
420async fn ipv4(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv4Addr>> {
421 Ok(ip_payload(sock, RtAddrFamily::Inet, ifa_index)
422 .await
423 .map_err(BoxErrorWrapper)
424 .error("Failed to get IP address")?
425 .map(Ipv4Addr::from))
426}
427
428async fn ipv6(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv6Addr>> {
429 Ok(ip_payload(sock, RtAddrFamily::Inet6, ifa_index)
430 .await
431 .map_err(BoxErrorWrapper)
432 .error("Failed to get IPv6 address")?
433 .map(Ipv6Addr::from))
434}
435
436async fn read_nameservers() -> Result<Vec<IpAddr>> {
437 let file = util::read_file("/etc/resolv.conf")
438 .await
439 .error("Failed to read /etc/resolv.conf")?;
440 let mut nameservers = Vec::new();
441
442 for line in file.lines() {
443 let mut line_parts = line.split_whitespace();
444 if line_parts.next() == Some("nameserver") {
445 if let Some(mut ip) = line_parts.next() {
446 if let Some((without_zone_id, _zone_id)) = ip.split_once('%') {
448 ip = without_zone_id;
449 }
450 nameservers.push(ip.parse().error("Unable to parse ip")?);
451 }
452 }
453 }
454
455 Ok(nameservers)
456}
457
458#[derive(Debug, PartialEq, Eq)]
460pub enum Operstate {
461 Unknown,
465 Notpresent,
468 Down,
471 Lowerlayerdown,
474 Testing,
476 Dormant,
479 Up,
481}
482
483impl From<u8> for Operstate {
484 fn from(value: u8) -> Self {
485 match value {
486 1 => Self::Notpresent,
487 2 => Self::Down,
488 3 => Self::Lowerlayerdown,
489 4 => Self::Testing,
490 5 => Self::Dormant,
491 6 => Self::Up,
492 _ => Self::Unknown,
493 }
494 }
495}