From 186747ecd217feabac2f25cc86a5cc3436551145 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Thu, 9 May 2024 17:51:16 -0600 Subject: [PATCH 1/9] 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/9] 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 18e884cb9b2c4f4fb963a989afaaba49506c1e36 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Mon, 13 May 2024 20:59:53 -0600 Subject: [PATCH 3/9] 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 --- 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 From 9cee739ae5f3f0e4fda5564a5e6bcbf74c90c689 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Fri, 10 May 2024 10:40:07 -0600 Subject: [PATCH 4/9] Support for tc-flower --- src/enc.rs | 128 ++ src/lib.rs | 5 + src/message.rs | 5 +- src/net/arp.rs | 138 ++ src/net/ethernet.rs | 408 ++++++ src/net/icmpv4.rs | 1266 ++++++++++++++++++ src/net/icmpv6.rs | 237 ++++ src/net/mod.rs | 16 + src/net/mpls.rs | 208 +++ src/net/vxlan.rs | 100 ++ src/tc/filters/cls_flags.rs | 5 + src/tc/filters/cls_flower.rs | 1747 +++++++++++++++++++++++++ src/tc/filters/flower/encap/erspan.rs | 284 ++++ src/tc/filters/flower/encap/geneve.rs | 210 +++ src/tc/filters/flower/encap/gtp.rs | 66 + src/tc/filters/flower/encap/mod.rs | 136 ++ src/tc/filters/flower/encap/vxlan.rs | 84 ++ src/tc/filters/flower/mod.rs | 11 + src/tc/filters/flower/mpls.rs | 289 ++++ src/tc/filters/flower_flags.rs | 17 + src/tc/filters/mod.rs | 19 +- src/tc/filters/u32_flags.rs | 19 +- src/tc/mod.rs | 25 +- src/tc/options.rs | 17 +- 24 files changed, 5410 insertions(+), 30 deletions(-) create mode 100644 src/enc.rs create mode 100644 src/net/arp.rs create mode 100644 src/net/ethernet.rs create mode 100644 src/net/icmpv4.rs create mode 100644 src/net/icmpv6.rs create mode 100644 src/net/mod.rs create mode 100644 src/net/mpls.rs create mode 100644 src/net/vxlan.rs create mode 100644 src/tc/filters/cls_flags.rs create mode 100644 src/tc/filters/cls_flower.rs create mode 100644 src/tc/filters/flower/encap/erspan.rs create mode 100644 src/tc/filters/flower/encap/geneve.rs create mode 100644 src/tc/filters/flower/encap/gtp.rs create mode 100644 src/tc/filters/flower/encap/mod.rs create mode 100644 src/tc/filters/flower/encap/vxlan.rs create mode 100644 src/tc/filters/flower/mod.rs create mode 100644 src/tc/filters/flower/mpls.rs create mode 100644 src/tc/filters/flower_flags.rs diff --git a/src/enc.rs b/src/enc.rs new file mode 100644 index 00000000..4ff58849 --- /dev/null +++ b/src/enc.rs @@ -0,0 +1,128 @@ +use netlink_packet_utils::DecodeError; + +/// An identifier for an encapsulation key used by a tunnel. +/// +/// Examples include a VNIs for VXLAN and Geneve tunnels, ERSPAN/GRE keys, and +/// GTP tunnel keys. +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct EncKeyId(u32); + +impl EncKeyId { + /// Create a new `EncKeyId` without checking the validity of the ID value. + /// + /// # Safety + /// + /// Failure to ensure the ID is within the valid range for the tunnel in + /// question may lead to semantically invalid netlink messages. + /// + /// If you know the tunnel type (e.g., vxlan) and wish to confirm that the + /// ID is within the valid range of values for that tunnel type, use the + /// corresponding new method (e.g., `new_vxlan_vni`). + #[must_use] + pub const fn new_unchecked(id: u32) -> Self { + Self(id) + } + + /// Create a new `EncKeyId` and confirm that it is within the valid range + /// of vxlan vni values. + /// + /// # Errors + /// Returns an error if the ID is zero or greater than or equal to 2^24. + pub fn new_vxlan_vni(id: u32) -> Result { + crate::net::vxlan::Vni::new(id).map(Into::into) + } + + /// Create a new `EncKeyId` and confirm that it is within the valid range + /// of geneve vni values. + /// + /// # Errors + /// + /// Returns an error if the ID is greater than or equal to 2^24. + pub fn new_geneve_vni(id: u32) -> Result { + match Self::new_nbit::<24>(id) { + Ok(id) => Ok(id), + Err(_) => Err(DecodeError::from( + "Geneve VNI must be less than 2^24, received {id}", + )), + } + } + + /// Create a new `EncKeyId` in the space of valid GRE keys. + /// + /// # Safety + /// + /// Since GRE keys are 32 bits and all values are legal, this method is not + /// failable. + #[must_use] + pub fn new_gre_key(id: u32) -> Self { + Self(id) + } + + /// Create a new `EncKeyId` and confirm that it is within the valid range + /// of gtp tunnel key values. + /// + /// # Errors + /// + /// Returns an error if the ID is zero. + pub fn new_gtp_key(id: u32) -> Result { + if id == 0 { + return Err(DecodeError::from( + "zero is not a legal GTP tunnel key", + )); + } + Ok(Self(id)) + } + + /// Create a new `EncKeyId` and confirm that it is within the valid range + /// of N bit values. + /// + /// # Errors + /// + /// Returns an error if the ID is greater than or equal to 2^N. + const fn new_nbit(id: u32) -> Result { + if id >= (1 << N) { + return Err(KeyTooLarge); + }; + Ok(Self(id)) + } +} + +impl From for u32 { + fn from(id: EncKeyId) -> u32 { + id.0 + } +} + +impl AsRef for EncKeyId { + fn as_ref(&self) -> &u32 { + &self.0 + } +} + +impl From for EncKeyId { + /// Convert `u32` to an `EncKeyId`. + /// + /// # Safety + /// + /// This conversion is infallible but may produce a semantically invalid key + /// depending on the tunnel type. + /// + /// If you know the tunnel type (e.g., vxlan) and wish to confirm that the + /// ID is within the valid range of values for that tunnel type, use the + /// corresponding "new" method on the `EncKeyId` type (e.g., + /// `EncKeyId::new_vxlan_vni`). + fn from(id: u32) -> Self { + Self(id) + } +} + +#[derive(Debug)] +#[must_use] +struct KeyTooLarge; + +impl From for EncKeyId { + fn from(vni: crate::net::vxlan::Vni) -> Self { + Self(vni.into()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 16a808bf..57a4014e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,10 +21,14 @@ mod address_family_linux; #[cfg(any(target_os = "linux", target_os = "fuchsia"))] pub use self::address_family_linux::AddressFamily; +mod enc; +pub use self::enc::*; + #[cfg(target_os = "freebsd")] mod address_family_freebsd; #[cfg(target_os = "freebsd")] pub use self::address_family_freebsd::AddressFamily; +pub mod net; #[cfg(not(any( target_os = "linux", @@ -32,6 +36,7 @@ pub use self::address_family_freebsd::AddressFamily; target_os = "freebsd", )))] mod address_family_fallback; + #[cfg(not(any( target_os = "linux", target_os = "fuchsia", diff --git a/src/message.rs b/src/message.rs index d1b20809..ce6cd7f4 100644 --- a/src/message.rs +++ b/src/message.rs @@ -8,7 +8,9 @@ use netlink_packet_utils::{ DecodeError, Emitable, Parseable, ParseableParametrized, }; -use crate::tc::{TcActionMessage, TcActionMessageBuffer}; +use crate::tc::{ + TcActionMessage, TcActionMessageBuffer, TcMessage, TcMessageBuffer, +}; use crate::{ address::{AddressHeader, AddressMessage, AddressMessageBuffer}, link::{LinkMessage, LinkMessageBuffer}, @@ -18,7 +20,6 @@ use crate::{ prefix::{PrefixMessage, PrefixMessageBuffer}, route::{RouteHeader, RouteMessage, RouteMessageBuffer}, rule::{RuleMessage, RuleMessageBuffer}, - tc::{TcMessage, TcMessageBuffer}, }; const RTM_NEWLINK: u16 = 16; diff --git a/src/net/arp.rs b/src/net/arp.rs new file mode 100644 index 00000000..14a6d9bb --- /dev/null +++ b/src/net/arp.rs @@ -0,0 +1,138 @@ +const RESERVED: u8 = 0; +const REQUEST: u8 = 1; +const REPLY: u8 = 2; +const REQUEST_REVERSE: u8 = 3; +const REPLY_REVERSE: u8 = 4; +const DRARP_REQUEST: u8 = 5; +const DRARP_REPLY: u8 = 6; +const DRARP_ERROR: u8 = 7; +const IN_ARP_REQUEST: u8 = 8; +const IN_ARP_REPLY: u8 = 9; +const ARP_NAK: u8 = 10; +const MARS_REQUEST: u8 = 11; +const MARS_MULTI: u8 = 12; +const MARS_MSERV: u8 = 13; +const MARS_JOIN: u8 = 14; +const MARS_LEAVE: u8 = 15; +const MARS_NAK: u8 = 16; +const MARS_UNSERV: u8 = 17; +const MARS_SJOIN: u8 = 18; +const MARS_SLEAVE: u8 = 19; +const MARS_GROUP_LIST_REQUEST: u8 = 20; +const MARS_GROUP_LIST_REPLY: u8 = 21; +const MARS_REDIRECT_MAP: u8 = 22; +const MAPO_SUNARP: u8 = 23; +const OP_EXP1: u8 = 24; +const OP_EXP2: u8 = 25; + +/// Enum of ARP operation codes. +/// +/// List from [iana.org][1] +/// +/// [1]: https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml +#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Hash)] +#[non_exhaustive] +#[repr(u8)] +pub enum Operation { + Reserved = RESERVED, + Request = REQUEST, + Reply = REPLY, + RequestReverse = REQUEST_REVERSE, + ReplyReverse = REPLY_REVERSE, + DrarpRequest = DRARP_REQUEST, + DrarpReply = DRARP_REPLY, + DrarpError = DRARP_ERROR, + InArpRequest = IN_ARP_REQUEST, + InArpReply = IN_ARP_REPLY, + ArpNak = ARP_NAK, + MarsRequest = MARS_REQUEST, + MarsMulti = MARS_MULTI, + MarsMServ = MARS_MSERV, + MarsJoin = MARS_JOIN, + MarsLeave = MARS_LEAVE, + MarsNAK = MARS_NAK, + MarsUnserv = MARS_UNSERV, + MarsSJoin = MARS_SJOIN, + MarsSLeave = MARS_SLEAVE, + MarsGroupListRequest = MARS_GROUP_LIST_REQUEST, + MarsGroupListReply = MARS_GROUP_LIST_REPLY, + MarsRedirectMap = MARS_REDIRECT_MAP, + MapoSUnarp = MAPO_SUNARP, + OpExp1 = OP_EXP1, + OpExp2 = OP_EXP2, + Other(u8), +} + +impl AsRef for Operation { + fn as_ref(&self) -> &u8 { + match self { + Operation::Reserved => &RESERVED, + Operation::Request => &REQUEST, + Operation::Reply => &REPLY, + Operation::RequestReverse => &REQUEST_REVERSE, + Operation::ReplyReverse => &REPLY_REVERSE, + Operation::DrarpRequest => &DRARP_REQUEST, + Operation::DrarpReply => &DRARP_REPLY, + Operation::DrarpError => &DRARP_ERROR, + Operation::InArpRequest => &IN_ARP_REQUEST, + Operation::InArpReply => &IN_ARP_REPLY, + Operation::ArpNak => &ARP_NAK, + Operation::MarsRequest => &MARS_REQUEST, + Operation::MarsMulti => &MARS_MULTI, + Operation::MarsMServ => &MARS_MSERV, + Operation::MarsJoin => &MARS_JOIN, + Operation::MarsLeave => &MARS_LEAVE, + Operation::MarsNAK => &MARS_NAK, + Operation::MarsUnserv => &MARS_UNSERV, + Operation::MarsSJoin => &MARS_SJOIN, + Operation::MarsSLeave => &MARS_SLEAVE, + Operation::MarsGroupListRequest => &MARS_GROUP_LIST_REQUEST, + Operation::MarsGroupListReply => &MARS_GROUP_LIST_REPLY, + Operation::MarsRedirectMap => &MARS_REDIRECT_MAP, + Operation::MapoSUnarp => &MAPO_SUNARP, + Operation::OpExp1 => &OP_EXP1, + Operation::OpExp2 => &OP_EXP2, + Operation::Other(x) => x, + } + } +} + +impl From for Operation { + fn from(value: u8) -> Self { + match value { + RESERVED => Operation::Reserved, + REQUEST => Operation::Request, + REPLY => Operation::Reply, + REQUEST_REVERSE => Operation::RequestReverse, + REPLY_REVERSE => Operation::ReplyReverse, + DRARP_REQUEST => Operation::DrarpRequest, + DRARP_REPLY => Operation::DrarpReply, + DRARP_ERROR => Operation::DrarpError, + IN_ARP_REQUEST => Operation::InArpRequest, + IN_ARP_REPLY => Operation::InArpReply, + ARP_NAK => Operation::ArpNak, + MARS_REQUEST => Operation::MarsRequest, + MARS_MULTI => Operation::MarsMulti, + MARS_MSERV => Operation::MarsMServ, + MARS_JOIN => Operation::MarsJoin, + MARS_LEAVE => Operation::MarsLeave, + MARS_NAK => Operation::MarsNAK, + MARS_UNSERV => Operation::MarsUnserv, + MARS_SJOIN => Operation::MarsSJoin, + MARS_SLEAVE => Operation::MarsSLeave, + MARS_GROUP_LIST_REQUEST => Operation::MarsGroupListRequest, + MARS_GROUP_LIST_REPLY => Operation::MarsGroupListReply, + MARS_REDIRECT_MAP => Operation::MarsRedirectMap, + MAPO_SUNARP => Operation::MapoSUnarp, + OP_EXP1 => Operation::OpExp1, + OP_EXP2 => Operation::OpExp2, + x => Operation::Other(x), + } + } +} + +impl From for u8 { + fn from(value: Operation) -> Self { + *value.as_ref() + } +} diff --git a/src/net/ethernet.rs b/src/net/ethernet.rs new file mode 100644 index 00000000..8c29a082 --- /dev/null +++ b/src/net/ethernet.rs @@ -0,0 +1,408 @@ +use netlink_packet_utils::DecodeError; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Mac([u8; 6]); +pub type MacMask = Mac; + +impl AsRef<[u8; 6]> for Mac { + fn as_ref(&self) -> &[u8; 6] { + &self.0 + } +} + +impl From<[u8; 6]> for Mac { + fn from(val: [u8; 6]) -> Self { + Self(val) + } +} + +impl From for [u8; 6] { + fn from(val: Mac) -> Self { + val.0 + } +} + +const ETH_TYPE_IPV4: u16 = 0x0800; +const ETH_TYPE_ARP: u16 = 0x0806; +const ETH_TYPE_WAKE_ON_LAN: u16 = 0x0842; +const ETH_TYPE_STREAM_RESERVATION_PROTOCOL: u16 = 0x22EA; +const ETH_TYPE_AUDIO_VIDEO_TRANSPORT_PROTOCOL: u16 = 0x22F0; +const ETH_TYPE_IETF_TRILL_PROTOCOL: u16 = 0x22F3; +const ETH_TYPE_REVERSE_ARP: u16 = 0x8035; +const ETH_TYPE_APPLE_TALK: u16 = 0x809B; +const ETH_TYPE_APPLE_TALK_ADDRESS_RESOLUTION_PROTOCOL: u16 = 0x80F3; +const ETH_TYPE_VLAN: u16 = 0x8100; +const ETH_TYPE_SIMPLE_LOOP_PREVENTION_PROTOCOL: u16 = 0x8102; +const ETH_TYPE_VIRTUAL_LINK_AGGREGATION_CONTROL_PROTOCOL: u16 = 0x8103; +const ETH_TYPE_IPX: u16 = 0x8137; +const ETH_TYPE_QNX_QNET: u16 = 0x8204; +const ETH_TYPE_IPV6: u16 = 0x86DD; +const ETH_TYPE_ETHERNET_FLOW_CONTROL: u16 = 0x8808; +const ETH_TYPE_ETHERNET_SLOW_PROTOCOLS: u16 = 0x8809; +const ETH_TYPE_COBRA_NET: u16 = 0x8819; +const ETH_TYPE_MPLS_UNICAST: u16 = 0x8847; +const ETH_TYPE_MPLS_MULTICAST: u16 = 0x8848; +const ETH_TYPE_PPPOE_DISCOVERY: u16 = 0x8863; +const ETH_TYPE_PPPOE: u16 = 0x8864; +const ETH_TYPE_EAP_OVER_LAN: u16 = 0x888E; +const ETH_TYPE_PROFINET: u16 = 0x8892; +const ETH_TYPE_HYPER_SCSI: u16 = 0x889A; +const ETH_TYPE_ATA_OVER_ETHERNET: u16 = 0x88A2; +const ETH_TYPE_ETHER_CAT_PROTOCOL: u16 = 0x88A4; +const ETH_TYPE_QINQ: u16 = 0x88A8; +const ETH_TYPE_GOOSE: u16 = 0x88B8; +const ETH_TYPE_GSE_MANAGEMENT_SERVICES: u16 = 0x88B9; +const ETH_TYPE_SVSAMPLED_VALUE_TRANSMISSION: u16 = 0x88BA; +const ETH_TYPE_MIKRO_TIK_ROMON: u16 = 0x88BF; +const ETH_TYPE_LINK_LAYER_DISCOVERY_PROTOCOL: u16 = 0x88CC; +const ETH_TYPE_SERCOS_III: u16 = 0x88CD; +const ETH_TYPE_HOME_PLUG_GREEN_PHY: u16 = 0x88E1; +const ETH_TYPE_MEDIA_REDUNDANCY_PROTOCOL: u16 = 0x88E3; +const ETH_TYPE_MAC_SEC: u16 = 0x88E5; +const ETH_TYPE_PROVIDER_BACKBONE_BRIDGES: u16 = 0x88E7; +const ETH_TYPE_PTP: u16 = 0x88F7; +const ETH_TYPE_NC_SI: u16 = 0x88F8; +const ETH_TYPE_PARALLEL_REDUNDANCY_PROTOCOL: u16 = 0x88FB; +const ETH_TYPE_CFM: u16 = 0x8902; +const ETH_TYPE_FCOE: u16 = 0x8906; +const ETH_TYPE_FCOE_INITIALIZATION: u16 = 0x8914; +const ETH_TYPE_RO_CE: u16 = 0x8915; +const ETH_TYPE_TT_ETHERNET_PROTOCOL_CONTROL_FRAME: u16 = 0x891D; +const ETH_TYPE_HSR: u16 = 0x892F; +const ETH_TYPE_ETHERNET_CONFIGURATION_TESTING: u16 = 0x9000; +const ETH_TYPE_REDUNDANCY_TAG: u16 = 0xF1C1; + +/// Ethernet Type (Ethertype) +/// +/// Enum of Ethertypes found in ethernet frame headers. +/// The list is not exhaustive or authoritative and includes only the most +/// common Ethertypes. +/// The list is based on the [Ethernet Type Wikipedia page][1]. +/// +/// [1]: https://en.wikipedia.org/wiki/EtherType +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +#[repr(u16)] +#[non_exhaustive] +pub enum Ethertype { + IPv4 = ETH_TYPE_IPV4, + Arp = ETH_TYPE_ARP, + WakeOnLan = ETH_TYPE_WAKE_ON_LAN, + StreamReservationProtocol = ETH_TYPE_STREAM_RESERVATION_PROTOCOL, + AudioVideoTransportProtocol = ETH_TYPE_AUDIO_VIDEO_TRANSPORT_PROTOCOL, + Trill = ETH_TYPE_IETF_TRILL_PROTOCOL, + ReverseArp = ETH_TYPE_REVERSE_ARP, + AppleTalk = ETH_TYPE_APPLE_TALK, + AppleTalkAddressResolutionProtocol = + ETH_TYPE_APPLE_TALK_ADDRESS_RESOLUTION_PROTOCOL, + Vlan = ETH_TYPE_VLAN, + SimpleLoopPreventionProtocol = ETH_TYPE_SIMPLE_LOOP_PREVENTION_PROTOCOL, + VirtualLinkAggregationControlProtocol = + ETH_TYPE_VIRTUAL_LINK_AGGREGATION_CONTROL_PROTOCOL, + Ipx = ETH_TYPE_IPX, + QnxQnet = ETH_TYPE_QNX_QNET, + IPv6 = ETH_TYPE_IPV6, + EthernetFlowControl = ETH_TYPE_ETHERNET_FLOW_CONTROL, + EthernetSlowProtocols = ETH_TYPE_ETHERNET_SLOW_PROTOCOLS, + CobraNet = ETH_TYPE_COBRA_NET, + MplsUnicast = ETH_TYPE_MPLS_UNICAST, + MplsMulticast = ETH_TYPE_MPLS_MULTICAST, + PPPoEDiscovery = ETH_TYPE_PPPOE_DISCOVERY, + PPPoE = ETH_TYPE_PPPOE, + EapOverLan = ETH_TYPE_EAP_OVER_LAN, + Profinet = ETH_TYPE_PROFINET, + HyperScsi = ETH_TYPE_HYPER_SCSI, + AtaOverEthernet = ETH_TYPE_ATA_OVER_ETHERNET, + EtherCatProtocol = ETH_TYPE_ETHER_CAT_PROTOCOL, + Qinq = ETH_TYPE_QINQ, + Goose = ETH_TYPE_GOOSE, + GseManagementServices = ETH_TYPE_GSE_MANAGEMENT_SERVICES, + SvsampledValueTransmission = ETH_TYPE_SVSAMPLED_VALUE_TRANSMISSION, + MikroTikRoMon = ETH_TYPE_MIKRO_TIK_ROMON, + LinkLayerDiscoveryProtocol = ETH_TYPE_LINK_LAYER_DISCOVERY_PROTOCOL, + SercosIII = ETH_TYPE_SERCOS_III, + HomePlugGreenPhy = ETH_TYPE_HOME_PLUG_GREEN_PHY, + MediaRedundancyProtocol = ETH_TYPE_MEDIA_REDUNDANCY_PROTOCOL, + MACsec = ETH_TYPE_MAC_SEC, + ProviderBackboneBridges = ETH_TYPE_PROVIDER_BACKBONE_BRIDGES, + Ptp = ETH_TYPE_PTP, + NcSi = ETH_TYPE_NC_SI, + ParallelRedundancyProtocol = ETH_TYPE_PARALLEL_REDUNDANCY_PROTOCOL, + Cfm = ETH_TYPE_CFM, + FCoE = ETH_TYPE_FCOE, + FCoEInitialization = ETH_TYPE_FCOE_INITIALIZATION, + RoCE = ETH_TYPE_RO_CE, + TtEthernetProtocolControlFrame = + ETH_TYPE_TT_ETHERNET_PROTOCOL_CONTROL_FRAME, + Hsr = ETH_TYPE_HSR, + EthernetConfigurationTesting = ETH_TYPE_ETHERNET_CONFIGURATION_TESTING, + RedundancyTag = ETH_TYPE_REDUNDANCY_TAG, + Other(u16), +} + +impl Ethertype { + /// Returns the value as big-endian bytes. + #[must_use] + pub fn as_be_bytes(&self) -> [u8; 2] { + self.as_ref().to_be_bytes() + } +} + +impl AsRef for Ethertype { + fn as_ref(&self) -> &u16 { + match self { + Ethertype::IPv4 => Ð_TYPE_IPV4, + Ethertype::Arp => Ð_TYPE_ARP, + Ethertype::WakeOnLan => Ð_TYPE_WAKE_ON_LAN, + Ethertype::StreamReservationProtocol => { + Ð_TYPE_STREAM_RESERVATION_PROTOCOL + } + Ethertype::AudioVideoTransportProtocol => { + Ð_TYPE_AUDIO_VIDEO_TRANSPORT_PROTOCOL + } + Ethertype::Trill => Ð_TYPE_IETF_TRILL_PROTOCOL, + Ethertype::ReverseArp => Ð_TYPE_REVERSE_ARP, + Ethertype::AppleTalk => Ð_TYPE_APPLE_TALK, + Ethertype::AppleTalkAddressResolutionProtocol => { + Ð_TYPE_APPLE_TALK_ADDRESS_RESOLUTION_PROTOCOL + } + Ethertype::Vlan => Ð_TYPE_VLAN, + Ethertype::SimpleLoopPreventionProtocol => { + Ð_TYPE_SIMPLE_LOOP_PREVENTION_PROTOCOL + } + Ethertype::VirtualLinkAggregationControlProtocol => { + Ð_TYPE_VIRTUAL_LINK_AGGREGATION_CONTROL_PROTOCOL + } + Ethertype::Ipx => Ð_TYPE_IPX, + Ethertype::QnxQnet => Ð_TYPE_QNX_QNET, + Ethertype::IPv6 => Ð_TYPE_IPV6, + Ethertype::EthernetFlowControl => Ð_TYPE_ETHERNET_FLOW_CONTROL, + Ethertype::EthernetSlowProtocols => { + Ð_TYPE_ETHERNET_SLOW_PROTOCOLS + } + Ethertype::CobraNet => Ð_TYPE_COBRA_NET, + Ethertype::MplsUnicast => Ð_TYPE_MPLS_UNICAST, + Ethertype::MplsMulticast => Ð_TYPE_MPLS_MULTICAST, + Ethertype::PPPoEDiscovery => Ð_TYPE_PPPOE_DISCOVERY, + Ethertype::PPPoE => Ð_TYPE_PPPOE, + Ethertype::EapOverLan => Ð_TYPE_EAP_OVER_LAN, + Ethertype::Profinet => Ð_TYPE_PROFINET, + Ethertype::HyperScsi => Ð_TYPE_HYPER_SCSI, + Ethertype::AtaOverEthernet => Ð_TYPE_ATA_OVER_ETHERNET, + Ethertype::EtherCatProtocol => Ð_TYPE_ETHER_CAT_PROTOCOL, + Ethertype::Qinq => Ð_TYPE_QINQ, + Ethertype::Goose => Ð_TYPE_GOOSE, + Ethertype::GseManagementServices => { + Ð_TYPE_GSE_MANAGEMENT_SERVICES + } + Ethertype::SvsampledValueTransmission => { + Ð_TYPE_SVSAMPLED_VALUE_TRANSMISSION + } + Ethertype::MikroTikRoMon => Ð_TYPE_MIKRO_TIK_ROMON, + Ethertype::LinkLayerDiscoveryProtocol => { + Ð_TYPE_LINK_LAYER_DISCOVERY_PROTOCOL + } + Ethertype::SercosIII => Ð_TYPE_SERCOS_III, + Ethertype::HomePlugGreenPhy => Ð_TYPE_HOME_PLUG_GREEN_PHY, + Ethertype::MediaRedundancyProtocol => { + Ð_TYPE_MEDIA_REDUNDANCY_PROTOCOL + } + Ethertype::MACsec => Ð_TYPE_MAC_SEC, + Ethertype::ProviderBackboneBridges => { + Ð_TYPE_PROVIDER_BACKBONE_BRIDGES + } + Ethertype::Ptp => Ð_TYPE_PTP, + Ethertype::NcSi => Ð_TYPE_NC_SI, + Ethertype::ParallelRedundancyProtocol => { + Ð_TYPE_PARALLEL_REDUNDANCY_PROTOCOL + } + Ethertype::Cfm => Ð_TYPE_CFM, + Ethertype::FCoE => Ð_TYPE_FCOE, + Ethertype::FCoEInitialization => Ð_TYPE_FCOE_INITIALIZATION, + Ethertype::RoCE => Ð_TYPE_RO_CE, + Ethertype::TtEthernetProtocolControlFrame => { + Ð_TYPE_TT_ETHERNET_PROTOCOL_CONTROL_FRAME + } + Ethertype::Hsr => Ð_TYPE_HSR, + Ethertype::EthernetConfigurationTesting => { + Ð_TYPE_ETHERNET_CONFIGURATION_TESTING + } + Ethertype::RedundancyTag => Ð_TYPE_REDUNDANCY_TAG, + Ethertype::Other(other) => other, + } + } +} + +impl From for Ethertype { + fn from(val: u16) -> Self { + match val { + ETH_TYPE_IPV4 => Ethertype::IPv4, + ETH_TYPE_ARP => Ethertype::Arp, + ETH_TYPE_WAKE_ON_LAN => Ethertype::WakeOnLan, + ETH_TYPE_STREAM_RESERVATION_PROTOCOL => { + Ethertype::StreamReservationProtocol + } + ETH_TYPE_AUDIO_VIDEO_TRANSPORT_PROTOCOL => { + Ethertype::AudioVideoTransportProtocol + } + ETH_TYPE_IETF_TRILL_PROTOCOL => Ethertype::Trill, + ETH_TYPE_REVERSE_ARP => Ethertype::ReverseArp, + ETH_TYPE_APPLE_TALK => Ethertype::AppleTalk, + ETH_TYPE_APPLE_TALK_ADDRESS_RESOLUTION_PROTOCOL => { + Ethertype::AppleTalkAddressResolutionProtocol + } + ETH_TYPE_VLAN => Ethertype::Vlan, + ETH_TYPE_SIMPLE_LOOP_PREVENTION_PROTOCOL => { + Ethertype::SimpleLoopPreventionProtocol + } + ETH_TYPE_VIRTUAL_LINK_AGGREGATION_CONTROL_PROTOCOL => { + Ethertype::VirtualLinkAggregationControlProtocol + } + ETH_TYPE_IPX => Ethertype::Ipx, + ETH_TYPE_QNX_QNET => Ethertype::QnxQnet, + ETH_TYPE_IPV6 => Ethertype::IPv6, + ETH_TYPE_ETHERNET_FLOW_CONTROL => Ethertype::EthernetFlowControl, + ETH_TYPE_ETHERNET_SLOW_PROTOCOLS => { + Ethertype::EthernetSlowProtocols + } + ETH_TYPE_COBRA_NET => Ethertype::CobraNet, + ETH_TYPE_MPLS_UNICAST => Ethertype::MplsUnicast, + ETH_TYPE_MPLS_MULTICAST => Ethertype::MplsMulticast, + ETH_TYPE_PPPOE_DISCOVERY => Ethertype::PPPoEDiscovery, + ETH_TYPE_PPPOE => Ethertype::PPPoE, + ETH_TYPE_EAP_OVER_LAN => Ethertype::EapOverLan, + ETH_TYPE_PROFINET => Ethertype::Profinet, + ETH_TYPE_HYPER_SCSI => Ethertype::HyperScsi, + ETH_TYPE_ATA_OVER_ETHERNET => Ethertype::AtaOverEthernet, + ETH_TYPE_ETHER_CAT_PROTOCOL => Ethertype::EtherCatProtocol, + ETH_TYPE_QINQ => Ethertype::Qinq, + ETH_TYPE_GOOSE => Ethertype::Goose, + ETH_TYPE_GSE_MANAGEMENT_SERVICES => { + Ethertype::GseManagementServices + } + ETH_TYPE_SVSAMPLED_VALUE_TRANSMISSION => { + Ethertype::SvsampledValueTransmission + } + ETH_TYPE_MIKRO_TIK_ROMON => Ethertype::MikroTikRoMon, + ETH_TYPE_LINK_LAYER_DISCOVERY_PROTOCOL => { + Ethertype::LinkLayerDiscoveryProtocol + } + ETH_TYPE_SERCOS_III => Ethertype::SercosIII, + ETH_TYPE_HOME_PLUG_GREEN_PHY => Ethertype::HomePlugGreenPhy, + ETH_TYPE_MEDIA_REDUNDANCY_PROTOCOL => { + Ethertype::MediaRedundancyProtocol + } + ETH_TYPE_MAC_SEC => Ethertype::MACsec, + ETH_TYPE_PROVIDER_BACKBONE_BRIDGES => { + Ethertype::ProviderBackboneBridges + } + ETH_TYPE_PTP => Ethertype::Ptp, + ETH_TYPE_NC_SI => Ethertype::NcSi, + ETH_TYPE_PARALLEL_REDUNDANCY_PROTOCOL => { + Ethertype::ParallelRedundancyProtocol + } + ETH_TYPE_CFM => Ethertype::Cfm, + ETH_TYPE_FCOE => Ethertype::FCoE, + ETH_TYPE_FCOE_INITIALIZATION => Ethertype::FCoEInitialization, + ETH_TYPE_RO_CE => Ethertype::RoCE, + ETH_TYPE_TT_ETHERNET_PROTOCOL_CONTROL_FRAME => { + Ethertype::TtEthernetProtocolControlFrame + } + ETH_TYPE_HSR => Ethertype::Hsr, + ETH_TYPE_ETHERNET_CONFIGURATION_TESTING => { + Ethertype::EthernetConfigurationTesting + } + ETH_TYPE_REDUNDANCY_TAG => Ethertype::RedundancyTag, + _ => Self::Other(val), + } + } +} + +impl From for u16 { + fn from(val: Ethertype) -> Self { + *val.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct VlanId(u16); + +impl VlanId { + /// Creates a new `VlanId` value. + /// + /// # Errors + /// + /// Returns an error if the ID is greater than or equal to 4096. + pub fn new(id: u16) -> Result { + if id >= 4096 { + return Err(DecodeError::from("VLAN ID must be less than 4096")); + } + Ok(Self(id)) + } +} + +impl TryFrom for VlanId { + type Error = DecodeError; + + fn try_from(id: u16) -> Result { + Self::new(id) + } +} + +impl AsRef for VlanId { + fn as_ref(&self) -> &u16 { + &self.0 + } +} + +impl From for u16 { + fn from(val: VlanId) -> Self { + val.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct VlanPrio(u8); + +impl VlanPrio { + /// Creates a new `VlanPrio` value. + /// + /// # Errors + /// + /// Returns an error if the priority is greater than 7. + pub fn new(prio: u8) -> Result { + if prio > Self::HIGHEST.into() { + return Err(DecodeError::from("VLAN priority must be less than 8")); + } + Ok(Self(prio)) + } + + const BEST_EFFORT: Self = Self(0); + const HIGHEST: Self = Self(7); +} + +impl TryFrom for VlanPrio { + type Error = DecodeError; + + fn try_from(prio: u8) -> Result { + Self::new(prio) + } +} + +impl AsRef for VlanPrio { + fn as_ref(&self) -> &u8 { + &self.0 + } +} + +impl From for u8 { + fn from(val: VlanPrio) -> Self { + val.0 + } +} + +impl Default for VlanPrio { + fn default() -> Self { + Self::BEST_EFFORT + } +} diff --git a/src/net/icmpv4.rs b/src/net/icmpv4.rs new file mode 100644 index 00000000..ca781a6e --- /dev/null +++ b/src/net/icmpv4.rs @@ -0,0 +1,1266 @@ +const ECHO_REPLY: u8 = 0; +const DESTINATION_UNREACHABLE: u8 = 3; +const SOURCE_QUENCH: u8 = 4; // deprecated by iana +const REDIRECT: u8 = 5; +const ALTERNATE_HOST_ADDRESS: u8 = 6; // deprecated by iana +const ECHO_REQUEST: u8 = 8; +const ROUTER_ADVERTISEMENT: u8 = 9; +const ROUTER_SOLICITATION: u8 = 10; +const TIME_EXCEEDED: u8 = 11; +const PARAMETER_PROBLEM: u8 = 12; +const TIMESTAMP_REQUEST: u8 = 13; +const TIMESTAMP_REPLY: u8 = 14; +const INFORMATION_REQUEST: u8 = 15; // deprecated by iana +const INFORMATION_REPLY: u8 = 16; // deprecated by iana +const ADDRESS_MASK_REQUEST: u8 = 17; // deprecated by iana +const ADDRESS_MASK_REPLY: u8 = 18; // deprecated by iana +const TRACEROUTE: u8 = 30; // deprecated by iana +const DATAGRAM_CONVERSION_ERROR: u8 = 31; // deprecated by iana +const MOBILE_HOST_REDIRECT: u8 = 32; // deprecated by iana +const IPV6_WHERE_ARE_YOU: u8 = 33; // deprecated by iana +const IPV6_IAM_HERE: u8 = 34; // deprecated by iana +const MOBILE_REGISTRATION_REQUEST: u8 = 35; // deprecated by iana +const MOBILE_REGISTRATION_REPLY: u8 = 36; // deprecated by iana +const DOMAIN_NAME_REQUEST: u8 = 37; // deprecated by iana +const DOMAIN_NAME_REPLY: u8 = 38; // deprecated by iana +const SKIP: u8 = 39; // deprecated by iana +const PHOTURIS: u8 = 40; +const EXTENDED_ECHO_REQUEST: u8 = 42; +const EXTENDED_ECHO_REPLY: u8 = 43; + +/// Enum of `ICMPv4` message types. +/// +/// This enum is non-exhaustive as more `Type`s may be added in the future by +/// the IANA. +/// +/// Codes sourced from [iana.org][1] +/// +/// [1]: https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Type { + EchoReply = ECHO_REPLY, + DestinationUnreachable = DESTINATION_UNREACHABLE, + SourceQuench = SOURCE_QUENCH, // deprecated by iana + Redirect = REDIRECT, + AlternateHostAddress = ALTERNATE_HOST_ADDRESS, // deprecated by iana + EchoRequest = ECHO_REQUEST, + RouterAdvertisement = ROUTER_ADVERTISEMENT, + RouterSolicitation = ROUTER_SOLICITATION, + TimeExceeded = TIME_EXCEEDED, + ParameterProblem = PARAMETER_PROBLEM, + TimestampRequest = TIMESTAMP_REQUEST, + TimestampReply = TIMESTAMP_REPLY, + InformationRequest = INFORMATION_REQUEST, // deprecated by iana + InformationReply = INFORMATION_REPLY, // deprecated by iana + AddressMaskRequest = ADDRESS_MASK_REQUEST, // deprecated by iana + AddressMaskReply = ADDRESS_MASK_REPLY, // deprecated by iana + Traceroute = TRACEROUTE, // deprecated by iana + DatagramConversionError = DATAGRAM_CONVERSION_ERROR, // deprecated by iana + MobileHostRedirect = MOBILE_HOST_REDIRECT, // deprecated by iana + Ipv6WhereAreYou = IPV6_WHERE_ARE_YOU, // deprecated by iana + Ipv6IAmHere = IPV6_IAM_HERE, // deprecated by iana + MobileRegistrationRequest = MOBILE_REGISTRATION_REQUEST, /* deprecated + * by iana */ + MobileRegistrationReply = MOBILE_REGISTRATION_REPLY, // deprecated by iana + DomainNameRequest = DOMAIN_NAME_REQUEST, // deprecated by iana + DomainNameReply = DOMAIN_NAME_REPLY, // deprecated by iana + Skip = SKIP, // deprecated by iana + Photuris = PHOTURIS, + ExtendedEchoRequest = EXTENDED_ECHO_REQUEST, + ExtendedEchoReply = EXTENDED_ECHO_REPLY, + Other(u8), +} + +impl AsRef for Type { + fn as_ref(&self) -> &u8 { + match self { + Type::EchoReply => &ECHO_REPLY, + Type::DestinationUnreachable => &DESTINATION_UNREACHABLE, + Type::SourceQuench => &SOURCE_QUENCH, + Type::Redirect => &REDIRECT, + Type::AlternateHostAddress => &ALTERNATE_HOST_ADDRESS, + Type::EchoRequest => &ECHO_REQUEST, + Type::RouterAdvertisement => &ROUTER_ADVERTISEMENT, + Type::RouterSolicitation => &ROUTER_SOLICITATION, + Type::TimeExceeded => &TIME_EXCEEDED, + Type::ParameterProblem => &PARAMETER_PROBLEM, + Type::TimestampRequest => &TIMESTAMP_REQUEST, + Type::TimestampReply => &TIMESTAMP_REPLY, + Type::InformationRequest => &INFORMATION_REQUEST, + Type::InformationReply => &INFORMATION_REPLY, + Type::AddressMaskRequest => &ADDRESS_MASK_REQUEST, + Type::AddressMaskReply => &ADDRESS_MASK_REPLY, + Type::Traceroute => &TRACEROUTE, + Type::DatagramConversionError => &DATAGRAM_CONVERSION_ERROR, + Type::MobileHostRedirect => &MOBILE_HOST_REDIRECT, + Type::Ipv6WhereAreYou => &IPV6_WHERE_ARE_YOU, + Type::Ipv6IAmHere => &IPV6_IAM_HERE, + Type::MobileRegistrationRequest => &MOBILE_REGISTRATION_REQUEST, + Type::MobileRegistrationReply => &MOBILE_REGISTRATION_REPLY, + Type::DomainNameRequest => &DOMAIN_NAME_REQUEST, + Type::DomainNameReply => &DOMAIN_NAME_REPLY, + Type::Skip => &SKIP, + Type::Photuris => &PHOTURIS, + Type::ExtendedEchoRequest => &EXTENDED_ECHO_REQUEST, + Type::ExtendedEchoReply => &EXTENDED_ECHO_REPLY, + Type::Other(x) => x, + } + } +} + +impl From for Type { + fn from(value: u8) -> Self { + match value { + ECHO_REPLY => Type::EchoReply, + DESTINATION_UNREACHABLE => Type::DestinationUnreachable, + SOURCE_QUENCH => Type::SourceQuench, + REDIRECT => Type::Redirect, + ALTERNATE_HOST_ADDRESS => Type::AlternateHostAddress, + ECHO_REQUEST => Type::EchoRequest, + ROUTER_ADVERTISEMENT => Type::RouterAdvertisement, + ROUTER_SOLICITATION => Type::RouterSolicitation, + TIME_EXCEEDED => Type::TimeExceeded, + PARAMETER_PROBLEM => Type::ParameterProblem, + TIMESTAMP_REQUEST => Type::TimestampRequest, + TIMESTAMP_REPLY => Type::TimestampReply, + INFORMATION_REQUEST => Type::InformationRequest, + INFORMATION_REPLY => Type::InformationReply, + ADDRESS_MASK_REQUEST => Type::AddressMaskRequest, + ADDRESS_MASK_REPLY => Type::AddressMaskReply, + TRACEROUTE => Type::Traceroute, + DATAGRAM_CONVERSION_ERROR => Type::DatagramConversionError, + MOBILE_HOST_REDIRECT => Type::MobileHostRedirect, + IPV6_WHERE_ARE_YOU => Type::Ipv6WhereAreYou, + IPV6_IAM_HERE => Type::Ipv6IAmHere, + MOBILE_REGISTRATION_REQUEST => Type::MobileRegistrationRequest, + MOBILE_REGISTRATION_REPLY => Type::MobileRegistrationReply, + DOMAIN_NAME_REQUEST => Type::DomainNameRequest, + DOMAIN_NAME_REPLY => Type::DomainNameReply, + SKIP => Type::Skip, + PHOTURIS => Type::Photuris, + EXTENDED_ECHO_REQUEST => Type::ExtendedEchoRequest, + EXTENDED_ECHO_REPLY => Type::ExtendedEchoReply, + x => Type::Other(x), + } + } +} + +const NO_CODE: u8 = 0; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum EchoReply { + NoCode = NO_CODE, + Other(u8), +} + +impl AsRef for EchoReply { + fn as_ref(&self) -> &u8 { + match self { + EchoReply::NoCode => &NO_CODE, + EchoReply::Other(x) => x, + } + } +} + +impl From for EchoReply { + fn from(value: u8) -> Self { + match value { + NO_CODE => EchoReply::NoCode, + x => EchoReply::Other(x), + } + } +} + +impl From for u8 { + fn from(value: EchoReply) -> u8 { + *value.as_ref() + } +} + +const NET_UNREACHABLE: u8 = 0; +const HOST_UNREACHABLE: u8 = 1; +const PROTOCOL_UNREACHABLE: u8 = 2; +const PORT_UNREACHABLE: u8 = 3; +const FRAGMENTATION_NEEDED_AND_DONT_FRAGMENT_WAS_SET: u8 = 4; +const SOURCE_ROUTE_FAILED: u8 = 5; +const DESTINATION_NETWORK_UNKNOWN: u8 = 6; +const DESTINATION_HOST_UNKNOWN: u8 = 7; +const SOURCE_HOST_ISOLATED: u8 = 8; +const COMMUNICATION_WITH_DESTINATION_NETWORK_IS_ADMINISTRATIVELY_PROHIBITED: + u8 = 9; +const COMMUNICATION_WITH_DESTINATION_HOST_IS_ADMINISTRATIVELY_PROHIBITED: u8 = + 10; +const DESTINATION_NETWORK_UNREACHABLE_FOR_TYPE_OF_SERVICE: u8 = 11; +const DESTINATION_HOST_UNREACHABLE_FOR_TYPE_OF_SERVICE: u8 = 12; +const COMMUNICATION_ADMINISTRATIVELY_PROHIBITED: u8 = 13; +const HOST_PRECEDENCE_VIOLATION: u8 = 14; +const PRECEDENCE_CUTOFF_IN_EFFECT: u8 = 15; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum DestinationUnreachable { + NetUnreachable = NET_UNREACHABLE, + HostUnreachable = HOST_UNREACHABLE, + ProtocolUnreachable = PROTOCOL_UNREACHABLE, + PortUnreachable = PORT_UNREACHABLE, + FragmentationNeededAndDontFragmentWasSet = + FRAGMENTATION_NEEDED_AND_DONT_FRAGMENT_WAS_SET, + SourceRouteFailed = SOURCE_ROUTE_FAILED, + DestinationNetworkUnknown = DESTINATION_NETWORK_UNKNOWN, + DestinationHostUnknown = DESTINATION_HOST_UNKNOWN, + SourceHostIsolated = SOURCE_HOST_ISOLATED, + CommunicationWithDestinationNetworkIsAdministrativelyProhibited = + COMMUNICATION_WITH_DESTINATION_NETWORK_IS_ADMINISTRATIVELY_PROHIBITED, + CommunicationWithDestinationHostIsAdministrativelyProhibited = + COMMUNICATION_WITH_DESTINATION_HOST_IS_ADMINISTRATIVELY_PROHIBITED, + DestinationNetworkUnreachableForTypeOfService = + DESTINATION_NETWORK_UNREACHABLE_FOR_TYPE_OF_SERVICE, + DestinationHostUnreachableForTypeOfService = + DESTINATION_HOST_UNREACHABLE_FOR_TYPE_OF_SERVICE, + CommunicationAdministrativelyProhibited = + COMMUNICATION_ADMINISTRATIVELY_PROHIBITED, + HostPrecedenceViolation = HOST_PRECEDENCE_VIOLATION, + PrecedenceCutoffInEffect = PRECEDENCE_CUTOFF_IN_EFFECT, + Other(u8), +} + +impl AsRef for DestinationUnreachable { + fn as_ref(&self) -> &u8 { + match self { + DestinationUnreachable::NetUnreachable => &NET_UNREACHABLE, + DestinationUnreachable::HostUnreachable => &HOST_UNREACHABLE, + DestinationUnreachable::ProtocolUnreachable => &PROTOCOL_UNREACHABLE, + DestinationUnreachable::PortUnreachable => &PORT_UNREACHABLE, + DestinationUnreachable::FragmentationNeededAndDontFragmentWasSet => &FRAGMENTATION_NEEDED_AND_DONT_FRAGMENT_WAS_SET, + DestinationUnreachable::SourceRouteFailed => &SOURCE_ROUTE_FAILED, + DestinationUnreachable::DestinationNetworkUnknown => &DESTINATION_NETWORK_UNKNOWN, + DestinationUnreachable::DestinationHostUnknown => &DESTINATION_HOST_UNKNOWN, + DestinationUnreachable::SourceHostIsolated => &SOURCE_HOST_ISOLATED, + DestinationUnreachable::CommunicationWithDestinationNetworkIsAdministrativelyProhibited => &COMMUNICATION_WITH_DESTINATION_NETWORK_IS_ADMINISTRATIVELY_PROHIBITED, + DestinationUnreachable::CommunicationWithDestinationHostIsAdministrativelyProhibited => &COMMUNICATION_WITH_DESTINATION_HOST_IS_ADMINISTRATIVELY_PROHIBITED, + DestinationUnreachable::DestinationNetworkUnreachableForTypeOfService => &DESTINATION_NETWORK_UNREACHABLE_FOR_TYPE_OF_SERVICE, + DestinationUnreachable::DestinationHostUnreachableForTypeOfService => &DESTINATION_HOST_UNREACHABLE_FOR_TYPE_OF_SERVICE, + DestinationUnreachable::CommunicationAdministrativelyProhibited => &COMMUNICATION_ADMINISTRATIVELY_PROHIBITED, + DestinationUnreachable::HostPrecedenceViolation => &HOST_PRECEDENCE_VIOLATION, + DestinationUnreachable::PrecedenceCutoffInEffect => &PRECEDENCE_CUTOFF_IN_EFFECT, + DestinationUnreachable::Other(x) => x, + } + } +} + +impl From for DestinationUnreachable { + fn from(value: u8) -> Self { + match value { + NET_UNREACHABLE => DestinationUnreachable::NetUnreachable, + HOST_UNREACHABLE => DestinationUnreachable::HostUnreachable, + PROTOCOL_UNREACHABLE => DestinationUnreachable::ProtocolUnreachable, + PORT_UNREACHABLE => DestinationUnreachable::PortUnreachable, + FRAGMENTATION_NEEDED_AND_DONT_FRAGMENT_WAS_SET => DestinationUnreachable::FragmentationNeededAndDontFragmentWasSet, + SOURCE_ROUTE_FAILED => DestinationUnreachable::SourceRouteFailed, + DESTINATION_NETWORK_UNKNOWN => DestinationUnreachable::DestinationNetworkUnknown, + DESTINATION_HOST_UNKNOWN => DestinationUnreachable::DestinationHostUnknown, + SOURCE_HOST_ISOLATED => DestinationUnreachable::SourceHostIsolated, + COMMUNICATION_WITH_DESTINATION_NETWORK_IS_ADMINISTRATIVELY_PROHIBITED => DestinationUnreachable::CommunicationWithDestinationNetworkIsAdministrativelyProhibited, + COMMUNICATION_WITH_DESTINATION_HOST_IS_ADMINISTRATIVELY_PROHIBITED => DestinationUnreachable::CommunicationWithDestinationHostIsAdministrativelyProhibited, + DESTINATION_NETWORK_UNREACHABLE_FOR_TYPE_OF_SERVICE => DestinationUnreachable::DestinationNetworkUnreachableForTypeOfService, + DESTINATION_HOST_UNREACHABLE_FOR_TYPE_OF_SERVICE => DestinationUnreachable::DestinationHostUnreachableForTypeOfService, + COMMUNICATION_ADMINISTRATIVELY_PROHIBITED => DestinationUnreachable::CommunicationAdministrativelyProhibited, + HOST_PRECEDENCE_VIOLATION => DestinationUnreachable::HostPrecedenceViolation, + PRECEDENCE_CUTOFF_IN_EFFECT => DestinationUnreachable::PrecedenceCutoffInEffect, + x => DestinationUnreachable::Other(x), + } + } +} + +impl From for u8 { + fn from(value: DestinationUnreachable) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum SourceQuench { + NoCode = 0, + Other(u8), +} + +impl AsRef for SourceQuench { + fn as_ref(&self) -> &u8 { + match self { + SourceQuench::NoCode => &0, + SourceQuench::Other(x) => x, + } + } +} + +impl From for SourceQuench { + fn from(value: u8) -> Self { + match value { + 0 => SourceQuench::NoCode, + x => SourceQuench::Other(x), + } + } +} + +impl From for u8 { + fn from(value: SourceQuench) -> u8 { + *value.as_ref() + } +} + +mod redirect { + pub(super) const NET: u8 = 0; + pub(super) const HOST: u8 = 1; + pub(super) const TO_SAND_NET: u8 = 2; + pub(super) const TO_SAND_HOST: u8 = 3; +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Redirect { + Net = redirect::NET, + Host = redirect::HOST, + ToSAndNet = redirect::TO_SAND_NET, + ToSAndHost = redirect::TO_SAND_HOST, + Other(u8), +} + +impl AsRef for Redirect { + fn as_ref(&self) -> &u8 { + match self { + Redirect::Net => &redirect::NET, + Redirect::Host => &redirect::HOST, + Redirect::ToSAndNet => &redirect::TO_SAND_NET, + Redirect::ToSAndHost => &redirect::TO_SAND_HOST, + Redirect::Other(x) => x, + } + } +} + +impl From for Redirect { + fn from(value: u8) -> Self { + match value { + redirect::NET => Redirect::Net, + redirect::HOST => Redirect::Host, + redirect::TO_SAND_NET => Redirect::ToSAndNet, + redirect::TO_SAND_HOST => Redirect::ToSAndHost, + x => Redirect::Other(x), + } + } +} + +impl From for u8 { + fn from(value: Redirect) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum AlternateHostAddress { + NoCode = 0, + Other(u8), +} + +impl AsRef for AlternateHostAddress { + fn as_ref(&self) -> &u8 { + match self { + AlternateHostAddress::NoCode => &0, + AlternateHostAddress::Other(x) => x, + } + } +} + +impl From for AlternateHostAddress { + fn from(value: u8) -> Self { + match value { + 0 => AlternateHostAddress::NoCode, + x => AlternateHostAddress::Other(x), + } + } +} + +impl From for u8 { + fn from(value: AlternateHostAddress) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum EchoRequest { + NoCode = 0, + Other(u8), +} + +impl AsRef for EchoRequest { + fn as_ref(&self) -> &u8 { + match self { + EchoRequest::NoCode => &0, + EchoRequest::Other(x) => x, + } + } +} + +impl From for EchoRequest { + fn from(value: u8) -> Self { + match value { + 0 => EchoRequest::NoCode, + x => EchoRequest::Other(x), + } + } +} + +impl From for u8 { + fn from(value: EchoRequest) -> u8 { + *value.as_ref() + } +} + +const DOES_NOT_ROUTE_COMMON_TRAFFIC: u8 = 16; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum RouterAdvertisement { + NoCode = 0, + DoesNotRouteCommonTraffic = DOES_NOT_ROUTE_COMMON_TRAFFIC, + Other(u8), +} + +impl AsRef for RouterAdvertisement { + fn as_ref(&self) -> &u8 { + match self { + RouterAdvertisement::NoCode => &0, + RouterAdvertisement::DoesNotRouteCommonTraffic => { + &DOES_NOT_ROUTE_COMMON_TRAFFIC + } + RouterAdvertisement::Other(x) => x, + } + } +} + +impl From for RouterAdvertisement { + fn from(value: u8) -> Self { + match value { + 0 => RouterAdvertisement::NoCode, + DOES_NOT_ROUTE_COMMON_TRAFFIC => { + RouterAdvertisement::DoesNotRouteCommonTraffic + } + x => RouterAdvertisement::Other(x), + } + } +} + +impl From for u8 { + fn from(value: RouterAdvertisement) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum RouterSolicitation { + NoCode = 0, + Other(u8), +} + +impl AsRef for RouterSolicitation { + fn as_ref(&self) -> &u8 { + match self { + RouterSolicitation::NoCode => &0, + RouterSolicitation::Other(x) => x, + } + } +} + +impl From for RouterSolicitation { + fn from(value: u8) -> Self { + match value { + 0 => RouterSolicitation::NoCode, + x => RouterSolicitation::Other(x), + } + } +} + +impl From for u8 { + fn from(value: RouterSolicitation) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum TimeExceeded { + TtlExceededInTransit = 0, + FragmentReassembly = 1, + Other(u8), +} + +impl AsRef for TimeExceeded { + fn as_ref(&self) -> &u8 { + match self { + TimeExceeded::TtlExceededInTransit => &0, + TimeExceeded::FragmentReassembly => &1, + TimeExceeded::Other(x) => x, + } + } +} + +impl From for TimeExceeded { + fn from(value: u8) -> Self { + match value { + 0 => TimeExceeded::TtlExceededInTransit, + 1 => TimeExceeded::FragmentReassembly, + x => TimeExceeded::Other(x), + } + } +} + +impl From for u8 { + fn from(value: TimeExceeded) -> u8 { + *value.as_ref() + } +} + +const POINTER_INDICATES_THE_ERROR: u8 = 0; +const MISSING_A_REQUIRED_OPTION: u8 = 1; +const BAD_LENGTH: u8 = 2; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum ParameterProblem { + PointerIndicatesTheError = POINTER_INDICATES_THE_ERROR, + MissingARequiredOption = MISSING_A_REQUIRED_OPTION, + BadLength = BAD_LENGTH, + Other(u8), +} + +impl AsRef for ParameterProblem { + fn as_ref(&self) -> &u8 { + match self { + ParameterProblem::PointerIndicatesTheError => { + &POINTER_INDICATES_THE_ERROR + } + ParameterProblem::MissingARequiredOption => { + &MISSING_A_REQUIRED_OPTION + } + ParameterProblem::BadLength => &BAD_LENGTH, + ParameterProblem::Other(x) => x, + } + } +} + +impl From for ParameterProblem { + fn from(value: u8) -> Self { + match value { + POINTER_INDICATES_THE_ERROR => { + ParameterProblem::PointerIndicatesTheError + } + MISSING_A_REQUIRED_OPTION => { + ParameterProblem::MissingARequiredOption + } + BAD_LENGTH => ParameterProblem::BadLength, + x => ParameterProblem::Other(x), + } + } +} + +impl From for u8 { + fn from(value: ParameterProblem) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum TimestampRequest { + NoCode = 0, + Other(u8), +} + +impl AsRef for TimestampRequest { + fn as_ref(&self) -> &u8 { + match self { + TimestampRequest::NoCode => &0, + TimestampRequest::Other(x) => x, + } + } +} + +impl From for TimestampRequest { + fn from(value: u8) -> Self { + match value { + 0 => TimestampRequest::NoCode, + x => TimestampRequest::Other(x), + } + } +} + +impl From for u8 { + fn from(value: TimestampRequest) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum TimestampReply { + NoCode = 0, + Other(u8), +} + +impl AsRef for TimestampReply { + fn as_ref(&self) -> &u8 { + match self { + TimestampReply::NoCode => &0, + TimestampReply::Other(x) => x, + } + } +} + +impl From for TimestampReply { + fn from(value: u8) -> Self { + match value { + 0 => TimestampReply::NoCode, + x => TimestampReply::Other(x), + } + } +} + +impl From for u8 { + fn from(value: TimestampReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum InformationRequest { + NoCode = 0, + Other(u8), +} + +impl AsRef for InformationRequest { + fn as_ref(&self) -> &u8 { + match self { + InformationRequest::NoCode => &0, + InformationRequest::Other(x) => x, + } + } +} + +impl From for InformationRequest { + fn from(value: u8) -> Self { + match value { + 0 => InformationRequest::NoCode, + x => InformationRequest::Other(x), + } + } +} + +impl From for u8 { + fn from(value: InformationRequest) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum InformationReply { + NoCode = 0, + Other(u8), +} + +impl AsRef for InformationReply { + fn as_ref(&self) -> &u8 { + match self { + InformationReply::NoCode => &0, + InformationReply::Other(x) => x, + } + } +} + +impl From for InformationReply { + fn from(value: u8) -> Self { + match value { + 0 => InformationReply::NoCode, + x => InformationReply::Other(x), + } + } +} + +impl From for u8 { + fn from(value: InformationReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum AddressMaskRequest { + NoCode = 0, + Other(u8), +} + +impl AsRef for AddressMaskRequest { + fn as_ref(&self) -> &u8 { + match self { + AddressMaskRequest::NoCode => &0, + AddressMaskRequest::Other(x) => x, + } + } +} + +impl From for AddressMaskRequest { + fn from(value: u8) -> Self { + match value { + 0 => AddressMaskRequest::NoCode, + x => AddressMaskRequest::Other(x), + } + } +} + +impl From for u8 { + fn from(value: AddressMaskRequest) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum AddressMaskReply { + NoCode = 0, + Other(u8), +} + +impl AsRef for AddressMaskReply { + fn as_ref(&self) -> &u8 { + match self { + AddressMaskReply::NoCode => &0, + AddressMaskReply::Other(x) => x, + } + } +} + +impl From for AddressMaskReply { + fn from(value: u8) -> Self { + match value { + 0 => AddressMaskReply::NoCode, + x => AddressMaskReply::Other(x), + } + } +} + +impl From for u8 { + fn from(value: AddressMaskReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Traceroute { + NoCode = 0, + Other(u8), +} + +impl AsRef for Traceroute { + fn as_ref(&self) -> &u8 { + match self { + Traceroute::NoCode => &0, + Traceroute::Other(x) => x, + } + } +} + +impl From for Traceroute { + fn from(value: u8) -> Self { + match value { + 0 => Traceroute::NoCode, + x => Traceroute::Other(x), + } + } +} + +impl From for u8 { + fn from(value: Traceroute) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum DatagramConversionError { + Other(u8), +} + +impl AsRef for DatagramConversionError { + fn as_ref(&self) -> &u8 { + match self { + DatagramConversionError::Other(x) => x, + } + } +} + +impl From for DatagramConversionError { + fn from(value: u8) -> Self { + DatagramConversionError::Other(value) + } +} + +impl From for u8 { + fn from(value: DatagramConversionError) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum MobileHostRedirect { + Other(u8), +} + +impl AsRef for MobileHostRedirect { + fn as_ref(&self) -> &u8 { + match self { + MobileHostRedirect::Other(x) => x, + } + } +} + +impl From for MobileHostRedirect { + fn from(value: u8) -> Self { + MobileHostRedirect::Other(value) + } +} + +impl From for u8 { + fn from(value: MobileHostRedirect) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Ipv6WhereAreYou { + Other(u8), +} + +impl AsRef for Ipv6WhereAreYou { + fn as_ref(&self) -> &u8 { + match self { + Ipv6WhereAreYou::Other(x) => x, + } + } +} + +impl From for Ipv6WhereAreYou { + fn from(value: u8) -> Self { + Ipv6WhereAreYou::Other(value) + } +} + +impl From for u8 { + fn from(value: Ipv6WhereAreYou) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Ipv6IAmHere { + Other(u8), +} + +impl AsRef for Ipv6IAmHere { + fn as_ref(&self) -> &u8 { + match self { + Ipv6IAmHere::Other(x) => x, + } + } +} + +impl From for Ipv6IAmHere { + fn from(value: u8) -> Self { + Ipv6IAmHere::Other(value) + } +} + +impl From for u8 { + fn from(value: Ipv6IAmHere) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum MobileRegistrationRequest { + Other(u8), +} + +impl AsRef for MobileRegistrationRequest { + fn as_ref(&self) -> &u8 { + match self { + MobileRegistrationRequest::Other(x) => x, + } + } +} + +impl From for MobileRegistrationRequest { + fn from(value: u8) -> Self { + MobileRegistrationRequest::Other(value) + } +} + +impl From for u8 { + fn from(value: MobileRegistrationRequest) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum MobileRegistrationReply { + Other(u8), +} + +impl AsRef for MobileRegistrationReply { + fn as_ref(&self) -> &u8 { + match self { + MobileRegistrationReply::Other(x) => x, + } + } +} + +impl From for MobileRegistrationReply { + fn from(value: u8) -> Self { + MobileRegistrationReply::Other(value) + } +} + +impl From for u8 { + fn from(value: MobileRegistrationReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum DomainNameRequest { + Other(u8), +} + +impl AsRef for DomainNameRequest { + fn as_ref(&self) -> &u8 { + match self { + DomainNameRequest::Other(x) => x, + } + } +} + +impl From for DomainNameRequest { + fn from(value: u8) -> Self { + DomainNameRequest::Other(value) + } +} + +impl From for u8 { + fn from(value: DomainNameRequest) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum DomainNameReply { + Other(u8), +} + +impl AsRef for DomainNameReply { + fn as_ref(&self) -> &u8 { + match self { + DomainNameReply::Other(x) => x, + } + } +} + +impl From for DomainNameReply { + fn from(value: u8) -> Self { + DomainNameReply::Other(value) + } +} + +impl From for u8 { + fn from(value: DomainNameReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Skip { + Other(u8), +} + +impl AsRef for Skip { + fn as_ref(&self) -> &u8 { + match self { + Skip::Other(x) => x, + } + } +} + +impl From for Skip { + fn from(value: u8) -> Self { + Skip::Other(value) + } +} + +impl From for u8 { + fn from(value: Skip) -> u8 { + *value.as_ref() + } +} + +const BAD_SPI: u8 = 0; +const AUTHENTICATION_FAILED: u8 = 1; +const DECOMPRESSION_FAILED: u8 = 2; +const DECRYPTION_FAILED: u8 = 3; +const NEED_AUTHENTICATION: u8 = 4; +const NEED_AUTHORIZATION: u8 = 5; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Photuris { + BadSpi = BAD_SPI, + AuthenticationFailed = AUTHENTICATION_FAILED, + DecompressionFailed = DECOMPRESSION_FAILED, + DecryptionFailed = DECRYPTION_FAILED, + NeedAuthentication = NEED_AUTHENTICATION, + NeedAuthorization = NEED_AUTHORIZATION, + Other(u8), +} + +impl AsRef for Photuris { + fn as_ref(&self) -> &u8 { + match self { + Photuris::BadSpi => &BAD_SPI, + Photuris::AuthenticationFailed => &AUTHENTICATION_FAILED, + Photuris::DecompressionFailed => &DECOMPRESSION_FAILED, + Photuris::DecryptionFailed => &DECRYPTION_FAILED, + Photuris::NeedAuthentication => &NEED_AUTHENTICATION, + Photuris::NeedAuthorization => &NEED_AUTHORIZATION, + Photuris::Other(x) => x, + } + } +} + +impl From for Photuris { + fn from(value: u8) -> Self { + match value { + BAD_SPI => Photuris::BadSpi, + AUTHENTICATION_FAILED => Photuris::AuthenticationFailed, + DECOMPRESSION_FAILED => Photuris::DecompressionFailed, + DECRYPTION_FAILED => Photuris::DecryptionFailed, + NEED_AUTHENTICATION => Photuris::NeedAuthentication, + NEED_AUTHORIZATION => Photuris::NeedAuthorization, + x => Photuris::Other(x), + } + } +} + +impl From for u8 { + fn from(value: Photuris) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum ExtendedEchoRequest { + NoError = 0, + Other(u8), +} + +impl AsRef for ExtendedEchoRequest { + fn as_ref(&self) -> &u8 { + match self { + ExtendedEchoRequest::NoError => &0, + ExtendedEchoRequest::Other(x) => x, + } + } +} + +impl From for ExtendedEchoRequest { + fn from(value: u8) -> Self { + match value { + 0 => ExtendedEchoRequest::NoError, + x => ExtendedEchoRequest::Other(x), + } + } +} + +impl From for u8 { + fn from(value: ExtendedEchoRequest) -> u8 { + *value.as_ref() + } +} + +const NO_ERROR: u8 = 0; +const MALFORMED_QUERY: u8 = 1; +const NO_SUCH_INTERFACE: u8 = 2; +const NO_SUCH_TABLE_ENTRY: u8 = 3; +const MULTIPLE_INTERFACES_SATISFY_QUERY: u8 = 4; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum ExtendedEchoReply { + NoError = NO_ERROR, + MalformedQuery = MALFORMED_QUERY, + NoSuchInterface = NO_SUCH_INTERFACE, + NoSuchTableEntry = NO_SUCH_TABLE_ENTRY, + MultipleInterfacesSatisfyQuery = MULTIPLE_INTERFACES_SATISFY_QUERY, + Other(u8), +} + +impl AsRef for ExtendedEchoReply { + fn as_ref(&self) -> &u8 { + match self { + ExtendedEchoReply::NoError => &NO_ERROR, + ExtendedEchoReply::MalformedQuery => &MALFORMED_QUERY, + ExtendedEchoReply::NoSuchInterface => &NO_SUCH_INTERFACE, + ExtendedEchoReply::NoSuchTableEntry => &NO_SUCH_TABLE_ENTRY, + ExtendedEchoReply::MultipleInterfacesSatisfyQuery => { + &MULTIPLE_INTERFACES_SATISFY_QUERY + } + ExtendedEchoReply::Other(x) => x, + } + } +} + +impl From for ExtendedEchoReply { + fn from(value: u8) -> Self { + match value { + NO_ERROR => ExtendedEchoReply::NoError, + MALFORMED_QUERY => ExtendedEchoReply::MalformedQuery, + NO_SUCH_INTERFACE => ExtendedEchoReply::NoSuchInterface, + NO_SUCH_TABLE_ENTRY => ExtendedEchoReply::NoSuchTableEntry, + MULTIPLE_INTERFACES_SATISFY_QUERY => { + ExtendedEchoReply::MultipleInterfacesSatisfyQuery + } + x => ExtendedEchoReply::Other(x), + } + } +} + +impl From for u8 { + fn from(value: ExtendedEchoReply) -> u8 { + *value.as_ref() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[repr(u8)] +pub enum Code { + EchoReply(EchoReply), + DestinationUnreachable(DestinationUnreachable), + SourceQuench(SourceQuench), + Redirect(Redirect), + AlternateHostAddress(AlternateHostAddress), + EchoRequest(EchoRequest), + RouterAdvertisement(RouterAdvertisement), + RouterSolicitation(RouterSolicitation), + TimeExceeded(TimeExceeded), + ParameterProblem(ParameterProblem), + TimestampRequest(TimestampRequest), + TimestampReply(TimestampReply), + InformationRequest(InformationRequest), + InformationReply(InformationReply), + AddressMaskRequest(AddressMaskRequest), + AddressMaskReply(AddressMaskReply), + Traceroute(Traceroute), + DatagramConversionError(DatagramConversionError), + MobileHostRedirect(MobileHostRedirect), + Ipv6WhereAreYou(Ipv6WhereAreYou), + Ipv6IAmHere(Ipv6IAmHere), + MobileRegistrationRequest(MobileRegistrationRequest), + MobileRegistrationReply(MobileRegistrationReply), + DomainNameRequest(DomainNameRequest), + DomainNameReply(DomainNameReply), + Skip(Skip), + Photuris(Photuris), + ExtendedEchoRequest(ExtendedEchoRequest), + ExtendedEchoReply(ExtendedEchoReply), + Other(u8), +} + +impl AsRef for Code { + fn as_ref(&self) -> &u8 { + match self { + Code::EchoReply(x) => x.as_ref(), + Code::DestinationUnreachable(x) => x.as_ref(), + Code::SourceQuench(x) => x.as_ref(), + Code::Redirect(x) => x.as_ref(), + Code::AlternateHostAddress(x) => x.as_ref(), + Code::EchoRequest(x) => x.as_ref(), + Code::RouterAdvertisement(x) => x.as_ref(), + Code::RouterSolicitation(x) => x.as_ref(), + Code::TimeExceeded(x) => x.as_ref(), + Code::ParameterProblem(x) => x.as_ref(), + Code::TimestampRequest(x) => x.as_ref(), + Code::TimestampReply(x) => x.as_ref(), + Code::InformationRequest(x) => x.as_ref(), + Code::InformationReply(x) => x.as_ref(), + Code::AddressMaskRequest(x) => x.as_ref(), + Code::AddressMaskReply(x) => x.as_ref(), + Code::Traceroute(x) => x.as_ref(), + Code::DatagramConversionError(x) => x.as_ref(), + Code::MobileHostRedirect(x) => x.as_ref(), + Code::Ipv6WhereAreYou(x) => x.as_ref(), + Code::Ipv6IAmHere(x) => x.as_ref(), + Code::MobileRegistrationRequest(x) => x.as_ref(), + Code::MobileRegistrationReply(x) => x.as_ref(), + Code::DomainNameRequest(x) => x.as_ref(), + Code::DomainNameReply(x) => x.as_ref(), + Code::Skip(x) => x.as_ref(), + Code::Photuris(x) => x.as_ref(), + Code::ExtendedEchoRequest(x) => x.as_ref(), + Code::ExtendedEchoReply(x) => x.as_ref(), + Code::Other(x) => x, + } + } +} diff --git a/src/net/icmpv6.rs b/src/net/icmpv6.rs new file mode 100644 index 00000000..fdc54dfc --- /dev/null +++ b/src/net/icmpv6.rs @@ -0,0 +1,237 @@ +const DESTINATION_UNREACHABLE: u8 = 1; +const PACKET_TOO_BIG: u8 = 2; +const TIME_EXCEEDED: u8 = 3; +const PARAMETER_PROBLEM: u8 = 4; +const ECHO_REQUEST: u8 = 128; +const ECHO_REPLY: u8 = 129; +const MULTICAST_LISTENER_QUERY: u8 = 130; +const MULTICAST_LISTENER_REPORT: u8 = 131; +const MULTICAST_LISTENER_DONE: u8 = 132; +const ROUTER_SOLICITATION: u8 = 133; +const ROUTER_ADVERTISEMENT: u8 = 134; +const NEIGHBOR_SOLICITATION: u8 = 135; +const NEIGHBOR_ADVERTISEMENT: u8 = 136; +const REDIRECT_MESSAGE: u8 = 137; +const ROUTER_RENUMBERING: u8 = 138; +const ICMP_NODE_INFORMATION_QUERY: u8 = 139; +const ICMP_NODE_INFORMATION_RESPONSE: u8 = 140; +const INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION_MESSAGE: u8 = 141; +const INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT_MESSAGE: u8 = 142; +const VERSION2_MULTICAST_LISTENER_REPORT: u8 = 143; +const HOME_AGENT_ADDRESS_DISCOVERY_REQUEST_MESSAGE: u8 = 144; +const HOME_AGENT_ADDRESS_DISCOVERY_REPLY_MESSAGE: u8 = 145; +const MOBILE_PREFIX_SOLICITATION: u8 = 146; +const MOBILE_PREFIX_ADVERTISEMENT: u8 = 147; +const CERTIFICATION_PATH_SOLICITATION_MESSAGE: u8 = 148; +const CERTIFICATION_PATH_ADVERTISEMENT_MESSAGE: u8 = 149; +const EXPERIMENTAL_MOBILITY_PROTOCOLS: u8 = 150; +const MULTICAST_ROUTER_ADVERTISEMENT: u8 = 151; +const MULTICAST_ROUTER_SOLICITATION: u8 = 152; +const MULTICAST_ROUTER_TERMINATION: u8 = 153; +const FMIPV6_MESSAGES: u8 = 154; +const RPLCONTROL_MESSAGE: u8 = 155; +const ILNPV6_LOCATOR_UPDATE_MESSAGE: u8 = 156; +const DUPLICATE_ADDRESS_REQUEST: u8 = 157; +const DUPLICATE_ADDRESS_CONFIRMATION: u8 = 158; +const MPLCONTROL_MESSAGE: u8 = 159; +const EXTENDED_ECHO_REQUEST: u8 = 160; +const EXTENDED_ECHO_REPLY: u8 = 161; + +/// Enum of `ICMPv6` message types. +/// +/// This enum is not exhaustive. +/// List sourced from [iana.org][1] +/// +/// [1]: https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-2 +#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Hash)] +#[non_exhaustive] +#[repr(u8)] +pub enum Type { + DestinationUnreachable = DESTINATION_UNREACHABLE, + PacketTooBig = PACKET_TOO_BIG, + TimeExceeded = TIME_EXCEEDED, + ParameterProblem = PARAMETER_PROBLEM, + EchoRequest = ECHO_REQUEST, + EchoReply = ECHO_REPLY, + MulticastListenerQuery = MULTICAST_LISTENER_QUERY, + MulticastListenerReport = MULTICAST_LISTENER_REPORT, + MulticastListenerDone = MULTICAST_LISTENER_DONE, + RouterSolicitation = ROUTER_SOLICITATION, + RouterAdvertisement = ROUTER_ADVERTISEMENT, + NeighborSolicitation = NEIGHBOR_SOLICITATION, + NeighborAdvertisement = NEIGHBOR_ADVERTISEMENT, + RedirectMessage = REDIRECT_MESSAGE, + RouterRenumbering = ROUTER_RENUMBERING, + IcmpNodeInformationQuery = ICMP_NODE_INFORMATION_QUERY, + IcmpNodeInformationResponse = ICMP_NODE_INFORMATION_RESPONSE, + InverseNeighborDiscoverySolicitationMessage = + INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION_MESSAGE, + InverseNeighborDiscoveryAdvertisementMessage = + INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT_MESSAGE, + Version2MulticastListenerReport = VERSION2_MULTICAST_LISTENER_REPORT, + HomeAgentAddressDiscoveryRequestMessage = + HOME_AGENT_ADDRESS_DISCOVERY_REQUEST_MESSAGE, + HomeAgentAddressDiscoveryReplyMessage = + HOME_AGENT_ADDRESS_DISCOVERY_REPLY_MESSAGE, + MobilePrefixSolicitation = MOBILE_PREFIX_SOLICITATION, + MobilePrefixAdvertisement = MOBILE_PREFIX_ADVERTISEMENT, + CertificationPathSolicitationMessage = + CERTIFICATION_PATH_SOLICITATION_MESSAGE, + CertificationPathAdvertisementMessage = + CERTIFICATION_PATH_ADVERTISEMENT_MESSAGE, + ExperimentalMobilityProtocols = EXPERIMENTAL_MOBILITY_PROTOCOLS, + MulticastRouterAdvertisement = MULTICAST_ROUTER_ADVERTISEMENT, + MulticastRouterSolicitation = MULTICAST_ROUTER_SOLICITATION, + MulticastRouterTermination = MULTICAST_ROUTER_TERMINATION, + FMIPv6Messages = FMIPV6_MESSAGES, + RPLControlMessage = RPLCONTROL_MESSAGE, + ILNPv6LocatorUpdateMessage = ILNPV6_LOCATOR_UPDATE_MESSAGE, + DuplicateAddressRequest = DUPLICATE_ADDRESS_REQUEST, + DuplicateAddressConfirmation = DUPLICATE_ADDRESS_CONFIRMATION, + MPLControlMessage = MPLCONTROL_MESSAGE, + ExtendedEchoRequest = EXTENDED_ECHO_REQUEST, + ExtendedEchoReply = EXTENDED_ECHO_REPLY, + Other(u8), +} + +impl AsRef for Type { + fn as_ref(&self) -> &u8 { + match self { + Type::DestinationUnreachable => &DESTINATION_UNREACHABLE, + Type::PacketTooBig => &PACKET_TOO_BIG, + Type::TimeExceeded => &TIME_EXCEEDED, + Type::ParameterProblem => &PARAMETER_PROBLEM, + Type::EchoRequest => &ECHO_REQUEST, + Type::EchoReply => &ECHO_REPLY, + Type::MulticastListenerQuery => &MULTICAST_LISTENER_QUERY, + Type::MulticastListenerReport => &MULTICAST_LISTENER_REPORT, + Type::MulticastListenerDone => &MULTICAST_LISTENER_DONE, + Type::RouterSolicitation => &ROUTER_SOLICITATION, + Type::RouterAdvertisement => &ROUTER_ADVERTISEMENT, + Type::NeighborSolicitation => &NEIGHBOR_SOLICITATION, + Type::NeighborAdvertisement => &NEIGHBOR_ADVERTISEMENT, + Type::RedirectMessage => &REDIRECT_MESSAGE, + Type::RouterRenumbering => &ROUTER_RENUMBERING, + Type::IcmpNodeInformationQuery => &ICMP_NODE_INFORMATION_QUERY, + Type::IcmpNodeInformationResponse => { + &ICMP_NODE_INFORMATION_RESPONSE + } + Type::InverseNeighborDiscoverySolicitationMessage => { + &INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION_MESSAGE + } + Type::InverseNeighborDiscoveryAdvertisementMessage => { + &INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT_MESSAGE + } + Type::Version2MulticastListenerReport => { + &VERSION2_MULTICAST_LISTENER_REPORT + } + Type::HomeAgentAddressDiscoveryRequestMessage => { + &HOME_AGENT_ADDRESS_DISCOVERY_REQUEST_MESSAGE + } + Type::HomeAgentAddressDiscoveryReplyMessage => { + &HOME_AGENT_ADDRESS_DISCOVERY_REPLY_MESSAGE + } + Type::MobilePrefixSolicitation => &MOBILE_PREFIX_SOLICITATION, + Type::MobilePrefixAdvertisement => &MOBILE_PREFIX_ADVERTISEMENT, + Type::CertificationPathSolicitationMessage => { + &CERTIFICATION_PATH_SOLICITATION_MESSAGE + } + Type::CertificationPathAdvertisementMessage => { + &CERTIFICATION_PATH_ADVERTISEMENT_MESSAGE + } + Type::ExperimentalMobilityProtocols => { + &EXPERIMENTAL_MOBILITY_PROTOCOLS + } + Type::MulticastRouterAdvertisement => { + &MULTICAST_ROUTER_ADVERTISEMENT + } + Type::MulticastRouterSolicitation => &MULTICAST_ROUTER_SOLICITATION, + Type::MulticastRouterTermination => &MULTICAST_ROUTER_TERMINATION, + Type::FMIPv6Messages => &FMIPV6_MESSAGES, + Type::RPLControlMessage => &RPLCONTROL_MESSAGE, + Type::ILNPv6LocatorUpdateMessage => &ILNPV6_LOCATOR_UPDATE_MESSAGE, + Type::DuplicateAddressRequest => &DUPLICATE_ADDRESS_REQUEST, + Type::DuplicateAddressConfirmation => { + &DUPLICATE_ADDRESS_CONFIRMATION + } + Type::MPLControlMessage => &MPLCONTROL_MESSAGE, + Type::ExtendedEchoRequest => &EXTENDED_ECHO_REQUEST, + Type::ExtendedEchoReply => &EXTENDED_ECHO_REPLY, + Type::Other(x) => x, + } + } +} + +impl From for Type { + fn from(value: u8) -> Self { + match value { + DESTINATION_UNREACHABLE => Self::DestinationUnreachable, + PACKET_TOO_BIG => Self::PacketTooBig, + TIME_EXCEEDED => Self::TimeExceeded, + PARAMETER_PROBLEM => Self::ParameterProblem, + ECHO_REQUEST => Self::EchoRequest, + ECHO_REPLY => Self::EchoReply, + MULTICAST_LISTENER_QUERY => Self::MulticastListenerQuery, + MULTICAST_LISTENER_REPORT => Self::MulticastListenerReport, + MULTICAST_LISTENER_DONE => Self::MulticastListenerDone, + ROUTER_SOLICITATION => Self::RouterSolicitation, + ROUTER_ADVERTISEMENT => Self::RouterAdvertisement, + NEIGHBOR_SOLICITATION => Self::NeighborSolicitation, + NEIGHBOR_ADVERTISEMENT => Self::NeighborAdvertisement, + REDIRECT_MESSAGE => Self::RedirectMessage, + ROUTER_RENUMBERING => Self::RouterRenumbering, + ICMP_NODE_INFORMATION_QUERY => Self::IcmpNodeInformationQuery, + ICMP_NODE_INFORMATION_RESPONSE => Self::IcmpNodeInformationResponse, + INVERSE_NEIGHBOR_DISCOVERY_SOLICITATION_MESSAGE => { + Self::InverseNeighborDiscoverySolicitationMessage + } + INVERSE_NEIGHBOR_DISCOVERY_ADVERTISEMENT_MESSAGE => { + Self::InverseNeighborDiscoveryAdvertisementMessage + } + VERSION2_MULTICAST_LISTENER_REPORT => { + Self::Version2MulticastListenerReport + } + HOME_AGENT_ADDRESS_DISCOVERY_REQUEST_MESSAGE => { + Self::HomeAgentAddressDiscoveryRequestMessage + } + HOME_AGENT_ADDRESS_DISCOVERY_REPLY_MESSAGE => { + Self::HomeAgentAddressDiscoveryReplyMessage + } + MOBILE_PREFIX_SOLICITATION => Self::MobilePrefixSolicitation, + MOBILE_PREFIX_ADVERTISEMENT => Self::MobilePrefixAdvertisement, + CERTIFICATION_PATH_SOLICITATION_MESSAGE => { + Self::CertificationPathSolicitationMessage + } + CERTIFICATION_PATH_ADVERTISEMENT_MESSAGE => { + Self::CertificationPathAdvertisementMessage + } + EXPERIMENTAL_MOBILITY_PROTOCOLS => { + Self::ExperimentalMobilityProtocols + } + MULTICAST_ROUTER_ADVERTISEMENT => { + Self::MulticastRouterAdvertisement + } + MULTICAST_ROUTER_SOLICITATION => Self::MulticastRouterSolicitation, + MULTICAST_ROUTER_TERMINATION => Self::MulticastRouterTermination, + FMIPV6_MESSAGES => Self::FMIPv6Messages, + RPLCONTROL_MESSAGE => Self::RPLControlMessage, + ILNPV6_LOCATOR_UPDATE_MESSAGE => Self::ILNPv6LocatorUpdateMessage, + DUPLICATE_ADDRESS_REQUEST => Self::DuplicateAddressRequest, + DUPLICATE_ADDRESS_CONFIRMATION => { + Self::DuplicateAddressConfirmation + } + MPLCONTROL_MESSAGE => Self::MPLControlMessage, + EXTENDED_ECHO_REQUEST => Self::ExtendedEchoRequest, + EXTENDED_ECHO_REPLY => Self::ExtendedEchoReply, + x => Self::Other(x), + } + } +} + +impl From for u8 { + fn from(value: Type) -> Self { + *value.as_ref() + } +} + +pub type Code = u8; diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 00000000..e30fd0db --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,16 @@ +// General purpose networking abstractions. +#![forbid(unsafe_code)] +#![deny( + clippy::all, + clippy::pedantic, + clippy::unwrap_used, + clippy::expect_used, + clippy::panic, +)] + +pub mod arp; +pub mod ethernet; +pub mod icmpv4; +pub mod icmpv6; +pub mod mpls; +pub mod vxlan; diff --git a/src/net/mpls.rs b/src/net/mpls.rs new file mode 100644 index 00000000..f77ca907 --- /dev/null +++ b/src/net/mpls.rs @@ -0,0 +1,208 @@ +use anyhow::Error; +use std::convert::TryFrom; +use std::fmt; +use std::fmt::{Display, Formatter}; + +/// An MPLS label. +/// +/// The MPLS label is a 20-bit value used to identify a particular forwarding +/// equivalence class (FEC) or to apply a particular operation to a packet. +/// +/// See [RFC 3032][1] for more information. +/// +/// # Outstanding questions: +/// +/// Should we add handling for reserved labels as per the [IANA specs][2]? +/// +/// [1]: https://datatracker.ietf.org/doc/html/rfc3032 +/// [2]: https://www.iana.org/assignments/mpls-label-values/mpls-label-values.xhtml +#[derive(Debug, PartialEq, Eq, Clone, Copy, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct Label(u32); + +impl Label { + /// Creates a new `Label` value. + /// Note: + /// this function will allow you to create a label with a value greater than + /// 0xFFFFF. + /// Use `new` if you want to ensure that the label + /// value is less than 0xFFFFF. + #[must_use] + pub fn new_unchecked(label: u32) -> Self { + Self(label) + } + + /// Creates a new `Label` value, ensuring that the label value is a legal + /// MPLS label. + /// + /// # Safety + /// You must ensure that the label value is less than 0xFFFFF if you want + /// the resulting `Label` value to be semantically valid. + /// + /// # Errors + /// Returns an error if the label value is greater than 20 bits (2^20 - 1 or + /// 0xFFFFF). + pub fn new(label: u32) -> Result { + if label > 0xFFFFF { + Err(Error::msg("MPLS label must be less than 0xFFFFF"))?; + } + Ok(Self(label)) + } +} + +impl TryFrom for Label { + type Error = Error; + + fn try_from(label: u32) -> Result { + Self::new(label) + } +} + +impl From