git: dc23abfdea97 - stable/13 - pf: implement adaptive mode

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 06 Oct 2021 08:47:27 UTC
The branch stable/13 has been updated by kp:

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

commit dc23abfdea971252ad4041a750167366d8aed0df
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2021-07-24 11:59:34 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2021-10-06 08:46:53 +0000

    pf: implement adaptive mode
    
    Use atomic counters to ensure that we correctly track the number of half
    open states and syncookie responses in-flight.
    This determines if we activate or deactivate syncookies in adaptive
    mode.
    
    MFC after:      1 week
    Sponsored by:   Modirum MDPay
    Differential Revision:  https://reviews.freebsd.org/D32134
    
    (cherry picked from commit bf8637181a2bb81206ff8c685f1632d07b8feb13)
---
 sys/net/pfvar.h                |  5 ++++-
 sys/netpfil/pf/pf.c            | 15 +++++++++++++
 sys/netpfil/pf/pf_syncookies.c | 51 +++++++++++++++++++++++++++++++++++++-----
 3 files changed, 65 insertions(+), 6 deletions(-)

diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 1eee2ec36351..b8267e43c0c4 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1369,7 +1369,8 @@ struct pf_pdesc {
 enum pf_syncookies_mode {
 	PF_SYNCOOKIES_NEVER = 0,
 	PF_SYNCOOKIES_ALWAYS = 1,
-	PF_SYNCOOKIES_MODE_MAX = PF_SYNCOOKIES_ALWAYS
+	PF_SYNCOOKIES_ADAPTIVE = 2,
+	PF_SYNCOOKIES_MODE_MAX = PF_SYNCOOKIES_ADAPTIVE
 };
 
 #ifdef _KERNEL
@@ -1389,6 +1390,8 @@ struct pf_kstatus {
 	bool		keep_counters;
 	enum pf_syncookies_mode	syncookies_mode;
 	bool		syncookies_active;
+	uint64_t	syncookies_inflight[2];
+	uint32_t	states_halfopen;
 };
 #endif
 
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 20e775148b7a..90c856ce5fcf 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -493,6 +493,15 @@ pf_set_protostate(struct pf_kstate *s, int which, u_int8_t newstate)
 		s->dst.state = newstate;
 	if (which == PF_PEER_DST)
 		return;
+	if (s->src.state == newstate)
+		return;
+	if (s->creatorid == V_pf_status.hostid &&
+	    s->key[PF_SK_STACK] != NULL &&
+	    s->key[PF_SK_STACK]->proto == IPPROTO_TCP &&
+	    !(TCPS_HAVEESTABLISHED(s->src.state) ||
+	    s->src.state == TCPS_CLOSED) &&
+	    (TCPS_HAVEESTABLISHED(newstate) || newstate == TCPS_CLOSED))
+		atomic_add_32(&V_pf_status.states_halfopen, -1);
 
 	s->src.state = newstate;
 }
@@ -1924,6 +1933,11 @@ pf_unlink_state(struct pf_kstate *s, u_int flags)
 
 	s->timeout = PFTM_UNLINKED;
 
+	/* Ensure we remove it from the list of halfopen states, if needed. */
+	if (s->key[PF_SK_STACK] != NULL &&
+	    s->key[PF_SK_STACK]->proto == IPPROTO_TCP)
+		pf_set_protostate(s, PF_PEER_BOTH, TCPS_CLOSED);
+
 	PF_HASHROW_UNLOCK(ih);
 
 	pf_detach_state(s);
@@ -4014,6 +4028,7 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
 		pf_set_protostate(s, PF_PEER_SRC, TCPS_SYN_SENT);
 		pf_set_protostate(s, PF_PEER_DST, TCPS_CLOSED);
 		s->timeout = PFTM_TCP_FIRST_PACKET;
+		atomic_add_32(&V_pf_status.states_halfopen, 1);
 		break;
 	case IPPROTO_UDP:
 		pf_set_protostate(s, PF_PEER_SRC, PFUDPS_SINGLE);
diff --git a/sys/netpfil/pf/pf_syncookies.c b/sys/netpfil/pf/pf_syncookies.c
index 4eabbb5e2744..11093b636777 100644
--- a/sys/netpfil/pf/pf_syncookies.c
+++ b/sys/netpfil/pf/pf_syncookies.c
@@ -106,6 +106,8 @@ struct pf_syncookie_status {
 	struct callout	keytimeout;
 	uint8_t		oddeven;
 	uint8_t		key[2][SIPHASH_KEY_LENGTH];
+	uint32_t	hiwat;	/* absolute; # of states */
+	uint32_t	lowat;
 };
 VNET_DEFINE_STATIC(struct pf_syncookie_status, pf_syncookie_status);
 #define V_pf_syncookie_status	VNET(pf_syncookie_status)
@@ -242,7 +244,24 @@ pf_synflood_check(struct pf_pdesc *pd)
 	if (pd->pf_mtag && (pd->pf_mtag->tag & PF_TAG_SYNCOOKIE_RECREATED))
 		return (0);
 
