git: 7d6def07e03e - stable/12 - pfctl: userspace adaptive syncookies configration

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

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

commit 7d6def07e03e9e94f5c1f42f173e12c9c5effb2a
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2021-08-13 11:42:59 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2021-10-06 08:46:58 +0000

    pfctl: userspace adaptive syncookies configration
    
    Hook up the userspace bits to configure syncookies in adaptive mode.
    
    MFC after:      1 week
    Sponsored by:   Modirum MDPay
    Differential Revision:  https://reviews.freebsd.org/D32136
    
    (cherry picked from commit 5062afff9de7e67da96e3f0dcb9d8bbd5a4e1c5b)
---
 lib/libpfctl/libpfctl.c   | 55 ++++++++++++++++++++++++++++++++++++++++++-----
 lib/libpfctl/libpfctl.h   |  6 +++++-
 sbin/pfctl/parse.y        | 43 ++++++++++++++++++++++++++++++++++--
 sbin/pfctl/pfctl.c        | 51 ++++++++++++++++++++++++++++++++++++++++++-
 sbin/pfctl/pfctl_parser.c |  5 +++--
 sbin/pfctl/pfctl_parser.h |  8 +++++++
 sys/net/pfvar.h           |  3 +++
 7 files changed, 160 insertions(+), 11 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 3d52502f9ba8..c2d57d8136ca 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -50,6 +50,12 @@
 
 #include "libpfctl.h"
 
+const char* PFCTL_SYNCOOKIES_MODE_NAMES[] = {
+	"never",
+	"always",
+	"adaptive"
+};
+
 static int	_pfctl_clear_states(int , const struct pfctl_kill *,
 		    unsigned int *, uint64_t);
 
@@ -932,17 +938,40 @@ pfctl_kill_states(int dev, const struct pfctl_kill *kill, unsigned int *killed)
 	return (_pfctl_clear_states(dev, kill, killed, DIOCKILLSTATESNV));
 }
 
+static int
+pfctl_get_limit(int dev, const int index, u_int *limit)
+{
+	struct pfioc_limit pl;
+
+	bzero(&pl, sizeof(pl));
+	pl.index = index;
+
+	if (ioctl(dev, DIOCGETLIMIT, &pl) == -1)
+		return (errno);
+
+	*limit = pl.limit;
+
+	return (0);
+}
+
 int
 pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s)
 {
 	struct pfioc_nv	 nv;
 	nvlist_t	*nvl;
 	int		 ret;
+	u_int		 state_limit;
+
+	ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit);
+	if (ret != 0)
+		return (ret);
 
 	nvl = nvlist_create(0);
 
 	nvlist_add_bool(nvl, "enabled", s->mode != PFCTL_SYNCOOKIES_NEVER);
-	nvlist_add_bool(nvl, "adaptive", false); /* XXX TODO */
+	nvlist_add_bool(nvl, "adaptive", s->mode == PFCTL_SYNCOOKIES_ADAPTIVE);
+	nvlist_add_number(nvl, "highwater", state_limit * s->highwater / 100);
+	nvlist_add_number(nvl, "lowwater", state_limit * s->lowwater / 100);
 
 	nv.data = nvlist_pack(nvl, &nv.len);
 	nv.size = nv.len;
@@ -960,12 +989,18 @@ pfctl_get_syncookies(int dev, struct pfctl_syncookies *s)
 {
 	struct pfioc_nv	 nv;
 	nvlist_t	*nvl;
-	bool		enabled, adaptive;
+	int		 ret;
+	u_int		 state_limit;
+	bool		 enabled, adaptive;
+
+	ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit);
+	if (ret != 0)
+		return (ret);
 
 	bzero(s, sizeof(*s));
 
