git: 8a42005d1e49 - main - pf: support basic L3 filtering in the Ethernet rules

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Mon, 14 Mar 2022 21:43:55 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=8a42005d1e491932666eb9f0be3e70ea1a28a3f7

commit 8a42005d1e491932666eb9f0be3e70ea1a28a3f7
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2022-03-08 08:48:11 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2022-03-14 21:42:37 +0000

    pf: support basic L3 filtering in the Ethernet rules
    
    Allow filtering based on the source or destination IP/IPv6 address in
    the Ethernet layer rules.
    
    Reviewed by:    pauamma_gundo.com (man), debdrup (man)
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D34482
---
 lib/libpfctl/libpfctl.c   | 10 +++++++-
 lib/libpfctl/libpfctl.h   |  1 +
 sbin/pfctl/parse.y        | 63 +++++++++++++++++++++++++++++++++++-----------
 sbin/pfctl/pfctl_parser.c |  7 +++++-
 share/man/man5/pf.conf.5  |  4 +--
 sys/net/pfvar.h           |  1 +
 sys/netpfil/pf/pf.c       | 64 +++++++++++++++++++++++++++++++++++++++++------
 sys/netpfil/pf/pf_nv.c    | 30 ++++++++++++++++++++++
 8 files changed, 154 insertions(+), 26 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 896ae1829e1a..8f064594260b 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -598,6 +598,11 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule)
 	pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "dst"),
 	    &rule->dst);
 
+	pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipsrc"),
+	    &rule->ipsrc);
+	pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipdst"),
+	    &rule->ipdst);
+
 	rule->evaluations = nvlist_get_number(nvl, "evaluations");
 	rule->packets[0] = nvlist_get_number(nvl, "packets-in");
 	rule->packets[1] = nvlist_get_number(nvl, "packets-out");
@@ -659,7 +664,7 @@ pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket,
     const char *path, struct pfctl_eth_rule *rule, bool clear,
     char *anchor_call)
 {
-	uint8_t buf[1024];
+	uint8_t buf[2048];
 	struct pfioc_nv nv;
 	nvlist_t *nvl;
 	void *data;
@@ -738,6 +743,9 @@ pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor,
 	nvlist_add_nvlist(nvl, "dst", addr);
 	nvlist_destroy(addr);
 
+	pfctl_nv_add_rule_addr(nvl, "ipsrc", &r->ipsrc);
+	pfctl_nv_add_rule_addr(nvl, "ipdst", &r->ipdst);
+
 	nvlist_add_string(nvl, "qname", r->qname);
 	nvlist_add_string(nvl, "tagname", r->tagname);
 	nvlist_add_number(nvl, "dnpipe", r->dnpipe);
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 02781403a3a2..b7f703b64def 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -89,6 +89,7 @@ struct pfctl_eth_rule {
 	uint8_t			 direction;
 	uint16_t		 proto;
 	struct pfctl_eth_addr	 src, dst;
+	struct pf_rule_addr	 ipsrc, ipdst;
 
 	/* Stats */
 	uint64_t		 evaluations;
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 8a0aa4279337..bcbbfe872c6c 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -350,7 +350,8 @@ 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_mac *, struct node_mac *, const char *);
+		    struct node_mac *, struct node_mac *,
+		    struct node_host *, struct node_host *, const char *);
 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 *,
@@ -492,7 +493,7 @@ int	parseport(char *, struct range *r, int);
 %token	REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
 %token	SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY
 %token	RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
-%token	ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES
+%token	ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES L3
 %token	ETHER
 %token	BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET
 %token	ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
@@ -523,7 +524,7 @@ int	parseport(char *, struct range *r, int);
 %type	<v.icmp>		icmp_list icmp_item
 %type	<v.icmp>		icmp6_list icmp6_item
 %type	<v.number>		reticmpspec reticmp6spec
-%type	<v.fromto>		fromto
+%type	<v.fromto>		fromto l3fromto
 %type	<v.peer>		ipportspec from to
 %type	<v.host>		ipspec toipspec xhost host dynaddr host_list
 %type	<v.host>		redir_host_list redirspec
@@ -1182,7 +1183,7 @@ scrubaction	: no SCRUB {
 		}
 		;
 
