git: 39282ef356db - main - pf: backport OpenBSD syntax of "scrub" option for "match" and "pass" rules

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 14 Apr 2023 07:52:49 UTC
The branch main has been updated by kp:

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

commit 39282ef356db25879660427de8607716a8439077
Author:     Kajetan Staszkiewicz <vegeta@tuxpowered.net>
AuthorDate: 2023-04-13 16:12:59 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-04-14 07:04:06 +0000

    pf: backport OpenBSD syntax of "scrub" option for "match" and "pass" rules
    
    Introduce the OpenBSD syntax of "scrub" option for "match" and "pass"
    rules and the "set reassemble" flag. The patch is backward-compatible,
    pf.conf can be still written in FreeBSD-style.
    
    Obtained from:  OpenBSD
    MFC after:      never
    Sponsored by:   InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D38025
---
 lib/libpfctl/libpfctl.c        |   1 +
 lib/libpfctl/libpfctl.h        |   3 +-
 sbin/pfctl/parse.y             | 119 ++++++++++++----
 sbin/pfctl/pf_print_state.c    |  16 +++
 sbin/pfctl/pfctl.c             |  39 ++++++
 sbin/pfctl/pfctl_parser.c      |  58 ++++++--
 sbin/pfctl/pfctl_parser.h      |   3 +
 share/man/man5/pf.conf.5       |  11 +-
 sys/net/pfvar.h                |  64 ++++++---
 sys/netpfil/pf/if_pfsync.c     |  25 +++-
 sys/netpfil/pf/pf.c            | 305 ++++++++++++++++++++++++++++++-----------
 sys/netpfil/pf/pf.h            |  61 ++++++---
 sys/netpfil/pf/pf_ioctl.c      |  28 +++-
 sys/netpfil/pf/pf_norm.c       | 154 ++++++++++++++-------
 sys/netpfil/pf/pf_syncookies.c |   4 +-
 15 files changed, 675 insertions(+), 216 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index f34f83cfb57c..c75f9ab12889 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -225,6 +225,7 @@ pfctl_get_status(int dev)
 	status->states = nvlist_get_number(nvl, "states");
 	status->src_nodes = nvlist_get_number(nvl, "src_nodes");
 	status->syncookies_active = nvlist_get_bool(nvl, "syncookies_active");
