diff --git a/src/link/link_info/info_data.rs b/src/link/link_info/info_data.rs index fc847214..ca48d3db 100644 --- a/src/link/link_info/info_data.rs +++ b/src/link/link_info/info_data.rs @@ -8,8 +8,8 @@ use netlink_packet_core::{ use super::super::{ InfoBond, InfoBridge, InfoGeneve, InfoGreTap, InfoGreTap6, InfoGreTun, InfoGreTun6, InfoGtp, InfoHsr, InfoIpTunnel, InfoIpVlan, InfoIpVtap, - InfoIpoib, InfoKind, InfoMacSec, InfoMacVlan, InfoMacVtap, InfoTun, - InfoVeth, InfoVlan, InfoVrf, InfoVti, InfoVxlan, InfoXfrm, + InfoIpoib, InfoKind, InfoMacSec, InfoMacVlan, InfoMacVtap, InfoNetkit, + InfoTun, InfoVeth, InfoVlan, InfoVrf, InfoVti, InfoVxlan, InfoXfrm, }; const IFLA_INFO_DATA: u16 = 2; @@ -41,6 +41,7 @@ pub enum InfoData { Hsr(Vec), Geneve(Vec), IpTunnel(Vec), + Netkit(Vec), Other(Vec), } @@ -71,6 +72,7 @@ impl Nla for InfoData { Self::Gtp(nlas) => nlas.as_slice().buffer_len(), Self::Geneve(nlas) => nlas.as_slice().buffer_len(), Self::IpTunnel(nlas) => nlas.as_slice().buffer_len(), + Self::Netkit(nlas) => nlas.as_slice().buffer_len(), Self::Other(v) => v.len(), } } @@ -101,6 +103,7 @@ impl Nla for InfoData { Self::Gtp(nlas) => nlas.as_slice().emit(buffer), Self::Geneve(nlas) => nlas.as_slice().emit(buffer), Self::IpTunnel(nlas) => nlas.as_slice().emit(buffer), + Self::Netkit(nlas) => nlas.as_slice().emit(buffer), Self::Other(v) => buffer.copy_from_slice(v.as_slice()), } } @@ -378,6 +381,17 @@ impl InfoData { } InfoData::Geneve(v) } + InfoKind::Netkit => { + let mut v = Vec::new(); + for nla in NlasIterator::new(payload) { + let nla = &nla.context(format!( + "invalid IFLA_INFO_DATA for {kind} {payload:?}" + ))?; + let parsed = InfoNetkit::parse(nla)?; + v.push(parsed); + } + InfoData::Netkit(v) + } _ => InfoData::Other(payload.to_vec()), }) } diff --git a/src/link/link_info/infos.rs b/src/link/link_info/infos.rs index c7977eec..2897c769 100644 --- a/src/link/link_info/infos.rs +++ b/src/link/link_info/infos.rs @@ -42,6 +42,7 @@ const XFRM: &str = "xfrm"; const MACSEC: &str = "macsec"; const HSR: &str = "hsr"; const GENEVE: &str = "geneve"; +const NETKIT: &str = "netkit"; #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] @@ -201,6 +202,7 @@ pub enum InfoKind { MacSec, Hsr, Geneve, + Netkit, Other(String), } @@ -239,6 +241,7 @@ impl std::fmt::Display for InfoKind { Self::MacSec => MACSEC, Self::Hsr => HSR, Self::Geneve => GENEVE, + Self::Netkit => NETKIT, Self::Other(s) => s.as_str(), } ) @@ -277,6 +280,7 @@ impl Nla for InfoKind { Self::MacSec => MACSEC.len(), Self::Hsr => HSR.len(), Self::Geneve => GENEVE.len(), + Self::Netkit => NETKIT.len(), Self::Other(s) => s.len(), }; len + 1 @@ -335,6 +339,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoKind { XFRM => Self::Xfrm, HSR => Self::Hsr, GENEVE => Self::Geneve, + NETKIT => Self::Netkit, _ => Self::Other(s), }) } diff --git a/src/link/link_info/mod.rs b/src/link/link_info/mod.rs index 9d7de53f..5da367cd 100644 --- a/src/link/link_info/mod.rs +++ b/src/link/link_info/mod.rs @@ -19,6 +19,7 @@ mod iptunnel; mod ipvlan; mod mac_vlan; mod macsec; +mod netkit; mod tun; mod veth; mod vlan; @@ -55,6 +56,7 @@ pub use self::{ }, mac_vlan::{InfoMacVlan, InfoMacVtap, MacVlanMode, MacVtapMode}, macsec::{InfoMacSec, MacSecCipherId, MacSecOffload, MacSecValidate}, + netkit::{InfoNetkit, NetkitMode, NetkitPolicy}, tun::InfoTun, veth::InfoVeth, vlan::{InfoVlan, VlanQosMapping}, diff --git a/src/link/link_info/netkit.rs b/src/link/link_info/netkit.rs new file mode 100644 index 00000000..8d05c480 --- /dev/null +++ b/src/link/link_info/netkit.rs @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{ + emit_u32, parse_u32, parse_u8, DecodeError, DefaultNla, Emitable, + ErrorContext, Nla, NlaBuffer, Parseable, +}; + +use super::super::{LinkMessage, LinkMessageBuffer}; + +const NETKIT_MODE_L2: u32 = 0; +const NETKIT_MODE_L3: u32 = 1; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum NetkitMode { + L2, + L3, + Other(u32), +} + +impl From for u32 { + fn from(mode: NetkitMode) -> Self { + match mode { + NetkitMode::L2 => NETKIT_MODE_L2, + NetkitMode::L3 => NETKIT_MODE_L3, + NetkitMode::Other(value) => value, + } + } +} + +impl From for NetkitMode { + fn from(value: u32) -> Self { + match value { + NETKIT_MODE_L2 => NetkitMode::L2, + NETKIT_MODE_L3 => NetkitMode::L3, + _ => NetkitMode::Other(value), + } + } +} + +const NETKIT_POLICY_PASS: u32 = 0; +const NETKIT_POLICY_DROP: u32 = 2; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum NetkitPolicy { + Pass, + Drop, + Other(u32), +} + +impl From for u32 { + fn from(policy: NetkitPolicy) -> Self { + match policy { + NetkitPolicy::Pass => NETKIT_POLICY_PASS, + NetkitPolicy::Drop => NETKIT_POLICY_DROP, + NetkitPolicy::Other(value) => value, + } + } +} + +impl From for NetkitPolicy { + fn from(value: u32) -> Self { + match value { + NETKIT_POLICY_PASS => NetkitPolicy::Pass, + NETKIT_POLICY_DROP => NetkitPolicy::Drop, + _ => NetkitPolicy::Other(value), + } + } +} + +const IFLA_NETKIT_PEER_INFO: u16 = 1; +const IFLA_NETKIT_PRIMARY: u16 = 2; +const IFLA_NETKIT_POLICY: u16 = 3; +const IFLA_NETKIT_PEER_POLICY: u16 = 4; +const IFLA_NETKIT_MODE: u16 = 5; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum InfoNetkit { + Peer(LinkMessage), + Primary(bool), + Policy(NetkitPolicy), + PeerPolicy(NetkitPolicy), + Mode(NetkitMode), + Other(DefaultNla), +} + +impl Nla for InfoNetkit { + fn value_len(&self) -> usize { + match *self { + Self::Peer(ref message) => message.buffer_len(), + Self::Primary(_) => 1, + Self::Policy(_) | Self::PeerPolicy(_) | Self::Mode(_) => 4, + Self::Other(ref attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match *self { + Self::Peer(ref message) => message.emit(buffer), + Self::Primary(value) => { + buffer[0] = value as u8; + } + Self::Policy(value) | Self::PeerPolicy(value) => { + emit_u32(buffer, value.into()).unwrap(); + } + Self::Mode(value) => { + emit_u32(buffer, value.into()).unwrap(); + } + Self::Other(ref attr) => attr.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match *self { + Self::Peer(_) => IFLA_NETKIT_PEER_INFO, + Self::Primary(_) => IFLA_NETKIT_PRIMARY, + Self::Policy(_) => IFLA_NETKIT_POLICY, + Self::PeerPolicy(_) => IFLA_NETKIT_PEER_POLICY, + Self::Mode(_) => IFLA_NETKIT_MODE, + Self::Other(ref attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for InfoNetkit { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + IFLA_NETKIT_PEER_INFO => { + let err = "failed to parse netkit peer info"; + let buffer = + LinkMessageBuffer::new_checked(&payload).context(err)?; + Self::Peer(LinkMessage::parse(&buffer).context(err)?) + } + IFLA_NETKIT_PRIMARY => { + let value = parse_u8(payload)? != 0; + Self::Primary(value) + } + IFLA_NETKIT_POLICY => Self::Policy(parse_u32(payload)?.into()), + IFLA_NETKIT_PEER_POLICY => { + Self::PeerPolicy(parse_u32(payload)?.into()) + } + IFLA_NETKIT_MODE => Self::Mode(parse_u32(payload)?.into()), + kind => Self::Other( + DefaultNla::parse(buf) + .context(format!("unknown NLA type {kind} for netkit"))?, + ), + }) + } +} diff --git a/src/link/mod.rs b/src/link/mod.rs index 1b99bb19..e6ea44b2 100644 --- a/src/link/mod.rs +++ b/src/link/mod.rs @@ -48,12 +48,12 @@ pub use self::{ InfoBridge, InfoBridgePort, InfoData, InfoGeneve, InfoGreTap, InfoGreTap6, InfoGreTun, InfoGreTun6, InfoGtp, InfoHsr, InfoIpTunnel, InfoIpVlan, InfoIpVtap, InfoIpoib, InfoKind, InfoMacSec, InfoMacVlan, - InfoMacVtap, InfoPortData, InfoPortKind, InfoTun, InfoVeth, InfoVlan, - InfoVrf, InfoVrfPort, InfoVti, InfoVxlan, InfoXfrm, IpVlanFlags, - IpVlanMode, IpVtapFlags, IpVtapMode, LinkInfo, LinkXstats, + InfoMacVtap, InfoNetkit, InfoPortData, InfoPortKind, InfoTun, InfoVeth, + InfoVlan, InfoVrf, InfoVrfPort, InfoVti, InfoVxlan, InfoXfrm, + IpVlanFlags, IpVlanMode, IpVtapFlags, IpVtapMode, LinkInfo, LinkXstats, MacSecCipherId, MacSecOffload, MacSecValidate, MacVlanMode, - MacVtapMode, MiiStatus, TunnelEncapFlags, TunnelEncapType, - VlanQosMapping, + MacVtapMode, MiiStatus, NetkitMode, NetkitPolicy, TunnelEncapFlags, + TunnelEncapType, VlanQosMapping, }, link_layer_type::LinkLayerType, link_mode::LinkMode, diff --git a/src/link/tests/mod.rs b/src/link/tests/mod.rs index 1bee0793..d8f277c5 100644 --- a/src/link/tests/mod.rs +++ b/src/link/tests/mod.rs @@ -27,6 +27,8 @@ mod macvtap; #[cfg(test)] mod message; #[cfg(test)] +mod netkit; +#[cfg(test)] mod prop_list; #[cfg(test)] mod sriov; diff --git a/src/link/tests/netkit.rs b/src/link/tests/netkit.rs new file mode 100644 index 00000000..8a21cfd8 --- /dev/null +++ b/src/link/tests/netkit.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_core::{Emitable, Parseable}; + +use crate::{ + link::{ + InfoData, InfoKind, InfoNetkit, LinkAttribute, LinkHeader, LinkInfo, + LinkLayerType, LinkMessage, LinkMessageBuffer, NetkitMode, + NetkitPolicy, + }, + AddressFamily, +}; + +#[test] +fn test_create_netkit() { + // Captured from kernel 6.14.0 with iproute2-6.14.0 + // ```sh + // sudo ip link add nlmon0 type nlmon + // sudo ip link set nlmon0 up + // sudo tcpdump -i nlmon0 -w netkit.pcap & + // sudo ip link add nktest0 type netkit peer name nktest1 + // sudo pkill tcpdump + // ``` + // + // Tests basic netkit attributes (PRIMARY, POLICY, MODE) without + // kernel-version-specific attributes that may vary + let raw: Vec = vec![ + 0x00, // interface family AF_UNSPEC + 0x00, // reserved + 0x00, 0x00, // link layer type 0 (Netrom) + 0x00, 0x00, 0x00, 0x00, // iface index 0 + 0x00, 0x00, 0x00, 0x00, // device flags 0 + 0x00, 0x00, 0x00, 0x00, // change flags 0 + 0x0c, 0x00, // length 12 + 0x03, 0x00, // IFLA_IFNAME + 0x6e, 0x6b, 0x74, 0x65, 0x73, 0x74, 0x30, 0x00, // "nktest0\0" + 0x2c, 0x00, // length 44 + 0x12, 0x00, // IFLA_LINKINFO 18 + 0x0b, 0x00, // length 11 + 0x01, 0x00, // IFLA_INFO_KIND 1 + 0x6e, 0x65, 0x74, 0x6b, 0x69, 0x74, 0x00, // 'netkit\0' + 0x00, // padding + 0x1c, 0x00, // length 28 + 0x02, 0x00, // IFLA_INFO_DATA 2 + 0x05, 0x00, // length 5 + 0x02, 0x00, // NETKIT_INFO_PRIMARY + 0x01, // value: true + 0x00, 0x00, 0x00, // padding + 0x08, 0x00, // length 8 + 0x03, 0x00, // NETKIT_INFO_POLICY + 0x00, 0x00, 0x00, 0x00, // value: 0 (PASS) + 0x08, 0x00, // length 8 + 0x05, 0x00, // NETKIT_INFO_MODE + 0x01, 0x00, 0x00, 0x00, // value: 1 (L3) + ]; + + let expected = LinkMessage { + header: LinkHeader { + interface_family: AddressFamily::Unspec, + index: 0, + link_layer_type: LinkLayerType::Netrom, + ..Default::default() + }, + attributes: vec![ + LinkAttribute::IfName("nktest0".to_string()), + LinkAttribute::LinkInfo(vec![ + LinkInfo::Kind(InfoKind::Netkit), + LinkInfo::Data(InfoData::Netkit(vec![ + InfoNetkit::Primary(true), + InfoNetkit::Policy(NetkitPolicy::Pass), + InfoNetkit::Mode(NetkitMode::L3), + ])), + ]), + ], + }; + + assert_eq!( + expected, + LinkMessage::parse(&LinkMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +}