pf: Keep every chunk of a table that a ruleset defines

libpfctl sends a table definition in chunks of 256 addresses, each as
its own PFNL_CMD_INA_DEFINE.  The kernel treated every chunk as a new
definition and replaced the shadow table, so only the last chunk
survived.

Mark the chunks after the first with PF_ID_APPEND and have the kernel
add them to the shadow table in progress.  A request without the
attribute still replaces the shadow; one with it and no definition in
progress fails with EINVAL.  Old kernels ignore the attribute, so both
halves are needed; the ioctl sends all addresses at once and is not
affected.  Mask the flags userland passes to PFR_FLAG_ALLMASK, as
PFR_FLAG_APPEND is kernel-internal.

Add a test that defines a table of 1,000 addresses, also in an anchor.

Reviewed by:		kp
Fixes:			219ce30638 ("pf: convert DIOCRINADEFINE to netlink")
Sponsored by:		Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D60876
This commit is contained in:
R. Christian McDonald committed 2026-10-20 08:52:00 -04:00
1 parent 6aa2c651df
commit a317317519
7 files changed
+146 -3

No files matched your search

+8 -2
View File
@@ -4044,7 +4044,7 @@ SNL_DECLARE_PARSER(ina_define_parser, struct genlmsghdr, snl_f_p_empty, ap_ina_d
static int
_pfctl_ina_define(struct pfctl_handle *h, struct pfr_table *tbl,
struct pfr_addr *addr, int size, int *nadd, int *naddr,
int ticket, int flags)
int ticket, int flags, bool append)
{
struct snl_writer nw;
struct snl_errmsg_data e = {};
@@ -4061,6 +4061,7 @@ _pfctl_ina_define(struct pfctl_handle *h, struct pfr_table *tbl,
snl_add_msg_attr_table(&nw, PF_ID_TABLE, tbl);
snl_add_msg_attr_u32(&nw, PF_ID_TICKET, ticket);
snl_add_msg_attr_u32(&nw, PF_ID_FLAGS, flags);
snl_add_msg_attr_bool(&nw, PF_ID_APPEND, append);
for (int i = 0; i < size; i++)
snl_add_msg_attr_pfr_addr(&nw, PF_ID_ADDR, &addr[i]);
@@ -4106,10 +4107,15 @@ pfctl_ina_define(struct pfctl_handle *h, struct pfr_table *tbl,
if (naddr)
*naddr = 0;
/*
* The addresses go in chunks. The first one defines the table, the
* others add to it; without that, each would define it anew and
* only the last would be left.
*/
do {
chunk_size = MIN(size - off, 256);
ret = _pfctl_ina_define(h, tbl, &addr[off], chunk_size,
&partial_add, &partial_addr, ticket, flags);
&partial_add, &partial_addr, ticket, flags, off > 0);
if (ret != 0)
break;
if (nadd)