diff options
Diffstat (limited to 'rustables/src/expr')
-rw-r--r-- | rustables/src/expr/bitwise.rs | 68 | ||||
-rw-r--r-- | rustables/src/expr/cmp.rs | 230 | ||||
-rw-r--r-- | rustables/src/expr/counter.rs | 13 | ||||
-rw-r--r-- | rustables/src/expr/ct.rs | 65 | ||||
-rw-r--r-- | rustables/src/expr/immediate.rs | 54 | ||||
-rw-r--r-- | rustables/src/expr/log.rs | 12 | ||||
-rw-r--r-- | rustables/src/expr/lookup.rs | 56 | ||||
-rw-r--r-- | rustables/src/expr/masquerade.rs | 12 | ||||
-rw-r--r-- | rustables/src/expr/meta.rs | 134 | ||||
-rw-r--r-- | rustables/src/expr/mod.rs | 113 | ||||
-rw-r--r-- | rustables/src/expr/nat.rs | 48 | ||||
-rw-r--r-- | rustables/src/expr/payload.rs | 368 | ||||
-rw-r--r-- | rustables/src/expr/verdict.rs | 175 |
13 files changed, 1348 insertions, 0 deletions
diff --git a/rustables/src/expr/bitwise.rs b/rustables/src/expr/bitwise.rs new file mode 100644 index 0000000..1eb81ab --- /dev/null +++ b/rustables/src/expr/bitwise.rs @@ -0,0 +1,68 @@ +use super::{Expression, Rule}; +use crate::expr::cmp::ToSlice; +use rustables_sys::{self as sys, libc}; +use std::ffi::c_void; +use std::os::raw::c_char; + +/// Expression for performing bitwise masking and XOR on the data in a register. +pub struct Bitwise<M: ToSlice, X: ToSlice> { + mask: M, + xor: X, +} + +impl<M: ToSlice, X: ToSlice> Bitwise<M, X> { + /// Returns a new `Bitwise` instance that first masks the value it's applied to with `mask` + /// and then performs xor with the value in `xor`. + pub fn new(mask: M, xor: X) -> Self { + Self { mask, xor } + } +} + +impl<M: ToSlice, X: ToSlice> Expression for Bitwise<M, X> { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"bitwise\0" as *const _ as *const c_char + )); + + let mask = self.mask.to_slice(); + let xor = self.xor.to_slice(); + assert!(mask.len() == xor.len()); + let len = mask.len() as u32; + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_BITWISE_SREG as u16, + libc::NFT_REG_1 as u32, + ); + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_BITWISE_DREG as u16, + libc::NFT_REG_1 as u32, + ); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_BITWISE_LEN as u16, len); + + sys::nftnl_expr_set( + expr, + sys::NFTNL_EXPR_BITWISE_MASK as u16, + mask.as_ref() as *const _ as *const c_void, + len, + ); + sys::nftnl_expr_set( + expr, + sys::NFTNL_EXPR_BITWISE_XOR as u16, + xor.as_ref() as *const _ as *const c_void, + len, + ); + + expr + } + } +} + +#[macro_export] +macro_rules! nft_expr_bitwise { + (mask $mask:expr,xor $xor:expr) => { + $crate::expr::Bitwise::new($mask, $xor) + }; +} diff --git a/rustables/src/expr/cmp.rs b/rustables/src/expr/cmp.rs new file mode 100644 index 0000000..f22a3ff --- /dev/null +++ b/rustables/src/expr/cmp.rs @@ -0,0 +1,230 @@ +use super::{Expression, Rule}; +use rustables_sys::{self as sys, libc}; +use std::{ + borrow::Cow, + ffi::{c_void, CString}, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, + os::raw::c_char, + slice, +}; +use tracing::trace; + +/// Comparison operator. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum CmpOp { + /// Equals. + Eq, + /// Not equal. + Neq, + /// Less than. + Lt, + /// Less than, or equal. + Lte, + /// Greater than. + Gt, + /// Greater than, or equal. + Gte, +} + +impl CmpOp { + /// Returns the corresponding `NFT_*` constant for this comparison operation. + pub fn to_raw(self) -> u32 { + use self::CmpOp::*; + match self { + Eq => libc::NFT_CMP_EQ as u32, + Neq => libc::NFT_CMP_NEQ as u32, + Lt => libc::NFT_CMP_LT as u32, + Lte => libc::NFT_CMP_LTE as u32, + Gt => libc::NFT_CMP_GT as u32, + Gte => libc::NFT_CMP_GTE as u32, + } + } +} + +/// Comparator expression. Allows comparing the content of the netfilter register with any value. +pub struct Cmp<T: ToSlice> { + op: CmpOp, + data: T, +} + +impl<T: ToSlice> Cmp<T> { + /// Returns a new comparison expression comparing the value loaded in the register with the + /// data in `data` using the comparison operator `op`. + pub fn new(op: CmpOp, data: T) -> Self { + Cmp { op, data } + } +} + +impl<T: ToSlice> Expression for Cmp<T> { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc(b"cmp\0" as *const _ as *const c_char)); + + let data = self.data.to_slice(); + trace!("Creating a cmp expr comparing with data {:?}", data); + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_CMP_SREG as u16, + libc::NFT_REG_1 as u32, + ); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_CMP_OP as u16, self.op.to_raw()); + sys::nftnl_expr_set( + expr, + sys::NFTNL_EXPR_CMP_DATA as u16, + data.as_ref() as *const _ as *const c_void, + data.len() as u32, + ); + + expr + } + } +} + +#[macro_export(local_inner_macros)] +macro_rules! nft_expr_cmp { + (@cmp_op ==) => { + $crate::expr::CmpOp::Eq + }; + (@cmp_op !=) => { + $crate::expr::CmpOp::Neq + }; + (@cmp_op <) => { + $crate::expr::CmpOp::Lt + }; + (@cmp_op <=) => { + $crate::expr::CmpOp::Lte + }; + (@cmp_op >) => { + $crate::expr::CmpOp::Gt + }; + (@cmp_op >=) => { + $crate::expr::CmpOp::Gte + }; + ($op:tt $data:expr) => { + $crate::expr::Cmp::new(nft_expr_cmp!(@cmp_op $op), $data) + }; +} + +/// A type that can be converted into a byte buffer. +pub trait ToSlice { + /// Returns the data this type represents. + fn to_slice(&self) -> Cow<'_, [u8]>; +} + +impl<'a> ToSlice for [u8; 0] { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::Borrowed(&[]) + } +} + +impl<'a> ToSlice for &'a [u8] { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::Borrowed(self) + } +} + +impl<'a> ToSlice for &'a [u16] { + fn to_slice(&self) -> Cow<'_, [u8]> { + let ptr = self.as_ptr() as *const u8; + let len = self.len() * 2; + Cow::Borrowed(unsafe { slice::from_raw_parts(ptr, len) }) + } +} + +impl ToSlice for IpAddr { + fn to_slice(&self) -> Cow<'_, [u8]> { + match *self { + IpAddr::V4(ref addr) => addr.to_slice(), + IpAddr::V6(ref addr) => addr.to_slice(), + } + } +} + +impl ToSlice for Ipv4Addr { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::Owned(self.octets().to_vec()) + } +} + +impl ToSlice for Ipv6Addr { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::Owned(self.octets().to_vec()) + } +} + +impl ToSlice for u8 { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::Owned(vec![*self]) + } +} + +impl ToSlice for u16 { + fn to_slice(&self) -> Cow<'_, [u8]> { + let b0 = (*self & 0x00ff) as u8; + let b1 = (*self >> 8) as u8; + Cow::Owned(vec![b0, b1]) + } +} + +impl ToSlice for u32 { + fn to_slice(&self) -> Cow<'_, [u8]> { + let b0 = *self as u8; + let b1 = (*self >> 8) as u8; + let b2 = (*self >> 16) as u8; + let b3 = (*self >> 24) as u8; + Cow::Owned(vec![b0, b1, b2, b3]) + } +} + +impl ToSlice for i32 { + fn to_slice(&self) -> Cow<'_, [u8]> { + let b0 = *self as u8; + let b1 = (*self >> 8) as u8; + let b2 = (*self >> 16) as u8; + let b3 = (*self >> 24) as u8; + Cow::Owned(vec![b0, b1, b2, b3]) + } +} + +impl<'a> ToSlice for &'a str { + fn to_slice(&self) -> Cow<'_, [u8]> { + Cow::from(self.as_bytes()) + } +} + +/// Can be used to compare the value loaded by [`Meta::IifName`] and [`Meta::OifName`]. Please +/// note that it is faster to check interface index than name. +/// +/// [`Meta::IifName`]: enum.Meta.html#variant.IifName +/// [`Meta::OifName`]: enum.Meta.html#variant.OifName +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum InterfaceName { + /// Interface name must be exactly the value of the `CString`. + Exact(CString), + /// Interface name must start with the value of the `CString`. + /// + /// `InterfaceName::StartingWith("eth")` will look like `eth*` when printed and match against + /// `eth0`, `eth1`, ..., `eth99` and so on. + StartingWith(CString), +} + +impl ToSlice for InterfaceName { + fn to_slice(&self) -> Cow<'_, [u8]> { + let bytes = match *self { + InterfaceName::Exact(ref name) => name.as_bytes_with_nul(), + InterfaceName::StartingWith(ref name) => name.as_bytes(), + }; + Cow::from(bytes) + } +} + +impl<'a> ToSlice for &'a InterfaceName { + fn to_slice(&self) -> Cow<'_, [u8]> { + let bytes = match *self { + InterfaceName::Exact(ref name) => name.as_bytes_with_nul(), + InterfaceName::StartingWith(ref name) => name.as_bytes(), + }; + Cow::from(bytes) + } +} diff --git a/rustables/src/expr/counter.rs b/rustables/src/expr/counter.rs new file mode 100644 index 0000000..c2a0b5d --- /dev/null +++ b/rustables/src/expr/counter.rs @@ -0,0 +1,13 @@ +use super::{Expression, Rule}; +use rustables_sys as sys; +use std::os::raw::c_char; + +/// A counter expression adds a counter to the rule that is incremented to count number of packets +/// and number of bytes for all packets that has matched the rule. +pub struct Counter; + +impl Expression for Counter { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + try_alloc!(unsafe { sys::nftnl_expr_alloc(b"counter\0" as *const _ as *const c_char) }) + } +} diff --git a/rustables/src/expr/ct.rs b/rustables/src/expr/ct.rs new file mode 100644 index 0000000..c0349ab --- /dev/null +++ b/rustables/src/expr/ct.rs @@ -0,0 +1,65 @@ +use super::{Expression, Rule}; +use rustables_sys::{self as sys, libc}; +use std::os::raw::c_char; + +bitflags::bitflags! { + pub struct States: u32 { + const INVALID = 1; + const ESTABLISHED = 2; + const RELATED = 4; + const NEW = 8; + const UNTRACKED = 64; + } +} + +pub enum Conntrack { + State, + Mark { set: bool }, +} + +impl Conntrack { + fn raw_key(&self) -> u32 { + match *self { + Conntrack::State => libc::NFT_CT_STATE as u32, + Conntrack::Mark { .. } => libc::NFT_CT_MARK as u32, + } + } +} + +impl Expression for Conntrack { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc(b"ct\0" as *const _ as *const c_char)); + + if let Conntrack::Mark { set: true } = self { + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_CT_SREG as u16, + libc::NFT_REG_1 as u32, + ); + } else { + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_CT_DREG as u16, + libc::NFT_REG_1 as u32, + ); + } + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_CT_KEY as u16, self.raw_key()); + + expr + } + } +} + +#[macro_export] +macro_rules! nft_expr_ct { + (state) => { + $crate::expr::Conntrack::State + }; + (mark set) => { + $crate::expr::Conntrack::Mark { set: true } + }; + (mark) => { + $crate::expr::Conntrack::Mark { set: false } + }; +} diff --git a/rustables/src/expr/immediate.rs b/rustables/src/expr/immediate.rs new file mode 100644 index 0000000..e5ccc2a --- /dev/null +++ b/rustables/src/expr/immediate.rs @@ -0,0 +1,54 @@ +use super::{Expression, Register, Rule}; +use rustables_sys as sys; +use std::ffi::c_void; +use std::mem::size_of_val; +use std::os::raw::c_char; + +/// An immediate expression. Used to set immediate data. +/// Verdicts are handled separately by [Verdict]. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Immediate<T> { + pub data: T, + pub register: Register, +} + +impl<T> Immediate<T> { + pub fn new(data: T, register: Register) -> Self { + Self { data, register } + } +} + +impl<T> Expression for Immediate<T> { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"immediate\0" as *const _ as *const c_char + )); + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_IMM_DREG as u16, + self.register.to_raw(), + ); + + sys::nftnl_expr_set( + expr, + sys::NFTNL_EXPR_IMM_DATA as u16, + &self.data as *const _ as *const c_void, + size_of_val(&self.data) as u32, + ); + + expr + } + } +} + +#[macro_export] +macro_rules! nft_expr_immediate { + (data $value:expr) => { + $crate::expr::Immediate { + data: $value, + register: $crate::expr::Register::Reg1, + } + }; +} diff --git a/rustables/src/expr/log.rs b/rustables/src/expr/log.rs new file mode 100644 index 0000000..d6e0089 --- /dev/null +++ b/rustables/src/expr/log.rs @@ -0,0 +1,12 @@ +use super::{Expression, Rule}; +use rustables_sys as sys; +use std::os::raw::c_char; + +/// A Log expression will log all packets that match the rule. +pub struct Log; + +impl Expression for Log { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + try_alloc!(unsafe { sys::nftnl_expr_alloc(b"log\0" as *const _ as *const c_char) }) + } +} diff --git a/rustables/src/expr/lookup.rs b/rustables/src/expr/lookup.rs new file mode 100644 index 0000000..ac22440 --- /dev/null +++ b/rustables/src/expr/lookup.rs @@ -0,0 +1,56 @@ +use super::{Expression, Rule}; +use crate::set::Set; +use rustables_sys::{self as sys, libc}; +use std::ffi::CString; +use std::os::raw::c_char; + +pub struct Lookup { + set_name: CString, + set_id: u32, +} + +impl Lookup { + pub fn new<K>(set: &Set<'_, K>) -> Self { + Lookup { + set_name: set.get_name().to_owned(), + set_id: set.get_id(), + } + } +} + +impl Expression for Lookup { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"lookup\0" as *const _ as *const c_char + )); + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_LOOKUP_SREG as u16, + libc::NFT_REG_1 as u32, + ); + sys::nftnl_expr_set_str( + expr, + sys::NFTNL_EXPR_LOOKUP_SET as u16, + self.set_name.as_ptr() as *const _ as *const c_char, + ); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_LOOKUP_SET_ID as u16, self.set_id); + + // This code is left here since it's quite likely we need it again when we get further + // if self.reverse { + // sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_LOOKUP_FLAGS as u16, + // libc::NFT_LOOKUP_F_INV as u32); + // } + + expr + } + } +} + +#[macro_export] +macro_rules! nft_expr_lookup { + ($set:expr) => { + $crate::expr::Lookup::new($set) + }; +} diff --git a/rustables/src/expr/masquerade.rs b/rustables/src/expr/masquerade.rs new file mode 100644 index 0000000..66e9e0e --- /dev/null +++ b/rustables/src/expr/masquerade.rs @@ -0,0 +1,12 @@ +use super::{Expression, Rule}; +use rustables_sys as sys; +use std::os::raw::c_char; + +/// Sets the source IP to that of the output interface. +pub struct Masquerade; + +impl Expression for Masquerade { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + try_alloc!(unsafe { sys::nftnl_expr_alloc(b"masq\0" as *const _ as *const c_char) }) + } +} diff --git a/rustables/src/expr/meta.rs b/rustables/src/expr/meta.rs new file mode 100644 index 0000000..a91cb27 --- /dev/null +++ b/rustables/src/expr/meta.rs @@ -0,0 +1,134 @@ +use super::{Expression, Rule}; +use rustables_sys::{self as sys, libc}; +use std::os::raw::c_char; + +/// A meta expression refers to meta data associated with a packet. +#[non_exhaustive] +pub enum Meta { + /// Packet ethertype protocol (skb->protocol), invalid in OUTPUT. + Protocol, + /// Packet mark. + Mark { set: bool }, + /// Packet input interface index (dev->ifindex). + Iif, + /// Packet output interface index (dev->ifindex). + Oif, + /// Packet input interface name (dev->name) + IifName, + /// Packet output interface name (dev->name). + OifName, + /// Packet input interface type (dev->type). + IifType, + /// Packet output interface type (dev->type). + OifType, + /// Originating socket UID (fsuid). + SkUid, + /// Originating socket GID (fsgid). + SkGid, + /// Netfilter protocol (Transport layer protocol). + NfProto, + /// Layer 4 protocol number. + L4Proto, + /// Socket control group (skb->sk->sk_classid). + Cgroup, + /// A 32bit pseudo-random number + PRandom, +} + +impl Meta { + /// Returns the corresponding `NFT_*` constant for this meta expression. + pub fn to_raw_key(&self) -> u32 { + use Meta::*; + match *self { + Protocol => libc::NFT_META_PROTOCOL as u32, + Mark { .. } => libc::NFT_META_MARK as u32, + Iif => libc::NFT_META_IIF as u32, + Oif => libc::NFT_META_OIF as u32, + IifName => libc::NFT_META_IIFNAME as u32, + OifName => libc::NFT_META_OIFNAME as u32, + IifType => libc::NFT_META_IIFTYPE as u32, + OifType => libc::NFT_META_OIFTYPE as u32, + SkUid => libc::NFT_META_SKUID as u32, + SkGid => libc::NFT_META_SKGID as u32, + NfProto => libc::NFT_META_NFPROTO as u32, + L4Proto => libc::NFT_META_L4PROTO as u32, + Cgroup => libc::NFT_META_CGROUP as u32, + PRandom => libc::NFT_META_PRANDOM as u32, + } + } +} + +impl Expression for Meta { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"meta\0" as *const _ as *const c_char + )); + + if let Meta::Mark { set: true } = self { + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_META_SREG as u16, + libc::NFT_REG_1 as u32, + ); + } else { + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_META_DREG as u16, + libc::NFT_REG_1 as u32, + ); + } + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_META_KEY as u16, self.to_raw_key()); + expr + } + } +} + +#[macro_export] +macro_rules! nft_expr_meta { + (proto) => { + $crate::expr::Meta::Protocol + }; + (mark set) => { + $crate::expr::Meta::Mark { set: true } + }; + (mark) => { + $crate::expr::Meta::Mark { set: false } + }; + (iif) => { + $crate::expr::Meta::Iif + }; + (oif) => { + $crate::expr::Meta::Oif + }; + (iifname) => { + $crate::expr::Meta::IifName + }; + (oifname) => { + $crate::expr::Meta::OifName + }; + (iiftype) => { + $crate::expr::Meta::IifType + }; + (oiftype) => { + $crate::expr::Meta::OifType + }; + (skuid) => { + $crate::expr::Meta::SkUid + }; + (skgid) => { + $crate::expr::Meta::SkGid + }; + (nfproto) => { + $crate::expr::Meta::NfProto + }; + (l4proto) => { + $crate::expr::Meta::L4Proto + }; + (cgroup) => { + $crate::expr::Meta::Cgroup + }; + (random) => { + $crate::expr::Meta::PRandom + }; +} diff --git a/rustables/src/expr/mod.rs b/rustables/src/expr/mod.rs new file mode 100644 index 0000000..1364904 --- /dev/null +++ b/rustables/src/expr/mod.rs @@ -0,0 +1,113 @@ +//! A module with all the nftables expressions that can be added to [`Rule`]s to build up how +//! they match against packets. +//! +//! [`Rule`]: struct.Rule.html + +use super::rule::Rule; +use rustables_sys::{self as sys, libc}; + +/// Trait for every safe wrapper of an nftables expression. +pub trait Expression { + /// Allocates and returns the low level `nftnl_expr` representation of this expression. + /// The caller to this method is responsible for freeing the expression. + fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr; +} + +/// A netfilter data register. The expressions store and read data to and from these +/// when evaluating rule statements. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(i32)] +pub enum Register { + Reg1 = libc::NFT_REG_1, + Reg2 = libc::NFT_REG_2, + Reg3 = libc::NFT_REG_3, + Reg4 = libc::NFT_REG_4, +} + +impl Register { + pub fn to_raw(self) -> u32 { + self as u32 + } +} + +mod bitwise; +pub use self::bitwise::*; + +mod cmp; +pub use self::cmp::*; + +mod counter; +pub use self::counter::*; + +pub mod ct; +pub use self::ct::*; + +mod immediate; +pub use self::immediate::*; + +mod log; +pub use self::log::*; + +mod lookup; +pub use self::lookup::*; + +mod masquerade; +pub use self::masquerade::*; + +mod meta; +pub use self::meta::*; + +mod nat; +pub use self::nat::*; + +mod payload; +pub use self::payload::*; + +mod verdict; +pub use self::verdict::*; + +#[macro_export(local_inner_macros)] +macro_rules! nft_expr { + (bitwise mask $mask:expr,xor $xor:expr) => { + nft_expr_bitwise!(mask $mask, xor $xor) + }; + (cmp $op:tt $data:expr) => { + nft_expr_cmp!($op $data) + }; + (counter) => { + $crate::expr::Counter + }; + (ct $key:ident set) => { + nft_expr_ct!($key set) + }; + (ct $key:ident) => { + nft_expr_ct!($key) + }; + (immediate $expr:ident $value:expr) => { + nft_expr_immediate!($expr $value) + }; + (log) => { + nft_expr_log!() + }; + (lookup $set:expr) => { + nft_expr_lookup!($set) + }; + (masquerade) => { + $crate::expr::Masquerade + }; + (meta $expr:ident set) => { + nft_expr_meta!($expr set) + }; + (meta $expr:ident) => { + nft_expr_meta!($expr) + }; + (payload $proto:ident $field:ident) => { + nft_expr_payload!($proto $field) + }; + (verdict $verdict:ident) => { + nft_expr_verdict!($verdict) + }; + (verdict $verdict:ident $chain:expr) => { + nft_expr_verdict!($verdict $chain) + }; +} diff --git a/rustables/src/expr/nat.rs b/rustables/src/expr/nat.rs new file mode 100644 index 0000000..d60e5ea --- /dev/null +++ b/rustables/src/expr/nat.rs @@ -0,0 +1,48 @@ +use super::{Expression, Register, Rule}; +use crate::ProtoFamily; +use rustables_sys::{self as sys, libc}; +use std::os::raw::c_char; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(i32)] +pub enum NatType { + /// Source NAT. Changes the source address of a packet + SNat = libc::NFT_NAT_SNAT, + /// Destination NAT. Changeth the destination address of a packet + DNat = libc::NFT_NAT_DNAT, +} + +/// A source or destination NAT statement. Modifies the source or destination address +/// (and possibly port) of packets. +pub struct Nat { + pub nat_type: NatType, + pub family: ProtoFamily, + pub ip_register: Register, + pub port_register: Option<Register>, +} + +impl Expression for Nat { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + let expr = + try_alloc!(unsafe { sys::nftnl_expr_alloc(b"nat\0" as *const _ as *const c_char) }); + + unsafe { + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_NAT_TYPE as u16, self.nat_type as u32); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_NAT_FAMILY as u16, self.family as u32); + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_NAT_REG_ADDR_MIN as u16, + self.ip_register.to_raw(), + ); + if let Some(port_register) = self.port_register { + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_NAT_REG_PROTO_MIN as u16, + port_register.to_raw(), + ); + } + } + + expr + } +} diff --git a/rustables/src/expr/payload.rs b/rustables/src/expr/payload.rs new file mode 100644 index 0000000..2da4e1f --- /dev/null +++ b/rustables/src/expr/payload.rs @@ -0,0 +1,368 @@ +use super::{Expression, Rule}; +use rustables_sys::{self as sys, libc}; +use std::os::raw::c_char; + +trait HeaderField { + fn offset(&self) -> u32; + fn len(&self) -> u32; +} + +/// Payload expressions refer to data from the packet's payload. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Payload { + LinkLayer(LLHeaderField), + Network(NetworkHeaderField), + Transport(TransportHeaderField), +} + +impl Payload { + fn base(self) -> u32 { + match self { + Payload::LinkLayer(_) => libc::NFT_PAYLOAD_LL_HEADER as u32, + Payload::Network(_) => libc::NFT_PAYLOAD_NETWORK_HEADER as u32, + Payload::Transport(_) => libc::NFT_PAYLOAD_TRANSPORT_HEADER as u32, + } + } +} + +impl HeaderField for Payload { + fn offset(&self) -> u32 { + use self::Payload::*; + match *self { + LinkLayer(ref f) => f.offset(), + Network(ref f) => f.offset(), + Transport(ref f) => f.offset(), + } + } + + fn len(&self) -> u32 { + use self::Payload::*; + match *self { + LinkLayer(ref f) => f.len(), + Network(ref f) => f.len(), + Transport(ref f) => f.len(), + } + } +} + +impl Expression for Payload { + fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr { + unsafe { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"payload\0" as *const _ as *const c_char + )); + + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_BASE as u16, self.base()); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_OFFSET as u16, self.offset()); + sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_LEN as u16, self.len()); + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_PAYLOAD_DREG as u16, + libc::NFT_REG_1 as u32, + ); + + expr + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum LLHeaderField { + Daddr, + Saddr, + EtherType, +} + +impl HeaderField for LLHeaderField { + fn offset(&self) -> u32 { + use self::LLHeaderField::*; + match *self { + Daddr => 0, + Saddr => 6, + EtherType => 12, + } + } + + fn len(&self) -> u32 { + use self::LLHeaderField::*; + match *self { + Daddr => 6, + Saddr => 6, + EtherType => 2, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum NetworkHeaderField { + Ipv4(Ipv4HeaderField), + Ipv6(Ipv6HeaderField), +} + +impl HeaderField for NetworkHeaderField { + fn offset(&self) -> u32 { + use self::NetworkHeaderField::*; + match *self { + Ipv4(ref f) => f.offset(), + Ipv6(ref f) => f.offset(), + } + } + + fn len(&self) -> u32 { + use self::NetworkHeaderField::*; + match *self { + Ipv4(ref f) => f.len(), + Ipv6(ref f) => f.len(), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum Ipv4HeaderField { + Ttl, + Protocol, + Saddr, + Daddr, +} + +impl HeaderField for Ipv4HeaderField { + fn offset(&self) -> u32 { + use self::Ipv4HeaderField::*; + match *self { + Ttl => 8, + Protocol => 9, + Saddr => 12, + Daddr => 16, + } + } + + fn len(&self) -> u32 { + use self::Ipv4HeaderField::*; + match *self { + Ttl => 1, + Protocol => 1, + Saddr => 4, + Daddr => 4, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum Ipv6HeaderField { + NextHeader, + HopLimit, + Saddr, + Daddr, +} + +impl HeaderField for Ipv6HeaderField { + fn offset(&self) -> u32 { + use self::Ipv6HeaderField::*; + match *self { + NextHeader => 6, + HopLimit => 7, + Saddr => 8, + Daddr => 24, + } + } + + fn len(&self) -> u32 { + use self::Ipv6HeaderField::*; + match *self { + NextHeader => 1, + HopLimit => 1, + Saddr => 16, + Daddr => 16, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum TransportHeaderField { + Tcp(TcpHeaderField), + Udp(UdpHeaderField), + Icmpv6(Icmpv6HeaderField), +} + +impl HeaderField for TransportHeaderField { + fn offset(&self) -> u32 { + use self::TransportHeaderField::*; + match *self { + Tcp(ref f) => f.offset(), + Udp(ref f) => f.offset(), + Icmpv6(ref f) => f.offset(), + } + } + + fn len(&self) -> u32 { + use self::TransportHeaderField::*; + match *self { + Tcp(ref f) => f.len(), + Udp(ref f) => f.len(), + Icmpv6(ref f) => f.len(), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum TcpHeaderField { + Sport, + Dport, +} + +impl HeaderField for TcpHeaderField { + fn offset(&self) -> u32 { + use self::TcpHeaderField::*; + match *self { + Sport => 0, + Dport => 2, + } + } + + fn len(&self) -> u32 { + use self::TcpHeaderField::*; + match *self { + Sport => 2, + Dport => 2, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum UdpHeaderField { + Sport, + Dport, + Len, +} + +impl HeaderField for UdpHeaderField { + fn offset(&self) -> u32 { + use self::UdpHeaderField::*; + match *self { + Sport => 0, + Dport => 2, + Len => 4, + } + } + + fn len(&self) -> u32 { + use self::UdpHeaderField::*; + match *self { + Sport => 2, + Dport => 2, + Len => 2, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +pub enum Icmpv6HeaderField { + Type, + Code, + Checksum, +} + +impl HeaderField for Icmpv6HeaderField { + fn offset(&self) -> u32 { + use self::Icmpv6HeaderField::*; + match *self { + Type => 0, + Code => 1, + Checksum => 2, + } + } + + fn len(&self) -> u32 { + use self::Icmpv6HeaderField::*; + match *self { + Type => 1, + Code => 1, + Checksum => 2, + } + } +} + +#[macro_export(local_inner_macros)] +macro_rules! nft_expr_payload { + (@ipv4_field ttl) => { + $crate::expr::Ipv4HeaderField::Ttl + }; + (@ipv4_field protocol) => { + $crate::expr::Ipv4HeaderField::Protocol + }; + (@ipv4_field saddr) => { + $crate::expr::Ipv4HeaderField::Saddr + }; + (@ipv4_field daddr) => { + $crate::expr::Ipv4HeaderField::Daddr + }; + + (@ipv6_field nextheader) => { + $crate::expr::Ipv6HeaderField::NextHeader + }; + (@ipv6_field hoplimit) => { + $crate::expr::Ipv6HeaderField::HopLimit + }; + (@ipv6_field saddr) => { + $crate::expr::Ipv6HeaderField::Saddr + }; + (@ipv6_field daddr) => { + $crate::expr::Ipv6HeaderField::Daddr + }; + + (@tcp_field sport) => { + $crate::expr::TcpHeaderField::Sport + }; + (@tcp_field dport) => { + $crate::expr::TcpHeaderField::Dport + }; + + (@udp_field sport) => { + $crate::expr::UdpHeaderField::Sport + }; + (@udp_field dport) => { + $crate::expr::UdpHeaderField::Dport + }; + (@udp_field len) => { + $crate::expr::UdpHeaderField::Len + }; + + (ethernet daddr) => { + $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::Daddr) + }; + (ethernet saddr) => { + $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::Saddr) + }; + (ethernet ethertype) => { + $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::EtherType) + }; + + (ipv4 $field:ident) => { + $crate::expr::Payload::Network($crate::expr::NetworkHeaderField::Ipv4( + nft_expr_payload!(@ipv4_field $field), + )) + }; + (ipv6 $field:ident) => { + $crate::expr::Payload::Network($crate::expr::NetworkHeaderField::Ipv6( + nft_expr_payload!(@ipv6_field $field), + )) + }; + + (tcp $field:ident) => { + $crate::expr::Payload::Transport($crate::expr::TransportHeaderField::Tcp( + nft_expr_payload!(@tcp_field $field), + )) + }; + (udp $field:ident) => { + $crate::expr::Payload::Transport($crate::expr::TransportHeaderField::Udp( + nft_expr_payload!(@udp_field $field), + )) + }; +} diff --git a/rustables/src/expr/verdict.rs b/rustables/src/expr/verdict.rs new file mode 100644 index 0000000..dc006bb --- /dev/null +++ b/rustables/src/expr/verdict.rs @@ -0,0 +1,175 @@ +use super::{Expression, Rule}; +use crate::ProtoFamily; +use rustables_sys::{ + self as sys, + libc::{self, c_char}, +}; +use std::ffi::{CStr, CString}; + +/// A verdict expression. In the background, this is usually an "Immediate" expression in nftnl +/// terms, but here it is simplified to only represent a verdict. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum Verdict { + /// Silently drop the packet. + Drop, + /// Accept the packet and let it pass. + Accept, + /// Reject the packet and return a message. + Reject(RejectionType), + Queue, + Continue, + Break, + Jump { + chain: CString, + }, + Goto { + chain: CString, + }, + Return, +} + +/// The type of rejection message sent by the Reject verdict. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum RejectionType { + /// Return an ICMP unreachable packet + Icmp(IcmpCode), + /// Reject by sending a TCP RST packet + TcpRst, +} + +impl RejectionType { + fn to_raw(&self, family: ProtoFamily) -> u32 { + use libc::*; + let value = match *self { + RejectionType::Icmp(..) => match family { + ProtoFamily::Bridge | ProtoFamily::Inet => NFT_REJECT_ICMPX_UNREACH, + _ => NFT_REJECT_ICMP_UNREACH, + }, + RejectionType::TcpRst => NFT_REJECT_TCP_RST, + }; + value as u32 + } +} + +/// An ICMP reject code. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[repr(u8)] +pub enum IcmpCode { + NoRoute = libc::NFT_REJECT_ICMPX_NO_ROUTE as u8, + PortUnreach = libc::NFT_REJECT_ICMPX_PORT_UNREACH as u8, + HostUnreach = libc::NFT_REJECT_ICMPX_HOST_UNREACH as u8, + AdminProhibited = libc::NFT_REJECT_ICMPX_ADMIN_PROHIBITED as u8, +} + +impl Verdict { + unsafe fn to_immediate_expr(&self, immediate_const: i32) -> *mut sys::nftnl_expr { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"immediate\0" as *const _ as *const c_char + )); + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_IMM_DREG as u16, + libc::NFT_REG_VERDICT as u32, + ); + + if let Some(chain) = self.chain() { + sys::nftnl_expr_set_str(expr, sys::NFTNL_EXPR_IMM_CHAIN as u16, chain.as_ptr()); + } + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_IMM_VERDICT as u16, + immediate_const as u32, + ); + + expr + } + + unsafe fn to_reject_expr( + &self, + reject_type: RejectionType, + family: ProtoFamily, + ) -> *mut sys::nftnl_expr { + let expr = try_alloc!(sys::nftnl_expr_alloc( + b"reject\0" as *const _ as *const c_char + )); + + sys::nftnl_expr_set_u32( + expr, + sys::NFTNL_EXPR_REJECT_TYPE as u16, + reject_type.to_raw(family), + ); + + let reject_code = match reject_type { + RejectionType::Icmp(code) => code as u8, + RejectionType::TcpRst => 0, + }; + + sys::nftnl_expr_set_u8(expr, sys::NFTNL_EXPR_REJECT_CODE as u16, reject_code); + + expr + } + + fn chain(&self) -> Option<&CStr> { + match *self { + Verdict::Jump { ref chain } => Some(chain.as_c_str()), + Verdict::Goto { ref chain } => Some(chain.as_c_str()), + _ => None, + } + } +} + +impl Expression for Verdict { + fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr { + let immediate_const = match *self { + Verdict::Drop => libc::NF_DROP, + Verdict::Accept => libc::NF_ACCEPT, + Verdict::Queue => libc::NF_QUEUE, + Verdict::Continue => libc::NFT_CONTINUE, + Verdict::Break => libc::NFT_BREAK, + Verdict::Jump { .. } => libc::NFT_JUMP, + Verdict::Goto { .. } => libc::NFT_GOTO, + Verdict::Return => libc::NFT_RETURN, + Verdict::Reject(reject_type) => { + return unsafe { + self.to_reject_expr(reject_type, rule.get_chain().get_table().get_family()) + } + } + }; + unsafe { self.to_immediate_expr(immediate_const) } + } +} + +#[macro_export] +macro_rules! nft_expr_verdict { + (drop) => { + $crate::expr::Verdict::Drop + }; + (accept) => { + $crate::expr::Verdict::Accept + }; + (reject icmp $code:expr) => { + $crate::expr::Verdict::Reject(RejectionType::Icmp($code)) + }; + (reject tcp-rst) => { + $crate::expr::Verdict::Reject(RejectionType::TcpRst) + }; + (queue) => { + $crate::expr::Verdict::Queue + }; + (continue) => { + $crate::expr::Verdict::Continue + }; + (break) => { + $crate::expr::Verdict::Break + }; + (jump $chain:expr) => { + $crate::expr::Verdict::Jump { chain: $chain } + }; + (goto $chain:expr) => { + $crate::expr::Verdict::Goto { chain: $chain } + }; + (return) => { + $crate::expr::Verdict::Return + }; +} |