git: 54c62e3e5d8c - main - pf: work around icmp6 packet-too-big not being sent when binat-ing

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Mon, 22 Jan 2024 12:52:51 UTC
The branch main has been updated by kp:

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

commit 54c62e3e5d8cd90c5571a1d4c8c5f062d580480e
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-01-17 17:11:27 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-01-22 11:52:14 +0000

    pf: work around icmp6 packet-too-big not being sent when binat-ing
    
    If we're applying NPTv6 we pass a packet with a modified source and/or
    destination address to the network stack.
    
    If that packet then turns out to be larger than the MTU of the sending
    interface the stack will attempt to generate an icmp6 packet-too-big
    error, but may fail to look up the appropriate source address for that
    error message. Even if it does, pf would still have to undo the binat
    operation inside the icmp6 packet so the sending host can make sense of
    the error.
    
    We can avoid both problems entirely by having pf also perform the MTU
    check (taking the potential refragmentation into account), and
    generating the icmp6 error directly in pf.
    
    See also:       https://redmine.pfsense.org/issues/14290
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D43499
---
 sys/net/pfvar.h          |  1 +
 sys/netpfil/pf/pf.c      | 12 ++++++++++++
 sys/netpfil/pf/pf_norm.c | 15 +++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index f0742c99a4a8..ff3370bc105e 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2297,6 +2297,7 @@ int	pf_normalize_ip6(struct mbuf **, struct pfi_kkif *, u_short *,
 void	pf_poolmask(struct pf_addr *, struct pf_addr*,
 	    struct pf_addr *, struct pf_addr *, sa_family_t);
 void	pf_addr_inc(struct pf_addr *, sa_family_t);
+int	pf_max_frag_size(struct mbuf *);
 int	pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *, bool);
 #endif /* INET6 */
 
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 9bd9828a99d9..38a5a45d7991 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -8510,6 +8510,18 @@ pf_test6(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0, struct inpcb
 		return (PF_PASS);
 	}
 
+	/*
+	 * If we end up changing IP addresses (e.g. binat) the stack may get
+	 * confused and fail to send the icmp6 packet too big error. Just send
+	 * it here, before we do any NAT.
+	 */
+	if (dir == PF_OUT && IN6_LINKMTU(ifp) < pf_max_frag_size(m)) {
+		PF_RULES_RUNLOCK();
+		*m0 = NULL;
+		icmp6_error(m, ICMP6_PACKET_TOO_BIG, 0, IN6_LINKMTU(ifp));
+		return (PF_DROP);
+	}
+
 	memset(&pd, 0, sizeof(pd));
 	TAILQ_INIT(&pd.sctp_multihome_jobs);
 	if (default_actions != NULL)
diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c
index f5d1a66f6467..295377bef3e8 100644
--- a/sys/netpfil/pf/pf_norm.c
+++ b/sys/netpfil/pf/pf_norm.c
@@ -939,6 +939,21 @@ fail:
 #endif	/* INET6 */
 
 #ifdef INET6
+int
+pf_max_frag_size(struct mbuf *m)
+{
+	struct m_tag *tag;
+	struct pf_fragment_tag *ftag;
+
+	tag = m_tag_find(m, PACKET_TAG_PF_REASSEMBLED, NULL);
+	if (tag == NULL)
+		return (m->m_pkthdr.len);
+
+	ftag = (struct pf_fragment_tag *)(tag + 1);
+
+	return (ftag->ft_maxlen);
+}
+
 int
 pf_refragment6(struct ifnet *ifp, struct mbuf **m0, struct m_tag *mtag,
     bool forward)