Commit graph

154 commits

Author SHA1 Message Date
Kristof Provost
8716d8c7d9 pf: configurable action on limiter exceeded
This change extends pf(4) limiters so administrator
can specify action the rule executes when limit is
reached. By default when limit is reached the limiter
overrides action specified by rule to no-match.
If administrator wants to block packet instead then
rule with limiter should be changed to:

   pass in from any to any state limiter test (block)

OK dlg@

Obtained from:	OpenBSD, sashan <sashan@openbsd.org>, 04394254d9
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2026-01-14 07:44:43 +01:00
Kristof Provost
c72fb110e4 pf: convert state limiter interface to netlink
This is a new feature with new ioctl calls, so we can safely remove them
right now.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2026-01-14 07:44:39 +01:00
Kristof Provost
4616481212 pf: introduce source and state limiters
both source and state limiters can provide constraints on the number
of states that a set of rules can create, and optionally the rate
at which they are created. state limiters have a single limit, but
source limiters apply limits against a source address (or network).
the source address entries are dynamically created and destroyed,
and are also limited.

this started out because i was struggling to understand the source and
state tracking options in pf.conf, and looking at the code made it
worse. it looked like some functionality was missing, and the code also
did some things that surprised me. taking a step back from it, even it
if did work, what is described doesn't work well outside very simple
environments.

the functionality i'm talking about is most of the stuff in the
Stateful Tracking Options section of pf.conf(4).

some of the problems are illustrated one of the simplest options:
the "max number" option that limits the number of states that a
rule is allowed to create:

- wiring limits up to rules is a problem because when you load a
  new ruleset the limit is reset, allowing more states to be created
  than you intended.
- a single "rule" in pf.conf can expand to multiple rules in the
  kernel thanks to things like macro expansion for multiple ports.
  "max 1000" on a line in pf.conf could end up being many times
  that in effect.
- when a state limit on a rule is reached, the packet is dropped.
  this makes it difficult to do other things with the packet, such a
  redirect it to a tarpit or another server that replies with an
  outage notices or such.

a state limiter solves these problems. the example from the pf.conf.5
change demonstrates this:

     An example use case for a state limiter is to restrict the number of
     connections allowed to a service that is accessible via multiple
     protocols, e.g. a DNS server that can be accessed by both TCP and UDP on
     port 53, DNS-over-TLS on TCP port 853, and DNS-over-HTTPS on TCP port 443
     can be limited to 1000 concurrent connections:

           state limiter "dns-server" id 1 limit 1000

           pass in proto { tcp udp } to port domain state limiter "dns-server"
           pass in proto tcp to port { 853 443 } state limiter "dns-server"

a single limit across all these protocols can't be implemented with
per rule state limits, and any limits that were applied are reset
if the ruleset is reloaded.

the existing source-track implementation appears to be incomplete,
i could only see code for "source-track global", but not "source-track
rule". source-track global is too heavy and unweildy a hammer, and
source-track rule would suffer the same issues around rule lifetimes
and expansions that the "max number" state tracking config above has.

a slightly expanded example from the pf.conf.5 change for source limiters:

     An example use for a source limiter is the mitigation of denial of
     service caused by the exhaustion of firewall resources by network or port
     scans from outside the network.  The states created by any one scanner
     from any one source address can be limited to avoid impacting other
     sources.  Below, up to 10000 IPv4 hosts and IPv6 /64 networks from the
     external network are each limited to a maximum of 1000 connections, and
     are rate limited to creating 100 states over a 10 second interval:

           source limiter "internet" id 1 entries 10000 \
                   limit 1000 rate 100/10 \
                   inet6 mask 64

           block in on egress
           pass in quick on egress source limiter "internet"
           pass in on egress proto tcp probability 20% rdr-to $tarpit

the extra bit is if the source limiter doesn't have "space" for the
state, the rule doesn't match and you can fall through to tarpitting
20% of the tcp connections for fun.

i've been using this in anger in production for over 3 years now.