-	return (V_pf_status.syncookies_mode);
+	if (V_pf_status.syncookies_mode != PF_SYNCOOKIES_ADAPTIVE)
+		return (V_pf_status.syncookies_mode);
+
+	if (!V_pf_status.syncookies_active &&
+	    atomic_load_32(&V_pf_status.states_halfopen) >
+	    V_pf_syncookie_status.hiwat) {
+		/* We'd want to 'pf_syncookie_newkey()' here, but that requires
+		 * the rules write lock, which we can't get with the read lock
+		 * held. */
+		callout_reset(&V_pf_syncookie_status.keytimeout, 0,
+		    pf_syncookie_rotate, curvnet);
+		V_pf_status.syncookies_active = true;
+		DPFPRINTF(LOG_WARNING,
+		    ("synflood detected, enabling syncookies\n"));
+		// XXXTODO V_pf_status.lcounters[LCNT_SYNFLOODS]++;
+	}
+
+	return (V_pf_status.syncookies_active);
 }
 
 void
@@ -257,6 +276,9 @@ pf_syncookie_send(struct mbuf *m, int off, struct pf_pdesc *pd)
 	    iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN|TH_ACK, 0, mss,
 	    0, 1, 0);
 	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],
+	    1);
 }
 
 uint8_t
@@ -272,11 +294,17 @@ pf_syncookie_validate(struct pf_pdesc *pd)
 	ack = ntohl(pd->hdr.tcp.th_ack) - 1;
 	cookie.cookie = (ack & 0xff) ^ (ack >> 24);
 
+	/* we don't know oddeven before setting the cookie (union) */
+        if (atomic_load_64(&V_pf_status.syncookies_inflight[cookie.flags.oddeven])
+	    == 0)
+                return (0);
+
 	hash = pf_syncookie_mac(pd, cookie, seq);
 	if ((ack & ~0xff) != (hash & ~0xff))
 		return (0);
 
 	counter_u64_add(V_pf_status.lcounters[KLCNT_SYNCOOKIES_VALID], 1);
+	atomic_add_64(&V_pf_status.syncookies_inflight[cookie.flags.oddeven], -1);
 
 	return (1);
 }
@@ -290,13 +318,22 @@ pf_syncookie_rotate(void *arg)
 	CURVNET_SET((struct vnet *)arg);
 
 	/* do we want to disable syncookies? */
-	if (V_pf_status.syncookies_active) {
+	if (V_pf_status.syncookies_active &&
+	    ((V_pf_status.syncookies_mode == PF_SYNCOOKIES_ADAPTIVE &&
+	    (atomic_load_32(&V_pf_status.states_halfopen) +
+	    atomic_load_64(&V_pf_status.syncookies_inflight[0]) +
+	    atomic_load_64(&V_pf_status.syncookies_inflight[1])) <
+	    V_pf_syncookie_status.lowat) ||
+	    V_pf_status.syncookies_mode == PF_SYNCOOKIES_NEVER)
+			) {
 		V_pf_status.syncookies_active = false;
-		DPFPRINTF(PF_DEBUG_MISC, ("syncookies disabled"));
+		DPFPRINTF(PF_DEBUG_MISC, ("syncookies disabled\n"));
 	}
 
 	/* nothing in flight any more? delete keys and return */
-	if (!V_pf_status.syncookies_active) {
+	if (!V_pf_status.syncookies_active &&
+	    atomic_load_64(&V_pf_status.syncookies_inflight[0]) == 0 &&
+	    atomic_load_64(&V_pf_status.syncookies_inflight[1]) == 0) {
 		memset(V_pf_syncookie_status.key[0], 0,
 		    PF_SYNCOOKIE_SECRET_SIZE);
 		memset(V_pf_syncookie_status.key[1], 0,
@@ -305,8 +342,10 @@ pf_syncookie_rotate(void *arg)
 		return;
 	}
 
+	PF_RULES_WLOCK();
 	/* new key, including timeout */
 	pf_syncookie_newkey();
+	PF_RULES_WUNLOCK();
 
 	CURVNET_RESTORE();
 }
@@ -316,11 +355,13 @@ pf_syncookie_newkey(void)
 {
 	PF_RULES_WASSERT();
 
+	MPASS(V_pf_syncookie_status.oddeven < 2);
 	V_pf_syncookie_status.oddeven = (V_pf_syncookie_status.oddeven + 1) & 0x1;
+	atomic_store_64(&V_pf_status.syncookies_inflight[V_pf_syncookie_status.oddeven], 0);
 	arc4random_buf(V_pf_syncookie_status.key[V_pf_syncookie_status.oddeven],
 	    PF_SYNCOOKIE_SECRET_SIZE);
 	callout_reset(&V_pf_syncookie_status.keytimeout,
-	    PF_SYNCOOKIE_SECRET_LIFETIME, pf_syncookie_rotate, curvnet);
+	    PF_SYNCOOKIE_SECRET_LIFETIME * hz, pf_syncookie_rotate, curvnet);
 }
 
 /*