Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 11 Dec 2025 10:36:29 +0000
From:      Kristof Provost <kp@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: ac4fb06d096d - main - pf: handle TTL expired during nat64
Message-ID:  <693a9ead.38c7d.75fb604c@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help

The branch main has been updated by kp:

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

commit ac4fb06d096d6308b9522f454b68fbfc45bb8531
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-12-10 16:27:51 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-12-11 09:25:33 +0000

    pf: handle TTL expired during nat64
    
    If the TTL (or hop limit) expires during nat64 translation we may
    need to send the error message in the original address family (i.e.
    pre-translation).
    We'd usually handle this in pf_route()/pf_route6(), but at that point we
    have already translated the packet, making it difficult to include it in
    the generated ICMP message.
    
    Check for this case in pf_translate_af() and send icmp errors directly
    from it.
    
    PR:             291527
    MFC after:      2 weeks
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D54166
---
 sys/net/pfvar.h               |  1 -
 sys/netpfil/pf/pf.c           | 25 ++++++++++++++++++++-----
 tests/sys/netpfil/pf/nat64.py | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 7568b83f89fd..90e926ef3cb1 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2518,7 +2518,6 @@ int	pf_socket_lookup(struct pf_pdesc *);
 struct pf_state_key *pf_alloc_state_key(int);
 int	pf_translate(struct pf_pdesc *, struct pf_addr *, u_int16_t,
 	    struct pf_addr *, u_int16_t, u_int16_t, int);
-int	pf_translate_af(struct pf_pdesc *);
 bool	pf_init_threshold(struct pf_kthreshold *, uint32_t, uint32_t);
 uint16_t	pf_tagname2tag(const char *);
 #ifdef ALTQ
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 2f09d47dc6e1..2ebaf61a165f 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -3605,8 +3605,8 @@ pf_change_icmp(struct pf_addr *ia, u_int16_t *ip, struct pf_addr *oa,
 	}
 }
 
-int
-pf_translate_af(struct pf_pdesc *pd)
+static int
+pf_translate_af(struct pf_pdesc *pd, struct pf_krule *r)
 {
 #if defined(INET) && defined(INET6)
 	struct mbuf		*mp;
@@ -3617,6 +3617,21 @@ pf_translate_af(struct pf_pdesc *pd)
 	struct pf_fragment_tag	*ftag;
 	int			 hlen;
 
+	if (pd->ttl == 1) {
+		/* We'd generate an ICMP error. Do so now rather than after af translation. */
+		if (pd->af == AF_INET) {
+			pf_send_icmp(pd->m, ICMP_TIMXCEED,
+			    ICMP_TIMXCEED_INTRANS, 0, pd->af, r,
+			    pd->act.rtableid);
+		} else {
+			pf_send_icmp(pd->m, ICMP6_TIME_EXCEEDED,
+			    ICMP6_TIME_EXCEED_TRANSIT, 0, pd->af, r,
+			    pd->act.rtableid);
+		}
+
+		return (-1);
+	}
+
 	hlen = pd->naf == AF_INET ? sizeof(*ip4) : sizeof(*ip6);
 
 	/* trim the old header */
@@ -9186,7 +9201,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 
 	if (pd->dir == PF_IN) {
 		if (ip->ip_ttl <= IPTTLDEC) {
-			if (r->rt != PF_DUPTO)
+			if (r->rt != PF_DUPTO && pd->naf == pd->af)
 				pf_send_icmp(m0, ICMP_TIMXCEED,
 				    ICMP_TIMXCEED_INTRANS, 0, pd->af, r,
 				    pd->act.rtableid);
@@ -9511,7 +9526,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 
 	if (pd->dir == PF_IN) {
 		if (ip6->ip6_hlim <= IPV6_HLIMDEC) {
-			if (r->rt != PF_DUPTO)
+			if (r->rt != PF_DUPTO && pd->naf == pd->af)
 				pf_send_icmp(m0, ICMP6_TIME_EXCEEDED,
 				    ICMP6_TIME_EXCEED_TRANSIT, 0, pd->af, r,
 				    pd->act.rtableid);
@@ -11354,7 +11369,7 @@ done:
 		*m0 = NULL;
 		break;
 	case PF_AFRT:
-		if (pf_translate_af(&pd)) {
+		if (pf_translate_af(&pd, r)) {
 			*m0 = pd.m;
 			action = PF_DROP;
 			break;
diff --git a/tests/sys/netpfil/pf/nat64.py b/tests/sys/netpfil/pf/nat64.py
index 705de72f5bc4..ba0d2ae01a9e 100644
--- a/tests/sys/netpfil/pf/nat64.py
+++ b/tests/sys/netpfil/pf/nat64.py
@@ -329,6 +329,42 @@ class TestNAT64(VnetTestTemplate):
         for r in packets:
             r.show()
 
+    @pytest.mark.require_user("root")
+    @pytest.mark.require_progs(["scapy"])
+    def test_ttl_one(self):
+        """
+            PR 291527: invalid ICMP error generated by nat64 router
+        """
+        ifname = self.vnet.iface_alias_map["if1"].name
+        gw_mac = self.vnet.iface_alias_map["if1"].epairb.ether
+        ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1")
+
+        import scapy.all as sp
+
+        pkt = sp.Ether(dst=gw_mac) \
+            / sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=1) \
+            / sp.SCTP(sport=1111, dport=2222) \
+            / sp.SCTPChunkInit(init_tag=1, n_in_streams=1, n_out_streams=1, \
+                a_rwnd=1500, params=[])
+        s = DelayedSend(pkt, sendif=ifname)
+
+        found = False
+        packets = sp.sniff(iface=ifname, timeout=5)
+        for r in packets:
+            print("Reply packet:")
+            r.show()
+
+            ip6 = r.getlayer(sp.IPv6)
+            icmp6 = r.getlayer(sp.ICMPv6TimeExceeded)
+            if not ip6 or not icmp6:
+                continue
+            assert ip6.src == "2001:db8::1"
+            assert ip6.dst == "2001:db8::2"
+            assert icmp6.type == 3 # Time exceeded
+            assert icmp6.code == 0 # hop limit exceeded in transit
+            found = True
+        assert found
+
     @pytest.mark.require_user("root")
     @pytest.mark.require_progs(["scapy"])
     def test_ttl_zero(self):



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?693a9ead.38c7d.75fb604c>