NetBSD-Users archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: npf on a router: configuration issues



On Tue, Apr 01, 2025 at 08:57:38AM -0400, Greg Troxel wrote:
> I am trying to configure npf in a router/nat context and unclear on some
> things, with the documentation not being clear enough to unconfuse me.
> This is intended today as a series of questions I'd like answers for,
> although I see it as also serving as a documentation bug report.

Since I need to write my observations down sometime I might as well start
now. :-)

> 1) There are groups.  The documentation says
> 
>    "NPF requires that all rules be defined within groups.  Groups can be
>    thought of as higher level rules which can contain subrules.  Groups may
>    have the following options: name, interface, and direction.  Packets
>    matching group criteria are passed to the ruleset of that group.  If a
>    packet does not match any group, it is passed to the default group.  The
>    default group must always be defined."
> 
>    a) Will a packet be processed by all groups that match it (meaning
>       direction and interface)?  If a packet is processed by multiple
>       groups, is there a defined order?  Is it like the rules are the
>       concatenation of the groups?  Or is there some
>       first-matching-group, and if so is the ordering from the config
>       file, or ?

To the first question: I think not. I have on a dom0 that acts as router:

$localnet = { 10.200.0.0/16 }

group "wg" on wg0 {
    pass all 
}

group "external" on $ext_if {
    pass stateful out final all 
    ...
}

group "internal" on $int_if {
    block in all
    pass in final from $localnet
    pass out final all
}

group default {
    pass final on lo0 all
    block all
}

That is enough to allow packets from and to wg0 talk to the domU's
in "internal".  So the "block all" in "default" isn't considered.

Group processing order is actually not documented.  It seems to me that
groups are processed in the order that they are defined.  With the group
criteria serving as a first-level discriminator for the packets.  The
rules inside a group seem to be the only rules considered for packets
matching the group criteria.  But all this is, again, not actually
documented.

>    b) Is it really meant that "if a packet does not match any defined
>       group, then -- and only then -- will it be processed by the special
>       group default (which is default NOT in quotes, as a keyword not a
>       name)"?

As far as I can tell: yes.

>    c) If a packet is ever processed by more than one group, how does
>      `final` work?

'final' works as an early return from the rules set.

>    d) I don't see any ability to use nested groups.  (Given limited
>    selectors, I don't see why I would want to.)  Is that correct?

Neither see I.  There are dynamic rules, but I think you can't actually
define initial rules for such a ruleset in the config file.  I haven't
tested that though.  And the documentation doesn't seem to indicate that
you could a nested dynamic ruleset from a dynamic ruleset.

> 2) It seems obvious (dangerous I know) that a packet might be processed
>    on ingress on $lan_if and then on egress on $wan_if, and that these
>    processings should be independent.  Is this true?

Going by https://rmind.github.io/npf/intro.html#processing
it seems to me packets are processed on ingress and egresses in the
forwarding case.

> 3) In some other firewalls, I have seen a concept of separate processing
>    for
>     - incoming on an interface
>     - from the forwarding part of the stack inbound to the host
>     - to the forwarding part of the stack outbound from the host
>     - outgoing on an interface

I have working knowledge only of ipf and netfilter/iptables.  And I
haven't had to touch my ipf config in the last 15 years or so.  I've
forgotten what I knew about other paket filters.  Certainly
netfilter/iptables does not have this concept.  It has a different concept
that might be confused with what you described, but not easily.

>    I don't see this concept in npf.

I see only https://rmind.github.io/npf/intro.html#processing discussing
this a bit.  That seems to be a different concept.  I.e. packets are
processed "on each interface a packet is traversing, either as incoming or
outgoing."

>    a) Am I reading the docs correctly?
> 
>    b) Assuming so, and I want to
>         - block packets heading to the host to most ports, except for a few
>         - allow outbound transit packets without regard to blocked ports
>       how do I do this?  It looks like I have to have my block rules
>       narrowed by $ifaddrs and run on each interface, making groups
>       awkward.  Surely there must be a better way, as I don't think my
>       intent is unusual.

You don't actually define what the "host" is.  It seems you mean the
router's addresses.  You are correct in that you have to narrow the rules
down to ifaddrs(iface) for all interfaces (except lo0 which you can
short-circuit).

So, either for each interface:

group "ifaceN" in on ifaceN {
	pass stateful in final proto tcp to ifaddrs(ifaceN) port $allowed_ports
	...
}

or

group default {
	pass final on lo0 all
	pass stateful in final on iface0 proto tcp to ifaddrs(iface0) port $allowed_ports
	pass stateful in final on iface1 proto tcp to ifaddrs(iface0) port $allowed_ports
	...
}

>       I don't see a way to put dst-is-host packets in a group.

Not with group criteria.

>    c) Or is inbound rule processing limited to packets that are for this
>       host?  Outbound seems not to be limited like that, because
>       otherwise NAT wouldn't work.

No. inbount rule processing also handles packets that would be forwarded.
NAT rules aren't defined in groups with NPF. And from
https://rmind.github.io/npf/intro.html#processing I infer that packets
have to first match "pass" rules before NAT is performed.

> 4) I understand stateful processing on outbound, given a default block
>    on inbound.  I understand stateful processing for TCP on inbound so
>    that bare SYN to allowed ports creates a state entry, and then future
>    packets that match the flow are allowed.

The main reasons I use stateful rules are that:
- I expect stateful rules to also allow ICMP related to the "connection".
- it trades processing speed (number of rules that have to be matched for
  each packet) for space (both in the state table and in the brain because
  of more complicated rules.)

>    Why would I want to or not want to use stateful processing for
>    inbound UDP?  Inbound ICMP echo?

Of the top of my hat and with no claim to be extensive:

For inbound UDP so that e.g. PORT UNREACHABLE from the hosts can be
returned, if you find that useful (e.g. for traceroute).

For inbound ICMP echo request not so much if you allow outbound ICMP
anyway.

There's also the matter of personal style in writing rule sets.  And
perhaps it may be easier to create such rule sets programmatically.

> 5) The NAT examples almost all suggest a group with "pass stateful out
>    final".
> 
>    Is there any reason there needs to be such a group/rules if the rules
>    that exist anyway on the outbound interface have "pass stateful out"?

Well, certain types of NAT require state, but
https://rmind.github.io/npf/nat.html says it does stateful filtering
automatically if required.  That would imply that "pass stateful" is not
required at all.  I haven't experimented with that, however.  However,
adding "stateful" to a rule that also is affected by NAT can make it
explicit that you expect state to be maintained and does no harm.

--chris


Home | Main Index | Thread Index | Old Index