git: fc6e50699615 - main - pflow: add RFC8158 NAT support

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 16 Jan 2024 08:52:01 UTC
The branch main has been updated by kp:

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

commit fc6e50699615c93f39d008709f87c754d9b6c7d3
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-12-13 15:55:28 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-01-16 08:45:55 +0000

    pflow: add RFC8158 NAT support
    
    Extend pflow(4) to send NAT44 Session Create and Delete events.
    This applies only to IPFIX (i.e. proto version 10), and requires no
    user configuration.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D43114
---
 sbin/pfctl/parse.y     |  16 ++++
 sys/net/pflow.h        |  44 ++++++++++
 sys/netpfil/pf/pf.c    |   3 +-
 sys/netpfil/pf/pflow.c | 213 +++++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 260 insertions(+), 16 deletions(-)

diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 94b7e241cd25..9ec86f898240 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -4680,6 +4680,7 @@ natrule		: nataction interface af proto fromto tag tagged rtable
 		    redirpool pool_opts
 		{
 			struct pfctl_rule	r;
+			struct node_state_opt	*o;
 
 			if (check_rulestate(PFCTL_STATE_NAT))
 				YYERROR;
@@ -4855,6 +4856,21 @@ natrule		: nataction interface af proto fromto tag tagged rtable
 				r.rpool.mape = $10.mape;
 			}
 
+			o = keep_state_defaults;
+			while (o) {
+				switch (o->type) {
+				case PF_STATE_OPT_PFLOW:
+					if (r.rule_flag & PFRULE_PFLOW) {
+						yyerror("state pflow option: "
+						    "multiple definitions");
+						YYERROR;
+					}
+					r.rule_flag |= PFRULE_PFLOW;
+					break;
+				}
+				o = o->next;
+			}
+
 			expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4,
 			    $5.src_os, $5.src.host, $5.src.port, $5.dst.host,
 			    $5.dst.port, 0, 0, 0, "");
diff --git a/sys/net/pflow.h b/sys/net/pflow.h
index 4a63f7640629..84fcf1327a17 100644
--- a/sys/net/pflow.h
+++ b/sys/net/pflow.h
@@ -68,6 +68,14 @@
 #define PFIX_IE_destinationIPv6Address		 28
 #define PFIX_IE_flowStartMilliseconds		152
 #define PFIX_IE_flowEndMilliseconds		153
