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                if index != if_index {
171                    continue;
172                }
173
174                let Ok(ap) = socket.get_station_info(index).await else {
175                    continue;
176                };
177
178                // TODO: are there any situations when there is more than one station?
179                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), // 100kbit/s -> bit/s
213                }));
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        // stats looks something like that:
239        //
240        // #[repr(C)]
241        // struct RtnlLinkStats64 {
242        //     rx_packets: u64,
243        //     tx_packets: u64,
244        //     rx_bytes: u64,
245        //     tx_bytes: u64,
246        //     // the rest is omitted
247        // }
248        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                // TODO: use the zone id somehow?
447                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// Source: https://www.kernel.org/doc/Documentation/networking/operstates.txt
459#[derive(Debug, PartialEq, Eq)]
460pub enum Operstate {
461    /// Interface is in unknown state, neither driver nor userspace has set
462    /// operational state. Interface must be considered for user data as
463    /// setting operational state has not been implemented in every driver.
464    Unknown,
465    /// Unused in current kernel (notpresent interfaces normally disappear),
466    /// just a numerical placeholder.
467    Notpresent,
468    /// Interface is unable to transfer data on L1, f.e. ethernet is not
469    /// plugged or interface is ADMIN down.
470    Down,
471    /// Interfaces stacked on an interface that is IF_OPER_DOWN show this
472    /// state (f.e. VLAN).
473    Lowerlayerdown,
474    /// Unused in current kernel.
475    Testing,
476    /// Interface is L1 up, but waiting for an external event, f.e. for a
477    /// protocol to establish. (802.1X)
478    Dormant,
479    /// Interface is operational up and can be used.
480    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}