#[macro_use]
#[allow(dead_code)]
mod helper;
use crate::helper::pfcli;
use assert_matches::assert_matches;
use std::net::Ipv4Addr;
static ANCHOR_NAME: &str = "pfctl-rs.integration.testing.filter-rules";
fn before_each() {
pfctl::PfCtl::new()
.unwrap()
.try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)
.unwrap();
}
fn after_each() {
pfcli::flush_rules(ANCHOR_NAME, pfcli::FlushOptions::Rules);
pfctl::PfCtl::new()
.unwrap()
.try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Filter)
.unwrap();
}
test!(drop_all_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(pfcli::get_rules(ANCHOR_NAME), &["block drop all"]);
});
test!(return_all_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Return))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME), &["block return all"]
);
});
test!(drop_by_direction_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.direction(pfctl::Direction::Out)
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(pfcli::get_rules(ANCHOR_NAME), &["block drop out all"]);
});
test!(drop_quick_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.quick(true)
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(pfcli::get_rules(ANCHOR_NAME), &["block drop quick all"]);
});
test!(drop_by_ip_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.proto(pfctl::Proto::Tcp)
.from(Ipv4Addr::new(192, 168, 0, 1))
.to(Ipv4Addr::new(127, 0, 0, 1))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["block drop inet proto tcp from 192.168.0.1 to 127.0.0.1"]
);
});
test!(drop_by_port_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.proto(pfctl::Proto::Tcp)
.from(pfctl::Port::One(3000, pfctl::PortUnaryModifier::Equal))
.to(pfctl::Port::One(8080, pfctl::PortUnaryModifier::Equal))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["block drop proto tcp from any port = 3000 to any port = 8080"]
);
});
test!(drop_by_port_range_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.proto(pfctl::Proto::Tcp)
.from(pfctl::Port::Range(3000, 4000, pfctl::PortRangeModifier::Inclusive))
.to(pfctl::Port::Range(5000, 6000, pfctl::PortRangeModifier::Exclusive))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["block drop proto tcp from any port 3000:4000 to any port 5000 >< 6000"]
);
});
test!(drop_by_interface_rule {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.interface("utun0")
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["block drop on utun0 all"]
);
});
test!(pass_out_route_rule {
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::Out)
.route(
pfctl::Route::RouteTo(
pfctl::PoolAddr::new("lo0", Ipv4Addr::new(127, 0, 0, 1))
)
)
.proto(pfctl::Proto::Udp)
.from(Ipv4Addr::new(1, 2, 3, 4))
.to(pfctl::Port::from(53))
.build()
.unwrap();
let mut change = pfctl::AnchorChange::new();
change.set_filter_rules(vec![rule]);
let mut trans = pfctl::Transaction::new();
trans.add_change(ANCHOR_NAME, change);
assert_matches!(trans.commit(), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&[
"pass out route-to (lo0 127.0.0.1) inet proto udp \
from 1.2.3.4 to any port = 53 no state"
]
);
});
test!(pass_in_reply_to_rule {
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::In)
.interface("lo1")
.route(pfctl::Route::reply_to(pfctl::Interface::from("lo9")))
.from(Ipv4Addr::new(6, 7, 8, 9))
.build()
.unwrap();
let mut change = pfctl::AnchorChange::new();
change.set_filter_rules(vec![rule]);
let mut trans = pfctl::Transaction::new();
trans.add_change(ANCHOR_NAME, change);
assert_matches!(trans.commit(), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["pass in on lo1 reply-to lo9 inet from 6.7.8.9 to any no state"]
);
});
test!(pass_in_dup_to_rule {
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.direction(pfctl::Direction::In)
.interface("lo1")
.route(pfctl::Route::DupTo(pfctl::PoolAddr::new("lo8", Ipv4Addr::new(1, 2, 3, 4))))
.from(Ipv4Addr::new(6, 7, 8, 9))
.build()
.unwrap();
let mut change = pfctl::AnchorChange::new();
change.set_filter_rules(vec![rule]);
let mut trans = pfctl::Transaction::new();
trans.add_change(ANCHOR_NAME, change);
assert_matches!(trans.commit(), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["pass in on lo1 dup-to (lo8 1.2.3.4) inet from 6.7.8.9 to any no state"]
);
});
test!(flush_filter_rules {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME).len(),
1
);
assert_matches!(pf.flush_rules(ANCHOR_NAME, pfctl::RulesetKind::Filter), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&[] as &[&str]
);
});
test!(all_state_policies {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule1 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.from(Ipv4Addr::new(192, 168, 1, 1))
.keep_state(pfctl::StatePolicy::None)
.build()
.unwrap();
let rule2 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.from(Ipv4Addr::new(192, 168, 1, 2))
.proto(pfctl::Proto::Tcp)
.tcp_flags(
(
[pfctl::TcpFlag::Syn],
[pfctl::TcpFlag::Syn, pfctl::TcpFlag::Ack, pfctl::TcpFlag::Fin, pfctl::TcpFlag::Rst]
)
)
.keep_state(pfctl::StatePolicy::Keep)
.build()
.unwrap();
let rule3 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.from(Ipv4Addr::new(192, 168, 1, 3))
.proto(pfctl::Proto::Tcp)
.keep_state(pfctl::StatePolicy::Modulate)
.build()
.unwrap();
let rule4 = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Pass)
.from(Ipv4Addr::new(192, 168, 1, 4))
.proto(pfctl::Proto::Tcp)
.keep_state(pfctl::StatePolicy::SynProxy)
.build()
.unwrap();
for rule in [rule1, rule2, rule3, rule4].iter() {
assert_matches!(pf.add_rule(ANCHOR_NAME, rule), Ok(()));
}
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&[
"pass inet from 192.168.1.1 to any no state",
"pass inet proto tcp from 192.168.1.2 to any flags S/FSRA keep state",
"pass inet proto tcp from 192.168.1.3 to any flags any modulate state",
"pass inet proto tcp from 192.168.1.4 to any flags any synproxy state"
]
);
});
test!(logging {
let mut pf = pfctl::PfCtl::new().unwrap();
let rule = pfctl::FilterRuleBuilder::default()
.action(pfctl::FilterRuleAction::Drop(pfctl::DropAction::Drop))
.log(pfctl::RuleLogSet::new(&[
pfctl::RuleLog::ExcludeMatchingState,
pfctl::RuleLog::IncludeMatchingState,
pfctl::RuleLog::SocketOwner,
]))
.build()
.unwrap();
assert_matches!(pf.add_rule(ANCHOR_NAME, &rule), Ok(()));
assert_eq!(
pfcli::get_rules(ANCHOR_NAME),
&["block drop log (all, user) all"]
);
});