#[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"] ); }); // TODO(andrej): // currently only transactions support Route. We need to unify code // in lib.rs for adding single rule and code in transaction.rs. 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"] ); });