git: e11dacbf8484 - main - pf: partially import OpenBSD's NAT rewrite

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 17 Dec 2024 10:07:39 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=e11dacbf8484adc7bbb61b20fee3ab8385745925

commit e11dacbf8484adc7bbb61b20fee3ab8385745925
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-10-11 12:15:48 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-12-17 10:07:12 +0000

    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
---
 lib/libpfctl/libpfctl.c    |  10 ++--
 lib/libpfctl/libpfctl.h    |   9 +++-
 sbin/pfctl/pfctl.c         |   6 +--
 sbin/pfctl/pfctl_parser.h  |   2 +-
 sys/net/pfvar.h            |  31 ++++++++---
 sys/netpfil/pf/if_pfsync.c |   2 +-
 sys/netpfil/pf/pf.c        |  28 +++++-----
 sys/netpfil/pf/pf_ioctl.c  | 125 +++++++++++++++++++++++++++++----------------
 sys/netpfil/pf/pf_lb.c     |  76 +++++++++++++--------------
 sys/netpfil/pf/pf_nl.c     |  24 ++++++---
 sys/netpfil/pf/pf_nl.h     |   4 +-
 sys/netpfil/pf/pf_nv.c     |   4 +-
 12 files changed, 201 insertions(+), 120 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 4634fa99cb19..21d0b24601a4 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -731,7 +731,7 @@ pf_nvrule_to_rule(const nvlist_t *nvl, struct pfctl_rule *rule)
 	strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"),
 	    PF_TABLE_NAME_SIZE);
 
-	pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool);
+	pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rdr);
 
 	rule->evaluations = nvlist_get_number(nvl, "evaluations");
 	pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL);
@@ -1226,7 +1226,8 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
 	snl_add_msg_attr_string(nw, PF_RT_TAGNAME, r->tagname);
 	snl_add_msg_attr_string(nw, PF_RT_MATCH_TAGNAME, r->match_tagname);
 	snl_add_msg_attr_string(nw, PF_RT_OVERLOAD_TBLNAME, r->overload_tblname);
-	snl_add_msg_attr_rpool(nw, PF_RT_RPOOL, &r->rpool);
+	snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RDR, &r->rdr);
+	snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_NAT, &r->nat);
 	snl_add_msg_attr_u32(nw, PF_RT_OS_FINGERPRINT, r->os_fingerprint);
 	snl_add_msg_attr_u32(nw, PF_RT_RTABLEID, r->rtableid);
 	snl_add_msg_attr_timeouts(nw, PF_RT_TIMEOUT, r->timeout);
@@ -1596,7 +1597,7 @@ static struct snl_attr_parser ap_getrule[] = {
 	{ .type = PF_RT_TAGNAME, .off = _OUT(r.tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string },
 	{ .type = PF_RT_MATCH_TAGNAME, .off = _OUT(r.match_tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string },
 	{ .type = PF_RT_OVERLOAD_TBLNAME, .off = _OUT(r.overload_tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string },
-	{ .type = PF_RT_RPOOL, .off = _OUT(r.rpool), .arg = &pool_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_RT_RPOOL_RDR, .off = _OUT(r.rdr), .arg = &pool_parser, .cb = snl_attr_get_nested },
 	{ .type = PF_RT_OS_FINGERPRINT, .off = _OUT(r.os_fingerprint), .cb = snl_attr_get_uint32 },
 	{ .type = PF_RT_RTABLEID, .off = _OUT(r.rtableid), .cb = snl_attr_get_uint32 },
 	{ .type = PF_RT_TIMEOUT, .off = _OUT(r.timeout), .arg = &timeout_parser, .cb = snl_attr_get_nested_timeouts },
@@ -1660,6 +1661,7 @@ static struct snl_attr_parser ap_getrule[] = {
 	{ .type = PF_RT_ANCHOR_CALL, .off = _OUT(anchor_call), .arg = (void*)MAXPATHLEN, .cb = snl_attr_copy_string },
 	{ .type = PF_RT_RCV_IFNAME, .off = _OUT(r.rcv_ifname), .arg = (void*)IFNAMSIZ, .cb = snl_attr_copy_string },
 	{ .type = PF_RT_MAX_SRC_CONN, .off = _OUT(r.max_src_conn), .cb = snl_attr_get_uint32 },
+	{ .type = PF_RT_RPOOL_NAT, .off = _OUT(r.nat), .arg = &pool_parser, .cb = snl_attr_get_nested },
 };
 static struct snl_field_parser fp_getrule[] = {};
 #undef _OUT
@@ -2771,7 +2773,7 @@ pfctl_begin_addrs(struct pfctl_handle *h, uint32_t *ticket)
 }
 
 int
-pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa)
+pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which __unused)
 {
 	struct snl_writer nw;
 	struct snl_errmsg_data e = {};
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 5c3f1376960c..7b4aa0555758 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -174,7 +174,12 @@ struct pfctl_rule {
 	char			 overload_tblname[PF_TABLE_NAME_SIZE];
 
 	TAILQ_ENTRY(pfctl_rule)	 entries;
-	struct pfctl_pool	 rpool;
+	struct pfctl_pool	 nat;
+	union {
+		/* Alias old and new names. */
+		struct pfctl_pool	 rpool;
+		struct pfctl_pool	 rdr;
+	};
 
 	uint64_t		 evaluations;
 	uint64_t		 packets[2];
@@ -521,7 +526,7 @@ int	pfctl_get_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t *second
 int	pfctl_set_limit(struct pfctl_handle *h, const int index, const uint limit);
 int	pfctl_get_limit(struct pfctl_handle *h, const int index, uint *limit);
 int	pfctl_begin_addrs(struct pfctl_handle *h, uint32_t *ticket);
-int	pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa);
+int	pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which);
 int	pfctl_get_addrs(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
 	    uint8_t r_action, const char *anchor, uint32_t *nr);
 int	pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index aa3db4619972..4d77c7937a74 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1687,7 +1687,7 @@ pfctl_show_creators(int opts)
 
 /* callbacks for rule/nat/rdr/addr */
 int
-pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af)
+pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af, int which)
 {
 	struct pf_pooladdr *pa;
 	int ret;
@@ -1701,7 +1701,7 @@ pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af)
 	TAILQ_FOREACH(pa, &p->list, entries) {
 		memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr));
 		if ((pf->opts & PF_OPT_NOACTION) == 0) {
-			if ((ret = pfctl_add_addr(pf->h, &pf->paddr)) != 0)
+			if ((ret = pfctl_add_addr(pf->h, &pf->paddr, which)) != 0)
 				errc(1, ret, "DIOCADDADDR");
 		}
 	}
