git: 288bec2b2bd1 - main - pf: fold pf_test_fragment() into pf_test_rule()

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 01 Oct 2024 10:12:11 UTC
The branch main has been updated by kp:

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

commit 288bec2b2bd10d80cdc35a687e8a373f5931c80d
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-09-13 15:07:06 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-10-01 07:55:13 +0000

    pf: fold pf_test_fragment() into pf_test_rule()
    
    Reduces code and fixes a bunch of bugs with fragment handling not being in sync
    with the rest of the ruleset.
    
    Much feedback from mpf, bluhm & markus
    Thanks to Tony Sarendal for help with testing
    
    ok bluhm; various previous versions ok henning, claudio, mpf, markus
    
    Note that while this changes the order of src addr/src port/dst addr/dst port
    skips this doesn't actually affect the kernel/userspace ABI. The kernel always
    recalculates skip steps. As a result we have to fix one of the pfctl parser
    tests. Note that this is an order change that does not affect what packets are
    acceppted or dropped.
    
    Obtained from:  OpenBSD, mcbride <mcbride@openbsd.org>, 04c69899a7
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D46705
---
 sbin/pfctl/parse.y               |   4 +-
 sbin/pfctl/pfctl_optimize.c      |   2 +-
 sbin/pfctl/tests/files/pf0004.ok |  24 +--
 sys/net/pfvar.h                  |   2 +
 sys/netpfil/pf/pf.c              | 450 ++++++++++++++++-----------------------
 sys/netpfil/pf/pf.h              |   4 +-
 6 files changed, 198 insertions(+), 288 deletions(-)

diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 9d95122f9826..ad25f1996d36 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -6073,10 +6073,10 @@ expand_rule(struct pfctl_rule *r,
 	LOOP_THROUGH(struct node_proto, proto, protos,
 	LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types,
 	LOOP_THROUGH(struct node_host, src_host, src_hosts,
-	LOOP_THROUGH(struct node_port, src_port, src_ports,
-	LOOP_THROUGH(struct node_os, src_os, src_oses,
 	LOOP_THROUGH(struct node_host, dst_host, dst_hosts,
+	LOOP_THROUGH(struct node_port, src_port, src_ports,
 	LOOP_THROUGH(struct node_port, dst_port, dst_ports,
+	LOOP_THROUGH(struct node_os, src_os, src_oses,
 	LOOP_THROUGH(struct node_uid, uid, uids,
 	LOOP_THROUGH(struct node_gid, gid, gids,
 
diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c
index 7440bf8a506d..9858f38b8671 100644
--- a/sbin/pfctl/pfctl_optimize.c
+++ b/sbin/pfctl/pfctl_optimize.c
@@ -249,8 +249,8 @@ static const char *skip_comparitors_names[PF_SKIP_COUNT];
     { "af", PF_SKIP_AF, skip_cmp_af },			\
     { "proto", PF_SKIP_PROTO, skip_cmp_proto },		\
     { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr },	\
-    { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port },	\
     { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr },	\
+    { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port },	\
     { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port }	\
 }
 
diff --git a/sbin/pfctl/tests/files/pf0004.ok b/sbin/pfctl/tests/files/pf0004.ok
index 5fca4a50f7b1..87b71cdeff3d 100644
--- a/sbin/pfctl/tests/files/pf0004.ok
+++ b/sbin/pfctl/tests/files/pf0004.ok
@@ -15,48 +15,48 @@ block drop in proto tcp from any port >= 80 to any port 1024:2048
 block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = ircd
 block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668
 block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535
-block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = ircd
-block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
-block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = ircd
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = ircd
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668
 block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535
 block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = ircd
 block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668
 block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535
-block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = ircd
-block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
-block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = ircd
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = ircd
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668
 block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6667
 block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668
 block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535
-block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667
-block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
-block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6667
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6667
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668
 block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6667
 block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668
 block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535
-block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6667
-block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
-block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6667
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6667
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6667
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668
 block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index dfc42c16547f..79dcd0d65985 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1619,6 +1619,8 @@ struct pf_pdesc {
 					 * state code. Easier than tags */
 #define PFDESC_TCP_NORM	0x0001		/* TCP shall be statefully scrubbed */
 #define PFDESC_IP_REAS	0x0002		/* IP frags would've been reassembled */
+	u_int16_t	 virtual_proto;
+#define PF_VPROTO_FRAGMENT	256
 	sa_family_t	 af;
 	u_int8_t	 proto;
 	u_int8_t	 tos;
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 19e0014ce4eb..ccfe1a0fcd96 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -333,9 +333,6 @@ static int		 pf_create_state(struct pf_krule *, struct pf_krule *,
 static int		 pf_state_key_addr_setup(struct pf_pdesc *, struct mbuf *,
 			    int, struct pf_state_key_cmp *, int, struct pf_addr *,
 			    int, struct pf_addr *, int);
-static int		 pf_test_fragment(struct pf_krule **, struct pfi_kkif *,
-			    struct mbuf *, struct pf_pdesc *,
-			    struct pf_krule **, struct pf_kruleset **);
 static int		 pf_tcp_track_full(struct pf_kstate **,
 			    struct pfi_kkif *, struct mbuf *, int,
 			    struct pf_pdesc *, u_short *, int *);
@@ -2962,13 +2959,13 @@ pf_calc_skip_steps(struct pf_krulequeue *rules)
 		if (cur->src.neg != prev->src.neg ||
 		    pf_addr_wrap_neq(&cur->src.addr, &prev->src.addr))
 			PF_SET_SKIP_STEPS(PF_SKIP_SRC_ADDR);
+		if (cur->dst.neg != prev->dst.neg ||
+		    pf_addr_wrap_neq(&cur->dst.addr, &prev->dst.addr))
+			PF_SET_SKIP_STEPS(PF_SKIP_DST_ADDR);
 		if (cur->src.port[0] != prev->src.port[0] ||
 		    cur->src.port[1] != prev->src.port[1] ||
 		    cur->src.port_op != prev->src.port_op)
 			PF_SET_SKIP_STEPS(PF_SKIP_SRC_PORT);
-		if (cur->dst.neg != prev->dst.neg ||
-		    pf_addr_wrap_neq(&cur->dst.addr, &prev->dst.addr))
-			PF_SET_SKIP_STEPS(PF_SKIP_DST_ADDR);
 		if (cur->dst.port[0] != prev->dst.port[0] ||
 		    cur->dst.port[1] != prev->dst.port[1] ||
 		    cur->dst.port_op != prev->dst.port_op)
@@ -4877,6 +4874,14 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 	return (action);
 }
 
+#define PF_TEST_ATTRIB(t, a)\
+	do {				\
+		if (t) {		\
+			r = a;		\
+			goto nextrule;	\
+		}			\
+	} while (0)
+
 static int
 pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
     struct mbuf *m, int off, struct pf_pdesc *pd, struct pf_krule **am,
@@ -4916,7 +4921,7 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
 		pd->lookup.done = 1;
 	}
 
-	switch (pd->proto) {
+	switch (pd->virtual_proto) {
 	case IPPROTO_TCP:
 		sport = th->th_sport;
 		dport = th->th_dport;
@@ -5131,108 +5136,136 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
 
 	while (r != NULL) {
 		pf_counter_u64_add(&r->evaluations, 1);
-		if (pfi_kkif_match(r->kif, kif) == r->ifnot)
-			r = r->skip[PF_SKIP_IFP].ptr;
-		else if (r->direction && r->direction != pd->dir)
-			r = r->skip[PF_SKIP_DIR].ptr;
-		else if (r->af && r->af != af)
-			r = r->skip[PF_SKIP_AF].ptr;
-		else if (r->proto && r->proto != pd->proto)
-			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, saddr, af,
-		    r->src.neg, kif, M_GETFIB(m)))
-			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
-		/* tcp/udp only. port_op always 0 in other cases */
-		else if (r->src.port_op && !pf_match_port(r->src.port_op,
-		    r->src.port[0], r->src.port[1], sport))
-			r = r->skip[PF_SKIP_SRC_PORT].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, daddr, af,
-		    r->dst.neg, NULL, M_GETFIB(m)))
-			r = r->skip[PF_SKIP_DST_ADDR].ptr;
-		/* tcp/udp only. port_op always 0 in other cases */
-		else if (r->dst.port_op && !pf_match_port(r->dst.port_op,
-		    r->dst.port[0], r->dst.port[1], dport))
-			r = r->skip[PF_SKIP_DST_PORT].ptr;
-		/* icmp only. type always 0 in other cases */
-		else if (r->type && r->type != icmptype + 1)
-			r = TAILQ_NEXT(r, entries);
-		/* icmp only. type always 0 in other cases */
-		else if (r->code && r->code != icmpcode + 1)
-			r = TAILQ_NEXT(r, entries);
-		else if (r->tos && !(r->tos == pd->tos))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->rule_flag & PFRULE_FRAGMENT)
-			r = TAILQ_NEXT(r, entries);
-		else if (pd->proto == IPPROTO_TCP &&
-		    (r->flagset & th->th_flags) != r->flags)
-			r = TAILQ_NEXT(r, entries);
-		/* tcp/udp only. uid.op always 0 in other cases */
-		else if (r->uid.op && (pd->lookup.done || (pd->lookup.done =
-		    pf_socket_lookup(pd, m), 1)) &&
-		    !pf_match_uid(r->uid.op, r->uid.uid[0], r->uid.uid[1],
-		    pd->lookup.uid))
-			r = TAILQ_NEXT(r, entries);
-		/* tcp/udp only. gid.op always 0 in other cases */
-		else if (r->gid.op && (pd->lookup.done || (pd->lookup.done =
-		    pf_socket_lookup(pd, m), 1)) &&
-		    !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1],
-		    pd->lookup.gid))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->prio &&
-		    !pf_match_ieee8021q_pcp(r->prio, m))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->prob &&
-		    r->prob <= arc4random())
-			r = TAILQ_NEXT(r, entries);
-		else if (r->match_tag && !pf_match_tag(m, r, &tag,
-		    pd->pf_mtag ? pd->pf_mtag->tag : 0))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->rcv_kif && !pf_match_rcvif(m, r))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->os_fingerprint != PF_OSFP_ANY &&
-		    (pd->proto != IPPROTO_TCP || !pf_osfp_match(
+		PF_TEST_ATTRIB(pfi_kkif_match(r->kif, kif) == r->ifnot,
+			r->skip[PF_SKIP_IFP].ptr);
+		PF_TEST_ATTRIB(r->direction && r->direction != pd->dir,
+			r->skip[PF_SKIP_DIR].ptr);
+		PF_TEST_ATTRIB(r->af && r->af != af,
+			r->skip[PF_SKIP_AF].ptr);
+		PF_TEST_ATTRIB(r->proto && r->proto != pd->proto,
+			r->skip[PF_SKIP_PROTO].ptr);
+		PF_TEST_ATTRIB(PF_MISMATCHAW(&r->src.addr, saddr, af,
+		    r->src.neg, kif, M_GETFIB(m)),
+			r->skip[PF_SKIP_SRC_ADDR].ptr);
+		PF_TEST_ATTRIB(PF_MISMATCHAW(&r->dst.addr, daddr, af,
+		    r->dst.neg, NULL, M_GETFIB(m)),
+			r->skip[PF_SKIP_DST_ADDR].ptr);
+		switch (pd->virtual_proto) {
+		case PF_VPROTO_FRAGMENT:
+			/* tcp/udp only. port_op always 0 in other cases */
+			PF_TEST_ATTRIB((r->src.port_op || r->dst.port_op),
+				TAILQ_NEXT(r, entries));
+			PF_TEST_ATTRIB((pd->proto == IPPROTO_TCP && r->flagset),
+				TAILQ_NEXT(r, entries));
+			/* icmp only. type/code always 0 in other cases */
+			PF_TEST_ATTRIB((r->type || r->code),
+				TAILQ_NEXT(r, entries));
+			/* tcp/udp only. {uid|gid}.op always 0 in other cases */
+			PF_TEST_ATTRIB((r->gid.op || r->uid.op),
+				TAILQ_NEXT(r, entries));
+			break;
+
+		case IPPROTO_TCP:
+			PF_TEST_ATTRIB((r->flagset & th->th_flags) != r->flags,
+				TAILQ_NEXT(r, entries));
+			/* FALLTHROUGH */
+		case IPPROTO_SCTP:
+		case IPPROTO_UDP:
+			/* tcp/udp only. port_op always 0 in other cases */
+			PF_TEST_ATTRIB(r->src.port_op && !pf_match_port(r->src.port_op,
+			    r->src.port[0], r->src.port[1], sport),
+				r->skip[PF_SKIP_SRC_PORT].ptr);
+			/* tcp/udp only. port_op always 0 in other cases */
+			PF_TEST_ATTRIB(r->dst.port_op && !pf_match_port(r->dst.port_op,
+			    r->dst.port[0], r->dst.port[1], dport),
+				r->skip[PF_SKIP_DST_PORT].ptr);
+			/* tcp/udp only. uid.op always 0 in other cases */
+			PF_TEST_ATTRIB(r->uid.op && (pd->lookup.done || (pd->lookup.done =
+			    pf_socket_lookup(pd, m), 1)) &&
+			    !pf_match_uid(r->uid.op, r->uid.uid[0], r->uid.uid[1],
+			    pd->lookup.uid),
+				TAILQ_NEXT(r, entries));
+			/* tcp/udp only. gid.op always 0 in other cases */
+			PF_TEST_ATTRIB(r->gid.op && (pd->lookup.done || (pd->lookup.done =
+			    pf_socket_lookup(pd, m), 1)) &&
+			    !pf_match_gid(r->gid.op, r->gid.gid[0], r->gid.gid[1],
+			    pd->lookup.gid),
+				TAILQ_NEXT(r, entries));
+			break;
+
+		case IPPROTO_ICMP:
+		case IPPROTO_ICMPV6:
+			/* icmp only. type always 0 in other cases */
+			PF_TEST_ATTRIB(r->type && r->type != icmptype + 1,
+				TAILQ_NEXT(r, entries));
+			/* icmp only. type always 0 in other cases */
+			PF_TEST_ATTRIB(r->code && r->code != icmpcode + 1,
+				TAILQ_NEXT(r, entries));
+			break;
+
+		default:
+			break;
+		}
+		PF_TEST_ATTRIB(r->tos && !(r->tos == pd->tos),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB(r->prio &&
+		    !pf_match_ieee8021q_pcp(r->prio, m),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB(r->prob &&
+		    r->prob <= arc4random(),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB(r->match_tag && !pf_match_tag(m, r, &tag,
+		    pd->pf_mtag ? pd->pf_mtag->tag : 0),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB(r->rcv_kif && !pf_match_rcvif(m, r),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB((r->rule_flag & PFRULE_FRAGMENT &&
+		    pd->virtual_proto != PF_VPROTO_FRAGMENT),
+			TAILQ_NEXT(r, entries));
+		PF_TEST_ATTRIB(r->os_fingerprint != PF_OSFP_ANY &&
+		    (pd->virtual_proto != IPPROTO_TCP || !pf_osfp_match(
 		    pf_osfp_fingerprint(pd, m, off, th),
-		    r->os_fingerprint)))
-			r = TAILQ_NEXT(r, entries);
-		else {
-			if (r->tag)
-				tag = r->tag;
-			if (r->anchor == NULL) {
-				if (r->action == PF_MATCH) {
-					ri = malloc(sizeof(struct pf_krule_item), M_PF_RULE_ITEM, M_NOWAIT | M_ZERO);
-					if (ri == NULL) {
-						REASON_SET(&reason, PFRES_MEMORY);
-						goto cleanup;
-					}
-					ri->r = r;
-					SLIST_INSERT_HEAD(&match_rules, ri, entry);
-					pf_counter_u64_critical_enter();
-					pf_counter_u64_add_protected(&r->packets[pd->dir == PF_OUT], 1);
-					pf_counter_u64_add_protected(&r->bytes[pd->dir == PF_OUT], pd->tot_len);
-					pf_counter_u64_critical_exit();
-					pf_rule_to_actions(r, &pd->act);
-					if (r->log || pd->act.log & PF_LOG_MATCHES)
-						PFLOG_PACKET(kif, m,
-						    r->action, PFRES_MATCH, r,
-						    a, ruleset, pd, 1);
-				} else {
-					match = 1;
-					*rm = r;
-					*am = a;
-					*rsm = ruleset;
-					if (pd->act.log & PF_LOG_MATCHES)
-						PFLOG_PACKET(kif, m,
-						    r->action, PFRES_MATCH, r,
-						    a, ruleset, pd, 1);
+		    r->os_fingerprint)),
+			TAILQ_NEXT(r, entries));
+		/* FALLTHROUGH */
+		if (r->tag)
+			tag = r->tag;
+		if (r->anchor == NULL) {
+			if (r->action == PF_MATCH) {
+				ri = malloc(sizeof(struct pf_krule_item), M_PF_RULE_ITEM, M_NOWAIT | M_ZERO);
+				if (ri == NULL) {
+					REASON_SET(&reason, PFRES_MEMORY);
+					goto cleanup;
 				}
-				if ((*rm)->quick)
-					break;
-				r = TAILQ_NEXT(r, entries);
-			} else
-				pf_step_into_anchor(anchor_stack, &asd,
-				    &ruleset, PF_RULESET_FILTER, &r, &a,
-				    &match);
-		}
+				ri->r = r;
+				SLIST_INSERT_HEAD(&match_rules, ri, entry);
+				pf_counter_u64_critical_enter();
+				pf_counter_u64_add_protected(&r->packets[pd->dir == PF_OUT], 1);
+				pf_counter_u64_add_protected(&r->bytes[pd->dir == PF_OUT], pd->tot_len);
+				pf_counter_u64_critical_exit();
+				pf_rule_to_actions(r, &pd->act);
+				if (r->log || pd->act.log & PF_LOG_MATCHES)
+					PFLOG_PACKET(kif, m,
+					    r->action, PFRES_MATCH, r,
+					    a, ruleset, pd, 1);
+			} else {
+				match = 1;
+				*rm = r;
+				*am = a;
+				*rsm = ruleset;
+				if (pd->act.log & PF_LOG_MATCHES)
+					PFLOG_PACKET(kif, m,
+					    r->action, PFRES_MATCH, r,
+					    a, ruleset, pd, 1);
+			}
+			if ((*rm)->quick)
+				break;
+			r = TAILQ_NEXT(r, entries);
+		} else
+			pf_step_into_anchor(anchor_stack, &asd,
+			    &ruleset, PF_RULESET_FILTER, &r, &a,
+			    &match);
+nextrule:
 		if (r == NULL && pf_step_out_of_anchor(anchor_stack, &asd,
 		    &ruleset, PF_RULESET_FILTER, &r, &a, &match))
 			break;
@@ -5252,7 +5285,8 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
 		PFLOG_PACKET(kif, m, r->action, reason, r, a, ruleset, pd, 1);
 	}
 
-	if ((r->action == PF_DROP) &&
+	if (pd->virtual_proto != PF_VPROTO_FRAGMENT &&
+	   (r->action == PF_DROP) &&
 	    ((r->rule_flag & PFRULE_RETURNRST) ||
 	    (r->rule_flag & PFRULE_RETURNICMP) ||
 	    (r->rule_flag & PFRULE_RETURN))) {
@@ -5270,8 +5304,9 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
 	if (pd->act.rtableid >= 0)
 		M_SETFIB(m, pd->act.rtableid);
 
-	if (!state_icmp && (r->keep_state || nr != NULL ||
-	    (pd->flags & PFDESC_TCP_NORM))) {
+	if (pd->virtual_proto != PF_VPROTO_FRAGMENT &&
+	   (!state_icmp && (r->keep_state || nr != NULL ||
+	    (pd->flags & PFDESC_TCP_NORM)))) {
 		int action;
 		action = pf_create_state(r, nr, a, pd, nsn, nk, sk, m, off,
 		    sport, dport, &rewrite, kif, sm, tag, bproto_sum, bip_sum,
@@ -5595,133 +5630,6 @@ drop:
 	return (PF_DROP);
 }
 
-static int
-pf_test_fragment(struct pf_krule **rm, struct pfi_kkif *kif,
-    struct mbuf *m, struct pf_pdesc *pd, struct pf_krule **am,
-    struct pf_kruleset **rsm)
-{
-	struct pf_krule		*r, *a = NULL;
-	struct pf_kruleset	*ruleset = NULL;
-	struct pf_krule_slist	 match_rules;
-	struct pf_krule_item	*ri;
-	sa_family_t		 af = pd->af;
-	u_short			 reason;
-	int			 tag = -1;
-	int			 asd = 0;
-	int			 match = 0;
-	struct pf_kanchor_stackframe	anchor_stack[PF_ANCHOR_STACKSIZE];
-
-	PF_RULES_RASSERT();
-
-	r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_FILTER].active.ptr);
-	SLIST_INIT(&match_rules);
-	while (r != NULL) {
-		pf_counter_u64_add(&r->evaluations, 1);
-		if (pfi_kkif_match(r->kif, kif) == r->ifnot)
-			r = r->skip[PF_SKIP_IFP].ptr;
-		else if (r->direction && r->direction != pd->dir)
-			r = r->skip[PF_SKIP_DIR].ptr;
-		else if (r->af && r->af != af)
-			r = r->skip[PF_SKIP_AF].ptr;
-		else if (r->proto && r->proto != pd->proto)
-			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, pd->src, af,
-		    r->src.neg, kif, M_GETFIB(m)))
-			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af,
-		    r->dst.neg, NULL, M_GETFIB(m)))
-			r = r->skip[PF_SKIP_DST_ADDR].ptr;
-		else if (r->tos && !(r->tos == pd->tos))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->os_fingerprint != PF_OSFP_ANY)
-			r = TAILQ_NEXT(r, entries);
-		else if (pd->proto == IPPROTO_UDP &&
-		    (r->src.port_op || r->dst.port_op))
-			r = TAILQ_NEXT(r, entries);
-		else if (pd->proto == IPPROTO_TCP &&
-		    (r->src.port_op || r->dst.port_op || r->flagset))
-			r = TAILQ_NEXT(r, entries);
-		else if ((pd->proto == IPPROTO_ICMP ||
-		    pd->proto == IPPROTO_ICMPV6) &&
-		    (r->type || r->code))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->prio &&
-		    !pf_match_ieee8021q_pcp(r->prio, m))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->prob && r->prob <=
-		    (arc4random() % (UINT_MAX - 1) + 1))
-			r = TAILQ_NEXT(r, entries);
-		else if (r->match_tag && !pf_match_tag(m, r, &tag,
-		    pd->pf_mtag ? pd->pf_mtag->tag : 0))
-			r = TAILQ_NEXT(r, entries);
-		else {
-			if (r->anchor == NULL) {
-				if (r->action == PF_MATCH) {
-					ri = malloc(sizeof(struct pf_krule_item), M_PF_RULE_ITEM, M_NOWAIT | M_ZERO);
-					if (ri == NULL) {
-						REASON_SET(&reason, PFRES_MEMORY);
-						goto cleanup;
-					}
-					ri->r = r;
-					SLIST_INSERT_HEAD(&match_rules, ri, entry);
-					pf_counter_u64_critical_enter();
-					pf_counter_u64_add_protected(&r->packets[pd->dir == PF_OUT], 1);
-					pf_counter_u64_add_protected(&r->bytes[pd->dir == PF_OUT], pd->tot_len);
-					pf_counter_u64_critical_exit();
-					pf_rule_to_actions(r, &pd->act);
-					if (r->log)
-						PFLOG_PACKET(kif, m,
-						    r->action, PFRES_MATCH, r,
-						    a, ruleset, pd, 1);
-				} else {
-					match = 1;
-					*rm = r;
-					*am = a;
-					*rsm = ruleset;
-				}
-				if ((*rm)->quick)
-					break;
-				r = TAILQ_NEXT(r, entries);
-			} else
-				pf_step_into_anchor(anchor_stack, &asd,
-				    &ruleset, PF_RULESET_FILTER, &r, &a,
-				    &match);
-		}
-		if (r == NULL && pf_step_out_of_anchor(anchor_stack, &asd,
-		    &ruleset, PF_RULESET_FILTER, &r, &a, &match))
-			break;
-	}
-	r = *rm;
-	a = *am;
-	ruleset = *rsm;
-
-	REASON_SET(&reason, PFRES_MATCH);
-
-	/* apply actions for last matching pass/block rule */
-	pf_rule_to_actions(r, &pd->act);
-
-	if (r->log)
-		PFLOG_PACKET(kif, m, r->action, reason, r, a, ruleset, pd, 1);
-
-	if (r->action != PF_PASS)
-		return (PF_DROP);
-
-	if (tag > 0 && pf_tag_packet(m, pd, tag)) {
-		REASON_SET(&reason, PFRES_MEMORY);
-		goto cleanup;
-	}
-
-	return (PF_PASS);
-
-cleanup:
-	while ((ri = SLIST_FIRST(&match_rules))) {
-		SLIST_REMOVE_HEAD(&match_rules, entry);
-		free(ri, M_PF_RULE_ITEM);
-	}
-
-	return (PF_DROP);
-}
-
 static int
 pf_tcp_track_full(struct pf_kstate **state, struct pfi_kkif *kif,
     struct mbuf *m, int off, struct pf_pdesc *pd, u_short *reason,
@@ -8556,7 +8464,8 @@ pf_dummynet_route(struct pf_pdesc *pd, struct pf_kstate *s,
 static int
 pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf *m,
     u_short *action, u_short *reason, struct pfi_kkif *kif, struct pf_krule **a,
-    struct pf_krule **r, struct pf_kruleset **ruleset, int *off, int *hdrlen,
+    struct pf_krule **r, struct pf_kstate **s, struct pf_kruleset **ruleset,
+    int *off, int *hdrlen, struct inpcb *inp,
     struct pf_rule_actions *default_actions)
 {
 
@@ -8589,7 +8498,7 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf *m,
 		pd->sport = pd->dport = NULL;
 		pd->ip_sum = &h->ip_sum;
 		pd->proto_sum = NULL;
-		pd->proto = h->ip_p;
+		pd->virtual_proto = pd->proto = h->ip_p;
 		pd->dir = dir;
 		pd->sidx = (dir == PF_IN) ? 0 : 1;
 		pd->didx = (dir == PF_IN) ? 1 : 0;
@@ -8600,9 +8509,21 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf *m,
 		if (h->ip_hl > 5)	/* has options */
 			pd->badopts++;
 
-		/* fragments not reassembled handled later */
-		if (h->ip_off & htons(IP_MF | IP_OFFMASK))
-			return (0);
+		if (h->ip_off & htons(IP_MF | IP_OFFMASK)) {
+			/*
+			 * handle fragments that aren't reassembled by
+			 * normalization
+			 */
+			pd->virtual_proto = PF_VPROTO_FRAGMENT;
+			if (kif == NULL || r == NULL)   /* pflog */
+				*action = PF_DROP;
+			else
+				*action = pf_test_rule(r, s, kif, m, *off,
+				    pd, a, ruleset, inp, *hdrlen);
+			if (*action != PF_PASS)
+				REASON_SET(reason, PFRES_FRAG);
+			return (-1);
+		}
 
 		break;
 	}
@@ -8624,17 +8545,19 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf *m,
 		pd->tos = IPV6_DSCP(h);
 		pd->tot_len = ntohs(h->ip6_plen) + sizeof(struct ip6_hdr);
 		*off = ((caddr_t)h - m->m_data) + sizeof(struct ip6_hdr);
-		pd->proto = h->ip6_nxt;
+		pd->virtual_proto = pd->proto = h->ip6_nxt;
 		pd->act.rtableid = -1;
 
 		do {
 			switch (pd->proto) {
 			case IPPROTO_FRAGMENT:
+				pd->virtual_proto = PF_VPROTO_FRAGMENT;
 				if (kif == NULL || r == NULL) /* pflog */
 					*action = PF_DROP;
 				else
-					*action = pf_test_fragment(r, kif,
-					    m, pd, a, ruleset);
+					*action = pf_test_rule(r, s, kif, m, *off,
+					    pd, a, ruleset, inp,
+					    *hdrlen);
 				if (*action == PF_DROP)
 					REASON_SET(reason, PFRES_FRAG);
 				return (-1);
@@ -8682,7 +8605,7 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf *m,
 					*off += (opt6.ip6e_len + 2) * 4;
 				else
 					*off += (opt6.ip6e_len + 1) * 8;
-				pd->proto = opt6.ip6e_nxt;
+				pd->virtual_proto = pd->proto = opt6.ip6e_nxt;
 				/* goto the next header */
 				break;
 			}
@@ -9000,7 +8923,7 @@ pf_test(sa_family_t af, int dir, int pflags, struct ifnet *ifp, struct mbuf **m0
 	}
 
 	if (pf_setup_pdesc(af, dir, &pd, m, &action, &reason, kif, &a, &r,
-		&ruleset, &off, &hdrlen, default_actions) == -1) {
+		&s, &ruleset, &off, &hdrlen, inp, default_actions) == -1) {
 		if (action != PF_PASS)
 			pd.act.log |= PF_LOG_FORCE;
 		goto done;
@@ -9060,32 +8983,17 @@ pf_test(sa_family_t af, int dir, int pflags, struct ifnet *ifp, struct mbuf **m0
 			m_tag_delete(m, mtag);
 	}
 
-	switch (af) {
-#ifdef INET
-	case AF_INET:
-		/* handle fragments that didn't get reassembled by normalization */
-		if (h->ip_off & htons(IP_MF | IP_OFFMASK)) {
-			action = pf_test_fragment(&r, kif, m, &pd, &a, &ruleset);
-			goto done;
-		}
-		break;
-#endif
 #ifdef INET6
-	case AF_INET6:
-		/*
-		 * we do not support jumbogram.  if we keep going, zero ip6_plen
-		 * will do something bad, so drop the packet for now.
-		 */
-		if (htons(h6->ip6_plen) == 0) {
-			action = PF_DROP;
-			REASON_SET(&reason, PFRES_NORM);	/*XXX*/
-			goto done;
-		}
-		break;
-#endif
-	default:
-		panic("Unknown af %d", af);
+	/*
+	 * we do not support jumbogram.  if we keep going, zero ip6_plen
+	 * will do something bad, so drop the packet for now.
+	 */
+	if (af == AF_INET6 && htons(h6->ip6_plen) == 0) {
+		action = PF_DROP;
+		REASON_SET(&reason, PFRES_NORM);	/*XXX*/
+		goto done;
 	}
+#endif
 
 	switch (pd.proto) {
 	case IPPROTO_TCP: {
diff --git a/sys/netpfil/pf/pf.h b/sys/netpfil/pf/pf.h
index 063abe83274b..77d4ea6c5e70 100644
--- a/sys/netpfil/pf/pf.h
+++ b/sys/netpfil/pf/pf.h
@@ -491,8 +491,8 @@ struct pf_rule {
 #define PF_SKIP_AF		2
 #define PF_SKIP_PROTO		3
 #define PF_SKIP_SRC_ADDR	4
-#define PF_SKIP_SRC_PORT	5
-#define PF_SKIP_DST_ADDR	6
+#define PF_SKIP_DST_ADDR	5
+#define PF_SKIP_SRC_PORT	6
 #define PF_SKIP_DST_PORT	7
 #define PF_SKIP_COUNT		8
 	union pf_rule_ptr	 skip[PF_SKIP_COUNT];