sashan@ has been poking me along (slowly) to get it in a good enough
shape for the tree for a long time. it's been one of those years.

bluhm@ says this doesnt break the regress tests.
ok sashan@

Obtained from:	OpenBSD, dlg <dlg@openbsd.org>, 8463cae72e
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2026-01-14 07:44:38 +01:00
Kristof Provost
96c7e70c18 pf: convert DIOCRCLRASTATS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-30 14:16:53 +01:00
Kristof Provost
c2e7a52374 pf: move DIOCRCLRASTATS into libpfctl
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-30 14:16:52 +01:00
Kristof Provost
823ebd7c4f libpfctl: export a get states variant that takes a pfctl_handle
Some checks are pending
Cross-build Kernel / amd64 ubuntu-22.04 (clang-15) (push) Waiting to run
Cross-build Kernel / aarch64 ubuntu-22.04 (clang-15) (push) Waiting to run
Cross-build Kernel / amd64 ubuntu-24.04 (clang-18) (push) Waiting to run
Cross-build Kernel / aarch64 ubuntu-24.04 (clang-18) (push) Waiting to run
Cross-build Kernel / amd64 macos-latest (clang-18) (push) Waiting to run
Cross-build Kernel / aarch64 macos-latest (clang-18) (push) Waiting to run
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-21 18:33:06 +01:00
Kristof Provost
ad7f49f98b libpfctl: fix tstats address count
Reported by:	Marcos Mendoza <mmendoza@netgate.com>
See also:	https://redmine.pfsense.org/issues/16588
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-18 14:39:27 +01:00
Kristof Provost
08f54dfca1 pf: convert DIOCRGETASTATS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-05 13:24:52 +01:00
Kristof Provost
4aa79010bc pfctl: move astats query into libpfctl
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-12-05 13:24:51 +01:00
Kristof Provost
238ad591da libpfctl: improve error handling
If we fail to open /dev/pf don't try to close it again. That would result in
errno getting overwritten by close(), hiding potentially useful information.

MFC after:	2 weeks
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-11-25 14:50:09 +01:00
Kristof Provost
f27e44e2e3 pf: convert DIOCRGETADDRS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-11-05 14:59:29 +01:00
Kristof Provost
9bb1c46b4c libpfctl: fix error handling
In two cases we returned E2BIG where it should have been a boolean ('false').

MFC after:	1 week
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-11-05 14:59:29 +01:00
Kristof Provost
08ed87a4a2 pf: convert DIOCRSETADDRS to netlink
The list of addresses is potentially very large. Larger than we can fit in a
single netlink request, so we indicate via the PFR_FLAG_START/PFR_FLAG_DONE
flags when we start and finish, so the kernel can work out which addresses need
to be removed.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-10-31 16:24:09 +01:00
Jose Luis Duran
a943a96a50
libpfctl: Fix displaying deeply nested anchors
Set the number of rulesets (i.e., anchors) directly attached to the
anchor and its path in pfctl_get_ruleset().

While here, add a test to document this behavior.

PR:		290478
Reviewed by:	kp
Fixes:		041ce1d690 ("pfctl: recursively flush rules and tables")
MFC after:	2 days
Differential Revision:	https://reviews.freebsd.org/D53358
2025-10-28 11:29:19 +00:00
Kristof Provost
bdb205c53e libpfctl: fix memory leak in pfctl_get_status()
Remember to also free ncounters.

Fixes:		c00aca9a71 ("pf: Show pf fragment reassembly counters.")
MFC after:	3 days
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-10-01 14:09:39 +02:00
Kristof Provost
b84666f798 pf: export expiration time as time_t
time_t has a different size on different platforms (i.e. 32-bit on i386, 64-bit
on others). Rather than always exporting it as 64-bits use the platform-native
size.
This means we can safely write directly into a time_t variable, which we can't
do on i386 eif we export 64 bits.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-09-25 14:41:11 +02:00
Kristof Provost
b9d652bb75 pf: print 'once' rule expire time
Obtained from:	OpenBSD, sashan <sashan@openbsd.org>, 8cf23eed7f
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-09-25 14:41:09 +02:00
Kristof Provost
c00aca9a71 pf: Show pf fragment reassembly counters.
Framgent count and statistics are stored in struct pf_status.  From
there pfctl(8) and systat(1) collect and show them.  Note that pfctl
-s info needs the -v switch to show fragments.

