aboutsummaryrefslogtreecommitdiff
path: root/rustables/src/expr
diff options
context:
space:
mode:
Diffstat (limited to 'rustables/src/expr')
-rw-r--r--rustables/src/expr/bitwise.rs68
-rw-r--r--rustables/src/expr/cmp.rs230
-rw-r--r--rustables/src/expr/counter.rs13
-rw-r--r--rustables/src/expr/ct.rs65
-rw-r--r--rustables/src/expr/immediate.rs54
-rw-r--r--rustables/src/expr/log.rs12
-rw-r--r--rustables/src/expr/lookup.rs56
-rw-r--r--rustables/src/expr/masquerade.rs12
-rw-r--r--rustables/src/expr/meta.rs134
-rw-r--r--rustables/src/expr/mod.rs113
-rw-r--r--rustables/src/expr/nat.rs48
-rw-r--r--rustables/src/expr/payload.rs368
-rw-r--r--rustables/src/expr/verdict.rs175
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
+ };
+}