netlink-packet-route 0.32.1

netlink packet types
Documentation
// SPDX-License-Identifier: MIT

use netlink_packet_core::{Emitable, Parseable};

use crate::{
    stats::{
        AfSpecStats, BridgeMcastStats, BridgeStpXstats, BridgeVlanXstats,
        BridgeXstat, HwStatsInfo, LinkXstatGroup, OffloadXstat, StatsAttribute,
        StatsFilterMask, StatsHeader, StatsMessage, StatsMessageBuffer,
    },
    AddressFamily,
};

// Synthetic data matching kernel layout: RTM_NEWSTATS for br-test
// (ifindex=129, filter_mask=0x06) with IFLA_STATS_LINK_XSTATS containing
// bridge VLAN + MCAST data. All counter values are zero.
// TODO: replace with real nlmon capture.
#[test]
fn test_parsing_bridge_xstats() {
    let raw: Vec<u8> = {
        let mut v = vec![
            0x00, 0x00, 0x00, 0x00, // family=0, pad=0
            0x81, 0x00, 0x00, 0x00, // ifindex=129
            0x06, 0x00, 0x00, 0x00, // filter_mask=0x06
            // NLA: IFLA_STATS_LINK_XSTATS(type=2), len=296
            0x28, 0x01, 0x02, 0x00,
            // Inner: LINK_XSTATS_TYPE_BRIDGE(type=1), len=292
            0x24, 0x01, 0x01, 0x00,
            // Inner: BRIDGE_XSTATS_VLAN(type=1), len=44
            0x2c, 0x00, 0x01, 0x00,
            // struct bridge_vlan_xstats (40 bytes)
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rx_bytes=0
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, // rx_packets=0
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tx_bytes=0
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, // tx_packets=0
            0x01, 0x00, // vid=1
            0x27, 0x00, // flags=0x27
            0x00, 0x00, 0x00, 0x00, // pad2=0
            // Inner: BRIDGE_XSTATS_MCAST(type=2), len=244
            0xf4, 0x00, 0x02, 0x00,
        ];
        // struct br_mcast_stats (240 bytes, all zeros)
        v.extend_from_slice(&[0u8; 240]);
        v
    };

    let expected = StatsMessage {
        header: StatsHeader {
            family: AddressFamily::Unspec,
            ifindex: 129,
            filter_mask: StatsFilterMask::LinkXstats
                | StatsFilterMask::LinkXstatsPort,
        },
        attributes: vec![StatsAttribute::LinkXstats(vec![
            LinkXstatGroup::Bridge(vec![
                BridgeXstat::Vlan(BridgeVlanXstats {
                    vid: 1,
                    flags: 0x27,
                    ..Default::default()
                }),
                BridgeXstat::Mcast(BridgeMcastStats::default()),
            ]),
        ])],
    };

    assert_eq!(
        expected,
        StatsMessage::parse(&StatsMessageBuffer::new(&raw)).unwrap()
    );

    let mut buf = vec![0; expected.buffer_len()];
    expected.emit(&mut buf);
    assert_eq!(buf, raw);
}

// nlmon capture of kernel reply on `ip stats show dev vnet3` where vnet3
// is a linux bridge port
#[test]
fn test_parsing_bridge_port_xstats() {
    let raw: Vec<u8> = vec![
        0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
        0xcc, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x36, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xce, 0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x5c, 0x01, 0x03, 0x00, 0x58, 0x01, 0x01, 0x00, 0x2c, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x04, 0x00,
        0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x12, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x1c, 0x00, 0x04, 0x00, 0x18, 0x00, 0x02, 0x80, 0x14, 0x00, 0x03, 0x80,
        0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x05, 0x00,
    ];

    let expected = StatsMessage {
        header: StatsHeader {
            family: AddressFamily::Unspec,
            ifindex: 9,
            filter_mask: StatsFilterMask::Link64
                | StatsFilterMask::LinkXstats
                | StatsFilterMask::LinkXstatsPort
                | StatsFilterMask::LinkOffloadXstats
                | StatsFilterMask::AfSpec,
        },
        attributes: vec![
            StatsAttribute::Link64(crate::link::Stats64 {
                tx_packets: 11574,
                tx_bytes: 1248974,
                ..Default::default()
            }),
            StatsAttribute::LinkXstatsPort(vec![LinkXstatGroup::Bridge(vec![
                BridgeXstat::Vlan(BridgeVlanXstats {
                    vid: 1,
                    flags: 6,
                    ..Default::default()
                }),
                BridgeXstat::Mcast(BridgeMcastStats::default()),
                BridgeXstat::Stp(BridgeStpXstats {
                    transition_blk: 2,
                    transition_fwd: 1,
                    tx_bpdu: 10002,
                    ..Default::default()
                }),
            ])]),
            StatsAttribute::LinkOffloadXstats(vec![OffloadXstat::HwStatsInfo(
                HwStatsInfo {
                    request: Some(0),
                    used: Some(0),
                },
            )]),
            StatsAttribute::AfSpec(AfSpecStats(vec![])),
        ],
    };

    assert_eq!(
        expected,
        StatsMessage::parse(&StatsMessageBuffer::new(&raw)).unwrap()
    );

    let mut buf = vec![0; expected.buffer_len()];
    expected.emit(&mut buf);
    assert_eq!(buf, raw);
}