Skip to content

Commit

Permalink
Add NAT rule type
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Sep 25, 2024
1 parent 18deb13 commit 5b5dd0b
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 6 deletions.
4 changes: 4 additions & 0 deletions examples/flush_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ fn main() {
.expect("Unable to flush filter rules");
println!("Flushed filter rules under anchor {}", anchor_name);

pf.flush_rules(&anchor_name, pfctl::RulesetKind::Nat)
.expect("Unable to flush nat rules");
println!("Flushed nat rules under anchor {}", anchor_name);

pf.flush_rules(&anchor_name, pfctl::RulesetKind::Redirect)
.expect("Unable to flush redirect rules");
println!("Flushed redirect rules under anchor {}", anchor_name);
Expand Down
2 changes: 2 additions & 0 deletions src/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::ffi;
#[non_exhaustive]
pub enum AnchorKind {
Filter,
Nat,
Redirect,
Scrub,
}
Expand All @@ -21,6 +22,7 @@ impl From<AnchorKind> for u8 {
fn from(anchor_kind: AnchorKind) -> u8 {
match anchor_kind {
AnchorKind::Filter => ffi::pfvar::PF_PASS as u8,
AnchorKind::Nat => ffi::pfvar::PF_NAT as u8,
AnchorKind::Redirect => ffi::pfvar::PF_RDR as u8,
AnchorKind::Scrub => ffi::pfvar::PF_SCRUB as u8,
}
Expand Down
26 changes: 26 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,31 @@ impl PfCtl {
trans.commit()
}

pub fn add_nat_rule(&mut self, anchor: &str, rule: &NatRule) -> Result<()> {
// prepare pfioc_rule
let mut pfioc_rule = unsafe { mem::zeroed::<ffi::pfvar::pfioc_rule>() };
utils::copy_anchor_name(anchor, &mut pfioc_rule.anchor[..])?;
rule.try_copy_to(&mut pfioc_rule.rule)?;

// register NAT address in newly created address pool
let nat_to = rule.get_nat_to();
let pool_ticket = utils::get_pool_ticket(self.fd())?;
utils::add_pool_address(self.fd(), nat_to.ip(), pool_ticket)?;

// copy address pool in pf_rule
let nat_pool = nat_to.ip().to_pool_addr_list()?;
pfioc_rule.rule.rpool.list = unsafe { nat_pool.to_palist() };
nat_to.port().try_copy_to(&mut pfioc_rule.rule.rpool)?;

// set tickets
pfioc_rule.pool_ticket = pool_ticket;
pfioc_rule.ticket = utils::get_ticket(self.fd(), anchor, AnchorKind::Nat)?;

// append rule
pfioc_rule.action = ffi::pfvar::PF_CHANGE_ADD_TAIL as u32;
ioctl_guard!(ffi::pf_change_rule(self.fd(), &mut pfioc_rule))
}

pub fn add_redirect_rule(&mut self, anchor: &str, rule: &RedirectRule) -> Result<()> {
// prepare pfioc_rule
let mut pfioc_rule = unsafe { mem::zeroed::<ffi::pfvar::pfioc_rule>() };
Expand Down Expand Up @@ -402,6 +427,7 @@ impl PfCtl {
let mut anchor_change = AnchorChange::new();
match kind {
RulesetKind::Filter => anchor_change.set_filter_rules(Vec::new()),
RulesetKind::Nat => anchor_change.set_nat_rules(Vec::new()),
RulesetKind::Redirect => anchor_change.set_redirect_rules(Vec::new()),
RulesetKind::Scrub => anchor_change.set_scrub_rules(Vec::new()),
};
Expand Down
84 changes: 84 additions & 0 deletions src/rule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,90 @@ impl TryCopyTo<ffi::pfvar::pf_rule> for FilterRule {
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_builder::Builder)]
#[builder(setter(into))]
#[builder(build_fn(error = "Error"))]
pub struct NatRule {
action: NatRuleAction,
#[builder(default)]
interface: Interface,
#[builder(default)]
af: AddrFamily,
#[builder(default)]
from: Endpoint,
#[builder(default)]
to: Endpoint,
nat_to: NatEndpoint,
}

impl NatRule {
/// Returns the `AddrFamily` this rule matches against. Returns an `InvalidRuleCombination`
/// error if this rule has an invalid combination of address families.
fn get_af(&self) -> Result<AddrFamily> {
let endpoint_af = compatible_af(self.from.get_af(), self.to.get_af())?;
let nat_af = compatible_af(endpoint_af, self.nat_to.0.get_af())?;
compatible_af(self.af, nat_af)
}

/// Accessor for `nat_to`
pub fn get_nat_to(&self) -> Endpoint {
self.nat_to.0
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct NatEndpoint(Endpoint);

impl From<Ip> for NatEndpoint {
fn from(ip: Ip) -> Self {
// Default NAT port range
const NAT_LOWER_DEFAULT: u16 = 32768;
const NAT_UPPER_DEFAULT: u16 = 49151;

Self(Endpoint::new(
ip,
Port::Range(
NAT_LOWER_DEFAULT,
NAT_UPPER_DEFAULT,
PortRangeModifier::Inclusive,
),
))
}
}

impl From<Endpoint> for NatEndpoint {
fn from(endpoint: Endpoint) -> Self {
Self(endpoint)
}
}

impl From<Ipv4Addr> for NatEndpoint {
fn from(ip: Ipv4Addr) -> Self {
Self::from(Ip::from(ip))
}
}

impl From<Ipv6Addr> for NatEndpoint {
fn from(ip: Ipv6Addr) -> Self {
Self::from(Ip::from(ip))
}
}

impl TryCopyTo<ffi::pfvar::pf_rule> for NatRule {
type Error = crate::Error;

fn try_copy_to(&self, pf_rule: &mut ffi::pfvar::pf_rule) -> Result<()> {
pf_rule.action = self.action.into();
self.interface.try_copy_to(&mut pf_rule.ifname)?;
pf_rule.af = self.get_af()?.into();

self.from.try_copy_to(&mut pf_rule.src)?;
self.to.try_copy_to(&mut pf_rule.dst)?;

Ok(())
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_builder::Builder)]
#[builder(setter(into))]
#[builder(build_fn(error = "Error"))]
Expand Down
14 changes: 14 additions & 0 deletions src/rule/rule_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ impl From<DropAction> for u32 {
}
}

/// Enum describing what should happen to a packet that matches a NAT rule.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NatRuleAction {
Nat,
}

impl From<NatRuleAction> for u8 {
fn from(rule_action: NatRuleAction) -> Self {
match rule_action {
NatRuleAction::Nat => ffi::pfvar::PF_NAT as u8,
}
}
}

/// Enum describing what should happen to a packet that matches a redirect rule.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RedirectRuleAction {
Expand Down
2 changes: 2 additions & 0 deletions src/ruleset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::ffi;
#[non_exhaustive]
pub enum RulesetKind {
Filter,
Nat,
Redirect,
Scrub,
}
Expand All @@ -21,6 +22,7 @@ impl From<RulesetKind> for i32 {
fn from(ruleset_kind: RulesetKind) -> Self {
match ruleset_kind {
RulesetKind::Filter => ffi::pfvar::PF_RULESET_FILTER as i32,
RulesetKind::Nat => ffi::pfvar::PF_RULESET_NAT as i32,
RulesetKind::Redirect => ffi::pfvar::PF_RULESET_RDR as i32,
RulesetKind::Scrub => ffi::pfvar::PF_RULESET_SCRUB as i32,
}
Expand Down
56 changes: 54 additions & 2 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// except according to those terms.

use crate::{
conversion::TryCopyTo, ffi, utils, FilterRule, PoolAddrList, RedirectRule, Result, RulesetKind,
ScrubRule,
conversion::TryCopyTo, ffi, utils, FilterRule, NatRule, PoolAddrList, RedirectRule, Result,
RulesetKind, ScrubRule,
};
use std::{
collections::HashMap,
Expand Down Expand Up @@ -60,6 +60,13 @@ impl Transaction {
.map(|rules| (anchor.clone(), rules))
})
.collect();
let nat_changes: Vec<(String, Vec<NatRule>)> = self
.change_by_anchor
.iter_mut()
.filter_map(|(anchor, change)| {
change.nat_rules.take().map(|rules| (anchor.clone(), rules))
})
.collect();
let redirect_changes: Vec<(String, Vec<RedirectRule>)> = self
.change_by_anchor
.iter_mut()
Expand Down Expand Up @@ -87,6 +94,11 @@ impl Transaction {
let mut pfioc_elements: Vec<ffi::pfvar::pfioc_trans_pfioc_trans_e> = filter_changes
.iter()
.map(|(anchor, _)| Self::new_trans_element(anchor, RulesetKind::Filter))
.chain(
nat_changes
.iter()
.map(|(anchor, _)| Self::new_trans_element(anchor, RulesetKind::Nat)),
)
.chain(
redirect_changes
.iter()
Expand Down Expand Up @@ -115,6 +127,15 @@ impl Transaction {
}
}

// add NAT rules into transaction
for ((anchor_name, nat_rules), ticket) in
nat_changes.into_iter().zip(ticket_iterator.by_ref())
{
for nat_rule in nat_rules.iter() {
Self::add_nat_rule(fd, &anchor_name, nat_rule, ticket)?;
}
}

// add redirect rules into transaction
for ((anchor_name, redirect_rules), ticket) in
redirect_changes.into_iter().zip(ticket_iterator.by_ref())
Expand Down Expand Up @@ -170,6 +191,31 @@ impl Transaction {
Ok(())
}

/// Internal helper to add nat rule into transaction
fn add_nat_rule(fd: RawFd, anchor: &str, rule: &NatRule, ticket: u32) -> Result<()> {
// prepare pfioc_rule
let mut pfioc_rule = unsafe { mem::zeroed::<ffi::pfvar::pfioc_rule>() };
utils::copy_anchor_name(anchor, &mut pfioc_rule.anchor[..])?;
rule.try_copy_to(&mut pfioc_rule.rule)?;

// register NAT address in newly created address pool
let nat_to = rule.get_nat_to();
let pool_ticket = utils::get_pool_ticket(fd)?;
utils::add_pool_address(fd, nat_to.ip(), pool_ticket)?;

// copy address pool in pf_rule
let nat_pool = nat_to.ip().to_pool_addr_list()?;
pfioc_rule.rule.rpool.list = unsafe { nat_pool.to_palist() };
nat_to.port().try_copy_to(&mut pfioc_rule.rule.rpool)?;

// set tickets
pfioc_rule.pool_ticket = pool_ticket;
pfioc_rule.ticket = ticket;

// add rule into transaction
ioctl_guard!(ffi::pf_add_rule(fd, &mut pfioc_rule))
}

/// Internal helper to add redirect rule into transaction
fn add_redirect_rule(fd: RawFd, anchor: &str, rule: &RedirectRule, ticket: u32) -> Result<()> {
// prepare pfioc_rule
Expand Down Expand Up @@ -242,6 +288,7 @@ impl Transaction {
#[derive(Debug)]
pub struct AnchorChange {
filter_rules: Option<Vec<FilterRule>>,
nat_rules: Option<Vec<NatRule>>,
redirect_rules: Option<Vec<RedirectRule>>,
scrub_rules: Option<Vec<ScrubRule>>,
}
Expand All @@ -257,6 +304,7 @@ impl AnchorChange {
pub fn new() -> Self {
AnchorChange {
filter_rules: None,
nat_rules: None,
redirect_rules: None,
scrub_rules: None,
}
Expand All @@ -266,6 +314,10 @@ impl AnchorChange {
self.filter_rules = Some(rules);
}

pub fn set_nat_rules(&mut self, rules: Vec<NatRule>) {
self.nat_rules = Some(rules);
}

pub fn set_redirect_rules(&mut self, rules: Vec<RedirectRule>) {
self.redirect_rules = Some(rules);
}
Expand Down
Loading

0 comments on commit 5b5dd0b

Please sign in to comment.