use std::net::{Ipv4Addr, Ipv6Addr};
use super::expr::Expr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum Family {
Ip = 2,
Ip6 = 10,
Inet = 1,
Arp = 3,
Bridge = 7,
Netdev = 5,
}
impl Family {
pub fn from_u8(val: u8) -> Option<Self> {
match val {
2 => Some(Self::Ip),
10 => Some(Self::Ip6),
1 => Some(Self::Inet),
3 => Some(Self::Arp),
7 => Some(Self::Bridge),
5 => Some(Self::Netdev),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Hook {
Prerouting,
Input,
Forward,
Output,
Postrouting,
Ingress,
}
impl Hook {
pub fn to_u32(self) -> u32 {
match self {
Self::Prerouting => 0,
Self::Input => 1,
Self::Forward => 2,
Self::Output => 3,
Self::Postrouting => 4,
Self::Ingress => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChainType {
Filter,
Nat,
Route,
}
impl ChainType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Filter => "filter",
Self::Nat => "nat",
Self::Route => "route",
}
}
pub fn from_kernel_string(s: &str) -> Option<Self> {
match s {
"filter" => Some(Self::Filter),
"nat" => Some(Self::Nat),
"route" => Some(Self::Route),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Priority {
Raw,
Mangle,
DstNat,
Filter,
Security,
SrcNat,
Custom(i32),
}
impl Priority {
pub fn to_i32(self) -> i32 {
match self {
Self::Raw => -300,
Self::Mangle => -150,
Self::DstNat => -100,
Self::Filter => 0,
Self::Security => 50,
Self::SrcNat => 100,
Self::Custom(v) => v,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Policy {
Accept,
Drop,
}
impl Policy {
pub fn to_u32(self) -> u32 {
match self {
Self::Accept => 1,
Self::Drop => 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Verdict {
Accept,
Drop,
Continue,
Return,
Jump(String),
Goto(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CtState(pub u32);
impl CtState {
pub const INVALID: Self = Self(1);
pub const ESTABLISHED: Self = Self(2);
pub const RELATED: Self = Self(4);
pub const NEW: Self = Self(8);
pub const UNTRACKED: Self = Self(64);
pub const fn empty() -> Self {
Self(0)
}
pub fn bits(self) -> u32 {
self.0
}
}
impl Default for CtState {
fn default() -> Self {
Self::empty()
}
}
impl std::ops::BitOr for CtState {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for CtState {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LimitUnit {
Second,
Minute,
Hour,
Day,
}
impl LimitUnit {
pub fn to_u64(self) -> u64 {
match self {
Self::Second => 1,
Self::Minute => 60,
Self::Hour => 3600,
Self::Day => 86400,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
#[non_exhaustive]
pub enum Register {
Verdict = 0,
R0 = 1,
R1 = 2,
R2 = 3,
R3 = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CmpOp {
Eq = 0,
Neq = 1,
Lt = 2,
Lte = 3,
Gt = 4,
Gte = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PayloadBase {
LinkLayer = 0,
Network = 1,
Transport = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MetaKey {
Len = 0,
Protocol = 1,
Mark = 3,
Iif = 4,
Oif = 5,
IifName = 6,
OifName = 7,
SkUid = 10,
SkGid = 11,
NfProto = 15,
L4Proto = 16,
CGroup = 23,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CtKey {
State = 0,
Direction = 1,
Status = 2,
Mark = 3,
Expiration = 7,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NatType {
Snat = 0,
Dnat = 1,
}
#[derive(Debug, Clone)]
pub struct NatExpr {
pub nat_type: NatType,
pub family: Family,
pub addr: Option<Ipv4Addr>,
pub port: Option<u16>,
}
impl NatExpr {
pub fn snat(family: Family) -> Self {
Self {
nat_type: NatType::Snat,
family,
addr: None,
port: None,
}
}
pub fn dnat(family: Family) -> Self {
Self {
nat_type: NatType::Dnat,
family,
addr: None,
port: None,
}
}
pub fn addr(mut self, addr: Ipv4Addr) -> Self {
self.addr = Some(addr);
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
}
#[derive(Debug, Clone)]
pub struct Flowtable {
pub family: Family,
pub table: String,
pub name: String,
pub devs: Vec<String>,
pub priority: i32,
pub flags: u32,
pub use_count: u32,
pub handle: u64,
}
impl Flowtable {
pub fn new(
family: Family,
table: impl Into<String>,
name: impl Into<String>,
) -> Self {
Self {
family,
table: table.into(),
name: name.into(),
devs: Vec::new(),
priority: 0,
flags: 0,
use_count: 0,
handle: 0,
}
}
pub fn device(mut self, dev: impl Into<String>) -> Self {
self.devs.push(dev.into());
self
}
pub fn priority(mut self, p: i32) -> Self {
self.priority = p;
self
}
pub fn hw_offload(mut self, on: bool) -> Self {
if on {
self.flags |= super::NFT_FLOWTABLE_HW_OFFLOAD;
} else {
self.flags &= !super::NFT_FLOWTABLE_HW_OFFLOAD;
}
self
}
pub fn counter(mut self, on: bool) -> Self {
if on {
self.flags |= super::NFT_FLOWTABLE_COUNTER;
} else {
self.flags &= !super::NFT_FLOWTABLE_COUNTER;
}
self
}
}
#[derive(Debug, Clone)]
pub struct Table {
pub name: String,
pub family: Family,
pub flags: u32,
pub use_count: u32,
pub handle: u64,
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct Chain {
pub(crate) table: String,
pub(crate) name: String,
pub(crate) family: Family,
pub(crate) hook: Option<Hook>,
pub(crate) priority: Option<Priority>,
pub(crate) chain_type: Option<ChainType>,
pub(crate) policy: Option<Policy>,
pub(crate) device: Option<String>,
}
impl Chain {
pub fn new(table: &str, name: &str) -> Self {
Self {
table: table.to_string(),
name: name.to_string(),
family: Family::Inet,
hook: None,
priority: None,
chain_type: None,
policy: None,
device: None,
}
}
pub fn family(mut self, family: Family) -> Self {
self.family = family;
self
}
pub fn hook(mut self, hook: Hook) -> Self {
self.hook = Some(hook);
self
}
pub fn priority(mut self, priority: Priority) -> Self {
self.priority = Some(priority);
self
}
pub fn chain_type(mut self, chain_type: ChainType) -> Self {
self.chain_type = Some(chain_type);
self
}
pub fn policy(mut self, policy: Policy) -> Self {
self.policy = Some(policy);
self
}
pub fn device(mut self, dev: impl Into<String>) -> Self {
self.device = Some(dev.into());
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ChainInfo {
pub table: String,
pub name: String,
pub family: Family,
pub hook: Option<u32>,
pub priority: Option<i32>,
pub chain_type: Option<ChainType>,
pub device: Option<String>,
pub policy: Option<u32>,
pub handle: u64,
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct Rule {
pub(crate) table: String,
pub(crate) chain: String,
pub(crate) family: Family,
pub(crate) exprs: Vec<super::expr::Expr>,
pub(crate) position: Option<u64>,
pub(crate) comment: Option<String>,
}
impl Rule {
pub fn new(table: &str, chain: &str) -> Self {
Self {
table: table.to_string(),
chain: chain.to_string(),
family: Family::Inet,
exprs: Vec::new(),
position: None,
comment: None,
}
}
pub fn family(mut self, family: Family) -> Self {
self.family = family;
self
}
pub fn position(mut self, pos: u64) -> Self {
self.position = Some(pos);
self
}
pub fn comment(mut self, comment: &str) -> Self {
self.comment = Some(comment.to_string());
self
}
pub fn comment_ref(&self) -> Option<&str> {
self.comment.as_deref()
}
pub fn match_tcp_dport(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![6u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 2,
len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: port.to_be_bytes().to_vec(),
});
self
}
pub fn match_udp_dport(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![17u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 2,
len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: port.to_be_bytes().to_vec(),
});
self
}
fn push_addr_match(
&mut self,
octets: &[u8],
offset: u32,
prefix: u8,
full_prefix: u8,
op: CmpOp,
) {
let len = octets.len() as u32;
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Network,
offset,
len,
});
if prefix >= full_prefix {
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op,
data: octets.to_vec(),
});
return;
}
let mask = prefix_to_mask(octets.len(), prefix);
let masked: Vec<u8> = octets.iter().zip(&mask).map(|(a, m)| a & m).collect();
self.exprs.push(Expr::Bitwise {
sreg: Register::R0,
dreg: Register::R0,
len,
mask,
xor: vec![0; octets.len()],
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op,
data: masked,
});
}
pub fn match_saddr_v4(mut self, addr: Ipv4Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 12, prefix, 32, CmpOp::Eq);
self
}
pub fn match_saddr_v6(mut self, addr: Ipv6Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 8, prefix, 128, CmpOp::Eq);
self
}
pub fn match_daddr_v4(mut self, addr: Ipv4Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 16, prefix, 32, CmpOp::Eq);
self
}
pub fn match_daddr_v6(mut self, addr: Ipv6Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 24, prefix, 128, CmpOp::Eq);
self
}
pub fn match_iif(mut self, name: &str) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::IifName,
});
let mut data = name.as_bytes().to_vec();
data.push(0); data.resize(16, 0);
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data,
});
self
}
pub fn match_oif(mut self, name: &str) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::OifName,
});
let mut data = name.as_bytes().to_vec();
data.push(0);
data.resize(16, 0);
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data,
});
self
}
pub fn match_ct_state(mut self, state: CtState) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Ct {
dreg: Register::R0,
key: CtKey::State,
});
self.exprs.push(Expr::Bitwise {
sreg: Register::R0,
dreg: Register::R0,
len: 4,
mask: state.bits().to_ne_bytes().to_vec(),
xor: vec![0; 4],
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Neq,
data: vec![0; 4],
});
self
}
pub fn accept(mut self) -> Self {
self.exprs.push(super::expr::Expr::Verdict(Verdict::Accept));
self
}
pub fn drop(mut self) -> Self {
self.exprs.push(super::expr::Expr::Verdict(Verdict::Drop));
self
}
pub fn jump(mut self, chain: &str) -> Self {
self.exprs
.push(super::expr::Expr::Verdict(Verdict::Jump(chain.to_string())));
self
}
pub fn goto(mut self, chain: &str) -> Self {
self.exprs
.push(super::expr::Expr::Verdict(Verdict::Goto(chain.to_string())));
self
}
pub fn counter(mut self) -> Self {
self.exprs.push(super::expr::Expr::Counter);
self
}
pub fn limit(mut self, rate: u64, unit: LimitUnit) -> Self {
self.exprs.push(super::expr::Expr::Limit {
rate,
unit,
burst: 5,
});
self
}
pub fn masquerade(mut self) -> Self {
self.exprs.push(super::expr::Expr::Masquerade);
self
}
pub fn snat(mut self, addr: Ipv4Addr, port: Option<u16>) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Immediate {
dreg: Register::R0,
data: addr.octets().to_vec(),
});
if let Some(p) = port {
self.exprs.push(Expr::Immediate {
dreg: Register::R1,
data: p.to_be_bytes().to_vec(),
});
}
self.exprs.push(Expr::Nat(NatExpr {
nat_type: NatType::Snat,
family: Family::Ip,
addr: Some(addr),
port,
}));
self
}
pub fn dnat(mut self, addr: Ipv4Addr, port: Option<u16>) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Immediate {
dreg: Register::R0,
data: addr.octets().to_vec(),
});
if let Some(p) = port {
self.exprs.push(Expr::Immediate {
dreg: Register::R1,
data: p.to_be_bytes().to_vec(),
});
}
self.exprs.push(Expr::Nat(NatExpr {
nat_type: NatType::Dnat,
family: Family::Ip,
addr: Some(addr),
port,
}));
self
}
pub fn redirect(mut self, port: Option<u16>) -> Self {
use super::expr::Expr;
if let Some(p) = port {
self.exprs.push(Expr::Immediate {
dreg: Register::R0,
data: p.to_be_bytes().to_vec(),
});
}
self.exprs.push(Expr::Redirect { port });
self
}
pub fn log(mut self, prefix: Option<&str>) -> Self {
self.exprs.push(super::expr::Expr::Log {
prefix: prefix.map(String::from),
group: None,
});
self
}
pub fn match_saddr_in_set(mut self, set: &str) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Network,
offset: 12,
len: 4,
});
self.exprs.push(Expr::Lookup {
set: set.to_string(),
sreg: Register::R0,
});
self
}
pub fn match_daddr_in_set(mut self, set: &str) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Network,
offset: 16,
len: 4,
});
self.exprs.push(Expr::Lookup {
set: set.to_string(),
sreg: Register::R0,
});
self
}
pub fn match_l4proto(mut self, proto: u8) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![proto],
});
self
}
pub fn match_tcp_sport(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![6u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 0, len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: port.to_be_bytes().to_vec(),
});
self
}
pub fn match_udp_sport(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![17u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 0,
len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: port.to_be_bytes().to_vec(),
});
self
}
pub fn match_icmp_type(mut self, icmp_type: u8) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![1u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 0,
len: 1,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![icmp_type],
});
self
}
pub fn match_icmpv6_type(mut self, icmp_type: u8) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![58u8], });
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 0,
len: 1,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![icmp_type],
});
self
}
pub fn match_saddr_v4_not(mut self, addr: Ipv4Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 12, prefix, 32, CmpOp::Neq);
self
}
pub fn match_saddr_v6_not(mut self, addr: Ipv6Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 8, prefix, 128, CmpOp::Neq);
self
}
pub fn match_daddr_v4_not(mut self, addr: Ipv4Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 16, prefix, 32, CmpOp::Neq);
self
}
pub fn match_daddr_v6_not(mut self, addr: Ipv6Addr, prefix: u8) -> Self {
self.push_addr_match(&addr.octets(), 24, prefix, 128, CmpOp::Neq);
self
}
pub fn match_tcp_dport_not(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![6u8],
});
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 2,
len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Neq,
data: port.to_be_bytes().to_vec(),
});
self
}
pub fn match_udp_dport_not(mut self, port: u16) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: vec![17u8],
});
self.exprs.push(Expr::Payload {
dreg: Register::R0,
base: PayloadBase::Transport,
offset: 2,
len: 2,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Neq,
data: port.to_be_bytes().to_vec(),
});
self
}
pub fn match_mark(mut self, mark: u32) -> Self {
use super::expr::Expr;
self.exprs.push(Expr::Meta {
dreg: Register::R0,
key: MetaKey::Mark,
});
self.exprs.push(Expr::Cmp {
sreg: Register::R0,
op: CmpOp::Eq,
data: mark.to_ne_bytes().to_vec(),
});
self
}
pub fn reject(mut self) -> Self {
self.exprs.push(super::expr::Expr::Verdict(Verdict::Drop));
self
}
pub fn expressions(mut self, exprs: Vec<super::expr::Expr>) -> Self {
self.exprs = exprs;
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct RuleInfo {
pub table: String,
pub chain: String,
pub family: Family,
pub handle: u64,
pub position: Option<u64>,
pub comment: Option<String>,
pub userdata_raw: Option<Vec<u8>>,
pub expression_bytes: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SetKeyType {
Ipv4Addr,
Ipv6Addr,
EtherAddr,
InetService,
IfIndex,
Mark,
}
impl SetKeyType {
pub fn type_id(self) -> u32 {
match self {
Self::Ipv4Addr => 7, Self::Ipv6Addr => 8, Self::EtherAddr => 9, Self::InetService => 13, Self::IfIndex => 15, Self::Mark => 12, }
}
#[allow(clippy::len_without_is_empty)]
pub fn len(self) -> u32 {
match self {
Self::Ipv4Addr => 4,
Self::Ipv6Addr => 16,
Self::EtherAddr => 8, Self::InetService => 2,
Self::IfIndex => 4,
Self::Mark => 4,
}
}
}
#[derive(Debug, Clone)]
#[must_use = "builders do nothing unless used"]
pub struct Set {
pub(crate) table: String,
pub(crate) name: String,
pub(crate) family: Family,
pub(crate) key_type: SetKeyType,
pub(crate) flags: u32,
}
impl Set {
pub fn new(table: &str, name: &str) -> Self {
Self {
table: table.to_string(),
name: name.to_string(),
family: Family::Inet,
key_type: SetKeyType::Ipv4Addr,
flags: 0,
}
}
pub fn family(mut self, family: Family) -> Self {
self.family = family;
self
}
pub fn key_type(mut self, key_type: SetKeyType) -> Self {
self.key_type = key_type;
self
}
pub fn constant(mut self) -> Self {
self.flags |= super::NFT_SET_CONSTANT;
self
}
}
#[derive(Debug, Clone)]
pub struct SetElement {
pub key: Vec<u8>,
}
impl SetElement {
pub fn new(key: Vec<u8>) -> Self {
Self { key }
}
pub fn ipv4(addr: Ipv4Addr) -> Self {
Self {
key: addr.octets().to_vec(),
}
}
pub fn ipv6(addr: std::net::Ipv6Addr) -> Self {
Self {
key: addr.octets().to_vec(),
}
}
pub fn port(port: u16) -> Self {
Self {
key: port.to_be_bytes().to_vec(),
}
}
}
#[derive(Debug, Clone)]
pub struct SetInfo {
pub table: String,
pub name: String,
pub family: Family,
pub flags: u32,
pub key_type: u32,
pub key_len: u32,
pub handle: u64,
}
fn prefix_to_mask(width: usize, prefix: u8) -> Vec<u8> {
let prefix = (prefix as usize).min(width * 8);
let full_bytes = prefix / 8;
let remainder = prefix % 8;
let mut mask = vec![0u8; width];
for byte in mask.iter_mut().take(full_bytes) {
*byte = 0xff;
}
if remainder != 0 && full_bytes < width {
mask[full_bytes] = 0xff << (8 - remainder);
}
mask
}
#[cfg(test)]
mod tests {
use super::*;
fn find_nat_expr(rule: &Rule) -> Option<&NatExpr> {
rule.exprs.iter().find_map(|e| match e {
super::super::expr::Expr::Nat(n) => Some(n),
_ => None,
})
}
#[test]
fn dnat_inet_table_uses_ip_family() {
let rule = Rule::new("nat", "prerouting")
.family(Family::Inet)
.dnat("10.0.0.1".parse().unwrap(), Some(8080));
let nat = find_nat_expr(&rule).expect("should have NAT expr");
assert_eq!(nat.family, Family::Ip);
assert_eq!(nat.nat_type, NatType::Dnat);
}
#[test]
fn snat_inet_table_uses_ip_family() {
let rule = Rule::new("nat", "postrouting")
.family(Family::Inet)
.snat("10.0.0.1".parse().unwrap(), None);
let nat = find_nat_expr(&rule).expect("should have NAT expr");
assert_eq!(nat.family, Family::Ip);
assert_eq!(nat.nat_type, NatType::Snat);
}
#[test]
fn dnat_ip_table_uses_ip_family() {
let rule = Rule::new("nat", "prerouting")
.family(Family::Ip)
.dnat("192.168.1.1".parse().unwrap(), Some(80));
let nat = find_nat_expr(&rule).expect("should have NAT expr");
assert_eq!(nat.family, Family::Ip);
}
#[test]
fn snat_with_port() {
let rule = Rule::new("nat", "postrouting")
.family(Family::Inet)
.snat("192.168.1.1".parse().unwrap(), Some(1024));
let nat = find_nat_expr(&rule).expect("should have NAT expr");
assert_eq!(nat.family, Family::Ip);
assert_eq!(nat.port, Some(1024));
assert_eq!(nat.addr, Some("192.168.1.1".parse().unwrap()));
}
use super::super::expr::Expr;
fn payload_exprs(rule: &Rule) -> Vec<(PayloadBase, u32, u32)> {
rule.exprs
.iter()
.filter_map(|e| match e {
Expr::Payload {
base, offset, len, ..
} => Some((*base, *offset, *len)),
_ => None,
})
.collect()
}
fn cmp_exprs(rule: &Rule) -> Vec<(CmpOp, Vec<u8>)> {
rule.exprs
.iter()
.filter_map(|e| match e {
Expr::Cmp { op, data, .. } => Some((*op, data.clone())),
_ => None,
})
.collect()
}
fn bitwise_exprs(rule: &Rule) -> Vec<Vec<u8>> {
rule.exprs
.iter()
.filter_map(|e| match e {
Expr::Bitwise { mask, .. } => Some(mask.clone()),
_ => None,
})
.collect()
}
#[test]
fn match_saddr_v6_exact() {
let addr: Ipv6Addr = "2001:db8::1".parse().unwrap();
let rule = Rule::new("filter", "input").match_saddr_v6(addr, 128);
let payloads = payload_exprs(&rule);
let cmps = cmp_exprs(&rule);
assert_eq!(payloads, vec![(PayloadBase::Network, 8, 16)]);
assert_eq!(cmps.len(), 1);
assert_eq!(cmps[0].0, CmpOp::Eq);
assert_eq!(cmps[0].1, addr.octets().to_vec());
assert!(bitwise_exprs(&rule).is_empty(), "no mask for /128");
}
#[test]
fn match_saddr_v6_with_prefix() {
let addr: Ipv6Addr = "2001:db8:cafe::beef".parse().unwrap();
let rule = Rule::new("filter", "input").match_saddr_v6(addr, 64);
let payloads = payload_exprs(&rule);
let cmps = cmp_exprs(&rule);
let masks = bitwise_exprs(&rule);
assert_eq!(payloads, vec![(PayloadBase::Network, 8, 16)]);
assert_eq!(masks.len(), 1);
assert_eq!(masks[0], prefix_to_mask(16, 64));
assert_eq!(cmps.len(), 1);
assert_eq!(cmps[0].0, CmpOp::Eq);
let expected: Vec<u8> = addr
.octets()
.iter()
.zip(prefix_to_mask(16, 64).iter())
.map(|(a, m)| a & m)
.collect();
assert_eq!(cmps[0].1, expected);
}
#[test]
fn match_saddr_v6_overlong_prefix_takes_fast_path() {
let addr: Ipv6Addr = "2001:db8::1".parse().unwrap();
let rule = Rule::new("filter", "input").match_saddr_v6(addr, 200);
assert!(bitwise_exprs(&rule).is_empty());
let cmps = cmp_exprs(&rule);
assert_eq!(cmps.len(), 1);
assert_eq!(cmps[0].1, addr.octets().to_vec());
}
#[test]
fn match_daddr_v6_uses_offset_24() {
let addr: Ipv6Addr = "fd00::1".parse().unwrap();
let rule = Rule::new("filter", "output").match_daddr_v6(addr, 128);
let payloads = payload_exprs(&rule);
assert_eq!(payloads, vec![(PayloadBase::Network, 24, 16)]);
}
#[test]
fn match_saddr_v6_not_flips_op_to_neq() {
let addr: Ipv6Addr = "2001:db8::1".parse().unwrap();
let rule = Rule::new("filter", "input").match_saddr_v6_not(addr, 128);
let cmps = cmp_exprs(&rule);
assert_eq!(cmps.len(), 1);
assert_eq!(cmps[0].0, CmpOp::Neq);
}
#[test]
fn match_daddr_v6_not_uses_offset_24_and_neq() {
let addr: Ipv6Addr = "2001:db8::abcd".parse().unwrap();
let rule = Rule::new("filter", "output").match_daddr_v6_not(addr, 64);
let payloads = payload_exprs(&rule);
let cmps = cmp_exprs(&rule);
assert_eq!(payloads, vec![(PayloadBase::Network, 24, 16)]);
assert_eq!(cmps.len(), 1);
assert_eq!(cmps[0].0, CmpOp::Neq);
}
#[test]
fn prefix_to_mask_boundaries() {
assert_eq!(prefix_to_mask(4, 0), vec![0u8; 4]);
assert_eq!(prefix_to_mask(4, 32), vec![0xff; 4]);
assert_eq!(prefix_to_mask(4, 24), vec![0xff, 0xff, 0xff, 0x00]);
assert_eq!(prefix_to_mask(16, 0), vec![0u8; 16]);
assert_eq!(prefix_to_mask(16, 128), vec![0xff; 16]);
let mut sixty_four = vec![0u8; 16];
for byte in sixty_four.iter_mut().take(8) {
*byte = 0xff;
}
assert_eq!(prefix_to_mask(16, 64), sixty_four);
let mut sixty_eight = vec![0u8; 16];
for byte in sixty_eight.iter_mut().take(8) {
*byte = 0xff;
}
sixty_eight[8] = 0xf0;
assert_eq!(prefix_to_mask(16, 68), sixty_eight);
assert_eq!(prefix_to_mask(16, 200), vec![0xff; 16]);
}
}