i3status_rs/
netlink.rs

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
19// From `linux/rtnetlink.h`
20const 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        // TODO: use netlink for the these too
76        // I don't believe that this should ever change, so set it now:
77        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        /// <https://github.com/torvalds/linux/blob/9ff9b0d392ea08090cd1780fb196f36dbb586529/drivers/net/wireless/intel/ipw2x00/ipw2200.c#L4322-L4334>
135        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        // Ignore connection error because `nl80211` might not be enabled on the system.
159        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                && index == if_index
171                && let Ok(ap) = socket.get_station_info(index).await
172            {
173                // TODO: are there any situations when there is more than one station?
174                let Some(ap) = ap.into_iter().next() else {
175                    continue;
176                };
177
178                let bss = socket
179                    .get_bss_info(index)
180                    .await
181                    .unwrap_or_default()
182                    .into_iter()
183                    .find(|bss| bss.status == Some(1));
184
185                let raw_signal = match ap.signal {
186                    Some(signal) => Some(signal),
187                    None => bss
188                        .as_ref()
189                        .and_then(|bss| bss.signal)
190                        .map(|s| (s / 100) as i8),
191                };
192
193                let ssid = interface
194                    .ssid
195                    .as_deref()
196                    .map(|ssid| String::from_utf8_lossy(ssid).into_owned())
197                    .or_else(|| {
198                        bss.as_ref()
199                            .and_then(|bss| bss.information_elements.as_deref())
200                            .and_then(ssid_from_bss_info_elements)
201                    });
202
203                return Ok(Some(Self {
204                    ssid,
205                    frequency: interface.frequency.map(|f| f as f64 * 1e6),
206                    signal: raw_signal.map(|s| signal_percents(s as f64)),
207                    bitrate: ap.tx_bitrate.map(|b| b as f64 * 1e5), // 100kbit/s -> bit/s
208                }));
209            }
210        }
211        Ok(None)
212    }
213}
214
215#[derive(Debug, Default, Clone, Copy)]
216pub struct InterfaceStats {
217    pub rx_bytes: u64,
218    pub tx_bytes: u64,
219}
220
221impl ops::Sub for InterfaceStats {
222    type Output = Self;
223
224    fn sub(mut self, rhs: Self) -> Self::Output {
225        self.rx_bytes = self.rx_bytes.saturating_sub(rhs.rx_bytes);
226        self.tx_bytes = self.tx_bytes.saturating_sub(rhs.tx_bytes);
227        self
228    }
229}
230
231impl InterfaceStats {
232    fn from_stats64(stats: &[u8]) -> Self {
233        // stats looks something like that:
234        //
235        // #[repr(C)]
236        // struct RtnlLinkStats64 {
237        //     rx_packets: u64,
238        //     tx_packets: u64,
239        //     rx_bytes: u64,
240        //     tx_bytes: u64,
241        //     // the rest is omitted
242        // }
243        assert!(stats.len() >= 8 * 4);
244        Self {
245            rx_bytes: u64::from_ne_bytes(stats[16..24].try_into().unwrap()),
246            tx_bytes: u64::from_ne_bytes(stats[24..32].try_into().unwrap()),
247        }
248    }
249}
250
251#[derive(Debug)]
252pub struct Interface {
253    pub index: i32,
254    pub operstate: Operstate,
255    pub name: String,
256    pub stats: Option<InterfaceStats>,
257}
258
259macro_rules! recv_until_done {
260    ($sock:ident, $payload:ident: $payload_type:ty => $($code:tt)*) => {
261        let mut buf = Vec::new();
262        'recv: loop {
263            let msgs = $sock.recv::<u16, $payload_type>(&mut buf).await?;
264            for msg in msgs {
265                if msg.nl_type == libc::NLMSG_DONE as u16 {
266                    break 'recv;
267                }
268                if let NlPayload::Payload($payload) = msg.nl_payload {
269                    $($code)*
270                }
271            }
272        }
273    };
274}
275
276async fn get_interfaces(
277    sock: &mut NlSocket,
278    filter: Option<&Regex>,
279) -> Result<Vec<Interface>, Box<dyn StdError + Send + Sync + 'static>> {
280    sock.send(&Nlmsghdr::new(
281        None,
282        Rtm::Getlink,
283        NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
284        None,
285        None,
286        NlPayload::Payload(Ifinfomsg::new(
287            RtAddrFamily::Unspecified,
288            Arphrd::None,
289            0,
290            IffFlags::empty(),
291            IffFlags::empty(),
292            RtBuffer::new(),
293        )),
294    ))
295    .await?;
296
297    let mut interfaces = Vec::new();
298
299    recv_until_done!(sock, msg: Ifinfomsg => {
300        let mut name = None;
301        let mut stats = None;
302        let mut operstate = Operstate::Unknown;
303        for attr in msg.rtattrs.iter() {
304            match attr.rta_type {
305                Ifla::Ifname => name = Some(attr.get_payload_as_with_len()?),
306                Ifla::Stats64 => stats = Some(InterfaceStats::from_stats64(attr.payload().as_ref())),
307                Ifla::Operstate => operstate = attr.get_payload_as::<u8>()?.into(),
308                _ => (),
309            }
310        }
311        let name: String = name.unwrap();
312        if filter.is_none_or(|f| f.is_match(&name)) {
313            interfaces.push(Interface {
314                index: msg.ifi_index,
315                operstate,
316                name,
317                stats,
318            });
319        }
320    });
321
322    Ok(interfaces)
323}
324
325async fn get_default_interface(
326    sock: &mut NlSocket,
327) -> Result<i32, Box<dyn StdError + Send + Sync + 'static>> {
328    sock.send(&Nlmsghdr::new(
329        None,
330        Rtm::Getroute,
331        NlmFFlags::new(&[NlmF::Request, NlmF::Dump]),
332        None,
333        None,
334        NlPayload::Payload(Rtmsg {
335            rtm_family: RtAddrFamily::Inet,
336            rtm_dst_len: 0,
337            rtm_src_len: 0,
338            rtm_tos: 0,
339            rtm_table: RtTable::Unspec,
340            rtm_protocol: Rtprot::Unspec,
341            rtm_scope: RtScope::Universe,
342            rtm_type: Rtn::Unspec,
343            rtm_flags: RtmFFlags::empty(),
344            rtattrs: RtBuffer::new(),
345        }),
346    ))
347    .await?;
348
349    let mut best_index = 0;
350    let mut best_metric = u32::MAX;
351
352    recv_until_done!(sock, msg: Rtmsg => {
353        if msg.rtm_type != Rtn::Unicast {
354            continue;
355        }
356        // Only check default routes (rtm_dst_len == 0)
357        if msg.rtm_dst_len != 0 {
358            continue;
359        }
360
361        let mut index = None;
362        let mut metric = 0u32;
363        for attr in msg.rtattrs.iter() {
364            match attr.rta_type {
365                Rta::Oif => index = Some(attr.get_payload_as::<i32>()?),
366                Rta::Priority => metric = attr.get_payload_as::<u32>()?,
367                _ => (),
368            }
369        }
370        if let Some(i) = index
371            && metric < best_metric {
372                best_metric = metric;
373                best_index = i;
374            }
375    });
376
377    Ok(best_index)
378}
379
380async fn ip_payload<const BYTES: usize>(
381    sock: &mut NlSocket,
382    ifa_family: RtAddrFamily,
383    ifa_index: i32,
384) -> Result<Option<[u8; BYTES]>, Box<dyn StdError + Send + Sync + 'static>> {
385    sock.send(&Nlmsghdr::new(
386        None,
387        Rtm::Getaddr,
388        NlmFFlags::new(&[NlmF::Dump, NlmF::Request]),
389        None,
390        None,
391        NlPayload::Payload(Ifaddrmsg {
392            ifa_family,
393            ifa_prefixlen: 0,
394            ifa_flags: IfaFFlags::empty(),
395            ifa_scope: 0,
396            ifa_index: 0,
397            rtattrs: RtBuffer::new(),
398        }),
399    ))
400    .await?;
401
402    let mut payload = None;
403
404    recv_until_done!(sock, msg: Ifaddrmsg => {
405        if msg.ifa_index != ifa_index || msg.ifa_scope >= RT_SCOPE_HOST || payload.is_some() {
406            continue;
407        }
408
409        let attr_handle = msg.rtattrs.get_attr_handle();
410
411        if let Some(attr) = attr_handle.get_attribute(Ifa::Local)
412            .or_else(|| attr_handle.get_attribute(Ifa::Address))
413            && let Ok(p) = attr.rta_payload.as_ref().try_into()
414        {
415            payload = Some(p);
416        }
417    });
418
419    Ok(payload)
420}
421
422async fn ipv4(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv4Addr>> {
423    Ok(ip_payload(sock, RtAddrFamily::Inet, ifa_index)
424        .await
425        .map_err(BoxErrorWrapper)
426        .error("Failed to get IP address")?
427        .map(Ipv4Addr::from))
428}
429
430async fn ipv6(sock: &mut NlSocket, ifa_index: i32) -> Result<Option<Ipv6Addr>> {
431    Ok(ip_payload(sock, RtAddrFamily::Inet6, ifa_index)
432        .await
433        .map_err(BoxErrorWrapper)
434        .error("Failed to get IPv6 address")?
435        .map(Ipv6Addr::from))
436}
437
438async fn read_nameservers() -> Result<Vec<IpAddr>> {
439    let file = util::read_file("/etc/resolv.conf")
440        .await
441        .error("Failed to read /etc/resolv.conf")?;
442    let mut nameservers = Vec::new();
443
444    for line in file.lines() {
445        let mut line_parts = line.split_whitespace();
446        if line_parts.next() == Some("nameserver")
447            && let Some(mut ip) = line_parts.next()
448        {
449            // TODO: use the zone id somehow?
450            if let Some((without_zone_id, _zone_id)) = ip.split_once('%') {
451                ip = without_zone_id;
452            }
453            nameservers.push(ip.parse().error("Unable to parse ip")?);
454        }
455    }
456
457    Ok(nameservers)
458}
459
460// Source: https://www.kernel.org/doc/Documentation/networking/operstates.txt
461#[derive(Debug, PartialEq, Eq)]
462pub enum Operstate {
463    /// Interface is in unknown state, neither driver nor userspace has set
464    /// operational state. Interface must be considered for user data as
465    /// setting operational state has not been implemented in every driver.
466    Unknown,
467    /// Unused in current kernel (notpresent interfaces normally disappear),
468    /// just a numerical placeholder.
469    Notpresent,
470    /// Interface is unable to transfer data on L1, f.e. ethernet is not
471    /// plugged or interface is ADMIN down.
472    Down,
473    /// Interfaces stacked on an interface that is IF_OPER_DOWN show this
474    /// state (f.e. VLAN).
475    Lowerlayerdown,
476    /// Unused in current kernel.
477    Testing,
478    /// Interface is L1 up, but waiting for an external event, f.e. for a
479    /// protocol to establish. (802.1X)
480    Dormant,
481    /// Interface is operational up and can be used.
482    Up,
483}
484
485impl From<u8> for Operstate {
486    fn from(value: u8) -> Self {
487        match value {
488            1 => Self::Notpresent,
489            2 => Self::Down,
490            3 => Self::Lowerlayerdown,
491            4 => Self::Testing,
492            5 => Self::Dormant,
493            6 => Self::Up,
494            _ => Self::Unknown,
495        }
496    }
497}