git: 87a89d6e14ac - main - pfctl: support lists of mac addresses

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Wed, 02 Mar 2022 16:01:00 UTC
The branch main has been updated by kp:

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

commit 87a89d6e14ac5730572d454ec12a3a30d492816e
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2021-09-30 15:09:57 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2022-03-02 16:00:07 +0000

    pfctl: support lists of mac addresses
    
    Teach the 'ether' rules to accept { mac1, mac2, ... } lists, similar to
    the lists of interfaces or IP addresses we already supported for layer 3
    filtering.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D32481
---
 sbin/pfctl/parse.y            | 98 ++++++++++++++++++++++++++++---------------
 sbin/pfctl/pfctl_parser.h     |  7 ++++
 tests/sys/netpfil/pf/ether.sh | 15 +++++++
 3 files changed, 87 insertions(+), 33 deletions(-)

diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index a2d8cfef8232..b856621ecf41 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -349,7 +349,8 @@ void		 expand_label_proto(const char *, char *, size_t, u_int8_t);
 void		 expand_label_nr(const char *, char *, size_t,
 		    struct pfctl_rule *);
 void		 expand_eth_rule(struct pfctl_eth_rule *,
-		    struct node_if *, struct node_etherproto *);
+		    struct node_if *, struct node_etherproto *,
+		    struct node_mac *, struct node_mac *);
 void		 expand_rule(struct pfctl_rule *, struct node_if *,
 		    struct node_host *, struct node_proto *, struct node_os *,
 		    struct node_host *, struct node_port *, struct node_host *,
@@ -419,15 +420,12 @@ typedef struct {
 			struct node_os	*src_os;
 		}			 fromto;
 		struct {
-			u_int8_t	 src[ETHER_ADDR_LEN];
-			u_int8_t	 srcneg;
-			u_int8_t	 dst[ETHER_ADDR_LEN];
-			u_int8_t	 dstneg;
+			struct node_mac	*src;
+			struct node_mac	*dst;
 		}			 etherfromto;
-		u_int8_t		 mac[ETHER_ADDR_LEN];
+		struct node_mac		*mac;
 		struct {
-			uint8_t		 mac[ETHER_ADDR_LEN];
-			u_int8_t	 neg;
+			struct node_mac	*mac;
 		} etheraddr;
 		struct {
 			struct node_host	*host;
@@ -560,7 +558,7 @@ int	parseport(char *, struct range *r, int);
 %type	<v.etherproto>		etherproto etherproto_list etherproto_item
 %type	<v.etherfromto>		etherfromto
 %type	<v.etheraddr>		etherfrom etherto
-%type	<v.mac>			mac
+%type	<v.mac>			xmac mac mac_list macspec
 %%
 
 ruleset		: /* empty */
@@ -1191,11 +1189,6 @@ etherrule	: ETHER action dir quick interface etherproto etherfromto etherfilter_
 			r.action = $2.b1;
 			r.direction = $3;
 			r.quick = $4.quick;
-			/* XXX TODO: ! support */
-			memcpy(&r.src.addr, $7.src, sizeof(r.src.addr));
-			r.src.neg = $7.srcneg;
-			memcpy(&r.dst.addr, $7.dst, sizeof(r.dst.addr));
-			r.dst.neg = $7.dstneg;
 			if ($8.tag != NULL)
 				memcpy(&r.tagname, $8.tag, sizeof(r.tagname));
 			if ($8.queues.qname != NULL)
@@ -1203,7 +1196,7 @@ etherrule	: ETHER action dir quick interface etherproto etherfromto etherfilter_
 			r.dnpipe = $8.dnpipe;
 			r.dnflags = $8.free_flags;
 
-			expand_eth_rule(&r, $5, $6);
+			expand_eth_rule(&r, $5, $6, $7.src, $7.dst);
 		}
 		;
 
@@ -3169,48 +3162,78 @@ protoval	: STRING			{
 		;
 
 etherfromto	: ALL				{
-			bzero($$.src, sizeof($$.src));
-			$$.srcneg = 0;
-			bzero($$.dst, sizeof($$.dst));
-			$$.dstneg = 0;
+			$$.src = NULL;
+			$$.dst = NULL;
 		}
 		| etherfrom etherto		{
-			memcpy(&$$.src, $1.mac, sizeof($$.src));
-			$$.srcneg = $1.neg;
-			memcpy(&$$.dst, $2.mac, sizeof($$.dst));
-			$$.dstneg = $2.neg;
+			$$.src = $1.mac;
+			$$.dst = $2.mac;
 		}
 		;
 
 etherfrom	: /* emtpy */			{
 			bzero(&$$, sizeof($$));
 		}
-		| FROM not mac			{
-			memcpy(&$$.mac, $3, sizeof($$));
-			$$.neg = $2;
+		| FROM macspec			{
+			$$.mac = $2;
 		}
 		;
 
 etherto		: /* empty */			{
 			bzero(&$$, sizeof($$));
 		}
-		| TO not mac			{
-			memcpy(&$$.mac, $3, sizeof($$));
-			$$.neg = $2;
+		| TO macspec			{
+			$$.mac = $2;
 		}
 		;
 
 mac		: string			{
+			$$ = calloc(1, sizeof(struct node_mac));
+			if ($$ == NULL)
+				err(1, "mac: calloc");
+
 			if (sscanf($1, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
-			    &$$[0], &$$[1], &$$[2], &$$[3], &$$[4],
-			    &$$[5]) != 6) {
+			    &$$->mac[0], &$$->mac[1], &$$->mac[2], &$$->mac[3], &$$->mac[4],
+			    &$$->mac[5]) != 6) {
 				free($$);
 				free($1);
 				yyerror("invalid MAC address");
 				YYERROR;
 			}
+			free($1);
+			$$->next = NULL;
+			$$->tail = $$;
+		}
+xmac		: not mac {
+			struct node_mac	*n;
+
+			for (n = $2; n != NULL; n = n->next)
+				n->neg = $1;
+			$$ = $2;
 		}
 		;
+macspec		: xmac {
+			$$ = $1;
+		}
+		| '{' optnl mac_list '}'
+		{
+			$$ = $3;
+		}
+		;
+mac_list	: xmac optnl {
+			$$ = $1;
+		}
+		| mac_list comma xmac {
+			if ($3 == NULL)
+				$$ = $1;
+			else if ($1 == NULL)
+				$$ = $3;
+			else {
+				$1->tail->next = $3;
+				$1->tail = $3->tail;
+				$$ = $1;
+			}
+		}
 
 fromto		: ALL				{
 			$$.src.host = NULL;
@@ -5616,27 +5639,36 @@ expand_queue(struct pf_altq *a, struct node_if *interfaces,
 
 void
 expand_eth_rule(struct pfctl_eth_rule *r,
-    struct node_if *interfaces, struct node_etherproto *protos)
+    struct node_if *interfaces, struct node_etherproto *protos,
+    struct node_mac *srcs, struct node_mac *dsts)
 {
 	struct pfctl_eth_rule *rule;
 
 	LOOP_THROUGH(struct node_if, interface, interfaces,
 	LOOP_THROUGH(struct node_etherproto, proto, protos,
+	LOOP_THROUGH(struct node_mac, src, srcs,
+	LOOP_THROUGH(struct node_mac, dst, dsts,
 		r->nr = pf->eth_nr++;
 		strlcpy(r->ifname, interface->ifname,
 		    sizeof(r->ifname));
 		r->ifnot = interface->not;
 		r->proto = proto->proto;
+		bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
+		r->src.neg = src->neg;
+		bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
+		r->dst.neg = dst->neg;
 
 		if ((rule = calloc(1, sizeof(*rule))) == NULL)
 			err(1, "calloc");
 		bcopy(r, rule, sizeof(*rule));
 
 		TAILQ_INSERT_TAIL(&pf->eth_rules, rule, entries);
-	));
+	))));
 
 	FREE_LIST(struct node_if, interfaces);
 	FREE_LIST(struct node_etherproto, protos);
+	FREE_LIST(struct node_mac, srcs);
+	FREE_LIST(struct node_mac, dsts);
 }
 
 void
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index 1d69682b1b6c..990f860fe8e3 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -135,6 +135,13 @@ struct node_host {
 	struct node_host	*tail;
 };
 
+struct node_mac {
+	u_int8_t	 mac[ETHER_ADDR_LEN];
+	bool		 neg;
+	struct node_mac	*next;
+	struct node_mac	*tail;
+};
+
 struct node_os {
 	char			*os;
 	pf_osfp_t		 fingerprint;
diff --git a/tests/sys/netpfil/pf/ether.sh b/tests/sys/netpfil/pf/ether.sh
index a7e23779396f..8ca8d3fbf0c8 100644
--- a/tests/sys/netpfil/pf/ether.sh
+++ b/tests/sys/netpfil/pf/ether.sh
@@ -66,6 +66,11 @@ mac_body()
 		"ether block to 00:01:02:03:04:05"
 	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
 
+	# Should still fail for 'to', even if it's in a list
+	pft_set_rules alcatraz \
+		"ether block to { ${epair_a_mac}, 00:01:02:0:04:05 }"
+	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
+
 	# Now try this with an interface specified
 	pft_set_rules alcatraz \
 		"ether block on ${epair}b from ${epair_a_mac}"
@@ -84,6 +89,16 @@ mac_body()
 	pft_set_rules alcatraz \
 		"ether block out on ${epair}b to ! ${epair_a_mac}"
 	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
+
+	# Block everything not us
+	pft_set_rules alcatraz \
+		"ether block out on ${epair}b to { ! ${epair_a_mac} }"
+	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
+
+	# Block us now
+	pft_set_rules alcatraz \
+		"ether block out on ${epair}b to { ! 00:01:02:03:04:05 }"
+	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
 }
 
 mac_cleanup()