diff --git a/src/tc/actions/action.rs b/src/tc/actions/action.rs index d56ba9a7..2e061c82 100644 --- a/src/tc/actions/action.rs +++ b/src/tc/actions/action.rs @@ -14,6 +14,7 @@ use crate::tc::TcStats2; use super::{ TcActionMirror, TcActionMirrorOption, TcActionNat, TcActionNatOption, + TcActionTunnelKey, TcActionTunnelKeyOption, }; /// TODO: determine when and why to use this as opposed to the buffer's `kind`. @@ -287,6 +288,11 @@ pub enum TcActionOption { /// /// These options type can be used to perform network address translation. Nat(TcActionNatOption), + /// Tunnel key options. + /// + /// These options type can be used to assign encapsulation properties to + /// the packet. + TunnelKey(TcActionTunnelKeyOption), /// Other action types not yet supported by this library. Other(DefaultNla), } @@ -296,6 +302,7 @@ impl Nla for TcActionOption { match self { Self::Mirror(nla) => nla.value_len(), Self::Nat(nla) => nla.value_len(), + Self::TunnelKey(nla) => nla.value_len(), Self::Other(nla) => nla.value_len(), } } @@ -304,6 +311,7 @@ impl Nla for TcActionOption { match self { Self::Mirror(nla) => nla.emit_value(buffer), Self::Nat(nla) => nla.emit_value(buffer), + Self::TunnelKey(nla) => nla.emit_value(buffer), Self::Other(nla) => nla.emit_value(buffer), } } @@ -312,6 +320,7 @@ impl Nla for TcActionOption { match self { Self::Mirror(nla) => nla.kind(), Self::Nat(nla) => nla.kind(), + Self::TunnelKey(nla) => nla.kind(), Self::Other(nla) => nla.kind(), } } @@ -335,6 +344,10 @@ where TcActionNatOption::parse(buf) .context("failed to parse nat action")?, ), + TcActionTunnelKey::KIND => Self::TunnelKey( + TcActionTunnelKeyOption::parse(buf) + .context("failed to parse tunnel_key action")?, + ), _ => Self::Other( DefaultNla::parse(buf) .context("failed to parse action options")?, @@ -548,3 +561,46 @@ impl From for i32 { } } } + +pub const TC_TCF_BUF_LEN: usize = 32; + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct Tcf { + pub install: u64, + pub lastuse: u64, + pub expires: u64, + pub firstuse: u64, +} + +// kernel struct `tcf_t` +buffer!(TcfBuffer(TC_TCF_BUF_LEN) { + install: (u64, 0..8), + lastuse: (u64, 8..16), + expires: (u64, 16..24), + firstuse: (u64, 24..32), +}); + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Tcf { + fn parse(buf: &TcfBuffer<&T>) -> Result { + Ok(Self { + install: buf.install(), + lastuse: buf.lastuse(), + expires: buf.expires(), + firstuse: buf.firstuse(), + }) + } +} + +impl Emitable for Tcf { + fn buffer_len(&self) -> usize { + TC_TCF_BUF_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut packet = TcfBuffer::new(buffer); + packet.set_install(self.install); + packet.set_lastuse(self.lastuse); + packet.set_expires(self.expires); + packet.set_firstuse(self.firstuse); + } +} diff --git a/src/tc/actions/mirror.rs b/src/tc/actions/mirror.rs index 19307a29..cec6fde2 100644 --- a/src/tc/actions/mirror.rs +++ b/src/tc/actions/mirror.rs @@ -12,7 +12,9 @@ use netlink_packet_utils::{ DecodeError, }; -use super::{TcActionGeneric, TcActionGenericBuffer}; +use super::{ + TcActionGeneric, TcActionGenericBuffer, Tcf, TcfBuffer, TC_TCF_BUF_LEN, +}; /// Traffic control action used to mirror or redirect packets. #[derive(Debug, PartialEq, Eq, Clone)] @@ -30,8 +32,8 @@ const TCA_MIRRED_PARMS: u16 = 2; #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionMirrorOption { - /// TODO: document this after we make it something better than `Vec` - Tm(Vec), + /// Rule installation and usage time + Tm(Tcf), /// Parameters for the mirred action. Parms(TcMirror), /// Other attributes unknown at the time of writing. @@ -41,7 +43,7 @@ pub enum TcActionMirrorOption { impl Nla for TcActionMirrorOption { fn value_len(&self) -> usize { match self { - Self::Tm(bytes) => bytes.len(), + Self::Tm(_) => TC_TCF_BUF_LEN, Self::Parms(_) => TC_MIRRED_BUF_LEN, Self::Other(attr) => attr.value_len(), } @@ -49,7 +51,7 @@ impl Nla for TcActionMirrorOption { fn emit_value(&self, buffer: &mut [u8]) { match self { - Self::Tm(bytes) => buffer.copy_from_slice(bytes.as_slice()), + Self::Tm(p) => p.emit(buffer), Self::Parms(p) => p.emit(buffer), Self::Other(attr) => attr.emit_value(buffer), } @@ -69,7 +71,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { - TCA_MIRRED_TM => Self::Tm(payload.to_vec()), + TCA_MIRRED_TM => { + Self::Tm(Tcf::parse(&TcfBuffer::new_checked(payload)?)?) + } TCA_MIRRED_PARMS => Self::Parms(TcMirror::parse( &TcMirrorBuffer::new_checked(payload)?, )?), diff --git a/src/tc/actions/mod.rs b/src/tc/actions/mod.rs index e426ee30..aef20219 100644 --- a/src/tc/actions/mod.rs +++ b/src/tc/actions/mod.rs @@ -4,7 +4,7 @@ pub use nat_flag::TcNatFlags; pub use self::action::{ TcAction, TcActionAttribute, TcActionGeneric, TcActionGenericBuffer, - TcActionOption, TcActionType, + TcActionOption, TcActionType, Tcf, TcfBuffer, TC_TCF_BUF_LEN, }; pub use self::header::{TcActionMessageBuffer, TcActionMessageHeader}; pub use self::message::{ @@ -16,6 +16,9 @@ pub use self::mirror::{ TcMirrorBuffer, }; pub use self::nat::{TcActionNat, TcActionNatOption, TcNat, TcNatBuffer}; +pub use self::tunnel_key::{ + TcActionTunnelKey, TcActionTunnelKeyOption, TcTunnelKey, +}; mod action; mod header; @@ -23,6 +26,7 @@ mod message; mod mirror; mod nat; mod nat_flag; +mod tunnel_key; #[cfg(test)] pub mod tests; diff --git a/src/tc/actions/nat.rs b/src/tc/actions/nat.rs index a46cc528..2f9a8b14 100644 --- a/src/tc/actions/nat.rs +++ b/src/tc/actions/nat.rs @@ -11,7 +11,10 @@ use netlink_packet_utils::{ DecodeError, }; -use super::{nat_flag::TcNatFlags, TcActionGeneric, TcActionGenericBuffer}; +use super::{ + nat_flag::TcNatFlags, TcActionGeneric, TcActionGenericBuffer, Tcf, + TcfBuffer, TC_TCF_BUF_LEN, +}; const TCA_NAT_PARMS: u16 = 1; const TCA_NAT_TM: u16 = 2; @@ -29,8 +32,8 @@ impl TcActionNat { #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionNatOption { - /// TODO: document this after we make it something better than `Vec` - Tm(Vec), + /// Rule installation and usage time + Tm(Tcf), /// Parameters for the nat action. Parms(TcNat), /// Other attributes unknown at the time of writing. @@ -40,7 +43,7 @@ pub enum TcActionNatOption { impl Nla for TcActionNatOption { fn value_len(&self) -> usize { match self { - Self::Tm(bytes) => bytes.len(), + Self::Tm(_) => TC_TCF_BUF_LEN, Self::Parms(v) => v.buffer_len(), Self::Other(attr) => attr.value_len(), } @@ -48,7 +51,7 @@ impl Nla for TcActionNatOption { fn emit_value(&self, buffer: &mut [u8]) { match self { - Self::Tm(bytes) => buffer.copy_from_slice(bytes.as_slice()), + Self::Tm(p) => p.emit(buffer), Self::Parms(p) => p.emit(buffer), Self::Other(attr) => attr.emit_value(buffer), } @@ -68,7 +71,9 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> fn parse(buf: &NlaBuffer<&'a T>) -> Result { let payload = buf.value(); Ok(match buf.kind() { - TCA_NAT_TM => Self::Tm(payload.to_vec()), + TCA_NAT_TM => { + Self::Tm(Tcf::parse(&TcfBuffer::new_checked(payload)?)?) + } TCA_NAT_PARMS => { Self::Parms(TcNat::parse(&TcNatBuffer::new_checked(payload)?)?) } diff --git a/src/tc/actions/tests/message.rs b/src/tc/actions/tests/message.rs index d12efd14..4db8358e 100644 --- a/src/tc/actions/tests/message.rs +++ b/src/tc/actions/tests/message.rs @@ -33,7 +33,7 @@ mod mirror { use crate::tc::TcMirrorActionType::{EgressRedir, IngressMirror}; use crate::tc::TcStats2::{Basic, BasicHw, Queue}; use crate::tc::{ - TcAction, TcActionGeneric, TcMirror, TcStatsBasic, TcStatsQueue, + TcAction, TcActionGeneric, TcMirror, TcStatsBasic, TcStatsQueue, Tcf, }; use crate::AddressFamily; @@ -223,11 +223,12 @@ mod mirror { eaction: EgressRedir, ifindex: 1, })), - Mirror(Tm(vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ])), + Mirror(Tm(Tcf { + install: 0, + lastuse: 0, + expires: 0, + firstuse: 0, + })), ]), ], }, @@ -269,11 +270,12 @@ mod mirror { eaction: IngressMirror, ifindex: 1, })), - Mirror(Tm(vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, - ])), + Mirror(Tm(Tcf { + install: 0, + lastuse: 0, + expires: 0, + firstuse: 0, + })), ]), ], }, diff --git a/src/tc/actions/tests/mirror.rs b/src/tc/actions/tests/mirror.rs index 74343239..ef8d9cb3 100644 --- a/src/tc/actions/tests/mirror.rs +++ b/src/tc/actions/tests/mirror.rs @@ -4,8 +4,10 @@ use netlink_packet_utils::nla::NlaBuffer; use netlink_packet_utils::{Emitable, Parseable}; use crate::tc::{ - TcActionGeneric, TcActionGenericBuffer, TcActionMirrorOption, TcActionType, - TcMirror, TcMirrorActionType, TcMirrorBuffer, + TcAction, TcActionAttribute, TcActionGeneric, TcActionGenericBuffer, + TcActionMirrorOption, TcActionOption, TcActionType, TcMirror, + TcMirrorActionType, TcMirrorBuffer, TcStats2, TcStatsBasic, TcStatsQueue, + Tcf, }; #[test] @@ -64,22 +66,76 @@ fn tc_mirror_example_parse_back() { assert_eq!(orig, parsed); } +// > act actions add action mirred egress redirect dev veth1 +// > tools/nl_dump.py dump_actions mirred +// Note: 5.15 and 6.8 kernels do NOT set NLA_F_NESTED for TCA_ACT_OPTIONS +// #[test] -fn tc_mirror_tm_default_parse_back() { - let mirror_option = TcActionMirrorOption::Tm(vec![]); - let mut buffer = vec![0; mirror_option.buffer_len()]; - mirror_option.emit(&mut buffer); - let nla_buf = NlaBuffer::new_checked(&buffer).unwrap(); - let parsed = TcActionMirrorOption::parse(&nla_buf).unwrap(); - assert_eq!(mirror_option, parsed); -} +fn get_mirred_eggress_redirect_action() { + let raw = vec![ + 0x9C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x01, 0x00, 0x6D, 0x69, 0x72, 0x72, + 0x65, 0x64, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, 0x14, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x01, 0x00, 0x68, 0x3D, 0xB6, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x3D, 0xB6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; -#[test] -fn tc_mirror_tm_example_parse_back() { - let mirror_option = TcActionMirrorOption::Tm(vec![1, 2, 3]); - let mut buffer = vec![0; mirror_option.buffer_len()]; - mirror_option.emit(&mut buffer); - let nla_buf = NlaBuffer::new_checked(&buffer).unwrap(); - let parsed = TcActionMirrorOption::parse(&nla_buf).unwrap(); - assert_eq!(mirror_option, parsed); + let expected = TcAction { + tab: 0, + attributes: vec![ + TcActionAttribute::Kind("mirred".to_string()), + TcActionAttribute::Stats(vec![ + TcStats2::Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + TcActionAttribute::Options(vec![ + TcActionOption::Mirror(TcActionMirrorOption::Parms(TcMirror { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: TcActionType::Ok, + refcnt: 2, + bindcnt: 2, + }, + eaction: TcMirrorActionType::EgressRedir, + ifindex: 3, + })), + TcActionOption::Mirror(TcActionMirrorOption::Tm(Tcf { + install: 45497704, + lastuse: 45497704, + expires: 0, + firstuse: 0, + })), + ]), + ], + }; + + assert_eq!(expected, TcAction::parse(&NlaBuffer::new(&raw)).unwrap()); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); } diff --git a/src/tc/actions/tests/mod.rs b/src/tc/actions/tests/mod.rs index e36825fe..22a68d5c 100644 --- a/src/tc/actions/tests/mod.rs +++ b/src/tc/actions/tests/mod.rs @@ -5,3 +5,4 @@ pub mod header; pub mod message; pub mod mirror; pub mod nat; +pub mod tunnel_key; diff --git a/src/tc/actions/tests/nat.rs b/src/tc/actions/tests/nat.rs index 06d2b804..fa5ecbaa 100644 --- a/src/tc/actions/tests/nat.rs +++ b/src/tc/actions/tests/nat.rs @@ -14,7 +14,7 @@ use crate::tc::TcActionOption::Nat; use crate::tc::TcStats2::{Basic, BasicHw, Queue}; use crate::tc::{ TcAction, TcActionGeneric, TcActionNatOption, TcActionType, TcNat, - TcNatFlags, TcStatsBasic, TcStatsQueue, + TcNatFlags, TcStatsBasic, TcStatsQueue, Tcf, }; use crate::AddressFamily; @@ -248,39 +248,6 @@ fn tc_action_nat_option_emit_uses_whole_buffer() { } } -fn tc_action_nat_option_tm_examples() -> [TcActionNatOption; 4] { - [ - Tm(vec![]), - Tm(vec![1]), - Tm(vec![1, 2, 3, 4]), - Tm(vec![99; 10]), - ] -} - -#[test] -fn tc_action_nat_option_parse_back_example_tm() { - for example in &tc_action_nat_option_tm_examples() { - let mut buffer = vec![0; example.buffer_len()]; - example.emit(&mut buffer); - let parsed = TcActionNatOption::parse( - &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), - ) - .unwrap(); - assert_eq!(example, &parsed); - } -} - -#[test] -fn tc_action_nat_option_emit_tm_uses_whole_buffer() { - for example in &tc_action_nat_option_tm_examples() { - let mut buffer1 = vec![0x00; example.buffer_len()]; - let mut buffer2 = vec![0xff; example.buffer_len()]; - example.emit(&mut buffer1); - example.emit(&mut buffer2); - assert_eq!(buffer1, buffer2); - } -} - /// Setup: /// /// ```bash @@ -357,10 +324,12 @@ fn test_get_filter_nat() { mask: Ipv4Addr::BROADCAST, flags: TcNatFlags::empty(), })), - Nat(Tm(vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ])), + Nat(Tm(Tcf { + install: 0, + lastuse: 0, + expires: 0, + firstuse: 0, + })), ]), ], }])], diff --git a/src/tc/actions/tests/tunnel_key.rs b/src/tc/actions/tests/tunnel_key.rs new file mode 100644 index 00000000..87fa4133 --- /dev/null +++ b/src/tc/actions/tests/tunnel_key.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::nla::NlaBuffer; +use netlink_packet_utils::{Emitable, Parseable}; + +use crate::tc::{ + TcAction, TcActionAttribute, TcActionGeneric, TcActionOption, + TcActionTunnelKeyOption, TcActionType, TcStats2, TcStatsBasic, + TcStatsQueue, TcTunnelKey, Tcf, +}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +// > tc actions add action tunnel_key set id 33 src_ip 1.2.3.4 dst_ip +// > 2.3.4.5 dst_port 4789 tos 1 ttl 2 +// > tools/nl_dump.py dump_actions tunnel_key +// Note: 5.15 and 6.8 kernels do NOT set NLA_F_NESTED for TCA_ACT_OPTIONS +#[test] +fn get_tunnel_key_vxlan_action_ipv4() { + let raw = vec![ + 0xD4, 0x00, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x74, 0x75, 0x6E, 0x6E, + 0x65, 0x6C, 0x5F, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x02, 0x80, 0x1C, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x21, 0x08, 0x00, 0x03, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x08, 0x00, 0x04, 0x00, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x00, 0x09, 0x00, 0x12, 0xB5, 0x00, 0x00, 0x05, 0x00, 0x0A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, + 0xB0, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x23, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcAction { + tab: 1, + attributes: vec![ + TcActionAttribute::Kind("tunnel_key".to_string()), + TcActionAttribute::Stats(vec![ + TcStats2::Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + TcActionAttribute::Options(vec![ + TcActionOption::TunnelKey(TcActionTunnelKeyOption::Parms( + TcTunnelKey { + generic: TcActionGeneric { + index: 2, + capab: 0, + action: TcActionType::Pipe, + refcnt: 1, + bindcnt: 0, + }, + t_action: 1, + }, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncKeyId( + 33, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncIpv4Src( + "1.2.3.4".parse::().unwrap(), + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncIpv4Dst( + "2.3.4.5".parse::().unwrap(), + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncDstPort( + 4789, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::NoCsum( + false, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncTos(1)), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncTtl(2)), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::Tm(Tcf { + install: 9136, + lastuse: 9136, + expires: 0, + firstuse: 0, + })), + ]), + ], + }; + + assert_eq!(expected, TcAction::parse(&NlaBuffer::new(&raw)).unwrap()); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// > tc actions add action tunnel_key set id 33 src_ip 2a00:1:: dst_ip +// > 2a01:2:: dst_port 4789 tos 1 ttl 2 +// > tools/nl_dump.py dump_actions tunnel_key +// Note: 5.15 and 6.8 kernels do NOT set NLA_F_NESTED for TCA_ACT_OPTIONS +#[test] +fn test_action_tunnel_key_vxlan_ipv6() { + let raw = vec![ + 0xEC, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x74, 0x75, 0x6E, 0x6E, + 0x65, 0x6C, 0x5F, 0x6B, 0x65, 0x79, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x94, 0x00, 0x02, 0x80, 0x1C, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x21, 0x14, 0x00, 0x05, 0x00, + 0x2A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x06, 0x00, 0x2A, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x12, 0xB5, 0x00, 0x00, 0x05, 0x00, 0x0A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, + 0xFB, 0x71, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFB, 0x71, 0x3A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcAction { + tab: 0, + attributes: vec![ + TcActionAttribute::Kind("tunnel_key".to_string()), + TcActionAttribute::Stats(vec![ + TcStats2::Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + TcActionAttribute::Options(vec![ + TcActionOption::TunnelKey(TcActionTunnelKeyOption::Parms( + TcTunnelKey { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: TcActionType::Pipe, + refcnt: 1, + bindcnt: 1, + }, + t_action: 1, + }, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncKeyId( + 33, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncIpv6Src( + "2a00:1::".parse::().unwrap(), + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncIpv6Dst( + "2a01:2::".parse::().unwrap(), + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncDstPort( + 4789, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::NoCsum( + false, + )), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncTos(1)), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::EncTtl(2)), + TcActionOption::TunnelKey(TcActionTunnelKeyOption::Tm(Tcf { + install: 3830267, + lastuse: 3830267, + expires: 0, + firstuse: 0, + })), + ]), + ], + }; + + assert_eq!(expected, TcAction::parse(&NlaBuffer::new(&raw)).unwrap()); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} diff --git a/src/tc/actions/tunnel_key.rs b/src/tc/actions/tunnel_key.rs new file mode 100755 index 00000000..1a2a2569 --- /dev/null +++ b/src/tc/actions/tunnel_key.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: MIT + +use crate::ip::{parse_ipv4_addr, parse_ipv6_addr}; +use anyhow::Context; +/// set tunnel key +/// +/// The set_tunnel action allows to set tunnel encap applied +/// at the last stage of action processing +use byteorder::{BigEndian, ByteOrder}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::{parse_u16_be, parse_u32_be, parse_u8}, + traits::{Emitable, Parseable}, + DecodeError, +}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use super::{ + TcActionGeneric, TcActionGenericBuffer, Tcf, TcfBuffer, TC_TCF_BUF_LEN, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub struct TcActionTunnelKey {} +impl TcActionTunnelKey { + pub const KIND: &'static str = "tunnel_key"; +} + +const TCA_TUNNEL_KEY_TM: u16 = 1; +const TCA_TUNNEL_KEY_PARMS: u16 = 2; +const TCA_TUNNEL_KEY_ENC_IPV4_SRC: u16 = 3; +const TCA_TUNNEL_KEY_ENC_IPV4_DST: u16 = 4; +const TCA_TUNNEL_KEY_ENC_IPV6_SRC: u16 = 5; +const TCA_TUNNEL_KEY_ENC_IPV6_DST: u16 = 6; +const TCA_TUNNEL_KEY_ENC_KEY_ID: u16 = 7; +// const TCA_TUNNEL_KEY_PAD: u16 = 8; +const TCA_TUNNEL_KEY_ENC_DST_PORT: u16 = 9; +const TCA_TUNNEL_KEY_NO_CSUM: u16 = 10; +// const TCA_TUNNEL_KEY_ENC_OPTS: u16 = 11; +const TCA_TUNNEL_KEY_ENC_TOS: u16 = 12; +const TCA_TUNNEL_KEY_ENC_TTL: u16 = 13; +// const TCA_TUNNEL_KEY_NO_FRAG: u16 = 14; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum TcActionTunnelKeyOption { + Tm(Tcf), + Parms(TcTunnelKey), + EncIpv4Src(Ipv4Addr), + EncIpv4Dst(Ipv4Addr), + EncIpv6Src(Ipv6Addr), + EncIpv6Dst(Ipv6Addr), + EncKeyId(u32), + EncDstPort(u16), + EncTos(u8), + EncTtl(u8), + NoCsum(bool), + Other(DefaultNla), +} + +impl Nla for TcActionTunnelKeyOption { + fn value_len(&self) -> usize { + match self { + Self::Tm(_) => TC_TCF_BUF_LEN, + Self::Parms(_) => TC_TUNNEL_KEY_BUF_LEN, + Self::EncIpv4Src(_) | Self::EncIpv4Dst(_) => 4, + Self::EncIpv6Src(_) | Self::EncIpv6Dst(_) => 16, + Self::EncKeyId(_) => 4, + Self::EncDstPort(_) => 2, + Self::EncTos(_) | Self::EncTtl(_) => 1, + Self::NoCsum(_) => 1, + Self::Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Tm(p) => p.emit(buffer), + Self::Parms(p) => p.emit(buffer), + Self::EncIpv4Src(ip) | Self::EncIpv4Dst(ip) => { + buffer.copy_from_slice(&ip.octets()) + } + Self::EncIpv6Src(ip) | Self::EncIpv6Dst(ip) => { + buffer.copy_from_slice(&ip.octets()) + } + Self::EncKeyId(i) => BigEndian::write_u32(buffer, *i), + Self::EncDstPort(i) => BigEndian::write_u16(buffer, *i), + Self::EncTos(i) => buffer[0] = *i, + Self::EncTtl(i) => buffer[0] = *i, + Self::NoCsum(i) => buffer[0] = *i as u8, + Self::Other(attr) => attr.emit_value(buffer), + } + } + fn kind(&self) -> u16 { + match self { + Self::Tm(_) => TCA_TUNNEL_KEY_TM, + Self::Parms(_) => TCA_TUNNEL_KEY_PARMS, + Self::EncIpv4Src(_) => TCA_TUNNEL_KEY_ENC_IPV4_SRC, + Self::EncIpv4Dst(_) => TCA_TUNNEL_KEY_ENC_IPV4_DST, + Self::EncIpv6Src(_) => TCA_TUNNEL_KEY_ENC_IPV6_SRC, + Self::EncIpv6Dst(_) => TCA_TUNNEL_KEY_ENC_IPV6_DST, + Self::EncKeyId(_) => TCA_TUNNEL_KEY_ENC_KEY_ID, + Self::EncDstPort(_) => TCA_TUNNEL_KEY_ENC_DST_PORT, + Self::EncTos(_) => TCA_TUNNEL_KEY_ENC_TOS, + Self::EncTtl(_) => TCA_TUNNEL_KEY_ENC_TTL, + Self::NoCsum(_) => TCA_TUNNEL_KEY_NO_CSUM, + Self::Other(nla) => nla.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for TcActionTunnelKeyOption +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + TCA_TUNNEL_KEY_TM => { + Self::Tm(Tcf::parse(&TcfBuffer::new_checked(payload)?)?) + } + TCA_TUNNEL_KEY_PARMS => Self::Parms(TcTunnelKey::parse( + &TcTunnelKeyBuffer::new_checked(payload)?, + )?), + TCA_TUNNEL_KEY_ENC_IPV4_SRC => Self::EncIpv4Src( + parse_ipv4_addr(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_IPV4_SRC")?, + ), + TCA_TUNNEL_KEY_ENC_IPV4_DST => Self::EncIpv4Dst( + parse_ipv4_addr(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_IPV4_DST")?, + ), + TCA_TUNNEL_KEY_ENC_IPV6_SRC => Self::EncIpv6Src( + parse_ipv6_addr(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_IPV6_SRC")?, + ), + TCA_TUNNEL_KEY_ENC_IPV6_DST => Self::EncIpv6Dst( + parse_ipv6_addr(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_IPV6_DST")?, + ), + TCA_TUNNEL_KEY_ENC_KEY_ID => Self::EncKeyId( + parse_u32_be(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_KEY_ID")?, + ), + TCA_TUNNEL_KEY_ENC_DST_PORT => Self::EncDstPort( + parse_u16_be(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_DST_PORT")?, + ), + TCA_TUNNEL_KEY_ENC_TOS => Self::EncTos( + parse_u8(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_TOS")?, + ), + TCA_TUNNEL_KEY_ENC_TTL => Self::EncTtl( + parse_u8(payload) + .context("failed to parse TCA_TUNNEL_KEY_ENC_TTL")?, + ), + TCA_TUNNEL_KEY_NO_CSUM => Self::NoCsum( + parse_u8(payload) + .context("invalid TCA_TUNNEL_KEY_NO_CSUM value")? + != 0, + ), + _ => Self::Other(DefaultNla::parse(buf)?), + }) + } +} + +const TC_TUNNEL_KEY_BUF_LEN: usize = TcActionGeneric::BUF_LEN + 4; + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct TcTunnelKey { + pub generic: TcActionGeneric, + pub t_action: i32, +} + +// kernel struct `tc_tunnel_key` +buffer!(TcTunnelKeyBuffer(TC_TUNNEL_KEY_BUF_LEN) { + generic: (slice, 0..20), + t_action: (i32, 20..24), +}); + +impl Emitable for TcTunnelKey { + fn buffer_len(&self) -> usize { + TC_TUNNEL_KEY_BUF_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut packet = TcTunnelKeyBuffer::new(buffer); + self.generic.emit(packet.generic_mut()); + packet.set_t_action(self.t_action); + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for TcTunnelKey +{ + fn parse(buf: &TcTunnelKeyBuffer<&T>) -> Result { + Ok(Self { + generic: TcActionGeneric::parse(&TcActionGenericBuffer::new( + buf.generic(), + ))?, + t_action: buf.t_action(), + }) + } +} diff --git a/src/tc/filters/flower/core.rs b/src/tc/filters/flower/core.rs new file mode 100644 index 00000000..a03bc0aa --- /dev/null +++ b/src/tc/filters/flower/core.rs @@ -0,0 +1,984 @@ +// SPDX-License-Identifier: MIT + +use super::TcFilterFlowerMplsOption; +use crate::ip::{parse_ipv4_addr, parse_ipv6_addr}; +use crate::tc::TcAction; +use anyhow::Context; +use byteorder::{BigEndian, ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, + parsers::{ + parse_mac, parse_u16, parse_u16_be, parse_u32, parse_u32_be, parse_u8, + }, + traits::Emitable, + DecodeError, Parseable, +}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +const TCA_FLOWER_CLASSID: u16 = 1; +const TCA_FLOWER_INDEV: u16 = 2; +const TCA_FLOWER_ACT: u16 = 3; +const TCA_FLOWER_KEY_ETH_DST: u16 = 4; +const TCA_FLOWER_KEY_ETH_DST_MASK: u16 = 5; +const TCA_FLOWER_KEY_ETH_SRC: u16 = 6; +const TCA_FLOWER_KEY_ETH_SRC_MASK: u16 = 7; +const TCA_FLOWER_KEY_ETH_TYPE: u16 = 8; +const TCA_FLOWER_KEY_IP_PROTO: u16 = 9; +const TCA_FLOWER_KEY_IPV4_SRC: u16 = 10; +const TCA_FLOWER_KEY_IPV4_SRC_MASK: u16 = 11; +const TCA_FLOWER_KEY_IPV4_DST: u16 = 12; +const TCA_FLOWER_KEY_IPV4_DST_MASK: u16 = 13; +const TCA_FLOWER_KEY_IPV6_SRC: u16 = 14; +const TCA_FLOWER_KEY_IPV6_SRC_MASK: u16 = 15; +const TCA_FLOWER_KEY_IPV6_DST: u16 = 16; +const TCA_FLOWER_KEY_IPV6_DST_MASK: u16 = 17; +const TCA_FLOWER_KEY_TCP_SRC: u16 = 18; +const TCA_FLOWER_KEY_TCP_DST: u16 = 19; +const TCA_FLOWER_KEY_UDP_SRC: u16 = 20; +const TCA_FLOWER_KEY_UDP_DST: u16 = 21; +const TCA_FLOWER_FLAGS: u16 = 22; +const TCA_FLOWER_KEY_VLAN_ID: u16 = 23; +const TCA_FLOWER_KEY_VLAN_PRIO: u16 = 24; +const TCA_FLOWER_KEY_VLAN_ETH_TYPE: u16 = 25; +const TCA_FLOWER_KEY_ENC_KEY_ID: u16 = 26; +const TCA_FLOWER_KEY_ENC_IPV4_SRC: u16 = 27; +const TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK: u16 = 28; +const TCA_FLOWER_KEY_ENC_IPV4_DST: u16 = 29; +const TCA_FLOWER_KEY_ENC_IPV4_DST_MASK: u16 = 30; +const TCA_FLOWER_KEY_ENC_IPV6_SRC: u16 = 31; +const TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK: u16 = 32; +const TCA_FLOWER_KEY_ENC_IPV6_DST: u16 = 33; +const TCA_FLOWER_KEY_ENC_IPV6_DST_MASK: u16 = 34; +const TCA_FLOWER_KEY_TCP_SRC_MASK: u16 = 35; +const TCA_FLOWER_KEY_TCP_DST_MASK: u16 = 36; +const TCA_FLOWER_KEY_UDP_SRC_MASK: u16 = 37; +const TCA_FLOWER_KEY_UDP_DST_MASK: u16 = 38; +const TCA_FLOWER_KEY_SCTP_SRC_MASK: u16 = 39; +const TCA_FLOWER_KEY_SCTP_DST_MASK: u16 = 40; +const TCA_FLOWER_KEY_SCTP_SRC: u16 = 41; +const TCA_FLOWER_KEY_SCTP_DST: u16 = 42; +const TCA_FLOWER_KEY_ENC_UDP_SRC_PORT: u16 = 43; +const TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK: u16 = 44; +const TCA_FLOWER_KEY_ENC_UDP_DST_PORT: u16 = 45; +const TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK: u16 = 46; +const TCA_FLOWER_KEY_FLAGS: u16 = 47; +const TCA_FLOWER_KEY_FLAGS_MASK: u16 = 48; +const TCA_FLOWER_KEY_ICMPV4_CODE: u16 = 49; +const TCA_FLOWER_KEY_ICMPV4_CODE_MASK: u16 = 50; +const TCA_FLOWER_KEY_ICMPV4_TYPE: u16 = 51; +const TCA_FLOWER_KEY_ICMPV4_TYPE_MASK: u16 = 52; +const TCA_FLOWER_KEY_ICMPV6_CODE: u16 = 53; +const TCA_FLOWER_KEY_ICMPV6_CODE_MASK: u16 = 54; +const TCA_FLOWER_KEY_ICMPV6_TYPE: u16 = 55; +const TCA_FLOWER_KEY_ICMPV6_TYPE_MASK: u16 = 56; +const TCA_FLOWER_KEY_ARP_SIP: u16 = 57; +const TCA_FLOWER_KEY_ARP_SIP_MASK: u16 = 58; +const TCA_FLOWER_KEY_ARP_TIP: u16 = 59; +const TCA_FLOWER_KEY_ARP_TIP_MASK: u16 = 60; +const TCA_FLOWER_KEY_ARP_OP: u16 = 61; +const TCA_FLOWER_KEY_ARP_OP_MASK: u16 = 62; +const TCA_FLOWER_KEY_ARP_SHA: u16 = 63; +const TCA_FLOWER_KEY_ARP_SHA_MASK: u16 = 64; +const TCA_FLOWER_KEY_ARP_THA: u16 = 65; +const TCA_FLOWER_KEY_ARP_THA_MASK: u16 = 66; +const TCA_FLOWER_KEY_MPLS_TTL: u16 = 67; +const TCA_FLOWER_KEY_MPLS_BOS: u16 = 68; +const TCA_FLOWER_KEY_MPLS_TC: u16 = 69; +const TCA_FLOWER_KEY_MPLS_LABEL: u16 = 70; +const TCA_FLOWER_KEY_TCP_FLAGS: u16 = 71; +const TCA_FLOWER_KEY_TCP_FLAGS_MASK: u16 = 72; +const TCA_FLOWER_KEY_IP_TOS: u16 = 73; +const TCA_FLOWER_KEY_IP_TOS_MASK: u16 = 74; +const TCA_FLOWER_KEY_IP_TTL: u16 = 75; +const TCA_FLOWER_KEY_IP_TTL_MASK: u16 = 76; +const TCA_FLOWER_KEY_CVLAN_ID: u16 = 77; +const TCA_FLOWER_KEY_CVLAN_PRIO: u16 = 78; +const TCA_FLOWER_KEY_CVLAN_ETH_TYPE: u16 = 79; +const TCA_FLOWER_KEY_ENC_IP_TOS: u16 = 80; +const TCA_FLOWER_KEY_ENC_IP_TOS_MASK: u16 = 81; +const TCA_FLOWER_KEY_ENC_IP_TTL: u16 = 82; +const TCA_FLOWER_KEY_ENC_IP_TTL_MASK: u16 = 83; +// const TCA_FLOWER_KEY_ENC_OPTS: u16 = 84; +// const TCA_FLOWER_KEY_ENC_OPTS_MASK: u16 = 85; +const TCA_FLOWER_IN_HW_COUNT: u16 = 86; +const TCA_FLOWER_KEY_PORT_SRC_MIN: u16 = 87; +const TCA_FLOWER_KEY_PORT_SRC_MAX: u16 = 88; +const TCA_FLOWER_KEY_PORT_DST_MIN: u16 = 89; +const TCA_FLOWER_KEY_PORT_DST_MAX: u16 = 90; +const TCA_FLOWER_KEY_CT_STATE: u16 = 91; +const TCA_FLOWER_KEY_CT_STATE_MASK: u16 = 92; +const TCA_FLOWER_KEY_CT_ZONE: u16 = 93; +const TCA_FLOWER_KEY_CT_ZONE_MASK: u16 = 94; +const TCA_FLOWER_KEY_CT_MARK: u16 = 95; +const TCA_FLOWER_KEY_CT_MARK_MASK: u16 = 96; +const TCA_FLOWER_KEY_CT_LABELS: u16 = 97; +const TCA_FLOWER_KEY_CT_LABELS_MASK: u16 = 98; +const TCA_FLOWER_KEY_MPLS_OPTS: u16 = 99; +const TCA_FLOWER_KEY_HASH: u16 = 100; +const TCA_FLOWER_KEY_HASH_MASK: u16 = 101; + +fn parse_bytes_16(payload: &[u8]) -> Result<[u8; 16], DecodeError> { + if payload.len() != 16 { + return Err(format!("invalid payload size: {payload:?}").into()); + } + let mut data = [0x00; 16]; + for (i, byte) in payload.iter().enumerate() { + data[i] = *byte; + } + Ok(data) +} + +macro_rules! nla_err { + // Match rule that takes an argument expression + ($message:expr) => { + format!("failed to parse {} value", stringify!($message)) + }; +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub struct TcFilterFlower {} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum TcFilterFlowerOption { + ClassId(u32), + InDev(u32), + Actions(Vec), + EthDst([u8; 6]), + EthDstMask([u8; 6]), + EthSrc([u8; 6]), + EthSrcMask([u8; 6]), + EthType(u16), + IpProto(u8), + IpTtl(u8), + IpTtlMask(u8), + IpTos(u8), + IpTosMask(u8), + Ipv4Src(Ipv4Addr), + Ipv4SrcMask(Ipv4Addr), + Ipv4Dst(Ipv4Addr), + Ipv4DstMask(Ipv4Addr), + Ipv6Src(Ipv6Addr), + Ipv6SrcMask(Ipv6Addr), + Ipv6Dst(Ipv6Addr), + Ipv6DstMask(Ipv6Addr), + TcpSrc(u16), + TcpDst(u16), + UdpSrc(u16), + UdpDst(u16), + SctpSrc(u16), + SctpDst(u16), + TcpSrcMask(u16), + TcpDstMask(u16), + UdpSrcMask(u16), + UdpDstMask(u16), + SctpSrcMask(u16), + SctpDstMask(u16), + Icmpv4Code(u8), + Icmpv4CodeMask(u8), + Icmpv4Type(u8), + Icmpv4TypeMask(u8), + Icmpv6Code(u8), + Icmpv6CodeMask(u8), + Icmpv6Type(u8), + Icmpv6TypeMask(u8), + ArpSip(Ipv4Addr), + ArpSipMask(Ipv4Addr), + ArpTip(Ipv4Addr), + ArpTipMask(Ipv4Addr), + ArpOp(u8), + ArpOpMask(u8), + ArpSha([u8; 6]), + ArpShaMask([u8; 6]), + ArpTha([u8; 6]), + ArpThaMask([u8; 6]), + MplsTtl(u8), + MplsBos(u8), + MplsTc(u8), + MplsLabel(u32), + TcpFlags(u16), + TcpFlagsMask(u16), + KeyFlags(u32), + KeyFlagsMask(u32), + + Flags(u32), + VlanId(u16), + VlanPrio(u8), + VlanEthType(u16), + + CvlanId(u16), + CvlanPrio(u8), + CvlanEthType(u16), + EncKeyId(u32), + EncKeyUdpSrcPort(u16), + EncKeyUdpSrcPortMask(u16), + EncKeyUdpDstPort(u16), + EncKeyUdpDstPortMask(u16), + EncKeyIpTtl(u8), + EncKeyIpTtlMask(u8), + EncKeyIpTos(u8), + EncKeyIpTosMask(u8), + EncKeyIpv4Src(Ipv4Addr), + EncKeyIpv4SrcMask(Ipv4Addr), + EncKeyIpv4Dst(Ipv4Addr), + EncKeyIpv4DstMask(Ipv4Addr), + EncKeyIpv6Src(Ipv6Addr), + EncKeyIpv6SrcMask(Ipv6Addr), + EncKeyIpv6Dst(Ipv6Addr), + EncKeyIpv6DstMask(Ipv6Addr), + InHwCount(u32), + PortSrcMin(u16), + PortSrcMax(u16), + PortDstMin(u16), + PortDstMax(u16), + CtState(u16), + CtStateMask(u16), + CtZone(u16), + CtZoneMask(u16), + CtMark(u32), + CtMarkMask(u32), + CtLabels([u8; 16]), + CtLabelsMask([u8; 16]), + MplsOpts(Vec), + KeyHash(u32), + KeyHashMask(u32), + + Other(DefaultNla), +} + +impl TcFilterFlower { + pub const KIND: &'static str = "flower"; +} + +impl Nla for TcFilterFlowerOption { + fn value_len(&self) -> usize { + match self { + Self::ClassId(_) => 4, + Self::InDev(_) => 4, + Self::Actions(acts) => acts.as_slice().buffer_len(), + Self::EthDst(_) + | Self::EthDstMask(_) + | Self::EthSrc(_) + | Self::EthSrcMask(_) => 6, + Self::EthType(_) => 2, + Self::IpProto(_) => 1, + Self::IpTtl(_) + | Self::IpTtlMask(_) + | Self::IpTos(_) + | Self::IpTosMask(_) => 1, + Self::Ipv4Src(_) + | Self::Ipv4SrcMask(_) + | Self::Ipv4Dst(_) + | Self::Ipv4DstMask(_) => 4, + Self::Ipv6Src(_) + | Self::Ipv6SrcMask(_) + | Self::Ipv6Dst(_) + | Self::Ipv6DstMask(_) => 16, + Self::TcpDst(_) + | Self::TcpSrc(_) + | Self::UdpDst(_) + | Self::UdpSrc(_) + | Self::SctpDst(_) + | Self::SctpSrc(_) + | Self::TcpDstMask(_) + | Self::TcpSrcMask(_) + | Self::UdpDstMask(_) + | Self::UdpSrcMask(_) + | Self::SctpDstMask(_) + | Self::SctpSrcMask(_) => 2, + Self::Icmpv4Code(_) + | Self::Icmpv4CodeMask(_) + | Self::Icmpv4Type(_) + | Self::Icmpv4TypeMask(_) + | Self::Icmpv6Code(_) + | Self::Icmpv6CodeMask(_) + | Self::Icmpv6Type(_) + | Self::Icmpv6TypeMask(_) => 1, + Self::ArpSip(_) + | Self::ArpSipMask(_) + | Self::ArpTip(_) + | Self::ArpTipMask(_) => 4, + Self::ArpOp(_) | Self::ArpOpMask(_) => 1, + Self::ArpSha(_) + | Self::ArpShaMask(_) + | Self::ArpTha(_) + | Self::ArpThaMask(_) => 6, + Self::MplsTtl(_) | Self::MplsBos(_) | Self::MplsTc(_) => 1, + Self::MplsLabel(_) => 4, + Self::TcpFlags(_) | Self::TcpFlagsMask(_) => 2, + Self::KeyFlags(_) | Self::KeyFlagsMask(_) => 4, + + Self::Flags(_) => 4, + Self::VlanId(_) => 2, + Self::VlanPrio(_) => 1, + Self::VlanEthType(_) => 2, + Self::CvlanId(_) => 2, + Self::CvlanPrio(_) => 1, + Self::CvlanEthType(_) => 2, + Self::EncKeyId(_) => 4, + Self::EncKeyUdpSrcPort(_) + | Self::EncKeyUdpSrcPortMask(_) + | Self::EncKeyUdpDstPort(_) + | Self::EncKeyUdpDstPortMask(_) => 2, + Self::EncKeyIpTtl(_) + | Self::EncKeyIpTtlMask(_) + | Self::EncKeyIpTos(_) + | Self::EncKeyIpTosMask(_) => 1, + Self::EncKeyIpv4Src(_) + | Self::EncKeyIpv4SrcMask(_) + | Self::EncKeyIpv4Dst(_) + | Self::EncKeyIpv4DstMask(_) => 4, + Self::EncKeyIpv6Src(_) + | Self::EncKeyIpv6SrcMask(_) + | Self::EncKeyIpv6Dst(_) + | Self::EncKeyIpv6DstMask(_) => 16, + Self::InHwCount(_) => 4, + Self::PortSrcMin(_) + | Self::PortSrcMax(_) + | Self::PortDstMin(_) + | Self::PortDstMax(_) => 2, + Self::CtState(_) + | Self::CtStateMask(_) + | Self::CtZone(_) + | Self::CtZoneMask(_) => 2, + Self::CtMark(_) | Self::CtMarkMask(_) => 4, + Self::CtLabels(_) | Self::CtLabelsMask(_) => 16, + Self::MplsOpts(attr) => attr.as_slice().buffer_len(), + Self::KeyHash(_) | Self::KeyHashMask(_) => 4, + + Self::Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::ClassId(i) => NativeEndian::write_u32(buffer, *i), + Self::InDev(i) => NativeEndian::write_u32(buffer, *i), + Self::Actions(acts) => acts.as_slice().emit(buffer), + Self::EthDst(b) + | Self::EthDstMask(b) + | Self::EthSrc(b) + | Self::EthSrcMask(b) => buffer.copy_from_slice(b.as_slice()), + Self::EthType(i) => BigEndian::write_u16(buffer, *i), + Self::IpProto(i) => buffer[0] = *i, + Self::IpTtl(i) + | Self::IpTtlMask(i) + | Self::IpTos(i) + | Self::IpTosMask(i) => buffer[0] = *i, + Self::Ipv4Src(ip) + | Self::Ipv4SrcMask(ip) + | Self::Ipv4Dst(ip) + | Self::Ipv4DstMask(ip) => buffer.copy_from_slice(&ip.octets()), + Self::Ipv6Src(ip) + | Self::Ipv6SrcMask(ip) + | Self::Ipv6Dst(ip) + | Self::Ipv6DstMask(ip) => buffer.copy_from_slice(&ip.octets()), + Self::TcpSrc(i) + | Self::TcpDst(i) + | Self::UdpSrc(i) + | Self::UdpDst(i) + | Self::SctpSrc(i) + | Self::SctpDst(i) + | Self::TcpSrcMask(i) + | Self::TcpDstMask(i) + | Self::UdpSrcMask(i) + | Self::UdpDstMask(i) + | Self::SctpSrcMask(i) + | Self::SctpDstMask(i) => BigEndian::write_u16(buffer, *i), + Self::Icmpv4Code(i) + | Self::Icmpv4CodeMask(i) + | Self::Icmpv4Type(i) + | Self::Icmpv4TypeMask(i) + | Self::Icmpv6Code(i) + | Self::Icmpv6CodeMask(i) + | Self::Icmpv6Type(i) + | Self::Icmpv6TypeMask(i) => buffer[0] = *i, + Self::ArpSip(ip) + | Self::ArpSipMask(ip) + | Self::ArpTip(ip) + | Self::ArpTipMask(ip) => buffer.copy_from_slice(&ip.octets()), + Self::ArpOp(i) | Self::ArpOpMask(i) => buffer[0] = *i, + Self::ArpSha(b) + | Self::ArpShaMask(b) + | Self::ArpTha(b) + | Self::ArpThaMask(b) => buffer.copy_from_slice(b.as_slice()), + Self::MplsTtl(i) => buffer[0] = *i, + Self::MplsBos(i) => buffer[0] = *i & 0x01, + Self::MplsTc(i) => buffer[0] = *i & 0x07, + Self::MplsLabel(i) => NativeEndian::write_u32(buffer, *i & 0xFFFFF), + Self::TcpFlags(i) | Self::TcpFlagsMask(i) => { + BigEndian::write_u16(buffer, *i) + } + Self::KeyFlags(i) | Self::KeyFlagsMask(i) => { + BigEndian::write_u32(buffer, *i) + } + Self::Flags(i) => NativeEndian::write_u32(buffer, *i), + Self::VlanId(i) => NativeEndian::write_u16(buffer, *i), + Self::VlanPrio(i) => buffer[0] = *i, + Self::VlanEthType(i) => BigEndian::write_u16(buffer, *i), + Self::CvlanId(i) => NativeEndian::write_u16(buffer, *i), + Self::CvlanPrio(i) => buffer[0] = *i, + Self::CvlanEthType(i) => BigEndian::write_u16(buffer, *i), + Self::EncKeyId(i) => BigEndian::write_u32(buffer, *i), + Self::EncKeyIpTtl(i) + | Self::EncKeyIpTtlMask(i) + | Self::EncKeyIpTos(i) + | Self::EncKeyIpTosMask(i) => buffer[0] = *i, + Self::EncKeyUdpSrcPort(i) + | Self::EncKeyUdpSrcPortMask(i) + | Self::EncKeyUdpDstPort(i) + | Self::EncKeyUdpDstPortMask(i) => BigEndian::write_u16(buffer, *i), + Self::EncKeyIpv4Src(ip) + | Self::EncKeyIpv4SrcMask(ip) + | Self::EncKeyIpv4Dst(ip) + | Self::EncKeyIpv4DstMask(ip) => { + buffer.copy_from_slice(&ip.octets()) + } + Self::EncKeyIpv6Src(ip) + | Self::EncKeyIpv6SrcMask(ip) + | Self::EncKeyIpv6Dst(ip) + | Self::EncKeyIpv6DstMask(ip) => { + buffer.copy_from_slice(&ip.octets()) + } + Self::InHwCount(i) => NativeEndian::write_u32(buffer, *i), + Self::PortSrcMin(i) + | Self::PortSrcMax(i) + | Self::PortDstMin(i) + | Self::PortDstMax(i) => BigEndian::write_u16(buffer, *i), + Self::CtState(i) + | Self::CtStateMask(i) + | Self::CtZone(i) + | Self::CtZoneMask(i) => NativeEndian::write_u16(buffer, *i), + Self::CtMark(i) | Self::CtMarkMask(i) => { + NativeEndian::write_u32(buffer, *i) + } + Self::CtLabels(b) | Self::CtLabelsMask(b) => { + buffer.copy_from_slice(b.as_slice()) + } + Self::MplsOpts(attr) => attr.as_slice().emit(buffer), + Self::KeyHash(i) | Self::KeyHashMask(i) => { + NativeEndian::write_u32(buffer, *i) + } + Self::Other(attr) => attr.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::ClassId(_) => TCA_FLOWER_CLASSID, + Self::InDev(_) => TCA_FLOWER_INDEV, + Self::Actions(_) => TCA_FLOWER_ACT, + Self::EthDst(_) => TCA_FLOWER_KEY_ETH_DST, + Self::EthDstMask(_) => TCA_FLOWER_KEY_ETH_DST_MASK, + Self::EthSrc(_) => TCA_FLOWER_KEY_ETH_SRC, + Self::EthSrcMask(_) => TCA_FLOWER_KEY_ETH_SRC_MASK, + Self::EthType(_) => TCA_FLOWER_KEY_ETH_TYPE, + Self::IpProto(_) => TCA_FLOWER_KEY_IP_PROTO, + Self::IpTtl(_) => TCA_FLOWER_KEY_IP_TTL, + Self::IpTtlMask(_) => TCA_FLOWER_KEY_IP_TTL_MASK, + Self::IpTos(_) => TCA_FLOWER_KEY_IP_TOS, + Self::IpTosMask(_) => TCA_FLOWER_KEY_IP_TOS_MASK, + Self::Ipv4Src(_) => TCA_FLOWER_KEY_IPV4_SRC, + Self::Ipv4SrcMask(_) => TCA_FLOWER_KEY_IPV4_SRC_MASK, + Self::Ipv4Dst(_) => TCA_FLOWER_KEY_IPV4_DST, + Self::Ipv4DstMask(_) => TCA_FLOWER_KEY_IPV4_DST_MASK, + Self::Ipv6Src(_) => TCA_FLOWER_KEY_IPV6_SRC, + Self::Ipv6SrcMask(_) => TCA_FLOWER_KEY_IPV6_SRC_MASK, + Self::Ipv6Dst(_) => TCA_FLOWER_KEY_IPV6_DST, + Self::Ipv6DstMask(_) => TCA_FLOWER_KEY_IPV6_DST_MASK, + Self::TcpSrc(_) => TCA_FLOWER_KEY_TCP_SRC, + Self::TcpDst(_) => TCA_FLOWER_KEY_TCP_DST, + Self::TcpSrcMask(_) => TCA_FLOWER_KEY_TCP_SRC_MASK, + Self::TcpDstMask(_) => TCA_FLOWER_KEY_TCP_DST_MASK, + Self::UdpSrc(_) => TCA_FLOWER_KEY_UDP_SRC, + Self::UdpDst(_) => TCA_FLOWER_KEY_UDP_DST, + Self::UdpSrcMask(_) => TCA_FLOWER_KEY_UDP_SRC_MASK, + Self::UdpDstMask(_) => TCA_FLOWER_KEY_UDP_DST_MASK, + Self::SctpSrc(_) => TCA_FLOWER_KEY_SCTP_SRC, + Self::SctpDst(_) => TCA_FLOWER_KEY_SCTP_DST, + Self::SctpSrcMask(_) => TCA_FLOWER_KEY_SCTP_SRC_MASK, + Self::SctpDstMask(_) => TCA_FLOWER_KEY_SCTP_DST_MASK, + Self::Flags(_) => TCA_FLOWER_FLAGS, + Self::VlanId(_) => TCA_FLOWER_KEY_VLAN_ID, + Self::VlanPrio(_) => TCA_FLOWER_KEY_VLAN_PRIO, + Self::VlanEthType(_) => TCA_FLOWER_KEY_VLAN_ETH_TYPE, + Self::EncKeyId(_) => TCA_FLOWER_KEY_ENC_KEY_ID, + Self::EncKeyUdpSrcPort(_) => TCA_FLOWER_KEY_ENC_UDP_SRC_PORT, + Self::EncKeyUdpSrcPortMask(_) => { + TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK + } + Self::EncKeyUdpDstPort(_) => TCA_FLOWER_KEY_ENC_UDP_DST_PORT, + Self::EncKeyUdpDstPortMask(_) => { + TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK + } + Self::Icmpv4Code(_) => TCA_FLOWER_KEY_ICMPV4_CODE, + Self::Icmpv4CodeMask(_) => TCA_FLOWER_KEY_ICMPV4_CODE_MASK, + Self::Icmpv4Type(_) => TCA_FLOWER_KEY_ICMPV4_TYPE, + Self::Icmpv4TypeMask(_) => TCA_FLOWER_KEY_ICMPV4_TYPE_MASK, + Self::Icmpv6Code(_) => TCA_FLOWER_KEY_ICMPV6_CODE, + Self::Icmpv6CodeMask(_) => TCA_FLOWER_KEY_ICMPV6_CODE_MASK, + Self::Icmpv6Type(_) => TCA_FLOWER_KEY_ICMPV6_TYPE, + Self::Icmpv6TypeMask(_) => TCA_FLOWER_KEY_ICMPV6_TYPE_MASK, + Self::ArpSip(_) => TCA_FLOWER_KEY_ARP_SIP, + Self::ArpSipMask(_) => TCA_FLOWER_KEY_ARP_SIP_MASK, + Self::ArpTip(_) => TCA_FLOWER_KEY_ARP_TIP, + Self::ArpTipMask(_) => TCA_FLOWER_KEY_ARP_TIP_MASK, + Self::ArpOp(_) => TCA_FLOWER_KEY_ARP_OP, + Self::ArpOpMask(_) => TCA_FLOWER_KEY_ARP_OP_MASK, + Self::ArpSha(_) => TCA_FLOWER_KEY_ARP_SHA, + Self::ArpShaMask(_) => TCA_FLOWER_KEY_ARP_SHA_MASK, + Self::ArpTha(_) => TCA_FLOWER_KEY_ARP_THA, + Self::ArpThaMask(_) => TCA_FLOWER_KEY_ARP_THA_MASK, + Self::MplsTtl(_) => TCA_FLOWER_KEY_MPLS_TTL, + Self::MplsBos(_) => TCA_FLOWER_KEY_MPLS_BOS, + Self::MplsTc(_) => TCA_FLOWER_KEY_MPLS_TC, + Self::MplsLabel(_) => TCA_FLOWER_KEY_MPLS_LABEL, + Self::TcpFlags(_) => TCA_FLOWER_KEY_TCP_FLAGS, + Self::TcpFlagsMask(_) => TCA_FLOWER_KEY_TCP_FLAGS_MASK, + Self::KeyFlags(_) => TCA_FLOWER_KEY_FLAGS, + Self::KeyFlagsMask(_) => TCA_FLOWER_KEY_FLAGS_MASK, + Self::CvlanId(_) => TCA_FLOWER_KEY_CVLAN_ID, + Self::CvlanPrio(_) => TCA_FLOWER_KEY_CVLAN_PRIO, + Self::CvlanEthType(_) => TCA_FLOWER_KEY_CVLAN_ETH_TYPE, + Self::EncKeyIpTtl(_) => TCA_FLOWER_KEY_ENC_IP_TTL, + Self::EncKeyIpTtlMask(_) => TCA_FLOWER_KEY_ENC_IP_TTL_MASK, + Self::EncKeyIpTos(_) => TCA_FLOWER_KEY_ENC_IP_TOS, + Self::EncKeyIpTosMask(_) => TCA_FLOWER_KEY_ENC_IP_TOS_MASK, + Self::EncKeyIpv4Src(_) => TCA_FLOWER_KEY_ENC_IPV4_SRC, + Self::EncKeyIpv4SrcMask(_) => TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK, + Self::EncKeyIpv4Dst(_) => TCA_FLOWER_KEY_ENC_IPV4_DST, + Self::EncKeyIpv4DstMask(_) => TCA_FLOWER_KEY_ENC_IPV4_DST_MASK, + Self::EncKeyIpv6Src(_) => TCA_FLOWER_KEY_ENC_IPV6_SRC, + Self::EncKeyIpv6SrcMask(_) => TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK, + Self::EncKeyIpv6Dst(_) => TCA_FLOWER_KEY_ENC_IPV6_DST, + Self::EncKeyIpv6DstMask(_) => TCA_FLOWER_KEY_ENC_IPV6_DST_MASK, + Self::InHwCount(_) => TCA_FLOWER_IN_HW_COUNT, + Self::PortSrcMin(_) => TCA_FLOWER_KEY_PORT_SRC_MIN, + Self::PortSrcMax(_) => TCA_FLOWER_KEY_PORT_SRC_MAX, + Self::PortDstMin(_) => TCA_FLOWER_KEY_PORT_DST_MIN, + Self::PortDstMax(_) => TCA_FLOWER_KEY_PORT_DST_MAX, + Self::CtState(_) => TCA_FLOWER_KEY_CT_STATE, + Self::CtStateMask(_) => TCA_FLOWER_KEY_CT_STATE_MASK, + Self::CtZone(_) => TCA_FLOWER_KEY_CT_ZONE, + Self::CtZoneMask(_) => TCA_FLOWER_KEY_CT_ZONE_MASK, + Self::CtMark(_) => TCA_FLOWER_KEY_CT_MARK, + Self::CtMarkMask(_) => TCA_FLOWER_KEY_CT_MARK_MASK, + Self::CtLabels(_) => TCA_FLOWER_KEY_CT_LABELS, + Self::CtLabelsMask(_) => TCA_FLOWER_KEY_CT_LABELS_MASK, + Self::MplsOpts(_) => TCA_FLOWER_KEY_MPLS_OPTS | NLA_F_NESTED, + Self::KeyHash(_) => TCA_FLOWER_KEY_HASH, + Self::KeyHashMask(_) => TCA_FLOWER_KEY_HASH_MASK, + Self::Other(attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for TcFilterFlowerOption +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + // TCA_FLOWER_CLASSID => Self::ClassId(TcHandle::from( + // parse_u32(payload).context("failed to parse + // TCA_FLOWER_CLASSID")?, )), + TCA_FLOWER_INDEV => Self::InDev( + parse_u32(payload).context(nla_err!(TCA_FLOWER_INDEV))?, + ), + TCA_FLOWER_ACT => { + let mut acts = vec![]; + for act in NlasIterator::new(payload) { + let act = act.context("invalid TCA_FLOWER_ACT")?; + acts.push( + TcAction::parse(&act) + .context(nla_err!(TCA_FLOWER_ACT))?, + ); + } + Self::Actions(acts) + } + TCA_FLOWER_KEY_ETH_DST => Self::EthDst( + parse_mac(payload).context(nla_err!(TCA_FLOWER_KEY_ETH_DST))?, + ), + TCA_FLOWER_KEY_ETH_DST_MASK => Self::EthDstMask( + parse_mac(payload) + .context(nla_err!(TCA_FLOWER_KEY_ETH_DST_MASK))?, + ), + TCA_FLOWER_KEY_ETH_SRC => Self::EthSrc( + parse_mac(payload).context(nla_err!(TCA_FLOWER_KEY_ETH_SRC))?, + ), + TCA_FLOWER_KEY_ETH_SRC_MASK => Self::EthSrcMask( + parse_mac(payload) + .context(nla_err!(TCA_FLOWER_KEY_ETH_SRC_MASK))?, + ), + TCA_FLOWER_KEY_ETH_TYPE => Self::EthType( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ETH_TYPE))?, + ), + TCA_FLOWER_KEY_IP_PROTO => Self::IpProto( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_IP_PROTO))?, + ), + TCA_FLOWER_KEY_IP_TTL => Self::IpTtl( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_IP_TTL))?, + ), + TCA_FLOWER_KEY_IP_TTL_MASK => Self::IpTtlMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_IP_TTL_MASK))?, + ), + TCA_FLOWER_KEY_IP_TOS => Self::IpTos( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_IP_TOS))?, + ), + TCA_FLOWER_KEY_IP_TOS_MASK => Self::IpTosMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_IP_TOS_MASK))?, + ), + TCA_FLOWER_KEY_IPV4_SRC => Self::Ipv4Src( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV4_SRC))?, + ), + TCA_FLOWER_KEY_IPV4_SRC_MASK => Self::Ipv4SrcMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV4_SRC_MASK))?, + ), + TCA_FLOWER_KEY_IPV4_DST => Self::Ipv4Dst( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV4_DST))?, + ), + TCA_FLOWER_KEY_IPV4_DST_MASK => Self::Ipv4DstMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV4_DST_MASK))?, + ), + TCA_FLOWER_KEY_IPV6_SRC => Self::Ipv6Src( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV6_SRC))?, + ), + TCA_FLOWER_KEY_IPV6_SRC_MASK => Self::Ipv6SrcMask( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV6_SRC_MASK))?, + ), + TCA_FLOWER_KEY_IPV6_DST => Self::Ipv6Dst( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV6_DST))?, + ), + TCA_FLOWER_KEY_IPV6_DST_MASK => Self::Ipv6DstMask( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_IPV6_DST_MASK))?, + ), + TCA_FLOWER_KEY_TCP_SRC => Self::TcpSrc( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_SRC))?, + ), + TCA_FLOWER_KEY_TCP_DST => Self::TcpDst( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_DST))?, + ), + TCA_FLOWER_KEY_TCP_SRC_MASK => Self::TcpSrcMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_SRC_MASK))?, + ), + TCA_FLOWER_KEY_TCP_DST_MASK => Self::TcpDstMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_DST_MASK))?, + ), + TCA_FLOWER_KEY_UDP_SRC => Self::UdpSrc( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_UDP_SRC))?, + ), + TCA_FLOWER_KEY_UDP_DST => Self::UdpDst( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_UDP_DST))?, + ), + TCA_FLOWER_KEY_UDP_SRC_MASK => Self::UdpSrcMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_UDP_SRC_MASK))?, + ), + TCA_FLOWER_KEY_UDP_DST_MASK => Self::UdpDstMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_UDP_DST_MASK))?, + ), + TCA_FLOWER_KEY_SCTP_SRC => Self::SctpSrc( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_SCTP_SRC))?, + ), + TCA_FLOWER_KEY_SCTP_DST => Self::SctpDst( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_SCTP_DST))?, + ), + TCA_FLOWER_KEY_SCTP_SRC_MASK => Self::SctpSrcMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_SCTP_SRC_MASK))?, + ), + TCA_FLOWER_KEY_SCTP_DST_MASK => Self::SctpDstMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_SCTP_DST_MASK))?, + ), + + TCA_FLOWER_FLAGS => Self::Flags( + parse_u32(payload).context(nla_err!(TCA_FLOWER_FLAGS))?, + ), + + TCA_FLOWER_KEY_VLAN_ID => Self::VlanId( + parse_u16(payload).context(nla_err!(TCA_FLOWER_KEY_VLAN_ID))?, + ), + TCA_FLOWER_KEY_VLAN_PRIO => Self::VlanPrio( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_VLAN_PRIO))?, + ), + TCA_FLOWER_KEY_VLAN_ETH_TYPE => Self::VlanEthType( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_VLAN_ETH_TYPE))?, + ), + TCA_FLOWER_KEY_CVLAN_ID => Self::CvlanId( + parse_u16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CVLAN_ID))?, + ), + TCA_FLOWER_KEY_CVLAN_PRIO => Self::CvlanPrio( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_CVLAN_PRIO))?, + ), + TCA_FLOWER_KEY_CVLAN_ETH_TYPE => Self::CvlanEthType( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_CVLAN_ETH_TYPE))?, + ), + TCA_FLOWER_KEY_ENC_KEY_ID => Self::EncKeyId( + parse_u32_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_KEY_ID))?, + ), + TCA_FLOWER_KEY_ENC_UDP_SRC_PORT => Self::EncKeyUdpSrcPort( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_UDP_SRC_PORT))?, + ), + TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK => Self::EncKeyUdpSrcPortMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK))?, + ), + TCA_FLOWER_KEY_ENC_UDP_DST_PORT => Self::EncKeyUdpDstPort( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_UDP_DST_PORT))?, + ), + TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK => Self::EncKeyUdpDstPortMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK))?, + ), + TCA_FLOWER_KEY_ICMPV4_CODE => Self::Icmpv4Code( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV4_CODE))?, + ), + TCA_FLOWER_KEY_ICMPV4_CODE_MASK => Self::Icmpv4CodeMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV4_CODE_MASK))?, + ), + TCA_FLOWER_KEY_ICMPV4_TYPE => Self::Icmpv4Type( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV4_TYPE))?, + ), + TCA_FLOWER_KEY_ICMPV4_TYPE_MASK => Self::Icmpv4TypeMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV4_TYPE_MASK))?, + ), + TCA_FLOWER_KEY_ICMPV6_CODE => Self::Icmpv6Code( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV6_CODE))?, + ), + TCA_FLOWER_KEY_ICMPV6_CODE_MASK => Self::Icmpv6CodeMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV6_CODE_MASK))?, + ), + TCA_FLOWER_KEY_ICMPV6_TYPE => Self::Icmpv6Type( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV6_TYPE))?, + ), + TCA_FLOWER_KEY_ICMPV6_TYPE_MASK => Self::Icmpv6TypeMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ICMPV6_TYPE_MASK))?, + ), + TCA_FLOWER_KEY_ARP_SIP => Self::ArpSip( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_SIP))?, + ), + TCA_FLOWER_KEY_ARP_SIP_MASK => Self::ArpSipMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_SIP_MASK))?, + ), + TCA_FLOWER_KEY_ARP_TIP => Self::ArpTip( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_TIP))?, + ), + TCA_FLOWER_KEY_ARP_TIP_MASK => Self::ArpTipMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_TIP_MASK))?, + ), + TCA_FLOWER_KEY_ARP_OP => Self::ArpOp( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_ARP_OP))?, + ), + TCA_FLOWER_KEY_ARP_OP_MASK => Self::ArpOpMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_OP_MASK))?, + ), + TCA_FLOWER_KEY_ARP_SHA => Self::ArpSha( + parse_mac(payload).context(nla_err!(TCA_FLOWER_KEY_ARP_SHA))?, + ), + TCA_FLOWER_KEY_ARP_SHA_MASK => Self::ArpShaMask( + parse_mac(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_SHA_MASK))?, + ), + TCA_FLOWER_KEY_ARP_THA => Self::ArpTha( + parse_mac(payload).context(nla_err!(TCA_FLOWER_KEY_ARP_THA))?, + ), + TCA_FLOWER_KEY_ARP_THA_MASK => Self::ArpThaMask( + parse_mac(payload) + .context(nla_err!(TCA_FLOWER_KEY_ARP_THA_MASK))?, + ), + TCA_FLOWER_KEY_MPLS_TTL => Self::MplsTtl( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_MPLS_TTL))?, + ), + TCA_FLOWER_KEY_MPLS_BOS => Self::MplsBos( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_MPLS_BOS))?, + ), + TCA_FLOWER_KEY_MPLS_TC => Self::MplsTc( + parse_u8(payload).context(nla_err!(TCA_FLOWER_KEY_MPLS_TC))?, + ), + TCA_FLOWER_KEY_MPLS_LABEL => Self::MplsLabel( + parse_u32(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_LABEL))?, + ), + TCA_FLOWER_KEY_TCP_FLAGS => Self::TcpFlags( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_FLAGS))?, + ), + TCA_FLOWER_KEY_TCP_FLAGS_MASK => Self::TcpFlagsMask( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_TCP_FLAGS_MASK))?, + ), + TCA_FLOWER_KEY_FLAGS => Self::KeyFlags( + parse_u32_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_FLAGS))?, + ), + TCA_FLOWER_KEY_FLAGS_MASK => Self::KeyFlagsMask( + parse_u32_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_FLAGS_MASK))?, + ), + + TCA_FLOWER_KEY_ENC_IP_TTL => Self::EncKeyIpTtl( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IP_TTL))?, + ), + TCA_FLOWER_KEY_ENC_IP_TTL_MASK => Self::EncKeyIpTtlMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IP_TTL_MASK))?, + ), + TCA_FLOWER_KEY_ENC_IP_TOS => Self::EncKeyIpTos( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IP_TOS))?, + ), + TCA_FLOWER_KEY_ENC_IP_TOS_MASK => Self::EncKeyIpTosMask( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IP_TOS_MASK))?, + ), + TCA_FLOWER_KEY_ENC_IPV4_SRC => Self::EncKeyIpv4Src( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV4_SRC))?, + ), + TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK => Self::EncKeyIpv4SrcMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK))?, + ), + TCA_FLOWER_KEY_ENC_IPV4_DST => Self::EncKeyIpv4Dst( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV4_DST))?, + ), + TCA_FLOWER_KEY_ENC_IPV4_DST_MASK => Self::EncKeyIpv4DstMask( + parse_ipv4_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV4_DST_MASK))?, + ), + TCA_FLOWER_KEY_ENC_IPV6_SRC => Self::EncKeyIpv6Src( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV6_SRC))?, + ), + TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK => Self::EncKeyIpv6SrcMask( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK))?, + ), + TCA_FLOWER_KEY_ENC_IPV6_DST => Self::EncKeyIpv6Dst( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV6_DST))?, + ), + TCA_FLOWER_KEY_ENC_IPV6_DST_MASK => Self::EncKeyIpv6DstMask( + parse_ipv6_addr(payload) + .context(nla_err!(TCA_FLOWER_KEY_ENC_IPV6_DST_MASK))?, + ), + TCA_FLOWER_IN_HW_COUNT => Self::InHwCount( + parse_u32(payload).context(nla_err!(TCA_FLOWER_IN_HW_COUNT))?, + ), + TCA_FLOWER_KEY_PORT_SRC_MIN => Self::PortSrcMin( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_PORT_SRC_MIN))?, + ), + TCA_FLOWER_KEY_PORT_SRC_MAX => Self::PortSrcMax( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_PORT_SRC_MAX))?, + ), + TCA_FLOWER_KEY_PORT_DST_MIN => Self::PortDstMin( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_PORT_DST_MIN))?, + ), + TCA_FLOWER_KEY_PORT_DST_MAX => Self::PortDstMax( + parse_u16_be(payload) + .context(nla_err!(TCA_FLOWER_KEY_PORT_DST_MAX))?, + ), + TCA_FLOWER_KEY_CT_STATE => Self::CtState( + parse_u16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_STATE))?, + ), + TCA_FLOWER_KEY_CT_STATE_MASK => Self::CtStateMask( + parse_u16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_STATE_MASK))?, + ), + TCA_FLOWER_KEY_CT_ZONE => Self::CtZone( + parse_u16(payload).context(nla_err!(TCA_FLOWER_KEY_CT_ZONE))?, + ), + TCA_FLOWER_KEY_CT_ZONE_MASK => Self::CtZoneMask( + parse_u16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_ZONE_MASK))?, + ), + TCA_FLOWER_KEY_CT_MARK => Self::CtMark( + parse_u32(payload).context(nla_err!(TCA_FLOWER_KEY_CT_MARK))?, + ), + TCA_FLOWER_KEY_CT_MARK_MASK => Self::CtMarkMask( + parse_u32(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_MARK_MASK))?, + ), + TCA_FLOWER_KEY_CT_LABELS => Self::CtLabels( + parse_bytes_16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_LABELS))?, + ), + TCA_FLOWER_KEY_CT_LABELS_MASK => Self::CtLabelsMask( + parse_bytes_16(payload) + .context(nla_err!(TCA_FLOWER_KEY_CT_LABELS_MASK))?, + ), + TCA_FLOWER_KEY_MPLS_OPTS => { + let mut nlas = vec![]; + for nla in NlasIterator::new(payload) { + let nla = + nla.context("invalid TCA_FLOWER_KEY_MPLS_OPTS nla")?; + nlas.push( + TcFilterFlowerMplsOption::parse(&nla) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPTS))?, + ) + } + Self::MplsOpts(nlas) + } + TCA_FLOWER_KEY_HASH => Self::KeyHash( + parse_u32(payload).context(nla_err!(TCA_FLOWER_KEY_HASH))?, + ), + TCA_FLOWER_KEY_HASH_MASK => Self::KeyHashMask( + parse_u32(payload) + .context(nla_err!(TCA_FLOWER_KEY_HASH_MASK))?, + ), + + _ => Self::Other( + DefaultNla::parse(buf).context("failed to parse flower nla")?, + ), + }) + } +} diff --git a/src/tc/filters/flower/mod.rs b/src/tc/filters/flower/mod.rs new file mode 100644 index 00000000..436d5c43 --- /dev/null +++ b/src/tc/filters/flower/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +mod core; +mod mpls; + +pub use self::core::{TcFilterFlower, TcFilterFlowerOption}; +pub use self::mpls::{TcFilterFlowerMplsLseOption, TcFilterFlowerMplsOption}; diff --git a/src/tc/filters/flower/mpls.rs b/src/tc/filters/flower/mpls.rs new file mode 100644 index 00000000..22768352 --- /dev/null +++ b/src/tc/filters/flower/mpls.rs @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use byteorder::{ByteOrder, NativeEndian}; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer, NlasIterator, NLA_F_NESTED}, + parsers::{parse_u32, parse_u8}, + traits::Emitable, + DecodeError, Parseable, +}; + +macro_rules! nla_err { + // Match rule that takes an argument expression + ($message:expr) => { + format!("failed to parse {} value", stringify!($message)) + }; +} + +/* + * NLA layout: + * TCA_FLOWER_KEY_MPLS_OPTS + * TCA_FLOWER_KEY_MPLS_OPT_LSE + * TCA_FLOWER_KEY_MPLS_OPT_LSE_* + * .. + * TCA_FLOWER_KEY_MPLS_OPT_LSE_* + * .. + * TCA_FLOWER_KEY_MPLS_OPT_LSE + * TCA_FLOWER_KEY_MPLS_OPT_LSE_* + */ + +const TCA_FLOWER_KEY_MPLS_OPT_LSE: u16 = 1; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum TcFilterFlowerMplsOption { + Lse(Vec), + Other(DefaultNla), +} + +impl Nla for TcFilterFlowerMplsOption { + fn value_len(&self) -> usize { + match self { + Self::Lse(attr) => attr.as_slice().buffer_len(), + Self::Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Lse(attr) => attr.as_slice().emit(buffer), + Self::Other(attr) => attr.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Lse(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE | NLA_F_NESTED, + Self::Other(attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for TcFilterFlowerMplsOption +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + Ok(match buf.kind() { + TCA_FLOWER_KEY_MPLS_OPT_LSE => { + let mut nlas = vec![]; + for nla in NlasIterator::new(buf.value()) { + let nla = + nla.context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE))?; + nlas.push( + TcFilterFlowerMplsLseOption::parse(&nla) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE))?, + ) + } + Self::Lse(nlas) + } + _ => Self::Other( + DefaultNla::parse(buf) + .context("failed to parse mpls option nla")?, + ), + }) + } +} + +const TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH: u16 = 1; +const TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL: u16 = 2; +const TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS: u16 = 3; +const TCA_FLOWER_KEY_MPLS_OPT_LSE_TC: u16 = 4; +const TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL: u16 = 5; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum TcFilterFlowerMplsLseOption { + LseDepth(u8), + LseTtl(u8), + LseBos(u8), + LseTc(u8), + LseLabel(u32), + + Other(DefaultNla), +} + +impl Nla for TcFilterFlowerMplsLseOption { + fn value_len(&self) -> usize { + match self { + Self::LseDepth(_) => 1, + Self::LseTtl(_) => 1, + Self::LseBos(_) => 1, + Self::LseTc(_) => 1, + Self::LseLabel(_) => 4, + + Self::Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::LseDepth(i) => buffer[0] = *i, + Self::LseTtl(i) => buffer[0] = *i, + Self::LseBos(i) => buffer[0] = *i, + Self::LseTc(i) => buffer[0] = *i, + Self::LseLabel(i) => NativeEndian::write_u32(buffer, *i & 0xFFFFF), + + Self::Other(attr) => attr.emit_value(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::LseDepth(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH, + Self::LseTtl(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL, + Self::LseBos(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS, + Self::LseTc(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE_TC, + Self::LseLabel(_) => TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL, + + Self::Other(attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for TcFilterFlowerMplsLseOption +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH => Self::LseDepth( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH))?, + ), + TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL => Self::LseTtl( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL))?, + ), + TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS => Self::LseBos( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS))?, + ), + + TCA_FLOWER_KEY_MPLS_OPT_LSE_TC => Self::LseTc( + parse_u8(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE_TC))?, + ), + + TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL => Self::LseLabel( + parse_u32(payload) + .context(nla_err!(TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL))?, + ), + + _ => Self::Other( + DefaultNla::parse(buf).context("failed to parse mpls nla")?, + ), + }) + } +} diff --git a/src/tc/filters/mod.rs b/src/tc/filters/mod.rs index 9e665a24..885ac429 100644 --- a/src/tc/filters/mod.rs +++ b/src/tc/filters/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT mod cls_u32; +mod flower; mod matchall; mod u32_flags; @@ -8,5 +9,9 @@ pub use self::cls_u32::{ TcFilterU32, TcFilterU32Option, TcU32Key, TcU32Selector, TcU32SelectorBuffer, }; +pub use self::flower::{ + TcFilterFlower, TcFilterFlowerMplsLseOption, TcFilterFlowerMplsOption, + TcFilterFlowerOption, +}; pub use self::matchall::{TcFilterMatchAll, TcFilterMatchAllOption}; pub use u32_flags::{TcU32OptionFlags, TcU32SelectorFlags}; diff --git a/src/tc/mod.rs b/src/tc/mod.rs index 9eb2cf2f..7450d791 100644 --- a/src/tc/mod.rs +++ b/src/tc/mod.rs @@ -14,14 +14,16 @@ pub use self::actions::{ TcActionMessage, TcActionMessageAttribute, TcActionMessageBuffer, TcActionMessageFlags, TcActionMessageFlagsWithSelector, TcActionMirror, TcActionMirrorOption, TcActionNat, TcActionNatOption, TcActionOption, - TcActionType, TcMirror, TcMirrorActionType, TcMirrorBuffer, TcNat, - TcNatBuffer, TcNatFlags, + TcActionTunnelKey, TcActionTunnelKeyOption, TcActionType, TcMirror, + TcMirrorActionType, TcMirrorBuffer, TcNat, TcNatBuffer, TcNatFlags, + TcTunnelKey, Tcf, }; pub use self::attribute::TcAttribute; pub use self::filters::{ - TcFilterMatchAll, TcFilterMatchAllOption, TcFilterU32, TcFilterU32Option, - TcU32Key, TcU32OptionFlags, TcU32Selector, TcU32SelectorBuffer, - TcU32SelectorFlags, + TcFilterFlower, TcFilterFlowerMplsLseOption, TcFilterFlowerMplsOption, + TcFilterFlowerOption, TcFilterMatchAll, TcFilterMatchAllOption, + TcFilterU32, TcFilterU32Option, TcU32Key, TcU32OptionFlags, TcU32Selector, + TcU32SelectorBuffer, TcU32SelectorFlags, }; pub use self::header::{TcHandle, TcHeader, TcMessageBuffer}; pub use self::message::TcMessage; diff --git a/src/tc/options.rs b/src/tc/options.rs index 1b180f4c..a13c38ce 100644 --- a/src/tc/options.rs +++ b/src/tc/options.rs @@ -8,8 +8,9 @@ use netlink_packet_utils::{ }; use super::{ - TcFilterMatchAll, TcFilterMatchAllOption, TcFilterU32, TcFilterU32Option, - TcQdiscFqCodel, TcQdiscFqCodelOption, TcQdiscIngress, TcQdiscIngressOption, + TcFilterFlower, TcFilterFlowerOption, TcFilterMatchAll, + TcFilterMatchAllOption, TcFilterU32, TcFilterU32Option, TcQdiscFqCodel, + TcQdiscFqCodelOption, TcQdiscIngress, TcQdiscIngressOption, }; #[derive(Debug, PartialEq, Eq, Clone)] @@ -19,6 +20,7 @@ pub enum TcOption { // Qdisc specific options Ingress(TcQdiscIngressOption), // Filter specific options + Flower(TcFilterFlowerOption), U32(TcFilterU32Option), // matchall options MatchAll(TcFilterMatchAllOption), @@ -32,6 +34,7 @@ impl Nla for TcOption { Self::FqCodel(u) => u.value_len(), Self::Ingress(u) => u.value_len(), Self::U32(u) => u.value_len(), + Self::Flower(u) => u.value_len(), Self::MatchAll(m) => m.value_len(), Self::Other(o) => o.value_len(), } @@ -41,6 +44,7 @@ impl Nla for TcOption { match self { Self::FqCodel(u) => u.emit_value(buffer), Self::Ingress(u) => u.emit_value(buffer), + Self::Flower(u) => u.emit_value(buffer), Self::U32(u) => u.emit_value(buffer), Self::MatchAll(m) => m.emit_value(buffer), Self::Other(o) => o.emit_value(buffer), @@ -51,6 +55,7 @@ impl Nla for TcOption { match self { Self::FqCodel(u) => u.kind(), Self::Ingress(u) => u.kind(), + Self::Flower(u) => u.kind(), Self::U32(u) => u.kind(), Self::MatchAll(m) => m.kind(), Self::Other(o) => o.kind(), @@ -72,6 +77,10 @@ where "failed to parse ingress TCA_OPTIONS attributes", )?) } + TcFilterFlower::KIND => Self::Flower( + TcFilterFlowerOption::parse(buf) + .context("failed to parse flower TCA_OPTIONS attributes")?, + ), TcQdiscFqCodel::KIND => { Self::FqCodel(TcQdiscFqCodelOption::parse(buf).context( "failed to parse fq_codel TCA_OPTIONS attributes", @@ -104,6 +113,7 @@ where Ok(match kind { TcFilterU32::KIND | TcFilterMatchAll::KIND + | TcFilterFlower::KIND | TcQdiscIngress::KIND | TcQdiscFqCodel::KIND => { let mut nlas = vec![]; diff --git a/src/tc/tests/filter_flower.rs b/src/tc/tests/filter_flower.rs new file mode 100644 index 00000000..d9785bed --- /dev/null +++ b/src/tc/tests/filter_flower.rs @@ -0,0 +1,1107 @@ +// SPDX-License-Identifier: MIT + +use std::net::{Ipv4Addr, Ipv6Addr}; + +use netlink_packet_utils::{Emitable, Parseable}; + +use crate::{ + tc::{ + TcAction, TcActionAttribute, TcActionGeneric, TcActionMirrorOption, + TcActionOption, TcActionType, TcAttribute, TcFilterFlowerMplsLseOption, + TcFilterFlowerMplsOption, TcFilterFlowerOption, TcHandle, TcHeader, + TcMessage, TcMessageBuffer, TcMirror, TcMirrorActionType, TcOption, + TcStats2, TcStatsBasic, TcStatsQueue, Tcf, + }, + AddressFamily, +}; + +/* + * struct tcmsg {} +TCA_KIND ("u32", "flower") defining type of options under TCA_OPTIONS +TCA_OPTIONS (container with ALL other options) + * TCA_FLOWER_* (matchers) + * TCA__ACT + * action#index + * TCA_ACT_KIND ("mirred") defining type of options for this action + * TCA_ACT_STATS + * TCA_ACT_OPTIONS + * TCA__PARAMS { tc_mirred / tc_gen } + * + */ + +fn get_mirred_action(gen_idx: u32) -> TcAction { + TcAction { + tab: 1, + attributes: vec![ + TcActionAttribute::Kind("mirred".to_string()), + TcActionAttribute::Stats(vec![ + TcStats2::Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + TcStats2::Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + TcActionAttribute::Options(vec![ + TcActionOption::Mirror(TcActionMirrorOption::Parms(TcMirror { + generic: TcActionGeneric { + index: gen_idx, + capab: 0, + action: TcActionType::Stolen, + refcnt: 1, + bindcnt: 1, + }, + eaction: TcMirrorActionType::EgressRedir, + ifindex: 7, + })), + TcActionOption::Mirror(TcActionMirrorOption::Tm(Tcf { + install: 4123, + lastuse: 4123, + expires: 0, + firstuse: 0, + })), + ]), + ], + } +} + +// Common test setup: +// ip link add veth1 type veth peer veth1.peer +// ip link set veth1 up +// ip link set veth1.peer up +// tc qdisc add dev veth1 ingress +// tc filter add dev veth1 ingress chain X handle 0x42 ... +// +// Capture: +// tools/nl_dump.py dump_flower -i veth1 -c X +// + +// TC rule: +// tc filter add dev veth1 ingress handle 0x42 \ +// proto ip flower src_mac 01:02:03:04:05:06 \ +// dst_mac 01:02:03:04:05:06 ip_proto tcp \ +// dst_ip 10.0.0.0/8 src_ip 11.0.0.0/8 \ +// action mirred egress redirect dev veth1.peer +#[test] +fn test_get_filter_flower_mac_ipv4() { + let raw = vec![ + 0x00, // tcm_family (AF_UNSPEC) + 0x00, // pad1 + 0x00, 0x00, // pad2 + 0x08, 0x00, 0x00, 0x00, // ifindex(LE): 8 + 0x42, 0x00, 0x00, 0x00, // tcm_handle(LE) (rule handle): 0x42 + 0xf2, 0xff, 0xff, 0xff, // tcm_parent + 0x08, 0x00, 0x00, 0xc0, // tcm_info + 0x0b, 0x00, // NLA length: 11 + 0x01, 0x00, // NLA type: TCA_KIND + 0x66, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x00, // NLA data: "flower" + 0x00, // pad + 0x08, 0x00, // NLA length: 8 + 0x0b, 0x00, // NLA type: 11 / TCA_CHAIN + 0x00, 0x00, 0x00, 0x00, // NLA data: 0 + 0x14, 0x01, // NLA length: 276 + 0x02, 0x00, // NLA type: 2 / TCA_OPTIONS + 0x0a, 0x00, // NLA length: 10 + 0x04, 0x00, // NLA type: 4 / TCA_FLOWER_KEY_ETH_DST + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // NLA data: 01:02:03:04:05:06 + 0x00, 0x00, // pad + 0x0a, 0x00, // NLA length: 10 + 0x05, 0x00, // NLA type: 5 / TCA_FLOWER_KEY_ETH_DST_MASK + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // NLA data: ff:ff:ff:ff:ff:ff + 0x00, 0x00, // pad + 0x0a, 0x00, // NLA length: 10, + 0x06, 0x00, // NLA type: 6 / TCA_FLOWER_KEY_ETH_SRC, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // NLA data: 01:02:03:04:05:06 + 0x00, 0x00, // pad + 0x0a, 0x00, // NLA length: 10, + 0x07, 0x00, // NLA type: 7 / TCA_FLOWER_KEY_ETH_SRC_MASK, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // NLA data: ff:ff:ff:ff:ff:ff + 0x00, 0x00, // pad + 0x06, 0x00, // NLA length: 6 + 0x08, 0x00, // NLA type: 8 / TCA_FLOWER_KEY_ETH_TYPE + 0x08, 0x00, // NLA data(BE16): 0x0800 / IPv4 + 0x00, 0x00, // pad + 0x05, 0x00, // NLA length: 5 + 0x09, 0x00, // NLA type: 9 / TCA_FLOWER_KEY_IP_PROTO + 0x06, // NLA data: 6 (IPPROTO_TCP) + 0x00, 0x00, 0x00, // pad + 0x08, 0x00, // NLA length: 8 + 0x0a, 0x00, // NLA type: 10 / TCA_FLOWER_KEY_IPV4_SRC + 0x0b, 0x00, 0x00, 0x00, // NLA data: 11.0.0.0 + 0x08, 0x00, // NLA length: 8 + 0x0b, 0x00, // NLA type: 11 / TCA_FLOWER_KEY_IPV4_SRC_MASK + 0xff, 0x00, 0x00, 0x00, // NLA data: 255.0.0.0 + 0x08, 0x00, // NLA length: 8 + 0x0c, 0x00, // NLA type: 12 / TCA_FLOWER_KEY_IPV4_DST + 0x0a, 0x00, 0x00, 0x00, // NLA data: 10.0.0.0 + 0x08, 0x00, // NLA length: 8 + 0x0d, 0x00, // NLA type: 13 / TCA_FLOWER_KEY_IPV4_DST_MASK + 0xff, 0x00, 0x00, 0x00, // NLA data: 255.0.0.0 + 0x08, 0x00, // NLA length: 8 + 0x16, 0x00, // NLA type: 22 / TCA_FLOWER_FLAGS + 0x08, 0x00, 0x00, 0x00, // NLA data: 8 / TCA_CLS_FLAGS_NOT_IN_HW + 0x08, 0x00, // NLA length: 8 + 0x56, 0x00, // NLA type: 86 / TCA_FLOWER_IN_HW_COUNT + 0x00, 0x00, 0x00, 0x00, // NLA data: 0 + 0xa0, 0x00, // NLA length: 160 + 0x03, 0x00, // TCA_FLOWER_ACT + 0x9c, 0x00, // NLA length: 156 + 0x01, 0x00, // NLA type: 1 / + 0x0b, 0x00, // NLA length: 11 + 0x01, 0, // NLA type: 1 / TCA_ACT_KIND + 0x6d, 0x69, 0x72, 0x72, 0x65, 0x64, 0x00, // NLA data: mirred + 0x00, // + 0x44, 0x00, // NLA length: 68 + 0x04, 0x00, // NLA type: 4 / TCA_ACT_STATS + 0x14, 0x00, // NLA length: 20 + 0x01, 0x00, // NLA type: 1 / TCA_STATS_BASIC + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes: 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // packets: 0 + 0x14, 0x00, // NLA length: 20 + 0x07, 0x00, // NLA type: 7 / TCA_STATS_BASIC_HW + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes: 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // paclets: 0 + 0x18, 0x00, // NLA length: 24 + 0x03, 0x00, // NLA type: 3 / TCA_STATS_QUEUE + 0x00, 0x00, 0x00, 0x00, // qlen: 0 + 0x00, 0x00, 0x00, 0x00, // backlog: 0 + 0x00, 0x00, 0x00, 0x00, // drops: 0 + 0x00, 0x00, 0x00, 0x00, // requests: 0 + 0x00, 0x00, 0x00, 0x00, // overlimits: 0 + 0x48, 0x00, // NLA length: 72 + 0x02, 0x80, // NLA type: 2 / TCA_ACT_OPTIONS, + 0x20, 0x00, // NLA length: 32 + 0x02, 0x00, // NLA type: 2 / TCA_MIRRED_PARMS (struct tc_mirred) + 0x02, 0x00, 0x00, 0x00, // tc_mirred::tc_gen::index // 2 + 0x00, 0x00, 0x00, 0x00, // tc_mirred::tc_gen::capab 0 + 0x04, 0x00, 0x00, + 0x00, // tc_mirred::tc_gen::action 4 / TC_ACT_STOLEN + 0x01, 0x00, 0x00, 0x00, // tc_mirred::tc_gen::refctnt 1 + 0x01, 0x00, 0x00, 0x00, // tc_mirred::tc_gen::bindctnt 1 + 0x01, 0x00, 0x00, 0x00, // tc_mirred::eaction 1 / TCA_EGRESS_REDIR + 0x07, 0x00, 0x00, 0x00, // tc_mirred:ifindex 7 + 0x24, 0x00, // NLA length: 36 + 0x01, 0x00, // NLA type: 1 / TCA_MIRRED_TM (struct tcf_t) + 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // tcf_t::install 4123 + 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // tcf_t::lastuse 4123 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tcf_t::expires + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tcf_t::firstuse + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x42, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(0), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthDst([ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + ])), + TcOption::Flower(TcFilterFlowerOption::EthDstMask([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ])), + TcOption::Flower(TcFilterFlowerOption::EthSrc([ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + ])), + TcOption::Flower(TcFilterFlowerOption::EthSrcMask([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ])), + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::IpProto(6)), + TcOption::Flower(TcFilterFlowerOption::Ipv4Src( + "11.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv4SrcMask( + "255.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv4Dst( + "10.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv4DstMask( + "255.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Flags(1 << 3)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + TcOption::Flower(TcFilterFlowerOption::Actions(vec![ + get_mirred_action(2), + ])), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x43 \ +// proto ipv6 flower src_ip 2a00:1::/64 \ +// dst_ip 2a01:2::/32 ip_ttl 63 ip_proto udp \ +// dst_port 137 src_port 121 \ +// action mirred egress redirect dev veth1.peer +#[test] +fn test_get_filter_flower_ipv6_ports() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0xf2, 0xff, 0xff, 0xff, 0x86, 0xdd, 0xff, 0xbf, 0x0b, 0x00, 0x01, 0x00, + 0x66, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x01, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x86, 0xdd, 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x4b, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x4c, 0x00, + 0xff, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0e, 0x00, 0x2a, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00, + 0x2a, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x11, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x14, 0x00, 0x00, 0x79, 0x00, 0x00, 0x06, 0x00, 0x25, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x06, 0x00, 0x15, 0x00, 0x00, 0x89, 0x00, 0x00, + 0x06, 0x00, 0x26, 0x00, 0xff, 0xff, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xa0, 0x00, 0x03, 0x00, 0x9c, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x01, 0x00, + 0x6d, 0x69, 0x72, 0x72, 0x65, 0x64, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, 0x1b, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x43, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221216646, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(0), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x86DD)), + TcOption::Flower(TcFilterFlowerOption::IpProto(17)), + TcOption::Flower(TcFilterFlowerOption::IpTtl(63)), + TcOption::Flower(TcFilterFlowerOption::IpTtlMask(0xFF)), + TcOption::Flower(TcFilterFlowerOption::Ipv6Src( + "2a00:1::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv6SrcMask( + "ffff:ffff:ffff:ffff::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv6Dst( + "2a01:2::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::Ipv6DstMask( + "ffff:ffff::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::UdpSrc(121)), + TcOption::Flower(TcFilterFlowerOption::UdpSrcMask(65535)), + TcOption::Flower(TcFilterFlowerOption::UdpDst(137)), + TcOption::Flower(TcFilterFlowerOption::UdpDstMask(65535)), + TcOption::Flower(TcFilterFlowerOption::Flags(1 << 3)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + TcOption::Flower(TcFilterFlowerOption::Actions(vec![ + get_mirred_action(3), + ])), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto ip flower ip_proto tcp src_port 11-2222 \ +// dst_port 22-3333 tcp_flags 0x01/0xF +#[test] +fn test_get_filter_flower_ipv4_tcp_portrange() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x54, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x47, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x00, 0x48, 0x00, + 0x00, 0x0F, 0x00, 0x00, 0x06, 0x00, 0x59, 0x00, 0x00, 0x16, 0x00, 0x00, + 0x06, 0x00, 0x5A, 0x00, 0x0D, 0x05, 0x00, 0x00, 0x06, 0x00, 0x57, 0x00, + 0x00, 0x0B, 0x00, 0x00, 0x06, 0x00, 0x58, 0x00, 0x08, 0xAE, 0x00, 0x00, + 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::IpProto(6)), + TcOption::Flower(TcFilterFlowerOption::TcpFlags(0x01)), + TcOption::Flower(TcFilterFlowerOption::TcpFlagsMask(0x0F)), + TcOption::Flower(TcFilterFlowerOption::PortDstMin(22)), + TcOption::Flower(TcFilterFlowerOption::PortDstMax(3333)), + TcOption::Flower(TcFilterFlowerOption::PortSrcMin(11)), + TcOption::Flower(TcFilterFlowerOption::PortSrcMax(2222)), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto ip flower ip_proto icmp \ +// ip_proto icmp code 8/254 type 6/252 +#[test] +fn test_get_filter_flower_ipv4_icmp() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x44, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x33, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x34, 0x00, + 0xFC, 0x00, 0x00, 0x00, 0x05, 0x00, 0x31, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x32, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::IpProto(1)), + TcOption::Flower(TcFilterFlowerOption::Icmpv4Type(6)), + TcOption::Flower(TcFilterFlowerOption::Icmpv4TypeMask(252)), + TcOption::Flower(TcFilterFlowerOption::Icmpv4Code(8)), + TcOption::Flower(TcFilterFlowerOption::Icmpv4CodeMask(254)), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto ipv6 flower ip_proto icmpv6 \ +// ip_proto icmp code 8/254 type 6/252 +#[test] +fn test_get_filter_flower_ipv6_icmp() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x86, 0xDD, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x44, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x86, 0xDD, 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x37, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x38, 0x00, + 0xFC, 0x00, 0x00, 0x00, 0x05, 0x00, 0x35, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x36, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221282182, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x86DD)), + TcOption::Flower(TcFilterFlowerOption::IpProto(58)), + TcOption::Flower(TcFilterFlowerOption::Icmpv6Type(6)), + TcOption::Flower(TcFilterFlowerOption::Icmpv6TypeMask(252)), + TcOption::Flower(TcFilterFlowerOption::Icmpv6Code(8)), + TcOption::Flower(TcFilterFlowerOption::Icmpv6CodeMask(254)), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto ip flower ip_proto tcp ct_state +trk \ +// ct_zone 11 ct_mark 3456/65535 ct_label 12345678 +#[test] +fn test_get_filter_flower_ct() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x09, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x5B, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5C, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x5D, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x5E, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0x5F, 0x00, + 0x80, 0x0D, 0x00, 0x00, 0x08, 0x00, 0x60, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x14, 0x00, 0x61, 0x00, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x62, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::IpProto(6)), + TcOption::Flower(TcFilterFlowerOption::CtState(8)), + TcOption::Flower(TcFilterFlowerOption::CtStateMask(8)), + TcOption::Flower(TcFilterFlowerOption::CtZone(11)), + TcOption::Flower(TcFilterFlowerOption::CtZoneMask(0xFFFF)), + TcOption::Flower(TcFilterFlowerOption::CtMark(3456)), + TcOption::Flower(TcFilterFlowerOption::CtMarkMask(0xFFFF)), + TcOption::Flower(TcFilterFlowerOption::CtLabels([ + 0x12, 0x34, 0x56, 0x78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ])), + TcOption::Flower(TcFilterFlowerOption::CtLabelsMask([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, + ])), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto arp flower arp_sip 10.0.0.0/23 arp_tip 11.0.0.0/24 \ +// arp_op request arp_tha 01:02:03:04:05:06/FF:FF:FF:FF:FF:0F \ +// arp_sha 02:03:04:05:06:07/FF:FF:FF:FF:0F:0F +#[test] +fn test_get_filter_flower_arp() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x06, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x06, 0x00, 0x00, 0x08, 0x00, 0x39, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x3A, 0x00, 0xFF, 0xFF, 0xFE, 0x00, 0x08, 0x00, 0x3B, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x3C, 0x00, 0xFF, 0xFF, 0xFF, 0x00, + 0x05, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x3E, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x3F, 0x00, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x00, 0x00, 0x0A, 0x00, 0x40, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0F, 0x0F, 0x00, 0x00, 0x0A, 0x00, 0x41, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x00, 0x00, 0x0A, 0x00, 0x42, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x0F, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221227016, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0806)), + TcOption::Flower(TcFilterFlowerOption::ArpSip( + "10.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::ArpSipMask( + "255.255.254.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::ArpTip( + "11.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::ArpTipMask( + "255.255.255.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::ArpOp(1)), + TcOption::Flower(TcFilterFlowerOption::ArpOpMask(255)), + TcOption::Flower(TcFilterFlowerOption::ArpSha([ + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + ])), + TcOption::Flower(TcFilterFlowerOption::ArpShaMask([ + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, + ])), + TcOption::Flower(TcFilterFlowerOption::ArpTha([ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + ])), + TcOption::Flower(TcFilterFlowerOption::ArpThaMask([ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + ])), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto 802.1AD flower vlan_ethtype 802.1Q \ +// vlan 11 vlan_prio 1 cvlan_ethtype 0x0800 cvlan 22 cvlan_prio 3 +#[test] +fn test_get_filter_flower_vlan() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x88, 0xA8, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x17, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x18, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x4D, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x05, 0x00, 0x4E, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x19, 0x00, 0x81, 0x00, 0x00, 0x00, 0x06, 0x00, 0x4F, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221268616, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::VlanId(11)), + TcOption::Flower(TcFilterFlowerOption::VlanPrio(1)), + TcOption::Flower(TcFilterFlowerOption::CvlanId(22)), + TcOption::Flower(TcFilterFlowerOption::CvlanPrio(3)), + TcOption::Flower(TcFilterFlowerOption::VlanEthType(0x8100)), + TcOption::Flower(TcFilterFlowerOption::CvlanEthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto mpls_uc flower mpls_label 132 mpls_bos 1 mpls_ttl 11 mpls_tc 5 +#[test] +fn test_get_filter_flower_mpls_uc_single() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x88, 0x47, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x88, 0x47, 0x00, 0x00, 0x05, 0x00, 0x43, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x45, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x46, 0x00, + 0x84, 0x00, 0x00, 0x00, 0x05, 0x00, 0x44, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221243784, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x08847)), + TcOption::Flower(TcFilterFlowerOption::MplsTtl(11)), + TcOption::Flower(TcFilterFlowerOption::MplsTc(5)), + TcOption::Flower(TcFilterFlowerOption::MplsLabel(132)), + TcOption::Flower(TcFilterFlowerOption::MplsBos(1)), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress handle 0x1 \ +// proto mpls_uc flower mpls lse depth 1 label 33 \ +// tc 3 bos 0 ttl 48 lse depth 2 label 44 tc 4 bos 1 ttl 47 +#[test] +fn test_get_filter_flower_mpls_uc_lse() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x88, 0x47, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x78, 0x00, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x88, 0x47, 0x00, 0x00, 0x5C, 0x00, 0x63, 0x80, 0x2C, 0x00, 0x01, 0x80, + 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x01, 0x80, 0x05, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x02, 0x00, 0x2F, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, 0x2C, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x16, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x1, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221243784, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x08847)), + TcOption::Flower(TcFilterFlowerOption::MplsOpts(vec![ + TcFilterFlowerMplsOption::Lse(vec![ + TcFilterFlowerMplsLseOption::LseDepth(1), + TcFilterFlowerMplsLseOption::LseTtl(48), + TcFilterFlowerMplsLseOption::LseBos(0), + TcFilterFlowerMplsLseOption::LseTc(3), + TcFilterFlowerMplsLseOption::LseLabel(33), + ]), + TcFilterFlowerMplsOption::Lse(vec![ + TcFilterFlowerMplsLseOption::LseDepth(2), + TcFilterFlowerMplsLseOption::LseTtl(47), + TcFilterFlowerMplsLseOption::LseBos(1), + TcFilterFlowerMplsLseOption::LseTc(4), + TcFilterFlowerMplsLseOption::LseLabel(44), + ]), + ])), + TcOption::Flower(TcFilterFlowerOption::Flags(8)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress chain 5 handle 0x44 \ +// proto ip flower enc_key_id 33 enc_dst_port 4789 \ +// enc_dst_ip 2a00:1::/32 enc_src_ip 2a01:2::/64 \ +// enc_ttl 62 enc_tos 1 action mirred egress redirect dev veth1.peer +#[test] +fn test_get_filter_flower_ipv4_enc() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x14, 0x01, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1B, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x1C, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0x1D, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1E, 0x00, 0xFF, 0xFF, 0xFF, 0x00, + 0x08, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x21, 0x06, 0x00, 0x2D, 0x00, + 0x12, 0xB5, 0x00, 0x00, 0x06, 0x00, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x05, 0x00, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x51, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x05, 0x00, 0x52, 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x53, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x00, 0x03, 0x00, 0x9C, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x01, 0x00, + 0x6D, 0x69, 0x72, 0x72, 0x65, 0x64, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, 0x1B, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1B, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x44, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv4Src( + "11.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv4SrcMask( + "255.255.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv4Dst( + "10.0.0.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv4DstMask( + "255.255.255.0".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyId(33)), + TcOption::Flower(TcFilterFlowerOption::EncKeyUdpDstPort(4789)), + TcOption::Flower(TcFilterFlowerOption::EncKeyUdpDstPortMask( + 65535, + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTos(1)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTosMask(255)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTtl(62)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTtlMask(255)), + TcOption::Flower(TcFilterFlowerOption::Flags(1 << 3)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + TcOption::Flower(TcFilterFlowerOption::Actions(vec![ + get_mirred_action(3), + ])), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} + +// TC rule: +// tc filter add dev veth1 ingress chain 5 handle 0x44 \ +// proto ip flower enc_key_id 33 enc_dst_port 4789 \ +// enc_dst_ip 2a00:1::/32 enc_src_ip 2a01:2::/64 \ +// enc_ttl 62 enc_tos 1 action mirred egress redirect dev veth1.peer +#[test] +fn test_get_filter_flower_ipv6_enc() { + let raw = vec![ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0xF2, 0xFF, 0xFF, 0xFF, 0x08, 0x00, 0x00, 0xC0, 0x0B, 0x00, 0x01, 0x00, + 0x66, 0x6C, 0x6F, 0x77, 0x65, 0x72, 0x00, 0x00, 0x08, 0x00, 0x0B, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x44, 0x01, 0x02, 0x00, 0x06, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1F, 0x00, 0x2A, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x20, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x21, 0x00, + 0x2A, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x22, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x21, 0x06, 0x00, 0x2D, 0x00, + 0x12, 0xB5, 0x00, 0x00, 0x06, 0x00, 0x2E, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x05, 0x00, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x51, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x05, 0x00, 0x52, 0x00, 0x3E, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x53, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA0, 0x00, 0x03, 0x00, 0x9C, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x01, 0x00, + 0x6D, 0x69, 0x72, 0x72, 0x65, 0x64, 0x00, 0x00, 0x44, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, 0x1B, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1B, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let expected = TcMessage { + header: TcHeader { + family: AddressFamily::Unspec, + index: 8, + handle: TcHandle { + major: 0, + minor: 0x44, + }, + parent: TcHandle { + major: 65535, + minor: 65522, + }, + info: 3221225480, + }, + attributes: vec![ + TcAttribute::Kind("flower".to_string()), + TcAttribute::Chain(5), + TcAttribute::Options(vec![ + TcOption::Flower(TcFilterFlowerOption::EthType(0x0800)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv6Src( + "2a01:2::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv6SrcMask( + "ffff:ffff:ffff:ffff::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv6Dst( + "2a00:1::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpv6DstMask( + "ffff:ffff::".parse::().unwrap(), + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyId(33)), + TcOption::Flower(TcFilterFlowerOption::EncKeyUdpDstPort(4789)), + TcOption::Flower(TcFilterFlowerOption::EncKeyUdpDstPortMask( + 65535, + )), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTos(1)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTosMask(255)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTtl(62)), + TcOption::Flower(TcFilterFlowerOption::EncKeyIpTtlMask(255)), + TcOption::Flower(TcFilterFlowerOption::Flags(1 << 3)), + TcOption::Flower(TcFilterFlowerOption::InHwCount(0)), + TcOption::Flower(TcFilterFlowerOption::Actions(vec![ + get_mirred_action(3), + ])), + ]), + ], + }; + + assert_eq!( + expected, + TcMessage::parse(&TcMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} diff --git a/src/tc/tests/filter_matchall.rs b/src/tc/tests/filter_matchall.rs index 58755d04..efa4b99d 100644 --- a/src/tc/tests/filter_matchall.rs +++ b/src/tc/tests/filter_matchall.rs @@ -8,6 +8,7 @@ use crate::{ TcActionOption, TcActionType, TcAttribute, TcFilterMatchAllOption, TcHandle, TcHeader, TcMessage, TcMessageBuffer, TcMirror, TcMirrorActionType, TcOption, TcStats2, TcStatsBasic, TcStatsQueue, + Tcf, }, AddressFamily, }; @@ -155,12 +156,12 @@ fn test_get_filter_matchall() { }), ), TcActionOption::Mirror( - // TODO(Gris Ge) - TcActionMirrorOption::Tm(vec![ - 144, 3, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - 2, 0, 0, 0, 0, 0, 0, - ]), + TcActionMirrorOption::Tm(Tcf { + install: 912, + lastuse: 514, + expires: 0, + firstuse: 514, + }), ), ]), ], diff --git a/src/tc/tests/mod.rs b/src/tc/tests/mod.rs index 05058a46..295b39a5 100644 --- a/src/tc/tests/mod.rs +++ b/src/tc/tests/mod.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +#[cfg(test)] +mod filter_flower; #[cfg(test)] mod filter_matchall; #[cfg(test)] diff --git a/tools/nl_dump.py b/tools/nl_dump.py new file mode 100755 index 00000000..f3aca280 --- /dev/null +++ b/tools/nl_dump.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: MIT +# Simple tool to dump rtnetlink messages (or part of messages) + +from ctypes import c_ubyte +from ctypes import c_uint +from ctypes import c_ushort +from ctypes import Structure +from ctypes import sizeof +from enum import Enum +import socket +import struct +import argparse + + +def roundup2(val: int, num: int) -> int: + if val % num: + return (val | (num - 1)) + 1 + else: + return val + + +def align4(val: int) -> int: + return roundup2(val, 4) + + +def print_struct(struct): + kv = [] + for pair in struct._fields_: + kv.append("{}: {}".format(pair[0], getattr(struct, pair[0]))) + return "{ " + ", ".join(kv) + " }" + + +def print_bytes(descr, data): + print("===vv {} (len:{:3d}) vv===".format(descr, len(data))) + off = 0 + step = 16 + while off < len(data): + for i in range(step): + if off + i < len(data): + print("{}0x{:02X},".format(" " * int(bool(i)), data[off + i]), end="") + print("") + off += step + print("--------------------") + + +class Nlmsghdr(Structure): + _fields_ = [ + ("nlmsg_len", c_uint), + ("nlmsg_type", c_ushort), + ("nlmsg_flags", c_ushort), + ("nlmsg_seq", c_uint), + ("nlmsg_pid", c_uint), + ] + + def __str__(self): + return print_struct(self) + + +class Nlattr(Structure): + _fields_ = [ + ("nla_len", c_ushort), + ("nla_type", c_ushort), + ] + + +class NlAttr(object): + def __init__(self, nla_type, data): + if isinstance(nla_type, Enum): + self._nla_type = nla_type.value + self._enum = nla_type + else: + self._nla_type = nla_type + self._enum = None + self.nla_list = [] + self._data = data + + def parse_nla_list(self, attr_map): + ret = [] + off = 0 + data = self._data + while len(data) - off >= 4: + nla_len, raw_nla_type = struct.unpack("@HH", data[off : off + 4]) + if nla_len + off > len(data): + raise ValueError( + "attr length {} > than the remaining length {}".format( + nla_len, len(data) - off + ) + ) + nla_type = raw_nla_type & 0x3FFF + if nla_type in attr_map: + v = attr_map[nla_type] + val = v["ad"].cls.from_bytes(data[off : off + nla_len], v["ad"].val) + if "child" in v: + # nested + child_data = data[off + 4 : off + nla_len] + if v.get("is_array", False): + # Array of nested attributes + val = self.parse_child_array( + child_data, v["ad"].val, v["child"] + ) + else: + val = self.parse_child(child_data, v["ad"].val, v["child"]) + else: + # unknown attribute + val = NlAttr(raw_nla_type, data[off + 4 : off + nla_len]) + ret.append(val) + off += align4(nla_len) + self.nla_list = ret + assert off == len(self._data) + return self + + def get_attr(self, nla_type): + return [a for a in self.nla_list if a._nla_type == nla_type][0] + + def __bytes__(self): + ret = self._data + if align4(len(ret)) != len(ret): + ret = self._data + bytes(align4(len(ret)) - len(ret)) + return struct.pack("@HH", len(self._data) + 4, self._nla_type) + ret + + def __repr__(self): + return ( + "{ " + + "nla_type: {}, nla_len: {}".format(self._nla_type, len(self._data)) + + " }" + ) + + +class BaseNetlinkMessage(object): + nl_attr_map = {} + + def __init__(self, nlmsg_type): + pass + + def _parse_hdr(self, data): + return data, len(data) + + def parse(self): + data_hdr, hdr_off = self._parse_hdr(self._orig_data[16:]) + self.data_hdr = data_hdr + if len(self._orig_data) > 16 + hdr_off: + fake_nla = NlAttr(0, self._orig_data[16 + hdr_off :]) + fake_nla.parse_nla_list(self.nl_attr_map) + self.root = fake_nla + # assert 16 + hdr_off + off == len(self._orig_data) + + def parse_attrs(self, data: bytes, attr_map): + ret = [] + off = 0 + while len(data) - off >= 4: + nla_len, raw_nla_type = struct.unpack("@HH", data[off : off + 4]) + if nla_len + off > len(data): + raise ValueError( + "attr length {} > than the remaining length {}".format( + nla_len, len(data) - off + ) + ) + nla_type = raw_nla_type & 0x3FFF + if nla_type in attr_map: + v = attr_map[nla_type] + val = v["ad"].cls.from_bytes(data[off : off + nla_len], v["ad"].val) + if "child" in v: + # nested + child_data = data[off + 4 : off + nla_len] + if v.get("is_array", False): + # Array of nested attributes + val = self.parse_child_array( + child_data, v["ad"].val, v["child"] + ) + else: + val = self.parse_child(child_data, v["ad"].val, v["child"]) + else: + # unknown attribute + val = NlAttr(raw_nla_type, data[off + 4 : off + nla_len]) + ret.append(val) + off += align4(nla_len) + return ret, off + + @classmethod + def from_bytes(cls, data): + nl_hdr = Nlmsghdr.from_buffer_copy(data[:16]) + self = cls(nl_hdr.nlmsg_type) + self._orig_data = data + self.nl_hdr = nl_hdr + return self + + def print_message(self): + print("==") + print(self.nl_hdr) + print(self.data_hdr) + # print(self.attrs) + + def print_as_bytes(self, descr: str, skip_nlhdr=True): + print_bytes(descr, self._orig_data[16:]) + + +class Tcmsg(Structure): + _fields_ = [ + ("tcm_family", c_ubyte), + ("tcm__pad1", c_ubyte), + ("tcm__pad2", c_ushort), + ("tcm_ifindex", c_uint), + ("tcm_handle", c_uint), + ("tcm_parent", c_uint), + ("tcm_info", c_uint), + ] + + def __str__(self): + return print_struct(self) + + +class TcNetlinkMessage(BaseNetlinkMessage): + nl_attr_map = {} + + def _parse_hdr(self, data): + if len(data) < sizeof(Tcmsg): + raise ValueError("message too short for Tcmsg") + data_hdr = Tcmsg.from_buffer_copy(data) + return data_hdr, sizeof(Tcmsg) + + +class Actmsg(Structure): + _fields_ = [ + ("tca_family", c_ubyte), + ("tca__pad1", c_ubyte), + ("tca__pad2", c_ushort), + ] + + def __str__(self): + return print_struct(self) + + +class ActNetlinkMessage(BaseNetlinkMessage): + nl_attr_map = {} + + def _parse_hdr(self, data): + if len(data) < sizeof(Actmsg): + raise ValueError("message too short for Tcmsg") + data_hdr = Actmsg.from_buffer_copy(data) + return data_hdr, sizeof(Actmsg) + + +class Rtnlsock: + AF_NETLINK = 16 + NETLINK_ROUTE = 0 + + msg_map = { + 44: TcNetlinkMessage, # RTM_NEWTFILTER + 45: TcNetlinkMessage, # RTM_DELTFILTER + 46: TcNetlinkMessage, # RTM_GETTFILTER + 48: ActNetlinkMessage, # RTM_NEWACTION + 49: ActNetlinkMessage, # RTM_DELACTION + 50: ActNetlinkMessage, # RTM_GETACTION + } + + def __init__(self): + s = socket.socket(self.AF_NETLINK, socket.SOCK_RAW, self.NETLINK_ROUTE) + s.setsockopt(270, 10, 1) # NETLINK_CAP_ACK + s.setsockopt(270, 11, 1) # NETLINK_EXT_ACK + self.sock_fd = s + self._data = bytes() + + def write_data(self, data: bytes): + self.sock_fd.sendmsg([data]) + + def read_data(self): + while True: + self._data += self.sock_fd.recv(65535) + if len(self._data) >= sizeof(Nlmsghdr): + break + + def read_message(self): + if len(self._data) < sizeof(Nlmsghdr): + self.read_data() + hdr = Nlmsghdr.from_buffer_copy(self._data) + while hdr.nlmsg_len > len(self._data): + self.read_data() + raw_msg = self._data[: hdr.nlmsg_len] + self._data = self._data[hdr.nlmsg_len :] + cls = self.msg_map.get(hdr.nlmsg_type, BaseNetlinkMessage) + return cls.from_bytes(raw_msg) + + def read_dump(self): + ret = [] + while True: + msg = self.read_message() + if msg.nl_hdr.nlmsg_type == 3: + break + ret.append(msg) + return ret + + +# Netlink attribute definitions +TCA_ACT_TAB = 1 +TCA_ACT_KIND = 1 + + +def dump_tc_rules(ifname: str, is_flower=False, chain=None): + ifindex = socket.if_nametoindex(ifname) + nl = Rtnlsock() + + parent = int(is_flower) * 4294967282 + data = bytes(Tcmsg(tcm_ifindex=ifindex, tcm_parent=parent)) + if chain is not None: + chain_nla = Nlattr(nla_len=8, nla_type=11) + data = data + bytes(chain_nla) + struct.pack("@I", chain) + + nl_len = len(data) + sizeof(Nlmsghdr) + nl_hdr = Nlmsghdr(nlmsg_len=nl_len, nlmsg_type=46, nlmsg_flags=0x305, nlmsg_seq=1) + nl.write_data(bytes(nl_hdr) + data) + while True: + msg = nl.read_message() + if msg.nl_hdr.nlmsg_type == 3: + # end of dump + break + if len(msg._orig_data) <= 64: + # skip all header-only / chain-only TC message nonsense + continue + msg.parse() + msg.print_message() + msg.print_as_bytes("test") + + +def dump_tc_actions(act_type: str): + nl = Rtnlsock() + + data = bytes(Actmsg()) + + data += bytes( + NlAttr( + TCA_ACT_TAB, + bytes( + NlAttr( + 1, bytes(NlAttr(TCA_ACT_KIND, act_type.encode() + b"\x00")) # idx 1 + ) + ), + ) + ) + data += bytes( + NlAttr(2, b"\x01\x00\x00\x00\x01\x00\x00\x00") + ) # TCA_ROOT_FLAGS bitfield32 - TCA_ACT_FLAG_LARGE_DUMP_ON + + nl_len = len(data) + sizeof(Nlmsghdr) + nl_hdr = Nlmsghdr(nlmsg_len=nl_len, nlmsg_type=50, nlmsg_flags=0x305, nlmsg_seq=1) + nl.write_data(bytes(nl_hdr) + data) + msg = nl.read_dump()[0] + msg.parse() + msg.print_message() + for a in msg.root.get_attr(1).parse_nla_list({}).nla_list: + print(a) + action_type = a.parse_nla_list({}).get_attr(1)._data.decode("utf-8") + s = "order: {} type: {}".format(a._nla_type, action_type) + print_bytes(s, bytes(a)) + + +def main(): + p = argparse.ArgumentParser(prog="nl_dump") + subparsers = p.add_subparsers(dest="cmd", required=True, help="sub-command help") + p_actions = subparsers.add_parser( + "dump_actions", help="dump actions of a certain type" + ) + p_actions.add_argument( + "-a", "--action", type=str, required=True, help="action type" + ) + p_flower = subparsers.add_parser("dump_flower", help="dump tc-flower(8) rules") + p_flower.add_argument("-i", "--interface", type=str, required=True, help="ifname") + p_flower.add_argument("-c", "--chain", type=int, help="chain#") + p_u32 = subparsers.add_parser("dump_u32", help="dump tc-u32(8) rules") + p_u32.add_argument("-i", "--interface", type=str, required=True, help="interface") + + args = p.parse_args() + + if args.cmd == "dump_actions": + dump_tc_actions(args.action) + elif args.cmd == "dump_flower": + dump_tc_rules(args.interface, True, args.chain) + elif args.cmd == "dump_u32": + dump_tc_rules(args.interface, False, None) + + +if __name__ == "__main__": + main()