+#define PFIX_IE_postNATSourceIPv4Address	225
+#define PFIX_IE_postNATDestinationIPv4Address	226
+#define PFIX_IE_postNAPTSourceTransportPort	227
+#define PFIX_IE_postNAPTDestinationTransportPort	228
+#define PFIX_IE_natEvent			230
+#define PFIX_NAT_EVENT_SESSION_CREATE		4
+#define PFIX_NAT_EVENT_SESSION_DELETE		5
+#define PFIX_IE_timeStamp			323
 
 struct pflow_flow {
 	u_int32_t	src_ip;
@@ -148,10 +156,28 @@ struct pflow_ipfix_tmpl_ipv6 {
 #define PFLOW_IPFIX_TMPL_IPV6_ID 257
 } __packed;
 
+struct pflow_ipfix_tmpl_nat44 {
+	struct pflow_tmpl_hdr	h;
+	struct pflow_tmpl_fspec timestamp;
+	struct pflow_tmpl_fspec nat_event;
+	struct pflow_tmpl_fspec protocol;
+	struct pflow_tmpl_fspec src_ip;
+	struct pflow_tmpl_fspec src_port;
+	struct pflow_tmpl_fspec postnat_src_ip;
+	struct pflow_tmpl_fspec postnat_src_port;
+	struct pflow_tmpl_fspec dst_ip;
+	struct pflow_tmpl_fspec dst_port;
+	struct pflow_tmpl_fspec postnat_dst_ip;
+	struct pflow_tmpl_fspec postnat_dst_port;
+#define PFLOW_IPFIX_TMPL_NAT44_FIELD_COUNT 11
+#define PFLOW_IPFIX_TMPL_NAT44_ID 258
+};
+
 struct pflow_ipfix_tmpl {
 	struct pflow_set_header	set_header;
 	struct pflow_ipfix_tmpl_ipv4	ipv4_tmpl;
 	struct pflow_ipfix_tmpl_ipv6	ipv6_tmpl;
+	struct pflow_ipfix_tmpl_nat44	nat44_tmpl;
 } __packed;
 
 struct pflow_ipfix_flow4 {
@@ -186,6 +212,20 @@ struct pflow_ipfix_flow6 {
 	/* XXX padding needed? */
 } __packed;
 
+struct pflow_ipfix_nat4 {
+	u_int64_t	timestamp;	/* timeStamp */
+	u_int8_t	nat_event;	/* natEvent */
+	u_int8_t	protocol;	/* protocolIdentifier */
+	u_int32_t	src_ip;		/* sourceIPv4Address */
+	u_int16_t	src_port;	/* sourceTransportPort */
+	u_int32_t	postnat_src_ip;	/* postNATSourceIPv4Address */
+	u_int16_t	postnat_src_port;/* postNAPTSourceTransportPort */
+	u_int32_t	dest_ip;	/* destinationIPv4Address */
+	u_int16_t	dest_port;	/* destinationTransportPort */
+	u_int32_t	postnat_dest_ip;/* postNATDestinationIPv4Address */
+	u_int16_t	postnat_dest_port;/* postNAPTDestinationTransportPort */
+} __packed;
+
 #ifdef _KERNEL
 
 struct pflow_softc {
@@ -199,13 +239,16 @@ struct pflow_softc {
 	unsigned int		 sc_count;
 	unsigned int		 sc_count4;
 	unsigned int		 sc_count6;
+	unsigned int		 sc_count_nat4;
 	unsigned int		 sc_maxcount;
 	unsigned int		 sc_maxcount4;
 	unsigned int		 sc_maxcount6;
+	unsigned int		 sc_maxcount_nat4;
 	u_int64_t		 sc_gcounter;
 	u_int32_t		 sc_sequence;
 	struct callout		 sc_tmo;
 	struct callout		 sc_tmo6;
+	struct callout		 sc_tmo_nat4;
 	struct callout		 sc_tmo_tmpl;
 	struct intr_event	*sc_swi_ie;
 	void			*sc_swi_cookie;
@@ -219,6 +262,7 @@ struct pflow_softc {
 	u_int32_t		 sc_observation_dom;
 	struct mbuf		*sc_mbuf;	/* current cumulative mbuf */
 	struct mbuf		*sc_mbuf6;	/* current cumulative mbuf */
+	struct mbuf		*sc_mbuf_nat4;
 	CK_LIST_ENTRY(pflow_softc) sc_next;
 	struct epoch_context	 sc_epoch_ctx;
 };
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 89f6e000f6cf..9bd9828a99d9 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -4875,7 +4875,8 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 		s->state_flags |= PFSTATE_SLOPPY;
 	if (pd->flags & PFDESC_TCP_NORM) /* Set by old-style scrub rules */
 		s->state_flags |= PFSTATE_SCRUB_TCP;
-	if (r->rule_flag & PFRULE_PFLOW)
+	if ((r->rule_flag & PFRULE_PFLOW) ||
+	    (nr != NULL && nr->rule_flag & PFRULE_PFLOW))
 		s->state_flags |= PFSTATE_PFLOW;
 
 	s->act.log = pd->act.log & PF_LOG_ALL;
diff --git a/sys/netpfil/pf/pflow.c b/sys/netpfil/pf/pflow.c
index 398851bf17d0..ce5e8ec6547c 100644
--- a/sys/netpfil/pf/pflow.c
+++ b/sys/netpfil/pf/pflow.c
@@ -70,6 +70,12 @@
 #define DPRINTF(x)
 #endif
 
+enum pflow_family_t {
+	PFLOW_INET,
+	PFLOW_INET6,
+	PFLOW_NAT4,
+};
+
 static void	pflow_output_process(void *);
 static int	pflow_create(int);
 static int	pflow_destroy(int, bool);
@@ -80,12 +86,13 @@ static int	pflowvalidsockaddr(const struct sockaddr *, int);
 static struct mbuf	*pflow_get_mbuf(struct pflow_softc *, u_int16_t);
 static void	pflow_flush(struct pflow_softc *);
 static int	pflow_sendout_v5(struct pflow_softc *);
-static int	pflow_sendout_ipfix(struct pflow_softc *, sa_family_t);
+static int	pflow_sendout_ipfix(struct pflow_softc *, enum pflow_family_t);
 static int	pflow_sendout_ipfix_tmpl(struct pflow_softc *);
 static int	pflow_sendout_mbuf(struct pflow_softc *, struct mbuf *);
 static void	pflow_timeout(void *);
 static void	pflow_timeout6(void *);
 static void	pflow_timeout_tmpl(void *);
+static void	pflow_timeout_nat4(void *);
 static void	copy_flow_data(struct pflow_flow *, struct pflow_flow *,
 	const struct pf_kstate *, struct pf_state_key *, int, int);
 static void	copy_flow_ipfix_4_data(struct pflow_ipfix_flow4 *,
@@ -106,6 +113,9 @@ static int	copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow,
 	struct pflow_softc *sc);
 static int	copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow,
 	struct pflow_softc *sc);
+static int	copy_nat_ipfix_4_to_m(struct pflow_ipfix_nat4 *,
+	const struct pf_kstate *, struct pflow_softc *,
+	uint8_t, uint64_t);
 
 static const char pflowname[] = "pflow";
 
@@ -303,6 +313,53 @@ pflow_create(int unit)
 	    htons(PFIX_IE_protocolIdentifier);
 	pflowif->sc_tmpl_ipfix.ipv6_tmpl.protocol.len = htons(1);
 
+	/* NAT44 create template */
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.h.tmpl_id =
+	    htons(PFLOW_IPFIX_TMPL_NAT44_ID);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.h.field_count =
+	    htons(PFLOW_IPFIX_TMPL_NAT44_FIELD_COUNT);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.timestamp.field_id =
+	    htons(PFIX_IE_timeStamp);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.timestamp.len =
+	    htons(8);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.nat_event.field_id =
+	    htons(PFIX_IE_natEvent);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.nat_event.len =
+	    htons(1);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.protocol.field_id =
+	    htons(PFIX_IE_protocolIdentifier);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.protocol.len = htons(1);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.src_ip.field_id =
+	    htons(PFIX_IE_sourceIPv4Address);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.src_ip.len =
+	    htons(4);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.src_port.field_id =
+	    htons(PFIX_IE_sourceTransportPort);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.src_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_src_ip.field_id =
+	    htons(PFIX_IE_postNATSourceIPv4Address);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_src_ip.len =
+	    htons(4);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_src_port.field_id =
+	    htons(PFIX_IE_postNAPTSourceTransportPort);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_src_port.len =
+	    htons(2);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.dst_ip.field_id =
+	    htons(PFIX_IE_destinationIPv4Address);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.dst_ip.len =
+	    htons(4);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.dst_port.field_id =
+	    htons(PFIX_IE_destinationTransportPort);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.dst_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_dst_ip.field_id =
+	    htons(PFIX_IE_postNATDestinationIPv4Address);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_dst_ip.len =
+	    htons(4);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_dst_port.field_id =
+	    htons(PFIX_IE_postNAPTDestinationTransportPort);
+	pflowif->sc_tmpl_ipfix.nat44_tmpl.postnat_dst_port.len =
+	    htons(2);
+
 	pflowif->sc_id = unit;
 	pflowif->sc_vnet = curvnet;
 
@@ -311,6 +368,7 @@ pflow_create(int unit)
 
 	callout_init_mtx(&pflowif->sc_tmo, &pflowif->sc_lock, 0);
 	callout_init_mtx(&pflowif->sc_tmo6, &pflowif->sc_lock, 0);
+	callout_init_mtx(&pflowif->sc_tmo_nat4, &pflowif->sc_lock, 0);
 	callout_init_mtx(&pflowif->sc_tmo_tmpl, &pflowif->sc_lock, 0);
 
 	error = swi_add(&pflowif->sc_swi_ie, pflowname, pflow_output_process,
@@ -374,10 +432,12 @@ pflow_destroy(int unit, bool drain)
 
 	callout_drain(&sc->sc_tmo);
 	callout_drain(&sc->sc_tmo6);
+	callout_drain(&sc->sc_tmo_nat4);
 	callout_drain(&sc->sc_tmo_tmpl);
 
 	m_freem(sc->sc_mbuf);
 	m_freem(sc->sc_mbuf6);
+	m_freem(sc->sc_mbuf_nat4);
 
 	PFLOW_LOCK(sc);
 	mbufq_drain(&sc->sc_outputqueue);
@@ -425,18 +485,26 @@ pflowvalidsockaddr(const struct sockaddr *sa, int ignore_port)
 int
 pflow_calc_mtu(struct pflow_softc *sc, int mtu, int hdrsz)
 {
+	size_t min;
 
 	sc->sc_maxcount4 = (mtu - hdrsz -
 	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow4);
 	sc->sc_maxcount6 = (mtu - hdrsz -
 	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow6);
+	sc->sc_maxcount_nat4 = (mtu - hdrsz -
+	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_nat4);
 	if (sc->sc_maxcount4 > PFLOW_MAXFLOWS)
 		sc->sc_maxcount4 = PFLOW_MAXFLOWS;
 	if (sc->sc_maxcount6 > PFLOW_MAXFLOWS)
 		sc->sc_maxcount6 = PFLOW_MAXFLOWS;
-	return (hdrsz + sizeof(struct udpiphdr) +
-	    MIN(sc->sc_maxcount4 * sizeof(struct pflow_ipfix_flow4),
-	    sc->sc_maxcount6 * sizeof(struct pflow_ipfix_flow6)));
+	if (sc->sc_maxcount_nat4 > PFLOW_MAXFLOWS)
+		sc->sc_maxcount_nat4 = PFLOW_MAXFLOWS;
+
+	min = MIN(sc->sc_maxcount4 * sizeof(struct pflow_ipfix_flow4),
+	    sc->sc_maxcount6 * sizeof(struct pflow_ipfix_flow6));
+	min = MIN(min, sc->sc_maxcount_nat4 * sizeof(struct pflow_ipfix_nat4));
+
+	return (hdrsz + sizeof(struct udpiphdr) + min);
 }
 
 static void
@@ -628,6 +696,28 @@ copy_flow_ipfix_6_data(struct pflow_ipfix_flow6 *flow1,
 	flow1->tos = flow2->tos = st->rule.ptr->tos;
 }
 
+static void
+copy_nat_ipfix_4_data(struct pflow_ipfix_nat4 *nat1,
+    struct pflow_ipfix_nat4 *nat2, const struct pf_kstate *st,
+    struct pf_state_key *sk, struct pflow_softc *sc, int src, int dst)
+{
+	nat1->src_ip = nat2->dest_ip = st->key[PF_SK_STACK]->addr[src].v4.s_addr;
+	nat1->src_port = nat2->dest_port = st->key[PF_SK_STACK]->port[src];
+	nat1->dest_ip = nat2->src_ip = st->key[PF_SK_STACK]->addr[dst].v4.s_addr;
+	nat1->dest_port = nat2->src_port = st->key[PF_SK_STACK]->port[dst];
+	nat1->postnat_src_ip = nat2->postnat_dest_ip = st->key[PF_SK_WIRE]->addr[src].v4.s_addr;
+	nat1->postnat_src_port = nat2->postnat_dest_port = st->key[PF_SK_WIRE]->port[src];
+	nat1->postnat_dest_ip = nat2->postnat_src_ip = st->key[PF_SK_WIRE]->addr[dst].v4.s_addr;
+	nat1->postnat_dest_port = nat2->postnat_src_port = st->key[PF_SK_WIRE]->port[dst];
+	nat1->protocol = nat2->protocol = sk->proto;
+
+	/*
+	 * Because we have to generate a create and delete event we'll fill out the
+	 * timestamp and nat_event fields when we transmit. As opposed to doing this
+	 * work a second time.
+	*/
+}
+
 static void
 export_pflow(const struct pf_kstate *st)
 {
@@ -755,7 +845,7 @@ copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow, struct pflow_softc *sc)
 	sc->sc_count4++;
 
 	if (sc->sc_count4 >= sc->sc_maxcount4)
-		ret = pflow_sendout_ipfix(sc, AF_INET);
+		ret = pflow_sendout_ipfix(sc, PFLOW_INET);
 	return(ret);
 }
 
@@ -785,11 +875,46 @@ copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow, struct pflow_softc *sc)
 	sc->sc_count6++;
 
 	if (sc->sc_count6 >= sc->sc_maxcount6)
-		ret = pflow_sendout_ipfix(sc, AF_INET6);
+		ret = pflow_sendout_ipfix(sc, PFLOW_INET6);
 
 	return(ret);
 }
 
+int
+copy_nat_ipfix_4_to_m(struct pflow_ipfix_nat4 *nat, const struct pf_kstate *st,
+    struct pflow_softc *sc, uint8_t event, uint64_t timestamp)
+{
+	int		ret = 0;
+
+	PFLOW_ASSERT(sc);
+
+	if (sc->sc_mbuf_nat4 == NULL) {
+		if ((sc->sc_mbuf_nat4 =
+		    pflow_get_mbuf(sc, PFLOW_IPFIX_TMPL_NAT44_ID)) == NULL) {
+			return (ENOBUFS);
+		}
+		sc->sc_count_nat4 = 0;
+		callout_reset(&sc->sc_tmo, PFLOW_TIMEOUT * hz,
+		    pflow_timeout_nat4, sc);
+	}
+
+	nat->nat_event = event;
+	nat->timestamp = htobe64(pf_get_time() - (pf_get_uptime() - timestamp));
+	m_copyback(sc->sc_mbuf_nat4, PFLOW_SET_HDRLEN +
+	    (sc->sc_count_nat4 * sizeof(struct pflow_ipfix_nat4)),
+	    sizeof(struct pflow_ipfix_nat4), (caddr_t)nat);
+	sc->sc_count_nat4++;
+
+	if (V_pflowstats.pflow_flows == sc->sc_gcounter)
+		V_pflowstats.pflow_flows++;
+
+	sc->sc_gcounter++;
+	if (sc->sc_count_nat4 >= sc->sc_maxcount_nat4)
+		ret = pflow_sendout_ipfix(sc, PFLOW_NAT4);
+
+	return (ret);
+}
+
 static int
 pflow_pack_flow(const struct pf_kstate *st, struct pf_state_key *sk,
     struct pflow_softc *sc)
@@ -815,17 +940,30 @@ pflow_pack_flow(const struct pf_kstate *st, struct pf_state_key *sk,
 	return (ret);
 }
 
+static bool
+pflow_is_natd(const struct pf_kstate *st)
+{
+	/* If ports or addresses are different we've been NAT-ed. */
+	return (memcmp(st->key[PF_SK_WIRE], st->key[PF_SK_STACK],
+	    sizeof(struct pf_addr) * 2 + sizeof(uint16_t) * 2) != 0);
+}
+
 static int
 pflow_pack_flow_ipfix(const struct pf_kstate *st, struct pf_state_key *sk,
     struct pflow_softc *sc)
 {
 	struct pflow_ipfix_flow4	 flow4_1, flow4_2;
+	struct pflow_ipfix_nat4		 nat4_1, nat4_2;
 	struct pflow_ipfix_flow6	 flow6_1, flow6_2;
 	int				 ret = 0;
+	bool				 nat = false;
+
 	if (sk->af == AF_INET) {
 		bzero(&flow4_1, sizeof(flow4_1));
 		bzero(&flow4_2, sizeof(flow4_2));
 
+		nat = pflow_is_natd(st);
+
 		if (st->direction == PF_OUT)
 			copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc,
 			    1, 0);
@@ -833,11 +971,30 @@ pflow_pack_flow_ipfix(const struct pf_kstate *st, struct pf_state_key *sk,
 			copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc,
 			    0, 1);
 
-		if (st->bytes[0] != 0) /* first flow from state */
+		if (nat)
+			copy_nat_ipfix_4_data(&nat4_1, &nat4_2, st, sk, sc, 1, 0);
+
+		if (st->bytes[0] != 0) /* first flow from state */ {
 			ret = copy_flow_ipfix_4_to_m(&flow4_1, sc);
 
-		if (st->bytes[1] != 0) /* second flow from state */
+			if (ret == 0 && nat) {
+				ret = copy_nat_ipfix_4_to_m(&nat4_1, st, sc,
+				    PFIX_NAT_EVENT_SESSION_CREATE, st->creation);
+				ret |= copy_nat_ipfix_4_to_m(&nat4_1, st, sc,
+				    PFIX_NAT_EVENT_SESSION_DELETE, st->expire);
+			}
+		}
+
+		if (st->bytes[1] != 0) /* second flow from state */ {
 			ret = copy_flow_ipfix_4_to_m(&flow4_2, sc);
+
+			if (ret == 0 && nat) {
+				ret = copy_nat_ipfix_4_to_m(&nat4_2, st, sc,
+				    PFIX_NAT_EVENT_SESSION_CREATE, st->creation);
+				ret |= copy_nat_ipfix_4_to_m(&nat4_2, st, sc,
+				    PFIX_NAT_EVENT_SESSION_DELETE, st->expire);
+			}
+		}
 	} else if (sk->af == AF_INET6) {
 		bzero(&flow6_1, sizeof(flow6_1));
 		bzero(&flow6_2, sizeof(flow6_2));
@@ -871,7 +1028,7 @@ pflow_timeout(void *v)
 		pflow_sendout_v5(sc);
 		break;
 	case PFLOW_PROTO_10:
-		pflow_sendout_ipfix(sc, AF_INET);
+		pflow_sendout_ipfix(sc, PFLOW_INET);
 		break;
 	default: /* NOTREACHED */
 		panic("Unsupported version %d", sc->sc_version);
@@ -892,7 +1049,7 @@ pflow_timeout6(void *v)
 		return;
 
 	CURVNET_SET(sc->sc_vnet);
-	pflow_sendout_ipfix(sc, AF_INET6);
+	pflow_sendout_ipfix(sc, PFLOW_INET6);
 	CURVNET_RESTORE();
 }
 
@@ -911,6 +1068,21 @@ pflow_timeout_tmpl(void *v)
 	CURVNET_RESTORE();
 }
 
