Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions src/netlink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use libc::c_uchar;

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::ops;
use std::path::Path;

use crate::errors::*;
use crate::util;
Expand Down Expand Up @@ -72,19 +71,16 @@ impl NetDevice {
.await
.error("Failed to read nameservers")?;

// TODO: use netlink for the these too
// I don't believe that this should ever change, so set it now:
let path = Path::new("/sys/class/net").join(&iface.name);
// Detect interface type using netlink link_type data
let tun = iface.name.starts_with("tun")
|| iface.name.starts_with("tap")
|| tokio::fs::try_exists(path.join("tun_flags"))
.await
.error("Unable to stat file")?;
let (wg, ppp) = util::read_file(path.join("uevent"))
.await
.map_or((false, false), |c| {
(c.contains("wireguard"), c.contains("ppp"))
});
|| iface.link_type.as_deref() == Some("tun")
|| iface.link_type.as_deref() == Some("tap");
let (wg, ppp) = match iface.link_type.as_deref() {
Some("wireguard") => (true, false),
Some("ppp") => (false, true),
_ => (false, false),
};

let icon = if wifi_info.is_some() {
"net_wireless"
Expand Down Expand Up @@ -256,6 +252,7 @@ pub struct Interface {
pub operstate: Operstate,
pub name: String,
pub stats: Option<InterfaceStats>,
pub link_type: Option<String>,
}

macro_rules! recv_until_done {
Expand All @@ -275,6 +272,52 @@ macro_rules! recv_until_done {
};
}

/// Extract link type (e.g., "tun", "wireguard", "ppp") from IFLA_LINKINFO attribute
fn extract_link_type(linkinfo_bytes: &[u8]) -> Option<String> {
// Parse nested rtattrs in linkinfo to find IFLA_INFO_KIND (type 1)
const IFLA_INFO_KIND: u16 = 1; // type 1 for the kind/type of device we want
const RTATTR_HEADER_SIZE: usize = 4; // 2 bytes length + 2 bytes type
const RTATTR_TYPE_FIELD_SIZE: usize = 2; // size of u16 for length/type
const RTATTR_ALIGNMENT_STRIDE: usize = 4; // netlink attributes are 4-byte aligned
const RTATTR_MIN_PAYLOAD_SIZE: usize = 4; // minimum size for valid payload

let mut offset = 0;

// Continue while we have at least the header size left
while offset + RTATTR_HEADER_SIZE <= linkinfo_bytes.len() {

// Read the first 2 bytes to get the total length of this attribute (header + payload)
let len = u16::from_ne_bytes(
linkinfo_bytes[offset..offset + RTATTR_TYPE_FIELD_SIZE]
.try_into()
.ok()?,
) as usize;

// Read the next 2 bytes to get the attribute type identifier
let typ = u16::from_ne_bytes(
linkinfo_bytes[offset + RTATTR_TYPE_FIELD_SIZE..offset + RTATTR_HEADER_SIZE]
.try_into()
.ok()?,
);

// Check if this is the IFLA_INFO_KIND attribute (type 1) with valid payload
if typ == IFLA_INFO_KIND && len > RTATTR_MIN_PAYLOAD_SIZE {
let payload_len = len - RTATTR_HEADER_SIZE;
let payload = &linkinfo_bytes[offset + RTATTR_HEADER_SIZE..offset + RTATTR_HEADER_SIZE + payload_len];
let kind = String::from_utf8_lossy(payload)
.trim_end_matches('\0')
.to_string();
return Some(kind);
}

// Calculate the next attribute's offset by rounding current length up to 4-byte alignment
let aligned_len = (len + RTATTR_ALIGNMENT_STRIDE - 1) / RTATTR_ALIGNMENT_STRIDE * RTATTR_ALIGNMENT_STRIDE;
offset += aligned_len;
}

None
}

async fn get_interfaces(
sock: &mut NlSocket,
filter: Option<&Regex>,
Expand Down Expand Up @@ -302,11 +345,13 @@ async fn get_interfaces(
let mut name = None;
let mut stats = None;
let mut operstate = Operstate::Unknown;
let mut link_type = None;
for attr in msg.rtattrs.iter() {
match attr.rta_type {
Ifla::Ifname => name = Some(attr.get_payload_as_with_len()?),
Ifla::Stats64 => stats = Some(InterfaceStats::from_stats64(attr.payload().as_ref())),
Ifla::Operstate => operstate = attr.get_payload_as::<u8>()?.into(),
Ifla::Linkinfo => link_type = extract_link_type(attr.payload().as_ref()),
_ => (),
}
}
Expand All @@ -317,6 +362,7 @@ async fn get_interfaces(
operstate,
name,
stats,
link_type,
});
}
});
Expand Down
Loading