-etherrule	: ETHER action dir quick interface etherproto etherfromto etherfilter_opts
+etherrule	: ETHER action dir quick interface etherproto etherfromto l3fromto etherfilter_opts
 		{
 			struct pfctl_eth_rule	r;
 
@@ -1194,14 +1195,15 @@ etherrule	: ETHER action dir quick interface etherproto etherfromto etherfilter_
 			r.action = $2.b1;
 			r.direction = $3;
 			r.quick = $4.quick;
-			if ($8.tag != NULL)
-				memcpy(&r.tagname, $8.tag, sizeof(r.tagname));
-			if ($8.queues.qname != NULL)
-				memcpy(&r.qname, $8.queues.qname, sizeof(r.qname));
-			r.dnpipe = $8.dnpipe;
-			r.dnflags = $8.free_flags;
+			if ($9.tag != NULL)
+				memcpy(&r.tagname, $9.tag, sizeof(r.tagname));
+			if ($9.queues.qname != NULL)
+				memcpy(&r.qname, $9.queues.qname, sizeof(r.qname));
+			r.dnpipe = $9.dnpipe;
+			r.dnflags = $9.free_flags;
 
-			expand_eth_rule(&r, $5, $6, $7.src, $7.dst, "");
+			expand_eth_rule(&r, $5, $6, $7.src, $7.dst,
+			    $8.src.host, $8.dst.host, "");
 		}
 		;
 
@@ -1236,7 +1238,7 @@ etherpfa_anchor	: '{'
 		| /* empty */
 		;
 
-etheranchorrule	: ETHER ANCHOR anchorname dir quick interface etherproto etherfromto etherpfa_anchor
+etheranchorrule	: ETHER ANCHOR anchorname dir quick interface etherproto etherfromto l3fromto etherpfa_anchor
 		{
 			struct pfctl_eth_rule	r;
 
@@ -1286,6 +1288,7 @@ etheranchorrule	: ETHER ANCHOR anchorname dir quick interface etherproto etherfr
 			r.quick = $5.quick;
 
 			expand_eth_rule(&r, $6, $7, $8.src, $8.dst,
+			    $9.src.host, $9.dst.host,
 			    pf->eastack[pf->asd + 1] ? pf->ealast->name : $3);
 
 			free($3);
@@ -3254,6 +3257,13 @@ protoval	: STRING			{
 		}
 		;
 
+l3fromto	: /* empty */			{
+			bzero(&$$, sizeof($$));
+		}
+		| L3 fromto			{
+			$$ = $2;
+		}
+		;
 etherfromto	: ALL				{
 			$$.src = NULL;
 			$$.dst = NULL;
@@ -5733,23 +5743,45 @@ expand_queue(struct pf_altq *a, struct node_if *interfaces,
 		return (0);
 }
 
+static int
+pf_af_to_proto(sa_family_t af)
+{
+	if (af == AF_INET)
+		return (ETHERTYPE_IP);
+	if (af == AF_INET6)
+		return (ETHERTYPE_IPV6);
+
+	return (0);
+}
+
 void
 expand_eth_rule(struct pfctl_eth_rule *r,
     struct node_if *interfaces, struct node_etherproto *protos,
-    struct node_mac *srcs, struct node_mac *dsts, const char *anchor_call)
+    struct node_mac *srcs, struct node_mac *dsts,
+    struct node_host *ipsrcs, struct node_host *ipdsts, const char *anchor_call)
 {
 	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,
+	LOOP_THROUGH(struct node_host, ipsrc, ipsrcs,
+	LOOP_THROUGH(struct node_host, ipdst, ipdsts,
 		strlcpy(r->ifname, interface->ifname,
 		    sizeof(r->ifname));
 		r->ifnot = interface->not;
 		r->proto = proto->proto;
+		if (!r->proto && ipsrc->af)
+			r->proto = pf_af_to_proto(ipsrc->af);
+		else if (!r->proto && ipdst->af)
+			r->proto = pf_af_to_proto(ipdst->af);
 		bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
 		bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN);
 		r->src.neg = src->neg;
 		r->src.isset = src->isset;
+		r->ipsrc.addr = ipsrc->addr;
+		r->ipsrc.neg = ipsrc->not;
+		r->ipdst.addr = ipdst->addr;
+		r->ipdst.neg = ipdst->not;
 		bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
 		bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN);
 		r->dst.neg = dst->neg;
@@ -5757,12 +5789,14 @@ expand_eth_rule(struct pfctl_eth_rule *r,
 		r->nr = pf->eastack[pf->asd]->match++;
 
 		pfctl_append_eth_rule(pf, r, anchor_call);
-	))));
+	))))));
 
 	FREE_LIST(struct node_if, interfaces);
 	FREE_LIST(struct node_etherproto, protos);
 	FREE_LIST(struct node_mac, srcs);
 	FREE_LIST(struct node_mac, dsts);
+	FREE_LIST(struct node_host, ipsrcs);
+	FREE_LIST(struct node_host, ipdsts);
 }
 
 void