@@ -2045,7 +2045,7 @@ pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth)
 
 	was_present = false;
 	if ((pf->opts & PF_OPT_NOACTION) == 0) {
-		if (pfctl_add_pool(pf, &r->rpool, r->af))
+		if (pfctl_add_pool(pf, &r->rpool, r->af, PF_RDR))
 			return (1);
 		error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket,
 		    pf->paddr.ticket);
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 498027f5968c..551f2ff7537c 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -281,7 +281,7 @@ int	pfctl_append_rule(struct pfctl *, struct pfctl_rule *, const char *);
 int	pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *,
 	    const char *);
 int	pfctl_add_altq(struct pfctl *, struct pf_altq *);
-int	pfctl_add_pool(struct pfctl *, struct pfctl_pool *, sa_family_t);
+int	pfctl_add_pool(struct pfctl *, struct pfctl_pool *, sa_family_t, int);
 void	pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *);
 void	pfctl_clear_pool(struct pfctl_pool *);
 
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 100775347143..8cee1de14cb5 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -785,7 +785,8 @@ struct pf_krule {
 	char			 overload_tblname[PF_TABLE_NAME_SIZE];
 
 	TAILQ_ENTRY(pf_krule)	 entries;
-	struct pf_kpool		 rpool;
+	struct pf_kpool		 nat;
+	struct pf_kpool		 rdr;
 
 	struct pf_counter_u64	 evaluations;
 	struct pf_counter_u64	 packets[2];
@@ -1604,8 +1605,10 @@ struct pf_pdesc {
 
 	struct pf_addr	*src;		/* src address */
 	struct pf_addr	*dst;		/* dst address */
-	u_int16_t *sport;
-	u_int16_t *dport;
+	u_int16_t	*sport;
+	u_int16_t	*dport;
+	u_int16_t	 osport;
+	u_int16_t	 odport;
 	struct pf_mtag	*pf_mtag;
 	struct pf_rule_actions	act;
 
@@ -2192,7 +2195,7 @@ VNET_DECLARE(struct unrhdr64, pf_stateid);
 TAILQ_HEAD(pf_altqqueue, pf_altq);
 VNET_DECLARE(struct pf_altqqueue,	 pf_altqs[4]);
 #define	V_pf_altqs			 VNET(pf_altqs)
-VNET_DECLARE(struct pf_kpalist,		 pf_pabuf);
+VNET_DECLARE(struct pf_kpalist,		 pf_pabuf[2]);
 #define	V_pf_pabuf			 VNET(pf_pabuf)
 
 VNET_DECLARE(u_int32_t,			 ticket_altqs_active);
@@ -2527,6 +2530,20 @@ VNET_DECLARE(struct pf_limit, pf_limits[PF_LIMIT_MAX]);
 #endif /* _KERNEL */
 
 #ifdef _KERNEL
+struct pf_nl_pooladdr {
+	u_int32_t		 action;
+	u_int32_t		 ticket;
+	u_int32_t		 nr;
+	u_int32_t		 r_num;
+	u_int8_t		 r_action;
+	u_int8_t		 r_last;
+	u_int8_t		 af;
+	char			 anchor[MAXPATHLEN];
+	struct pf_pooladdr	 addr;
+	/* Above this is identical to pfioc_pooladdr */
+	int			 which;
+};
+
 VNET_DECLARE(struct pf_kanchor_global,		 pf_anchors);
 #define	V_pf_anchors				 VNET(pf_anchors)
 VNET_DECLARE(struct pf_kanchor,			 pf_main_anchor);
@@ -2579,9 +2596,9 @@ int			 pf_ioctl_set_timeout(int, int, int *);
 int			 pf_ioctl_get_limit(int, unsigned int *);
 int			 pf_ioctl_set_limit(int, unsigned int, unsigned int *);
 int			 pf_ioctl_begin_addrs(uint32_t *);
-int			 pf_ioctl_add_addr(struct pfioc_pooladdr *);
-int			 pf_ioctl_get_addrs(struct pfioc_pooladdr *);
-int			 pf_ioctl_get_addr(struct pfioc_pooladdr *);
+int			 pf_ioctl_add_addr(struct pf_nl_pooladdr *);
+int			 pf_ioctl_get_addrs(struct pf_nl_pooladdr *);
+int			 pf_ioctl_get_addr(struct pf_nl_pooladdr *);
 int			 pf_ioctl_get_rulesets(struct pfioc_ruleset *);
 int			 pf_ioctl_get_ruleset(struct pfioc_ruleset *);
 
diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c
index 5923675ff144..60bfb05d1570 100644
--- a/sys/netpfil/pf/if_pfsync.c
+++ b/sys/netpfil/pf/if_pfsync.c
@@ -582,7 +582,7 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
 			 * give up, as we can't be sure that we will pick the
 			 * same one as the pfsync peer did.
 			 */
-			rpool_first = TAILQ_FIRST(&(r->rpool.list));
+			rpool_first = TAILQ_FIRST(&(r->rdr.list));
 			if ((rpool_first == NULL) ||
 			    (TAILQ_NEXT(rpool_first, entries) != NULL)) {
 				DPFPRINTF(PF_DEBUG_MISC,
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index d0ddff9f38a2..806800174f03 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -157,7 +157,7 @@ SDT_PROBE_DEFINE2(pf, purge, state, rowcount, "int", "size_t");
 
 /* state tables */
 VNET_DEFINE(struct pf_altqqueue,	 pf_altqs[4]);
-VNET_DEFINE(struct pf_kpalist,		 pf_pabuf);
+VNET_DEFINE(struct pf_kpalist,		 pf_pabuf[2]);
 VNET_DEFINE(struct pf_altqqueue *,	 pf_altqs_active);
 VNET_DEFINE(struct pf_altqqueue *,	 pf_altq_ifs_active);
 VNET_DEFINE(struct pf_altqqueue *,	 pf_altqs_inactive);
@@ -332,8 +332,7 @@ static int		 pf_test_rule(struct pf_krule **, struct pf_kstate **,
 			    struct pf_kruleset **, struct inpcb *);
 static int		 pf_create_state(struct pf_krule *, struct pf_krule *,
 			    struct pf_krule *, struct pf_pdesc *,
-			    struct pf_state_key *, struct pf_state_key *,
-			    u_int16_t, u_int16_t, int *,
+			    struct pf_state_key *, struct pf_state_key *, int *,
 			    struct pf_kstate **, int, u_int16_t, u_int16_t,
 			    struct pf_krule_slist *, struct pf_udp_mapping *);
 static int		 pf_state_key_addr_setup(struct pf_pdesc *,
@@ -1027,7 +1026,7 @@ pf_insert_src_node(struct pf_ksrc_node **sn, struct pf_srchash **sh,
 	u_short			 reason = 0;
 
 	KASSERT((rule->rule_flag & PFRULE_SRCTRACK ||
-	    rule->rpool.opts & PF_POOL_STICKYADDR),
+	    rule->rdr.opts & PF_POOL_STICKYADDR),
 	    ("%s for non-tracking rule %p", __func__, rule));
 
 	/*
@@ -1242,7 +1241,8 @@ pf_initialize(void)
 	TAILQ_INIT(&V_pf_altqs[1]);
 	TAILQ_INIT(&V_pf_altqs[2]);
 	TAILQ_INIT(&V_pf_altqs[3]);
-	TAILQ_INIT(&V_pf_pabuf);
+	TAILQ_INIT(&V_pf_pabuf[0]);
+	TAILQ_INIT(&V_pf_pabuf[1]);
 	V_pf_altqs_active = &V_pf_altqs[0];
 	V_pf_altq_ifs_active = &V_pf_altqs[1];
 	V_pf_altqs_inactive = &V_pf_altqs[2];
@@ -4979,6 +4979,8 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
 		sport = dport = 0;
 		break;
 	}
+	pd->osport = sport;
+	pd->odport = dport;
 
 	r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_FILTER].active.ptr);
 
@@ -5327,7 +5329,7 @@ nextrule:
 	    (pd->flags & PFDESC_TCP_NORM)))) {
 		int action;
 		action = pf_create_state(r, nr, a, pd, nk, sk,
-		    sport, dport, &rewrite, sm, tag, bproto_sum, bip_sum,
+		    &rewrite, sm, tag, bproto_sum, bip_sum,
 		    &match_rules, udp_mapping);
 		if (action != PF_PASS) {
 			pf_udp_mapping_release(udp_mapping);
@@ -5382,9 +5384,9 @@ cleanup:
 static int
 pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
     struct pf_pdesc *pd, struct pf_state_key *nk, struct pf_state_key *sk,
-    u_int16_t sport, u_int16_t dport, int *rewrite, struct pf_kstate **sm,
-    int tag, u_int16_t bproto_sum, u_int16_t bip_sum,
-    struct pf_krule_slist *match_rules, struct pf_udp_mapping *udp_mapping)
+    int *rewrite, struct pf_kstate **sm, int tag, u_int16_t bproto_sum,
+    u_int16_t bip_sum, struct pf_krule_slist *match_rules,
+    struct pf_udp_mapping *udp_mapping)
 {
 	struct pf_kstate	*s = NULL;
 	struct pf_ksrc_node	*sn = NULL;
@@ -5405,14 +5407,14 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 	}
 	/* src node for filter rule */
 	if ((r->rule_flag & PFRULE_SRCTRACK ||
-	    r->rpool.opts & PF_POOL_STICKYADDR) &&
+	    r->rdr.opts & PF_POOL_STICKYADDR) &&
 	    (sn_reason = pf_insert_src_node(&sn, &snh, r, pd->src, pd->af,
 	    &pd->act.rt_addr, pd->act.rt_kif)) != 0) {
 		REASON_SET(&reason, sn_reason);
 		goto csfailed;
 	}
 	/* src node for translation rule */
-	if (nr != NULL && (nr->rpool.opts & PF_POOL_STICKYADDR) &&
+	if (nr != NULL && (nr->rdr.opts & PF_POOL_STICKYADDR) &&
 	    (sn_reason = pf_insert_src_node(&nsn, &nsnh, nr, &sk->addr[pd->sidx],
 	    pd->af, &nk->addr[1], NULL)) != 0 ) {
 		REASON_SET(&reason, sn_reason);
@@ -5535,7 +5537,9 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 	if (nr == NULL) {
 		KASSERT((sk == NULL && nk == NULL), ("%s: nr %p sk %p, nk %p",
 		    __func__, nr, sk, nk));
-		sk = pf_state_key_setup(pd, pd->src, pd->dst, sport, dport);
+		MPASS(pd->sport == NULL || (pd->osport == *pd->sport));
+		MPASS(pd->dport == NULL || (pd->odport == *pd->dport));
+		sk = pf_state_key_setup(pd, pd->src, pd->dst, pd->osport, pd->odport);
 		if (sk == NULL)
 			goto csfailed;
 		nk = sk;
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 0fdf41a9811f..35af04eda837 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -100,7 +100,7 @@ SDT_PROBE_DEFINE2(pf, ioctl, addrule, error, "int", "int");
 SDT_PROBE_DEFINE2(pf, ioctl, nvchk, error, "int", "int");
 
 static struct pf_kpool	*pf_get_kpool(const char *, u_int32_t, u_int8_t,
-			    u_int32_t, u_int8_t, u_int8_t, u_int8_t);
+			    u_int32_t, u_int8_t, u_int8_t, u_int8_t, int);
 
 static void		 pf_mv_kpool(struct pf_kpalist *, struct pf_kpalist *);
 static void		 pf_empty_kpool(struct pf_kpalist *);
@@ -430,12 +430,14 @@ pfattach_vnet(void)
 static struct pf_kpool *
 pf_get_kpool(const char *anchor, u_int32_t ticket, u_int8_t rule_action,
     u_int32_t rule_number, u_int8_t r_last, u_int8_t active,
-    u_int8_t check_ticket)
+    u_int8_t check_ticket, int which)
 {
 	struct pf_kruleset	*ruleset;
 	struct pf_krule		*rule;
 	int			 rs_num;
 
+	MPASS(which == PF_RDR || which == PF_NAT);
+
 	ruleset = pf_find_kruleset(anchor);
 	if (ruleset == NULL)
 		return (NULL);
@@ -468,7 +470,10 @@ pf_get_kpool(const char *anchor, u_int32_t ticket, u_int8_t rule_action,
 	if (rule == NULL)
 		return (NULL);
 
-	return (&rule->rpool);
+	if (which == PF_NAT)
+		return (&rule->nat);
+	else
+		return (&rule->rdr);
 }
 
 static void
@@ -605,7 +610,7 @@ pf_free_rule(struct pf_krule *rule)
 	if (rule->rcv_kif)
 		pfi_kkif_unref(rule->rcv_kif);
 	pf_kanchor_remove(rule);
-	pf_empty_kpool(&rule->rpool.list);
+	pf_empty_kpool(&rule->rdr.list);
 
 	pf_krule_free(rule);
 }
@@ -1824,7 +1829,8 @@ pf_krule_alloc(void)
 	struct pf_krule *rule;
 
 	rule = malloc(sizeof(struct pf_krule), M_PFRULE, M_WAITOK | M_ZERO);
-	mtx_init(&rule->rpool.mtx, "pf_krule_pool", NULL, MTX_DEF);
+	mtx_init(&rule->nat.mtx, "pf_krule_nat_pool", NULL, MTX_DEF);
+	mtx_init(&rule->rdr.mtx, "pf_krule_rdr_pool", NULL, MTX_DEF);
 	rule->timestamp = uma_zalloc_pcpu(pf_timestamp_pcpu_zone,
 	    M_WAITOK | M_ZERO);
 	return (rule);
@@ -1862,7 +1868,8 @@ pf_krule_free(struct pf_krule *rule)
 	counter_u64_free(rule->src_nodes);
 	uma_zfree_pcpu(pf_timestamp_pcpu_zone, rule->timestamp);
 
-	mtx_destroy(&rule->rpool.mtx);
+	mtx_destroy(&rule->nat.mtx);
+	mtx_destroy(&rule->rdr.mtx);
 	free(rule, M_PFRULE);
 }
 
@@ -1966,7 +1973,7 @@ pf_rule_to_krule(const struct pf_rule *rule, struct pf_krule *krule)
 	if (ret != 0)
 		return (ret);
 
-	pf_pool_to_kpool(&rule->rpool, &krule->rpool);
+	pf_pool_to_kpool(&rule->rpool, &krule->rdr);
 
 	/* Don't allow userspace to set evaluations, packets or bytes. */
 	/* kif, anchor, overload_tbl are not copied over. */
@@ -2096,7 +2103,8 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
 	rule->src_nodes = counter_u64_alloc(M_WAITOK);
 	rule->cuid = uid;
 	rule->cpid = pid;
-	TAILQ_INIT(&rule->rpool.list);
+	TAILQ_INIT(&rule->rdr.list);
+	TAILQ_INIT(&rule->nat.list);
 
 	PF_CONFIG_LOCK();
 	PF_RULES_WLOCK();
@@ -2194,13 +2202,15 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
 	    (rule->set_prio[0] > PF_PRIO_MAX ||
 	    rule->set_prio[1] > PF_PRIO_MAX))
 		error = EINVAL;
-	TAILQ_FOREACH(pa, &V_pf_pabuf, entries)
-		if (pa->addr.type == PF_ADDR_TABLE) {
-			pa->addr.p.tbl = pfr_attach_table(ruleset,
-			    pa->addr.v.tblname);
-			if (pa->addr.p.tbl == NULL)
-				error = ENOMEM;
-		}
+	for (int i = 0; i < 2; i++) {
+		TAILQ_FOREACH(pa, &V_pf_pabuf[i], entries)
+			if (pa->addr.type == PF_ADDR_TABLE) {
+				pa->addr.p.tbl = pfr_attach_table(ruleset,
+				    pa->addr.v.tblname);
+				if (pa->addr.p.tbl == NULL)
+					error = ENOMEM;
+			}
+	}
 
 	rule->overload_tbl = NULL;
 	if (rule->overload_tblname[0]) {
@@ -2212,14 +2222,15 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
 			    PFR_TFLAG_ACTIVE;
 	}
 
-	pf_mv_kpool(&V_pf_pabuf, &rule->rpool.list);
+	pf_mv_kpool(&V_pf_pabuf[0], &rule->nat.list);
+	pf_mv_kpool(&V_pf_pabuf[1], &rule->rdr.list);
 	if (((((rule->action == PF_NAT) || (rule->action == PF_RDR) ||
 	    (rule->action == PF_BINAT)) && rule->anchor == NULL) ||
 	    (rule->rt > PF_NOPFROUTE)) &&
-	    (TAILQ_FIRST(&rule->rpool.list) == NULL))
+	    (TAILQ_FIRST(&rule->rdr.list) == NULL))
 		error = EINVAL;
 
-	if (rule->action == PF_PASS && rule->rpool.opts & PF_POOL_STICKYADDR &&
+	if (rule->action == PF_PASS && rule->rdr.opts & PF_POOL_STICKYADDR &&
 	    !rule->keep_state) {
 		error = EINVAL;
 	}
@@ -2230,7 +2241,8 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
 		ERROUT(error);
 	}
 
-	rule->rpool.cur = TAILQ_FIRST(&rule->rpool.list);
+	rule->nat.cur = TAILQ_FIRST(&rule->nat.list);
+	rule->rdr.cur = TAILQ_FIRST(&rule->rdr.list);
 	TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr,
 	    rule, entries);
 	ruleset->rules[rs_num].inactive.rcount++;
@@ -2538,7 +2550,8 @@ int
 pf_ioctl_begin_addrs(uint32_t *ticket)
 {
 	PF_RULES_WLOCK();
-	pf_empty_kpool(&V_pf_pabuf);
+	pf_empty_kpool(&V_pf_pabuf[0]);
+	pf_empty_kpool(&V_pf_pabuf[1]);
 	*ticket = ++V_ticket_pabuf;
 	PF_RULES_WUNLOCK();
 
@@ -2546,12 +2559,14 @@ pf_ioctl_begin_addrs(uint32_t *ticket)
 }
 
 int
-pf_ioctl_add_addr(struct pfioc_pooladdr *pp)
+pf_ioctl_add_addr(struct pf_nl_pooladdr *pp)
 {
 	struct pf_kpooladdr	*pa = NULL;
 	struct pfi_kkif		*kif = NULL;
 	int error;
 
+	MPASS(pp->which == PF_RDR || pp->which == PF_NAT);
+
 #ifndef INET
 	if (pp->af == AF_INET)
 		return (EAFNOSUPPORT);
@@ -2596,7 +2611,8 @@ pf_ioctl_add_addr(struct pfioc_pooladdr *pp)
 		PF_RULES_WUNLOCK();
 		goto out;
 	}
-	TAILQ_INSERT_TAIL(&V_pf_pabuf, pa, entries);
+	TAILQ_INSERT_TAIL(&V_pf_pabuf[pp->which == PF_RDR ? 1 : 0],
+	    pa, entries);
 	PF_RULES_WUNLOCK();
 
 	return (0);
@@ -2607,19 +2623,21 @@ out:
 }
 
 int
-pf_ioctl_get_addrs(struct pfioc_pooladdr *pp)
+pf_ioctl_get_addrs(struct pf_nl_pooladdr *pp)
 {
 	struct pf_kpool		*pool;
 	struct pf_kpooladdr	*pa;
 
 	PF_RULES_RLOCK_TRACKER;
 
+	MPASS(pp->which == PF_RDR || pp->which == PF_NAT);
+
 	pp->anchor[sizeof(pp->anchor) - 1] = 0;
 	pp->nr = 0;
 
 	PF_RULES_RLOCK();
 	pool = pf_get_kpool(pp->anchor, pp->ticket, pp->r_action,
-	    pp->r_num, 0, 1, 0);
+	    pp->r_num, 0, 1, 0, pp->which);
 	if (pool == NULL) {
 		PF_RULES_RUNLOCK();
 		return (EBUSY);
@@ -2632,19 +2650,21 @@ pf_ioctl_get_addrs(struct pfioc_pooladdr *pp)
 }
 
 int
-pf_ioctl_get_addr(struct pfioc_pooladdr *pp)
+pf_ioctl_get_addr(struct pf_nl_pooladdr *pp)
 {
 	struct pf_kpool		*pool;
 	struct pf_kpooladdr	*pa;
 	u_int32_t		 nr = 0;
 
+	MPASS(pp->which == PF_RDR || pp->which == PF_NAT);
+
 	PF_RULES_RLOCK_TRACKER;
 
 	pp->anchor[sizeof(pp->anchor) - 1] = 0;
 
 	PF_RULES_RLOCK();
 	pool = pf_get_kpool(pp->anchor, pp->ticket, pp->r_action,
-	    pp->r_num, 0, 1, 1);
+	    pp->r_num, 0, 1, 1, pp->which);
 	if (pool == NULL) {
 		PF_RULES_RUNLOCK();
 		return (EBUSY);
@@ -3626,7 +3646,8 @@ DIOCGETRULENV_error:
 			newrule->src_nodes = counter_u64_alloc(M_WAITOK);
 			newrule->cuid = td->td_ucred->cr_ruid;
 			newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0;
-			TAILQ_INIT(&newrule->rpool.list);
+			TAILQ_INIT(&newrule->nat.list);
+			TAILQ_INIT(&newrule->rdr.list);
 		}
 #define	ERROUT(x)	ERROUT_IOCTL(DIOCCHANGERULE_error, x)
 
