From c80c6783a4b06dfcf9e1e749e044d6260a185d89 Mon Sep 17 00:00:00 2001 From: Erich Heine Date: Fri, 14 Apr 2023 16:17:20 +0800 Subject: [PATCH] Re #19 - Add the nat action to tc Signed-off-by: Erich Heine --- src/rtnl/tc/constants.rs | 7 ++ src/rtnl/tc/nlas/action/mod.rs | 8 ++ src/rtnl/tc/nlas/action/nat.rs | 160 +++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/rtnl/tc/nlas/action/nat.rs diff --git a/src/rtnl/tc/constants.rs b/src/rtnl/tc/constants.rs index cb098264..f4331973 100644 --- a/src/rtnl/tc/constants.rs +++ b/src/rtnl/tc/constants.rs @@ -93,3 +93,10 @@ pub const TCA_EGRESS_REDIR: i32 = 1; /* packet redirect to EGRESS */ pub const TCA_EGRESS_MIRROR: i32 = 2; /* mirror packet to EGRESS */ pub const TCA_INGRESS_REDIR: i32 = 3; /* packet redirect to INGRESS */ pub const TCA_INGRESS_MIRROR: i32 = 4; /* mirror packet to INGRESS */ + +/// NAT action attr +pub const TCA_NAT_UNSPEC: u16 = 0; +pub const TCA_NAT_PARMS: u16 = 1; +pub const TCA_NAT_TM: u16 = 2; + +pub const TCA_NAT_FLAG_EGRESS: u32 = 1; diff --git a/src/rtnl/tc/nlas/action/mod.rs b/src/rtnl/tc/nlas/action/mod.rs index 1373f7c7..37dea0cb 100644 --- a/src/rtnl/tc/nlas/action/mod.rs +++ b/src/rtnl/tc/nlas/action/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pub mod mirred; +pub mod nat; use anyhow::Context; use byteorder::{ByteOrder, NativeEndian}; @@ -160,6 +161,7 @@ impl nla::Nla for ActNla { #[non_exhaustive] pub enum ActOpt { Mirred(mirred::Nla), + Nat(nat::Nla), // Other options Other(DefaultNla), } @@ -169,6 +171,7 @@ impl nla::Nla for ActOpt { use self::ActOpt::*; match self { Mirred(nla) => nla.value_len(), + Nat(nla) => nla.value_len(), Other(nla) => nla.value_len(), } } @@ -177,6 +180,7 @@ impl nla::Nla for ActOpt { use self::ActOpt::*; match self { Mirred(nla) => nla.emit_value(buffer), + Nat(nla) => nla.emit_value(buffer), Other(nla) => nla.emit_value(buffer), } } @@ -185,6 +189,7 @@ impl nla::Nla for ActOpt { use self::ActOpt::*; match self { Mirred(nla) => nla.kind(), + Nat(nla) => nla.kind(), Other(nla) => nla.kind(), } } @@ -204,6 +209,9 @@ where mirred::Nla::parse(buf) .context("failed to parse mirred action")?, ), + nat::KIND => Self::Nat( + nat::Nla::parse(buf).context("failed to parse nat action")?, + ), _ => Self::Other( DefaultNla::parse(buf) .context("failed to parse action options")?, diff --git a/src/rtnl/tc/nlas/action/nat.rs b/src/rtnl/tc/nlas/action/nat.rs new file mode 100644 index 00000000..6b92cc5c --- /dev/null +++ b/src/rtnl/tc/nlas/action/nat.rs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT + +/// Nat action +/// +/// The nat action maps one IP prefix to another +use std::net::Ipv4Addr; + +use netlink_packet_utils::{ + nla::{self, DefaultNla, NlaBuffer}, + traits::{Emitable, Parseable}, + DecodeError, +}; + +use crate::tc::{constants::*, TC_GEN_BUF_LEN}; + +pub const KIND: &str = "nat"; +pub const TC_NAT_BUF_LEN: usize = TC_GEN_BUF_LEN + 16; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum Nla { + Unspec(Vec), + Tm(Vec), + Parms(TcNat), + Other(DefaultNla), +} + +impl nla::Nla for Nla { + fn value_len(&self) -> usize { + use self::Nla::*; + match self { + Unspec(bytes) | Tm(bytes) => bytes.len(), + Parms(_) => TC_NAT_BUF_LEN, + Other(attr) => attr.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + use self::Nla::*; + match self { + Unspec(bytes) | Tm(bytes) => { + buffer.copy_from_slice(bytes.as_slice()) + } + Parms(p) => p.emit(buffer), + Other(attr) => attr.emit_value(buffer), + } + } + fn kind(&self) -> u16 { + use self::Nla::*; + match self { + Unspec(_) => TCA_NAT_UNSPEC, + Tm(_) => TCA_NAT_TM, + Parms(_) => TCA_NAT_PARMS, + Other(nla) => nla.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nla { + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + use self::Nla::*; + let payload = buf.value(); + Ok(match buf.kind() { + TCA_NAT_UNSPEC => Unspec(payload.to_vec()), + TCA_NAT_TM => Tm(payload.to_vec()), + TCA_NAT_PARMS => { + Parms(TcNat::parse(&TcNatBuffer::new_checked(payload)?)?) + } + _ => Other(DefaultNla::parse(buf)?), + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[non_exhaustive] +pub struct TcNat { + pub index: u32, + pub capab: u32, + pub action: i32, + pub refcnt: i32, + pub bindcnt: i32, + + pub old_addr: Vec, + pub new_addr: Vec, + pub mask: Vec, + pub flags: u32, +} + +buffer!(TcNatBuffer(TC_NAT_BUF_LEN) { + index: (u32, 0..4), + capab: (u32, 4..8), + action: (i32, 8..12), + refcnt: (i32, 12..16), + bindcnt: (i32, 16..20), + + old_addr: (slice, TC_GEN_BUF_LEN..(TC_GEN_BUF_LEN+4)), + new_addr: (slice, (TC_GEN_BUF_LEN +4)..(TC_GEN_BUF_LEN+8)), + mask: (slice, (TC_GEN_BUF_LEN +8)..(TC_GEN_BUF_LEN+12)), + flags: (u32, (TC_GEN_BUF_LEN+12)..TC_NAT_BUF_LEN), +}); + +impl TcNat { + pub fn set_new_addr(mut self, target: Ipv4Addr) -> Self { + self.new_addr = target.octets().to_vec(); + self + } + + pub fn set_old_addr(mut self, target: Ipv4Addr) -> Self { + self.old_addr = target.octets().to_vec(); + self + } + + pub fn set_prefix(mut self, prefix_len: usize) -> Self { + assert!(prefix_len <= 32); + + let prefix: u32 = if prefix_len == 0 { + 0x0 + } else { + !((1 << (32 - prefix_len)) - 1) + }; + self.mask = prefix.to_be_bytes().to_vec(); + + self + } +} +impl Emitable for TcNat { + fn buffer_len(&self) -> usize { + TC_NAT_BUF_LEN + } + + fn emit(&self, buffer: &mut [u8]) { + let mut packet = TcNatBuffer::new(buffer); + packet.set_index(self.index); + packet.set_capab(self.capab); + packet.set_action(self.action); + packet.set_refcnt(self.refcnt); + packet.set_bindcnt(self.bindcnt); + + packet.old_addr_mut().copy_from_slice(&self.old_addr[0..4]); + packet.new_addr_mut().copy_from_slice(&self.new_addr[0..4]); + packet.mask_mut().copy_from_slice(&self.mask[0..4]); + packet.set_flags(self.flags); + } +} + +impl<'buf, T: AsRef<[u8]> + ?Sized> Parseable> for TcNat { + fn parse(buf: &TcNatBuffer<&'buf T>) -> Result { + Ok(Self { + index: buf.index(), + capab: buf.capab(), + action: buf.action(), + refcnt: buf.refcnt(), + bindcnt: buf.bindcnt(), + old_addr: buf.old_addr().to_vec(), + new_addr: buf.new_addr().to_vec(), + mask: buf.mask().to_vec(), + flags: buf.flags(), + }) + } +}