Index | Thread | Search

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

Download raw body.

Thread
And, here's a version without my debug printfs.

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	23 Sep 2025 17:00:00 -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,7 +970,7 @@ 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;
@@ -866,7 +981,7 @@ export_pflow_if(struct pf_state *st, str
 		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)
@@ -930,30 +1045,45 @@ copy_flow_to_m(struct pflow_flow *flow, 
 }
 
 int
-copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow, struct pflow_softc *sc)
+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);
+	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);
 		}
-		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);
-
-	pflowstat_inc(pflow_flows);
-	sc->sc_gcounter++;
-	sc->sc_count4++;
+		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 +1091,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 +1104,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 +1144,58 @@ 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);
-
-		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 +1216,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 +1247,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 +1263,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 +1288,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 +1341,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 +1354,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 +1380,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	23 Sep 2025 17:00:00 -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;
 };