Last active
August 22, 2022 08:45
-
-
Save MaxVerevkin/6419a79ecb8b85d9e0ec90a48c856d02 to your computer and use it in GitHub Desktop.
list interfaces
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // [dependencies] | |
| // neli = "0.6.2" | |
| #![allow(dead_code)] | |
| use std::convert::TryInto; | |
| use std::error::Error; | |
| use std::net::{Ipv4Addr, Ipv6Addr}; | |
| use neli::attr::Attribute; | |
| use neli::consts::{ | |
| nl::{NlmF, NlmFFlags}, | |
| rtnl::{Arphrd, Iff, IffFlags, Ifla, RtScope, RtTable, Rta, RtmFFlags, Rtn, Rtprot}, | |
| rtnl::{Ifa, IfaFFlags, RtAddrFamily, Rtm}, | |
| socket::NlFamily, | |
| }; | |
| use neli::nl::{NlPayload, Nlmsghdr}; | |
| use neli::rtnl::{Ifaddrmsg, Ifinfomsg, Rtmsg}; | |
| use neli::socket::NlSocketHandle; | |
| use neli::types::RtBuffer; | |
| fn main() { | |
| dbg!(get_interfaces().unwrap()); | |
| } | |
| #[derive(Debug)] | |
| struct Interface { | |
| index: i32, | |
| is_default: bool, | |
| is_up: bool, | |
| name: String, | |
| rx_bytes: u64, | |
| tx_bytes: u64, | |
| ip: Option<Ipv4Addr>, | |
| ipv6: Option<Ipv6Addr>, | |
| } | |
| fn get_interfaces() -> Result<Vec<Interface>, Box<dyn Error>> { | |
| let mut sock = NlSocketHandle::connect(NlFamily::Route, None, &[])?; | |
| let mut interfaces = Vec::new(); | |
| // Request the list of all interfaces | |
| sock.send(Nlmsghdr::new( | |
| None, | |
| Rtm::Getlink, | |
| NlmFFlags::new(&[NlmF::Dump, NlmF::Request]), | |
| None, | |
| None, | |
| NlPayload::Payload(Ifinfomsg::new( | |
| RtAddrFamily::Unspecified, | |
| Arphrd::None, | |
| 0, | |
| IffFlags::empty(), | |
| IffFlags::empty(), | |
| RtBuffer::new(), | |
| )), | |
| ))?; | |
| for header in sock.iter(false) { | |
| let header: Nlmsghdr<Rtm, Ifinfomsg> = header?; | |
| if let NlPayload::Payload(p) = header.nl_payload { | |
| assert_eq!(header.nl_type, Rtm::Newlink); | |
| let mut name = None; | |
| let mut rx_bytes = None; | |
| let mut tx_bytes = None; | |
| for attr in p.rtattrs.iter() { | |
| match attr.rta_type { | |
| Ifla::Ifname => name = Some(attr.get_payload_as_with_len::<String>().unwrap()), | |
| Ifla::Stats64 => { | |
| let stats: &[u8] = attr.payload().as_ref(); | |
| rx_bytes = Some(RtnlLinkStats64::read_rx_bytes(stats)); | |
| tx_bytes = Some(RtnlLinkStats64::read_tx_bytes(stats)); | |
| } | |
| _ => (), | |
| } | |
| } | |
| interfaces.push(Interface { | |
| index: p.ifi_index, | |
| is_up: p.ifi_flags.contains(&Iff::Up), | |
| name: name.unwrap(), | |
| rx_bytes: rx_bytes.unwrap(), | |
| tx_bytes: tx_bytes.unwrap(), | |
| // These are filled later | |
| is_default: false, | |
| ip: None, | |
| ipv6: None, | |
| }); | |
| } | |
| } | |
| // Request the routing table to determine the default interface | |
| sock.send(Nlmsghdr::new( | |
| None, | |
| Rtm::Getroute, | |
| NlmFFlags::new(&[NlmF::Request, NlmF::Dump]), | |
| None, | |
| None, | |
| NlPayload::Payload(Rtmsg { | |
| rtm_family: RtAddrFamily::Inet, | |
| rtm_dst_len: 0, | |
| rtm_src_len: 0, | |
| rtm_tos: 0, | |
| rtm_table: RtTable::Unspec, | |
| rtm_protocol: Rtprot::Unspec, | |
| rtm_scope: RtScope::Universe, | |
| rtm_type: Rtn::Unspec, | |
| rtm_flags: RtmFFlags::empty(), | |
| rtattrs: RtBuffer::new(), | |
| }), | |
| ))?; | |
| for header in sock.iter(false) { | |
| let header: Nlmsghdr<Rtm, Rtmsg> = header?; | |
| if let NlPayload::Payload(p) = header.nl_payload { | |
| assert_eq!(header.nl_type, Rtm::Newroute); | |
| if p.rtm_type != Rtn::Unicast { | |
| continue; | |
| } | |
| let mut index = None; | |
| let mut is_default = false; | |
| for attr in p.rtattrs.iter() { | |
| match attr.rta_type { | |
| Rta::Oif => index = Some(attr.get_payload_as::<i32>().unwrap()), | |
| Rta::Gateway => is_default = true, | |
| _ => (), | |
| } | |
| } | |
| if is_default { | |
| let index = index.unwrap(); | |
| interfaces | |
| .iter_mut() | |
| .find(|i| i.index == index) | |
| .unwrap() | |
| .is_default = true; | |
| } | |
| } | |
| } | |
| // Finally, fetch the IP addeesses | |
| for i in &mut interfaces { | |
| i.ip = ipv4(&mut sock, i.index)?; | |
| i.ipv6 = ipv6(&mut sock, i.index)?; | |
| } | |
| Ok(interfaces) | |
| } | |
| fn ip_payload( | |
| sock: &mut NlSocketHandle, | |
| ifa_family: RtAddrFamily, | |
| ifa_index: i32, | |
| ) -> Result<Option<neli::types::Buffer>, Box<dyn Error>> { | |
| sock.send(Nlmsghdr::new( | |
| None, | |
| Rtm::Getaddr, | |
| NlmFFlags::new(&[NlmF::Dump, NlmF::Request]), | |
| None, | |
| None, | |
| NlPayload::Payload(Ifaddrmsg { | |
| ifa_family, | |
| ifa_prefixlen: 0, | |
| ifa_flags: IfaFFlags::empty(), | |
| ifa_scope: 0, | |
| ifa_index: 0, | |
| rtattrs: RtBuffer::new(), | |
| }), | |
| ))?; | |
| let mut payload = None; | |
| for response in sock.iter(false) { | |
| let header: Nlmsghdr<Rtm, Ifaddrmsg> = response?; | |
| if let NlPayload::Payload(p) = header.nl_payload { | |
| assert_eq!(header.nl_type, Rtm::Newaddr); | |
| if p.ifa_index != ifa_index { | |
| continue; | |
| } | |
| if let Some(rtattr) = p.rtattrs.into_iter().find(|a| a.rta_type == Ifa::Address) { | |
| payload = Some(rtattr.rta_payload); | |
| } | |
| } | |
| } | |
| Ok(payload) | |
| } | |
| fn ipv4(sock: &mut NlSocketHandle, ifa_index: i32) -> Result<Option<Ipv4Addr>, Box<dyn Error>> { | |
| match ip_payload(sock, RtAddrFamily::Inet, ifa_index)? { | |
| None => Ok(None), | |
| Some(payload) => { | |
| let payload: &[u8; 4] = payload.as_ref().try_into()?; | |
| Ok(Some(Ipv4Addr::from(*payload))) | |
| } | |
| } | |
| } | |
| fn ipv6(sock: &mut NlSocketHandle, ifa_index: i32) -> Result<Option<Ipv6Addr>, Box<dyn Error>> { | |
| match ip_payload(sock, RtAddrFamily::Inet6, ifa_index)? { | |
| None => Ok(None), | |
| Some(payload) => { | |
| let payload: &[u8; 16] = payload.as_ref().try_into()?; | |
| Ok(Some(Ipv6Addr::from(*payload))) | |
| } | |
| } | |
| } | |
| // Adapted from linux/if_link.h:215 | |
| #[repr(C)] | |
| #[allow(dead_code)] | |
| struct RtnlLinkStats64 { | |
| rx_packets: u64, | |
| tx_packets: u64, | |
| rx_bytes: u64, | |
| tx_bytes: u64, | |
| // the rest is omitted | |
| } | |
| impl RtnlLinkStats64 { | |
| fn read_rx_bytes(bytes: &[u8]) -> u64 { | |
| assert!(bytes.len() >= std::mem::size_of::<Self>()); | |
| let this = bytes.as_ptr() as *const u64; | |
| unsafe { this.add(2).read_unaligned() } | |
| } | |
| fn read_tx_bytes(bytes: &[u8]) -> u64 { | |
| assert!(bytes.len() >= std::mem::size_of::<Self>()); | |
| let this = bytes.as_ptr() as *const u64; | |
| unsafe { this.add(3).read_unaligned() } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment