git: 1f61367f8d61 - main - pf: support matching on tags for Ethernet rules

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Mon, 20 Jun 2022 09:09:07 UTC
The branch main has been updated by kp:

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

commit 1f61367f8d61fd6963a47296a86f553c403b5f91
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2022-05-31 12:00:52 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2022-06-20 08:16:20 +0000

    pf: support matching on tags for Ethernet rules
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D35362
---
 lib/libpfctl/libpfctl.c   |  6 ++++++
 lib/libpfctl/libpfctl.h   |  3 +++
 sbin/pfctl/parse.y        | 33 +++++++++++++++++++++++++++++++++
 sbin/pfctl/pfctl_parser.c |  5 +++++
 share/man/man5/pf.conf.5  |  7 ++++++-
 sys/net/pfvar.h           |  4 ++++
 sys/netpfil/pf/pf.c       | 23 +++++++++++++++++++++--
 sys/netpfil/pf/pf_ioctl.c |  6 ++++++
 sys/netpfil/pf/pf_nv.c    |  9 +++++++++
 9 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 6a883a814902..3adfb7b94af3 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -629,6 +629,10 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule)
 	rule->ifnot = nvlist_get_bool(nvl, "ifnot");
 	rule->direction = nvlist_get_number(nvl, "direction");
 	rule->proto = nvlist_get_number(nvl, "proto");
+	strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"),
+	    PF_TAG_NAME_SIZE);
+	rule->match_tag = nvlist_get_number(nvl, "match_tag");
+	rule->match_tag_not = nvlist_get_bool(nvl, "match_tag_not");
 
 	pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "src"),
 	    &rule->src);
@@ -780,6 +784,8 @@ pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor,
 	nvlist_add_bool(nvl, "ifnot", r->ifnot);
 	nvlist_add_number(nvl, "direction", r->direction);
 	nvlist_add_number(nvl, "proto", r->proto);
+	nvlist_add_string(nvl, "match_tagname", r->match_tagname);
+	nvlist_add_bool(nvl, "match_tag_not", r->match_tag_not);
 
 	addr = pfctl_eth_addr_to_nveth_addr(&r->src);
 	if (addr == NULL) {
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index ab4b6a27c787..261913e1873d 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -94,6 +94,9 @@ struct pfctl_eth_rule {
 	uint16_t		 proto;
 	struct pfctl_eth_addr	 src, dst;
 	struct pf_rule_addr	 ipsrc, ipdst;
+	char			 match_tagname[PF_TAG_NAME_SIZE];
+	uint16_t		 match_tag;
+	bool			 match_tag_not;
 
 	/* Stats */
 	uint64_t		 evaluations;
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 21729fc7ba4e..506716bca689 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -1197,6 +1197,14 @@ etherrule	: ETHER action dir quick interface etherproto etherfromto l3fromto eth
 			r.quick = $4.quick;
 			if ($9.tag != NULL)
 				memcpy(&r.tagname, $9.tag, sizeof(r.tagname));
+			if ($9.match_tag)
+				if (strlcpy(r.match_tagname, $9.match_tag,
+				    PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+					yyerror("tag too long, max %u chars",
+					    PF_TAG_NAME_SIZE - 1);
+					YYERROR;
+				}
+			r.match_tag_not = $9.match_tag_not;
 			if ($9.queues.qname != NULL)
 				memcpy(&r.qname, $9.queues.qname, sizeof(r.qname));
 			r.dnpipe = $9.dnpipe;
@@ -1320,6 +1328,10 @@ etherfilter_opt	: etherqname	{
 		| TAG string				{
 			filter_opts.tag = $2;
 		}
+		| not TAGGED string			{
+			filter_opts.match_tag = $3;
+			filter_opts.match_tag_not = $1;
+		}
 		| DNPIPE number {
 			filter_opts.dnpipe = $2;
 			filter_opts.free_flags |= PFRULE_DN_IS_PIPE;
@@ -5772,6 +5784,18 @@ expand_eth_rule(struct pfctl_eth_rule *r,
     struct node_mac *srcs, struct node_mac *dsts,
     struct node_host *ipsrcs, struct node_host *ipdsts, const char *anchor_call)
 {
+	char tagname[PF_TAG_NAME_SIZE];
+	char match_tagname[PF_TAG_NAME_SIZE];
+	char qname[PF_QNAME_SIZE];
+
+	if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname))
+		errx(1, "expand_eth_rule: tagname");
+	if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >=
+	    sizeof(match_tagname))
+		errx(1, "expand_eth_rule: match_tagname");
+	if (strlcpy(qname, r->qname, sizeof(qname)) >= sizeof(qname))
+		errx(1, "expand_eth_rule: qname");
+
 	LOOP_THROUGH(struct node_if, interface, interfaces,
 	LOOP_THROUGH(struct node_etherproto, proto, protos,
 	LOOP_THROUGH(struct node_mac, src, srcs,
@@ -5800,6 +5824,15 @@ expand_eth_rule(struct pfctl_eth_rule *r,
 		r->dst.isset = dst->isset;
 		r->nr = pf->eastack[pf->asd]->match++;
 
+		if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >=
+		    sizeof(r->tagname))
+			errx(1, "expand_eth_rule: r->tagname");
+		if (strlcpy(r->match_tagname, match_tagname,
+		    sizeof(r->match_tagname)) >= sizeof(r->match_tagname))
+			errx(1, "expand_eth_rule: r->match_tagname");
+		if (strlcpy(r->qname, qname, sizeof(r->qname)) >= sizeof(r->qname))
+			errx(1, "expand_eth_rule: r->qname");
+
 		pfctl_append_eth_rule(pf, r, anchor_call);
 	))))));
 
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 1f6a194591c0..a05683f0cbce 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -791,6 +791,11 @@ print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call,
 		printf(" queue %s", r->qname);
 	if (r->tagname[0])
 		printf(" tag %s", r->tagname);
+	if (r->match_tagname[0]) {
+		if (r->match_tag_not)
+			printf(" !");
+		printf(" tagged %s", r->match_tagname);
+	}
 	if (r->dnpipe)
 		printf(" %s %d",
 		    r->dnflags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue",
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index d136d9887cd5..267d84387fe9 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -744,6 +744,11 @@ is not the last matching rule.
 Further matching rules can replace the tag with a
 new one but will not remove a previously applied tag.
 A packet is only ever assigned one tag at a time.
+.It Ar tagged Aq Ar string
+Used to specify that packets must already be tagged with the given tag in order
+to match the rule.
+Inverse tag matching can also be done by specifying the !  operator before the
+tagged keyword.
 .Sh TRAFFIC NORMALIZATION
 Traffic normalization is used to sanitize packet content in such
 a way that there are no ambiguities in packet interpretation on
@@ -3083,7 +3088,7 @@ logopts        = logopt [ "," logopts ]
 logopt         = "all" | "user" | "to" interface-name
 
 etherfilteropt-list = etherfilteropt-list etherfilteropt | etherfilteropt
-etherfilteropt = "tag" string | "queue" ( string )
+etherfilteropt = "tag" string | "tagged" string | "queue" ( string )
 
 filteropt-list = filteropt-list filteropt | filteropt
 filteropt      = user | group | flags | icmp-type | icmp6-type | "tos" tos |
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 4ad758fe439b..ef30058966dc 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -672,6 +672,10 @@ struct pf_keth_rule {
 	uint16_t		 proto;
 	struct pf_keth_rule_addr src, dst;
 	struct pf_rule_addr	 ipsrc, ipdst;
+	char			 match_tagname[PF_TAG_NAME_SIZE];
+	uint16_t		 match_tag;
+	bool			 match_tag_not;
+
 
 	/* Stats */
 	counter_u64_t		 evaluations;
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 8e3cd98879a6..172ed52dbc03 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -3835,6 +3835,16 @@ pf_match_eth_addr(const uint8_t *a, const struct pf_keth_rule_addr *r)
 	return (match ^ r->neg);
 }
 
+static int
+pf_match_eth_tag(struct mbuf *m, struct pf_keth_rule *r, int *tag, int mtag)
+{
+	if (*tag == -1)
+		*tag = mtag;
+
+	return ((!r->match_tag_not && r->match_tag == *tag) ||
+	    (r->match_tag_not && r->match_tag != *tag));
+}
+
 static int
 pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 {
@@ -3848,6 +3858,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 	sa_family_t af = 0;
 	uint16_t proto;
 	int asd = 0, match = 0;
+	int tag = -1;
 	uint8_t action;
 	struct pf_keth_anchor_stackframe	anchor_stack[PF_ANCHOR_STACKSIZE];
 
@@ -3959,7 +3970,15 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 			    "ip_dst");
 			r = TAILQ_NEXT(r, entries);
 		}