-	nv.data = malloc(128);
-	nv.len = nv.size = 128;
+	nv.data = malloc(256);
+	nv.len = nv.size = 256;
 
 	if (ioctl(dev, DIOCGETSYNCOOKIES, &nv)) {
 		free(nv.data);
@@ -981,7 +1016,17 @@ pfctl_get_syncookies(int dev, struct pfctl_syncookies *s)
 	enabled = nvlist_get_bool(nvl, "enabled");
 	adaptive = nvlist_get_bool(nvl, "adaptive");
 
-	s->mode = enabled ? PFCTL_SYNCOOKIES_ALWAYS : PFCTL_SYNCOOKIES_NEVER;
+	if (enabled) {
+		if (adaptive)
+			s->mode = PFCTL_SYNCOOKIES_ADAPTIVE;
+		else
+			s->mode = PFCTL_SYNCOOKIES_ALWAYS;
+	} else {
+		s->mode = PFCTL_SYNCOOKIES_NEVER;
+	}
+
+	s->highwater = nvlist_get_number(nvl, "highwater") * 100 / state_limit;
+	s->lowwater = nvlist_get_number(nvl, "lowwater") * 100 / state_limit;
 
 	nvlist_destroy(nvl);
 
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 70de7627f0a6..70c144772c02 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -273,11 +273,15 @@ struct pfctl_states {
 
 enum pfctl_syncookies_mode {
 	PFCTL_SYNCOOKIES_NEVER,
-	PFCTL_SYNCOOKIES_ALWAYS
+	PFCTL_SYNCOOKIES_ALWAYS,
+	PFCTL_SYNCOOKIES_ADAPTIVE
 };
+extern const char* PFCTL_SYNCOOKIES_MODE_NAMES[];
 
 struct pfctl_syncookies {
 	enum pfctl_syncookies_mode	mode;
+	uint8_t				highwater;	/* Percent */
+	uint8_t				lowwater;	/* Percent */
 };
 
 struct pfctl_status* pfctl_get_status(int dev);
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 0320cdc7ec00..2bfb1e651d65 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -317,6 +317,7 @@ static struct codel_opts	 codel_opts;
 static struct node_hfsc_opts	 hfsc_opts;
 static struct node_fairq_opts	 fairq_opts;
 static struct node_state_opt	*keep_state_defaults = NULL;
+static struct pfctl_watermarks	 syncookie_opts;
 
 int		 disallow_table(struct node_host *, const char *);
 int		 disallow_urpf_failed(struct node_host *, const char *);
@@ -442,6 +443,7 @@ typedef struct {
 		struct node_hfsc_opts	 hfsc_opts;
 		struct node_fairq_opts	 fairq_opts;
 		struct codel_opts	 codel_opts;
+		struct pfctl_watermarks	*watermarks;
 	} v;
 	int lineno;
 } YYSTYPE;
@@ -527,6 +529,7 @@ int	parseport(char *, struct range *r, int);
 %type	<v.pool_opts>		pool_opts pool_opt pool_opts_l
 %type	<v.tagged>		tagged
 %type	<v.rtableid>		rtable
+%type	<v.watermarks>		syncookie_opts
 %%
 
 ruleset		: /* empty */
@@ -725,14 +728,19 @@ option		: SET OPTIMIZATION STRING		{
 		| SET KEEPCOUNTERS {
 			pf->keep_counters = true;
 		}
-		| SET SYNCOOKIES syncookie_val {
-			pf->syncookies = $3;
+		| SET SYNCOOKIES syncookie_val syncookie_opts {
+			if (pfctl_cfg_syncookies(pf, $3, $4)) {
+				yyerror("error setting syncookies");
+				YYERROR;
+			}
 		}
 		;
 
 syncookie_val  : STRING        {
 			if (!strcmp($1, "never"))
 				$$ = PFCTL_SYNCOOKIES_NEVER;
+			else if (!strcmp($1, "adaptive"))
+				$$ = PFCTL_SYNCOOKIES_ADAPTIVE;
 			else if (!strcmp($1, "always"))
 				$$ = PFCTL_SYNCOOKIES_ALWAYS;
 			else {
@@ -741,6 +749,37 @@ syncookie_val  : STRING        {
 			}
 		}
 		;
+syncookie_opts  : /* empty */                   { $$ = NULL; }
+		| {
+			memset(&syncookie_opts, 0, sizeof(syncookie_opts));
+		  } '(' syncookie_opt_l ')'     { $$ = &syncookie_opts; }
+		;
+
+syncookie_opt_l : syncookie_opt_l comma syncookie_opt
+		| syncookie_opt
+		;
+
+syncookie_opt   : STRING STRING {
+			double   val;
+			char    *cp;
+
+			val = strtod($2, &cp);
+			if (cp == NULL || strcmp(cp, "%"))
+				YYERROR;
+			if (val <= 0 || val > 100) {
+				yyerror("illegal percentage value");
+				YYERROR;
+			}
+			if (!strcmp($1, "start")) {
+				syncookie_opts.hi = val;
+			} else if (!strcmp($1, "end")) {
+				syncookie_opts.lo = val;
+			} else {
+				yyerror("illegal syncookie option");
+				YYERROR;
+			}
+		}
+		;
 
 stringall	: STRING	{ $$ = $1; }
 		| ALL		{
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index 8f3698e398f6..d7bde0012e9b 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1812,6 +1812,10 @@ pfctl_init_options(struct pfctl *pf)
 	pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT;
 
 	pf->debug = PF_DEBUG_URGENT;
+
+	pf->syncookies = false;
+	pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT;
+	pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT;
 }
 
 int
@@ -2069,7 +2073,9 @@ pfctl_load_syncookies(struct pfctl *pf, u_int8_t val)
 
 	bzero(&cookies, sizeof(cookies));
 
-	cookies.mode = val ? PFCTL_SYNCOOKIES_ALWAYS : PFCTL_SYNCOOKIES_NEVER;
+	cookies.mode = val;
+	cookies.lowwater = pf->syncookieswat[0];
+	cookies.highwater = pf->syncookieswat[1];
 
 	if (pfctl_set_syncookies(dev, &cookies)) {
 		warnx("DIOCSETSYNCOOKIES");
@@ -2078,6 +2084,49 @@ pfctl_load_syncookies(struct pfctl *pf, u_int8_t val)
 	return (0);
 }
 
+int
+pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w)
+{
+	if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) {
+		warnx("syncookies start/end only apply to adaptive");
+		return (1);
+	}
+	if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) {
+		if (!w->hi)
+			w->hi = PF_SYNCOOKIES_HIWATPCT;
+		if (!w->lo)
+			w->lo = w->hi / 2;
+		if (w->lo >= w->hi) {
+			warnx("start must be higher than end");
+			return (1);
+		}
+		pf->syncookieswat[0] = w->lo;
+		pf->syncookieswat[1] = w->hi;
+		pf->syncookieswat_set = 1;
+	}
+
+	if (pf->opts & PF_OPT_VERBOSE) {
+		if (val == PF_SYNCOOKIES_NEVER)
+			printf("set syncookies never\n");
+		else if (val == PF_SYNCOOKIES_ALWAYS)
+			printf("set syncookies always\n");
+		else if (val == PF_SYNCOOKIES_ADAPTIVE) {
+			if (pf->syncookieswat_set)
+				printf("set syncookies adaptive (start %u%%, "
+				    "end %u%%)\n", pf->syncookieswat[1],
+				    pf->syncookieswat[0]);
+			else
+				printf("set syncookies adaptive\n");
+		} else {        /* cannot happen */
+			warnx("king bula ate all syncookies");
+			return (1);
+		}
+	}
+
+	pf->syncookies = val;
+	return (0);
+}
+
 int
 pfctl_set_debug(struct pfctl *pf, char *d)
 {
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 24931f023431..dc4a2254d733 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
 #include <net/pfvar.h>
 #include <arpa/inet.h>
 
+#include <assert.h>
 #include <search.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -618,9 +619,9 @@ print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts)
 		}
 
 		printf("Syncookies\n");
+		assert(cookies->mode <= PFCTL_SYNCOOKIES_ADAPTIVE);
 		printf("  %-25s %s\n", "mode",
-		    cookies->mode == PFCTL_SYNCOOKIES_NEVER ?
-		    "never" : "always");
+		    PFCTL_SYNCOOKIES_MODE_NAMES[cookies->mode]);
 	}
 }
 
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 12a66e1ae710..484830c61791 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -101,6 +101,8 @@ struct pfctl {
 	char		*ifname;
 	bool		 keep_counters;
 	u_int8_t	 syncookies;
+	u_int8_t	 syncookieswat[2];	/* lowat, highwat, in % */
+	u_int8_t	 syncookieswat_set;
 
 	u_int8_t	 timeout_set[PFTM_MAX];
 	u_int8_t	 limit_set[PF_LIMIT_MAX];
@@ -200,6 +202,11 @@ struct pfctl_altq {
 	} meta;
 };
 
