From 186747ecd217feabac2f25cc86a5cc3436551145 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Thu, 9 May 2024 17:51:16 -0600 Subject: [PATCH 1/3] Docs for existing tc-actions --- src/tc/actions/mirror.rs | 16 ++++++++++++++++ src/tc/actions/nat.rs | 11 +++++++++++ src/tc/actions/nat_flag.rs | 1 + 3 files changed, 28 insertions(+) diff --git a/src/tc/actions/mirror.rs b/src/tc/actions/mirror.rs index c818da94..19307a29 100644 --- a/src/tc/actions/mirror.rs +++ b/src/tc/actions/mirror.rs @@ -14,21 +14,27 @@ use netlink_packet_utils::{ use super::{TcActionGeneric, TcActionGenericBuffer}; +/// Traffic control action used to mirror or redirect packets. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub struct TcActionMirror {} impl TcActionMirror { + /// The `TcActionAttribute::Kind` of this action. pub const KIND: &'static str = "mirred"; } const TCA_MIRRED_TM: u16 = 1; const TCA_MIRRED_PARMS: u16 = 2; +/// Options for the `TcActionMirror` action. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionMirrorOption { + /// TODO: document this after we make it something better than `Vec` Tm(Vec), + /// Parameters for the mirred action. Parms(TcMirror), + /// Other attributes unknown at the time of writing. Other(DefaultNla), } @@ -74,11 +80,15 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> const TC_MIRRED_BUF_LEN: usize = TcActionGeneric::BUF_LEN + 8; +/// Parameters for the mirred action. #[derive(Debug, PartialEq, Eq, Clone, Default)] #[non_exhaustive] pub struct TcMirror { + /// Generic action parameters. pub generic: TcActionGeneric, + /// Describes how the packet be mirrored or redirected. pub eaction: TcMirrorActionType, + /// Interface index to mirror or redirect to. pub ifindex: u32, } @@ -121,14 +131,20 @@ const TCA_EGRESS_MIRROR: i32 = 2; const TCA_INGRESS_REDIR: i32 = 3; const TCA_INGRESS_MIRROR: i32 = 4; +/// Type of mirroring or redirecting action. #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] #[non_exhaustive] pub enum TcMirrorActionType { #[default] + /// Redirect to the egress pipeline. EgressRedir, + /// Mirror to the egress pipeline. EgressMirror, + /// Redirect to the ingress pipeline. IngressRedir, + /// Mirror to the ingress pipeline. IngressMirror, + /// Other action type unknown at the time of writing. Other(i32), } diff --git a/src/tc/actions/nat.rs b/src/tc/actions/nat.rs index a1d6a350..0c44ef4c 100644 --- a/src/tc/actions/nat.rs +++ b/src/tc/actions/nat.rs @@ -16,6 +16,7 @@ use super::{nat_flag::TcNatFlags, TcActionGeneric, TcActionGenericBuffer}; const TCA_NAT_PARMS: u16 = 1; const TCA_NAT_TM: u16 = 2; +/// Network address translation action. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub struct TcActionNat {} @@ -24,11 +25,15 @@ impl TcActionNat { pub(crate) const KIND: &'static str = "nat"; } +/// Options for the [`TcActionNat`] action. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionNatOption { + /// > TODO: document this after we make it something better than `Vec` Tm(Vec), + /// Parameters for the nat action. Parms(TcNat), + /// Other attributes unknown at the time of writing. Other(DefaultNla), } @@ -74,13 +79,19 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> const TC_NAT_BUF_LEN: usize = TcActionGeneric::BUF_LEN + 16; +/// Network address translation action. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub struct TcNat { + /// Common attributes for all actions. pub generic: TcActionGeneric, + /// Original address. pub old_addr: Ipv4Addr, + /// New address. pub new_addr: Ipv4Addr, + /// Mask of the old address pub mask: Ipv4Addr, + /// Flags for the NAT action. pub flags: TcNatFlags, } diff --git a/src/tc/actions/nat_flag.rs b/src/tc/actions/nat_flag.rs index 045dfca5..85a896cd 100644 --- a/src/tc/actions/nat_flag.rs +++ b/src/tc/actions/nat_flag.rs @@ -9,6 +9,7 @@ const TCA_ACT_FLAGS_NO_RTNL: u32 = 1u32 << (TCA_ACT_FLAGS_USER_BITS + 3); const TCA_ACT_FLAGS_AT_INGRESS: u32 = 1u32 << (TCA_ACT_FLAGS_USER_BITS + 4); bitflags! { + /// Network Address Translation flags. #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] #[non_exhaustive] pub struct TcNatFlags: u32 { From 8f2d6ec1f736a13aecf235029ece524ce017db56 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Thu, 9 May 2024 17:59:16 -0600 Subject: [PATCH 2/3] Support for tc-actions # Summary of feature This is easiest to explain by way of iproute2. `tc` allows actions to be created independently of filters. Once created, these actions may then 1. be associated with _zero or more_ filters, 2. live updated (and updates will be seen by all filters using that action), 3. be deleted (only after all filters using that action have been deleted). For example, consider the following : ```bash for i in x y z; do ip link add dev "$i" type dummy tc qdisc add dev "$i" clsact done tc actions add action mirred egress redirect dev y tc actions add action gact drop ``` At this point, we could 1. list the `mirred` actions ```bash $ tc actions list action mirred total acts 1 action order 0: mirred (Egress Redirect to device y) stolen index 1 ref 1 bind 0 not_in_hw used_hw_stats disabled ``` 2. list the `gact` actions ```bash $ tc actions list action gact total acts 1 action order 0: gact action drop random type none pass val 0 index 1 ref 1 bind 0 not_in_hw used_hw_stats disabled ``` 3. create any number of filters using either or both of these actions by index ```bash tc filter add dev x ingress pref 1000 proto ip flower dst_ip 8.8.8.8 action mirred index 1 tc filter add dev z ingress pref 1000 proto ip flower dst_ip 8.8.8.8 action mirred index 1 action gact index 1 ``` 4. display those filters as normal (with per-action statistics) ```bash $ tc -s filter show dev z ingress filter protocol ip pref 1000 flower chain 0 filter protocol ip pref 1000 flower chain 0 handle 0x1 eth_type ipv4 dst_ip 8.8.8.8 not_in_hw action order 1: mirred (Egress Redirect to device y) stolen index 1 ref 3 bind 2 installed 599 sec used 599 sec Action statistics: Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 action order 2: gact action drop random type none pass val 0 index 1 ref 2 bind 1 installed 599 sec used 599 sec Action statistics: Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 ``` 5. centrally update those actions (e.g., change `drop` to `pass`) ```bash $ tc actions change action gact pass index 1 $ tc -s filter show dev z ingress filter protocol ip pref 1000 flower chain 0 filter protocol ip pref 1000 flower chain 0 handle 0x1 eth_type ipv4 dst_ip 8.8.8.8 not_in_hw action order 1: mirred (Egress Redirect to device y) stolen index 1 ref 3 bind 2 installed 838 sec used 838 sec Action statistics: Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 action order 2: gact action pass random type none pass val 0 index 1 ref 2 bind 1 installed 838 sec used 838 sec Action statistics: Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) backlog 0b 0p requeues 0 ``` 6. attempts to delete those actions while in use will be rejected (albeit with a buggy error message from `iproute2`/`tc`) ```bash $ tc actions delete action gact index 1 Error: Failed to delete TC action. We have an error talking to the kernel Command "action" is unknown, try "tc actions help" ``` 7. Removing all filters that use an action will allow the action to be deleted ```bash $ tc filter del dev z ingress pref 1000 $ tc actions delete action gact index 1 $ tc filter del dev x ingress pref 1000 $ tc actions delete action mirred index 1 ``` --- Cargo.toml | 1 + src/message.rs | 61 +++++- src/tc/actions/action.rs | 323 +++++++++++++++++++++++------ src/tc/actions/header.rs | 52 +++++ src/tc/actions/message.rs | 275 ++++++++++++++++++++++++ src/tc/actions/mod.rs | 21 +- src/tc/actions/nat.rs | 2 +- src/tc/actions/tests/action.rs | 70 +++++++ src/tc/actions/tests/header.rs | 82 ++++++++ src/tc/actions/tests/message.rs | 357 ++++++++++++++++++++++++++++++++ src/tc/actions/tests/mirror.rs | 85 ++++++++ src/tc/actions/tests/mod.rs | 7 + src/tc/actions/tests/nat.rs | 343 ++++++++++++++++++++++++++++++ src/tc/mod.rs | 8 +- src/tc/tests/action_nat.rs | 164 --------------- src/tc/tests/mod.rs | 2 - 16 files changed, 1611 insertions(+), 242 deletions(-) create mode 100644 src/tc/actions/header.rs create mode 100644 src/tc/actions/message.rs create mode 100644 src/tc/actions/tests/action.rs create mode 100644 src/tc/actions/tests/header.rs create mode 100644 src/tc/actions/tests/message.rs create mode 100644 src/tc/actions/tests/mirror.rs create mode 100644 src/tc/actions/tests/mod.rs create mode 100644 src/tc/actions/tests/nat.rs delete mode 100644 src/tc/tests/action_nat.rs diff --git a/Cargo.toml b/Cargo.toml index a25aa39e..a346ddb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,5 +27,6 @@ netlink-packet-utils = { version = "0.5.2" } name = "dump_packet_links" [dev-dependencies] +hex = "0.4.3" netlink-sys = { version = "0.8.5" } pretty_assertions = "0.7.2" diff --git a/src/message.rs b/src/message.rs index 3529f07a..d1b20809 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT use anyhow::Context; -use netlink_packet_utils::{ - DecodeError, Emitable, Parseable, ParseableParametrized, -}; - use netlink_packet_core::{ NetlinkDeserializable, NetlinkHeader, NetlinkPayload, NetlinkSerializable, }; +use netlink_packet_utils::{ + DecodeError, Emitable, Parseable, ParseableParametrized, +}; +use crate::tc::{TcActionMessage, TcActionMessageBuffer}; use crate::{ address::{AddressHeader, AddressMessage, AddressMessageBuffer}, link::{LinkMessage, LinkMessageBuffer}, @@ -46,9 +46,9 @@ const RTM_GETTCLASS: u16 = 42; const RTM_NEWTFILTER: u16 = 44; const RTM_DELTFILTER: u16 = 45; const RTM_GETTFILTER: u16 = 46; -// const RTM_NEWACTION: u16 = 48; -// const RTM_DELACTION: u16 = 49; -// const RTM_GETACTION: u16 = 50; +const RTM_NEWACTION: u16 = 48; +const RTM_DELACTION: u16 = 49; +const RTM_GETACTION: u16 = 50; const RTM_NEWPREFIX: u16 = 52; // const RTM_GETMULTICAST: u16 = 58; // const RTM_GETANYCAST: u16 = 62; @@ -291,6 +291,21 @@ impl<'a, T: AsRef<[u8]> + ?Sized> } } + RTM_NEWACTION | RTM_DELACTION | RTM_GETACTION => { + let err = "invalid tc action message"; + let msg = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf.inner()) + .context(err)?, + ) + .context(err)?; + match message_type { + RTM_NEWACTION => RouteNetlinkMessage::NewTrafficAction(msg), + RTM_DELACTION => RouteNetlinkMessage::DelTrafficAction(msg), + RTM_GETACTION => RouteNetlinkMessage::GetTrafficAction(msg), + _ => unreachable!(), + } + } + // ND ID Messages RTM_NEWNSID | RTM_GETNSID | RTM_DELNSID => { let err = "invalid nsid message"; @@ -348,6 +363,9 @@ pub enum RouteNetlinkMessage { NewTrafficFilter(TcMessage), DelTrafficFilter(TcMessage), GetTrafficFilter(TcMessage), + NewTrafficAction(TcActionMessage), + DelTrafficAction(TcActionMessage), + GetTrafficAction(TcActionMessage), NewTrafficChain(TcMessage), DelTrafficChain(TcMessage), GetTrafficChain(TcMessage), @@ -460,6 +478,18 @@ impl RouteNetlinkMessage { matches!(self, RouteNetlinkMessage::GetTrafficFilter(_)) } + pub fn is_new_action(&self) -> bool { + matches!(self, RouteNetlinkMessage::NewTrafficAction(_)) + } + + pub fn is_del_action(&self) -> bool { + matches!(self, RouteNetlinkMessage::DelTrafficAction(_)) + } + + pub fn is_get_action(&self) -> bool { + matches!(self, RouteNetlinkMessage::GetTrafficAction(_)) + } + pub fn is_new_chain(&self) -> bool { matches!(self, RouteNetlinkMessage::NewTrafficChain(_)) } @@ -528,6 +558,9 @@ impl RouteNetlinkMessage { NewTrafficFilter(_) => RTM_NEWTFILTER, DelTrafficFilter(_) => RTM_DELTFILTER, GetTrafficFilter(_) => RTM_GETTFILTER, + NewTrafficAction(_) => RTM_NEWACTION, + DelTrafficAction(_) => RTM_DELACTION, + GetTrafficAction(_) => RTM_GETACTION, NewTrafficChain(_) => RTM_NEWCHAIN, DelTrafficChain(_) => RTM_DELCHAIN, GetTrafficChain(_) => RTM_GETCHAIN, @@ -598,7 +631,12 @@ impl Emitable for RouteNetlinkMessage { | NewRule(ref msg) | DelRule(ref msg) | GetRule(ref msg) - => msg.buffer_len() + => msg.buffer_len(), + + | NewTrafficAction(ref msg) + | DelTrafficAction(ref msg) + | GetTrafficAction(ref msg) + => msg.buffer_len(), } } @@ -658,7 +696,12 @@ impl Emitable for RouteNetlinkMessage { | NewRule(ref msg) | DelRule(ref msg) | GetRule(ref msg) - => msg.emit(buffer) + => msg.emit(buffer), + + | NewTrafficAction(ref msg) + | DelTrafficAction(ref msg) + | GetTrafficAction(ref msg) + => msg.emit(buffer), } } } diff --git a/src/tc/actions/action.rs b/src/tc/actions/action.rs index 153a0e62..81203188 100644 --- a/src/tc/actions/action.rs +++ b/src/tc/actions/action.rs @@ -2,7 +2,7 @@ use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; - +use netlink_packet_utils::nla::NLA_F_NESTED; use netlink_packet_utils::{ nla::{DefaultNla, Nla, NlaBuffer, NlasIterator}, parsers::{parse_string, parse_u32}, @@ -10,17 +10,24 @@ use netlink_packet_utils::{ DecodeError, }; +use crate::tc::TcStats2; + use super::{ TcActionMirror, TcActionMirrorOption, TcActionNat, TcActionNatOption, }; -use crate::tc::TcStats2; +/// TODO: determine when and why to use this as opposed to the buffer's `kind`. const TCA_ACT_TAB: u16 = 1; +/// [`TcAction`] is a netlink message attribute that describes a [tc-action]. +/// +/// [tc-action]: https://man7.org/linux/man-pages/man8/tc-actions.8.html #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub struct TcAction { + /// Table id. Corresponds to the `Kind` of the action. pub tab: u16, + /// Attributes of the action. pub attributes: Vec, } @@ -39,7 +46,7 @@ impl Nla for TcAction { } fn emit_value(&self, buffer: &mut [u8]) { - self.attributes.as_slice().emit(buffer) + self.attributes.as_slice().emit(buffer); } fn kind(&self) -> u16 { @@ -49,62 +56,53 @@ impl Nla for TcAction { impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for TcAction { fn parse(buf: &NlaBuffer<&'a T>) -> Result { - let mut attributes = vec![]; - let mut kind = String::new(); - - for iter in NlasIterator::new(buf.value()) { - let buf = iter.context("invalid action nla")?; - let payload = buf.value(); - attributes.push(match buf.kind() { - TCA_ACT_KIND => { - kind = parse_string(payload) - .context("failed to parse TCA_ACT_KIND")?; - TcActionAttribute::Kind(kind.clone()) - } - TCA_ACT_OPTIONS => { - let mut nlas = vec![]; - for nla in NlasIterator::new(payload) { - let nla = nla.context("invalid TCA_ACT_OPTIONS")?; - nlas.push( - TcActionOption::parse_with_param(&nla, &kind) - .context(format!( - "failed to parse TCA_ACT_OPTIONS \ - for kind {kind}" - ))?, + // We need to find the `Kind` attribute before we can parse the others, + // as kind is used in calls to parse_with_param for the other + // attributes. + // Messages of this type which do not specify [`Kind`], or which specify + // `Kind` more than once are malformed and should be rejected. + // We cannot ensure that `Kind` will be the first attribute in the + // `attributes` `Vec` (although it usually is). + // As a result, we need to determine `Kind` first, then parse the rest + // of the attributes. + let kind = match NlasIterator::new(buf.value()) + .filter_map(|nla| { + let nla = match nla { + Ok(nla) => nla, + Err(e) => { + return Some( + Err(e).context("failed to parse action nla"), ) } - TcActionAttribute::Options(nlas) + }; + match nla.kind() { + TCA_ACT_KIND => Some( + parse_string(nla.value()) + .context("failed to parse TCA_ACT_KIND"), + ), + _ => None, } - TCA_ACT_INDEX => TcActionAttribute::Index( - parse_u32(payload) - .context("failed to parse TCA_ACT_INDEX")?, - ), - TCA_ACT_STATS => { - let mut nlas = vec![]; - for nla in NlasIterator::new(payload) { - let nla = nla.context("invalid TCA_ACT_STATS")?; - nlas.push( - TcStats2::parse_with_param(&nla, &kind).context( - format!( - "failed to parse TCA_ACT_STATS for \ - kind {kind}", - ), - )?, - ); - } - TcActionAttribute::Stats(nlas) + }) + .collect::, _>>() + { + Ok(kinds) => { + if kinds.is_empty() { + return Err(DecodeError::from("Missing TCA_ACT_KIND")); } - TCA_ACT_COOKIE => TcActionAttribute::Cookie(payload.to_vec()), - TCA_ACT_IN_HW_COUNT => TcActionAttribute::InHwCount( - parse_u32(payload) - .context("failed to parse TCA_ACT_IN_HW_COUNT")?, - ), - _ => TcActionAttribute::Other( - DefaultNla::parse(&buf) - .context("failed to parse action nla")?, - ), - }); - } + if kinds.len() > 1 { + return Err(DecodeError::from("Duplicate TCA_ACT_KIND")); + } + kinds[0].clone() + } + Err(e) => return Err(DecodeError::from(e.to_string())), + }; + + let attributes = NlasIterator::new(buf.value()) + .map(|nla| { + TcActionAttribute::parse_with_param(&nla?, kind.as_str()) + }) + .collect::, _>>()?; + Ok(Self { tab: buf.kind(), attributes, @@ -123,15 +121,55 @@ const TCA_ACT_COOKIE: u16 = 6; // const TCA_ACT_USED_HW_STATS: u16 = 9; const TCA_ACT_IN_HW_COUNT: u16 = 10; +/// Attributes of a traffic control action. #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionAttribute { + /// The [`Kind`] (general type or class) of the action (e.g. "mirred", + /// "nat"). + /// + /// [`Kind`]: #variant.Kind Kind(String), + /// Parameters of the action. Options(Vec), + /// Index of the action. + /// + /// This is used to identify the action in the kernel. + /// Each action [`Kind`] has a unique table of actions. + /// That is, each action [`Kind`] has its own set of [`Index`] values. + /// + /// If [`Index`] is zero on action creation, + /// the kernel will assign a unique index to the new action. + /// The combination of [`Kind`] and [`Index`] can then be used to identify + /// and interact with the action in the future. + /// + /// For example, one action can be used by multiple different filters by + /// referencing the action's [`Index`] when creating that filter. + /// Such multiply referenced actions will aggregate their statistics. + /// + /// The kernel will reject attempts to delete an action if it is in use by + /// a filter. + /// Remove all referencing filters before deleting the action. + /// + /// [`Kind`]: #variant.Kind + /// [`Index`]: #variant.Index Index(u32), + /// Statistics about the action (e.g., number of bytes and or packets + /// processed). Stats(Vec), + /// [`Cookie`] is an attribute which _is not interpreted by the kernel at + /// all_ and may be used to store up to 16 bytes of arbitrary data on + /// an action in the kernel. + /// Userspace processes may then use this data to store additional + /// information about the action or to correlate actions with other + /// data. + /// + /// [`Cookie`]: #variant.Cookie Cookie(Vec), + /// Number of times the action has been installed in hardware. InHwCount(u32), + /// Other attributes unknown at the time of writing or not yet supported by + /// this library. Other(DefaultNla), } @@ -156,7 +194,7 @@ impl Nla for TcActionAttribute { } Self::Options(opt) => opt.as_slice().emit(buffer), Self::Index(value) | Self::InHwCount(value) => { - NativeEndian::write_u32(buffer, *value) + NativeEndian::write_u32(buffer, *value); } Self::Stats(s) => s.as_slice().emit(buffer), Self::Other(attr) => attr.emit_value(buffer), @@ -165,7 +203,17 @@ impl Nla for TcActionAttribute { fn kind(&self) -> u16 { match self { Self::Kind(_) => TCA_ACT_KIND, - Self::Options(_) => TCA_ACT_OPTIONS, + Self::Options(opts) => { + // NOTE: the kernel simply doesn't consistently use the nested + // flag based on captured messages. + // This is heuristically trying to match the kernel's behavior + // but may not be correct. + if opts.len() == 1 { + TCA_ACT_OPTIONS | NLA_F_NESTED + } else { + TCA_ACT_OPTIONS + } + } Self::Index(_) => TCA_ACT_INDEX, Self::Stats(_) => TCA_ACT_STATS, Self::Cookie(_) => TCA_ACT_COOKIE, @@ -175,11 +223,78 @@ impl Nla for TcActionAttribute { } } +impl<'a, T, P> ParseableParametrized, P> for TcActionAttribute +where + T: AsRef<[u8]> + ?Sized, + P: AsRef, +{ + fn parse_with_param( + buf: &NlaBuffer<&'a T>, + kind: P, + ) -> Result { + Ok(match buf.kind() { + TCA_ACT_KIND => { + let buf_value = buf.value(); + TcActionAttribute::Kind( + parse_string(buf_value) + .context("failed to parse TCA_ACT_KIND")?, + ) + } + TCA_ACT_OPTIONS => TcActionAttribute::Options( + NlasIterator::new(buf.value()) + .map(|nla| { + let nla = nla.context("invalid TCA_ACT_OPTIONS")?; + TcActionOption::parse_with_param(&nla, kind.as_ref()) + .context("failed to parse TCA_ACT_OPTIONS") + }) + .collect::, _>>()?, + ), + TCA_ACT_INDEX => TcActionAttribute::Index( + parse_u32(buf.value()) + .context("failed to parse TCA_ACT_INDEX")?, + ), + TCA_ACT_STATS => TcActionAttribute::Stats( + NlasIterator::new(buf.value()) + .map(|nla| { + let nla = nla.context("invalid TCA_ACT_STATS")?; + TcStats2::parse_with_param(&nla, kind.as_ref()) + .context("failed to parse TCA_ACT_STATS") + }) + .collect::, _>>()?, + ), + TCA_ACT_COOKIE => TcActionAttribute::Cookie(buf.value().to_vec()), + TCA_ACT_IN_HW_COUNT => TcActionAttribute::InHwCount( + parse_u32(buf.value()) + .context("failed to parse TCA_ACT_IN_HW_COUNT")?, + ), + _ => TcActionAttribute::Other( + DefaultNla::parse(buf).context("failed to parse action nla")?, + ), + }) + } +} + +/// `TcActionOption` is a netlink message attribute that describes an option of +/// a [tc-actions] action. +/// +/// This enum is non-exhaustive as new action types may be added to the kernel +/// at any time. +/// Only a small subset of possible actions are currently supported. +/// +/// [tc-actions]: https://man7.org/linux/man-pages/man8/tc-actions.8.html #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionOption { + /// Mirror options. + /// + /// These options can be used to mirror (copy) or redirect frames / packets + /// to another network interface. Mirror(TcActionMirrorOption), + /// NAT options. + /// + /// These options type can be used to perform network address translation. Nat(TcActionNatOption), + /// Other action types not yet supported by this library. Other(DefaultNla), } @@ -235,14 +350,71 @@ where } } -// `define tc_gen` in `linux/pkt_cls.h` +/// Generic traffic control action parameters. +/// +/// This structure is used to describe attributes common to all traffic control +/// actions. +/// +/// See [`#define tc_gen` in `linux/pkt_cls.h`][`tc_gen`]. +/// +/// [`tc_gen`]: https://elixir.bootlin.com/linux/v6.8.9/source/include/uapi/linux/pkt_cls.h#L179 #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] #[non_exhaustive] pub struct TcActionGeneric { + /// The [`index`] of the action is a unique identifier used to track + /// actions installed in the kernel. + /// + /// Each action type (e.g. [`mirror`] or [`nat`]) has its own independent + /// [`index`] space. + /// If you assign the [`index`] field to `0` when creating an action, the + /// kernel will assign a unique [`index`] to the new action. + /// + /// [`mirror`]: struct.TcActionMirror.html + /// [`nat`]: struct.TcActionNat.html + /// [`index`]: #structfield.index pub index: u32, + /// NOTE: I cannot find any documentation on this field nor any place + /// where it is used in iproute2 or the Linux kernel. + /// The [`capab`] field is part of the [`#define tc_gen`] in the kernel, + /// and that `#define` is used in many places, + /// but I don't see any place using the [`capab`] field in any way. + /// I may be looking in the wrong place or missing something. + /// + /// [`#define tc_gen`]: https://elixir.bootlin.com/linux/v6.8.9/source/include/uapi/linux/pkt_cls.h#L179 + /// [`capab`]: #structfield.capab pub capab: u32, + /// Action type. pub action: TcActionType, + /// Reference count of this action. + /// + /// This refers to the number of times this action is referenced within the + /// kernel. + /// Actions are cleaned up (deleted) when [`refcnt`] reaches 0. + /// + /// If you create an action on its own (i.e., not associated with a + /// filter), the [`refcnt`] will be 1. + /// If that action is then associated with a filter, the [`refcnt`] will be + /// 2. + /// If you then delete that filter, the [`refcnt`] will be 1 and the action + /// will remain until you explicitly delete it (which is only possible + /// when the [`refcnt`] is 1 and the [`bindcnt`] is 0). + /// + /// If you were to create an action indirectly (e.g., as part of creating a + /// filter) then the [`refcnt`] will still be 1 (along with the + /// [`bindcnt`]). + /// If you then create another filter that references the same action, the + /// [`refcnt`] will be 2 (along with the [`bindcnt`]). + /// + /// If you then deleted both of those actions, + /// the [`refcnt`] would be 0 and the action would be removed from the + /// kernel. + /// + /// [`refcnt`]: #structfield.refcnt + /// [`bindcnt`]: #structfield.bindcnt pub refcnt: i32, + /// Bind count of this action. + /// + /// The number of filters that reference (bind to) this action. pub bindcnt: i32, } @@ -296,20 +468,55 @@ const TC_ACT_REPEAT: i32 = 6; const TC_ACT_REDIRECT: i32 = 7; const TC_ACT_TRAP: i32 = 8; +/// Generic traffic control action types. +/// +/// These are the possible "outcomes" for a packet after an action is applied to +/// it. +/// +/// This enum is non-exhaustive as new action types may be added to the kernel +/// at any time. #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] #[non_exhaustive] pub enum TcActionType { + /// No specific outcome specified (i.e., take the default for that action). #[default] Unspec, + /// Terminates packet processing and allows the packet to proceed. Ok, + /// Terminates packet processing and restart packet classification. Reclassify, + /// Drop the packet. Shot, + /// Pipe the packet to the next action (if any). Pipe, + /// The packet is removed from this processing pipeline and returned to + /// another. + /// This happens, for example, when using the "mirred" redirect action. Stolen, + /// Queue the packet for later processing. Queued, + /// Repeat the action. + /// + /// > TODO: confirm this. I have not used this action before and its + /// > semantics are unclear. Repeat, + /// Redirect the packet. + /// + /// > TODO: confirm semantics of this action. It is unclear how + /// > [`Redirect`] differs from [`Stolen`]. + /// + /// [`Stolen`]: #variant.Stolen + /// [`Redirect`]: #variant.Redirect Redirect, + /// Transition packet processing from the hardware to software. + /// + /// If this action is encountered by in software, it is equivalent to + /// [`Shot`]. + /// + /// [`Shot`]: #variant.Shot Trap, + /// Other action types not known at the time of writing or not yet + /// supported by this library. Other(i32), } diff --git a/src/tc/actions/header.rs b/src/tc/actions/header.rs new file mode 100644 index 00000000..2bc85938 --- /dev/null +++ b/src/tc/actions/header.rs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::nla::{NlaBuffer, NlasIterator}; +use netlink_packet_utils::{DecodeError, Emitable, Parseable}; + +use crate::AddressFamily; + +const TCA_HEADER_LEN: usize = 4; + +buffer!(TcActionMessageBuffer(TCA_HEADER_LEN) { + family: (u8, 0), + pad1: (u8, 1), + pad2: (u16, 2..TCA_HEADER_LEN), + payload: (slice, TCA_HEADER_LEN..), +}); + +impl<'a, T: AsRef<[u8]> + ?Sized> TcActionMessageBuffer<&'a T> { + /// Returns an iterator over the attributes of a `TcActionMessageBuffer`. + pub fn attributes( + &self, + ) -> impl Iterator, DecodeError>> { + NlasIterator::new(self.payload()) + } +} + +/// Header for a traffic control action message. +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct TcActionMessageHeader { + /// Address family (usually `AddressFamily::Unspec`). + pub family: AddressFamily, +} + +impl Emitable for TcActionMessageHeader { + fn buffer_len(&self) -> usize { + TCA_HEADER_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut packet = TcActionMessageBuffer::new(buffer); + packet.set_family(self.family.into()); + } +} + +impl> Parseable> + for TcActionMessageHeader +{ + fn parse(buf: &TcActionMessageBuffer) -> Result { + Ok(TcActionMessageHeader { + family: buf.family().into(), + }) + } +} diff --git a/src/tc/actions/message.rs b/src/tc/actions/message.rs new file mode 100644 index 00000000..5f3185d9 --- /dev/null +++ b/src/tc/actions/message.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use netlink_packet_utils::nla::{DefaultNla, NlaBuffer}; +use netlink_packet_utils::nla::{Nla, NlasIterator}; +use netlink_packet_utils::{DecodeError, Emitable, Parseable}; + +use crate::tc::actions::{TcActionMessageBuffer, TcActionMessageHeader}; +use crate::tc::TcAction; + +/// Message to describe [tc-actions] +/// +/// [tc-actions]: https://man7.org/linux/man-pages/man8/tc-actions.8.html +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[non_exhaustive] +pub struct TcActionMessage { + /// Header of the message. + pub header: TcActionMessageHeader, + /// Attributes of the message. + pub attributes: Vec, +} + +const TCA_ACT_FLAG_LARGE_DUMP_ON: u32 = 1 << 0; +const TCA_ACT_FLAG_TERSE_DUMP: u32 = 1 << 1; + +bitflags! { + /// Flags to configure action dumps (list operations). + #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, PartialOrd, Ord, Hash)] + #[non_exhaustive] + pub struct TcActionMessageFlags: u32 { + /// From `iproute2`'s [`rtnetlink.h`] + /// + /// If set, this flag enables more than TCA_ACT_MAX_PRIO actions in a single + /// actions listing operation. + /// + /// > TCA_ACT_FLAG_LARGE_DUMP_ON user->kernel to request for larger than + /// TCA_ACT_MAX_PRIO actions in a dump. + /// All dump responses will contain the number of actions being dumped + /// stored in for user app's consumption in TCA_ROOT_COUNT + /// + /// [`rtnetlink.h`]: https://github.com/iproute2/iproute2/blob/89210b9ec1c445ae963d181b5816d12a0cdafbb6/include/uapi/linux/rtnetlink.h#L803-L806 + const LargeDump = TCA_ACT_FLAG_LARGE_DUMP_ON; + /// If set, this flag restricts an action dump to only include essential + /// details. + /// + /// From `iproute2`'s [`rtnetlink.h`]: + /// + /// > TCA_ACT_FLAG_TERSE_DUMP user->kernel to request terse (brief) dump + /// that only includes essential action info (kind, index, etc.) + /// + /// [`rtnetlink.h`]: https://github.com/iproute2/iproute2/blob/89210b9ec1c445ae963d181b5816d12a0cdafbb6/include/uapi/linux/rtnetlink.h#L808-L809 + const TerseDump = TCA_ACT_FLAG_TERSE_DUMP; + const _ = !0; + } +} + +/// [`TcActionMessageFlagsWithSelector`] sets the [`TcActionMessageFlags`] which +/// are to be included in an operation, based on the accompanying [`flags`] and +/// [`selector`] fields. +/// +/// [`flags`]: #structfield.flags +/// [`selector`]: #structfield.selector +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, PartialOrd, Ord, Hash)] +pub struct TcActionMessageFlagsWithSelector { + /// A bitmask of [`TcActionMessageFlags`] to be associated with an + /// operation. + pub flags: TcActionMessageFlags, + /// A bitmask to determine which flags are to be included in an operation. + /// + /// Any flags which are set in the [`flags`] field but which are not set in + /// the [`selector`] field will be ignored. + /// + /// [`flags`]: #structfield.flags + /// [`selector`]: #structfield.selector + pub selector: TcActionMessageFlags, +} + +impl Nla for TcActionMessageFlagsWithSelector { + fn value_len(&self) -> usize { + 8 + } + + fn kind(&self) -> u16 { + TCA_ROOT_FLAGS + } + + fn emit_value(&self, buffer: &mut [u8]) { + buffer[..4].copy_from_slice(&self.flags.bits().to_ne_bytes()); + buffer[4..8].copy_from_slice(&self.selector.bits().to_ne_bytes()); + } +} + +impl TcActionMessageFlagsWithSelector { + /// Create a new [`TcActionMessageFlagsWithSelector`] with the given + /// [`flags`]. + /// The [`selector`] field is set to the same value as [`flags`] (i.e., none + /// of the [`flags`] will be ignored). + /// + /// [`flags`]: #structfield.flags + /// [`selector`]: #structfield.selector + #[must_use] + pub fn new(flags: TcActionMessageFlags) -> Self { + Self { + flags, + selector: flags, + } + } + + /// Create a new [`TcActionMessageFlagsWithSelector`] with the given + /// [`flags`] and [`selector`]. + /// + /// [`flags`]: #structfield.flags + /// [`selector`]: #structfield.selector + #[must_use] + pub fn new_with_selector( + flags: TcActionMessageFlags, + selector: TcActionMessageFlags, + ) -> Self { + Self { flags, selector } + } +} + +impl<'a, T: AsRef<[u8]> + 'a + ?Sized> Parseable> + for TcActionMessageFlagsWithSelector +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let value = buf.value(); + if value.len() != 8 { + return Err(DecodeError::from("invalid length")); + } + let flags = TcActionMessageFlags::from_bits(u32::from_ne_bytes( + value[0..4].try_into().context("invalid length")?, + )) + .ok_or_else(|| DecodeError::from("invalid flags"))?; + let selector = TcActionMessageFlags::from_bits(u32::from_ne_bytes( + value[4..].try_into().context("invalid length")?, + )) + .ok_or_else(|| DecodeError::from("invalid flags selector"))?; + Ok(Self::new_with_selector(flags, selector)) + } +} + +const TCA_ACT_TAB: u16 = 1; +const TCA_ROOT_FLAGS: u16 = 2; +const TCA_ROOT_COUNT: u16 = 3; +const TCA_ROOT_TIME_DELTA: u16 = 4; +const TCA_ROOT_EXT_WARN_MSG: u16 = 5; + +/// This enum is used to represent the different types of attributes that can be +/// part of a `TcActionMessage`. +/// +/// This enum is non-exhaustive, additional variants may be added in the future. +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum TcActionMessageAttribute { + /// Collection of `TcActions`. + Actions(Vec), + /// Flags to configure action dumps (list operations). + Flags(TcActionMessageFlagsWithSelector), + /// Number of actions being dumped. + RootCount(u32), + /// Time delta. + RootTimeDelta(u32), + /// Extended warning message. + RootExtWarnMsg(String), + /// Other attributes unknown at the time of writing. + Other(DefaultNla), +} + +impl<'a, T: AsRef<[u8]> + 'a + ?Sized> Parseable> + for TcActionMessageAttribute +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + Ok(match buf.kind() { + TCA_ACT_TAB => { + let actions = NlasIterator::new(buf.value()) + .map(|nla| TcAction::parse(&nla?)) + .collect::, _>>()?; + Self::Actions(actions) + } + TCA_ROOT_FLAGS => { + Self::Flags(TcActionMessageFlagsWithSelector::parse(buf)?) + } + TCA_ROOT_COUNT => { + let count = u32::from_ne_bytes( + buf.value().try_into().context("invalid length")?, + ); + Self::RootCount(count) + } + TCA_ROOT_TIME_DELTA => { + let delta = u32::from_be_bytes( + buf.value().try_into().context("invalid length")?, + ); + Self::RootTimeDelta(delta) + } + TCA_ROOT_EXT_WARN_MSG => { + let msg = String::from_utf8(buf.value().to_vec()) + .context("invalid utf8")?; + Self::RootExtWarnMsg(msg) + } + _ => Self::Other(DefaultNla::parse(buf)?), + }) + } +} + +impl Nla for TcActionMessageAttribute { + fn value_len(&self) -> usize { + match self { + Self::Actions(actions) => actions.as_slice().buffer_len(), + Self::Flags(_) => 8, + Self::RootCount(_) => 4, + Self::RootTimeDelta(_) => 4, + Self::RootExtWarnMsg(msg) => msg.len(), + Self::Other(nla) => nla.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Actions(_) => TCA_ACT_TAB, + Self::Flags(_) => TCA_ROOT_FLAGS, + Self::RootCount(_) => TCA_ROOT_COUNT, + Self::RootTimeDelta(_) => TCA_ROOT_TIME_DELTA, + Self::RootExtWarnMsg(_) => TCA_ROOT_EXT_WARN_MSG, + Self::Other(nla) => nla.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Actions(actions) => actions.as_slice().emit(buffer), + Self::Flags(flags) => { + flags.emit_value(buffer); + } + Self::RootCount(count) => { + buffer.copy_from_slice(&count.to_ne_bytes()); + } + Self::RootTimeDelta(delta) => { + buffer.copy_from_slice(&delta.to_be_bytes()); + } + Self::RootExtWarnMsg(msg) => buffer.copy_from_slice(msg.as_bytes()), + Self::Other(nla) => nla.emit_value(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + 'a + ?Sized> Parseable> + for TcActionMessage +{ + fn parse(buf: &TcActionMessageBuffer<&'a T>) -> Result { + let attrs: Result, DecodeError> = buf + .attributes() + .map(|attr| TcActionMessageAttribute::parse(&attr?)) + .collect::, _>>(); + + Ok(Self { + header: TcActionMessageHeader::parse(buf) + .context("failed to parse tc message header")?, + attributes: attrs?, + }) + } +} + +impl Emitable for TcActionMessage { + fn buffer_len(&self) -> usize { + self.header.buffer_len() + self.attributes.as_slice().buffer_len() + } + + fn emit(&self, buffer: &mut [u8]) { + self.header.emit(buffer); + self.attributes + .as_slice() + .emit(&mut buffer[self.header.buffer_len()..]); + } +} diff --git a/src/tc/actions/mod.rs b/src/tc/actions/mod.rs index 6102aaec..e426ee30 100644 --- a/src/tc/actions/mod.rs +++ b/src/tc/actions/mod.rs @@ -1,17 +1,28 @@ // SPDX-License-Identifier: MIT -mod action; -mod mirror; -mod nat; -mod nat_flag; +pub use nat_flag::TcNatFlags; pub use self::action::{ TcAction, TcActionAttribute, TcActionGeneric, TcActionGenericBuffer, TcActionOption, TcActionType, }; +pub use self::header::{TcActionMessageBuffer, TcActionMessageHeader}; +pub use self::message::{ + TcActionMessage, TcActionMessageAttribute, TcActionMessageFlags, + TcActionMessageFlagsWithSelector, +}; pub use self::mirror::{ TcActionMirror, TcActionMirrorOption, TcMirror, TcMirrorActionType, TcMirrorBuffer, }; pub use self::nat::{TcActionNat, TcActionNatOption, TcNat, TcNatBuffer}; -pub use nat_flag::TcNatFlags; + +mod action; +mod header; +mod message; +mod mirror; +mod nat; +mod nat_flag; + +#[cfg(test)] +pub mod tests; diff --git a/src/tc/actions/nat.rs b/src/tc/actions/nat.rs index 0c44ef4c..a46cc528 100644 --- a/src/tc/actions/nat.rs +++ b/src/tc/actions/nat.rs @@ -29,7 +29,7 @@ impl TcActionNat { #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum TcActionNatOption { - /// > TODO: document this after we make it something better than `Vec` + /// TODO: document this after we make it something better than `Vec` Tm(Vec), /// Parameters for the nat action. Parms(TcNat), diff --git a/src/tc/actions/tests/action.rs b/src/tc/actions/tests/action.rs new file mode 100644 index 00000000..2d75f432 --- /dev/null +++ b/src/tc/actions/tests/action.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +use crate::tc::{ + TcAction, TcActionAttribute, TcActionGeneric, TcActionGenericBuffer, + TcActionType, TcStats2, TcStatsBasic, +}; +use netlink_packet_utils::nla::NlaBuffer; +use netlink_packet_utils::{Emitable, Parseable}; + +#[test] +fn tc_action_generic_parse_back() { + let orig = TcActionGeneric { + index: 1, + capab: 2, + action: TcActionType::Reclassify, + refcnt: 3, + bindcnt: 4, + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionGeneric::parse( + &TcActionGenericBuffer::new_checked(buffer).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_parse_back_minimal() { + let orig = TcAction { + tab: 1, + attributes: vec![TcActionAttribute::Kind("example".into())], + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = + TcAction::parse(&NlaBuffer::new_checked(buffer.as_slice()).unwrap()) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_parse_back_example() { + let orig = TcAction { + tab: 1, + attributes: vec![ + TcActionAttribute::Kind("example".into()), + TcActionAttribute::Index(1), + TcActionAttribute::Cookie(vec![1, 2, 3, 4, 5, 6, 7, 8]), + TcActionAttribute::InHwCount(99), + TcActionAttribute::Stats(vec![ + TcStats2::Basic(TcStatsBasic { + bytes: 1, + packets: 2, + }), + TcStats2::BasicHw(TcStatsBasic { + bytes: 3, + packets: 4, + }), + ]), + TcActionAttribute::Options(vec![]), + ], + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = + TcAction::parse(&NlaBuffer::new_checked(buffer.as_slice()).unwrap()) + .unwrap(); + assert_eq!(orig, parsed); +} diff --git a/src/tc/actions/tests/header.rs b/src/tc/actions/tests/header.rs new file mode 100644 index 00000000..e074bfef --- /dev/null +++ b/src/tc/actions/tests/header.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT + +use crate::tc::actions::{TcActionMessageBuffer, TcActionMessageHeader}; +use crate::AddressFamily; +use netlink_packet_utils::{Emitable, Parseable}; + +#[test] +fn tc_action_message_header_parse_back_all_known_families() { + for family in [ + AddressFamily::Unspec, + // AddressFamily::Local, // `Local` and `Unix` overlap! + AddressFamily::Unix, + AddressFamily::Inet, + AddressFamily::Ax25, + AddressFamily::Ipx, + AddressFamily::Appletalk, + AddressFamily::Netrom, + AddressFamily::Bridge, + AddressFamily::Atmpvc, + AddressFamily::X25, + AddressFamily::Inet6, + AddressFamily::Rose, + AddressFamily::Decnet, + AddressFamily::Netbeui, + AddressFamily::Security, + AddressFamily::Key, + // AddressFamily::Route, // `Route` and `Netlink` overlap! + AddressFamily::Netlink, + AddressFamily::Packet, + AddressFamily::Ash, + AddressFamily::Econet, + AddressFamily::Atmsvc, + AddressFamily::Rds, + AddressFamily::Sna, + AddressFamily::Irda, + AddressFamily::Pppox, + AddressFamily::Wanpipe, + AddressFamily::Llc, + AddressFamily::Ib, + AddressFamily::Mpls, + AddressFamily::Can, + AddressFamily::Tipc, + AddressFamily::Bluetooth, + AddressFamily::Iucv, + AddressFamily::Rxrpc, + AddressFamily::Isdn, + AddressFamily::Phonet, + AddressFamily::Ieee802154, + AddressFamily::Caif, + AddressFamily::Alg, + AddressFamily::Nfc, + AddressFamily::Vsock, + AddressFamily::Kcm, + AddressFamily::Qipcrtr, + AddressFamily::Smc, + AddressFamily::Xdp, + AddressFamily::Mctp, + ] { + let orig = TcActionMessageHeader { family }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageHeader::parse( + &TcActionMessageBuffer::new_checked(&buffer).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); + } +} + +#[test] +fn tc_action_message_header_parse_back_other() { + let orig = TcActionMessageHeader { + family: AddressFamily::Other(99), + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageHeader::parse( + &TcActionMessageBuffer::new_checked(&buffer).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} diff --git a/src/tc/actions/tests/message.rs b/src/tc/actions/tests/message.rs new file mode 100644 index 00000000..47031710 --- /dev/null +++ b/src/tc/actions/tests/message.rs @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::nla::{DefaultNla, NlaBuffer}; +use netlink_packet_utils::{Emitable, Parseable}; + +use crate::tc::actions::message::TcActionMessageAttribute::{ + Actions, Flags, RootCount, RootExtWarnMsg, RootTimeDelta, +}; +use crate::tc::actions::message::{ + TcActionMessage, TcActionMessageAttribute, TcActionMessageFlags, + TcActionMessageFlagsWithSelector, +}; +use crate::tc::actions::{TcActionMessageBuffer, TcActionMessageHeader}; +use crate::tc::TcAction; +use crate::tc::TcActionAttribute::{Cookie, Index, Kind}; +use crate::AddressFamily; + +mod mirror { + use netlink_packet_utils::nla::DefaultNla; + use netlink_packet_utils::Parseable; + + use crate::tc::actions::message::TcActionMessage; + use crate::tc::actions::message::TcActionMessageAttribute::{ + Actions, RootCount, + }; + use crate::tc::actions::{TcActionMessageBuffer, TcActionMessageHeader}; + use crate::tc::TcActionAttribute::{ + InHwCount, Kind, Options, Other, Stats, + }; + use crate::tc::TcActionMirrorOption::{Parms, Tm}; + use crate::tc::TcActionOption::Mirror; + use crate::tc::TcActionType::{Pipe, Stolen}; + use crate::tc::TcMirrorActionType::{EgressRedir, IngressMirror}; + use crate::tc::TcStats2::{Basic, BasicHw, Queue}; + use crate::tc::{ + TcAction, TcActionGeneric, TcMirror, TcStatsBasic, TcStatsQueue, + }; + use crate::AddressFamily; + + /// Captured `TcActionMessage` examples used for testing. + mod message { + /// Request + /// ```bash + /// tc actions add action mirred egress redirect dev lo index 1 + /// ``` + pub(super) const CREATE1: &str = "0000000038000100340001000b0001006d69727265640000240002802000020001000000000000000400000000000000000000000100000001000000"; + /// Request + /// ```bash + /// tc actions add action mirred ingress mirror dev lo index 2 + /// ``` + pub(super) const CREATE2: &str = "0000000038000100340001000b0001006d69727265640000240002802000020002000000000000000300000000000000000000000400000001000000"; + /// Response + /// ```bash + /// tc actions list action mirred + /// ``` + pub(super) const LIST: &str = "00000000080003000200000064010100b00000000b0001006d6972726564000044000400140001000000000000000000000000000000000014000700000000000000000000000000000000001800030000000000000000000000000000000000000000000c000900000000000300000008000a0000000000480002002000020001000000000000000400000001000000000000000100000001000000240001000000000000000000000000000000000000000000000000000000000000000000b00001000b0001006d6972726564000044000400140001000000000000000000000000000000000014000700000000000000000000000000000000001800030000000000000000000000000000000000000000000c000900000000000300000008000a0000000000480002002000020002000000000000000300000001000000000000000400000001000000240001000000000000000000000000000000000000000000000000000000000000000000"; + } + + #[test] + fn parse_message1_create() { + let expected = TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("mirred".into()), + Options(vec![Mirror(Parms(TcMirror { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: Stolen, + refcnt: 0, + bindcnt: 0, + }, + eaction: EgressRedir, + ifindex: 1, + }))]), + ], + }])], + }; + + let buf = hex::decode(message::CREATE1).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, expected); + } + + #[test] + fn parse_message2_create() { + let expected = TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("mirred".into()), + Options(vec![Mirror(Parms(TcMirror { + generic: TcActionGeneric { + index: 2, + capab: 0, + action: Pipe, + refcnt: 0, + bindcnt: 0, + }, + eaction: IngressMirror, + ifindex: 1, + }))]), + ], + }])], + }; + + let buf = hex::decode(message::CREATE2).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, expected); + } + + #[test] + fn parse_message3_list() { + let expected = TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![ + RootCount(2), + Actions(vec![ + TcAction { + tab: 0, + attributes: vec![ + Kind("mirred".into()), + Stats(vec![ + Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + Other(DefaultNla::new( + 9, + vec![0, 0, 0, 0, 3, 0, 0, 0], + )), + InHwCount(0), + Options(vec![ + Mirror(Parms(TcMirror { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: Stolen, + refcnt: 1, + bindcnt: 0, + }, + 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, + ])), + ]), + ], + }, + TcAction { + tab: 1, + attributes: vec![ + Kind("mirred".into()), + Stats(vec![ + Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + Other(DefaultNla::new( + 9, + vec![0, 0, 0, 0, 3, 0, 0, 0], + )), + InHwCount(0), + Options(vec![ + Mirror(Parms(TcMirror { + generic: TcActionGeneric { + index: 2, + capab: 0, + action: Pipe, + refcnt: 1, + bindcnt: 0, + }, + 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, + ])), + ]), + ], + }, + ]), + ], + }; + let buf = hex::decode(message::LIST).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, expected); + } +} + +#[test] +fn tc_action_message_attribute_parse_back_blank_actions() { + let orig = Actions(vec![]); + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageAttribute::parse( + &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_attribute_parse_back_example_action() { + let orig = Actions(vec![TcAction { + tab: 9999, + attributes: vec![Kind("example".into())], + }]); + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageAttribute::parse( + &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_attribute_parse_back_multiple_example_action() { + let orig = Actions(vec![ + TcAction { + tab: 1111, + attributes: vec![Kind("example1".into())], + }, + TcAction { + tab: 2222, + attributes: vec![ + Kind("example2".into()), + Index(42), + Cookie(vec![1, 2, 3, 4, 5, 6]), + ], + }, + ]); + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageAttribute::parse( + &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_flags_parse_back_default() { + let orig = TcActionMessageFlagsWithSelector::default(); + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageFlagsWithSelector::parse( + &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_flags_parse_back_example_value() { + let orig = TcActionMessageFlagsWithSelector { + flags: TcActionMessageFlags::LargeDump + | TcActionMessageFlags::TerseDump, + selector: TcActionMessageFlags::LargeDump, + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessageFlagsWithSelector::parse( + &NlaBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_parse_back_default() { + let orig = TcActionMessage::default(); + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_action_message_parse_back_example_value() { + let orig = TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Alg, + }, + attributes: vec![ + Flags(TcActionMessageFlagsWithSelector { + flags: TcActionMessageFlags::LargeDump, + selector: TcActionMessageFlags::LargeDump, + }), + RootCount(42), + RootTimeDelta(43), + RootExtWarnMsg("hello".to_string()), + TcActionMessageAttribute::Other(DefaultNla::new( + 99, + vec![1, 2, 3, 4], + )), + ], + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} diff --git a/src/tc/actions/tests/mirror.rs b/src/tc/actions/tests/mirror.rs new file mode 100644 index 00000000..74343239 --- /dev/null +++ b/src/tc/actions/tests/mirror.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +use netlink_packet_utils::nla::NlaBuffer; +use netlink_packet_utils::{Emitable, Parseable}; + +use crate::tc::{ + TcActionGeneric, TcActionGenericBuffer, TcActionMirrorOption, TcActionType, + TcMirror, TcMirrorActionType, TcMirrorBuffer, +}; + +#[test] +fn tc_action_generic_parse_back() { + let orig = TcActionGeneric { + index: 1, + capab: 2, + action: TcActionType::Reclassify, + refcnt: 3, + bindcnt: 4, + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcActionGeneric::parse( + &TcActionGenericBuffer::new_checked(buffer).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_mirror_default_parse_back() { + let orig = TcMirror { + generic: Default::default(), + eaction: Default::default(), + ifindex: 111, + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcMirror::parse( + &TcMirrorBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[test] +fn tc_mirror_example_parse_back() { + let orig = TcMirror { + generic: TcActionGeneric { + index: 1, + capab: 2, + action: TcActionType::Ok, + refcnt: 3, + bindcnt: 4, + }, + eaction: TcMirrorActionType::IngressMirror, + ifindex: 99, + }; + let mut buffer = vec![0; orig.buffer_len()]; + orig.emit(&mut buffer); + let parsed = TcMirror::parse( + &TcMirrorBuffer::new_checked(buffer.as_slice()).unwrap(), + ) + .unwrap(); + assert_eq!(orig, parsed); +} + +#[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); +} + +#[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); +} diff --git a/src/tc/actions/tests/mod.rs b/src/tc/actions/tests/mod.rs new file mode 100644 index 00000000..e36825fe --- /dev/null +++ b/src/tc/actions/tests/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pub mod action; +pub mod header; +pub mod message; +pub mod mirror; +pub mod nat; diff --git a/src/tc/actions/tests/nat.rs b/src/tc/actions/tests/nat.rs new file mode 100644 index 00000000..e1eec778 --- /dev/null +++ b/src/tc/actions/tests/nat.rs @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT + +use std::net::Ipv4Addr; + +use netlink_packet_utils::nla::NlaBuffer; +use netlink_packet_utils::{Emitable, Parseable}; + +use crate::tc::actions::message::TcActionMessage; +use crate::tc::actions::message::TcActionMessageAttribute::Actions; +use crate::tc::actions::{TcActionMessageBuffer, TcActionMessageHeader}; +use crate::tc::TcActionAttribute::{InHwCount, Kind, Options, Stats}; +use crate::tc::TcActionNatOption::{Parms, Tm}; +use crate::tc::TcActionOption::Nat; +use crate::tc::TcStats2::{Basic, BasicHw, Queue}; +use crate::tc::{ + TcAction, TcActionGeneric, TcActionNatOption, TcActionType, TcNat, + TcNatFlags, TcStatsBasic, TcStatsQueue, +}; +use crate::AddressFamily; + +/// Capture of request for +/// +/// ```bash +/// tc actions add action nat ingress 1.2.3.4/32 5.6.7.0 index 1 +/// ``` +const TC_ACTION_NAT_EXAMPLE1: &str = "000000003c00010038000100080001006e6174002c0002802800010001000000000000000000000000000000000000000102030405060700ffffffff00000000"; + +fn tc_action_message_nat_example1() -> TcActionMessage { + TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("nat".into()), + Options(vec![Nat(TcActionNatOption::Parms(TcNat { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: TcActionType::Ok, + refcnt: 0, + bindcnt: 0, + }, + old_addr: Ipv4Addr::new(1, 2, 3, 4), + new_addr: Ipv4Addr::new(5, 6, 7, 0), + mask: Ipv4Addr::new(255, 255, 255, 255), + flags: TcNatFlags::empty(), + }))]), + ], + }])], + } +} + +#[test] +fn parse_tc_action_nat_example1() { + let buf = hex::decode(TC_ACTION_NAT_EXAMPLE1).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, tc_action_message_nat_example1()); +} + +#[test] +fn emit_tc_action_nat_example1() { + let example = tc_action_message_nat_example1(); + let mut buf = vec![0; example.buffer_len()]; + example.emit(&mut buf); + assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE1).unwrap()); +} + +/// Capture of request for +/// +/// ```bash +/// tc actions add action nat ingress 1.2.3.0/24 5.6.7.9 index 2 +/// ``` +const TC_ACTION_NAT_EXAMPLE2: &str = "000000003c00010038000100080001006e6174002c0002802800010002000000000000000000000000000000000000000102030005060709ffffff0000000000"; + +fn tc_action_message_nat_example2() -> TcActionMessage { + TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("nat".into()), + Options(vec![Nat(TcActionNatOption::Parms(TcNat { + generic: TcActionGeneric { + index: 2, + capab: 0, + action: TcActionType::Ok, + refcnt: 0, + bindcnt: 0, + }, + old_addr: Ipv4Addr::new(1, 2, 3, 0), + new_addr: Ipv4Addr::new(5, 6, 7, 9), + mask: Ipv4Addr::new(255, 255, 255, 0), + flags: TcNatFlags::empty(), + }))]), + ], + }])], + } +} + +#[test] +fn parse_tc_action_nat_example2() { + let buf = hex::decode(TC_ACTION_NAT_EXAMPLE2).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, tc_action_message_nat_example2()); +} + +#[test] +fn emit_tc_action_nat_example2() { + let example = tc_action_message_nat_example2(); + let mut buf = vec![0; example.buffer_len()]; + example.emit(&mut buf); + assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE2).unwrap()); +} + +/// Capture of request for +/// +/// ```bash +/// tc actions add action nat egress 2.3.4.0/24 5.6.7.9 index 3 +/// ``` +const TC_ACTION_NAT_EXAMPLE3: &str = "000000003c00010038000100080001006e6174002c0002802800010003000000000000000000000000000000000000000203040005060709ffffff0001000000"; + +fn tc_action_message_nat_example3() -> TcActionMessage { + TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("nat".into()), + Options(vec![Nat(TcActionNatOption::Parms(TcNat { + generic: TcActionGeneric { + index: 3, + capab: 0, + action: TcActionType::Ok, + refcnt: 0, + bindcnt: 0, + }, + old_addr: Ipv4Addr::new(2, 3, 4, 0), + new_addr: Ipv4Addr::new(5, 6, 7, 9), + mask: Ipv4Addr::new(255, 255, 255, 0), + flags: TcNatFlags::Egress, + }))]), + ], + }])], + } +} + +#[test] +fn parse_tc_action_nat_example3() { + let buf = hex::decode(TC_ACTION_NAT_EXAMPLE3).unwrap(); + let parsed = TcActionMessage::parse( + &TcActionMessageBuffer::new_checked(&buf).unwrap(), + ) + .unwrap(); + assert_eq!(parsed, tc_action_message_nat_example3()); +} + +#[test] +fn emit_tc_action_nat_example3() { + let example = tc_action_message_nat_example3(); + let mut buf = vec![0x00; example.buffer_len()]; + example.emit(&mut buf); + assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE3).unwrap()); +} + +const TC_ACTION_NAT_OPTION_PARAMS_EXAMPLES: [TcActionNatOption; 2] = [ + TcActionNatOption::Parms(TcNat { + flags: TcNatFlags::empty(), + generic: TcActionGeneric { + action: TcActionType::Reclassify, + bindcnt: 1, + capab: 2, + index: 3, + refcnt: 4, + }, + mask: Ipv4Addr::BROADCAST, + new_addr: Ipv4Addr::new(1, 2, 3, 4), + old_addr: Ipv4Addr::new(5, 6, 7, 8), + }), + TcActionNatOption::Parms(TcNat { + flags: TcNatFlags::empty(), + generic: TcActionGeneric { + action: TcActionType::Pipe, + bindcnt: 5, + capab: 6, + index: 7, + refcnt: 8, + }, + mask: Ipv4Addr::new(255, 255, 255, 254), + new_addr: Ipv4Addr::new(2, 1, 255, 0), + old_addr: Ipv4Addr::new(7, 2, 88, 44), + }), +]; + +#[test] +fn tc_action_nat_option_parse_back_example_params() { + for example in TC_ACTION_NAT_OPTION_PARAMS_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_uses_whole_buffer() { + for example in TC_ACTION_NAT_OPTION_PARAMS_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); + } +} + +fn tc_action_nat_option_tm_examples() -> [TcActionNatOption; 4] { + [ + TcActionNatOption::Tm(vec![]), + TcActionNatOption::Tm(vec![1]), + TcActionNatOption::Tm(vec![1, 2, 3, 4]), + TcActionNatOption::Tm(vec![99; 10]), + ] +} + +#[test] +fn tc_action_nat_option_parse_back_example_tm() { + for example in tc_action_nat_option_tm_examples().iter() { + 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().iter() { + 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 +/// tc actions flush action nat +/// tc actions add action nat ingress 192.0.2.1/32 203.0.113.1 index 1 +/// ``` +/// +/// Then capture netlink response message of this command: +/// +/// ```bash +/// tc -statistics actions get action nat index 1 +/// ``` +/// +/// Raw packet modification: +/// * cooked header removed (16 bytes). +/// * rtnetlink header removed (16 bytes). +#[test] +fn test_get_filter_nat() { + const RAW: &str = "00000000ac000100a8000100080001006e617400440004001400010000000000000000000000000000000000140007000000000000000000000000000000000018000300000000000000000000000000000000000000000008000a000000000050000200280001000100000000000000000000000100000000000000c0000201cb007101ffffffff00000000240002000000000000000000000000000000000000000000000000000000000000000000"; + let raw = hex::decode(RAW).unwrap(); + + let expected = TcActionMessage { + header: TcActionMessageHeader { + family: AddressFamily::Unspec, + }, + attributes: vec![Actions(vec![TcAction { + tab: 1, + attributes: vec![ + Kind("nat".into()), + Stats(vec![ + Basic(TcStatsBasic { + bytes: 0, + packets: 0, + }), + BasicHw(TcStatsBasic { + bytes: 0, + packets: 0, + }), + Queue(TcStatsQueue { + qlen: 0, + backlog: 0, + drops: 0, + requeues: 0, + overlimits: 0, + }), + ]), + InHwCount(0), + Options(vec![ + Nat(Parms(TcNat { + generic: TcActionGeneric { + index: 1, + capab: 0, + action: TcActionType::Ok, + refcnt: 1, + bindcnt: 0, + }, + old_addr: [192, 0, 2, 1].into(), + new_addr: [203, 0, 113, 1].into(), + 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, + ])), + ]), + ], + }])], + }; + + assert_eq!( + expected, + TcActionMessage::parse(&TcActionMessageBuffer::new(&raw)).unwrap() + ); + + let mut buf = vec![0; expected.buffer_len()]; + + expected.emit(&mut buf); + + assert_eq!(buf, raw); +} diff --git a/src/tc/mod.rs b/src/tc/mod.rs index 56608bd2..a083b968 100644 --- a/src/tc/mod.rs +++ b/src/tc/mod.rs @@ -11,9 +11,11 @@ mod stats; pub use self::actions::{ TcAction, TcActionAttribute, TcActionGeneric, TcActionGenericBuffer, - TcActionMirror, TcActionMirrorOption, TcActionNat, TcActionNatOption, - TcActionOption, TcActionType, TcMirror, TcMirrorActionType, TcMirrorBuffer, - TcNat, TcNatBuffer, TcNatFlags, + TcActionMessage, TcActionMessageAttribute, TcActionMessageBuffer, + TcActionMessageFlags, TcActionMessageFlagsWithSelector, TcActionMirror, + TcActionMirrorOption, TcActionNat, TcActionNatOption, TcActionOption, + TcActionType, TcMirror, TcMirrorActionType, TcMirrorBuffer, TcNat, + TcNatBuffer, TcNatFlags, }; pub use self::attribute::TcAttribute; pub use self::filters::{ diff --git a/src/tc/tests/action_nat.rs b/src/tc/tests/action_nat.rs deleted file mode 100644 index 172993b2..00000000 --- a/src/tc/tests/action_nat.rs +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MIT - -use std::net::Ipv4Addr; - -use netlink_packet_utils::{Emitable, Parseable}; - -use crate::{ - tc::{ - filters::{TcU32OptionFlags, TcU32SelectorFlags}, - TcAction, TcActionAttribute, TcActionGeneric, TcActionNatOption, - TcActionOption, TcActionType, TcAttribute, TcFilterU32Option, TcHandle, - TcHeader, TcMessage, TcMessageBuffer, TcNat, TcNatFlags, TcOption, - TcStats2, TcStatsBasic, TcStatsQueue, TcU32Key, TcU32Selector, - }, - AddressFamily, -}; - -// Setup: -// ip link add dummy1 type dummy -// ip link set dummy1 up -// tc qdisc add dev dummy1 root handle 1: prio bands 4 -// tc filter add dev dummy1 parent ffff: \ -// protocol ip prio 10 u32 \ -// match ip dst 192.0.2.1/32 \ -// action nat ingress 192.0.2.1/32 203.0.113.1 -// -// Capture nlmon of this command: -// -// tc -s filter show dev dummy1 -// -// Raw packet modification: -// * rtnetlink header removed. -#[test] -fn test_get_filter_nat() { - let raw = vec![ - 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x80, - 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x01, 0x00, - 0x75, 0x33, 0x32, 0x00, 0x08, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x00, 0x24, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x02, 0x02, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, - 0x08, 0x00, 0x0b, 0x00, 0x08, 0x00, 0x00, 0x00, 0xac, 0x00, 0x07, 0x00, - 0xa8, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00, 0x6e, 0x61, 0x74, 0x00, - 0x44, 0x00, 0x04, 0x00, 0x14, 0x00, 0x01, 0x00, 0x62, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 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, 0x08, 0x00, 0x0a, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x00, 0x28, 0x00, 0x01, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x02, - 0xcb, 0x00, 0x71, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x02, 0x00, 0x87, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x78, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x09, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ]; - - let expected = TcMessage { - header: TcHeader { - family: AddressFamily::Unspec, - index: 53, - handle: TcHandle { - major: 0x8000, - minor: 0x800, - }, - parent: TcHandle { major: 1, minor: 0 }, - info: 262152, // TODO(Gris Ge) - }, - attributes: vec![ - TcAttribute::Kind("u32".to_string()), - TcAttribute::Chain(0), - TcAttribute::Options(vec![ - TcOption::U32(TcFilterU32Option::Selector(TcU32Selector { - flags: TcU32SelectorFlags::Terminal, - offshift: 0, - nkeys: 1, - offmask: 0, - off: 0, - offoff: 0, - hoff: 0, - hmask: 0, - keys: vec![TcU32Key { - mask: 0xffffffff, - val: u32::from_ne_bytes( - Ipv4Addr::new(192, 0, 2, 2).octets(), - ), - off: 16, - offmask: 0, - }], - })), - TcOption::U32(TcFilterU32Option::Hash(u32::from_be(0x80))), - TcOption::U32(TcFilterU32Option::Flags( - TcU32OptionFlags::NotInHw, - )), - TcOption::U32(TcFilterU32Option::Action(vec![TcAction { - tab: 1, - attributes: vec![ - TcActionAttribute::Kind("nat".to_string()), - TcActionAttribute::Stats(vec![ - TcStats2::Basic(TcStatsBasic { - bytes: 98, - packets: 1, - }), - TcStats2::BasicHw(TcStatsBasic { - bytes: 0, - packets: 0, - }), - TcStats2::Queue(TcStatsQueue { - qlen: 0, - backlog: 0, - drops: 0, - requeues: 0, - overlimits: 0, - }), - ]), - TcActionAttribute::InHwCount(0), - TcActionAttribute::Options(vec![ - TcActionOption::Nat(TcActionNatOption::Parms( - TcNat { - generic: TcActionGeneric { - index: 1, - capab: 0, - action: TcActionType::Ok, - refcnt: 1, - bindcnt: 1, - }, - old_addr: Ipv4Addr::new(192, 0, 2, 2), - new_addr: Ipv4Addr::new(203, 0, 113, 1), - mask: Ipv4Addr::new(255, 255, 255, 255), - flags: TcNatFlags::empty(), - }, - )), - TcActionOption::Nat(TcActionNatOption::Tm(vec![ - 135, 20, 0, 0, 0, 0, 0, 0, 120, 7, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 7, 0, 0, 0, - 0, 0, 0, - ])), - ]), - ], - }])), - TcOption::U32(TcFilterU32Option::Pnct(vec![ - 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 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); -} diff --git a/src/tc/tests/mod.rs b/src/tc/tests/mod.rs index 62cb2237..05058a46 100644 --- a/src/tc/tests/mod.rs +++ b/src/tc/tests/mod.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: MIT -#[cfg(test)] -mod action_nat; #[cfg(test)] mod filter_matchall; #[cfg(test)] From 6a28989cb8749aa0bfb243c4447b2cf44e4790ee Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Mon, 13 May 2024 20:59:53 -0600 Subject: [PATCH 3/3] Feedback fixups Summary of changes: 1. adjust use of `NLA_F_NESTED` flag in both tests and emitted messages to reflect the kernel's stated usage (i.e. that `NLA_F_NESTED` be set for the tc-actions options). This required adjusting the tests which seem to have captured iproute2's incorrectly formed messages (iproute2 does not always set the `NLA_F_NESTED` flag) 2. Remove the use of hex as a dev dependency and use a const slice of `u8` instead as per @cathay4t request 3. remove links to iproute2's github as per @cathay4t request 4. trivial documentation cleanup 5. restore previously deleted test --- Cargo.toml | 1 - src/tc/actions/action.rs | 21 +++------ src/tc/actions/message.rs | 20 +------- src/tc/actions/tests/message.rs | 75 ++++++++++++++++++++++++----- src/tc/actions/tests/nat.rs | 84 +++++++++++++++++++++++---------- src/tc/tests/filter_matchall.rs | 2 +- 6 files changed, 134 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a346ddb5..a25aa39e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,5 @@ netlink-packet-utils = { version = "0.5.2" } name = "dump_packet_links" [dev-dependencies] -hex = "0.4.3" netlink-sys = { version = "0.8.5" } pretty_assertions = "0.7.2" diff --git a/src/tc/actions/action.rs b/src/tc/actions/action.rs index 81203188..d56ba9a7 100644 --- a/src/tc/actions/action.rs +++ b/src/tc/actions/action.rs @@ -25,7 +25,10 @@ const TCA_ACT_TAB: u16 = 1; #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub struct TcAction { - /// Table id. Corresponds to the `Kind` of the action. + /// Table id. + /// Corresponds to the [`Kind`] of the action. + /// + /// [`Kind`]: crate::tc::TcActionAttribute::Kind pub tab: u16, /// Attributes of the action. pub attributes: Vec, @@ -203,17 +206,7 @@ impl Nla for TcActionAttribute { fn kind(&self) -> u16 { match self { Self::Kind(_) => TCA_ACT_KIND, - Self::Options(opts) => { - // NOTE: the kernel simply doesn't consistently use the nested - // flag based on captured messages. - // This is heuristically trying to match the kernel's behavior - // but may not be correct. - if opts.len() == 1 { - TCA_ACT_OPTIONS | NLA_F_NESTED - } else { - TCA_ACT_OPTIONS - } - } + Self::Options(_) => TCA_ACT_OPTIONS | NLA_F_NESTED, Self::Index(_) => TCA_ACT_INDEX, Self::Stats(_) => TCA_ACT_STATS, Self::Cookie(_) => TCA_ACT_COOKIE, @@ -274,8 +267,8 @@ where } } -/// `TcActionOption` is a netlink message attribute that describes an option of -/// a [tc-actions] action. +/// [`TcActionOption`] is a netlink message attribute that describes an option +/// of a [tc-actions] action. /// /// This enum is non-exhaustive as new action types may be added to the kernel /// at any time. diff --git a/src/tc/actions/message.rs b/src/tc/actions/message.rs index 5f3185d9..9038ed1f 100644 --- a/src/tc/actions/message.rs +++ b/src/tc/actions/message.rs @@ -28,27 +28,11 @@ bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, PartialOrd, Ord, Hash)] #[non_exhaustive] pub struct TcActionMessageFlags: u32 { - /// From `iproute2`'s [`rtnetlink.h`] - /// - /// If set, this flag enables more than TCA_ACT_MAX_PRIO actions in a single + /// If set, this flag enables more than `TCA_ACT_MAX_PRIO` actions in a single /// actions listing operation. - /// - /// > TCA_ACT_FLAG_LARGE_DUMP_ON user->kernel to request for larger than - /// TCA_ACT_MAX_PRIO actions in a dump. - /// All dump responses will contain the number of actions being dumped - /// stored in for user app's consumption in TCA_ROOT_COUNT - /// - /// [`rtnetlink.h`]: https://github.com/iproute2/iproute2/blob/89210b9ec1c445ae963d181b5816d12a0cdafbb6/include/uapi/linux/rtnetlink.h#L803-L806 const LargeDump = TCA_ACT_FLAG_LARGE_DUMP_ON; /// If set, this flag restricts an action dump to only include essential /// details. - /// - /// From `iproute2`'s [`rtnetlink.h`]: - /// - /// > TCA_ACT_FLAG_TERSE_DUMP user->kernel to request terse (brief) dump - /// that only includes essential action info (kind, index, etc.) - /// - /// [`rtnetlink.h`]: https://github.com/iproute2/iproute2/blob/89210b9ec1c445ae963d181b5816d12a0cdafbb6/include/uapi/linux/rtnetlink.h#L808-L809 const TerseDump = TCA_ACT_FLAG_TERSE_DUMP; const _ = !0; } @@ -147,7 +131,7 @@ const TCA_ROOT_TIME_DELTA: u16 = 4; const TCA_ROOT_EXT_WARN_MSG: u16 = 5; /// This enum is used to represent the different types of attributes that can be -/// part of a `TcActionMessage`. +/// part of a [`TcActionMessage`]. /// /// This enum is non-exhaustive, additional variants may be added in the future. #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/tc/actions/tests/message.rs b/src/tc/actions/tests/message.rs index 47031710..d12efd14 100644 --- a/src/tc/actions/tests/message.rs +++ b/src/tc/actions/tests/message.rs @@ -39,21 +39,75 @@ mod mirror { /// Captured `TcActionMessage` examples used for testing. mod message { - /// Request + /// Capture of request message for + /// /// ```bash /// tc actions add action mirred egress redirect dev lo index 1 /// ``` - pub(super) const CREATE1: &str = "0000000038000100340001000b0001006d69727265640000240002802000020001000000000000000400000000000000000000000100000001000000"; - /// Request + pub(super) const CREATE1: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x01, 0x00, 0x34, 0x00, 0x01, + 0x00, 0x0b, 0x00, 0x01, 0x00, 0x6d, 0x69, 0x72, 0x72, 0x65, 0x64, + 0x00, 0x00, 0x24, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, + ]; + /// Capture of request message for + /// /// ```bash /// tc actions add action mirred ingress mirror dev lo index 2 /// ``` - pub(super) const CREATE2: &str = "0000000038000100340001000b0001006d69727265640000240002802000020002000000000000000300000000000000000000000400000001000000"; - /// Response + pub(super) const CREATE2: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x01, 0x00, 0x34, 0x00, 0x01, + 0x00, 0x0b, 0x00, 0x01, 0x00, 0x6d, 0x69, 0x72, 0x72, 0x65, 0x64, + 0x00, 0x00, 0x24, 0x00, 0x02, 0x80, 0x20, 0x00, 0x02, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, + ]; + /// Capture of request message for + /// /// ```bash /// tc actions list action mirred /// ``` - pub(super) const LIST: &str = "00000000080003000200000064010100b00000000b0001006d6972726564000044000400140001000000000000000000000000000000000014000700000000000000000000000000000000001800030000000000000000000000000000000000000000000c000900000000000300000008000a0000000000480002002000020001000000000000000400000001000000000000000100000001000000240001000000000000000000000000000000000000000000000000000000000000000000b00001000b0001006d6972726564000044000400140001000000000000000000000000000000000014000700000000000000000000000000000000001800030000000000000000000000000000000000000000000c000900000000000300000008000a0000000000480002002000020002000000000000000300000001000000000000000400000001000000240001000000000000000000000000000000000000000000000000000000000000000000"; + /// + /// after the messages in [`CREATE1`] and [`CREATE2`] have been added. + pub(super) const LIST: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x64, 0x01, 0x01, 0x00, 0xb0, 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, 0x0c, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 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, 0x0c, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x00, 0x02, 0x00, 0x20, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + ]; } #[test] @@ -81,9 +135,8 @@ mod mirror { }])], }; - let buf = hex::decode(message::CREATE1).unwrap(); let parsed = TcActionMessage::parse( - &TcActionMessageBuffer::new_checked(&buf).unwrap(), + &TcActionMessageBuffer::new_checked(&message::CREATE1).unwrap(), ) .unwrap(); assert_eq!(parsed, expected); @@ -114,7 +167,7 @@ mod mirror { }])], }; - let buf = hex::decode(message::CREATE2).unwrap(); + let buf = message::CREATE2; let parsed = TcActionMessage::parse( &TcActionMessageBuffer::new_checked(&buf).unwrap(), ) @@ -123,6 +176,7 @@ mod mirror { } #[test] + #[allow(clippy::too_many_lines)] fn parse_message3_list() { let expected = TcActionMessage { header: TcActionMessageHeader { @@ -226,9 +280,8 @@ mod mirror { ]), ], }; - let buf = hex::decode(message::LIST).unwrap(); let parsed = TcActionMessage::parse( - &TcActionMessageBuffer::new_checked(&buf).unwrap(), + &TcActionMessageBuffer::new_checked(&message::LIST).unwrap(), ) .unwrap(); assert_eq!(parsed, expected); diff --git a/src/tc/actions/tests/nat.rs b/src/tc/actions/tests/nat.rs index e1eec778..06d2b804 100644 --- a/src/tc/actions/tests/nat.rs +++ b/src/tc/actions/tests/nat.rs @@ -23,7 +23,14 @@ use crate::AddressFamily; /// ```bash /// tc actions add action nat ingress 1.2.3.4/32 5.6.7.0 index 1 /// ``` -const TC_ACTION_NAT_EXAMPLE1: &str = "000000003c00010038000100080001006e6174002c0002802800010001000000000000000000000000000000000000000102030405060700ffffffff00000000"; +const TC_ACTION_NAT_EXAMPLE1: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x6e, 0x61, 0x74, 0x00, 0x2c, 0x00, 0x02, 0x80, + 0x28, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, +]; fn tc_action_message_nat_example1() -> TcActionMessage { TcActionMessage { @@ -34,7 +41,7 @@ fn tc_action_message_nat_example1() -> TcActionMessage { tab: 1, attributes: vec![ Kind("nat".into()), - Options(vec![Nat(TcActionNatOption::Parms(TcNat { + Options(vec![Nat(Parms(TcNat { generic: TcActionGeneric { index: 1, capab: 0, @@ -54,7 +61,7 @@ fn tc_action_message_nat_example1() -> TcActionMessage { #[test] fn parse_tc_action_nat_example1() { - let buf = hex::decode(TC_ACTION_NAT_EXAMPLE1).unwrap(); + let buf = TC_ACTION_NAT_EXAMPLE1; let parsed = TcActionMessage::parse( &TcActionMessageBuffer::new_checked(&buf).unwrap(), ) @@ -67,7 +74,7 @@ fn emit_tc_action_nat_example1() { let example = tc_action_message_nat_example1(); let mut buf = vec![0; example.buffer_len()]; example.emit(&mut buf); - assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE1).unwrap()); + assert_eq!(buf.as_slice(), TC_ACTION_NAT_EXAMPLE1); } /// Capture of request for @@ -75,7 +82,14 @@ fn emit_tc_action_nat_example1() { /// ```bash /// tc actions add action nat ingress 1.2.3.0/24 5.6.7.9 index 2 /// ``` -const TC_ACTION_NAT_EXAMPLE2: &str = "000000003c00010038000100080001006e6174002c0002802800010002000000000000000000000000000000000000000102030005060709ffffff0000000000"; +const TC_ACTION_NAT_EXAMPLE2: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x6e, 0x61, 0x74, 0x00, 0x2c, 0x00, 0x02, 0x80, + 0x28, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x00, 0x05, 0x06, 0x07, 0x09, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, +]; fn tc_action_message_nat_example2() -> TcActionMessage { TcActionMessage { @@ -86,7 +100,7 @@ fn tc_action_message_nat_example2() -> TcActionMessage { tab: 1, attributes: vec![ Kind("nat".into()), - Options(vec![Nat(TcActionNatOption::Parms(TcNat { + Options(vec![Nat(Parms(TcNat { generic: TcActionGeneric { index: 2, capab: 0, @@ -106,7 +120,7 @@ fn tc_action_message_nat_example2() -> TcActionMessage { #[test] fn parse_tc_action_nat_example2() { - let buf = hex::decode(TC_ACTION_NAT_EXAMPLE2).unwrap(); + let buf = TC_ACTION_NAT_EXAMPLE2; let parsed = TcActionMessage::parse( &TcActionMessageBuffer::new_checked(&buf).unwrap(), ) @@ -119,7 +133,7 @@ fn emit_tc_action_nat_example2() { let example = tc_action_message_nat_example2(); let mut buf = vec![0; example.buffer_len()]; example.emit(&mut buf); - assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE2).unwrap()); + assert_eq!(buf.as_slice(), TC_ACTION_NAT_EXAMPLE2); } /// Capture of request for @@ -127,7 +141,14 @@ fn emit_tc_action_nat_example2() { /// ```bash /// tc actions add action nat egress 2.3.4.0/24 5.6.7.9 index 3 /// ``` -const TC_ACTION_NAT_EXAMPLE3: &str = "000000003c00010038000100080001006e6174002c0002802800010003000000000000000000000000000000000000000203040005060709ffffff0001000000"; +const TC_ACTION_NAT_EXAMPLE3: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x6e, 0x61, 0x74, 0x00, 0x2c, 0x00, 0x02, 0x80, + 0x28, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x04, 0x00, 0x05, 0x06, 0x07, 0x09, 0xff, 0xff, 0xff, 0x00, + 0x01, 0x00, 0x00, 0x00, +]; fn tc_action_message_nat_example3() -> TcActionMessage { TcActionMessage { @@ -138,7 +159,7 @@ fn tc_action_message_nat_example3() -> TcActionMessage { tab: 1, attributes: vec![ Kind("nat".into()), - Options(vec![Nat(TcActionNatOption::Parms(TcNat { + Options(vec![Nat(Parms(TcNat { generic: TcActionGeneric { index: 3, capab: 0, @@ -158,7 +179,7 @@ fn tc_action_message_nat_example3() -> TcActionMessage { #[test] fn parse_tc_action_nat_example3() { - let buf = hex::decode(TC_ACTION_NAT_EXAMPLE3).unwrap(); + let buf = TC_ACTION_NAT_EXAMPLE3; let parsed = TcActionMessage::parse( &TcActionMessageBuffer::new_checked(&buf).unwrap(), ) @@ -171,11 +192,11 @@ fn emit_tc_action_nat_example3() { let example = tc_action_message_nat_example3(); let mut buf = vec![0x00; example.buffer_len()]; example.emit(&mut buf); - assert_eq!(buf, hex::decode(TC_ACTION_NAT_EXAMPLE3).unwrap()); + assert_eq!(buf.as_slice(), TC_ACTION_NAT_EXAMPLE3); } const TC_ACTION_NAT_OPTION_PARAMS_EXAMPLES: [TcActionNatOption; 2] = [ - TcActionNatOption::Parms(TcNat { + Parms(TcNat { flags: TcNatFlags::empty(), generic: TcActionGeneric { action: TcActionType::Reclassify, @@ -188,7 +209,7 @@ const TC_ACTION_NAT_OPTION_PARAMS_EXAMPLES: [TcActionNatOption; 2] = [ new_addr: Ipv4Addr::new(1, 2, 3, 4), old_addr: Ipv4Addr::new(5, 6, 7, 8), }), - TcActionNatOption::Parms(TcNat { + Parms(TcNat { flags: TcNatFlags::empty(), generic: TcActionGeneric { action: TcActionType::Pipe, @@ -229,16 +250,16 @@ fn tc_action_nat_option_emit_uses_whole_buffer() { fn tc_action_nat_option_tm_examples() -> [TcActionNatOption; 4] { [ - TcActionNatOption::Tm(vec![]), - TcActionNatOption::Tm(vec![1]), - TcActionNatOption::Tm(vec![1, 2, 3, 4]), - TcActionNatOption::Tm(vec![99; 10]), + 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().iter() { + 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( @@ -251,7 +272,7 @@ fn tc_action_nat_option_parse_back_example_tm() { #[test] fn tc_action_nat_option_emit_tm_uses_whole_buffer() { - for example in tc_action_nat_option_tm_examples().iter() { + 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); @@ -278,8 +299,23 @@ fn tc_action_nat_option_emit_tm_uses_whole_buffer() { /// * rtnetlink header removed (16 bytes). #[test] fn test_get_filter_nat() { - const RAW: &str = "00000000ac000100a8000100080001006e617400440004001400010000000000000000000000000000000000140007000000000000000000000000000000000018000300000000000000000000000000000000000000000008000a000000000050000200280001000100000000000000000000000100000000000000c0000201cb007101ffffffff00000000240002000000000000000000000000000000000000000000000000000000000000000000"; - let raw = hex::decode(RAW).unwrap(); + const RAW: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0xac, 0x00, 0x01, 0x00, 0xa8, 0x00, 0x01, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x6e, 0x61, 0x74, 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, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x02, 0x80, 0x28, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x02, 0x01, 0xcb, 0x00, 0x71, 0x01, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; let expected = TcActionMessage { header: TcActionMessageHeader { @@ -332,12 +368,12 @@ fn test_get_filter_nat() { assert_eq!( expected, - TcActionMessage::parse(&TcActionMessageBuffer::new(&raw)).unwrap() + TcActionMessage::parse(&TcActionMessageBuffer::new(&RAW)).unwrap() ); let mut buf = vec![0; expected.buffer_len()]; expected.emit(&mut buf); - assert_eq!(buf, raw); + assert_eq!(buf, RAW); } diff --git a/src/tc/tests/filter_matchall.rs b/src/tc/tests/filter_matchall.rs index 135fadc9..58755d04 100644 --- a/src/tc/tests/filter_matchall.rs +++ b/src/tc/tests/filter_matchall.rs @@ -82,7 +82,7 @@ fn test_get_filter_matchall() { 0x0a, 0x00, // TCA_ACT_IN_HW_COUNT 0x00, 0x00, 0x00, 0x00, // 0 0x48, 0x00, // length 72 - 0x02, 0x00, // TCA_ACT_OPTIONS + 0x02, 0x80, // TCA_ACT_OPTIONS 0x20, 0x00, // length 32 0x02, 0x00, // TCA_MIRRED_PARMS 0x01, 0x00, 0x00, 0x00, // index 1