use std::net::IpAddr;
use winnow::{binary::le_u16, prelude::*, token::take};
use crate::netlink::{
error::Result,
parse::{FromNetlink, PResult, ToNetlink, parse_ip_addr, parse_string_from_bytes},
types::addr::{IfAddrMsg, IfaCacheinfo, Scope},
};
mod attr_ids {
pub const IFA_ADDRESS: u16 = 1;
pub const IFA_LOCAL: u16 = 2;
pub const IFA_LABEL: u16 = 3;
pub const IFA_BROADCAST: u16 = 4;
pub const IFA_ANYCAST: u16 = 5;
pub const IFA_CACHEINFO: u16 = 6;
pub const IFA_FLAGS: u16 = 8;
}
#[derive(Debug, Clone, Default)]
pub struct AddressMessage {
pub(crate) header: IfAddrMsg,
pub(crate) address: Option<IpAddr>,
pub(crate) local: Option<IpAddr>,
pub(crate) label: Option<String>,
pub(crate) broadcast: Option<IpAddr>,
pub(crate) anycast: Option<IpAddr>,
pub(crate) flags: Option<u32>,
pub(crate) cache_info: Option<AddressCacheInfo>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AddressCacheInfo {
pub preferred: u32,
pub valid: u32,
pub created: u32,
pub updated: u32,
}
impl AddressMessage {
pub fn new() -> Self {
Self::default()
}
pub fn family(&self) -> u8 {
self.header.ifa_family
}
pub fn prefix_len(&self) -> u8 {
self.header.ifa_prefixlen
}
pub fn ifindex(&self) -> u32 {
self.header.ifa_index
}
pub fn scope(&self) -> Scope {
Scope::from(self.header.ifa_scope)
}
pub fn address(&self) -> Option<&IpAddr> {
self.address.as_ref()
}
pub fn local(&self) -> Option<&IpAddr> {
self.local.as_ref()
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn broadcast(&self) -> Option<&IpAddr> {
self.broadcast.as_ref()
}
pub fn anycast(&self) -> Option<&IpAddr> {
self.anycast.as_ref()
}
pub fn flags(&self) -> u32 {
self.flags.unwrap_or(self.header.ifa_flags as u32)
}
pub fn cache_info(&self) -> Option<&AddressCacheInfo> {
self.cache_info.as_ref()
}
pub fn primary_address(&self) -> Option<&IpAddr> {
self.local.as_ref().or(self.address.as_ref())
}
pub fn is_ipv4(&self) -> bool {
self.header.ifa_family == libc::AF_INET as u8
}
pub fn is_ipv6(&self) -> bool {
self.header.ifa_family == libc::AF_INET6 as u8
}
pub fn is_secondary(&self) -> bool {
self.flags() & 0x01 != 0 }
pub fn is_permanent(&self) -> bool {
self.flags() & 0x80 != 0 }
pub fn is_deprecated(&self) -> bool {
self.flags() & 0x20 != 0 }
pub fn is_tentative(&self) -> bool {
self.flags() & 0x40 != 0 }
}
impl FromNetlink for AddressMessage {
fn write_dump_header(buf: &mut Vec<u8>) {
let header = IfAddrMsg::new();
buf.extend_from_slice(header.as_bytes());
}
fn parse(input: &mut &[u8]) -> PResult<Self> {
if input.len() < IfAddrMsg::SIZE {
return Err(winnow::error::ErrMode::Cut(
winnow::error::ContextError::new(),
));
}
let header_bytes: &[u8] = take(IfAddrMsg::SIZE).parse_next(input)?;
let header = *IfAddrMsg::from_bytes(header_bytes)
.map_err(|_| winnow::error::ErrMode::Cut(winnow::error::ContextError::new()))?;
let mut msg = AddressMessage {
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::IFA_ADDRESS => {
if let Ok(addr) = parse_ip_addr(attr_data, header.ifa_family) {
msg.address = Some(addr);
}
}
attr_ids::IFA_LOCAL => {
if let Ok(addr) = parse_ip_addr(attr_data, header.ifa_family) {
msg.local = Some(addr);
}
}
attr_ids::IFA_LABEL => {
msg.label = Some(parse_string_from_bytes(attr_data));
}
attr_ids::IFA_BROADCAST => {
if let Ok(addr) = parse_ip_addr(attr_data, header.ifa_family) {
msg.broadcast = Some(addr);
}
}
attr_ids::IFA_ANYCAST => {
if let Ok(addr) = parse_ip_addr(attr_data, header.ifa_family) {
msg.anycast = Some(addr);
}
}
attr_ids::IFA_FLAGS if attr_data.len() >= 4 => {
let bytes: [u8; 4] = attr_data[..4].try_into().unwrap();
msg.flags = Some(u32::from_ne_bytes(bytes));
}
attr_ids::IFA_CACHEINFO => {
if let Some(info) = IfaCacheinfo::from_bytes(attr_data) {
msg.cache_info = Some(AddressCacheInfo {
preferred: info.ifa_prefered,
valid: info.ifa_valid,
created: info.cstamp,
updated: info.tstamp,
});
}
}
_ => {} }
}
Ok(msg)
}
}
impl ToNetlink for AddressMessage {
fn netlink_len(&self) -> usize {
let mut len = IfAddrMsg::SIZE;
if self.address.is_some() {
len += nla_size(if self.is_ipv4() { 4 } else { 16 });
}
if self.local.is_some() {
len += nla_size(if self.is_ipv4() { 4 } else { 16 });
}
if let Some(ref label) = self.label {
len += nla_size(label.len() + 1);
}
if self.broadcast.is_some() {
len += nla_size(4); }
if self.flags.is_some() {
len += nla_size(4);
}
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 addr) = self.address {
write_attr(buf, attr_ids::IFA_ADDRESS, addr)?;
}
if let Some(ref addr) = self.local {
write_attr(buf, attr_ids::IFA_LOCAL, addr)?;
}
if let Some(ref label) = self.label {
write_attr_str(buf, attr_ids::IFA_LABEL, label);
}
if let Some(ref addr) = self.broadcast {
write_attr(buf, attr_ids::IFA_BROADCAST, addr)?;
}
if let Some(flags) = self.flags {
write_attr(buf, attr_ids::IFA_FLAGS, &flags)?;
}
Ok(buf.len() - start)
}
}
fn nla_size(payload_len: usize) -> usize {
(4 + payload_len + 3) & !3
}
fn write_attr_str(buf: &mut Vec<u8>, attr_type: u16, value: &str) {
let payload_len = value.len() + 1; let len = 4 + payload_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.as_bytes());
buf.push(0);
let aligned = (len + 3) & !3;
let padding = aligned - len;
for _ in 0..padding {
buf.push(0);
}
}
fn write_attr<T: ToNetlink>(buf: &mut Vec<u8>, attr_type: u16, value: &T) -> Result<()> {
let payload_len = value.netlink_len();
let len = 4 + payload_len;
buf.extend_from_slice(&(len as u16).to_ne_bytes());
buf.extend_from_slice(&attr_type.to_ne_bytes());
value.write_to(buf)?;
let aligned = (len + 3) & !3;
let padding = aligned - len;
for _ in 0..padding {
buf.push(0);
}
Ok(())
}
#[derive(Debug, Clone, Default)]
pub struct AddressMessageBuilder {
msg: AddressMessage,
}
impl AddressMessageBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn ipv4(mut self) -> Self {
self.msg.header.ifa_family = libc::AF_INET as u8;
self
}
pub fn ipv6(mut self) -> Self {
self.msg.header.ifa_family = libc::AF_INET6 as u8;
self
}
pub fn ifindex(mut self, index: u32) -> Self {
self.msg.header.ifa_index = index;
self
}
pub fn prefix_len(mut self, len: u8) -> Self {
self.msg.header.ifa_prefixlen = len;
self
}
pub fn scope(mut self, scope: Scope) -> Self {
self.msg.header.ifa_scope = scope as u8;
self
}
pub fn address(mut self, addr: IpAddr) -> Self {
match addr {
IpAddr::V4(_) => self.msg.header.ifa_family = libc::AF_INET as u8,
IpAddr::V6(_) => self.msg.header.ifa_family = libc::AF_INET6 as u8,
}
self.msg.address = Some(addr);
self
}
pub fn local(mut self, addr: IpAddr) -> Self {
self.msg.local = Some(addr);
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.msg.label = Some(label.into());
self
}
pub fn broadcast(mut self, addr: IpAddr) -> Self {
self.msg.broadcast = Some(addr);
self
}
pub fn flags(mut self, flags: u32) -> Self {
self.msg.flags = Some(flags);
self
}
pub fn build(self) -> AddressMessage {
self.msg
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
#[test]
fn test_builder() {
let msg = AddressMessageBuilder::new()
.ifindex(2)
.prefix_len(24)
.scope(Scope::Universe)
.address(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
.local(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
.label("eth0")
.build();
assert_eq!(msg.ifindex(), 2);
assert_eq!(msg.prefix_len(), 24);
assert!(msg.is_ipv4());
assert_eq!(msg.label, Some("eth0".to_string()));
}
#[test]
fn test_roundtrip() {
let original = AddressMessageBuilder::new()
.ifindex(5)
.prefix_len(24)
.address(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
.build();
let bytes = original.to_bytes().unwrap();
let parsed = AddressMessage::from_bytes(&bytes).unwrap();
assert_eq!(parsed.ifindex(), original.ifindex());
assert_eq!(parsed.prefix_len(), original.prefix_len());
assert_eq!(parsed.address, original.address);
}
}