#![allow(clippy::arithmetic_side_effects)]
use {
libc::{
AF_INET, AF_INET6, AF_NETLINK, IFLA_INFO_DATA, IFLA_INFO_KIND, IFLA_LINKINFO, MSG_DONTWAIT,
MSG_TRUNC, NDA_DST, NDA_LLADDR, NETLINK_EXT_ACK, NETLINK_GET_STRICT_CHK, NETLINK_ROUTE,
NLA_ALIGNTO, NLA_TYPE_MASK, NLM_F_DUMP, NLM_F_DUMP_INTR, NLM_F_MULTI, NLM_F_REQUEST,
NLMSG_DONE, NLMSG_ERROR, RTA_DST, RTA_GATEWAY, RTA_IIF, RTA_OIF, RTA_PREFSRC, RTA_PRIORITY,
RTA_TABLE, RTM_GETLINK, RTM_GETNEIGH, RTM_GETROUTE, RTM_NEWLINK, RTM_NEWNEIGH,
RTM_NEWROUTE, SO_RCVBUF, SOCK_RAW, SOL_NETLINK, SOL_SOCKET, nlattr, nlmsgerr, nlmsghdr,
recv, send, setsockopt, sockaddr_nl, socket,
},
std::{
collections::HashMap,
ffi::CStr,
io, mem,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
ptr, slice,
},
thiserror::Error,
};
const NETLINK_RCVBUF_SIZE: i32 = 1 << 16;
const NLA_HDR_LEN: usize = align_to(mem::size_of::<nlattr>(), NLA_ALIGNTO as usize);
const IFLA_MTU: u16 = 4;
const NDA_FLAGS_EXT: u16 = 15;
const NLM_F_ACK: u16 = 0x4;
const NLM_F_REPLACE: u16 = 0x100;
const NLM_F_CREATE: u16 = 0x400;
const NTF_EXT_LEARNED: u8 = 1 << 4;
const NTF_EXT_MANAGED: u32 = 1;
const NTF_EXT_VALIDATED: u32 = 1 << 2;
const NTF_USE: u8 = 1;
const IFLA_GRE_LOCAL: u16 = 6;
const IFLA_GRE_REMOTE: u16 = 7;
const IFLA_GRE_TTL: u16 = 8;
const IFLA_GRE_TOS: u16 = 9;
const IFLA_GRE_PMTUDISC: u16 = 10;
const IFLA_VLAN_ID: u16 = 1;
const IFLA_VLAN_PROTOCOL: u16 = 5;
#[repr(C)]
#[allow(non_camel_case_types)]
struct ifinfomsg {
ifi_family: u8,
__ifi_pad: u8,
ifi_type: u16,
ifi_index: u32,
ifi_flags: u32,
ifi_change: u32,
}
pub struct NetlinkSocket {
sock: OwnedFd,
_nl_pid: u32,
}
impl NetlinkSocket {
pub(crate) fn open() -> Result<Self, io::Error> {
let sock = unsafe { socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) };
if sock < 0 {
return Err(io::Error::last_os_error());
}
let sock = unsafe { OwnedFd::from_raw_fd(sock) };
let enable = 1i32;
for opt in [NETLINK_EXT_ACK, NETLINK_GET_STRICT_CHK] {
if unsafe {
setsockopt(
sock.as_raw_fd(),
SOL_NETLINK,
opt,
&enable as *const _ as *const _,
mem::size_of::<i32>() as u32,
)
} < 0
{
return Err(io::Error::last_os_error());
}
}
Ok(Self { sock, _nl_pid: 0 })
}
fn send(&self, msg: &[u8]) -> Result<(), io::Error> {
if unsafe {
send(
self.sock.as_raw_fd(),
msg.as_ptr() as *const _,
msg.len(),
0,
)
} < 0
{
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn recv(&self) -> Result<Vec<NetlinkMessage>, io::Error> {
self.recv_with_flags(0)
}
pub(crate) fn recv_nonblocking(&self) -> Result<Option<Vec<NetlinkMessage>>, io::Error> {
match self.recv_with_flags(MSG_DONTWAIT) {
Ok(messages) => Ok(Some(messages)),
Err(e)
if e.raw_os_error()
.is_some_and(|errno| errno == libc::EAGAIN || errno == libc::EWOULDBLOCK) =>
{
Ok(None)
}
Err(e) => Err(e),
}
}
fn recv_with_flags(&self, flags: i32) -> Result<Vec<NetlinkMessage>, io::Error> {
let mut buf = [0u8; 8 * 1024]; let mut messages = Vec::new();
let mut multipart = true;
'out: while multipart {
multipart = false;
let len = unsafe {
recv(
self.sock.as_raw_fd(),
buf.as_mut_ptr() as *mut _,
buf.len(),
flags | MSG_TRUNC,
)
};
if len < 0 {
return Err(io::Error::last_os_error());
}
if len == 0 {
break;
}
let len = len as usize;
if len > buf.len() {
return Err(io::Error::other("netlink datagram truncated"));
}
let mut offset = 0;
while offset < len {
let message = NetlinkMessage::read(&buf[offset..])?;
offset += align_to(message.header.nlmsg_len as usize, NLMSG_ALIGNTO as usize);
multipart = message.header.nlmsg_flags & NLM_F_MULTI as u16 != 0;
if message.header.nlmsg_flags & NLM_F_DUMP_INTR as u16 != 0 {
return Err(io::Error::new(
io::ErrorKind::Interrupted,
"netlink dump interrupted",
));
}
match message.header.nlmsg_type as i32 {
NLMSG_ERROR => {
let err = message.error.unwrap();
if err.error == 0 {
continue;
}
return Err(io::Error::from_raw_os_error(-err.error));
}
NLMSG_DONE => break 'out,
_ => messages.push(message),
}
}
}
Ok(messages)
}
pub fn bind(groups: u32) -> Result<Self, io::Error> {
let sock = Self::open()?;
let mut addr: sockaddr_nl = unsafe { mem::zeroed() };
addr.nl_family = AF_NETLINK as u16;
addr.nl_groups = groups;
if unsafe {
libc::bind(
sock.as_raw_fd(),
&addr as *const _ as *const _,
mem::size_of::<sockaddr_nl>() as u32,
)
} < 0
{
return Err(io::Error::last_os_error());
}
unsafe {
setsockopt(
sock.as_raw_fd(),
SOL_SOCKET,
SO_RCVBUF,
&NETLINK_RCVBUF_SIZE as *const _ as *const _,
mem::size_of::<i32>() as u32,
);
}
Ok(sock)
}
#[inline]
pub fn as_raw_fd(&self) -> RawFd {
self.sock.as_raw_fd()
}
}
#[derive(Debug, Clone)]
pub struct NetlinkMessage {
pub(crate) header: nlmsghdr,
data: Vec<u8>,
error: Option<nlmsgerr>,
}
impl NetlinkMessage {
fn read(buf: &[u8]) -> Result<Self, io::Error> {
if mem::size_of::<nlmsghdr>() > buf.len() {
return Err(io::Error::other("buffer smaller than nlmsghdr"));
}
let header = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlmsghdr) };
let msg_len = header.nlmsg_len as usize;
if msg_len < mem::size_of::<nlmsghdr>() || msg_len > buf.len() {
return Err(io::Error::other("invalid nlmsg_len"));
}
let data_offset = align_to(mem::size_of::<nlmsghdr>(), NLMSG_ALIGNTO as usize);
if data_offset >= buf.len() {
return Err(io::Error::other("need more data"));
}
let (data, error) = if header.nlmsg_type == NLMSG_ERROR as u16 {
if data_offset + mem::size_of::<nlmsgerr>() > buf.len() {
return Err(io::Error::other(
"NLMSG_ERROR but not enough space for nlmsgerr",
));
}
(
Vec::new(),
Some(unsafe {
ptr::read_unaligned(buf[data_offset..].as_ptr() as *const nlmsgerr)
}),
)
} else {
(buf[data_offset..msg_len].to_vec(), None)
};
Ok(Self {
header,
data,
error,
})
}
}
const fn align_to(v: usize, align: usize) -> usize {
(v + (align - 1)) & !(align - 1)
}
struct NlAttrsIterator<'a> {
attrs: &'a [u8],
offset: usize,
}
impl<'a> NlAttrsIterator<'a> {
fn new(attrs: &'a [u8]) -> Self {
Self { attrs, offset: 0 }
}
}
impl<'a> Iterator for NlAttrsIterator<'a> {
type Item = Result<NlAttr<'a>, NlAttrError>;
fn next(&mut self) -> Option<Self::Item> {
let buf = &self.attrs[self.offset..];
if buf.is_empty() {
return None;
}
if NLA_HDR_LEN > buf.len() {
self.offset = buf.len();
return Some(Err(NlAttrError::InvalidBufferLength {
size: buf.len(),
expected: NLA_HDR_LEN,
}));
}
let attr = unsafe { ptr::read_unaligned(buf.as_ptr() as *const nlattr) };
let len = attr.nla_len as usize;
let align_len = align_to(len, NLA_ALIGNTO as usize);
if len < NLA_HDR_LEN {
return Some(Err(NlAttrError::InvalidHeaderLength(len)));
}
if align_len > buf.len() {
return Some(Err(NlAttrError::InvalidBufferLength {
size: buf.len(),
expected: align_len,
}));
}
let data = &buf[NLA_HDR_LEN..len];
self.offset += align_len;
Some(Ok(NlAttr { header: attr, data }))
}
}
fn parse_attrs(buf: &[u8]) -> Result<HashMap<u16, NlAttr<'_>>, NlAttrError> {
let mut attrs = HashMap::new();
for attr in NlAttrsIterator::new(buf) {
let attr = attr?;
attrs.insert(attr.header.nla_type & NLA_TYPE_MASK as u16, attr);
}
Ok(attrs)
}
#[derive(Clone)]
struct NlAttr<'a> {
header: nlattr,
data: &'a [u8],
}
#[derive(Debug, Error, PartialEq, Eq)]
enum NlAttrError {
#[error("invalid buffer size `{size}`, expected `{expected}`")]
InvalidBufferLength { size: usize, expected: usize },
#[error("invalid nlattr header length `{0}`")]
InvalidHeaderLength(usize),
}
impl From<NlAttrError> for io::Error {
fn from(e: NlAttrError) -> Self {
Self::other(e)
}
}
fn bytes_of<T>(val: &T) -> &[u8] {
let size = mem::size_of::<T>();
unsafe { slice::from_raw_parts(slice::from_ref(val).as_ptr().cast(), size) }
}
const NLMSG_ALIGNTO: u32 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MacAddress(pub [u8; 6]);
impl MacAddress {
pub fn new(bytes: [u8; 6]) -> Self {
MacAddress(bytes)
}
pub fn as_bytes(&self) -> &[u8; 6] {
&self.0
}
}
impl std::fmt::Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GreTunnelInfo {
pub local: IpAddr,
pub remote: IpAddr,
pub ttl: u8,
pub tos: u8,
pub pmtudisc: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VlanLinkInfo {
pub vid: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InterfaceInfo {
pub if_index: u32,
pub mtu: u32,
pub gre_tunnel: Option<GreTunnelInfo>,
pub vlan_link: Option<VlanLinkInfo>,
}
impl InterfaceInfo {
pub fn is_gre(&self) -> bool {
self.gre_tunnel.is_some()
}
}
#[repr(C)]
struct InterfaceRequest {
header: nlmsghdr,
ifi: ifinfomsg,
}
pub fn netlink_get_interfaces(family: u8) -> Result<Vec<InterfaceInfo>, io::Error> {
let sock = NetlinkSocket::open()?;
let mut req = unsafe { mem::zeroed::<InterfaceRequest>() };
let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ifinfomsg>();
req.header = nlmsghdr {
nlmsg_len: nlmsg_len as u32,
nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
nlmsg_type: RTM_GETLINK,
nlmsg_pid: 0,
nlmsg_seq: 1,
};
req.ifi.ifi_family = family;
sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?;
let mut interfaces = Vec::new();
for msg in sock.recv()? {
if msg.header.nlmsg_type != RTM_NEWLINK {
continue;
}
if let Some(if_info) = parse_rtm_ifinfomsg(&msg) {
interfaces.push(if_info);
}
}
Ok(interfaces)
}
pub(crate) fn parse_rtm_ifinfomsg(msg: &NetlinkMessage) -> Option<InterfaceInfo> {
if msg.data.len() < mem::size_of::<ifinfomsg>() {
return None;
}
let ifi = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const ifinfomsg) };
let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<ifinfomsg>()..]) else {
return None;
};
let mtu = attrs
.get(&IFLA_MTU)
.and_then(|a| u32_from_ne_bytes(a.data))?;
let gre_tunnel = parse_gre_tunnel_info_from_linkinfo(&attrs);
let vlan_link = parse_vlan_link_info_from_linkinfo(&attrs);
Some(InterfaceInfo {
if_index: ifi.ifi_index,
mtu,
gre_tunnel,
vlan_link,
})
}
fn parse_vlan_link_info_from_linkinfo(attrs: &HashMap<u16, NlAttr>) -> Option<VlanLinkInfo> {
let vlan = parse_linkinfo_data_for_kind(attrs, b"vlan")?;
if let Some(proto) = vlan.get(&IFLA_VLAN_PROTOCOL) {
let proto = u16::from_be_bytes(proto.data.get(..2)?.try_into().ok()?);
if proto != libc::ETH_P_8021Q as u16 {
return None;
}
}
let vid = vlan
.get(&IFLA_VLAN_ID)
.and_then(|a| u16_from_ne_bytes(a.data))?;
Some(VlanLinkInfo { vid })
}
fn parse_gre_tunnel_info_from_linkinfo(attrs: &HashMap<u16, NlAttr>) -> Option<GreTunnelInfo> {
let gre = parse_linkinfo_data_for_kind(attrs, b"gre")?;
let u8_from_bytes = |data: &[u8]| -> Option<u8> { data.first().copied() };
let local = gre
.get(&IFLA_GRE_LOCAL)
.and_then(|a| parse_ip_address(a.data, AF_INET as u8))?;
let remote = gre
.get(&IFLA_GRE_REMOTE)
.and_then(|a| parse_ip_address(a.data, AF_INET as u8))?;
let ttl = gre.get(&IFLA_GRE_TTL).and_then(|a| u8_from_bytes(a.data))?;
let tos = gre.get(&IFLA_GRE_TOS).and_then(|a| u8_from_bytes(a.data))?;
let pmtudisc = gre
.get(&IFLA_GRE_PMTUDISC)
.and_then(|a| u8_from_bytes(a.data))?;
Some(GreTunnelInfo {
local,
remote,
ttl,
tos,
pmtudisc,
})
}
fn parse_linkinfo_data_for_kind<'a>(
attrs: &HashMap<u16, NlAttr<'a>>,
expected_kind: &[u8],
) -> Option<HashMap<u16, NlAttr<'a>>> {
let li = attrs.get(&IFLA_LINKINFO)?;
let info = parse_attrs(li.data).ok()?;
let kind_attr = info.get(&IFLA_INFO_KIND)?;
if kind_attr.data.is_empty() {
return None;
}
let kind = CStr::from_bytes_until_nul(kind_attr.data).ok()?;
if kind.to_bytes() != expected_kind {
return None;
}
let data_attr = info.get(&IFLA_INFO_DATA)?;
parse_attrs(data_attr.data).ok()
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NeighborEntry {
pub destination: Option<IpAddr>,
pub lladdr: Option<MacAddress>,
pub ifindex: i32,
pub state: u16,
pub flags: u8,
pub flags_ext: u32,
}
impl NeighborEntry {
#[inline]
pub fn key(&self) -> Option<(i32, Ipv4Addr)> {
match self.destination {
Some(IpAddr::V4(ip)) => Some((self.ifindex, ip)),
_ => None,
}
}
#[inline]
pub fn requires_refresh(&self) -> bool {
self.state & (libc::NUD_PERMANENT | libc::NUD_NOARP) == 0
&& self.flags & NTF_EXT_LEARNED == 0
&& self.flags_ext & (NTF_EXT_MANAGED | NTF_EXT_VALIDATED) == 0
}
}
#[repr(C)]
#[allow(non_camel_case_types)]
struct ndmsg {
ndm_family: u8,
_ndm_pad1: u8,
_ndm_pad2: u16,
ndm_ifindex: i32,
ndm_state: u16,
ndm_flags: u8,
_ndm_type: u8,
}
#[repr(C)]
struct NeighRequest {
header: nlmsghdr,
ndm: ndmsg,
}
pub fn netlink_use_neighbor(
sock: &NetlinkSocket,
if_index: u32,
ip: Ipv4Addr,
) -> Result<(), io::Error> {
let req = {
let mut req = unsafe { mem::zeroed::<NeighRequest>() };
let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ndmsg>();
req.header = nlmsghdr {
nlmsg_len: nlmsg_len as u32,
nlmsg_flags: (NLM_F_REQUEST as u16) | NLM_F_ACK | NLM_F_CREATE | NLM_F_REPLACE,
nlmsg_type: RTM_NEWNEIGH,
nlmsg_pid: 0,
nlmsg_seq: 1,
};
req.ndm.ndm_family = AF_INET as u8;
req.ndm.ndm_ifindex = if_index as i32;
req.ndm.ndm_state = 0;
req.ndm.ndm_flags = NTF_USE;
let mut req_buf = bytes_of(&req)[..nlmsg_len].to_vec();
push_nlattr(&mut req_buf, NDA_DST, &ip.octets());
let header = unsafe { &mut *(req_buf.as_mut_ptr() as *mut nlmsghdr) };
header.nlmsg_len = req_buf.len() as u32;
req_buf
};
sock.send(&req)?;
sock.recv()?;
Ok(())
}
pub fn netlink_get_neighbors(
if_index: Option<u32>,
family: u8,
) -> Result<Vec<NeighborEntry>, io::Error> {
let sock = NetlinkSocket::open()?;
let mut req = unsafe { mem::zeroed::<NeighRequest>() };
let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<ndmsg>();
req.header = nlmsghdr {
nlmsg_len: nlmsg_len as u32,
nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
nlmsg_type: RTM_GETNEIGH,
nlmsg_pid: 0,
nlmsg_seq: 1,
};
req.ndm.ndm_family = family;
if let Some(idx) = if_index {
req.ndm.ndm_ifindex = idx as i32;
}
sock.send(&bytes_of(&req)[..req.header.nlmsg_len as usize])?;
let mut neighbors = Vec::new();
for msg in sock.recv()? {
if msg.header.nlmsg_type != RTM_NEWNEIGH {
continue;
}
if msg.data.len() < mem::size_of::<ndmsg>() {
continue;
}
if let Some(neighbor) = parse_rtm_newneigh(&msg, if_index) {
neighbors.push(neighbor);
}
}
Ok(neighbors)
}
pub fn parse_rtm_newneigh(msg: &NetlinkMessage, if_index: Option<u32>) -> Option<NeighborEntry> {
if msg.data.len() < mem::size_of::<ndmsg>() {
return None;
}
let nd_msg = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const ndmsg) };
if let Some(idx) = if_index
&& nd_msg.ndm_ifindex != idx as i32
{
return None;
}
let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<ndmsg>()..]) else {
return None;
};
let mut neighbor = NeighborEntry {
destination: None,
lladdr: None,
ifindex: nd_msg.ndm_ifindex,
state: nd_msg.ndm_state,
flags: nd_msg.ndm_flags,
flags_ext: 0,
};
if let Some(dst_attr) = attrs.get(&NDA_DST) {
neighbor.destination = parse_ip_address(dst_attr.data, nd_msg.ndm_family);
}
if let Some(lladdr_attr) = attrs.get(&NDA_LLADDR)
&& lladdr_attr.data.len() >= 6
{
let mut mac = [0u8; 6];
mac.copy_from_slice(&lladdr_attr.data[0..6]);
neighbor.lladdr = Some(MacAddress(mac));
}
if let Some(flags_ext_attr) = attrs.get(&NDA_FLAGS_EXT)
&& flags_ext_attr.data.len() >= mem::size_of::<u32>()
{
let mut flags_ext = [0; mem::size_of::<u32>()];
flags_ext.copy_from_slice(&flags_ext_attr.data[..mem::size_of::<u32>()]);
neighbor.flags_ext = u32::from_ne_bytes(flags_ext);
}
Some(neighbor)
}
#[derive(Clone)]
pub struct RouteEntry {
pub destination: Option<IpAddr>,
pub gateway: Option<IpAddr>,
pub pref_src: Option<IpAddr>,
pub out_if_index: Option<i32>,
pub in_if_index: Option<i32>,
pub priority: Option<u32>,
pub table: Option<u32>,
pub protocol: u8,
pub scope: u8,
pub type_: u8,
pub family: u8,
pub dst_len: u8,
pub flags: u32,
}
#[repr(C)]
#[allow(non_camel_case_types)]
struct rtmsg {
rtm_family: u8,
rtm_dst_len: u8,
rtm_src_len: u8,
rtm_tos: u8,
rtm_table: u8,
rtm_protocol: u8,
rtm_scope: u8,
rtm_type: u8,
rtm_flags: u32,
}
#[repr(C)]
struct RouteRequest {
header: nlmsghdr,
rtm: rtmsg,
}
fn parse_ip_address(data: &[u8], family: u8) -> Option<IpAddr> {
match family as i32 {
AF_INET if data.len() == 4 => Some(IpAddr::V4(Ipv4Addr::new(
data[0], data[1], data[2], data[3],
))),
AF_INET6 if data.len() == 16 => {
let mut segments = [0u16; 8];
for i in 0..8 {
segments[i] = ((data[i * 2] as u16) << 8) | (data[i * 2 + 1] as u16);
}
Some(IpAddr::V6(Ipv6Addr::from(segments)))
}
_ => None,
}
}
pub fn netlink_get_routes(family: u8, table: u32) -> Result<Vec<RouteEntry>, io::Error> {
let sock = NetlinkSocket::open()?;
let mut req = unsafe { mem::zeroed::<RouteRequest>() };
let nlmsg_len = mem::size_of::<nlmsghdr>() + mem::size_of::<rtmsg>();
let table_attr_len = align_to(NLA_HDR_LEN + mem::size_of::<u32>(), NLA_ALIGNTO as usize);
req.header = nlmsghdr {
nlmsg_len: (nlmsg_len + table_attr_len) as u32,
nlmsg_flags: (NLM_F_REQUEST | NLM_F_DUMP) as u16,
nlmsg_type: RTM_GETROUTE,
nlmsg_pid: 0,
nlmsg_seq: 1,
};
req.rtm.rtm_family = family;
let mut req_buf = bytes_of(&req)[..nlmsg_len].to_vec();
push_nlattr(&mut req_buf, RTA_TABLE, &table);
sock.send(&req_buf)?;
let mut routes = Vec::new();
for msg in sock.recv()? {
if msg.header.nlmsg_type != RTM_NEWROUTE {
continue;
}
if let Some(route) = parse_rtm_newroute(&msg) {
debug_assert!(route.table == Some(table));
if route.table == Some(table) {
routes.push(route);
}
}
}
Ok(routes)
}
pub fn parse_rtm_newroute(msg: &NetlinkMessage) -> Option<RouteEntry> {
if msg.data.len() < mem::size_of::<rtmsg>() {
return None;
}
let rt_msg = unsafe { ptr::read_unaligned(msg.data.as_ptr() as *const rtmsg) };
let Ok(attrs) = parse_attrs(&msg.data[mem::size_of::<rtmsg>()..]) else {
return None;
};
let mut route = RouteEntry {
destination: None,
gateway: None,
pref_src: None,
out_if_index: None,
in_if_index: None,
priority: None,
table: Some(u32::from(rt_msg.rtm_table)),
protocol: rt_msg.rtm_protocol,
scope: rt_msg.rtm_scope,
type_: rt_msg.rtm_type,
family: rt_msg.rtm_family,
dst_len: rt_msg.rtm_dst_len,
flags: rt_msg.rtm_flags,
};
if let Some(dst_attr) = attrs.get(&RTA_DST) {
route.destination = parse_ip_address(dst_attr.data, rt_msg.rtm_family);
}
if let Some(gateway_attr) = attrs.get(&RTA_GATEWAY) {
route.gateway = parse_ip_address(gateway_attr.data, rt_msg.rtm_family);
}
if let Some(oif_attr) = attrs.get(&RTA_OIF) {
route.out_if_index = u32_from_ne_bytes(oif_attr.data).map(|i| i as i32);
}
if let Some(iif_attr) = attrs.get(&RTA_IIF) {
route.in_if_index = u32_from_ne_bytes(iif_attr.data).map(|i| i as i32);
}
if let Some(priority_attr) = attrs.get(&RTA_PRIORITY) {
route.priority = u32_from_ne_bytes(priority_attr.data);
}
if let Some(table_attr) = attrs.get(&RTA_TABLE) {
route.table = u32_from_ne_bytes(table_attr.data);
}
if let Some(prefsrc_attr) = attrs.get(&RTA_PREFSRC) {
route.pref_src = parse_ip_address(prefsrc_attr.data, rt_msg.rtm_family);
}
Some(route)
}
fn push_nlattr<T>(buf: &mut Vec<u8>, attr_type: u16, value: &T) {
let attr_len = NLA_HDR_LEN + mem::size_of::<T>();
let aligned_len = align_to(attr_len, NLA_ALIGNTO as usize);
let attr = nlattr {
nla_len: attr_len as u16,
nla_type: attr_type,
};
buf.extend_from_slice(bytes_of(&attr));
buf.extend_from_slice(bytes_of(value));
buf.resize(buf.len() + (aligned_len - attr_len), 0);
}
fn u32_from_ne_bytes(data: &[u8]) -> Option<u32> {
let bytes: [u8; 4] = data.get(..4)?.try_into().ok()?;
Some(u32::from_ne_bytes(bytes))
}
fn u16_from_ne_bytes(data: &[u8]) -> Option<u16> {
let bytes: [u8; 2] = data.get(..2)?.try_into().ok()?;
Some(u16::from_ne_bytes(bytes))
}