Index | Thread | Search

From:
Chris Cappuccio <chris@nmedia.net>
Subject:
if_pflow IPFIX NAT support
To:
tech@openbsd.org
Date:
Tue, 23 Sep 2025 09:26:25 -0700

Download raw body.

Thread
I sent out an earlier version of this years ago, but finally
got it tested and working. This adds template fields (as per the
IANA IPFIX IE list) and generates the new templates for pflow's
IPFIX (Netflow v10) exporter to explain these fields to
collectors.

In the earlier pflow days, pflow would actually send the pre-
NAT IPs as source or destination for flows in one direction and
post-NAT for the other. When IPFIX was added, the v5/9 exporter
was corrected to show only post-NAT IPs, with the intention 
to extend the templates and support NAT:

Index: if_pflow.c
===================================================================
RCS file: /cvs/src/sys/net/if_pflow.c,v
retrieving revision 1.111
diff -u -p -u -r1.111 if_pflow.c
--- if_pflow.c	7 Jul 2025 02:28:50 -0000	1.111
+++ if_pflow.c	20 Sep 2025 03:40:58 -0000
@@ -89,29 +89,34 @@ int	pflowioctl(struct ifnet *, u_long, c
 struct mbuf	*pflow_get_mbuf(struct pflow_softc *, u_int16_t);
 void	pflow_flush(struct pflow_softc *);
 int	pflow_sendout_v5(struct pflow_softc *);
-int	pflow_sendout_ipfix(struct pflow_softc *, sa_family_t);
+int	pflow_sendout_ipfix(struct pflow_softc *, sa_family_t, size_t, u_int16_t);
 int	pflow_sendout_ipfix_tmpl(struct pflow_softc *);
 int	pflow_sendout_mbuf(struct pflow_softc *, struct mbuf *);
 void	pflow_timeout(void *);
 void	pflow_timeout6(void *);
 void	pflow_timeout_tmpl(void *);
+void	pflow_timeout_nat(void *);
 void	copy_flow_data(struct pflow_flow *, struct pflow_flow *,
 	struct pf_state *, struct pf_state_key *, int, int);
 void	copy_flow_ipfix_4_data(struct pflow_ipfix_flow4 *,
 	struct pflow_ipfix_flow4 *, struct pf_state *, struct pf_state_key *,
 	struct pflow_softc *, int, int);
+void	copy_flow_ipfix_nat_4_data(struct pflow_ipfix_nat_flow4 *,
+	struct pflow_ipfix_nat_flow4 *, struct pf_state *,
+	struct pf_state_key *, struct pf_state_key *,
+	struct pflow_softc *, int, int);
 void	copy_flow_ipfix_6_data(struct pflow_ipfix_flow6 *,
 	struct pflow_ipfix_flow6 *, struct pf_state *, struct pf_state_key *,
 	struct pflow_softc *, int, int);
 int	pflow_pack_flow(struct pf_state *, struct pf_state_key *,
 	struct pflow_softc *);
 int	pflow_pack_flow_ipfix(struct pf_state *, struct pf_state_key *,
-	struct pflow_softc *);
+	struct pf_state_key *, struct pflow_softc *);
 int	export_pflow_if(struct pf_state*, struct pf_state_key *,
-	struct pflow_softc *);
+	struct pf_state_key *, struct pflow_softc *);
 int	copy_flow_to_m(struct pflow_flow *flow, struct pflow_softc *sc);
-int	copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow,
-	struct pflow_softc *sc);
+int	copy_flow_ipfix_4_to_m(void *flow, size_t size,
+	    struct pflow_softc *sc, u_int16_t tmpl);
 int	copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow,
 	struct pflow_softc *sc);
 
@@ -172,8 +177,8 @@ pflow_clone_create(struct if_clone *ifc,
 	/* ipfix IPv4 template */
 	pflowif->sc_tmpl_ipfix.ipv4_tmpl.h.tmpl_id =
 	    htons(PFLOW_IPFIX_TMPL_IPV4_ID);
-	pflowif->sc_tmpl_ipfix.ipv4_tmpl.h.field_count
-	    = htons(PFLOW_IPFIX_TMPL_IPV4_FIELD_COUNT);
+	pflowif->sc_tmpl_ipfix.ipv4_tmpl.h.field_count =
+	    htons(PFLOW_IPFIX_TMPL_IPV4_FIELD_COUNT);
 	pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_ip.field_id =
 	    htons(PFIX_IE_sourceIPv4Address);
 	pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_ip.len = htons(4);
@@ -211,6 +216,60 @@ pflow_clone_create(struct if_clone *ifc,
 	    htons(PFIX_IE_protocolIdentifier);
 	pflowif->sc_tmpl_ipfix.ipv4_tmpl.protocol.len = htons(1);
 
+	/* ipfix IPv4 NAT template */
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.h.tmpl_id =
+	    htons(PFLOW_IPFIX_TMPL_NAT_IPV4_ID);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.h.field_count =
+	    htons(PFLOW_IPFIX_TMPL_NAT_IPV4_FIELD_COUNT);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.src_ip.field_id =
+	    htons(PFIX_IE_sourceIPv4Address);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.src_ip.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.dest_ip.field_id =
+	    htons(PFIX_IE_destinationIPv4Address);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.dest_ip.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.if_index_in.field_id =
+	    htons(PFIX_IE_ingressInterface);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.if_index_in.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.if_index_out.field_id =
+	    htons(PFIX_IE_egressInterface);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.if_index_out.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.packets.field_id =
+	    htons(PFIX_IE_packetDeltaCount);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.packets.len = htons(8);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.octets.field_id =
+	    htons(PFIX_IE_octetDeltaCount);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.octets.len = htons(8);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.start.field_id =
+	    htons(PFIX_IE_flowStartMilliseconds);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.start.len = htons(8);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.finish.field_id =
+	    htons(PFIX_IE_flowEndMilliseconds);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.finish.len = htons(8);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_src_ip.field_id =
+	    htons(PFIX_IE_postNATSourceIPv4Address);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_src_ip.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_dest_ip.field_id =
+	    htons(PFIX_IE_postNATDestinationIPv4Address);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_dest_ip.len = htons(4);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_src_port.field_id =
+	    htons(PFIX_IE_postNAPTSourceTransportPort);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_src_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_dest_port.field_id =
+	    htons(PFIX_IE_postNAPTDestinationTransportPort);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.post_dest_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.src_port.field_id =
+	    htons(PFIX_IE_sourceTransportPort);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.src_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.dest_port.field_id =
+	    htons(PFIX_IE_destinationTransportPort);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.dest_port.len = htons(2);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.tos.field_id =
+	    htons(PFIX_IE_ipClassOfService);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.tos.len = htons(1);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.protocol.field_id =
+	    htons(PFIX_IE_protocolIdentifier);
+	pflowif->sc_tmpl_ipfix.ipv4_nat_tmpl.protocol.len = htons(1);
+
 	/* ipfix IPv6 template */
 	pflowif->sc_tmpl_ipfix.ipv6_tmpl.h.tmpl_id =
 	    htons(PFLOW_IPFIX_TMPL_IPV6_ID);
@@ -270,6 +329,7 @@ pflow_clone_create(struct if_clone *ifc,
 	timeout_set_proc(&pflowif->sc_tmo, pflow_timeout, pflowif);
 	timeout_set_proc(&pflowif->sc_tmo6, pflow_timeout6, pflowif);
 	timeout_set_proc(&pflowif->sc_tmo_tmpl, pflow_timeout_tmpl, pflowif);
+	timeout_set_proc(&pflowif->sc_tmo_nat, pflow_timeout_nat, pflowif);
 
 	task_set(&pflowif->sc_outputtask, pflow_output_process, pflowif);
 
@@ -302,6 +362,7 @@ pflow_clone_destroy(struct ifnet *ifp)
 	timeout_del(&sc->sc_tmo);
 	timeout_del(&sc->sc_tmo6);
 	timeout_del(&sc->sc_tmo_tmpl);
+	timeout_del(&sc->sc_tmo_nat);
 
 	pflow_flush(sc);
 	taskq_del_barrier(net_tq(ifp->if_index), &sc->sc_outputtask);
@@ -618,7 +679,7 @@ int
 pflow_calc_mtu(struct pflow_softc *sc, int mtu, int hdrsz)
 {
 	sc->sc_maxcount4 = (mtu - hdrsz -
-	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow4);
+	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_nat_flow4);
 	sc->sc_maxcount6 = (mtu - hdrsz -
 	    sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow6);
 	if (sc->sc_maxcount4 > PFLOW_MAXFLOWS)
@@ -626,7 +687,7 @@ pflow_calc_mtu(struct pflow_softc *sc, i
 	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),
+	    MIN(sc->sc_maxcount4 * sizeof(struct pflow_ipfix_nat_flow4),
 	    sc->sc_maxcount6 * sizeof(struct pflow_ipfix_flow6)));
 }
 
@@ -787,6 +848,59 @@ copy_flow_ipfix_4_data(struct pflow_ipfi
 }
 
 void
+copy_flow_ipfix_nat_4_data(struct pflow_ipfix_nat_flow4 *flow1,
+    struct pflow_ipfix_nat_flow4 *flow2, struct pf_state *st,
+    struct pf_state_key *sk, struct pf_state_key *skw,
+    struct pflow_softc *sc, int src, int dst)
+{
+	flow1->src_ip = sk->addr[src].v4.s_addr;
+	flow1->dest_ip = sk->addr[dst].v4.s_addr;
+	flow2->src_ip = sk->addr[dst].v4.s_addr;
+	flow2->dest_ip = sk->addr[src].v4.s_addr;
+
+	flow1->post_src_ip = skw->addr[src].v4.s_addr;
+	flow1->post_dest_ip = skw->addr[dst].v4.s_addr;
+	flow1->post_src_port = skw->port[src];
+	flow1->post_dest_port = skw->port[dst];
+
+	flow2->post_src_ip = skw->addr[dst].v4.s_addr;
+	flow2->post_dest_ip = skw->addr[src].v4.s_addr;
+	flow2->post_src_port = skw->port[dst];
+	flow2->post_dest_port = skw->port[src];
+
+	flow1->if_index_in = htonl(st->if_index_in);
+	flow1->if_index_out = htonl(st->if_index_out);
+	flow2->if_index_in = htonl(st->if_index_out);
+	flow2->if_index_out = htonl(st->if_index_in);
+
+	flow1->flow_packets = htobe64(st->packets[0]);
+	flow2->flow_packets = htobe64(st->packets[1]);
+	flow1->flow_octets = htobe64(st->bytes[0]);
+	flow2->flow_octets = htobe64(st->bytes[1]);
+
+	/*
+	 * Pretend the flow was created when the machine came up when creation
+	 * is in the future of the last time a package was seen due to pfsync.
+	 */
+	if (st->creation > st->expire)
+		flow1->flow_start = flow2->flow_start = htobe64((gettime() -
+		    getuptime())*1000);
+	else
+		flow1->flow_start = flow2->flow_start = htobe64((gettime() -
+		    (getuptime() - st->creation))*1000);
+	flow1->flow_finish = flow2->flow_finish = htobe64((gettime() -
+	    (getuptime() - st->expire))*1000);
+
+	flow1->src_port = sk->port[src];
+	flow1->dest_port = sk->port[dst];
+	flow2->src_port = sk->port[dst];
+	flow2->dest_port = sk->port[src];
+
+	flow1->protocol = flow2->protocol = sk->proto;
+	flow1->tos = flow2->tos = st->rule.ptr ? st->rule.ptr->tos : 0;
+}
+
+void
 copy_flow_ipfix_6_data(struct pflow_ipfix_flow6 *flow1,
     struct pflow_ipfix_flow6 *flow2, struct pf_state *st,
     struct pf_state_key *sk, struct pflow_softc *sc, int src, int dst)
@@ -829,20 +943,21 @@ int
 export_pflow(struct pf_state *st)
 {
 	struct pflow_softc	*sc = NULL;
-	struct pf_state_key	*sk;
+	struct pf_state_key	*sk, *skw;
 
 	sk = st->key[st->direction == PF_IN ? PF_SK_WIRE : PF_SK_STACK];
+	skw = st->key[st->direction == PF_OUT ? PF_SK_WIRE : PF_SK_STACK];
 
 	SMR_SLIST_FOREACH(sc, &pflowif_list, sc_next) {
 		mtx_enter(&sc->sc_mtx);
 		switch (sc->sc_version) {
 		case PFLOW_PROTO_5:
 			if (sk->af == AF_INET)
-				export_pflow_if(st, sk, sc);
+				export_pflow_if(st, sk, skw, sc);
 			break;
 		case PFLOW_PROTO_10:
 			if (sk->af == AF_INET || sk->af == AF_INET6)
-				export_pflow_if(st, sk, sc);
+				export_pflow_if(st, sk, skw, sc);
 			break;
 		default: /* NOTREACHED */
 			break;
@@ -855,18 +970,20 @@ export_pflow(struct pf_state *st)
 
 int
 export_pflow_if(struct pf_state *st, struct pf_state_key *sk,
-    struct pflow_softc *sc)
+    struct pf_state_key *skw, struct pflow_softc *sc)
 {
 	struct pf_state		 pfs_copy;
 	struct ifnet		*ifp = &sc->sc_if;
 	u_int64_t		 bytes[2];
 	int			 ret = 0;
 
+	printf("pflow: exporting flow, bytes[0]=%llu, bytes[1]=%llu\n", 
+       st->bytes[0], st->bytes[1]);
 	if (!(ifp->if_flags & IFF_RUNNING))
 		return (0);
 
 	if (sc->sc_version == PFLOW_PROTO_10)
-		return (pflow_pack_flow_ipfix(st, sk, sc));
+		return (pflow_pack_flow_ipfix(st, sk, skw, sc));
 
 	/* PFLOW_PROTO_5 */
 	if ((st->bytes[0] < (u_int64_t)PFLOW_MAXBYTES)
@@ -929,31 +1046,48 @@ copy_flow_to_m(struct pflow_flow *flow, 
 	return(ret);
 }
 
-int
-copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow, struct pflow_softc *sc)
+int copy_flow_ipfix_4_to_m(void *flow, size_t size, struct pflow_softc *sc,
+                          u_int16_t tmpl)
 {
 	int		ret = 0;
 
 	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
 
-	if (sc->sc_mbuf == NULL) {
-		if ((sc->sc_mbuf =
-		    pflow_get_mbuf(sc, PFLOW_IPFIX_TMPL_IPV4_ID)) == NULL) {
-			return (ENOBUFS);
-		}
-		sc->sc_count4 = 0;
-		timeout_add_sec(&sc->sc_tmo, PFLOW_TIMEOUT);
-	}
-	m_copyback(sc->sc_mbuf, PFLOW_SET_HDRLEN +
-	    (sc->sc_count4 * sizeof(struct pflow_ipfix_flow4)),
-	    sizeof(struct pflow_ipfix_flow4), flow, M_NOWAIT);
+printf("pflow: copying flow, tmpl=%d, count=%d\n", tmpl, 
+       tmpl == PFLOW_IPFIX_TMPL_NAT_IPV4_ID ? sc->sc_count4_nat : sc->sc_count4);
 
-	pflowstat_inc(pflow_flows);
-	sc->sc_gcounter++;
-	sc->sc_count4++;
+	if (tmpl == PFLOW_IPFIX_TMPL_NAT_IPV4_ID) {
+		if (sc->sc_mbuf_nat == NULL) {
+			if ((sc->sc_mbuf_nat = pflow_get_mbuf(sc, tmpl)) == NULL)
+				return (ENOBUFS);
+			sc->sc_count4_nat = 0;
+			timeout_add_sec(&sc->sc_tmo_nat, PFLOW_TIMEOUT);
+		}
+		m_copyback(sc->sc_mbuf_nat, PFLOW_SET_HDRLEN + 
+		    (sc->sc_count4_nat * size), size, flow, M_NOWAIT);
+		pflowstat_inc(pflow_flows);
+		sc->sc_gcounter++;
+		sc->sc_count4_nat++;
+
+		if (sc->sc_count4_nat >= sc->sc_maxcount4)
+			ret = pflow_sendout_ipfix(sc, AF_INET, size, tmpl);
+	} else {
+		if (sc->sc_mbuf == NULL) {
+			if ((sc->sc_mbuf = pflow_get_mbuf(sc, tmpl)) == NULL)
+				return (ENOBUFS);
+			sc->sc_count4 = 0;
+			timeout_add_sec(&sc->sc_tmo, PFLOW_TIMEOUT);
+		}
+		m_copyback(sc->sc_mbuf, PFLOW_SET_HDRLEN + 
+		    (sc->sc_count4 * size), size, flow, M_NOWAIT);
+		pflowstat_inc(pflow_flows);
+		sc->sc_gcounter++;
+		sc->sc_count4++;
 
-	if (sc->sc_count4 >= sc->sc_maxcount4)
-		ret = pflow_sendout_ipfix(sc, AF_INET);
+		if (sc->sc_count4 >= sc->sc_maxcount4)
+			ret = pflow_sendout_ipfix(sc, AF_INET, size, tmpl);
+	}
+    
 	return(ret);
 }
 
@@ -961,6 +1095,7 @@ int
 copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow, struct pflow_softc *sc)
 {
 	int		ret = 0;
+	int		size = sizeof(struct pflow_ipfix_flow6);
 
 	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
 
@@ -973,15 +1108,15 @@ copy_flow_ipfix_6_to_m(struct pflow_ipfi
 		timeout_add_sec(&sc->sc_tmo6, PFLOW_TIMEOUT);
 	}
 	m_copyback(sc->sc_mbuf6, PFLOW_SET_HDRLEN +
-	    (sc->sc_count6 * sizeof(struct pflow_ipfix_flow6)),
-	    sizeof(struct pflow_ipfix_flow6), flow, M_NOWAIT);
+	    (sc->sc_count6 * size), size, flow, M_NOWAIT);
 
 	pflowstat_inc(pflow_flows);
 	sc->sc_gcounter++;
 	sc->sc_count6++;
 
 	if (sc->sc_count6 >= sc->sc_maxcount6)
-		ret = pflow_sendout_ipfix(sc, AF_INET6);
+		ret = pflow_sendout_ipfix(sc, AF_INET6, size,
+		    PFLOW_IPFIX_TMPL_IPV6_ID);
 
 	return(ret);
 }
@@ -1013,27 +1148,60 @@ pflow_pack_flow(struct pf_state *st, str
 
 int
 pflow_pack_flow_ipfix(struct pf_state *st, struct pf_state_key *sk,
-    struct pflow_softc *sc)
+    struct pf_state_key *skw, struct pflow_softc *sc)
 {
 	struct pflow_ipfix_flow4	 flow4_1, flow4_2;
+	struct pflow_ipfix_nat_flow4	 natflow4_1, natflow4_2;
 	struct pflow_ipfix_flow6	 flow6_1, flow6_2;
 	int				 ret = 0;
-	if (sk->af == AF_INET) {
-		bzero(&flow4_1, sizeof(flow4_1));
-		bzero(&flow4_2, sizeof(flow4_2));
+	int				 is_nat = (sk != skw);
 
-		if (st->direction == PF_OUT)
-			copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc,
-			    1, 0);
-		else
-			copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc,
-			    0, 1);
-
-		if (st->bytes[0] != 0) /* first flow from state */
-			ret = copy_flow_ipfix_4_to_m(&flow4_1, sc);
+printf("pflow: is_nat=%d, af=%d\n", is_nat, sk->af);
 
-		if (st->bytes[1] != 0) /* second flow from state */
-			ret = copy_flow_ipfix_4_to_m(&flow4_2, sc);
+	if (sk->af == AF_INET) {
+		if (is_nat) {
+			bzero(&natflow4_1, sizeof(natflow4_1));
+			bzero(&natflow4_2, sizeof(natflow4_2));
+
+			if (st->direction == PF_OUT)
+				copy_flow_ipfix_nat_4_data(&natflow4_1,
+				    &natflow4_2, st, sk, skw, sc, 1, 0);
+			else
+				copy_flow_ipfix_nat_4_data(&natflow4_1,
+				    &natflow4_2, st, sk, skw, sc, 0, 1);
+
+			if (st->bytes[0] != 0) /* first flow from state */
+				ret = copy_flow_ipfix_4_to_m(
+				    (void *)&natflow4_1,
+				    sizeof(natflow4_1), sc,
+				    PFLOW_IPFIX_TMPL_NAT_IPV4_ID);
+			if (st->bytes[1] != 0) /* second flow from state */
+				ret = copy_flow_ipfix_4_to_m(
+				    (void *)&natflow4_2,
+				    sizeof(natflow4_2), sc,
+				    PFLOW_IPFIX_TMPL_NAT_IPV4_ID);
+		} else {
+			bzero(&flow4_1, sizeof(flow4_1));
+			bzero(&flow4_2, sizeof(flow4_2));
+
+			if (st->direction == PF_OUT)
+				copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st,
+				    sk, sc, 1, 0);
+			else
+				copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st,
+				    sk, sc, 0, 1);
+
+			if (st->bytes[0] != 0) /* first flow from state */
+				ret = copy_flow_ipfix_4_to_m(
+				    (void *)&flow4_1,
+				    sizeof(flow4_1), sc,
+				    PFLOW_IPFIX_TMPL_IPV4_ID);
+			if (st->bytes[1] != 0) /* second flow from state */
+				ret = copy_flow_ipfix_4_to_m(
+				    (void *)&flow4_2,
+				    sizeof(flow4_2), sc,
+				    PFLOW_IPFIX_TMPL_IPV4_ID);
+		}
 	} else if (sk->af == AF_INET6) {
 		bzero(&flow6_1, sizeof(flow6_1));
 		bzero(&flow6_2, sizeof(flow6_2));
@@ -1054,6 +1222,26 @@ pflow_pack_flow_ipfix(struct pf_state *s
 	return (ret);
 }
 
+void pflow_timeout_nat(void *v)
+{
+	struct pflow_softc	*sc = v;
+
+	mtx_enter(&sc->sc_mtx);
+	switch (sc->sc_version) {
+	case PFLOW_PROTO_5:
+		pflow_sendout_v5(sc);
+		break;
+	case PFLOW_PROTO_10:
+		pflow_sendout_ipfix(sc, AF_INET,
+		    sizeof(struct pflow_ipfix_nat_flow4),
+		    PFLOW_IPFIX_TMPL_NAT_IPV4_ID);
+		break;
+	default: /* NOTREACHED */
+		break;
+	}
+	mtx_leave(&sc->sc_mtx);
+}
+
 void
 pflow_timeout(void *v)
 {
@@ -1065,7 +1253,9 @@ pflow_timeout(void *v)
 		pflow_sendout_v5(sc);
 		break;
 	case PFLOW_PROTO_10:
-		pflow_sendout_ipfix(sc, AF_INET);
+		pflow_sendout_ipfix(sc, AF_INET,
+		    sizeof(struct pflow_ipfix_flow4),
+		    PFLOW_IPFIX_TMPL_IPV4_ID);
 		break;
 	default: /* NOTREACHED */
 		break;
@@ -1079,7 +1269,9 @@ pflow_timeout6(void *v)
 	struct pflow_softc	*sc = v;
 
 	mtx_enter(&sc->sc_mtx);
-	pflow_sendout_ipfix(sc, AF_INET6);
+	pflow_sendout_ipfix(sc, AF_INET6,
+	    sizeof(struct pflow_ipfix_flow6),
+	    PFLOW_IPFIX_TMPL_IPV6_ID);
 	mtx_leave(&sc->sc_mtx);
 }
 
@@ -1102,8 +1294,15 @@ 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, AF_INET,
+		    sizeof(struct pflow_ipfix_nat_flow4),
+		    PFLOW_IPFIX_TMPL_NAT_IPV4_ID);
+		pflow_sendout_ipfix(sc, AF_INET,
+		    sizeof(struct pflow_ipfix_flow4),
+		    PFLOW_IPFIX_TMPL_IPV4_ID);
+		pflow_sendout_ipfix(sc, AF_INET6,
+		    sizeof(struct pflow_ipfix_flow6),
+		    PFLOW_IPFIX_TMPL_IPV6_ID);
 		break;
 	default: /* NOTREACHED */
 		break;