input claudio@; OK henning@

Obtained from:	OpenBSD, bluhm <bluhm@openbsd.org>, 19e99d0613
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-09-15 11:32:34 +02:00
Kristof Provost
932ec59d99 pf: fix ICMP type/code representation
internal representation of icmp type/code in pfctl(8)/pf(4) does not
fit into u_int8_t. Issue has been noticed and kindly reported by
amalinin _at_ bh0.amt.ru via bugs@.

OK bluhm@

Obtained from:	OpenBSD, sashan <sashan@openbsd.org>, 1fdb608f55
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-09-02 23:10:19 +02:00
Lexi Winter
1a80bcc5fa Remove redundant PACKAGE for INTERNALLIB libraries
These libraries don't install anything, so they shouldn't have a
PACKAGE setting.  This avoids surprising behaviour in future if
e.g. manpages are added to an internal library.

Reported by:	des
Differential Revision:	https://reviews.freebsd.org/D51901
2025-08-23 01:57:23 +01:00
Kristof Provost
094a60281b pf: fix potential infinite loop adding/deleting addresses in tables
The 'nadd' returned by these calls is the number of addresses actually added
or deleted. It can differ from the number userspace sent to the kernel if the
addresses are already present (or not present for the delete case).
This meant that if all of the addresses were already handled the kernel would
return zero, putting us in an infinite loop.

Handle this, and extend the test case to provoke this scenario.

Reported by:	netchild@
Fixes:	bad279e12d ("pf: convert DIOCRDELADDRS to netlink")
Fixes:	8b388995b8 ("pf: convert DIOCRADDADDRS to netlink")
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-08-18 12:03:23 +02:00
Kristof Provost
bad279e12d pf: convert DIOCRDELADDRS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-08-06 00:27:14 +02:00
Kristof Provost
8b388995b8 pf: convert DIOCRADDADDRS to netlink
Add up to 64 addresses at once. We are limited by the netlink socket buffer, so
we can only add a limited number at once.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-08-06 00:27:14 +02:00
Kajetan Staszkiewicz
d2761422eb pf: Use different address family for source and redirection address
The function pf_map_addr() and source tracking operate on a single
address family. This made sense before introducing address family
translation. When combining af-to with route-to or with sticky-address,
the next-hop or the NAT address are of different address family than
the source address. For example in NAT64 scenaro an IPv6 source address
is translated to an IPv4 address and routed over IPv4 gateway.

Make source nodes dual-AF, that is have a separate source AF and
redirection AF. Store route AF in struct pf_kstate, export it to pfctl.
When loading rules with redirection pools with pfctl store address
family of each address. When printing states don't deduce next-hop's
address family from af-to, use the one stored in state.

Reviewed by:	kp
Approved by:	kp
Sponsored by:	InnoGames GmbH
Differential Revision:	https://reviews.freebsd.org/D51659
2025-08-01 12:11:30 +02:00
Kristof Provost
799e21d544 libpfctl: fix reporting of flush address count
The PFNL_CMD_CLR_ADDRS command returns a PF_T_NBR_DELETED, not a PF_TS_NZEO.
Handle this correctly.

While here add a test case to verify we return the expected counts when adding
or flushing addresses to/from a table.

PR:		288353
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-07-23 13:56:11 +02:00
Kristof Provost
41fd03c08f pf: add 'max-pkt-size'
Allow pf to limit packets to a specified maximum size. This applies to all
packets, and if reassembly is enabled, looks at the reassembled size, not the
size of individual fragments.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-06-27 16:55:15 +02:00
Kristof Provost
ff11f1c8c7 pf: add a generic packet rate matching filter
allows things like
pass in proto icmp max-pkt-rate 100/10
all packets matching the rule in the direction the state was created are
taken into consideration (typically: requests, but not replies).
Just like with the other max-*, the rule stops matching if the maximum is
reached, so in typical scenarios the default block rule would kick in then.
with input from Holger Mikolon
ok mikeb

