PF PF not loading rules at boot

Hello there,

I tried searching the forum for similar discussions, but they didn't lead me to a solution.

At home I have a FreeBSD server that acts as a router for the local network:

Code:
[root@firewall ~]# uname -sr
FreeBSD 13.2-RELEASE-p2
[root@firewall ~]#

At boot time the PF service is correctly enabled, but the PF rules are not loaded automatically: and I am forced to load them manually:

Code:
[root@firewall ~]# pfctl -sr
[root@firewall ~]#

After I manually load the rules:

Code:
[root@firewall ~]# pfctl -sr
scrub in all fragment reassemble
anchor "miniupnpd" all
block drop in quick on em0 from <badhosts> to any
block drop all
pass in inet from 172.16.0.0/16 to any flags S/SA keep state (if-bound)
pass out on em0 all flags S/SA keep state (if-bound)
pass inet proto icmp from 172.16.0.0/16 to any keep state (if-bound)
pass quick inet proto icmp all icmp-type echoreq keep state (if-bound)
pass quick proto tcp from any to any port = ssh flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = auth flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = 25522 flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = 51413 flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = 55522 flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = 65522 flags S/SA keep state (if-bound)
pass quick proto tcp from any to any port = domain flags S/SA keep state (if-bound)
pass quick proto udp from any to any port = domain keep state (if-bound)
pass quick proto udp from any to any port = 51413 keep state (if-bound)
pass quick proto udp from any to any port = bootps keep state (if-bound)
pass quick proto udp from any to any port = ssdp keep state (if-bound)
[root@firewall ~]#

I cannot find any information in the system logs as to why the rules are not properly loaded during the boot process.

Below is my /etc/rc.conf:

Code:
[root@firewall ~]# cat /etc/rc.conf
clear_tmp_enable="YES"
sendmail_enable="NONE"
hostname="firewall.xxx.tld"
ifconfig_em0="inet 192.168.1.1 netmask 255.255.255.0"
ifconfig_em1="inet 172.16.1.1 netmask 255.255.0.0"
defaultrouter="192.168.1.254"
sshd_enable="YES"
ntpd_enable="YES"
# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable
dumpdev="AUTO"
netwait_enable="YES"
netwait_ip="1.1.1.1"
netwait_timeout="60"
netwait_if="em0"
netwait_if_timeout="60"
gateway_enable="YES"
coretemp_load="YES"
dnsmasq_enable="YES"
oidentd_enable="YES"
ddclient_enable="YES"
syslog_ng_enable="YES"
syslogd_enable="NO"
miniupnpd_enable="YES"
pf_enable="YES"
pf_rules="/etc/pf.conf"        
pf_program="/sbin/pfctl"
[root@firewall ~]#

I have tried configuring /etc/rc.local to work around the problem by reloading the rules, either with the `service` command or with `pfctl`:

Code:
[root@firewall ~]# cat /etc/rc.local
date > /tmp/rc-local-debug
echo "Trying to restart PF rules" >> /tmp/rc-local-debug
/sbin/pfctl -f /etc/pf.conf
if [ $? -eq 0 ];
then
    echo "Command run successfully" >> /tmp/rc-local-debug
else
    echo "Command not run" >> /tmp/rc-local-debug
fi
[root@firewall ~]#

but the command on boot fails:

Code:
[root@firewall ~]# cat /tmp/rc-local-debug
Mon Aug 21 10:30:41 CEST 2023
Trying to restart PF rules
Command not ran

I don't think there are any problems with the configuration of the rules preventing reloading during the boot process, in any case, below is the rules file (on which any suggestions are welcome):

Code:
[root@firewall ~]# cat /etc/pf.conf
# Firewall rules

### Variables
### external interface
ext_if = "em0"

### internal interface
int_if = "em1"
int_net = "172.16.1.0/16"

### static hosts
galileo = "172.16.1.10"
magellan = "172.16.1.14"
ps4 = "172.16.1.11"
accesspoint = "172.16.1.2"

### Tables for large IPs
table <badhosts> persist file "/etc/pf.badhosts"

set debug urgent
set block-policy drop
set state-policy if-bound
set fingerprints "/etc/pf.os"
set ruleset-optimization none

scrub in all

# NAT rule for outgoing connections (static-port is useful for game consoles like PS4/Xbox)
nat on $ext_if inet from $ps4 to any -> $ext_if static-port
nat on $ext_if inet from $int_net to any -> $ext_if

# redirect from outside port to internal server
# example:
# rdr on $ext_if proto tcp from any to any port 80 -> 172.16.1.10 port 80

# Redirect rule for galileo (RPi2)
rdr pass on $ext_if proto tcp from any to $ext_if port 51413 -> $galileo port 51413
rdr pass on $ext_if proto udp from any to $ext_if port 51413 -> $galileo port 51413
rdr pass on $ext_if proto tcp from any to $ext_if port 65522 -> $galileo port 65522

# Redirect rule for magellan (RPi4)
rdr pass on $ext_if proto tcp from any to $ext_if port 55522 -> $magellan port 22

# enable UPnP (requires miniupnpd, game consoles needs this)
rdr-anchor "miniupnpd"
anchor "miniupnpd"

### $ExtIf block abusive hosts in temp and perm tables
block drop in quick on $ext_if from <badhosts> to any

block all
pass in from $int_net
pass out on $ext_if all