@@ -1148,7 +1347,7 @@ pflow_sendout_v5(struct pflow_softc *sc)
 }
 
 int
-pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af)
+pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af, size_t size, u_int16_t tmpl)
 {
 	struct mbuf			*m;
 	struct pflow_v10_header		*h10;
@@ -1161,14 +1360,23 @@ pflow_sendout_ipfix(struct pflow_softc *
 
 	switch (af) {
 	case AF_INET:
-		m = sc->sc_mbuf;
-		timeout_del(&sc->sc_tmo);
-		if (m == NULL)
-			return (0);
-		sc->sc_mbuf = NULL;
-		count = sc->sc_count4;
+		if (tmpl == PFLOW_IPFIX_TMPL_NAT_IPV4_ID) {
+			m = sc->sc_mbuf_nat;
+			timeout_del(&sc->sc_tmo_nat);
+			if (m == NULL)
+				return (0);
+			sc->sc_mbuf_nat = NULL;
+			count = sc->sc_count4_nat;
+		} else {
+			m = sc->sc_mbuf;
+			timeout_del(&sc->sc_tmo);
+			if (m == NULL)
+				return (0);
+			sc->sc_mbuf = NULL;
+			count = sc->sc_count4;
+		}
 		set_length = sizeof(struct pflow_set_header)
-		    + sc->sc_count4 * sizeof(struct pflow_ipfix_flow4);
+		    + count * size;
 		break;
 	case AF_INET6:
 		m = sc->sc_mbuf6;
@@ -1178,7 +1386,7 @@ pflow_sendout_ipfix(struct pflow_softc *
 		sc->sc_mbuf6 = NULL;
 		count = sc->sc_count6;
 		set_length = sizeof(struct pflow_set_header)
-		    + sc->sc_count6 * sizeof(struct pflow_ipfix_flow6);
+		    + sc->sc_count6 * size;
 		break;
 	default:
 		unhandled_af(af);
Index: if_pflow.h
===================================================================
RCS file: /cvs/src/sys/net/if_pflow.h,v
retrieving revision 1.23
diff -u -p -u -r1.23 if_pflow.h
--- if_pflow.h	16 Dec 2023 22:16:02 -0000	1.23
+++ if_pflow.h	20 Sep 2025 03:40:58 -0000
@@ -33,22 +33,26 @@
 
 /* RFC 5102 Information Element Identifiers */
 
-#define PFIX_IE_octetDeltaCount			  1
-#define PFIX_IE_packetDeltaCount		  2
-#define PFIX_IE_protocolIdentifier		  4
-#define PFIX_IE_ipClassOfService		  5
-#define PFIX_IE_sourceTransportPort		  7
-#define PFIX_IE_sourceIPv4Address		  8
-#define PFIX_IE_ingressInterface		 10
-#define PFIX_IE_destinationTransportPort	 11
-#define PFIX_IE_destinationIPv4Address		 12
-#define PFIX_IE_egressInterface			 14
-#define PFIX_IE_flowEndSysUpTime		 21
-#define PFIX_IE_flowStartSysUpTime		 22
-#define PFIX_IE_sourceIPv6Address		 27
-#define PFIX_IE_destinationIPv6Address		 28
-#define PFIX_IE_flowStartMilliseconds		152
-#define PFIX_IE_flowEndMilliseconds		153
+#define PFIX_IE_octetDeltaCount				  1
+#define PFIX_IE_packetDeltaCount			  2
+#define PFIX_IE_protocolIdentifier			  4
+#define PFIX_IE_ipClassOfService			  5
+#define PFIX_IE_sourceTransportPort		 	  7
+#define PFIX_IE_sourceIPv4Address		 	  8
+#define PFIX_IE_ingressInterface		 	 10
+#define PFIX_IE_destinationTransportPort	 	 11
+#define PFIX_IE_destinationIPv4Address		 	 12
+#define PFIX_IE_egressInterface			 	 14
+#define PFIX_IE_flowEndSysUpTime		 	 21
+#define PFIX_IE_flowStartSysUpTime		 	 22
+#define PFIX_IE_sourceIPv6Address		 	 27
+#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
 
 struct pflow_flow {
 	u_int32_t	src_ip;
@@ -110,6 +114,28 @@ struct pflow_ipfix_tmpl_ipv4 {
 #define PFLOW_IPFIX_TMPL_IPV4_ID 256
 } __packed;
 
+struct pflow_ipfix_tmpl_nat_ipv4 {
+	struct pflow_tmpl_hdr	h;
+	struct pflow_tmpl_fspec src_ip;
+	struct pflow_tmpl_fspec dest_ip;
+	struct pflow_tmpl_fspec if_index_in;
+	struct pflow_tmpl_fspec if_index_out;
+	struct pflow_tmpl_fspec packets;
+	struct pflow_tmpl_fspec octets;
+	struct pflow_tmpl_fspec start;
+	struct pflow_tmpl_fspec finish;
+	struct pflow_tmpl_fspec post_src_ip;
+	struct pflow_tmpl_fspec post_dest_ip;
+	struct pflow_tmpl_fspec post_src_port;
+	struct pflow_tmpl_fspec post_dest_port;
+	struct pflow_tmpl_fspec src_port;
+	struct pflow_tmpl_fspec dest_port;
+	struct pflow_tmpl_fspec tos;
+	struct pflow_tmpl_fspec protocol;
+#define PFLOW_IPFIX_TMPL_NAT_IPV4_FIELD_COUNT 16
+#define PFLOW_IPFIX_TMPL_NAT_IPV4_ID 257
+} __packed;
+
 /* update pflow_clone_create() when changing pflow_ipfix_tmpl_v6 */
 struct pflow_ipfix_tmpl_ipv6 {
 	struct pflow_tmpl_hdr	h;
@@ -126,13 +152,14 @@ struct pflow_ipfix_tmpl_ipv6 {
 	struct pflow_tmpl_fspec	tos;
 	struct pflow_tmpl_fspec	protocol;
 #define PFLOW_IPFIX_TMPL_IPV6_FIELD_COUNT 12
-#define PFLOW_IPFIX_TMPL_IPV6_ID 257
+#define PFLOW_IPFIX_TMPL_IPV6_ID 258
 } __packed;
 
 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_ipv4		ipv4_tmpl;
+	struct pflow_ipfix_tmpl_nat_ipv4	ipv4_nat_tmpl;
+	struct pflow_ipfix_tmpl_ipv6		ipv6_tmpl;
 } __packed;
 
 struct pflow_ipfix_flow4 {
@@ -151,6 +178,26 @@ struct pflow_ipfix_flow4 {
 	/* XXX padding needed? */
 } __packed;
 
+struct pflow_ipfix_nat_flow4 {
+	u_int32_t	src_ip;		/* sourceIPv4Address*/
+	u_int32_t	dest_ip;	/* destinationIPv4Address */
+	u_int32_t	if_index_in;	/* ingressInterface */
+	u_int32_t	if_index_out;	/* egressInterface */
+	u_int64_t	flow_packets;	/* packetDeltaCount */
+	u_int64_t	flow_octets;	/* octetDeltaCount */
+	int64_t		flow_start;	/* flowStartMilliseconds */
+	int64_t		flow_finish;	/* flowEndMilliseconds */
+	u_int32_t	post_src_ip;	/* postNATSourceIPv4Address */
+	u_int32_t	post_dest_ip;	/* postNATDestinationIPv4Address */
+	u_int16_t	post_src_port;	/* postNAPTSourceTransportPort */
+	u_int16_t	post_dest_port;	/* postNAPTDestinationTransportPort */
+	u_int16_t	src_port;	/* sourceTransportPort */
+	u_int16_t	dest_port;	/* destinationTransportPort */
+	u_int8_t	tos;		/* ipClassOfService */
+	u_int8_t	protocol;	/* protocolIdentifier */
+	/* XXX padding needed? */
+} __packed;
+
 struct pflow_ipfix_flow6 {
 	struct in6_addr src_ip;		/* sourceIPv6Address */
 	struct in6_addr dest_ip;	/* destinationIPv6Address */
@@ -187,6 +234,7 @@ struct pflow_softc {
 
 	unsigned int		 sc_count;	/* [m] */
 	unsigned int		 sc_count4;	/* [m] */
+	unsigned int		 sc_count4_nat;	/* [m] */
 	unsigned int		 sc_count6;	/* [m] */
 	unsigned int		 sc_maxcount;	/* [m] */
 	unsigned int		 sc_maxcount4;	/* [m] */
@@ -196,6 +244,7 @@ struct pflow_softc {
 	struct timeout		 sc_tmo;
 	struct timeout		 sc_tmo6;
 	struct timeout		 sc_tmo_tmpl;
+	struct timeout		 sc_tmo_nat;
 	struct mbuf_queue	 sc_outputqueue;
 	struct task		 sc_outputtask;
 	struct socket		*so;		/* [p] */
@@ -207,6 +256,8 @@ struct pflow_softc {
 	struct mbuf		*sc_mbuf;	/* [m] current cumulative
 						    mbuf */
 	struct mbuf		*sc_mbuf6;	/* [m] current cumulative
+						    mbuf */
+	struct mbuf		*sc_mbuf_nat;	/* [m] current cumulative
 						    mbuf */
 	SMR_SLIST_ENTRY(pflow_softc) sc_next;
 };