+		else if (r->match_tag && !pf_match_eth_tag(m, r, &tag,
+		    mtag ? mtag->tag : 0)) {
+			SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r,
+			    "match_tag");
+			r = TAILQ_NEXT(r, entries);
+		}
 		else {
+			if (r->tag)
+				tag = r->tag;
 			if (r->anchor == NULL) {
 				/* Rule matches */
 				rm = r;
@@ -4001,7 +4020,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 		return (PF_DROP);
 	}
 
-	if (r->tag > 0) {
+	if (tag > 0) {
 		if (mtag == NULL)
 			mtag = pf_get_mtag(m);
 		if (mtag == NULL) {
@@ -4009,7 +4028,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 			counter_u64_add(V_pf_status.counters[PFRES_MEMORY], 1);
 			return (PF_DROP);
 		}
-		mtag->tag = r->tag;
+		mtag->tag = tag;
 	}
 
 	if (r->qid != 0) {
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 1ccbbd3814ac..c07df7e6c05e 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -515,6 +515,8 @@ pf_free_eth_rule(struct pf_keth_rule *rule)
 
 	if (rule->tag)
 		tag_unref(&V_pf_tags, rule->tag);
+	if (rule->match_tag)
+		tag_unref(&V_pf_tags, rule->match_tag);
 #ifdef ALTQ
 	pf_qid_unref(rule->qid);
 #endif
@@ -2891,6 +2893,10 @@ DIOCGETETHRULE_error:
 		if (rule->tagname[0])
 			if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0)
 				error = EBUSY;
+		if (rule->match_tagname[0])
+			if ((rule->match_tag = pf_tagname2tag(
+			    rule->match_tagname)) == 0)
+				error = EBUSY;
 
 		if (error == 0 && rule->ipdst.addr.type == PF_ADDR_TABLE)
 			error = pf_eth_addr_setup(ruleset, &rule->ipdst.addr);
diff --git a/sys/netpfil/pf/pf_nv.c b/sys/netpfil/pf/pf_nv.c
index 5476b59d859f..456433ccbf70 100644
--- a/sys/netpfil/pf/pf_nv.c
+++ b/sys/netpfil/pf/pf_nv.c
@@ -1057,6 +1057,9 @@ pf_keth_rule_to_nveth_rule(const struct pf_keth_rule *krule)
 	nvlist_add_bool(nvl, "ifnot", krule->ifnot);
 	nvlist_add_number(nvl, "direction", krule->direction);
 	nvlist_add_number(nvl, "proto", krule->proto);
+	nvlist_add_string(nvl, "match_tagname", krule->match_tagname);
+	nvlist_add_number(nvl, "match_tag", krule->match_tag);
+	nvlist_add_bool(nvl, "match_tag_not", krule->match_tag_not);
 
 	addr = pf_keth_rule_addr_to_nveth_rule_addr(&krule->src);
 	if (addr == NULL) {
@@ -1165,6 +1168,12 @@ pf_nveth_rule_to_keth_rule(const nvlist_t *nvl,
 			return (EINVAL);
 	}
 
+	if (nvlist_exists_string(nvl, "match_tagname")) {
+		PFNV_CHK(pf_nvstring(nvl, "match_tagname", krule->match_tagname,
+		    sizeof(krule->match_tagname)));
+		PFNV_CHK(pf_nvbool(nvl, "match_tag_not", &krule->match_tag_not));
+	}
+
 	PFNV_CHK(pf_nvstring(nvl, "qname", krule->qname, sizeof(krule->qname)));
 	PFNV_CHK(pf_nvstring(nvl, "tagname", krule->tagname,
 	    sizeof(krule->tagname)));