+static void
+pflow_timeout_nat4(void *v)
+{
+	struct pflow_softc	*sc = v;
+
+	PFLOW_ASSERT(sc);
+
+	if (sc->sc_version != PFLOW_PROTO_10)
+		return;
+
+	CURVNET_SET(sc->sc_vnet);
+	pflow_sendout_ipfix(sc, PFLOW_NAT4);
+	CURVNET_RESTORE();
+}
+
 static void
 pflow_flush(struct pflow_softc *sc)
 {
@@ -921,8 +1093,9 @@ pflow_flush(struct pflow_softc *sc)
 		pflow_sendout_v5(sc);
 		break;
 	case PFLOW_PROTO_10:
-		pflow_sendout_ipfix(sc, AF_INET);
-		pflow_sendout_ipfix(sc, AF_INET6);
+		pflow_sendout_ipfix(sc, PFLOW_INET);
+		pflow_sendout_ipfix(sc, PFLOW_INET6);
+		pflow_sendout_ipfix(sc, PFLOW_NAT4);
 		break;
 	default: /* NOTREACHED */
 		break;
@@ -960,7 +1133,7 @@ pflow_sendout_v5(struct pflow_softc *sc)
 }
 
 static int
-pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af)
+pflow_sendout_ipfix(struct pflow_softc *sc, enum pflow_family_t af)
 {
 	struct mbuf			*m;
 	struct pflow_v10_header		*h10;
@@ -971,7 +1144,7 @@ pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af)
 	PFLOW_ASSERT(sc);
 
 	switch (af) {
-	case AF_INET:
+	case PFLOW_INET:
 		m = sc->sc_mbuf;
 		callout_stop(&sc->sc_tmo);
 		if (m == NULL)
@@ -981,7 +1154,7 @@ pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af)
 		set_length = sizeof(struct pflow_set_header)
 		    + sc->sc_count4 * sizeof(struct pflow_ipfix_flow4);
 		break;
-	case AF_INET6:
+	case PFLOW_INET6:
 		m = sc->sc_mbuf6;
 		callout_stop(&sc->sc_tmo6);
 		if (m == NULL)
@@ -991,6 +1164,16 @@ pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af)
 		set_length = sizeof(struct pflow_set_header)
 		    + sc->sc_count6 * sizeof(struct pflow_ipfix_flow6);
 		break;
+	case PFLOW_NAT4:
+		m = sc->sc_mbuf_nat4;
+		callout_stop(&sc->sc_tmo_nat4);
+		if (m == NULL)
+			return (0);
+		sc->sc_mbuf_nat4 = NULL;
+		count = sc->sc_count_nat4;
+		set_length = sizeof(struct pflow_set_header)
+		    + sc->sc_count_nat4 * sizeof(struct pflow_ipfix_nat4);
+		break;
 	default:
 		panic("Unsupported AF %d", af);
 	}