Obtained from:	OpenBSD, henning <henning@openbsd.org>, 5a4ae9a9cb
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D50798
2025-06-25 19:56:23 +02:00
Kristof Provost
581e064dde libpfctl: clear out source nodes before retrieving them
Zero out the struct pfctl_src_node before we read the next one. Avoid having
stray stack information (or the previous source node) influence our result.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-05-30 15:15:54 +02:00
Kristof Provost
b70fadca62 pf: fix dealing with 0 limits
uma doesn't like setting a limit on a zone which previously had none.
At startup pf applies the default limit to all zones, so we can assume a limit
was always set. We cope with the uma limitation by translating a limit of '0'
(i.e. unlimited) to INT_MAX. This is high enough that we'll never realistically
hit this limit, but it keeps uma happy.

Add a test case to provoke this, and while we're here also fix syncookie
handling of a 0 state limit.

See also:	d53927b0ba
Reported-by:	syzbot+02b784f183f79d4c07e4@syzkaller.appspotmail.com
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-05-08 15:10:25 +02:00
Kristof Provost
b543f426c2 pf: convert DIOCRCLRADDRS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-05-08 15:10:25 +02:00
Kristof Provost
a34efd08d9 libpfct: remove incorrect __unused annotation
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-05-08 15:10:24 +02:00
Kristof Provost
b3a68a2ec3 pf: convert DIOCRCLRTSTATS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-03-26 23:54:36 +01:00
Kristof Provost
9e8d2962aa pf: convert DIOCRGETTSTATS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-03-20 05:29:53 +01:00
Kajetan Staszkiewicz
07e070ef08 pf: Add support for multiple source node types
For every state pf creates up to two source nodes: a limiting one
struct pf_kstate -> src_node and a NAT one struct pf_kstate -> nat_src_node.
The limiting source node is tracking information needed for limits using
max-src-states and max-src-nodes and the NAT source node is tracking NAT
rules only.

On closer inspection some issues emerge:
- For route-to rules the redirection decision is stored in the limiting source
  node. Thus sticky-address and source limiting can't be used separately.
- Global source tracking, as promised in the man page, is totally absent from
  the code. Pfctl is capable of setting flags PFRULE_SRCTRACK (enable source
  tracking) and PFRULE_RULESRCTRACK (make source tracking per rule). The kernel
  code checks PFRULE_SRCTRACK but ignores PFRULE_RULESRCTRACK. That makes
  source tracking work per-rule only.

This patch is based on OpenBSD approach where source nodes have a type and each
state has an array of source node pointers indexed by source node type
instead of just two pointers. The conditions for limiting are applied
only to source nodes of PF_SN_LIMIT type. For global limit tracking
source nodes are attached to the default rule.

Reviewed by:		kp
Approved by:		kp (mentor)
Sponsored by:		InnoGames GmbH
Differential Revision:	https://reviews.freebsd.org/D39880
2025-02-13 15:59:12 +01:00
Kristof Provost
71594e3235 pf: support "!received-on <interface>"
ok dlg benno

Obtained from:	OpenBSD, henning <henning@openbsd.org>, 7d0482a910
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-02-13 13:38:44 +01:00
Kristof Provost
0d2058abf3 pf: convert DIOCRDELTABLES to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-02-10 12:09:47 +01:00
Kristof Provost
84a80eae69 pf: convert DIOCRADDTABLES to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-02-10 12:09:47 +01:00
Kristof Provost
0972294ef0 pf: add a dedicated pf pool for route options
As suggested by henning.
Which unbreaks ie route-to after the recent pf changes.

With much help debugging and pointing out of missing bits from claudio@

ok claudio@ "looks good" henning@

