Moving the pf rc.d scripts to run before netif

Maxim Khitrov mkhitrov at gmail.com
Tue Sep 15 17:46:07 UTC 2009


On Mon, Jun 15, 2009 at 3:26 PM, Doug Barton <dougb at freebsd.org> wrote:
> Gert Doering wrote:
>> Hi Doug,
>>
>> thanks for taking this up - and sorry for not responding more timely.
>>
>> I can't answer all the questions but might have a yet-unmentioned idea
>> that could solve all this in one go :-)
>>
>> On Mon, Jun 01, 2009 at 11:38:45AM -0700, Doug Barton wrote:
>>> 2. The previous rcorder for the pf script was right after netif (the
>>> network coming up) and before routing .... why? Is this related to how
>>> pf does its work? The reason I ask this question is that in order to
>>> fix the IPv6 rcorder problem in the pr the way that Gert is suggesting
>>> the "BEFORE: routing" would have to be removed because our IPv6
>>> startup depends on RA which depends on routing being up. (Side note,
>>> in the long term I'd like to revise this so that an IPv6-only host
>>> and/or a host with statically assigned IPv6 addresses can easily be
>>> configured within rc.d, but that's another thing altogether.)
>>>
>>> 3. Is the need to be able to use $ext_if after the network is up so
>>> overwhelmingly important that it justifies running pf after netif? Or
>>> is using ($ext_if) a reasonable solution?
>>
>> Well - let's turn this one around: since we *have* the functionality in
>> pf(4), let's not cripple it by building a framework that makes using this
>> functionality effectively impossible.  If I understand Bjoern right, this
>> is also a performance issue - ($ext_if) needs a per-packet lookup to
>> get the now-current address, while $ext_if reads the address at pf setup
>> time.
>>
>>
>> I can see the arguments for having the firewall initialization right at
>> the start - to avoid opening an window of opportunity where services are
>> "up" but the firewall hasn't yet been loaded.
>>
>>
>> So what about the following approach:
>>
>>  - split the firewall initialization into two halves
>>
>>  - the first half is run before any other networking stuff is configured
>>    and basically sets up a "deny everything incoming" filter (with
>>    exceptions for IPv6 RD/ND, of course).
>>
>>    Optionally this could permit outbound connections (with state), to
>>    enable things like bgpd to run.
>>
>>  - after this, run interface configuration, set up routing, ...
>>
>>  - when all this is finished, load the "real" set of firewall rules,
>>    which can now (if so desired) safely use $ext_if
>
> I already said I support this solution, I'm just waiting for someone
> with some real pf knowledge to propose something.
>
> Doug

Hello all,

I just ran into this problem of pf start-up order on 7.2. I have a
number of nat and rdr rules that allow people on the outside to access
some internal servers (web, mail, etc.). To avoid having to specify
public and private IPs once in the DNS server, which is on the
internal interface, and a second time in the pf configuration, all of
these rules use host names. During start-up, the DNS server cannot be
reached, so pf.conf is not loaded due to unresolved hostnames.

The solution I used is basically as explained by Gert. It works for
me, but would be much better if someone could commit a more permanent
fix. The idea is to have two separate pf configuration files and rc
scripts. One rc script (I called mine pf_init and put it into
/usr/local/etc/rc.d/ for now) runs instead of the current /etc/rc.d/pf
(before routing is enabled). This script loads /etc/pf.init which
contains the following configuration:

# pf configuration that is loaded before routing
set skip on lo
scrub in
block in

Just a basic filter that has no external dependencies and blocks all
incoming traffic. Outgoing traffic, like DNS queries, would be allowed
by the default pass rule.

The second rc script is a modified version of the current
/etc/rc.d/pf. All I did was change two lines:

--- /usr/src/etc/rc.d/pf        2009-04-14 23:14:26.000000000 -0400
+++ /etc/rc.d/pf        2009-09-15 13:06:07.000000000 -0400
@@ -7,2 +7,2 @@
-# REQUIRE: FILESYSTEMS netif pflog pfsync
-# BEFORE:  routing
+# REQUIRE: FILESYSTEMS netif pflog pfsync named
+# BEFORE:  DAEMON ntpdate

I added named to the REQUIRE list, which in my case is provided by
dnsmasq. This allows the firewall to query ISP and internal DNS
servers before the real pf.conf is loaded. I also wanted pf.conf
loaded before other network-related scripts like ntpdate. Not sure if
there are some other things that need to be included for a more
general solution, but this works for me.

My pf_init script is at the bottom of this message. Feel free to take
any of my work, improve it, and commit to the source tree. You would
need to move pf_init_rules variable, which is currently defined in
pf_init to /etc/defaults/rc.conf. That would allow people to specify a
config to use other than /etc/pf.init.

The only thing I wasn't sure about is if there is a better way to
disable stop and restart rc commands than to set stop_cmd and
restart_cmd to an empty function.  We don't want to use pf_init to
reload or restart the firewall, since that would replace the correct
ruleset with the pre-routing one.

- Max

/usr/local/etc/rc.d/pf_init:
#!/bin/sh
#
# PROVIDE: pf_init
# REQUIRE: FILESYSTEMS netif pflog pfsync
# BEFORE:  routing
# KEYWORD: nojail

. /etc/rc.subr

name="pf_init"
rcvar=`set_rcvar`
load_rc_config "pf"
start_cmd=""
stop_cmd="pf_pass"
restart_cmd="pf_pass"
pf_init_rules="/etc/pf.init"
required_files="$pf_init_rules"
required_modules="pf"

pf_start()
{
	echo "Enabling pf (init)."
	$pf_program -F all > /dev/null 2>&1
	$pf_program -f "$pf_init_rules" $pf_flags
	if ! $pf_program -s info | grep -q "Enabled" ; then
		$pf_program -e
	fi
}

pf_pass()
{
}

run_rc_command "$1"


More information about the freebsd-pf mailing list