use winnow::{binary::le_u16, prelude::*, token::take};
use crate::netlink::{
error::Result,
parse::{FromNetlink, PResult, ToNetlink, parse_string_from_bytes},
types::link::{IfInfoMsg, LinkStats64, OperState},
};
mod attr_ids {
pub const IFLA_ADDRESS: u16 = 1;
pub const IFLA_BROADCAST: u16 = 2;
pub const IFLA_IFNAME: u16 = 3;
pub const IFLA_MTU: u16 = 4;
pub const IFLA_LINK: u16 = 5;
pub const IFLA_QDISC: u16 = 6;
pub const IFLA_MASTER: u16 = 10;
pub const IFLA_TXQLEN: u16 = 13;
pub const IFLA_OPERSTATE: u16 = 16;
pub const IFLA_LINKINFO: u16 = 18;
pub const IFLA_STATS64: u16 = 23;
pub const IFLA_GROUP: u16 = 27;
pub const IFLA_PROMISCUITY: u16 = 30;
pub const IFLA_NUM_TX_QUEUES: u16 = 31;
pub const IFLA_NUM_RX_QUEUES: u16 = 32;
pub const IFLA_CARRIER: u16 = 33;
pub const IFLA_MIN_MTU: u16 = 50;
pub const IFLA_MAX_MTU: u16 = 51;
pub const IFLA_PERM_ADDRESS: u16 = 54;
}
mod info_ids {
pub const IFLA_INFO_KIND: u16 = 1;
pub const IFLA_INFO_DATA: u16 = 2;
pub const IFLA_INFO_SLAVE_KIND: u16 = 4;
pub const IFLA_INFO_SLAVE_DATA: u16 = 5;
}
#[derive(Debug, Clone, Default)]
pub struct LinkMessage {
pub(crate) header: IfInfoMsg,
pub(crate) name: Option<String>,
pub(crate) address: Option<Vec<u8>>,
pub(crate) broadcast: Option<Vec<u8>>,
pub(crate) perm_address: Option<Vec<u8>>,
pub(crate) mtu: Option<u32>,
pub(crate) min_mtu: Option<u32>,
pub(crate) max_mtu: Option<u32>,
pub(crate) link: Option<u32>,
pub(crate) qdisc: Option<String>,
pub(crate) master: Option<u32>,
pub(crate) txqlen: Option<u32>,
pub(crate) operstate: Option<OperState>,
pub(crate) group: Option<u32>,
pub(crate) promiscuity: Option<u32>,
pub(crate) num_tx_queues: Option<u32>,
pub(crate) num_rx_queues: Option<u32>,
pub(crate) carrier: Option<bool>,
pub(crate) link_info: Option<LinkInfo>,
pub(crate) stats: Option<LinkStats>,
}
#[derive(Debug, Clone, Default)]
pub struct LinkInfo {
pub(crate) kind: Option<String>,
pub(crate) slave_kind: Option<String>,
pub(crate) data: Option<Vec<u8>>,
pub(crate) slave_data: Option<Vec<u8>>,
}
impl LinkInfo {
pub fn kind(&self) -> Option<&str> {
self.kind.as_deref()
}
pub fn slave_kind(&self) -> Option<&str> {
self.slave_kind.as_deref()
}
pub fn data(&self) -> Option<&[u8]> {
self.data.as_deref()
}
pub fn slave_data(&self) -> Option<&[u8]> {
self.slave_data.as_deref()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct LinkStats {
pub rx_packets: u64,
pub tx_packets: u64,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_errors: u64,
pub tx_errors: u64,
pub rx_dropped: u64,
pub tx_dropped: u64,
pub multicast: u64,
pub collisions: u64,
}
impl LinkStats {
pub fn rx_packets(&self) -> u64 {
self.rx_packets
}
pub fn tx_packets(&self) -> u64 {
self.tx_packets
}
pub fn rx_bytes(&self) -> u64 {
self.rx_bytes
}
pub fn tx_bytes(&self) -> u64 {
self.tx_bytes
}
pub fn rx_errors(&self) -> u64 {
self.rx_errors
}
pub fn tx_errors(&self) -> u64 {
self.tx_errors
}
pub fn rx_dropped(&self) -> u64 {
self.rx_dropped
}
pub fn tx_dropped(&self) -> u64 {
self.tx_dropped
}
pub fn multicast(&self) -> u64 {
self.multicast
}
pub fn collisions(&self) -> u64 {
self.collisions
}
pub fn total_packets(&self) -> u64 {
self.rx_packets + self.tx_packets
}
pub fn total_bytes(&self) -> u64 {
self.rx_bytes + self.tx_bytes
}
pub fn total_errors(&self) -> u64 {
self.rx_errors + self.tx_errors
}
pub fn total_dropped(&self) -> u64 {
self.rx_dropped + self.tx_dropped
}
}
impl LinkMessage {
pub fn new() -> Self {
Self::default()
}
pub fn ifindex(&self) -> u32 {
self.header.ifi_index as u32
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn name_or<'a>(&'a self, default: &'a str) -> &'a str {
self.name.as_deref().unwrap_or(default)
}
pub fn address(&self) -> Option<&[u8]> {
self.address.as_deref()
}
pub fn broadcast(&self) -> Option<&[u8]> {
self.broadcast.as_deref()
}
pub fn perm_address(&self) -> Option<&[u8]> {
self.perm_address.as_deref()
}
pub fn mtu(&self) -> Option<u32> {
self.mtu
}
pub fn min_mtu(&self) -> Option<u32> {
self.min_mtu
}
pub fn max_mtu(&self) -> Option<u32> {
self.max_mtu
}
pub fn link(&self) -> Option<u32> {
self.link
}
pub fn qdisc(&self) -> Option<&str> {
self.qdisc.as_deref()
}
pub fn master(&self) -> Option<u32> {
self.master
}
pub fn txqlen(&self) -> Option<u32> {
self.txqlen
}
pub fn operstate(&self) -> Option<OperState> {
self.operstate
}
pub fn group(&self) -> Option<u32> {
self.group
}
pub fn promiscuity(&self) -> Option<u32> {
self.promiscuity
}
pub fn num_tx_queues(&self) -> Option<u32> {
self.num_tx_queues
}
pub fn num_rx_queues(&self) -> Option<u32> {
self.num_rx_queues
}
pub fn carrier(&self) -> Option<bool> {
self.carrier
}
pub fn link_info(&self) -> Option<&LinkInfo> {
self.link_info.as_ref()
}
pub fn stats(&self) -> Option<&LinkStats> {
self.stats.as_ref()
}
pub fn flags(&self) -> u32 {
self.header.ifi_flags
}
pub fn kind(&self) -> Option<&str> {
self.link_info.as_ref()?.kind.as_deref()
}
pub fn mac_address(&self) -> Option<String> {
let addr = self.address.as_ref()?;
if addr.len() == 6 {
Some(format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]
))
} else {
None
}
}
pub fn is_up(&self) -> bool {
self.header.ifi_flags & 0x1 != 0 }
pub fn is_running(&self) -> bool {
self.header.ifi_flags & 0x40 != 0 }
pub fn is_loopback(&self) -> bool {
self.header.ifi_flags & 0x8 != 0 }
pub fn is_broadcast(&self) -> bool {
self.header.ifi_flags & 0x2 != 0 }
pub fn is_pointopoint(&self) -> bool {
self.header.ifi_flags & 0x10 != 0 }
pub fn has_carrier(&self) -> bool {
self.carrier.unwrap_or(false)
}
pub fn total_bytes(&self) -> u64 {
self.stats.as_ref().map(|s| s.total_bytes()).unwrap_or(0)
}
pub fn total_packets(&self) -> u64 {
self.stats.as_ref().map(|s| s.total_packets()).unwrap_or(0)
}
pub fn total_errors(&self) -> u64 {
self.stats.as_ref().map(|s| s.total_errors()).unwrap_or(0)
}
pub fn total_dropped(&self) -> u64 {
self.stats.as_ref().map(|s| s.total_dropped()).unwrap_or(0)
}
pub fn rx_bytes(&self) -> u64 {
self.stats.as_ref().map(|s| s.rx_bytes()).unwrap_or(0)
}
pub fn tx_bytes(&self) -> u64 {
self.stats.as_ref().map(|s| s.tx_bytes()).unwrap_or(0)
}
pub fn rx_packets(&self) -> u64 {
self.stats.as_ref().map(|s| s.rx_packets()).unwrap_or(0)
}
pub fn tx_packets(&self) -> u64 {
self.stats.as_ref().map(|s| s.tx_packets()).unwrap_or(0)
}
pub fn rx_errors(&self) -> u64 {
self.stats.as_ref().map(|s| s.rx_errors()).unwrap_or(0)
}
pub fn tx_errors(&self) -> u64 {
self.stats.as_ref().map(|s| s.tx_errors()).unwrap_or(0)
}
pub fn rx_dropped(&self) -> u64 {
self.stats.as_ref().map(|s| s.rx_dropped()).unwrap_or(0)
}
pub fn tx_dropped(&self) -> u64 {
self.stats.as_ref().map(|s| s.tx_dropped()).unwrap_or(0)
}
}
impl FromNetlink for LinkMessage {
fn write_dump_header(buf: &mut Vec<u8>) {
let header = IfInfoMsg::new();
buf.extend_from_slice(header.as_bytes());
}
fn parse(input: &mut &[u8]) -> PResult<Self> {
if input.len() < IfInfoMsg::SIZE {
return Err(winnow::error::ErrMode::Cut(
winnow::error::ContextError::new(),
));
}
let header_bytes: &[u8] = take(IfInfoMsg::SIZE).parse_next(input)?;
let header = *IfInfoMsg::from_bytes(header_bytes)
.map_err(|_| winnow::error::ErrMode::Cut(winnow::error::ContextError::new()))?;
let mut msg = LinkMessage {
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::IFLA_IFNAME => {
msg.name = Some(parse_string_from_bytes(attr_data));
}
attr_ids::IFLA_ADDRESS => {
msg.address = Some(attr_data.to_vec());
}
attr_ids::IFLA_BROADCAST => {
msg.broadcast = Some(attr_data.to_vec());
}
attr_ids::IFLA_PERM_ADDRESS => {
msg.perm_address = Some(attr_data.to_vec());
}
attr_ids::IFLA_MTU if attr_data.len() >= 4 => {
msg.mtu = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_MIN_MTU if attr_data.len() >= 4 => {
msg.min_mtu = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_MAX_MTU if attr_data.len() >= 4 => {
msg.max_mtu = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_LINK if attr_data.len() >= 4 => {
msg.link = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_QDISC => {
msg.qdisc = Some(parse_string_from_bytes(attr_data));
}
attr_ids::IFLA_MASTER if attr_data.len() >= 4 => {
msg.master = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_TXQLEN if attr_data.len() >= 4 => {
msg.txqlen = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_OPERSTATE if !attr_data.is_empty() => {
msg.operstate = Some(OperState::from(attr_data[0]));
}
attr_ids::IFLA_GROUP if attr_data.len() >= 4 => {
msg.group = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_PROMISCUITY if attr_data.len() >= 4 => {
msg.promiscuity = Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_NUM_TX_QUEUES if attr_data.len() >= 4 => {
msg.num_tx_queues =
Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_NUM_RX_QUEUES if attr_data.len() >= 4 => {
msg.num_rx_queues =
Some(u32::from_ne_bytes(attr_data[..4].try_into().unwrap()));
}
attr_ids::IFLA_CARRIER if !attr_data.is_empty() => {
msg.carrier = Some(attr_data[0] != 0);
}
attr_ids::IFLA_LINKINFO => {
msg.link_info = Some(parse_link_info(attr_data));
}
attr_ids::IFLA_STATS64 => {
if let Some(stats) = LinkStats64::from_bytes(attr_data) {
msg.stats = Some(LinkStats {
rx_packets: stats.rx_packets,
tx_packets: stats.tx_packets,
rx_bytes: stats.rx_bytes,
tx_bytes: stats.tx_bytes,
rx_errors: stats.rx_errors,
tx_errors: stats.tx_errors,
rx_dropped: stats.rx_dropped,
tx_dropped: stats.tx_dropped,
multicast: stats.multicast,
collisions: stats.collisions,
});
}
}
_ => {} }
}
Ok(msg)
}
}
fn parse_link_info(data: &[u8]) -> LinkInfo {
let mut info = LinkInfo::default();
let mut input = data;
while !input.is_empty() && input.len() >= 4 {
let len = u16::from_ne_bytes(input[..2].try_into().unwrap()) as usize;
let attr_type = u16::from_ne_bytes(input[2..4].try_into().unwrap());
if len < 4 || input.len() < len {
break;
}
let payload = &input[4..len];
match attr_type & 0x3FFF {
info_ids::IFLA_INFO_KIND => {
info.kind = Some(parse_string_from_bytes(payload));
}
info_ids::IFLA_INFO_DATA => {
info.data = Some(payload.to_vec());
}
info_ids::IFLA_INFO_SLAVE_KIND => {
info.slave_kind = Some(parse_string_from_bytes(payload));
}
info_ids::IFLA_INFO_SLAVE_DATA => {
info.slave_data = Some(payload.to_vec());
}
_ => {}
}
let aligned = (len + 3) & !3;
if input.len() <= aligned {
break;
}
input = &input[aligned..];
}
info
}
impl ToNetlink for LinkMessage {
fn netlink_len(&self) -> usize {
let mut len = IfInfoMsg::SIZE;
if let Some(ref name) = self.name {
len += nla_size(name.len() + 1);
}
if let Some(ref addr) = self.address {
len += nla_size(addr.len());
}
if self.mtu.is_some() {
len += nla_size(4);
}
if self.master.is_some() {
len += nla_size(4);
}
if self.txqlen.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 name) = self.name {
write_attr_str(buf, attr_ids::IFLA_IFNAME, name);
}
if let Some(ref addr) = self.address {
write_attr_bytes(buf, attr_ids::IFLA_ADDRESS, addr);
}
if let Some(mtu) = self.mtu {
write_attr_u32(buf, attr_ids::IFLA_MTU, mtu);
}
if let Some(master) = self.master {
write_attr_u32(buf, attr_ids::IFLA_MASTER, master);
}
if let Some(txqlen) = self.txqlen {
write_attr_u32(buf, attr_ids::IFLA_TXQLEN, txqlen);
}
Ok(buf.len() - start)
}
}
fn nla_size(payload_len: usize) -> usize {
(4 + payload_len + 3) & !3
}
fn write_attr_u32(buf: &mut Vec<u8>, attr_type: u16, value: u32) {
let len: u16 = 8;
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());
}
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;
for _ in 0..(aligned - len) {
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);
}
}
#[derive(Debug, Clone, Default)]
pub struct LinkMessageBuilder {
msg: LinkMessage,
}
impl LinkMessageBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn ifindex(mut self, index: i32) -> Self {
self.msg.header.ifi_index = index;
self
}
pub fn flags(mut self, flags: u32) -> Self {
self.msg.header.ifi_flags = flags;
self
}
pub fn change(mut self, change: u32) -> Self {
self.msg.header.ifi_change = change;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.msg.name = Some(name.into());
self
}
pub fn mtu(mut self, mtu: u32) -> Self {
self.msg.mtu = Some(mtu);
self
}
pub fn address(mut self, addr: Vec<u8>) -> Self {
self.msg.address = Some(addr);
self
}
pub fn master(mut self, master: u32) -> Self {
self.msg.master = Some(master);
self
}
pub fn txqlen(mut self, txqlen: u32) -> Self {
self.msg.txqlen = Some(txqlen);
self
}
pub fn build(self) -> LinkMessage {
self.msg
}
}
mod bond_info_ids {
pub const IFLA_BOND_MODE: u16 = 1;
pub const IFLA_BOND_ACTIVE_SLAVE: u16 = 2;
pub const IFLA_BOND_MIIMON: u16 = 3;
pub const IFLA_BOND_UPDELAY: u16 = 4;
pub const IFLA_BOND_DOWNDELAY: u16 = 5;
pub const IFLA_BOND_USE_CARRIER: u16 = 6;
pub const IFLA_BOND_ARP_INTERVAL: u16 = 7;
pub const IFLA_BOND_ARP_VALIDATE: u16 = 9;
pub const IFLA_BOND_PRIMARY: u16 = 11;
pub const IFLA_BOND_XMIT_HASH_POLICY: u16 = 14;
pub const IFLA_BOND_ALL_SLAVES_ACTIVE: u16 = 17;
pub const IFLA_BOND_MIN_LINKS: u16 = 18;
pub const IFLA_BOND_AD_LACP_RATE: u16 = 21;
pub const IFLA_BOND_AD_INFO: u16 = 23;
}
mod bond_ad_info_ids {
pub const IFLA_BOND_AD_INFO_AGGREGATOR: u16 = 1;
pub const IFLA_BOND_AD_INFO_NUM_PORTS: u16 = 2;
pub const IFLA_BOND_AD_INFO_ACTOR_KEY: u16 = 3;
pub const IFLA_BOND_AD_INFO_PARTNER_KEY: u16 = 4;
pub const IFLA_BOND_AD_INFO_PARTNER_MAC: u16 = 5;
}
mod bond_slave_ids {
pub const IFLA_BOND_SLAVE_STATE: u16 = 1;
pub const IFLA_BOND_SLAVE_MII_STATUS: u16 = 2;
pub const IFLA_BOND_SLAVE_LINK_FAILURE_COUNT: u16 = 3;
pub const IFLA_BOND_SLAVE_PERM_HWADDR: u16 = 4;
pub const IFLA_BOND_SLAVE_QUEUE_ID: u16 = 5;
pub const IFLA_BOND_SLAVE_AD_AGGREGATOR_ID: u16 = 6;
pub const IFLA_BOND_SLAVE_PRIO: u16 = 9;
}
#[derive(Debug, Clone)]
pub struct BondInfo {
pub mode: u8,
pub miimon: u32,
pub updelay: u32,
pub downdelay: u32,
pub xmit_hash_policy: u8,
pub min_links: u32,
pub lacp_rate: Option<u8>,
pub ad_info: Option<BondAdInfo>,
pub primary: Option<u32>,
pub active_slave: Option<u32>,
pub use_carrier: bool,
pub all_slaves_active: bool,
pub arp_interval: u32,
pub arp_validate: Option<u32>,
}
impl BondInfo {
pub fn bond_mode(&self) -> Option<crate::netlink::link::BondMode> {
crate::netlink::link::BondMode::try_from(self.mode).ok()
}
pub fn hash_policy(&self) -> Option<crate::netlink::link::XmitHashPolicy> {
crate::netlink::link::XmitHashPolicy::try_from(self.xmit_hash_policy).ok()
}
}
#[derive(Debug, Clone)]
pub struct BondAdInfo {
pub aggregator_id: u16,
pub num_ports: u16,
pub actor_key: u16,
pub partner_key: u16,
pub partner_mac: [u8; 6],
}
#[derive(Debug, Clone)]
pub struct BondSlaveInfo {
pub state: BondSlaveState,
pub mii_status: MiiStatus,
pub link_failure_count: u32,
pub perm_hwaddr: Option<[u8; 6]>,
pub queue_id: Option<u16>,
pub ad_aggregator_id: Option<u16>,
pub prio: Option<i32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BondSlaveState {
Active,
Backup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MiiStatus {
Up,
Down,
}
impl LinkMessage {
pub fn bond_info(&self) -> Option<BondInfo> {
let link_info = self.link_info.as_ref()?;
if link_info.kind.as_deref() != Some("bond") {
return None;
}
let data = link_info.data.as_deref()?;
Some(parse_bond_info(data))
}
pub fn bond_slave_info(&self) -> Option<BondSlaveInfo> {
let link_info = self.link_info.as_ref()?;
if link_info.slave_kind.as_deref() != Some("bond") {
return None;
}
let data = link_info.slave_data.as_deref()?;
Some(parse_bond_slave_info(data))
}
pub fn is_bond_slave(&self) -> bool {
self.link_info
.as_ref()
.and_then(|i| i.slave_kind.as_deref())
== Some("bond")
}
}
fn parse_u32_ne(data: &[u8]) -> Option<u32> {
if data.len() >= 4 {
Some(u32::from_ne_bytes([data[0], data[1], data[2], data[3]]))
} else {
None
}
}
fn parse_u16_ne(data: &[u8]) -> Option<u16> {
if data.len() >= 2 {
Some(u16::from_ne_bytes([data[0], data[1]]))
} else {
None
}
}
fn parse_bond_info(data: &[u8]) -> BondInfo {
let mut info = BondInfo {
mode: 0,
miimon: 0,
updelay: 0,
downdelay: 0,
xmit_hash_policy: 0,
min_links: 0,
lacp_rate: None,
ad_info: None,
primary: None,
active_slave: None,
use_carrier: true,
all_slaves_active: false,
arp_interval: 0,
arp_validate: None,
};
let mut pos = 0;
while pos + 4 <= data.len() {
let len = u16::from_ne_bytes([data[pos], data[pos + 1]]) as usize;
let attr_type = u16::from_ne_bytes([data[pos + 2], data[pos + 3]]) & 0x3FFF;
if len < 4 || pos + len > data.len() {
break;
}
let payload = &data[pos + 4..pos + len];
match attr_type {
bond_info_ids::IFLA_BOND_MODE if !payload.is_empty() => {
info.mode = payload[0];
}
bond_info_ids::IFLA_BOND_MIIMON => {
if let Some(v) = parse_u32_ne(payload) {
info.miimon = v;
}
}
bond_info_ids::IFLA_BOND_UPDELAY => {
if let Some(v) = parse_u32_ne(payload) {
info.updelay = v;
}
}
bond_info_ids::IFLA_BOND_DOWNDELAY => {
if let Some(v) = parse_u32_ne(payload) {
info.downdelay = v;
}
}
bond_info_ids::IFLA_BOND_USE_CARRIER if !payload.is_empty() => {
info.use_carrier = payload[0] != 0;
}
bond_info_ids::IFLA_BOND_ARP_INTERVAL => {
if let Some(v) = parse_u32_ne(payload) {
info.arp_interval = v;
}
}
bond_info_ids::IFLA_BOND_ARP_VALIDATE => {
info.arp_validate = parse_u32_ne(payload);
}
bond_info_ids::IFLA_BOND_PRIMARY => {
info.primary = parse_u32_ne(payload);
}
bond_info_ids::IFLA_BOND_ACTIVE_SLAVE => {
info.active_slave = parse_u32_ne(payload);
}
bond_info_ids::IFLA_BOND_XMIT_HASH_POLICY if !payload.is_empty() => {
info.xmit_hash_policy = payload[0];
}
bond_info_ids::IFLA_BOND_ALL_SLAVES_ACTIVE if !payload.is_empty() => {
info.all_slaves_active = payload[0] != 0;
}
bond_info_ids::IFLA_BOND_MIN_LINKS => {
if let Some(v) = parse_u32_ne(payload) {
info.min_links = v;
}
}
bond_info_ids::IFLA_BOND_AD_LACP_RATE if !payload.is_empty() => {
info.lacp_rate = Some(payload[0]);
}
bond_info_ids::IFLA_BOND_AD_INFO => {
info.ad_info = Some(parse_bond_ad_info(payload));
}
_ => {}
}
pos += (len + 3) & !3;
}
info
}
fn parse_bond_ad_info(data: &[u8]) -> BondAdInfo {
let mut info = BondAdInfo {
aggregator_id: 0,
num_ports: 0,
actor_key: 0,
partner_key: 0,
partner_mac: [0; 6],
};
let mut pos = 0;
while pos + 4 <= data.len() {
let len = u16::from_ne_bytes([data[pos], data[pos + 1]]) as usize;
let attr_type = u16::from_ne_bytes([data[pos + 2], data[pos + 3]]) & 0x3FFF;
if len < 4 || pos + len > data.len() {
break;
}
let payload = &data[pos + 4..pos + len];
match attr_type {
bond_ad_info_ids::IFLA_BOND_AD_INFO_AGGREGATOR => {
if let Some(v) = parse_u16_ne(payload) {
info.aggregator_id = v;
}
}
bond_ad_info_ids::IFLA_BOND_AD_INFO_NUM_PORTS => {
if let Some(v) = parse_u16_ne(payload) {
info.num_ports = v;
}
}
bond_ad_info_ids::IFLA_BOND_AD_INFO_ACTOR_KEY => {
if let Some(v) = parse_u16_ne(payload) {
info.actor_key = v;
}
}
bond_ad_info_ids::IFLA_BOND_AD_INFO_PARTNER_KEY => {
if let Some(v) = parse_u16_ne(payload) {
info.partner_key = v;
}
}
bond_ad_info_ids::IFLA_BOND_AD_INFO_PARTNER_MAC if payload.len() >= 6 => {
info.partner_mac.copy_from_slice(&payload[..6]);
}
_ => {}
}
pos += (len + 3) & !3;
}
info
}
fn parse_bond_slave_info(data: &[u8]) -> BondSlaveInfo {
let mut info = BondSlaveInfo {
state: BondSlaveState::Backup,
mii_status: MiiStatus::Down,
link_failure_count: 0,
perm_hwaddr: None,
queue_id: None,
ad_aggregator_id: None,
prio: None,
};
let mut pos = 0;
while pos + 4 <= data.len() {
let len = u16::from_ne_bytes([data[pos], data[pos + 1]]) as usize;
let attr_type = u16::from_ne_bytes([data[pos + 2], data[pos + 3]]) & 0x3FFF;
if len < 4 || pos + len > data.len() {
break;
}
let payload = &data[pos + 4..pos + len];
match attr_type {
bond_slave_ids::IFLA_BOND_SLAVE_STATE if !payload.is_empty() => {
info.state = if payload[0] == 0 {
BondSlaveState::Active
} else {
BondSlaveState::Backup
};
}
bond_slave_ids::IFLA_BOND_SLAVE_MII_STATUS if !payload.is_empty() => {
info.mii_status = if payload[0] == 0 {
MiiStatus::Up
} else {
MiiStatus::Down
};
}
bond_slave_ids::IFLA_BOND_SLAVE_LINK_FAILURE_COUNT => {
if let Some(v) = parse_u32_ne(payload) {
info.link_failure_count = v;
}
}
bond_slave_ids::IFLA_BOND_SLAVE_PERM_HWADDR if payload.len() >= 6 => {
let mut addr = [0u8; 6];
addr.copy_from_slice(&payload[..6]);
info.perm_hwaddr = Some(addr);
}
bond_slave_ids::IFLA_BOND_SLAVE_QUEUE_ID => {
info.queue_id = parse_u16_ne(payload);
}
bond_slave_ids::IFLA_BOND_SLAVE_AD_AGGREGATOR_ID => {
info.ad_aggregator_id = parse_u16_ne(payload);
}
bond_slave_ids::IFLA_BOND_SLAVE_PRIO if payload.len() >= 4 => {
info.prio = Some(i32::from_ne_bytes([
payload[0], payload[1], payload[2], payload[3],
]));
}
_ => {}
}
pos += (len + 3) & !3;
}
info
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder() {
let msg = LinkMessageBuilder::new()
.ifindex(1)
.name("eth0")
.mtu(1500)
.build();
assert_eq!(msg.ifindex(), 1);
assert_eq!(msg.name, Some("eth0".to_string()));
assert_eq!(msg.mtu, Some(1500));
}
}