@@ -3723,14 +3744,16 @@ DIOCGETRULENV_error:
 				error = ENOMEM;
 			if (pf_kanchor_setup(newrule, ruleset, pcr->anchor_call))
 				error = EINVAL;
-			TAILQ_FOREACH(pa, &V_pf_pabuf, entries)
-				if (pa->addr.type == PF_ADDR_TABLE) {
-					pa->addr.p.tbl =
-					    pfr_attach_table(ruleset,
-					    pa->addr.v.tblname);
-					if (pa->addr.p.tbl == NULL)
-						error = ENOMEM;
-				}
+			for (int i = 0; i < 2; i++) {
+				TAILQ_FOREACH(pa, &V_pf_pabuf[i], entries)
+					if (pa->addr.type == PF_ADDR_TABLE) {
+						pa->addr.p.tbl =
+						    pfr_attach_table(ruleset,
+						    pa->addr.v.tblname);
+						if (pa->addr.p.tbl == NULL)
+							error = ENOMEM;
+					}
+			}
 
 			newrule->overload_tbl = NULL;
 			if (newrule->overload_tblname[0]) {
@@ -3743,13 +3766,14 @@ DIOCGETRULENV_error:
 					    PFR_TFLAG_ACTIVE;
 			}
 
-			pf_mv_kpool(&V_pf_pabuf, &newrule->rpool.list);
+			pf_mv_kpool(&V_pf_pabuf[0], &newrule->nat.list);
+			pf_mv_kpool(&V_pf_pabuf[1], &newrule->rdr.list);
 			if (((((newrule->action == PF_NAT) ||
 			    (newrule->action == PF_RDR) ||
 			    (newrule->action == PF_BINAT) ||
 			    (newrule->rt > PF_NOPFROUTE)) &&
 			    !newrule->anchor)) &&
-			    (TAILQ_FIRST(&newrule->rpool.list) == NULL))
+			    (TAILQ_FIRST(&newrule->rdr.list) == NULL))
 				error = EINVAL;
 
 			if (error) {
@@ -3759,9 +3783,11 @@ DIOCGETRULENV_error:
 				break;
 			}
 