@@ -6052,6 +6086,7 @@ lookup(char *s)
 		{ "interval",		INTERVAL},
 		{ "keep",		KEEP},
 		{ "keepcounters",	KEEPCOUNTERS},
+		{ "l3",			L3},
 		{ "label",		LABEL},
 		{ "limit",		LIMIT},
 		{ "linkshare",		LINKSHARE},
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 1637d7358d0d..b6d1ebc127e1 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -782,7 +782,12 @@ print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call,
 		printf(" to ");
 		print_eth_addr(&r->dst);
 	}
-
+	if (r->proto == ETHERTYPE_IP || r->proto == ETHERTYPE_IPV6) {
+		printf(" l3");
+		print_fromto(&r->ipsrc, PF_OSFP_ANY, &r->ipdst,
+		    r->proto == ETHERTYPE_IP ? AF_INET : AF_INET6, 0,
+		    0, 0);
+	}
 	if (r->qname[0])
 		printf(" queue %s", r->qname);
 	if (r->tagname[0])
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index 13ff88586d8e..d136d9887cd5 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -28,7 +28,7 @@
 .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd September 25, 2021
+.Dd March 9, 2022
 .Dt PF.CONF 5
 .Os
 .Sh NAME
@@ -3072,7 +3072,7 @@ option         = "set" ( [ "timeout" ( timeout | "{" timeout-list "}" ) ] |
 
 ether-rule     = "ether" etheraction [ ( "in" | "out" ) ]
                  [ "quick" ] [ "on" ifspec ] [ etherprotospec ]
-                 etherhosts [ etherfilteropt-list ]
+                 etherhosts [ "l3" hosts ] [ etherfilteropt-list ]
 
 pf-rule        = action [ ( "in" | "out" ) ]
                  [ "log" [ "(" logopts ")"] ] [ "quick" ]
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 57acab788b20..a61feb07334f 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -646,6 +646,7 @@ struct pf_keth_rule {
 	uint8_t			 direction;
 	uint16_t		 proto;
 	struct pf_keth_rule_addr src, dst;
+	struct pf_rule_addr	 ipsrc, ipdst;
 
 	/* Stats */
 	counter_u64_t		 evaluations;
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index fb0abc8cd035..a900da0b8bd1 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -279,7 +279,7 @@ static u_int32_t	 pf_tcp_iss(struct pf_pdesc *);
 void			 pf_rule_to_actions(struct pf_krule *,
 			    struct pf_rule_actions *);
 static int		 pf_test_eth_rule(int, struct pfi_kkif *,
-			    struct mbuf *);
+			    struct mbuf **);
 static int		 pf_test_rule(struct pf_krule **, struct pf_kstate **,
 			    int, struct pfi_kkif *, struct mbuf *, int,
 			    struct pf_pdesc *, struct pf_krule **,
@@ -3826,31 +3826,67 @@ pf_match_eth_addr(const uint8_t *a, const struct pf_keth_rule_addr *r)
 }
 
 static int
-pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m)
+pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
 {
+	struct mbuf *m = *m0;
 	struct ether_header *e;
 	struct pf_keth_rule *r, *rm, *a = NULL;
 	struct pf_keth_ruleset *ruleset = NULL;
 	struct pf_mtag *mtag;
 	struct pf_keth_ruleq *rules;
+	struct pf_addr *src, *dst;
+	sa_family_t af = 0;
+	uint16_t proto;
 	int asd = 0, match = 0;
 	uint8_t action;
 	struct pf_keth_anchor_stackframe	anchor_stack[PF_ANCHOR_STACKSIZE];
 
-	NET_EPOCH_ASSERT();
-
 	MPASS(kif->pfik_ifp->if_vnet == curvnet);
 	NET_EPOCH_ASSERT();
 
 	SDT_PROBE3(pf, eth, test_rule, entry, dir, kif->pfik_ifp, m);
 
-	e = mtod(m, struct ether_header *);
-
 	ruleset = V_pf_keth;
 	rules = ck_pr_load_ptr(&ruleset->active.rules);
 	r = TAILQ_FIRST(rules);
 	rm = NULL;
 
+	e = mtod(m, struct ether_header *);
+	proto = ntohs(e->ether_type);
+
+	switch (proto) {
+	case ETHERTYPE_IP: {
+		struct ip *ip;
+		m = m_pullup(m, sizeof(struct ether_header) +
+		    sizeof(struct ip));
+		if (m == NULL) {
+			*m0 = NULL;
+			return (PF_DROP);
+		}
+		af = AF_INET;
+		ip = mtodo(m, sizeof(struct ether_header));
+		src = (struct pf_addr *)&ip->ip_src;
+		dst = (struct pf_addr *)&ip->ip_dst;
+		break;
+	}
+	case ETHERTYPE_IPV6: {
+		struct ip6_hdr *ip6;
+		m = m_pullup(m, sizeof(struct ether_header) +
+		    sizeof(struct ip6_hdr));
+		if (m == NULL) {
+			*m0 = NULL;
+			return (PF_DROP);
+		}
+		af = AF_INET6;
+		ip6 = mtodo(m, sizeof(struct ether_header));
+		src = (struct pf_addr *)&ip6->ip6_src;
+		dst = (struct pf_addr *)&ip6->ip6_dst;
+		break;
+	}
+	}
+	e = mtod(m, struct ether_header *);
+	*m0 = m;
+
 	while (r != NULL) {
 		counter_u64_add(r->evaluations, 1);
 		SDT_PROBE2(pf, eth, test_rule, test, r->nr, r);
@@ -3865,7 +3901,7 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m)
 			    "dir");
 			r = r->skip[PFE_SKIP_DIR].ptr;
 		}
