#[macro_use]
#[allow(dead_code)]
mod helper;
use crate::helper::pfcli;
use assert_matches::assert_matches;
use std::net::Ipv4Addr;
const ANCHOR1_NAME: &str = "pfctl-rs.integration.testing.transactions-1";
const ANCHOR2_NAME: &str = "pfctl-rs.integration.testing.transactions-2";
const ANCHOR3_NAME: &str = "pfctl-rs.integration.testing.transactions-3";
const ANCHOR4_NAME: &str = "pfctl-rs.integration.testing.transactions-4";
const ANCHORS: [&str; 4] = [ANCHOR1_NAME, ANCHOR2_NAME, ANCHOR3_NAME, ANCHOR4_NAME];
fn before_each() {
for anchor_name in ANCHORS.iter() {
pfctl::PfCtl::new()
.unwrap()
.try_add_anchor(anchor_name, pfctl::AnchorKind::Filter)
.unwrap();
pfctl::PfCtl::new()
.unwrap()
.try_add_anchor(anchor_name, pfctl::AnchorKind::Nat)
.unwrap();
pfctl::PfCtl::new()
.unwrap()
.try_add_anchor(anchor_name, pfctl::AnchorKind::Redirect)
.unwrap();
pfctl::PfCtl::new()
.unwrap()
.try_add_anchor(anchor_name, pfctl::AnchorKind::Scrub)
.unwrap();
}
}
fn after_each() {
for anchor_name in ANCHORS.iter() {
pfcli::flush_rules(anchor_name, pfcli::FlushOptions::Rules);
pfcli::flush_rules(anchor_name, pfcli::FlushOptions::Nat);
pfctl::PfCtl::new()
.unwrap()
.try_remove_anchor(anchor_name, pfctl::AnchorKind::Filter)
.unwrap();
pfctl::PfCtl::new()
.unwrap()
.try_remove_anchor(anchor_name, pfctl::AnchorKind::Redirect)
.unwrap();
pfctl::PfCtl::new()
.unwrap()
.try_remove_anchor(anchor_name, pfctl::AnchorKind::Scrub)
.unwrap();
}
}
fn get_filter_rules() -> Vec<pfctl::FilterRule> {
let rule1 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.to(Ipv4Addr::new(1, 2, 3, 4))
.build()
.unwrap();
let rule2 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.to(Ipv4Addr::new(9, 8, 7, 6))
.build()
.unwrap();
vec![rule1, rule2]
}
fn get_nat_rules() -> Vec<pfctl::NatRule> {
let rule1 = pfctl::NatRuleBuilder::default()
.action(pfctl::NatRuleAction::Nat {
nat_to: Ipv4Addr::new(127, 0, 0, 1).into(),
})
.to(Ipv4Addr::new(1, 2, 3, 4))
.build()
.unwrap();
let rule2 = pfctl::NatRuleBuilder::default()
.action(pfctl::NatRuleAction::NoNat)
.to(Ipv4Addr::new(1, 3, 3, 7))
.build()
.unwrap();
vec![rule1, rule2]
}
fn get_redirect_rules() -> Vec<pfctl::RedirectRule> {
let rdr_rule1 = pfctl::RedirectRuleBuilder::default()
.action(pfctl::RedirectRuleAction::Redirect)
.from(Ipv4Addr::new(1, 2, 3, 4))
.to(pfctl::Port::from(3000))
.redirect_to(pfctl::Port::from(4000))
.build()
.unwrap();
let rdr_rule2 = pfctl::RedirectRuleBuilder::default()
.action(pfctl::RedirectRuleAction::Redirect)
.from(Ipv4Addr::new(1, 2, 3, 4))
.to(pfctl::Port::from(5000))
.redirect_to(pfctl::Port::from(6000))
.build()
.unwrap();
vec![rdr_rule1, rdr_rule2]
}
fn get_scrub_rules() -> Vec<pfctl::ScrubRule> {
let scrub_rule1 = pfctl::ScrubRuleBuilder::default()
.action(pfctl::ScrubRuleAction::Scrub)
.build()
.unwrap();
let scrub_rule2 = pfctl::ScrubRuleBuilder::default()
.action(pfctl::ScrubRuleAction::NoScrub)
.build()
.unwrap();
vec![scrub_rule1, scrub_rule2]
}
fn get_marker_filter_rule() -> pfctl::FilterRule {
pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.build()
.unwrap()
}
fn get_marker_redirect_rule() -> pfctl::RedirectRule {
pfctl::RedirectRuleBuilder::default()
.action(pfctl::RedirectRuleAction::Redirect)
.to(pfctl::Endpoint::from(pfctl::Port::from(1337)))
.redirect_to(pfctl::Endpoint::from(pfctl::Port::from(1338)))
.build()
.unwrap()
}
fn verify_filter_rules(anchor: &str) {
let rules = get_rules_filtered(anchor, |rule| !rule.contains("scrub"));
assert_eq!(
rules,
&[
"pass inet from any to 1.2.3.4 no state",
"pass inet from any to 9.8.7.6 no state",
]
);
}
fn verify_scrub_rules(anchor: &str) {
let rules = get_rules_filtered(anchor, |rule| rule.contains("scrub"));
assert_eq!(rules, &["scrub all fragment reassemble", "no scrub all",],);
}
fn get_rules_filtered(anchor: &str, filter: impl Fn(&str) -> bool) -> Vec<String> {
pfcli::get_rules(anchor)
.into_iter()
.filter(|rule| filter(rule))
.collect::<Vec<_>>()
}
fn verify_redirect_rules(anchor: &str) {
assert_eq!(
get_nat_rules_filtered(anchor, |rule| rule.contains("rdr")),
&[
"rdr inet from 1.2.3.4 to any port = 3000 -> any port 4000",
"rdr inet from 1.2.3.4 to any port = 5000 -> any port 6000",
]
);
}
fn verify_nat_rules(anchor: &str) {
assert_eq!(
get_nat_rules_filtered(anchor, |rule| rule.contains("nat")),
&[
"nat inet from any to 1.2.3.4 -> 127.0.0.1",
"no nat inet from any to 1.3.3.7",
]
);
}
fn get_nat_rules_filtered(anchor: &str, filter: impl Fn(&str) -> bool) -> Vec<String> {
pfcli::get_nat_rules(anchor)
.into_iter()
.filter(|rule| filter(rule))
.collect::<Vec<_>>()
}
fn verify_filter_marker(anchor: &str) {
assert_eq!(pfcli::get_rules(anchor), &["pass all no state"]);
}
fn verify_redirect_marker(anchor: &str) {
assert_eq!(
pfcli::get_nat_rules(anchor),
&["rdr from any to any port = 1337 -> any port 1338"]
);
}
test!(replace_many_rulesets_in_one_anchor {
let mut pf = pfctl::PfCtl::new().unwrap();
let mut change = pfctl::AnchorChange::new();
change.set_filter_rules(get_filter_rules());
change.set_nat_rules(get_nat_rules());
change.set_redirect_rules(get_redirect_rules());
change.set_scrub_rules(get_scrub_rules());
pf.set_rules(ANCHOR1_NAME, change).unwrap();
verify_filter_rules(ANCHOR1_NAME);
verify_scrub_rules(ANCHOR1_NAME);
verify_redirect_rules(ANCHOR1_NAME);
});
test!(replace_one_ruleset_in_many_anchors {
let mut pf = pfctl::PfCtl::new().unwrap();
pf.add_rule(ANCHOR1_NAME, &get_marker_filter_rule()).unwrap();
pf.add_redirect_rule(ANCHOR2_NAME, &get_marker_redirect_rule()).unwrap();
verify_filter_marker(ANCHOR1_NAME);
verify_redirect_marker(ANCHOR2_NAME);
let mut change1 = pfctl::AnchorChange::new();
change1.set_redirect_rules(get_redirect_rules());
let mut change2 = pfctl::AnchorChange::new();
change2.set_filter_rules(get_filter_rules());
let mut change3 = pfctl::AnchorChange::new();
change3.set_nat_rules(get_nat_rules());
let mut change4 = pfctl::AnchorChange::new();
change4.set_scrub_rules(get_scrub_rules());
let mut trans = pfctl::Transaction::new();
trans.add_change(ANCHOR1_NAME, change1);
trans.add_change(ANCHOR2_NAME, change2);
trans.add_change(ANCHOR3_NAME, change3);
trans.add_change(ANCHOR4_NAME, change4);
assert_matches!(trans.commit(), Ok(()));
verify_filter_marker(ANCHOR1_NAME);
verify_redirect_rules(ANCHOR1_NAME);
verify_filter_rules(ANCHOR2_NAME);
verify_nat_rules(ANCHOR3_NAME);
verify_scrub_rules(ANCHOR4_NAME);
verify_redirect_marker(ANCHOR2_NAME);
});