git: aa69fdf1542d - main - pfctl: change for af-to / NAT64 support.

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

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

commit aa69fdf1542db0247e9b991002603fc2046bcfbc
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-10-10 08:09:34 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-12-17 10:07:13 +0000

    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
---
 lib/libpfctl/libpfctl.c     |  13 ++-
 lib/libpfctl/libpfctl.h     |   7 +-
 sbin/pfctl/parse.y          | 190 +++++++++++++++++++++++++++++++++++---------
 sbin/pfctl/pf_print_state.c |  20 +++--
 sbin/pfctl/pfctl.c          |  38 ++++++---
 sbin/pfctl/pfctl_parser.c   |  19 ++++-
 sys/net/pfvar.h             |   1 +
 sys/netpfil/pf/pf_nl.c      |   1 +
 sys/netpfil/pf/pf_nl.h      |   1 +
 9 files changed, 225 insertions(+), 65 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 9fec8e77de26..2e4cdb91bad9 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1285,6 +1285,7 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
 	snl_add_msg_attr_u8(nw, PF_RT_PRIO, r->prio);
 	snl_add_msg_attr_u8(nw, PF_RT_SET_PRIO, r->set_prio[0]);
 	snl_add_msg_attr_u8(nw, PF_RT_SET_PRIO_REPLY, r->set_prio[1]);
+	snl_add_msg_attr_u8(nw, PF_RT_NAF, r->naf);
 
 	snl_add_msg_attr_ip6(nw, PF_RT_DIVERT_ADDRESS, &r->divert.addr.v6);
 	snl_add_msg_attr_u16(nw, PF_RT_DIVERT_PORT, r->divert.port);
@@ -1662,6 +1663,7 @@ static struct snl_attr_parser ap_getrule[] = {
 	{ .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 },
+	{ .type = PF_RT_NAF, .off = _OUT(r.naf), .cb = snl_attr_get_uint8 },
 };
 static struct snl_field_parser fp_getrule[] = {};
 #undef _OUT
@@ -2770,7 +2772,7 @@ pfctl_begin_addrs(struct pfctl_handle *h, uint32_t *ticket)
 }
 
 int
-pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which __unused)
+pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which)
 {
 	struct snl_writer nw;
 	struct snl_errmsg_data e = {};
@@ -2794,6 +2796,7 @@ pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int whic
 	snl_add_msg_attr_u8(&nw, PF_AA_AF, pa->af);
 	snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, pa->anchor);
 	snl_add_msg_attr_pool_addr(&nw, PF_AA_ADDR, &pa->addr);
+	snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which);
 
 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
 		return (ENXIO);
@@ -2817,7 +2820,7 @@ SNL_DECLARE_PARSER(get_addrs_parser, struct genlmsghdr, fp_get_addrs, ap_get_add
 
 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)
+    uint8_t r_action, const char *anchor, uint32_t *nr, int which)
 {
 	struct snl_writer nw;
 	struct snl_errmsg_data e = {};
@@ -2836,6 +2839,7 @@ pfctl_get_addrs(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
 	snl_add_msg_attr_u32(&nw, PF_AA_R_NUM, r_num);
 	snl_add_msg_attr_u8(&nw, PF_AA_R_ACTION, r_action);
 	snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, anchor);
+	snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which);
 
 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
 		return (ENXIO);
@@ -2879,7 +2883,8 @@ SNL_DECLARE_PARSER(get_addr_parser, struct genlmsghdr, fp_get_addr, ap_get_addr)
 
 int
 pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
-    uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa)
+    uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa,
+    int which)
 {
 	struct snl_writer nw;
 	struct snl_errmsg_data e = {};
@@ -2899,6 +2904,7 @@ pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
 	snl_add_msg_attr_u8(&nw, PF_AA_R_ACTION, r_action);
 	snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, anchor);
 	snl_add_msg_attr_u32(&nw, PF_AA_NR, nr);
+	snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which);
 
 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
 		return (ENXIO);