-		else if (r->proto && r->proto != ntohs(e->ether_type)) {
+		else if (r->proto && r->proto != proto) {
 			SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r,
 			    "proto");
 			r = r->skip[PFE_SKIP_PROTO].ptr;
@@ -3880,6 +3916,18 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m)
 			    "dst");
 			r = TAILQ_NEXT(r, entries);
 		}
+		else if (af != 0 && PF_MISMATCHAW(&r->ipsrc.addr, src, af,
+		    r->ipsrc.neg, kif, M_GETFIB(m))) {
+			SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r,
+			    "ip_src");
+			r = TAILQ_NEXT(r, entries);
+		}
+		else if (af != 0 && PF_MISMATCHAW(&r->ipdst.addr, dst, af,
+		    r->ipdst.neg, kif, M_GETFIB(m))) {
+			SDT_PROBE3(pf, eth, test_rule, mismatch, r->nr, r,
+			    "ip_dst");
+			r = TAILQ_NEXT(r, entries);
+		}
 		else {
 			if (r->anchor == NULL) {
 				/* Rule matches */
@@ -6737,7 +6785,7 @@ pf_test_eth(int dir, int pflags, struct ifnet *ifp, struct mbuf **m0,
 		return (PF_PASS);
 
 	/* Stateless! */
-	return (pf_test_eth_rule(dir, kif, m));
+	return (pf_test_eth_rule(dir, kif, m0));
 }
 
 #ifdef INET
diff --git a/sys/netpfil/pf/pf_nv.c b/sys/netpfil/pf/pf_nv.c
index 42434dabf565..3b0d92cc5d20 100644
--- a/sys/netpfil/pf/pf_nv.c
+++ b/sys/netpfil/pf/pf_nv.c
@@ -1071,6 +1071,22 @@ pf_keth_rule_to_nveth_rule(const struct pf_keth_rule *krule)
 	}
 	nvlist_add_nvlist(nvl, "dst", addr);
 
+	addr = pf_rule_addr_to_nvrule_addr(&krule->ipsrc);
+	if (addr == NULL) {
+		nvlist_destroy(nvl);
+		return (NULL);
+	}
+	nvlist_add_nvlist(nvl, "ipsrc", addr);
+	nvlist_destroy(addr);
+
+	addr = pf_rule_addr_to_nvrule_addr(&krule->ipdst);
+	if (addr == NULL) {
+		nvlist_destroy(nvl);
+		return (NULL);
+	}
+	nvlist_add_nvlist(nvl, "ipdst", addr);
+	nvlist_destroy(addr);
+
 	nvlist_add_number(nvl, "evaluations",
 	    counter_u64_fetch(krule->evaluations));
 	nvlist_add_number(nvl, "packets-in",
@@ -1125,6 +1141,20 @@ pf_nveth_rule_to_keth_rule(const nvlist_t *nvl,
 			return (error);
 	}
 
+	if (nvlist_exists_nvlist(nvl, "ipsrc")) {
+		error = pf_nvrule_addr_to_rule_addr(
+		    nvlist_get_nvlist(nvl, "ipsrc"), &krule->ipsrc);
+		if (error != 0)
+			return (error);
+	}
+
+	if (nvlist_exists_nvlist(nvl, "ipdst")) {
+		error = pf_nvrule_addr_to_rule_addr(
+		    nvlist_get_nvlist(nvl, "ipdst"), &krule->ipdst);
+		if (error != 0)
+			return (error);
+	}
+
 	PFNV_CHK(pf_nvstring(nvl, "qname", krule->qname, sizeof(krule->qname)));
 	PFNV_CHK(pf_nvstring(nvl, "tagname", krule->tagname,
 	    sizeof(krule->tagname)));