+struct pfctl_watermarks {
+	uint32_t	hi;
+	uint32_t	lo;
+};
+
 #ifdef __FreeBSD__
 /*
  * XXX
@@ -270,6 +277,7 @@ int	pfctl_set_logif(struct pfctl *, char *);
 int	pfctl_set_hostid(struct pfctl *, u_int32_t);
 int	pfctl_set_debug(struct pfctl *, char *);
 int	pfctl_set_interface_flags(struct pfctl *, char *, int, int);
+int	pfctl_cfg_syncookies(struct pfctl *, uint8_t, struct pfctl_watermarks *);
 
 int	parse_config(char *, struct pfctl *);
 int	parse_flags(char *);
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 5e2022ae8828..b6a1f779c073 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1373,6 +1373,9 @@ enum pf_syncookies_mode {
 	PF_SYNCOOKIES_MODE_MAX = PF_SYNCOOKIES_ADAPTIVE
 };
 
+#define	PF_SYNCOOKIES_HIWATPCT	25
+#define	PF_SYNCOOKIES_LOWATPCT	(PF_SYNCOOKIES_HIWATPCT / 2)
+
 #ifdef _KERNEL
 struct pf_kstatus {
 	counter_u64_t	counters[PFRES_MAX]; /* reason for passing/dropping */