git: 9c142388e4de - stable/15 - pf: Send syncookies from the receiving thread

From: Bojan Novković <bnovkov_at_FreeBSD.org>
Date: Mon, 12 Oct 2026 10:42:45 UTC
The branch stable/15 has been updated by bnovkov:

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

commit 9c142388e4dec145109dbf02e7728967c2cbf3b9
Author:     Bojan Novković <bnovkov@FreeBSD.org>
AuthorDate: 2026-08-17 14:10:36 +0000
Commit:     Bojan Novković <bnovkov@FreeBSD.org>
CommitDate: 2026-10-12 09:37:05 +0000

    pf: Send syncookies from the receiving thread
    
    pf sends outbound packets by offloading them to a single per-vnet SWI handler
    through the `V_pf_sendqueue` mbuf queue. A large DDoS attack may overwhelm
    that per-vnet queue with syncookie packets and cause contention in the SWI
    handler that negatively affects other pf operations.
    
    Fix this by sending the initial syncookie challenge from the context
    of the receiving thread. This avoids the syncookie-induced contention on
    the `pf_intr` mbuf queue.
    
    Sponsored by:   Klara, Inc.
    Sponsored by:   Entersekt
    MFC after:      3 weeks
    Reviewed by:    kp
    Differential Revision:  https://reviews.freebsd.org/D59068
    
    (cherry picked from commit bf05236727cf367ee8e22ef47febebb319db9ddb)
---
 sys/netpfil/pf/pf.c            | 67 ++++++++++++++++++++++++------------------
 sys/netpfil/pf/pf.h            |  9 +++++-
 sys/netpfil/pf/pf_syncookies.c | 31 +++++++++++++++++--
 3 files changed, 75 insertions(+), 32 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 6092d0b5566e..1e65bb4dfd96 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -2444,6 +2444,43 @@ pf_icmp_mapping(struct pf_pdesc *pd, u_int8_t type,
 	return (0);  /* These types match to their own state */
 }
 