Obtained from:	OpenBSD, jsg <jsg@openbsd.org>, 7fa5c09028
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-01-24 11:20:30 +01:00
Kristof Provost
7c882c69a4 libpfctl: use snl_f_p_empty instead of declaring own empty array
Just as we did in the kernel in e9255dafa1

Suggested by:	glebius
Reviewed by:	glebius, melifaro
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D48460
2025-01-16 10:05:09 +01:00
Kristof Provost
441d489493 pf: convert DIOCRCLRTABLES to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2025-01-14 09:54:17 +01:00
Kristof Provost
aa69fdf154 pfctl: change for af-to / NAT64 support.
The general syntax is:
pass in inet from any to 192.168.1.1 af-to inet6 from 2001::1 to 2001::2
In the NAT64 case the "to" is not needed in af-to and the IP is extraced
from the IPv6 dst (assuming a /64 prefix).
Again most work by sperreault@, mikeb@ and reyk@
OK mcbride@, put it in deraadt@

Obtained from:	OpenBSD, claudio <claudio@openbsd.org>, 0cde32ce3f
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D47790
2024-12-17 11:07:13 +01:00
Kristof Provost
ebe11b4698 pf: fix state export in the face of NAT64
Now that we can NAT64 we can have states where the wire and stack address
families (and protocol) are different.  Update the state export code to account
for this.

We keep exporting address family and protocol outside of the key, for backwards
compatibility. This'll return misleading information to userspace in the NAT64
case, but it's assumed that userspace will either understand NAT64 (and thus
look for them in the correct place), or not configure it.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D47787
2024-12-17 11:07:13 +01:00
Kristof Provost
e11dacbf84 pf: partially import OpenBSD's NAT rewrite
We won't follow this fully, because it involves breaking syntax changes
(removing nat/rdr rules and moving this functionality into regular rules) as
well as behaviour changes because NAT is now done after the rules evaluation,
rather than before it.

We import some related changes anyway, because it paves the way for nat64
support.
This change introduces a new pf_kpool in struct pf_krule, for nat. It is not yet
used (but will be for nat64) and renames the existing 'rpool' to 'rdr'.

Obtained from:	OpenBSD, henning <henning@openbsd.org>, 0ef3d4febe
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D47783
2024-12-17 11:07:12 +01:00
Kristof Provost
9c12533672 pf: convert DIOCGETSRCNODES to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2024-10-15 16:29:11 +02:00
Kristof Provost
48f5bf8be6 pf: convert DIOCGETRULESET to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D46938
2024-10-10 14:10:41 +02:00
Kristof Provost
25e0f8f99f pf: convert DIOCGETRULESETS to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D46930
2024-10-10 14:10:40 +02:00
Kajetan Staszkiewicz
7fe42038b2 pf: fix max-src-conn when rules are added via netlink
Reviewed by:	kp
Differential Revision:	https://reviews.freebsd.org/D46797
2024-09-27 14:29:20 +02:00
Kristof Provost
93e96359c9 libpfctl: ensure we return useful error codes
Return errno rather than -1 on error. This allows pfctl to report much
more useful errors.

Reported by:	Alexander Leidinger <Alexander@Leidinger.net>
MFC after:	1 week
2024-09-22 00:55:46 +02:00
Kristof Provost
2339ead638 pf: allow filtering on the receive interface
add support to pf for filtering a packet by the interface it was received
on. use the received-on IFNAME filter option on a pf.conf rule to restrict
which packet the interface had to be received on. eg:

  pass out on em0 from $foo to $bar received-on fxp0

ive been running this in production for a week now. i find it particularly
usefull with interface groups.

no objections, and a few "i like"s from henning, claudio, deraadt, mpf

Obtained from:	OpenBSD, dlg <dlg@openbsd.org>, 95b4320893
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D46577
2024-09-16 13:47:07 +02:00
Kristof Provost
9ae91f59c5 pf: convert DIOCGETADDR to netlink
Sponsored by:	Rubicon Communications, LLC ("Netgate")
2024-07-22 09:11:49 +02:00