-			newrule->rpool.cur = TAILQ_FIRST(&newrule->rpool.list);
+			newrule->nat.cur = TAILQ_FIRST(&newrule->nat.list);
+			newrule->rdr.cur = TAILQ_FIRST(&newrule->rdr.list);
 		}
-		pf_empty_kpool(&V_pf_pabuf);
+		pf_empty_kpool(&V_pf_pabuf[0]);
+		pf_empty_kpool(&V_pf_pabuf[1]);
 
 		if (pcr->action == PF_CHANGE_ADD_HEAD)
 			oldrule = TAILQ_FIRST(
@@ -4390,22 +4416,35 @@ DIOCGETSTATESV2_full:
 
 	case DIOCADDADDR: {
 		struct pfioc_pooladdr	*pp = (struct pfioc_pooladdr *)addr;
+		struct pf_nl_pooladdr npp = {};
 
-		error = pf_ioctl_add_addr(pp);
+		npp.which = PF_RDR;
+		memcpy(&npp, pp, sizeof(*pp));
+		error = pf_ioctl_add_addr(&npp);
 		break;
 	}
 
 	case DIOCGETADDRS: {
 		struct pfioc_pooladdr	*pp = (struct pfioc_pooladdr *)addr;
+		struct pf_nl_pooladdr npp = {};
+
+		npp.which = PF_RDR;
+		memcpy(&npp, pp, sizeof(*pp));
+		error = pf_ioctl_get_addrs(&npp);
+		memcpy(pp, &npp, sizeof(*pp));
 
-		error = pf_ioctl_get_addrs(pp);
 		break;
 	}
 
 	case DIOCGETADDR: {
 		struct pfioc_pooladdr	*pp = (struct pfioc_pooladdr *)addr;
+		struct pf_nl_pooladdr npp = {};
+
+		npp.which = PF_RDR;
+		memcpy(&npp, pp, sizeof(*pp));
+		error = pf_ioctl_get_addr(&npp);
+		memcpy(pp, &npp, sizeof(*pp));
 
-		error = pf_ioctl_get_addr(pp);
 		break;
 	}
 
@@ -4460,7 +4499,7 @@ DIOCGETSTATESV2_full:
 			ERROUT(EBUSY);
 
 		pool = pf_get_kpool(pca->anchor, pca->ticket, pca->r_action,
-		    pca->r_num, pca->r_last, 1, 1);
+		    pca->r_num, pca->r_last, 1, 1, PF_RDR);
 		if (pool == NULL)
 			ERROUT(EBUSY);
 
diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c
index 8087546683af..10129a5fab46 100644
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -149,8 +149,8 @@ pf_match_translation(struct pf_pdesc *pd,
 
 		if (r->action == PF_BINAT && pd->dir == PF_IN) {
 			src = &r->dst;
-			if (r->rpool.cur != NULL)
-				xdst = &r->rpool.cur->addr;
+			if (r->rdr.cur != NULL)
+				xdst = &r->rdr.cur->addr;
 		} else {
 			src = &r->src;
 			dst = &r->dst;
@@ -240,7 +240,7 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 	 * from the mapping. In this case we have to look up the src_node as
 	 * pf_map_addr would.
 	 */
-	if (proto == IPPROTO_UDP && (r->rpool.opts & PF_POOL_ENDPI)) {
+	if (proto == IPPROTO_UDP && (r->rdr.opts & PF_POOL_ENDPI)) {
 		struct pf_udp_endpoint_cmp udp_source;
 
 		bzero(&udp_source, sizeof(udp_source));
@@ -252,8 +252,8 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 			PF_ACPY(naddr, &(*udp_mapping)->endpoints[1].addr, af);
 			*nport = (*udp_mapping)->endpoints[1].port;
 			/* Try to find a src_node as per pf_map_addr(). */
-			if (*sn == NULL && r->rpool.opts & PF_POOL_STICKYADDR &&
-			    (r->rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_NONE)
+			if (*sn == NULL && r->rdr.opts & PF_POOL_STICKYADDR &&
+			    (r->rdr.opts & PF_POOL_TYPEMASK) != PF_POOL_NONE)
 				*sn = pf_find_src_node(saddr, r, af, sh, false);
 			if (*sn != NULL)
 				PF_SRC_NODE_UNLOCK(*sn);
@@ -363,7 +363,7 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 			tmp = cut;
 			for (tmp -= 1; tmp >= low && tmp <= 0xffff; --tmp) {
 				if (proto == IPPROTO_UDP &&
-				    (r->rpool.opts & PF_POOL_ENDPI)) {
+				    (r->rdr.opts & PF_POOL_ENDPI)) {
 					(*udp_mapping)->endpoints[1].port = htons(tmp);
 					if (pf_udp_mapping_insert(*udp_mapping) == 0) {
 						*nport = htons(tmp);
@@ -379,7 +379,7 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 			}
 		}
 
-		switch (r->rpool.opts & PF_POOL_TYPEMASK) {
+		switch (r->rdr.opts & PF_POOL_TYPEMASK) {
 		case PF_POOL_RANDOM:
 		case PF_POOL_ROUNDROBIN:
 			/*
@@ -423,13 +423,13 @@ pf_get_mape_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 	uint16_t i, ahigh, cut;
 	int ashift, psidshift;
 
-	ashift = 16 - r->rpool.mape.offset;
-	psidshift = ashift - r->rpool.mape.psidlen;
-	psmask = r->rpool.mape.psid & ((1U << r->rpool.mape.psidlen) - 1);
+	ashift = 16 - r->rdr.mape.offset;
+	psidshift = ashift - r->rdr.mape.psidlen;
+	psmask = r->rdr.mape.psid & ((1U << r->rdr.mape.psidlen) - 1);
 	psmask = psmask << psidshift;
 	highmask = (1U << psidshift) - 1;
 
-	ahigh = (1U << r->rpool.mape.offset) - 1;
+	ahigh = (1U << r->rdr.mape.offset) - 1;
 	cut = arc4random() & ahigh;
 	if (cut == 0)
 		cut = 1;
@@ -454,7 +454,7 @@ pf_map_addr(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr,
     struct pf_addr *naddr, struct pfi_kkif **nkif, struct pf_addr *init_addr)
 {
 	u_short			 reason = PFRES_MATCH;
-	struct pf_kpool		*rpool = &r->rpool;
+	struct pf_kpool		*rpool = &r->rdr;
 	struct pf_addr		*raddr = NULL, *rmask = NULL;
 
 	mtx_lock(&rpool->mtx);
@@ -629,7 +629,7 @@ pf_map_addr_sn(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr,
     struct pf_ksrc_node **sn, struct pf_srchash **sh)
 {
 	u_short			 reason = 0;
-	struct pf_kpool		*rpool = &r->rpool;
+	struct pf_kpool		*rpool = &r->rdr;
 
 	KASSERT(*sn == NULL, ("*sn not NULL"));
 
@@ -638,8 +638,8 @@ pf_map_addr_sn(sa_family_t af, struct pf_krule *r, struct pf_addr *saddr,
 	 * Request the sh to be unlocked if sn was not found, as we never
 	 * insert a new sn when parsing the ruleset.
 	 */
-	if (r->rpool.opts & PF_POOL_STICKYADDR &&
-	    (r->rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_NONE)
+	if (r->rdr.opts & PF_POOL_STICKYADDR &&
+	    (r->rdr.opts & PF_POOL_TYPEMASK) != PF_POOL_NONE)
 		*sn = pf_find_src_node(saddr, r, af, sh, false);
 
 	if (*sn != NULL) {
@@ -763,19 +763,19 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 			low = 1;
 			high = 65535;
 		} else {
-			low  = r->rpool.proxy_port[0];
-			high = r->rpool.proxy_port[1];
+			low  = r->rdr.proxy_port[0];
+			high = r->rdr.proxy_port[1];
 		}
-		if (r->rpool.mape.offset > 0) {
+		if (r->rdr.mape.offset > 0) {
 			if (pf_get_mape_sport(pd->af, pd->proto, r, saddr,
 			    sport, daddr, dport, naddr, nportp, &sn, &sh,
 			    udp_mapping)) {
 				DPFPRINTF(PF_DEBUG_MISC,
 				    ("pf: MAP-E port allocation (%u/%u/%u)"
 				    " failed\n",
-				    r->rpool.mape.offset,
-				    r->rpool.mape.psidlen,
-				    r->rpool.mape.psid));
+				    r->rdr.mape.offset,
+				    r->rdr.mape.psidlen,
+				    r->rdr.mape.psid));
 				reason = PFRES_MAPFAILED;
 				goto notrans;
 			}
@@ -784,7 +784,7 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 		    udp_mapping)) {
 			DPFPRINTF(PF_DEBUG_MISC,
 			    ("pf: NAT proxy port allocation (%u-%u) failed\n",
-			    r->rpool.proxy_port[0], r->rpool.proxy_port[1]));
+			    r->rdr.proxy_port[0], r->rdr.proxy_port[1]));
 			reason = PFRES_MAPFAILED;
 			goto notrans;
 		}
@@ -792,41 +792,41 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 	case PF_BINAT:
 		switch (pd->dir) {
 		case PF_OUT:
-			if (r->rpool.cur->addr.type == PF_ADDR_DYNIFTL){
+			if (r->rdr.cur->addr.type == PF_ADDR_DYNIFTL){
 				switch (pd->af) {
 #ifdef INET
 				case AF_INET:
-					if (r->rpool.cur->addr.p.dyn->
+					if (r->rdr.cur->addr.p.dyn->
 					    pfid_acnt4 < 1) {
 						reason = PFRES_MAPFAILED;
 						goto notrans;
 					}
 					PF_POOLMASK(naddr,
-					    &r->rpool.cur->addr.p.dyn->
+					    &r->rdr.cur->addr.p.dyn->
 					    pfid_addr4,
-					    &r->rpool.cur->addr.p.dyn->
+					    &r->rdr.cur->addr.p.dyn->
 					    pfid_mask4, saddr, AF_INET);
 					break;
 #endif /* INET */
 #ifdef INET6
 				case AF_INET6:
-					if (r->rpool.cur->addr.p.dyn->
+					if (r->rdr.cur->addr.p.dyn->
 					    pfid_acnt6 < 1) {
 						reason = PFRES_MAPFAILED;
 						goto notrans;
 					}
 					PF_POOLMASK(naddr,
-					    &r->rpool.cur->addr.p.dyn->
+					    &r->rdr.cur->addr.p.dyn->
 					    pfid_addr6,
-					    &r->rpool.cur->addr.p.dyn->
+					    &r->rdr.cur->addr.p.dyn->
 					    pfid_mask6, saddr, AF_INET6);
 					break;
 #endif /* INET6 */
 				}
 			} else
 				PF_POOLMASK(naddr,
-				    &r->rpool.cur->addr.v.a.addr,
-				    &r->rpool.cur->addr.v.a.mask, saddr,
+				    &r->rdr.cur->addr.v.a.addr,
+				    &r->rdr.cur->addr.v.a.mask, saddr,
 				    pd->af);
 			break;
 		case PF_IN:
@@ -871,27 +871,27 @@ pf_get_translation(struct pf_pdesc *pd, int off,
 		reason = pf_map_addr_sn(pd->af, r, saddr, naddr, NULL, NULL, &sn, &sh);
 		if (reason != 0)
 			goto notrans;
-		if ((r->rpool.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
-			PF_POOLMASK(naddr, naddr, &r->rpool.cur->addr.v.a.mask,
+		if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
+			PF_POOLMASK(naddr, naddr, &r->rdr.cur->addr.v.a.mask,
 			    daddr, pd->af);
 
 		/* Do not change SCTP ports. */
 		if (pd->proto == IPPROTO_SCTP)
 			break;
 
-		if (r->rpool.proxy_port[1]) {
+		if (r->rdr.proxy_port[1]) {
 			uint32_t	tmp_nport;
 
 			tmp_nport = ((ntohs(dport) - ntohs(r->dst.port[0])) %
-			    (r->rpool.proxy_port[1] - r->rpool.proxy_port[0] +
-			    1)) + r->rpool.proxy_port[0];
+			    (r->rdr.proxy_port[1] - r->rdr.proxy_port[0] +
+			    1)) + r->rdr.proxy_port[0];
 
 			/* Wrap around if necessary. */
 			if (tmp_nport > 65535)
 				tmp_nport -= 65535;
 			nport = htons((uint16_t)tmp_nport);
-		} else if (r->rpool.proxy_port[0])
-			nport = htons(r->rpool.proxy_port[0]);
+		} else if (r->rdr.proxy_port[0])
+			nport = htons(r->rdr.proxy_port[0]);
 		else
 			nport = dport;
 
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index ad7dc97cbc1a..52d77034c4b7 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -678,7 +678,7 @@ static const struct nlattr_parser nla_p_rule[] = {
 	{ .type = PF_RT_TAGNAME, .off = _OUT(tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = nlattr_get_chara },
 	{ .type = PF_RT_MATCH_TAGNAME, .off = _OUT(match_tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = nlattr_get_chara },
 	{ .type = PF_RT_OVERLOAD_TBLNAME, .off = _OUT(overload_tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = nlattr_get_chara },
-	{ .type = PF_RT_RPOOL, .off = _OUT(rpool), .arg = &pool_parser, .cb = nlattr_get_nested },
+	{ .type = PF_RT_RPOOL_RDR, .off = _OUT(rdr), .arg = &pool_parser, .cb = nlattr_get_nested },
 	{ .type = PF_RT_OS_FINGERPRINT, .off = _OUT(os_fingerprint), .cb = nlattr_get_uint32 },
 	{ .type = PF_RT_RTABLEID, .off = _OUT(rtableid), .cb = nlattr_get_uint32 },
 	{ .type = PF_RT_TIMEOUT, .off = _OUT(timeout), .arg = &timeout_parser, .cb = nlattr_get_nested_timeouts },
@@ -732,6 +732,7 @@ static const struct nlattr_parser nla_p_rule[] = {
 	{ .type = PF_RT_DIVERT_PORT, .off = _OUT(divert.port), .cb = nlattr_get_uint16 },
 	{ .type = PF_RT_RCV_IFNAME, .off = _OUT(rcv_ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara },
 	{ .type = PF_RT_MAX_SRC_CONN, .off = _OUT(max_src_conn), .cb = nlattr_get_uint32 },
+	{ .type = PF_RT_RPOOL_NAT, .off = _OUT(nat), .arg = &pool_parser, .cb = nlattr_get_nested },
 };
 NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
 #undef _OUT
@@ -915,7 +916,8 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	nlattr_add_string(nw, PF_RT_TAGNAME, rule->tagname);
 	nlattr_add_string(nw, PF_RT_MATCH_TAGNAME, rule->match_tagname);
 	nlattr_add_string(nw, PF_RT_OVERLOAD_TBLNAME, rule->overload_tblname);
-	nlattr_add_pool(nw, PF_RT_RPOOL, &rule->rpool);
+	nlattr_add_pool(nw, PF_RT_RPOOL_RDR, &rule->rdr);
+	nlattr_add_pool(nw, PF_RT_RPOOL_NAT, &rule->nat);
 	nlattr_add_u32(nw, PF_RT_OS_FINGERPRINT, rule->os_fingerprint);
 	nlattr_add_u32(nw, PF_RT_RTABLEID, rule->rtableid);
 	nlattr_add_timeout(nw, PF_RT_TIMEOUT, rule->timeout);
@@ -1528,7 +1530,7 @@ static const struct nlattr_parser nla_p_pool_addr[] = {
 NL_DECLARE_ATTR_PARSER(pool_addr_parser, nla_p_pool_addr);
 #undef _OUT
 
-#define	_OUT(_field)	offsetof(struct pfioc_pooladdr, _field)
+#define	_OUT(_field)	offsetof(struct pf_nl_pooladdr, _field)
 static const struct nlattr_parser nla_p_add_addr[] = {
 	{ .type = PF_AA_ACTION, .off = _OUT(action), .cb = nlattr_get_uint32 },
 	{ .type = PF_AA_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 },
@@ -1539,6 +1541,7 @@ static const struct nlattr_parser nla_p_add_addr[] = {
 	{ .type = PF_AA_AF, .off = _OUT(af), .cb = nlattr_get_uint8 },
 	{ .type = PF_AA_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara },
 	{ .type = PF_AA_ADDR, .off = _OUT(addr), .arg = &pool_addr_parser, .cb = nlattr_get_nested },
+	{ .type = PF_AA_WHICH, .off = _OUT(which), .cb = nlattr_get_uint32 },
 };
 static const struct nlfield_parser nlf_p_add_addr[] = {};
 #undef _OUT
@@ -1547,13 +1550,16 @@ NL_DECLARE_PARSER(add_addr_parser, struct genlmsghdr, nlf_p_add_addr, nla_p_add_
 static int
 pf_handle_add_addr(struct nlmsghdr *hdr, struct nl_pstate *npt)
 {
-	struct pfioc_pooladdr attrs = { 0 };
+	struct pf_nl_pooladdr attrs = { 0 };
 	int error;
 
 	error = nl_parse_nlmsg(hdr, &add_addr_parser, npt, &attrs);
 	if (error != 0)
 		return (error);
 
+	if (attrs.which == 0)
+		attrs.which = PF_RDR;
+
 	error = pf_ioctl_add_addr(&attrs);
 
 	return (error);
@@ -1562,7 +1568,7 @@ pf_handle_add_addr(struct nlmsghdr *hdr, struct nl_pstate *npt)
 static int
 pf_handle_get_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt)
 {
-	struct pfioc_pooladdr attrs = { 0 };
+	struct pf_nl_pooladdr attrs = { 0 };
 	struct nl_writer *nw = npt->nw;
 	struct genlmsghdr *ghdr_new;
 	int error;
@@ -1571,6 +1577,9 @@ pf_handle_get_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt)
 	if (error != 0)
 		return (error);
 
+	if (attrs.which == 0)
+		attrs.which = PF_RDR;
+
*** 73 LINES SKIPPED ***