tech-net archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: Dynamic & static address grouping in NPF
On Thu, Jul 24, 2025 at 09:25:30AM +0000, Emmanuel Nyarko wrote:
> In an attempt to fix this PR reported by Josh Mayor: https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=59507
>
> the crash ( from the PR ) was here "$clientgroup={ $self, $otherip }”
> $self =. Dynamic, $otherip = raw IP
>
> and this is because he was listing a dynamic address first and a raw IP second on the same rule.
>
> the basic issue I found was
> if you have a rule
> pass from { ifaddrs(wm0), 192.164.64.7 } on the same rule,
>
> an assertion failure.
>
> /*
> * L3 block cannot be inserted in the middle of a group.
> * In fact, it never is. Check and start the group after.
> */
> if (ingroup) {
> assert(ctx->nblocks == ctx->gblock);
> npfctl_bpf_group_exit(ctx);
> }
>
>
> This is a comment rmind@ left in a function that fetches the address family.
>
> Group here means : a group of addresses ( not the group keyword )
> { 192.168.65.7, 192.168.64.8, 192.168.64.9 } => a group
No, group here means a grouping on filter conditions as explained here:
https://nxr.netbsd.org/xref/src/usr.sbin/npf/npfctl/npf_bpf_comp.c?r=1.19#70
> What he means: always load the address family first.
Sort of. What this actually means is that he wants to load the address
family *once* and before the first static address is filtered.
> and when you list a static address, NPF extracts the address family and uses first when evaluating a group.
>
> (000) ld M[0] // 4 bytes offset the packet header to get the version
> (001) jeq #0x04 // the address family fetched on the rule ( version 4 here )
>
> /// before you load and compare the addresses.
Yes.
> But NPF doesn’t extract and check the version to use in filtering when you enter a dynamic address,
>
> when you put { Ifaddrs(wm0), 192.168.64.7 } in the same group,
Pretty much. That is because "ifaddrs(wm0)" is internally implemented as
an IFADDR table and it does a BPF_COP call to do the table lookup. And the
table lookup doesn't care about the BPF code having loaded the address
family into the BPF memory.
> now when it is now about to evaluate the static IP on rule
>
> doesn’t allow fetching the address family because NPF thinks it should be the first thing checked.
Correct.
> So it tries to exit the group,
Where does it try to exit the group? I don't see that.
> With this behaviour , NPF tries to tie each rule to one address family.
> based on this comment by rmind,
> /*
> * L3 block cannot be inserted in the middle of a group.
> * In fact, it never is. Check and start the group after.
> */
> if this is a statement we agree with, then I make it a compile time error
> when you try to insert a raw IP in the middle of a group that begins with
> a dynamic address.
You misunderstand the statement. It's also a bit misleading. What it says
is that the L3 block of statements should be the first thing done in such a
group, if it is necessary. And certainly it shouldn't be done multiple
times in the group.
But that comment was added in r1.1 of npf_bpf_comp.c and the filt_addr_list
stuff was added in r1.55 of npf_parse.y, i.e. much, much later. So this
comment doesn't reflect current reality.
> you are free to list only dynamic addresses in one group.(that doesn’t
> seg fault because no address family check )
That's also not accurate.
The following example asserts:
----------------------------------------------------------------------------
group default {
pass from { ifaddrs(wm0), 192.164.64.7 }
}
----------------------------------------------------------------------------
This is because, leaving out unimported details, it gets translated into the
following group
ifnet_table(wm0) or bpf_cidr(192.164.64.8)
and bpf_cidr() tries to add fetch_l3(AF_INET). Which, btw doesn't do any harm.
But the following doesn't.
----------------------------------------------------------------------------
group default {
pass from { 192.164.64.7, ifaddrs(wm0) }
}
----------------------------------------------------------------------------
Because it gets translated into:
bpf_cidr(192.164.64.8/32) or ifnet_table(wm0)
Here bpf_cidr() can add the fetch_l3(AF_INET) but if we're dealing with a
IPv6 address, the code generated by fetch_l3() will jump passt the
ifnet_table(wm0) too, I think, which is wrong.
Please add test cases. ;-)
But that seems wrong to me. It should get translated into
( bpf_cidr(192.164.64.8/32) ) or ifnet_table(wm0)
That is, the bpf_cidr() should be put into it's own group.
Consider this:
----------------------------------------------------------------------------
group default {
pass from { 192.164.64.7, 2001:db8::1, ifaddrs(wm0),
3fff:1334::/64, 192.0.2.0/24, ifaddrs(wm1) }
}
----------------------------------------------------------------------------
This could be translated into
( bpf_cidr(192.164.64.7/32) or bpf_cidr(192.0.2.0/24) ) or
( bpf_cidr(2001:db8::1/128) or bpf_cidr(3fff:1334::/64) or
( ifnet_table(wm0) or ifnet_table(wm1) )
And then bpf_cidr() could add the fetch_l3() at the beginning of each group
and it would work.
Note that the order of the terms doesn't matter. Except for performance, of
course.
--chris
Home |
Main Index |
Thread Index |
Old Index