+	status->reass = nvlist_get_number(nvl, "reass");
 
 	strlcpy(status->ifname, nvlist_get_string(nvl, "ifname"),
 	    IFNAMSIZ);
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 684e73c1815d..1a07b74dc10f 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -58,6 +58,7 @@ struct pfctl_status {
 	char		ifname[IFNAMSIZ];
 	uint8_t		pf_chksum[PF_MD5_DIGEST_LENGTH];
 	bool		syncookies_active;
+	uint32_t	reass;
 
 	struct pfctl_status_counters	 counters;
 	struct pfctl_status_counters	 lcounters;
@@ -347,7 +348,7 @@ struct pfctl_state {
 	uint32_t		 creation;
 	uint32_t		 expire;
 	uint32_t		 pfsync_time;
-	uint8_t			 state_flags;
+	uint16_t		 state_flags;
 	uint32_t		 sync_flags;
 };
 
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 6f9494828d53..e5629f9fcd5f 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -225,13 +225,21 @@ struct node_qassign {
 
 static struct filter_opts {
 	int			 marker;
-#define FOM_FLAGS	0x01
-#define FOM_ICMP	0x02
-#define FOM_TOS		0x04
-#define FOM_KEEP	0x08
-#define FOM_SRCTRACK	0x10
+#define FOM_FLAGS	0x0001
+#define FOM_ICMP	0x0002
+#define FOM_TOS		0x0004
+#define FOM_KEEP	0x0008
+#define FOM_SRCTRACK	0x0010
+#define FOM_MINTTL	0x0020
+#define FOM_MAXMSS	0x0040
+#define FOM_AFTO	0x0080 /* not yet implemmented */
+#define FOM_SETTOS	0x0100
+#define FOM_SCRUB_TCP	0x0200
 #define FOM_SETPRIO	0x0400
+#define FOM_ONCE	0x1000 /* not yet implemmented */
 #define FOM_PRIO	0x2000
+#define FOM_SETDELAY	0x4000
+#define FOM_FRAGCACHE	0x8000 /* does not exist in OpenBSD */
 	struct node_uid		*uid;
 	struct node_gid		*gid;
 	struct {
@@ -266,6 +274,12 @@ static struct filter_opts {
 		struct node_host	*addr;
 		u_int16_t		port;
 	}			 divert;
+	/* new-style scrub opts */
+	int			 nodf;
+	int			 minttl;
+	int			 settos;
+	int			 randomid;
+	int			 max_mss;
 } filter_opts;
 
 static struct antispoof_opts {
@@ -277,10 +291,6 @@ static struct antispoof_opts {
 
 static struct scrub_opts {
 	int			 marker;
-#define SOM_MINTTL	0x01
-#define SOM_MAXMSS	0x02
-#define SOM_FRAGCACHE	0x04
-#define SOM_SETTOS	0x08
 	int			 nodf;
 	int			 minttl;
 	int			 maxmss;
@@ -511,7 +521,7 @@ int	parseport(char *, struct range *r, int);
 %token	<v.i>			PORTBINARY
 %type	<v.interface>		interface if_list if_item_not if_item
 %type	<v.number>		number icmptype icmp6type uid gid
-%type	<v.number>		tos not yesno
+%type	<v.number>		tos not yesno optnodf
 %type	<v.probability>		probability
 %type	<v.i>			no dir af fragcache optimizer syncookie_val
 %type	<v.i>			sourcetrack flush unaryop statelock
@@ -631,7 +641,16 @@ optimizer	: string	{
 		}
 		;
 
-option		: SET OPTIMIZATION STRING		{
+optnodf		: /* empty */	{ $$ = 0; }
+		| NODF		{ $$ = 1; }
+		;
+
+option		: SET REASSEMBLE yesno optnodf		{
+			if (check_rulestate(PFCTL_STATE_OPTION))
+				YYERROR;
+			pfctl_set_reassembly(pf, $3, $4);
+		}
+		| SET OPTIMIZATION STRING		{
 			if (check_rulestate(PFCTL_STATE_OPTION)) {
 				free($3);
 				YYERROR;
@@ -1408,7 +1427,7 @@ scrubrule	: scrubaction dir logquick interface af proto fromto scrub_opts
 				r.min_ttl = $8.minttl;
 			if ($8.maxmss)
 				r.max_mss = $8.maxmss;
-			if ($8.marker & SOM_SETTOS) {
+			if ($8.marker & FOM_SETTOS) {
 				r.rule_flag |= PFRULE_SET_TOS;
 				r.set_tos = $8.settos;
 			}
@@ -1443,7 +1462,7 @@ scrub_opts	:	{
 		}
 		;
 
-scrub_opts_l	: scrub_opts_l scrub_opt
+scrub_opts_l	: scrub_opts_l comma scrub_opt
 		| scrub_opt
 		;
 
@@ -1455,7 +1474,7 @@ scrub_opt	: NODF	{
 			scrub_opts.nodf = 1;
 		}
 		| MINTTL NUMBER {
-			if (scrub_opts.marker & SOM_MINTTL) {
+			if (scrub_opts.marker & FOM_MINTTL) {
 				yyerror("min-ttl cannot be respecified");
 				YYERROR;
 			}
@@ -1463,11 +1482,11 @@ scrub_opt	: NODF	{
 				yyerror("illegal min-ttl value %d", $2);
 				YYERROR;
 			}
-			scrub_opts.marker |= SOM_MINTTL;
+			scrub_opts.marker |= FOM_MINTTL;
 			scrub_opts.minttl = $2;
 		}
 		| MAXMSS NUMBER {
-			if (scrub_opts.marker & SOM_MAXMSS) {
+			if (scrub_opts.marker & FOM_MAXMSS) {
 				yyerror("max-mss cannot be respecified");
 				YYERROR;
 			}
@@ -1475,23 +1494,23 @@ scrub_opt	: NODF	{
 				yyerror("illegal max-mss value %d", $2);
 				YYERROR;
 			}
-			scrub_opts.marker |= SOM_MAXMSS;
+			scrub_opts.marker |= FOM_MAXMSS;
 			scrub_opts.maxmss = $2;
 		}
 		| SETTOS tos {
-			if (scrub_opts.marker & SOM_SETTOS) {
+			if (scrub_opts.marker & FOM_SETTOS) {
 				yyerror("set-tos cannot be respecified");
 				YYERROR;
 			}
-			scrub_opts.marker |= SOM_SETTOS;
+			scrub_opts.marker |= FOM_SETTOS;
 			scrub_opts.settos = $2;
 		}
 		| fragcache {
-			if (scrub_opts.marker & SOM_FRAGCACHE) {
+			if (scrub_opts.marker & FOM_FRAGCACHE) {
 				yyerror("fragcache cannot be respecified");
 				YYERROR;
 			}
-			scrub_opts.marker |= SOM_FRAGCACHE;
+			scrub_opts.marker |= FOM_FRAGCACHE;
 			scrub_opts.fragcache = $1;
 		}
 		| REASSEMBLE STRING {
@@ -2351,6 +2370,21 @@ pfrule		: action dir logquick interface route af proto fromto
 			r.prob = $9.prob;
 			r.rtableid = $9.rtableid;
 
+			if ($9.nodf)
+				r.scrub_flags |= PFSTATE_NODF;
+			if ($9.randomid)
+				r.scrub_flags |= PFSTATE_RANDOMID;
+			if ($9.minttl)
+				r.min_ttl = $9.minttl;
+			if ($9.max_mss)
+				r.max_mss = $9.max_mss;
+			if ($9.marker & FOM_SETTOS) {
+				r.scrub_flags |= PFSTATE_SETTOS;
+				r.set_tos = $9.settos;
+			}
+			if ($9.marker & FOM_SCRUB_TCP)
+				r.scrub_flags |= PFSTATE_SCRUB_TCP;
+
 			if ($9.marker & FOM_PRIO) {
 				if ($9.prio == 0)
 					r.prio = PF_PRIO_ZERO;
@@ -2933,6 +2967,24 @@ filter_opt	: USER uids {
 			filter_opts.divert.port = 1;	/* some random value */
 #endif
 		}
+		| SCRUB '(' scrub_opts ')' {
+			filter_opts.nodf = $3.nodf;
+			filter_opts.minttl = $3.minttl;
+			if ($3.marker & FOM_SETTOS) {
+				/* Old style rules are "scrub set-tos 0x42"
+				 * New style are "set tos 0x42 scrub (...)"
+				 * What is in "scrub(...)"" is unfortunately the
+				 * original scrub syntax so it would overwrite
+				 * "set tos" of a pass/match rule.
+				 */
+				filter_opts.settos = $3.settos;
+			}
+			filter_opts.randomid = $3.randomid;
+			filter_opts.max_mss = $3.maxmss;
+			if ($3.reassemble_tcp)
+				filter_opts.marker |= FOM_SCRUB_TCP;
+			filter_opts.marker |= $3.marker;
+		}
 		| filter_sets
 		;
 
@@ -2953,6 +3005,14 @@ filter_set	: prio {
 			filter_opts.set_prio[0] = $1.b1;
 			filter_opts.set_prio[1] = $1.b2;
 		}
+		| TOS tos {
+			if (filter_opts.marker & FOM_SETTOS) {
+				yyerror("tos cannot be respecified");
+				YYERROR;
+			}
+			filter_opts.marker |= FOM_SETTOS;
+			filter_opts.settos = $2;
+		}
 prio		: PRIO NUMBER {
 			if ($2 < 0 || $2 > PF_PRIO_MAX) {
 				yyerror("prio must be 0 - %u", PF_PRIO_MAX);
@@ -5170,6 +5230,7 @@ rule_consistent(struct pfctl_rule *r, int anchor_call)
 
 	switch (r->action) {
 	case PF_PASS:
+	case PF_MATCH:
 	case PF_DROP:
 	case PF_SCRUB:
 	case PF_NOSCRUB:
@@ -5240,8 +5301,8 @@ filter_consistent(struct pfctl_rule *r, int anchor_call)
 		yyerror("max-src-nodes requires 'source-track rule'");
 		problems++;
 	}
-	if (r->action == PF_DROP && r->keep_state) {
-		yyerror("keep state on block rules doesn't make sense");
+	if (r->action != PF_PASS && r->keep_state) {
+		yyerror("keep state is great, but only for pass rules");
 		problems++;
 	}
 	if (r->rule_flag & PFRULE_STATESLOPPY &&
@@ -5251,6 +5312,18 @@ filter_consistent(struct pfctl_rule *r, int anchor_call)
 		    "synproxy state or modulate state");
 		problems++;
 	}
+	/* match rules rules */
+	if (r->action == PF_MATCH) {
+		if (r->divert.port) {
+			yyerror("divert is not supported on match rules");
+			problems++;
+		}
+		if (r->rt) {
+			yyerror("route-to, reply-to, dup-to and fastroute "
+			   "must not be used on match rules");
+			problems++;
+		}
+	}
 	return (-problems);
 }
 
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
index b66a296d6080..d23a0154b70d 100644
--- a/sbin/pfctl/pf_print_state.c
+++ b/sbin/pfctl/pf_print_state.c
@@ -339,8 +339,24 @@ print_state(struct pfctl_state *s, int opts)
 			printf(", anchor %u", s->anchor);
 		if (s->rule != -1)
 			printf(", rule %u", s->rule);
+		if (s->state_flags & PFSTATE_ALLOWOPTS)
+			printf(", allow-opts");
 		if (s->state_flags & PFSTATE_SLOPPY)
 			printf(", sloppy");
+		if (s->state_flags & PFSTATE_NOSYNC)
+			printf(", no-sync");
+		if (s->state_flags & PFSTATE_ACK)
+			printf(", psync-ack");
+		if (s->state_flags & PFSTATE_NODF)
+			printf(", no-df");
+		if (s->state_flags & PFSTATE_SETTOS)
+			printf(", set-tos");
+		if (s->state_flags & PFSTATE_RANDOMID)
+			printf(", random-id");
+		if (s->state_flags & PFSTATE_SCRUB_TCP)
+			printf(", scrub-tcp");
+		if (s->state_flags & PFSTATE_SETPRIO)
+			printf(", set-prio");
 		if (s->sync_flags & PFSYNC_FLAG_SRCNODE)
 			printf(", source-track");
 		if (s->sync_flags & PFSYNC_FLAG_NATSRCNODE)
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index f60e3f67e082..b5ef07064fb9 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -94,6 +94,7 @@ int	 pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int);
 int	 pfctl_load_debug(struct pfctl *, unsigned int);
 int	 pfctl_load_logif(struct pfctl *, char *);
 int	 pfctl_load_hostid(struct pfctl *, u_int32_t);
+int	 pfctl_load_reassembly(struct pfctl *, u_int32_t);
 int	 pfctl_load_syncookies(struct pfctl *, u_int8_t);
 int	 pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int,
 	    char *);
@@ -2258,6 +2259,7 @@ pfctl_init_options(struct pfctl *pf)
 	pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT;
 
 	pf->debug = PF_DEBUG_URGENT;
+	pf->reassemble = 0;
 
 	pf->syncookies = false;
 	pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT;
@@ -2318,6 +2320,11 @@ pfctl_load_options(struct pfctl *pf)
 		if (pfctl_load_hostid(pf, pf->hostid))
 			error = 1;
 
+	/* load reassembly settings */
+	if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set)
+		if (pfctl_load_reassembly(pf, pf->reassemble))
+			error = 1;
+
 	/* load keepcounters */
 	if (pfctl_set_keepcounters(pf->dev, pf->keep_counters))
 		error = 1;
@@ -2414,6 +2421,28 @@ pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds)
 	return (0);
 }
 
+int
+pfctl_set_reassembly(struct pfctl *pf, int on, int nodf)
+{
+	if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+		return (0);
+
+	pf->reass_set = 1;
+	if (on) {
+		pf->reassemble = PF_REASS_ENABLED;
+		if (nodf)
+			pf->reassemble |= PF_REASS_NODF;
+	} else {
+		pf->reassemble = 0;
+	}
+
+	if (pf->opts & PF_OPT_VERBOSE)
+		printf("set reassemble %s %s\n", on ? "yes" : "no",
+		    nodf ? "no-df" : "");
+
+	return (0);
+}
+
 int
 pfctl_set_optimization(struct pfctl *pf, const char *opt)
 {
@@ -2512,6 +2541,16 @@ pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid)
 	return (0);
 }
 
+int
+pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly)
+{
+	if (ioctl(dev, DIOCSETREASS, &reassembly)) {
+		warnx("DIOCSETREASS");
+		return (1);
+	}
+	return (0);
+}
+
 int
 pfctl_load_syncookies(struct pfctl *pf, u_int8_t val)
 {
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index fca1a7aa7b8f..15ed8c29abd0 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -429,6 +429,7 @@ print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2,
 			print_addr(&pooladdr->addr, af, 0);
 			break;
 		case PF_PASS:
+		case PF_MATCH:
 			if (PF_AZERO(&pooladdr->addr.v.a.addr, af))
 				printf("%s", pooladdr->ifname);
 			else {
@@ -624,6 +625,10 @@ print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts)
 		    PFCTL_SYNCOOKIES_MODE_NAMES[cookies->mode]);
 		printf("  %-25s %s\n", "active",
 		    s->syncookies_active ? "active" : "inactive");
+		printf("Reassemble %24s %s\n",
+		    s->reass & PF_REASS_ENABLED ? "yes" : "no",
+		    s->reass & PF_REASS_NODF ? "no-df" : ""
+		);
 	}
 }
 
@@ -685,6 +690,7 @@ print_src_node(struct pf_src_node *sn, int opts)
 				printf(", rdr rule %u", sn->rule.nr);
 			break;
 		case PF_PASS:
+		case PF_MATCH:
 			if (sn->rule.nr != -1)
 				printf(", filter rule %u", sn->rule.nr);
 			break;
@@ -810,7 +816,8 @@ void
 print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numeric)
 {
 	static const char *actiontypes[] = { "pass", "block", "scrub",
-	    "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr" };
+	    "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr",
+	    "", "", "match"};
 	static const char *anchortypes[] = { "anchor", "anchor", "anchor",
 	    "anchor", "nat-anchor", "nat-anchor", "binat-anchor",
 	    "binat-anchor", "rdr-anchor", "rdr-anchor" };
@@ -946,7 +953,7 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
 		print_flags(r->flags);
 		printf("/");
 		print_flags(r->flagset);
-	} else if (r->action == PF_PASS &&
+	} else if ((r->action == PF_PASS || r->action == PF_MATCH) &&
 	    (!r->proto || r->proto == IPPROTO_TCP) &&
 	    !(r->rule_flag & PFRULE_FRAGMENT) &&
 	    !anchor_call[0] && r->keep_state)
@@ -988,6 +995,10 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
 				    r->set_prio[1]);
 			comma = ",";
 		}
+		if (r->scrub_flags & PFSTATE_SETTOS) {
+			printf("%s tos 0x%2.2x", comma, r->set_tos);
+			comma = ",";
+		}
 		printf(" )");
 	}
 	if (!r->keep_state && r->action == PF_PASS && !anchor_call[0])
@@ -1113,26 +1124,43 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
 			}
 		printf(")");
 	}
-	if (r->rule_flag & PFRULE_FRAGMENT)
-		printf(" fragment");
-	if (r->rule_flag & PFRULE_NODF)
-		printf(" no-df");
-	if (r->rule_flag & PFRULE_RANDOMID)
-		printf(" random-id");
-	if (r->min_ttl)
-		printf(" min-ttl %d", r->min_ttl);
-	if (r->max_mss)
-		printf(" max-mss %d", r->max_mss);
-	if (r->rule_flag & PFRULE_SET_TOS)
-		printf(" set-tos 0x%2.2x", r->set_tos);
 	if (r->allow_opts)
 		printf(" allow-opts");
+	if (r->rule_flag & PFRULE_FRAGMENT)
+		printf(" fragment");
 	if (r->action == PF_SCRUB) {
+		/* Scrub flags for old-style scrub. */
+		if (r->rule_flag & PFRULE_NODF)
+			printf(" no-df");
+		if (r->rule_flag & PFRULE_RANDOMID)
+			printf(" random-id");
+		if (r->min_ttl)
+			printf(" min-ttl %d", r->min_ttl);
+		if (r->max_mss)
+			printf(" max-mss %d", r->max_mss);
+		if (r->rule_flag & PFRULE_SET_TOS)
+			printf(" set-tos 0x%2.2x", r->set_tos);
 		if (r->rule_flag & PFRULE_REASSEMBLE_TCP)
 			printf(" reassemble tcp");
-
+		/* The PFRULE_FRAGMENT_NOREASS is set on all rules by default! */
 		printf(" fragment %sreassemble",
 		    r->rule_flag & PFRULE_FRAGMENT_NOREASS ? "no " : "");
+	} else if (r->scrub_flags & PFSTATE_SCRUBMASK || r->min_ttl || r->max_mss) {
+		/* Scrub actions on normal rules. */
+		printf(" scrub(");
+		if (r->scrub_flags & PFSTATE_NODF)
+			printf(" no-df");
+		if (r->scrub_flags & PFSTATE_RANDOMID)
+			printf(" random-id");
+		if (r->min_ttl)
+			printf(" min-ttl %d", r->min_ttl);
+		if (r->scrub_flags & PFSTATE_SETTOS)
+			printf(" set-tos 0x%2.2x", r->set_tos);
+		if (r->scrub_flags & PFSTATE_SCRUB_TCP)
+			printf(" reassemble tcp");
+		if (r->max_mss)
+			printf(" max-mss %d", r->max_mss);
+		printf(")");
 	}
 	i = 0;
 	while (r->label[i][0])
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 13151cc33829..c5da3408fb93 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -101,6 +101,7 @@ struct pfctl {
 	u_int32_t	 limit[PF_LIMIT_MAX];
 	u_int32_t	 debug;
 	u_int32_t	 hostid;
+	u_int32_t	 reassemble;
 	char		*ifname;
 	bool		 keep_counters;
 	u_int8_t	 syncookies;
@@ -112,6 +113,7 @@ struct pfctl {
 	u_int8_t	 debug_set;
 	u_int8_t	 hostid_set;
 	u_int8_t	 ifname_set;
+	u_int8_t	 reass_set;
 };
 
 struct node_if {
@@ -285,6 +287,7 @@ void	pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *);
 void	pfctl_clear_pool(struct pfctl_pool *);
 
 int	pfctl_set_timeout(struct pfctl *, const char *, int, int);
+int	pfctl_set_reassembly(struct pfctl *, int, int);
 int	pfctl_set_optimization(struct pfctl *, const char *);
 int	pfctl_set_limit(struct pfctl *, const char *, unsigned int);
 int	pfctl_set_logif(struct pfctl *, char *);
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index ca7c64d1cf83..8e0bb42f257a 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -1489,6 +1489,15 @@ The packet is passed;
 state is created unless the
 .Ar no state
 option is specified.
+.It Ar match
+Action is unaltered, the previously matched rule's action still matters.
+Match rules apply queue and rtable assignments for every matched packet,
+subsequent matching pass or match rules can overwrite the assignment,
+if they don't specify a queue or an rtable, respectively, the previously
+set value remains.
+Additionally, match rules can contain log statements; the is logging done
+for each and every matching match rule, so it is possible to log a single
+packet multiple times.
 .El
 .Pp
 By default
@@ -3172,7 +3181,7 @@ schedulers     = ( cbq-def | priq-def | hfsc-def )
 bandwidth-spec = "number" ( "b" | "Kb" | "Mb" | "Gb" | "%" )
 
 etheraction    = "pass" | "block"
-action         = "pass" | "block" [ return ] | [ "no" ] "scrub"
+action         = "pass" | "match" | "block" [ return ] | [ "no" ] "scrub"
 return         = "drop" | "return" | "return-rst" [ "( ttl" number ")" ] |
                  "return-icmp" [ "(" icmpcode [ [ "," ] icmp6code ] ")" ] |
                  "return-icmp6" [ "(" icmp6code ")" ]
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index b97524f34bc3..e2cddb49728c 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -310,6 +310,7 @@ _Static_assert(sizeof(time_t) == 4 || sizeof(time_t) == 8, "unexpected time_t si
 
 SYSCTL_DECL(_net_pf);
 MALLOC_DECLARE(M_PFHASH);
+MALLOC_DECLARE(M_PF_RULE_ITEM);
 
 SDT_PROVIDER_DECLARE(pf);
 
@@ -593,8 +594,13 @@ struct pf_kpool {
 };
 
 struct pf_rule_actions {
+	int		 rtableid;
 	uint16_t	 qid;
 	uint16_t	 pqid;
+	uint16_t	 max_mss;
+	uint8_t		 log;
+	uint8_t		 set_tos;
+	uint8_t		 min_ttl;
 	uint16_t	 dnpipe;
 	uint16_t	 dnrpipe;	/* Reverse direction pipe */
 	uint32_t	 flags;
@@ -811,10 +817,18 @@ struct pf_krule {
 #endif
 };
 
+struct pf_krule_item {
+	SLIST_ENTRY(pf_krule_item)	 entry;
+	struct pf_krule			*r;
+};
+
+SLIST_HEAD(pf_krule_slist, pf_krule_item);
+
 struct pf_ksrc_node {
 	LIST_ENTRY(pf_ksrc_node) entry;
 	struct pf_addr	 addr;
 	struct pf_addr	 raddr;
+	struct pf_krule_slist	 match_rules;
 	union pf_krule_ptr rule;
 	struct pfi_kkif	*kif;
 	counter_u64_t	 bytes[2];
@@ -892,16 +906,6 @@ struct pf_state_cmp {
 	u_int8_t		 pad[3];
 };
 
-#define	PFSTATE_ALLOWOPTS	0x01
-#define	PFSTATE_SLOPPY		0x02
-/*  was	PFSTATE_PFLOW		0x04 */
-#define	PFSTATE_NOSYNC		0x08
-#define	PFSTATE_ACK		0x10
-#define	PFRULE_DN_IS_PIPE	0x40
-#define	PFRULE_DN_IS_QUEUE	0x80
-#define	PFSTATE_SETPRIO		0x0200
-#define	PFSTATE_SETMASK   (PFSTATE_SETPRIO)
-
 struct pf_state_scrub_export {
 	uint16_t	pfss_flags;
 	uint8_t		pfss_ttl;	/* stashed TTL		*/
@@ -952,12 +956,13 @@ struct pf_state_export {
 	uint8_t		 proto;
 	uint8_t		 direction;
 	uint8_t		 log;
-	uint8_t		 state_flags;
+	uint8_t		 state_flags_compat;
 	uint8_t		 timeout;
 	uint8_t		 sync_flags;
 	uint8_t		 updates;
+	uint16_t	 state_flags;
 
-	uint8_t		 spare[112];
+	uint8_t		 spare[110];
 };
 _Static_assert(sizeof(struct pf_state_export) == 384, "size incorrect");
 
@@ -974,7 +979,7 @@ struct pf_kstate {
 	 * end of the area
 	 */
 
-	u_int8_t		 state_flags;
+	u_int16_t		 state_flags;
 	u_int8_t		 timeout;
 	u_int8_t		 sync_state; /* PFSYNC_S_x */
 	u_int8_t		 sync_updates; /* XXX */
@@ -985,6 +990,7 @@ struct pf_kstate {
 	LIST_ENTRY(pf_kstate)	 entry;
 	struct pf_state_peer	 src;
 	struct pf_state_peer	 dst;
+	struct pf_krule_slist	 match_rules;
 	union pf_krule_ptr	 rule;
 	union pf_krule_ptr	 anchor;
 	union pf_krule_ptr	 nat_rule;
@@ -1000,18 +1006,22 @@ struct pf_kstate {
 	u_int32_t		 creation;
 	u_int32_t	 	 expire;
 	u_int32_t		 pfsync_time;
-	u_int16_t                qid;
-	u_int16_t                pqid;
+	u_int16_t		 qid;
+	u_int16_t		 pqid;
 	u_int16_t		 dnpipe;
 	u_int16_t		 dnrpipe;
 	u_int16_t		 tag;
 	u_int8_t		 log;
+	int			 rtableid;
+	u_int8_t		 min_ttl;
+	u_int8_t		 set_tos;
+	u_int16_t		 max_mss;
 };
 
 /*
- * Size <= fits 13 objects per page on LP64. Try to not grow the struct beyond that.
+ * Size <= fits 12 objects per page on LP64. Try to not grow the struct beyond that.
  */
-_Static_assert(sizeof(struct pf_kstate) <= 312, "pf_kstate size crosses 312 bytes");
+_Static_assert(sizeof(struct pf_kstate) <= 336, "pf_kstate size crosses 336 bytes");
 #endif
 
 /*
@@ -1061,9 +1071,9 @@ struct pfsync_state {
 	sa_family_t	 af;
 	u_int8_t	 proto;
 	u_int8_t	 direction;
-	u_int8_t	 __spare[2];
+	u_int16_t	 state_flags;
 	u_int8_t	 log;
-	u_int8_t	 state_flags;
+	u_int8_t	 state_flags_compat;
 	u_int8_t	 timeout;
 	u_int8_t	 sync_flags;
 	u_int8_t	 updates;
@@ -1545,6 +1555,7 @@ struct pf_kstatus {
 	bool		syncookies_active;
 	uint64_t	syncookies_inflight[2];
 	uint32_t	states_halfopen;
+	uint32_t	reass;
 };
 #endif
 
@@ -1897,6 +1908,7 @@ struct pfioc_iface {
 #define	DIOCGETETHRULES		_IOWR('D', 99, struct pfioc_nv)
 #define	DIOCGETETHRULESETS	_IOWR('D', 100, struct pfioc_nv)
 #define	DIOCGETETHRULESET	_IOWR('D', 101, struct pfioc_nv)
+#define DIOCSETREASS		_IOWR('D', 102, u_int32_t)
 
 struct pf_ifspeed_v0 {
 	char			ifname[IFNAMSIZ];
@@ -2249,12 +2261,12 @@ struct mbuf 	*pf_build_tcp(const struct pf_krule *, sa_family_t,
 		    const struct pf_addr *, const struct pf_addr *,
 		    u_int16_t, u_int16_t, u_int32_t, u_int32_t,
 		    u_int8_t, u_int16_t, u_int16_t, u_int8_t, int,
-		    u_int16_t);
+		    u_int16_t, int);
 void		 pf_send_tcp(const struct pf_krule *, sa_family_t,
 			    const struct pf_addr *, const struct pf_addr *,
 			    u_int16_t, u_int16_t, u_int32_t, u_int32_t,
 			    u_int8_t, u_int16_t, u_int16_t, u_int8_t, int,
-			    u_int16_t);
+			    u_int16_t, int);
 
 void			 pf_syncookies_init(void);
 void			 pf_syncookies_cleanup(void);
@@ -2368,6 +2380,16 @@ struct pf_state_key	*pf_state_key_setup(struct pf_pdesc *, struct pf_addr *,
 			    struct pf_addr *, u_int16_t, u_int16_t);
 struct pf_state_key	*pf_state_key_clone(struct pf_state_key *);
 
+int			 pf_normalize_mss(struct mbuf *m, int off,
+			    struct pf_pdesc *pd, u_int16_t maxmss);
+u_int16_t		 pf_rule_to_scrub_flags(u_int32_t);
+#ifdef INET
+void	pf_scrub_ip(struct mbuf **, uint32_t, uint8_t, uint8_t);
+#endif	/* INET */
+#ifdef INET6
+void	pf_scrub_ip6(struct mbuf **, uint32_t, uint8_t, uint8_t);
+#endif	/* INET6 */
+
 struct pfi_kkif		*pf_kkif_create(int);
 void			 pf_kkif_free(struct pfi_kkif *);
 void			 pf_kkif_zero(struct pfi_kkif *);
diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c
index c9d214d73470..2bf1146ceda8 100644
--- a/sys/netpfil/pf/if_pfsync.c
+++ b/sys/netpfil/pf/if_pfsync.c
@@ -581,7 +581,30 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 	st->direction = sp->direction;
 	st->log = sp->log;
 	st->timeout = sp->timeout;
-	st->state_flags = sp->state_flags;
+	/* 8 from old peers, 16 bits from new peers */
+	st->state_flags = sp->state_flags_compat | ntohs(sp->state_flags);
+
+	if (r == &V_pf_default_rule) {
+		/* ToS and Prio are not sent over struct pfsync_state */
+		st->state_flags &= ~PFSTATE_SETMASK;
+	} else {
+		/* Most actions are applied form state, not from rule. Until
+		 * pfsync can forward all those actions and their parameters we
+		 * must relay on restoring them from the found rule.
+		 * It's a copy of pf_rule_to_actions() */
+		st->qid = r->qid;
+		st->pqid = r->pqid;
+		st->rtableid = r->rtableid;
+		if (r->scrub_flags & PFSTATE_SETTOS)
+			st->set_tos = r->set_tos;
+		st->min_ttl = r->min_ttl;
+		st->max_mss = r->max_mss;
+		st->state_flags |= (r->scrub_flags & (PFSTATE_NODF|PFSTATE_RANDOMID|
+		    PFSTATE_SETTOS|PFSTATE_SCRUB_TCP|PFSTATE_SETPRIO));
+		st->dnpipe = r->dnpipe;
+		st->dnrpipe = r->dnrpipe;
+		/* FIXME: dnflags are not part of state, can't update them */
+	}
 
 	st->id = sp->id;
 	st->creatorid = sp->creatorid;
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index f20851cb60e8..5eebd44c297d 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -268,7 +268,7 @@ static void		 pf_change_icmp(struct pf_addr *, u_int16_t *,
 			    u_int16_t *, u_int16_t *, u_int16_t *,
 			    u_int16_t *, u_int8_t, sa_family_t);
 static void		 pf_send_icmp(struct mbuf *, u_int8_t, u_int8_t,
-			    sa_family_t, struct pf_krule *);
+			    sa_family_t, struct pf_krule *, int);
 static void		 pf_detach_state(struct pf_kstate *);
 static int		 pf_state_key_attach(struct pf_state_key *,
 			    struct pf_state_key *, struct pf_kstate *);
@@ -294,7 +294,7 @@ static int		 pf_create_state(struct pf_krule *, struct pf_krule *,
 			    struct pf_state_key *, struct mbuf *, int,
 			    u_int16_t, u_int16_t, int *, struct pfi_kkif *,
 			    struct pf_kstate **, int, u_int16_t, u_int16_t,
-			    int);
+			    int, struct pf_krule_slist *);
 static int		 pf_test_fragment(struct pf_krule **, int,
 			    struct pfi_kkif *, struct mbuf *, void *,
 			    struct pf_pdesc *, struct pf_krule **,
@@ -386,6 +386,7 @@ VNET_DEFINE(struct pf_limit, pf_limits[PF_LIMIT_MAX]);
 
 #define	STATE_INC_COUNTERS(s)						\
 	do {								\
+		struct pf_krule_item *mrm;				\
 		counter_u64_add(s->rule.ptr->states_cur, 1);		\
 		counter_u64_add(s->rule.ptr->states_tot, 1);		\
 		if (s->anchor.ptr != NULL) {				\
@@ -396,18 +397,26 @@ VNET_DEFINE(struct pf_limit, pf_limits[PF_LIMIT_MAX]);
 			counter_u64_add(s->nat_rule.ptr->states_cur, 1);\
 			counter_u64_add(s->nat_rule.ptr->states_tot, 1);\
 		}							\
+		SLIST_FOREACH(mrm, &s->match_rules, entry) {		\
+			counter_u64_add(mrm->r->states_cur, 1);		\
+			counter_u64_add(mrm->r->states_tot, 1);		\
+		}							\
 	} while (0)
 
 #define	STATE_DEC_COUNTERS(s)						\
 	do {								\
+		struct pf_krule_item *mrm;				\
 		if (s->nat_rule.ptr != NULL)				\
 			counter_u64_add(s->nat_rule.ptr->states_cur, -1);\
 		if (s->anchor.ptr != NULL)				\
 			counter_u64_add(s->anchor.ptr->states_cur, -1);	\
 		counter_u64_add(s->rule.ptr->states_cur, -1);		\
+		SLIST_FOREACH(mrm, &s->match_rules, entry)		\
+			counter_u64_add(mrm->r->states_cur, -1);	\
 	} while (0)
 
 MALLOC_DEFINE(M_PFHASH, "pf_hash", "pf(4) hash header structures");
+MALLOC_DEFINE(M_PF_RULE_ITEM, "pf_krule_item", "pf(4) rule items");
 VNET_DEFINE(struct pf_keyhash *, pf_keyhash);
 VNET_DEFINE(struct pf_idhash *, pf_idhash);
 VNET_DEFINE(struct pf_srchash *, pf_srchash);
@@ -2031,7 +2040,7 @@ pf_unlink_state(struct pf_kstate *s)
 		    s->key[PF_SK_WIRE]->port[1],
 		    s->key[PF_SK_WIRE]->port[0],
 		    s->src.seqhi, s->src.seqlo + 1,
-		    TH_RST|TH_ACK, 0, 0, 0, 1, s->tag);
+		    TH_RST|TH_ACK, 0, 0, 0, 1, s->tag, s->rtableid);
 	}
 
 	LIST_REMOVE(s, entry);
@@ -2066,11 +2075,17 @@ pf_alloc_state(int flags)
 void
 pf_free_state(struct pf_kstate *cur)
 {
+	struct pf_krule_item *ri;
 
 	KASSERT(cur->refs == 0, ("%s: %p has refs", __func__, cur));
 	KASSERT(cur->timeout == PFTM_UNLINKED, ("%s: timeout %u", __func__,
 	    cur->timeout));
 
+	while ((ri = SLIST_FIRST(&cur->match_rules))) {
+		SLIST_REMOVE_HEAD(&cur->match_rules, entry);
+		free(ri, M_PF_RULE_ITEM);
+	}
+
 	pf_normalize_tcp_cleanup(cur);
 	uma_zfree(V_pf_state_z, cur);
 	pf_counter_u64_add(&V_pf_status.fcounters[FCNT_STATE_REMOVALS], 1);
@@ -2084,6 +2099,7 @@ pf_purge_expired_states(u_int i, int maxcheck)
 {
 	struct pf_idhash *ih;
 	struct pf_kstate *s;
+	struct pf_krule_item *mrm;
 
 	V_pf_status.states = uma_zone_get_cur(V_pf_state_z);
 
@@ -2109,6 +2125,8 @@ relock:
 				if (s->anchor.ptr != NULL)
 					s->anchor.ptr->rule_ref |= PFRULE_REFS;
 				s->kif->pfik_flags |= PFI_IFLAG_REFS;
+				SLIST_FOREACH(mrm, &s->match_rules, entry)
+					mrm->r->rule_ref |= PFRULE_REFS;
 				if (s->rt_kif)
 					s->rt_kif->pfik_flags |= PFI_IFLAG_REFS;
 			}
@@ -2772,7 +2790,7 @@ pf_build_tcp(const struct pf_krule *r, sa_family_t af,
     const struct pf_addr *saddr, const struct pf_addr *daddr,
     u_int16_t sport, u_int16_t dport, u_int32_t seq, u_int32_t ack,
     u_int8_t flags, u_int16_t win, u_int16_t mss, u_int8_t ttl, int tag,
-    u_int16_t rtag)
+    u_int16_t rtag, int rtableid)
 {
 	struct mbuf	*m;
 	int		 len, tlen;
@@ -2824,8 +2842,8 @@ pf_build_tcp(const struct pf_krule *r, sa_family_t af,
 		m->m_flags |= M_SKIP_FIREWALL;
 	pf_mtag->tag = rtag;
 
-	if (r != NULL && r->rtableid >= 0)
-		M_SETFIB(m, r->rtableid);
+	if (rtableid >= 0)
+		M_SETFIB(m, rtableid);
 
 #ifdef ALTQ
 	if (r != NULL && r->qid) {
@@ -2923,13 +2941,13 @@ pf_send_tcp(const struct pf_krule *r, sa_family_t af,
     const struct pf_addr *saddr, const struct pf_addr *daddr,
     u_int16_t sport, u_int16_t dport, u_int32_t seq, u_int32_t ack,
     u_int8_t flags, u_int16_t win, u_int16_t mss, u_int8_t ttl, int tag,
-    u_int16_t rtag)
+    u_int16_t rtag, int rtableid)
 {
 	struct pf_send_entry *pfse;
 	struct mbuf	*m;
 
 	m = pf_build_tcp(r, af, saddr, daddr, sport, dport, seq, ack, flags,
-	    win, mss, ttl, tag, rtag);
+	    win, mss, ttl, tag, rtag, rtableid);
 	if (m == NULL)
 		return;
 
@@ -2961,7 +2979,7 @@ static void
 pf_return(struct pf_krule *r, struct pf_krule *nr, struct pf_pdesc *pd,
     struct pf_state_key *sk, int off, struct mbuf *m, struct tcphdr *th,
     struct pfi_kkif *kif, u_int16_t bproto_sum, u_int16_t bip_sum, int hdrlen,
-    u_short *reason)
+    u_short *reason, int rtableid)
 {
 	struct pf_addr	* const saddr = pd->src;
 	struct pf_addr	* const daddr = pd->dst;
@@ -3019,16 +3037,16 @@ pf_return(struct pf_krule *r, struct pf_krule *nr, struct pf_pdesc *pd,
 			pf_send_tcp(r, af, pd->dst,
 				pd->src, th->th_dport, th->th_sport,
 				ntohl(th->th_ack), ack, TH_RST|TH_ACK, 0, 0,
-				r->return_ttl, 1, 0);
+				r->return_ttl, 1, 0, rtableid);
 		}
 	} else if (pd->proto != IPPROTO_ICMP && af == AF_INET &&
 		r->return_icmp)
 		pf_send_icmp(m, r->return_icmp >> 8,
-			r->return_icmp & 255, af, r);
+			r->return_icmp & 255, af, r, rtableid);
 	else if (pd->proto != IPPROTO_ICMPV6 && af == AF_INET6 &&
 		r->return_icmp6)
 		pf_send_icmp(m, r->return_icmp6 >> 8,
-			r->return_icmp6 & 255, af, r);
+			r->return_icmp6 & 255, af, r, rtableid);
 }
*** 1207 LINES SKIPPED ***