Skip to content

Instantly share code, notes, and snippets.

@MaxVerevkin
Last active August 22, 2022 08:45
Show Gist options
  • Select an option

  • Save MaxVerevkin/6419a79ecb8b85d9e0ec90a48c856d02 to your computer and use it in GitHub Desktop.

Select an option

Save MaxVerevkin/6419a79ecb8b85d9e0ec90a48c856d02 to your computer and use it in GitHub Desktop.
list interfaces
// [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