pass inet proto icmp from $int_net                         # allow some ICMP for troubleshooting
pass quick inet proto icmp all icmp-type echoreq  # always allow ping
pass quick proto tcp from any to any port 22      # always allow ssh
pass quick proto tcp from any to any port 113     # always allow identd
pass quick proto tcp from any to any port 25522   # always allow ssh
pass quick proto tcp from any to any port 51413   # always allow transmission-daemon
pass quick proto tcp from any to any port 55522   # always allow ssh
pass quick proto tcp from any to any port 65522   # always allow ssh
pass quick proto tcp from any to any port 53      # always allow dns
pass quick proto udp from any to any port 53      # always allow dns
pass quick proto udp from any to any port 51413   # always allow transmission-daemon
pass quick proto udp from any to any port 67      # always allow dhcp
pass quick proto udp from any to any port 1900    # always allow miniupnpd
[root@firewall ~]#

Thanks in advance for the support
 
If you use hostnames instead of IP numbers in tables make sure that all hostnames do resolve. If a single hostname does not resolve, loading of the rules fails.
 
If you use hostnames instead of IP numbers in tables make sure that all hostnames do resolve. If a single hostname does not resolve, loading of the rules fails.

If you're referring to /etc/pf.badhosts, there are only IP addresses in CIDR notation:

Code:
[root@firewall ~]# grep -v '[0-9]' /etc/pf.badhosts
[root@firewall ~]#

However, I removed that table from PF rules and they are properly loaded at boot. Maybe the table is too large?

Code:
[root@firewall ~]# wc -l /etc/pf.badhosts
  122004 /etc/pf.badhosts
[root@firewall ~]#
 
Maybe there's a bad CIDR notation in there somewhere?

uhm, let's check that:

Code:
[root@firewall ~]# awk -F / '{print $2}' /etc/pf.badhosts | sort | uniq -c | sort -rn
47040 22
22220 24
13836 23
8532 21
7896 19
7872 20
3760 18
3680 16
2760 17
1964 15
1260 14
 624 13
 332 12
 132 11
  48 10
  40 25
   4 9
   4 27
[root@firewall ~]#

do you think there's something wrong?
 
I meant an invalid CIDR range, for example 10.0.0.1/24, which is not a network address.
 
I meant an invalid CIDR range, for example 10.0.0.1/24, which is not a network address.

Ah, ok:

Code:
[root@firewall ~]# awk -F . '{print $4}' /etc/pf.badhosts | cut -d / -f 1 | sort | uniq -c | sort -rn
121976 0
  28 128
[root@firewall ~]#

there's some entries that ends with ".128", so let's check their netmask:

Code:
[root@firewall ~]# grep ".128/" /etc/pf.badhosts
91.227.79.128/25
91.227.79.128/25
91.227.79.128/25
91.227.79.128/25
193.201.147.128/27
193.201.147.128/27
193.201.147.128/27
193.201.147.128/27
193.201.152.128/25
193.201.152.128/25
193.201.152.128/25
193.201.152.128/25
193.201.159.128/25
193.201.159.128/25
193.201.159.128/25
193.201.159.128/25
193.34.200.128/25
193.34.200.128/25
193.34.200.128/25
193.34.200.128/25
194.117.50.128/25
194.117.50.128/25
194.117.50.128/25
194.117.50.128/25
194.153.158.128/25
194.153.158.128/25
194.153.158.128/25
194.153.158.128/25
[root@firewall ~]#

All the previouse entries have at least a /25 netmask, so I think they are ok.
 
As your badhosts list is "large" you probably downloaded it somewhere. It might be that the file contains invisible characters which may prevent loading. So besides validating all CIDR you may also want to check if there are non-ASCII characters in the file.

As a general hint for loading tables @reboot I'd suggest to load a working ruleset (pf.conf without critical tables) and then adding tables via anchors a little time later.
Doing so prevents you from starting a system without any firewall protection caused by a loading failure which you may not even notice.
 
I suspect there's at least one interface that isn't up when PF is loading, leading to some interface-to-address translations to fail.
In your ruleset this would prevent the nat and rdr-to rules to be loaded. To account for interfaces that come up (and/or get/change their address) *after* PF loads you can specify them in parentheses. Its good practice to always do this, even on hosts with fixed IPs as it will make the ruleset more robust and also gives a visual indication that the (primary) address of the interface is actually used in that rule:

Code:
[...]
# NAT rule for outgoing connections (static-port is useful for game consoles like PS4/Xbox)
nat on $ext_if inet from $ps4 to any -> ($ext_if) static-port
nat on $ext_if inet from $int_net to any -> ($ext_if)

# Redirect rule for galileo (RPi2)
rdr pass on $ext_if proto tcp from any to ($ext_if) port 51413 -> $galileo port 51413
rdr pass on $ext_if proto udp from any to ($ext_if) port 51413 -> $galileo port 51413
rdr pass on $ext_if proto tcp from any to ($ext_if) port 65522 -> $galileo port 65522

# Redirect rule for magellan (RPi4)
rdr pass on $ext_if proto tcp from any to ($ext_if) port 55522 -> $magellan port 22


As for the culprit why the interface isn't fully up at that point, I've seen this on several desktop systems with the UEFI IP-stack enabled. On some NUCs it took considerable time after booting until the interface finally was available and working, regardless of the installed OS. I suspect the UEFI tries to reach some external resources (update servers?) and completely blocks the interface for the OS until that fails/timeouts.
At initial setup I turn off the UEFI IP stack by default for all of our clients (also for security) and never saw such behavior again afterwards...
 
Back
Top