pfctl 0.7.0

Library for interfacing with the Packet Filter (PF) firewall on macOS
Documentation
#[macro_use]
#[allow(dead_code)]
mod helper;

use crate::helper::pfcli;
use assert_matches::assert_matches;
use std::net::{Ipv4Addr, Ipv6Addr};

static ANCHOR_NAME: &str = "pfctl-rs.integration.testing.redirect-rules";

fn port_mapping_rule(ip: pfctl::Ip) -> pfctl::RedirectRule {
    pfctl::RedirectRuleBuilder::default()
        .action(pfctl::RedirectRuleAction::Redirect)
        .to(pfctl::Endpoint::new(ip, 3000))
        .redirect_to(pfctl::Endpoint::new(ip, 4000))
        .build()
        .unwrap()
}

fn redirect_rule_ipv4() -> pfctl::RedirectRule {
    port_mapping_rule(pfctl::Ip::from(Ipv4Addr::new(127, 0, 0, 1)))
}

fn redirect_rule_ipv6() -> pfctl::RedirectRule {
    port_mapping_rule(pfctl::Ip::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)))
}

fn before_each() {
    pfctl::PfCtl::new()
        .unwrap()
        .try_add_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)
        .unwrap();
}

fn after_each() {
    pfcli::flush_rules(ANCHOR_NAME, pfcli::FlushOptions::Nat);
    pfctl::PfCtl::new()
        .unwrap()
        .try_remove_anchor(ANCHOR_NAME, pfctl::AnchorKind::Redirect)
        .unwrap();
}

test!(flush_redirect_rules {
    let mut pf = pfctl::PfCtl::new().unwrap();
    let test_rules = [redirect_rule_ipv4(), redirect_rule_ipv6()];
    for rule in test_rules.iter() {
        assert_matches!(pf.add_redirect_rule(ANCHOR_NAME, rule), Ok(()));
        assert_eq!(pfcli::get_nat_rules(ANCHOR_NAME).len(), 1);

        assert_matches!(pf.flush_rules(ANCHOR_NAME, pfctl::RulesetKind::Redirect), Ok(()));
        assert_eq!(
            pfcli::get_nat_rules(ANCHOR_NAME),
            &[] as &[&str]
        );
    }
});

test!(add_redirect_rule_ipv4 {
    let mut pf = pfctl::PfCtl::new().unwrap();
    let rule = redirect_rule_ipv4();
    assert_matches!(pf.add_redirect_rule(ANCHOR_NAME, &rule), Ok(()));
    assert_eq!(
        pfcli::get_nat_rules(ANCHOR_NAME),
        &["rdr inet from any to 127.0.0.1 port = 3000 -> 127.0.0.1 port 4000"]
    );
});

test!(add_redirect_rule_ipv6 {
    let mut pf = pfctl::PfCtl::new().unwrap();
    let rule = redirect_rule_ipv6();
    assert_matches!(pf.add_redirect_rule(ANCHOR_NAME, &rule), Ok(()));
    assert_eq!(
        pfcli::get_nat_rules(ANCHOR_NAME),
        &["rdr inet6 from any to ::1 port = 3000 -> ::1 port 4000"]
    );
});

test!(add_redirect_rule_on_interface {
    let mut pf = pfctl::PfCtl::new().unwrap();
    let rule = pfctl::RedirectRuleBuilder::default()
        .action(pfctl::RedirectRuleAction::Redirect)
        .log(pfctl::RuleLog::ExcludeMatchingState)
        .interface("lo0")
        .from(Ipv4Addr::new(1, 2, 3, 4))
        .redirect_to(pfctl::Port::from(1237))
        .build()
        .unwrap();
    assert_matches!(pf.add_redirect_rule(ANCHOR_NAME, &rule), Ok(()));
    assert_eq!(
        pfcli::get_nat_rules(ANCHOR_NAME),
        &["rdr log on lo0 inet from 1.2.3.4 to any -> any port 1237"]
    );
});