@@ -3023,6 +3029,7 @@ static struct snl_attr_parser ap_srcnode[] = {
 	{ .type = PF_SN_CREATION, .off = _OUT(creation), .cb = snl_attr_get_uint64 },
 	{ .type = PF_SN_EXPIRE, .off = _OUT(expire), .cb = snl_attr_get_uint64 },
 	{ .type = PF_SN_CONNECTION_RATE, .off = _OUT(conn_rate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested },
+	{ .type = PF_SN_NAF, .off = _OUT(naf), .cb = snl_attr_get_uint8 },
 };
 static struct snl_field_parser fp_srcnode[] = {};
 #undef _OUT
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 7b4aa0555758..79756286563b 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -256,6 +256,7 @@ struct pfctl_rule {
 	uint8_t			 flush;
 	uint8_t			 prio;
 	uint8_t			 set_prio[2];
+	sa_family_t		 naf;
 
 	struct {
 		struct pf_addr		addr;
@@ -407,6 +408,7 @@ struct pfctl_src_node {
 	uint32_t		states;
 	uint32_t		conn;
 	sa_family_t		af;
+	sa_family_t		naf;
 	uint8_t			ruletype;
 	uint64_t		creation;
 	uint64_t		expire;
@@ -528,9 +530,10 @@ 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 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);
+	    uint8_t r_action, const char *anchor, uint32_t *nr, int which);
 int	pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num,
-	    uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa);
+	    uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa,
+	    int which);
 int	pfctl_get_rulesets(struct pfctl_handle *h, const char *path, uint32_t *nr);
 int	pfctl_get_ruleset(struct pfctl_handle *h, const char *path, uint32_t nr, struct pfioc_ruleset *rs);
 typedef int (*pfctl_get_srcnode_fn)(struct pfctl_src_node*, void *);
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index f198dcb0b054..fc24cbc238ba 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -222,6 +222,34 @@ struct node_qassign {
 	char		*pqname;
 };
 