+#ifdef INET
+void
+pf_send_ip_direct(struct mbuf *m)
+{
+	if (pf_isforlocal(m, AF_INET)) {
+		KASSERT(m->m_pkthdr.rcvif == V_loif,
+		    ("%s: rcvif != loif", __func__));
+
+		m->m_flags |= M_SKIP_FIREWALL;
+		m->m_pkthdr.csum_flags |= CSUM_IP_VALID | CSUM_IP_CHECKED |
+		    CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
+		m->m_pkthdr.csum_data = 0xffff;
+		ip_input(m);
+	} else {
+		ip_output(m, NULL, NULL, 0, NULL, NULL);
+	}
+}
+#endif
+
+#ifdef INET6
+void
+pf_send_ip6_direct(struct mbuf *m)
+{
+	if (pf_isforlocal(m, AF_INET6)) {
+		KASSERT(m->m_pkthdr.rcvif == V_loif,
+		    ("%s: rcvif != loif", __func__));
+
+		m->m_flags |= M_SKIP_FIREWALL | M_LOOP;
+		m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
+		m->m_pkthdr.csum_data = 0xffff;
+		ip6_input(m);
+	} else {
+		ip6_output(m, NULL, NULL, 0, NULL, NULL, NULL);
+	}
+}
+#endif
+
 void
 pf_intr(void *v)
 {
@@ -2464,20 +2501,7 @@ pf_intr(void *v)
 		switch (pfse->pfse_type) {
 #ifdef INET
 		case PFSE_IP: {
-			if (pf_isforlocal(pfse->pfse_m, AF_INET)) {
-				KASSERT(pfse->pfse_m->m_pkthdr.rcvif == V_loif,
-				    ("%s: rcvif != loif", __func__));
-
-				pfse->pfse_m->m_flags |= M_SKIP_FIREWALL;
-				pfse->pfse_m->m_pkthdr.csum_flags |=
-				    CSUM_IP_VALID | CSUM_IP_CHECKED |
-				    CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
-				pfse->pfse_m->m_pkthdr.csum_data = 0xffff;
-				ip_input(pfse->pfse_m);
-			} else {
-				ip_output(pfse->pfse_m, NULL, NULL, 0, NULL,
-				    NULL);
-			}
+			pf_send_ip_direct(pfse->pfse_m);
 			break;
 		}
 		case PFSE_ICMP:
@@ -2487,20 +2511,7 @@ pf_intr(void *v)
 #endif /* INET */
 #ifdef INET6
 		case PFSE_IP6:
-			if (pf_isforlocal(pfse->pfse_m, AF_INET6)) {
-				KASSERT(pfse->pfse_m->m_pkthdr.rcvif == V_loif,
-				    ("%s: rcvif != loif", __func__));
-
-				pfse->pfse_m->m_flags |= M_SKIP_FIREWALL |
-				    M_LOOP;
-				pfse->pfse_m->m_pkthdr.csum_flags |=
-				    CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
-				pfse->pfse_m->m_pkthdr.csum_data = 0xffff;
-				ip6_input(pfse->pfse_m);
-			} else {
-				ip6_output(pfse->pfse_m, NULL, NULL, 0, NULL,
-				    NULL, NULL);
-			}
+			pf_send_ip6_direct(pfse->pfse_m);
 			break;
 		case PFSE_ICMP6:
 			icmp6_error(pfse->pfse_m, pfse->icmpopts.type,
diff --git a/sys/netpfil/pf/pf.h b/sys/netpfil/pf/pf.h
index f8646b782fce..edb3bf265afd 100644
--- a/sys/netpfil/pf/pf.h
+++ b/sys/netpfil/pf/pf.h
@@ -737,5 +737,12 @@ RB_PROTOTYPE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare);
 RB_PROTOTYPE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare);
 
 int	 pf_get_ruleset_number(u_int8_t);
-
+#ifdef _KERNEL
+#ifdef INET
+void pf_send_ip_direct(struct mbuf *m);
+#endif
+#ifdef INET6
+void pf_send_ip6_direct(struct mbuf *m);
+#endif
+#endif	/* _KERNEL */
 #endif	/* _NET_PF_H_ */
diff --git a/sys/netpfil/pf/pf_syncookies.c b/sys/netpfil/pf/pf_syncookies.c
index 4a935bc65767..debf3fc29a8a 100644
--- a/sys/netpfil/pf/pf_syncookies.c
+++ b/sys/netpfil/pf/pf_syncookies.c
@@ -60,6 +60,9 @@
 
 //#include "pflog.h"
 
+#include "opt_inet.h"
+#include "opt_inet6.h"
+
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/mbuf.h>
@@ -80,11 +83,16 @@
 #include <net/route.h>
 
 #include <netinet/in.h>
+#include <netinet/in_var.h>
 #include <netinet/in_pcb.h>
 #include <netinet/ip.h>
+#include <netinet/ip_var.h>
 #include <netinet/tcp.h>
 #include <netinet/tcp_var.h>
 
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+
 #include <net/pfvar.h>
 #include <netpfil/pf/pf_nv.h>
 
@@ -291,13 +299,30 @@ pf_syncookie_send(struct pf_pdesc *pd)
 {
 	uint16_t	mss;
 	uint32_t	iss;
+	struct mbuf 	*m;
 
 	mss = max(V_tcp_mssdflt, pf_get_mss(pd));
 	iss = pf_syncookie_generate(pd, mss);
-	pf_send_tcp(NULL, pd->af, pd->dst, pd->src, *pd->dport, *pd->sport,
-	    iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN|TH_ACK, 0, mss,
-	    0, M_SKIP_FIREWALL | (pd->m->m_flags & M_LOOP), 0, 0,
+	m = pf_build_tcp(NULL, pd->af, pd->dst, pd->src, *pd->dport, *pd->sport,
+	    iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN | TH_ACK, 0, mss, 0,
+	    M_SKIP_FIREWALL | (pd->m->m_flags & M_LOOP), 0, 0, 0,
 	    pd->act.rtableid);
+	if (m == NULL)
+		return;
+	switch (pd->af) {
+#ifdef INET
+	case AF_INET:
+		pf_send_ip_direct(m);
+		break;
+#endif /* INET */
+#ifdef INET6
+	case AF_INET6:
+		pf_send_ip6_direct(m);
+		break;
+#endif /* INET6 */
+	default:
+		unhandled_af(pd->af);
+	}
 	counter_u64_add(V_pf_status.lcounters[KLCNT_SYNCOOKIES_SENT], 1);
 	/* XXX Maybe only in adaptive mode? */
 	atomic_add_64(&V_pf_status.syncookies_inflight[V_pf_syncookie_status.oddeven],