use std::net::IpAddr;
use winnow::{binary::le_u16, prelude::*, token::take};
use crate::netlink::{
error::Result,
parse::{FromNetlink, PResult, ToNetlink, parse_ip_addr},
types::neigh::{NdMsg, NeighborState},
};
mod attr_ids {
pub const NDA_DST: u16 = 1;
pub const NDA_LLADDR: u16 = 2;
pub const NDA_CACHEINFO: u16 = 3;
pub const NDA_PROBES: u16 = 4;
pub const NDA_VLAN: u16 = 5;
pub const NDA_PORT: u16 = 6;
pub const NDA_VNI: u16 = 7;
pub const NDA_IFINDEX: u16 = 8;
pub const NDA_MASTER: u16 = 9;
}
#[derive(Debug, Clone, Default)]
pub struct NeighborMessage {
pub(crate) header: NdMsg,
pub(crate) destination: Option<IpAddr>,
pub(crate) lladdr: Option<Vec<u8>>,
pub(crate) probes: Option<u32>,
pub(crate) vlan: Option<u16>,
pub(crate) port: Option<u16>,
pub(crate) vni: Option<u32>,
pub(crate) ifindex_attr: Option<u32>,
pub(crate) master: Option<u32>,
pub(crate) cache_info: Option<NeighborCacheInfo>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NeighborCacheInfo {
pub confirmed: u32,
pub used: u32,
pub updated: u32,
pub refcnt: u32,
}
impl NeighborMessage {
pub fn new() -> Self {
Self::default()
}
pub fn family(&self) -> u8 {
self.header.ndm_family
}
pub fn ifindex(&self) -> u32 {
self.header.ndm_ifindex as u32
}
pub fn state(&self) -> NeighborState {
NeighborState::from(self.header.ndm_state)
}
pub fn flags(&self) -> u8 {
self.header.ndm_flags
}
pub fn destination(&self) -> Option<&IpAddr> {
self.destination.as_ref()
}
pub fn lladdr(&self) -> Option<&[u8]> {
self.lladdr.as_deref()
}
pub fn probes(&self) -> Option<u32> {
self.probes
}
pub fn vlan(&self) -> Option<u16> {
self.vlan
}
pub fn port(&self) -> Option<u16> {
self.port
}
pub fn vni(&self) -> Option<u32> {
self.vni
}
pub fn master(&self) -> Option<u32> {
self.master
}
pub fn cache_info(&self) -> Option<&NeighborCacheInfo> {
self.cache_info.as_ref()
}
pub fn mac_address(&self) -> Option<String> {
let lladdr = self.lladdr.as_ref()?;
if lladdr.len() == 6 {
Some(format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
lladdr[0], lladdr[1], lladdr[2], lladdr[3], lladdr[4], lladdr[5]
))
} else {
None
}
}
pub fn is_ipv4(&self) -> bool {
self.header.ndm_family == libc::AF_INET as u8
}
pub fn is_ipv6(&self) -> bool {
self.header.ndm_family == libc::AF_INET6 as u8
}
pub fn is_reachable(&self) -> bool {
self.header.ndm_state & 0x02 != 0 }
pub fn is_permanent(&self) -> bool {
self.header.ndm_state & 0x80 != 0 }
pub fn is_stale(&self) -> bool {
self.header.ndm_state & 0x04 != 0 }
pub fn is_incomplete(&self) -> bool {
self.header.ndm_state & 0x01 != 0 }
pub fn is_failed(&self) -> bool {
self.header.ndm_state & 0x20 != 0 }
pub fn is_router(&self) -> bool {
self.header.ndm_flags & 0x80 != 0 }
pub fn is_proxy(&self) -> bool {
self.header.ndm_flags & 0x08 != 0 }
}
impl FromNetlink for NeighborMessage {
fn write_dump_header(buf: &mut Vec<u8>) {
let header = NdMsg::new();
buf.extend_from_slice(header.as_bytes());
}
fn parse(input: &mut &[u8]) -> PResult<Self> {
if input.len() < NdMsg::SIZE {
return Err(winnow::error::ErrMode::Cut(
winnow::error::ContextError::new(),
));
}
let header_bytes: &[u8] = take(NdMsg::SIZE).parse_next(input)?;
let header = *NdMsg::from_bytes(header_bytes)
.map_err(|_| winnow::error::ErrMode::Cut(winnow::error::ContextError::new()))?;
let mut msg = NeighborMessage {
header,
..Default::default()
};
while !input.is_empty() && input.len() >= 4 {
let len = le_u16.parse_next(input)? as usize;
let attr_type = le_u16.parse_next(input)?;
if len < 4 {
break;
}
let payload_len = len.saturating_sub(4);
if input.len() < payload_len {
break;
}
let attr_data: &[u8] = take(payload_len).parse_next(input)?;
let aligned = (len + 3) & !3;
let padding = aligned.saturating_sub(len);
if input.len() >= padding {
let _: &[u8] = take(padding).parse_next(input)?;
}
match attr_type & 0x3FFF {
attr_ids::NDA_DST => {
if let Ok(addr) = parse_ip_addr(attr_data, header.ndm_family) {
msg.destination = Some(addr);
}
}
attr_ids::NDA_LLADDR => {
msg.lladdr = Some(attr_data.to_vec());
}
attr_ids::NDA_PROBES if attr_data.len() >= 4 => {
msg.probes = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::NDA_VLAN if attr_data.len() >= 2 => {
msg.vlan = Some(u16::from_ne_bytes(attr_data[..2].try_into().unwrap()));
}
attr_ids::NDA_PORT if attr_data.len() >= 2 => {
msg.port = Some(u16::from_be_bytes(attr_data[..2].try_into().unwrap()));
}
attr_ids::NDA_VNI if attr_data.len() >= 4 => {
msg.vni = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::NDA_IFINDEX if attr_data.len() >= 4 => {
msg.ifindex_attr = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::NDA_MASTER if attr_data.len() >= 4 => {
msg.master = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::NDA_CACHEINFO if attr_data.len() >= 16 => {
msg.cache_info = Some(NeighborCacheInfo {
confirmed: u32::from_ne_bytes(attr_data[0..4].try_into().unwrap()),
used: u32::from_ne_bytes(attr_data[4..8].try_into().unwrap()),
updated: u32::from_ne_bytes(attr_data[8..12].try_into().unwrap()),
refcnt: u32::from_ne_bytes(attr_data[12..16].try_into().unwrap()),
});
}
_ => {} }
}
Ok(msg)
}
}
impl ToNetlink for NeighborMessage {
fn netlink_len(&self) -> usize {
let mut len = NdMsg::SIZE;
if self.destination.is_some() {
len += nla_size(if self.is_ipv4() { 4 } else { 16 });
}
if let Some(ref lladdr) = self.lladdr {
len += nla_size(lladdr.len());
}
if self.vlan.is_some() {
len += nla_size(2);
}
len
}
fn write_to(&self, buf: &mut Vec<u8>) -> Result<usize> {
let start = buf.len();
buf.extend_from_slice(self.header.as_bytes());
if let Some(ref dst) = self.destination {
write_attr_ip(buf, attr_ids::NDA_DST, dst);
}
if let Some(ref lladdr) = self.lladdr {
write_attr_bytes(buf, attr_ids::NDA_LLADDR, lladdr);
}
if let Some(vlan) = self.vlan {
write_attr_u16(buf, attr_ids::NDA_VLAN, vlan);
}
Ok(buf.len() - start)
}
}
fn nla_size(payload_len: usize) -> usize {
(4 + payload_len + 3) & !3
}
fn write_attr_u16(buf: &mut Vec<u8>, attr_type: u16, value: u16) {
let len: u16 = 6;
buf.extend_from_slice(&len.to_ne_bytes());
buf.extend_from_slice(&attr_type.to_ne_bytes());
buf.extend_from_slice(&value.to_ne_bytes());
buf.push(0); buf.push(0);
}
fn write_attr_bytes(buf: &mut Vec<u8>, attr_type: u16, value: &[u8]) {
let len = 4 + value.len();
buf.extend_from_slice(&(len as u16).to_ne_bytes());
buf.extend_from_slice(&attr_type.to_ne_bytes());
buf.extend_from_slice(value);
let aligned = (len + 3) & !3;
for _ in 0..(aligned - len) {
buf.push(0);
}
}
fn write_attr_ip(buf: &mut Vec<u8>, attr_type: u16, addr: &IpAddr) {
let octets = match addr {
IpAddr::V4(v4) => v4.octets().to_vec(),
IpAddr::V6(v6) => v6.octets().to_vec(),
};
let len = 4 + octets.len();
buf.extend_from_slice(&(len as u16).to_ne_bytes());
buf.extend_from_slice(&attr_type.to_ne_bytes());
buf.extend_from_slice(&octets);
let aligned = (len + 3) & !3;
for _ in 0..(aligned - len) {
buf.push(0);
}
}
#[derive(Debug, Clone, Default)]
pub struct NeighborMessageBuilder {
msg: NeighborMessage,
}
impl NeighborMessageBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn ifindex(mut self, index: u32) -> Self {
self.msg.header.ndm_ifindex = index as i32;
self
}
pub fn destination(mut self, addr: IpAddr) -> Self {
match addr {
IpAddr::V4(_) => self.msg.header.ndm_family = libc::AF_INET as u8,
IpAddr::V6(_) => self.msg.header.ndm_family = libc::AF_INET6 as u8,
}
self.msg.destination = Some(addr);
self
}
pub fn lladdr(mut self, addr: Vec<u8>) -> Self {
self.msg.lladdr = Some(addr);
self
}
pub fn state(mut self, state: NeighborState) -> Self {
self.msg.header.ndm_state = state as u16;
self
}
pub fn flags(mut self, flags: u8) -> Self {
self.msg.header.ndm_flags = flags;
self
}
pub fn permanent(mut self) -> Self {
self.msg.header.ndm_state |= 0x80; self
}
pub fn vlan(mut self, vlan: u16) -> Self {
self.msg.vlan = Some(vlan);
self
}
pub fn build(self) -> NeighborMessage {
self.msg
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
#[test]
fn test_builder() {
let msg = NeighborMessageBuilder::new()
.ifindex(2)
.destination(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
.lladdr(vec![0x00, 0x11, 0x22, 0x33, 0x44, 0x55])
.permanent()
.build();
assert_eq!(msg.ifindex(), 2);
assert!(msg.is_ipv4());
assert!(msg.is_permanent());
assert_eq!(msg.mac_address(), Some("00:11:22:33:44:55".to_string()));
}
}