+struct range {
+	int		 a;
+	int		 b;
+	int		 t;
+};
+struct redirection {
+	struct node_host	*host;
+	struct range		 rport;
+};
+
+static struct pool_opts {
+	int			 marker;
+#define POM_TYPE		0x01
+#define POM_STICKYADDRESS	0x02
+#define POM_ENDPI		0x04
+	u_int8_t		 opts;
+	int			 type;
+	int			 staticport;
+	struct pf_poolhashkey	*key;
+	struct pf_mape_portset	 mape;
+} pool_opts;
+
+struct redirspec {
+	struct redirection	 *rdr;
+	struct pool_opts	 pool_opts;
+	int		 af;
+};
+
 static struct filter_opts {
 	int			 marker;
 #define FOM_FLAGS	0x0001
@@ -231,7 +259,7 @@ static struct filter_opts {
 #define FOM_SRCTRACK	0x0010
 #define FOM_MINTTL	0x0020
 #define FOM_MAXMSS	0x0040
-#define FOM_AFTO	0x0080 /* not yet implemmented */
+#define FOM_AFTO	0x0080
 #define FOM_SETTOS	0x0100
 #define FOM_SCRUB_TCP	0x0200
 #define FOM_SETPRIO	0x0400
@@ -274,6 +302,8 @@ static struct filter_opts {
 		struct node_host	*addr;
 		u_int16_t		port;
 	}			 divert;
+	struct redirspec	 nat;
+	struct redirspec	 rdr;
 	/* new-style scrub opts */
 	int			 nodf;
 	int			 minttl;
@@ -323,19 +353,6 @@ static struct table_opts {
 	struct node_tinithead	init_nodes;
 } table_opts;
 
-static struct pool_opts {
-	int			 marker;
-#define POM_TYPE		0x01
-#define POM_STICKYADDRESS	0x02
-#define POM_ENDPI		0x04
-	u_int8_t		 opts;
-	int			 type;
-	int			 staticport;
-	struct pf_poolhashkey	*key;
-	struct pf_mape_portset	 mape;
-
-} pool_opts;
-
 static struct codel_opts	 codel_opts;
 static struct node_hfsc_opts	 hfsc_opts;
 static struct node_fairq_opts	 fairq_opts;
@@ -365,6 +382,7 @@ void		 expand_eth_rule(struct pfctl_eth_rule *,
 		    struct node_host *, struct node_host *, const char *,
 		    const char *);
 void		 expand_rule(struct pfctl_rule *, struct node_if *,
+		    struct node_host *,
 		    struct node_host *, struct node_proto *, struct node_os *,
 		    struct node_host *, struct node_port *, struct node_host *,
 		    struct node_port *, struct node_uid *, struct node_gid *,
@@ -417,11 +435,7 @@ typedef struct {
 			u_int16_t	 w;
 			u_int16_t	 w2;
 		}			 b;
-		struct range {
-			int		 a;
-			int		 b;
-			int		 t;
-		}			 range;
+		struct range		 range;
 		struct node_if		*interface;
 		struct node_proto	*proto;
 		struct node_etherproto	*etherproto;
@@ -453,10 +467,7 @@ typedef struct {
 			sa_family_t		 af;
 			struct pf_poolhashkey	*key;
 		}			 route;
-		struct redirection {
-			struct node_host	*host;
-			struct range		 rport;
-		}			*redirection;
+		struct redirection	*redirection;
 		struct {
 			int			 action;
 			struct node_state_opt	*options;
@@ -517,7 +528,7 @@ int	parseport(char *, struct range *r, int);
 %token	STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
 %token	MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW
 %token	TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
-%token	DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE
+%token	DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO
 %token	<v.string>		STRING
 %token	<v.number>		NUMBER
 %token	<v.i>			PORTBINARY
@@ -1071,8 +1082,9 @@ anchorrule	: ANCHOR anchorname dir quick interface af proto fromto
 
 			decide_address_family($8.src.host, &r.af);
 			decide_address_family($8.dst.host, &r.af);
+			r.naf = r.af;
 
-			expand_rule(&r, $5, NULL, $7, $8.src_os,
+			expand_rule(&r, $5, NULL, NULL, $7, $8.src_os,
 			    $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
 			    $9.uid, $9.gid, $9.rcv, $9.icmpspec,
 			    pf->astack[pf->asd + 1] ? pf->alast->name : $2);
@@ -1095,7 +1107,7 @@ anchorrule	: ANCHOR anchorname dir quick interface af proto fromto
 			decide_address_family($6.src.host, &r.af);
 			decide_address_family($6.dst.host, &r.af);
 
-			expand_rule(&r, $3, NULL, $5, $6.src_os,
+			expand_rule(&r, $3, NULL, NULL, $5, $6.src_os,
 			    $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
 			    0, 0, 0, 0, $2);
 			free($2);
@@ -1137,7 +1149,7 @@ anchorrule	: ANCHOR anchorname dir quick interface af proto fromto
 				r.dst.port_op = $6.dst.port->op;
 			}
 
-			expand_rule(&r, $3, NULL, $5, $6.src_os,
+			expand_rule(&r, $3, NULL, NULL, $5, $6.src_os,
 			    $6.src.host, $6.src.port, $6.dst.host, $6.dst.port,
 			    0, 0, 0, 0, $2);
 			free($2);
@@ -1460,7 +1472,7 @@ scrubrule	: scrubaction dir logquick interface af proto fromto scrub_opts
 			r.match_tag_not = $8.match_tag_not;
 			r.rtableid = $8.rtableid;
 
-			expand_rule(&r, $4, NULL, $6, $7.src_os,
+			expand_rule(&r, $4, NULL, NULL, $6, $7.src_os,
 			    $7.src.host, $7.src.port, $7.dst.host, $7.dst.port,
 			    NULL, NULL, NULL, NULL, "");
 		}
@@ -1625,7 +1637,7 @@ antispoof	: ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
 				}
 
 				if (h != NULL)
-					expand_rule(&r, j, NULL, NULL, NULL, h,
+					expand_rule(&r, j, NULL, NULL, NULL, NULL, h,
 					    NULL, NULL, NULL, NULL, NULL,
 					    NULL, NULL, "");
 
@@ -1647,7 +1659,7 @@ antispoof	: ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
 					else
 						h = ifa_lookup(i->ifname, 0);
 					if (h != NULL)
-						expand_rule(&r, NULL, NULL,
+						expand_rule(&r, NULL, NULL, NULL,
 						    NULL, NULL, h, NULL, NULL,
 						    NULL, NULL, NULL, NULL, NULL, "");
 				} else
@@ -2414,6 +2426,19 @@ pfrule		: action dir logquick interface route af proto fromto
 				r.scrub_flags |= PFSTATE_SETPRIO;
 			}
 
+			if ($9.marker & FOM_AFTO) {
+				if (!$6) {
+					yyerror("must indicate source address "
+					    "family with af-to");
+					YYERROR;
+				}
+				if ($6 == $9.nat.af) {
+					yyerror("incorrect address family "
+					    "translation");
+					YYERROR;
+				}
+			}
+
 			r.af = $6;
 			if ($9.tag)
 				if (strlcpy(r.tagname, $9.tag,
@@ -2699,6 +2724,7 @@ pfrule		: action dir logquick interface route af proto fromto
 
 			decide_address_family($8.src.host, &r.af);
 			decide_address_family($8.dst.host, &r.af);
+			r.naf = r.af;
 
 			if ($5.rt) {
 				if (!r.direction) {
@@ -2801,9 +2827,14 @@ pfrule		: action dir logquick interface route af proto fromto
 					r.free_flags |= PFRULE_DN_IS_QUEUE;
 			}
 
-			expand_rule(&r, $4, $5.host, $7, $8.src_os,
-			    $8.src.host, $8.src.port, $8.dst.host, $8.dst.port,
-			    $9.uid, $9.gid, $9.rcv, $9.icmpspec, "");
+			if ($9.marker & FOM_AFTO)
+				r.naf = $9.nat.af;
+
+			r.nat.opts = $9.nat.pool_opts.type;
+			r.nat.opts |= $9.nat.pool_opts.opts;
+			expand_rule(&r, $4, $5.host, $9.nat.rdr ? $9.nat.rdr->host : NULL,
+			    $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host,
+			    $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec, "");
 		}
 		;
 
@@ -3017,6 +3048,64 @@ filter_opt	: USER uids {
 				filter_opts.marker |= FOM_SCRUB_TCP;
 			filter_opts.marker |= $3.marker;
 		}
+		| AFTO af FROM redirspec pool_opts {
+			if (filter_opts.nat.rdr) {
+				yyerror("cannot respecify af-to");
+				YYERROR;
+			}
+			if ($2 == 0) {
+				yyerror("no address family specified");
+				YYERROR;
+			}
+			if ($4->af && $4->af != $2) {
+				yyerror("af-to addresses must be in the "
+				   "target address family");
+				YYERROR;
+			}
+			filter_opts.nat.af = $2;
+			filter_opts.nat.rdr = calloc(1, sizeof(struct redirection));
+			if (filter_opts.nat.rdr == NULL)
+				err(1, "af-to: calloc");
+			filter_opts.nat.rdr->host = $4;
+			memcpy(&filter_opts.nat.pool_opts, &$5,
+			    sizeof(filter_opts.nat.pool_opts));
+			filter_opts.rdr.rdr =
+			    calloc(1, sizeof(struct redirection));
+			bzero(&filter_opts.rdr.pool_opts,
+			    sizeof(filter_opts.rdr.pool_opts));
+			filter_opts.marker |= FOM_AFTO;
+		}
+		| AFTO af FROM redirspec pool_opts TO redirspec pool_opts {
+			if (filter_opts.nat.rdr) {
+				yyerror("cannot respecify af-to");
+				YYERROR;
+			}
+			if ($2 == 0) {
+				yyerror("no address family specified");
+				YYERROR;
+			}
+				if (($4->af && $4->af != $2) ||
+				($7->af && $7->af != $2)) {
+				yyerror("af-to addresses must be in the "
+				   "target address family");
+				YYERROR;
+			}
+			filter_opts.nat.af = $2;
+			filter_opts.nat.rdr = calloc(1, sizeof(struct redirection));
+			if (filter_opts.nat.rdr == NULL)
+				err(1, "af-to: calloc");
+			filter_opts.nat.rdr->host = $4;
+			memcpy(&filter_opts.nat.pool_opts, &$5,
+			    sizeof(filter_opts.nat.pool_opts));
+			filter_opts.rdr.af = $2;
+			filter_opts.rdr.rdr = calloc(1, sizeof(struct redirection));
+			if (filter_opts.rdr.rdr == NULL)
+				err(1, "af-to: calloc");
+			filter_opts.rdr.rdr->host = $7;
+			memcpy(&filter_opts.nat.pool_opts, &$8,
+			    sizeof(filter_opts.nat.pool_opts));
+			filter_opts.marker |= FOM_AFTO;
+		}
 		| filter_sets
 		;
 
@@ -4891,7 +4980,7 @@ natrule		: nataction interface af proto fromto tag tagged rtable
 				o = o->next;
 			}
 
-			expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4,
+			expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, NULL, $4,
 			    $5.src_os, $5.src.host, $5.src.port, $5.dst.host,
 			    $5.dst.port, 0, 0, 0, 0, "");
 			free($9);
@@ -5407,6 +5496,10 @@ filter_consistent(struct pfctl_rule *r, int anchor_call)
 			   "must not be used on match rules");
 			problems++;
 		}
+		if (r->naf != r->af) {
+			yyerror("af-to is not supported on match rules");
+			problems++;
+		}
 	}
 	if (r->rpool.opts & PF_POOL_STICKYADDR && !r->keep_state) {
 		yyerror("'sticky-address' requires 'keep state'");
@@ -6041,7 +6134,8 @@ expand_eth_rule(struct pfctl_eth_rule *r,
 
 void
 expand_rule(struct pfctl_rule *r,
-    struct node_if *interfaces, struct node_host *rpool_hosts,
+    struct node_if *interfaces, struct node_host *rdr_hosts,
+    struct node_host *nat_hosts,
     struct node_proto *protos, struct node_os *src_oses,
     struct node_host *src_hosts, struct node_port *src_ports,
     struct node_host *dst_hosts, struct node_port *dst_ports,
@@ -6186,8 +6280,8 @@ expand_rule(struct pfctl_rule *r,
 			r->os_fingerprint = PF_OSFP_ANY;
 		}
 
-		TAILQ_INIT(&r->rpool.list);
-		for (h = rpool_hosts; h != NULL; h = h->next) {
+		TAILQ_INIT(&r->rdr.list);
+		for (h = rdr_hosts; h != NULL; h = h->next) {
 			pa = calloc(1, sizeof(struct pf_pooladdr));
 			if (pa == NULL)
 				err(1, "expand_rule: calloc");
@@ -6201,6 +6295,24 @@ expand_rule(struct pfctl_rule *r,
 				pa->ifname[0] = 0;
 			TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries);
 		}
+		TAILQ_INIT(&r->nat.list);
+		for (h = nat_hosts; h != NULL; h = h->next) {
+			pa = calloc(1, sizeof(struct pf_pooladdr));
+			if (pa == NULL)
+				err(1, "expand_rule: calloc");
+			pa->addr = h->addr;
+			if (h->ifname != NULL) {
+				if (strlcpy(pa->ifname, h->ifname,
+				    sizeof(pa->ifname)) >=
+				    sizeof(pa->ifname))
+					errx(1, "expand_rule: strlcpy");
+			} else
+				pa->ifname[0] = 0;
+			TAILQ_INSERT_TAIL(&r->nat.list, pa, entries);
+		}
+
+		r->nat.proxy_port[0] = PF_NAT_PROXY_PORT_LOW;
+		r->nat.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH;
 
 		if (rule_consistent(r, anchor_call[0]) < 0 || error)
 			yyerror("skipping rule due to errors");
@@ -6231,7 +6343,8 @@ expand_rule(struct pfctl_rule *r,
 	FREE_LIST(struct node_uid, uids);
 	FREE_LIST(struct node_gid, gids);
 	FREE_LIST(struct node_icmp, icmp_types);
-	FREE_LIST(struct node_host, rpool_hosts);
+	FREE_LIST(struct node_host, rdr_hosts);
+	FREE_LIST(struct node_host, nat_hosts);
 
 	if (!added)
 		yyerror("rule expands to no valid combination");
@@ -6305,6 +6418,7 @@ lookup(char *s)
 {
 	/* this has to be sorted always */
 	static const struct keywords keywords[] = {
+		{ "af-to",		AFTO},
 		{ "all",		ALL},
 		{ "allow-opts",		ALLOWOPTS},
 		{ "altq",		ALTQ},
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
index 96da1e109fa8..e6495dfa4ca6 100644
--- a/sbin/pfctl/pf_print_state.c
+++ b/sbin/pfctl/pf_print_state.c
@@ -243,6 +243,8 @@ print_state(struct pfctl_state *s, int opts)
 	int min, sec;
 	sa_family_t af;
 	uint8_t proto;
+	int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af);
+	int idx;
 #ifndef __NO_STRICT_ALIGNMENT
 	struct pfctl_state_key aligned_key[2];
 
@@ -276,22 +278,26 @@ print_state(struct pfctl_state *s, int opts)
 	else
 		printf("%u ", proto);
 
-	print_host(&nk->addr[1], nk->port[1], af, opts);
-	if (PF_ANEQ(&nk->addr[1], &sk->addr[1], af) ||
+	print_host(&nk->addr[1], nk->port[1], nk->af, opts);
+	if (nk->af != sk->af || PF_ANEQ(&nk->addr[1], &sk->addr[1], nk->af) ||
 	    nk->port[1] != sk->port[1]) {
+		idx = afto ? 0 : 1;
 		printf(" (");
-		print_host(&sk->addr[1], sk->port[1], af, opts);
+		print_host(&sk->addr[idx], sk->port[idx], sk->af,
+		    opts);
 		printf(")");
 	}
-	if (s->direction == PF_OUT)
+	if (s->direction == PF_OUT || (afto && s->direction == PF_IN))
 		printf(" -> ");
 	else
 		printf(" <- ");
-	print_host(&nk->addr[0], nk->port[0], af, opts);
-	if (PF_ANEQ(&nk->addr[0], &sk->addr[0], af) ||
+	print_host(&nk->addr[0], nk->port[0], nk->af, opts);
+	if (nk->af != sk->af || PF_ANEQ(&nk->addr[0], &sk->addr[0], nk->af) ||
 	    nk->port[0] != sk->port[0]) {
+		idx = afto ? 1 : 0;
 		printf(" (");
-		print_host(&sk->addr[0], sk->port[0], af, opts);
+		print_host(&sk->addr[idx], sk->port[idx], sk->af,
+		    opts);
 		printf(")");
 	}
 
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 4d77c7937a74..09d6774b324f 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -93,7 +93,7 @@ int	 pfctl_load_hostid(struct pfctl *, u_int32_t);
 int	 pfctl_load_reassembly(struct pfctl *, u_int32_t);
 int	 pfctl_load_syncookies(struct pfctl *, u_int8_t);
 int	 pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int,
-	    char *);
+	    char *, int);
 void	 pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int);
 void	 pfctl_print_rule_counters(struct pfctl_rule *, int);
 int	 pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int, int);
@@ -956,7 +956,7 @@ pfctl_id_kill_states(int dev, const char *iface, int opts)
 
 int
 pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr,
-    u_int32_t ticket, int r_action, char *anchorname)
+    u_int32_t ticket, int r_action, char *anchorname, int which)
 {
 	struct pfioc_pooladdr pp;
 	struct pf_pooladdr *pa;
@@ -964,14 +964,14 @@ pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr,
 	int ret;
 
 	memset(&pp, 0, sizeof(pp));
-	if ((ret = pfctl_get_addrs(pfh, ticket, nr, r_action, anchorname, &mpnr)) != 0) {
+	if ((ret = pfctl_get_addrs(pfh, ticket, nr, r_action, anchorname, &mpnr, which)) != 0) {
 		warnc(ret, "DIOCGETADDRS");
 		return (-1);
 	}
 
 	TAILQ_INIT(&pool->list);
 	for (pnr = 0; pnr < mpnr; ++pnr) {
-		if ((ret = pfctl_get_addr(pfh, ticket, nr, r_action, anchorname, pnr, &pp)) != 0) {
+		if ((ret = pfctl_get_addr(pfh, ticket, nr, r_action, anchorname, pnr, &pp, which)) != 0) {
 			warnc(ret, "DIOCGETADDR");
 			return (-1);
 		}
@@ -1303,7 +1303,11 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
 		}
 
 		if (pfctl_get_pool(dev, &rule.rpool,
-		    nr, ri.ticket, PF_SCRUB, path) != 0)
+		    nr, ri.ticket, PF_SCRUB, path, PF_RDR) != 0)
+			goto error;
+
+		if (pfctl_get_pool(dev, &rule.nat,
+		    nr, ri.ticket, PF_SCRUB, path, PF_NAT) != 0)
 			goto error;
 
 		switch (format) {
@@ -1334,7 +1338,11 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
 		}
 
 		if (pfctl_get_pool(dev, &rule.rpool,
-		    nr, ri.ticket, PF_PASS, path) != 0)
+		    nr, ri.ticket, PF_PASS, path, PF_RDR) != 0)
+			goto error;
+
+		if (pfctl_get_pool(dev, &rule.nat,
+		    nr, ri.ticket, PF_PASS, path, PF_NAT) != 0)
 			goto error;
 
 		switch (format) {
@@ -1491,7 +1499,10 @@ pfctl_show_nat(int dev, char *path, int opts, char *anchorname, int depth,
 				return (-1);
 			}
 			if (pfctl_get_pool(dev, &rule.rpool, nr,
-			    ri.ticket, nattype[i], path) != 0)
+			    ri.ticket, nattype[i], path, PF_RDR) != 0)
+				return (-1);
+			if (pfctl_get_pool(dev, &rule.nat, nr,
+			    ri.ticket, nattype[i], path, PF_NAT) != 0)
 				return (-1);
 
 			if (dotitle) {
@@ -1692,11 +1703,6 @@ pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af, int which
 	struct pf_pooladdr *pa;
 	int ret;
 
-	if ((pf->opts & PF_OPT_NOACTION) == 0) {
-		if ((ret = pfctl_begin_addrs(pf->h, &pf->paddr.ticket)) != 0)
-			errc(1, ret, "DIOCBEGINADDRS");
-	}
-
 	pf->paddr.af = af;
 	TAILQ_FOREACH(pa, &p->list, entries) {
 		memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr));
@@ -2045,8 +2051,16 @@ 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 ((pf->opts & PF_OPT_NOACTION) == 0) {
+			if ((error = pfctl_begin_addrs(pf->h,
+			    &pf->paddr.ticket)) != 0)
+				errc(1, error, "DIOCBEGINADDRS");
+		}
+
 		if (pfctl_add_pool(pf, &r->rpool, r->af, PF_RDR))
 			return (1);
+		if (pfctl_add_pool(pf, &r->nat, r->naf ? r->naf : r->af, PF_NAT))
+			return (1);
 		error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket,
 		    pf->paddr.ticket);
 		switch (error) {
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index d6d04ba2a7de..7cbca9a75af2 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -665,7 +665,7 @@ print_src_node(struct pfctl_src_node *sn, int opts)
 	print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2);
 	printf(" -> ");
 	aw.v.a.addr = sn->raddr;
-	print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2);
+	print_addr(&aw, sn->naf ? sn->naf : sn->af, opts & PF_OPT_VERBOSE2);
 	printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states,
 	    sn->conn, sn->conn_rate.count / 1000,
 	    (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds);
@@ -1237,8 +1237,21 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
 		}
 #endif
 	}
-	if (!anchor_call[0] && (r->action == PF_NAT ||
-	    r->action == PF_BINAT || r->action == PF_RDR)) {
+	if (!anchor_call[0] && ! TAILQ_EMPTY(&r->nat.list) &&
+	    r->naf != r->af) {
+		printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6");
+		print_pool(&r->nat, r->nat.proxy_port[0], r->nat.proxy_port[1],
+		    r->naf ? r->naf : r->af, PF_NAT);
+		if (r->rdr.cur != NULL && !TAILQ_EMPTY(&r->rdr.list)) {
+			printf(" to ");
+			print_pool(&r->rdr, r->rdr.proxy_port[0],
+			    r->rdr.proxy_port[1], r->naf ? r->naf : r->af,
+			    PF_RDR);
+		}
+	}
+	if (!anchor_call[0] &&
+	    (r->action == PF_NAT || r->action == PF_BINAT ||
+		r->action == PF_RDR)) {
 		printf(" -> ");
 		print_pool(&r->rpool, r->rpool.proxy_port[0],
 		    r->rpool.proxy_port[1], r->af, r->action);
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 094bc38c4a1b..e0ac9561f463 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -901,6 +901,7 @@ struct pf_ksrc_node {
 	u_int32_t		 creation;
 	u_int32_t		 expire;
 	sa_family_t		 af;
+	sa_family_t		 naf;
 	u_int8_t		 ruletype;
 	struct mtx		*lock;
 };
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index 3af27e11d27f..79d6c380e31e 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -1796,6 +1796,7 @@ pf_handle_get_srcnodes(struct nlmsghdr *hdr, struct nl_pstate *npt)
 			nlattr_add_u32(nw, PF_SN_STATES, n->states);
 			nlattr_add_u32(nw, PF_SN_CONNECTIONS, n->conn);
 			nlattr_add_u8(nw, PF_SN_AF, n->af);
+			nlattr_add_u8(nw, PF_SN_NAF, n->naf);
 			nlattr_add_u8(nw, PF_SN_RULE_TYPE, n->ruletype);
 
 			nlattr_add_u64(nw, PF_SN_CREATION, secs - n->creation);
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 3af931978860..0f534bd623c4 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -419,6 +419,7 @@ enum pf_srcnodes_types_t {
 	PF_SN_CREATION		= 12, /* u64 */
 	PF_SN_EXPIRE		= 13, /* u64 */
 	PF_SN_CONNECTION_RATE	= 14, /* nested, pf_threshold */
+	PF_SN_NAF		= 15, /* u8 */
 };
 
 #ifdef _KERNEL