The branch main has been updated by kp:

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

commit b590f17a11aacaeb0a70d075df04d40c6b7eb947
Author:     Kristof Provost <[email protected]>
AuthorDate: 2022-01-20 17:31:45 +0000
Commit:     Kristof Provost <[email protected]>
CommitDate: 2022-03-02 16:00:08 +0000

    pf: support masking mac addresses
    
    When filtering Ethernet packets allow rules to specify a mac address
    with a mask. This indicates which bits of the specified address are
    significant. This allows users to do things like filter based on device
    manufacturer.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 lib/libpfctl/libpfctl.c   |  5 +++
 lib/libpfctl/libpfctl.h   |  1 +
 sbin/pfctl/parse.y        | 97 +++++++++++++++++++++++++++++++++++++++++------
 sbin/pfctl/pfctl_parser.c | 33 +++++++++++++++-
 sbin/pfctl/pfctl_parser.h |  1 +
 share/man/man5/pf.conf.5  |  2 +
 sys/net/pfvar.h           |  1 +
 sys/netpfil/pf/pf.c       | 12 ++++--
 sys/netpfil/pf/pf_nv.c    |  4 ++
 9 files changed, 140 insertions(+), 16 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 90733d421572..07699d66deaa 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -557,6 +557,10 @@ pfctl_nveth_addr_to_eth_addr(const nvlist_t *nvl, struct 
pfctl_eth_addr *addr)
        assert(len == sizeof(addr->addr));
        memcpy(addr->addr, data, sizeof(addr->addr));
 
+       data = nvlist_get_binary(nvl, "mask", &len);
+       assert(len == sizeof(addr->mask));
+       memcpy(addr->mask, data, sizeof(addr->mask));
+
        addr->neg = nvlist_get_bool(nvl, "neg");
 
        /* To make checks for 'is this address set?' easier. */
@@ -574,6 +578,7 @@ pfctl_eth_addr_to_nveth_addr(const struct pfctl_eth_addr 
*addr)
 
        nvlist_add_bool(nvl, "neg", addr->neg);
        nvlist_add_binary(nvl, "addr", &addr->addr, ETHER_ADDR_LEN);
+       nvlist_add_binary(nvl, "mask", &addr->mask, ETHER_ADDR_LEN);
 
        return (nvl);
 }
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 256fa49c4f25..4f8f55945d4b 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -73,6 +73,7 @@ struct pfctl_eth_rules_info {
 
 struct pfctl_eth_addr {
        uint8_t addr[ETHER_ADDR_LEN];
+       uint8_t mask[ETHER_ADDR_LEN];
        bool    neg;
        bool    isset;
 };
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 5f10c4ab2e17..346ec9d9a587 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -377,6 +377,9 @@ int  invalid_redirect(struct node_host *, sa_family_t);
 u_int16_t parseicmpspec(char *, sa_family_t);
 int     kw_casecmp(const void *, const void *);
 int     map_tos(char *string, int *);
+struct node_mac* node_mac_from_string(const char *);
+struct node_mac* node_mac_from_string_masklen(const char *, int);
+struct node_mac* node_mac_from_string_mask(const char *, const char *);
 
 static TAILQ_HEAD(loadanchorshead, loadanchors)
     loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead);
@@ -3277,22 +3280,25 @@ etherto         : /* empty */                   {
                }
                ;
 
-mac            : string                        {
-                       $$ = calloc(1, sizeof(struct node_mac));
+mac            : string '/' NUMBER             {
+                       $$ = node_mac_from_string_masklen($1, $3);
+                       free($1);
                        if ($$ == NULL)
-                               err(1, "mac: calloc");
-
-                       if (sscanf($1, 
"%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
-                           &$$->mac[0], &$$->mac[1], &$$->mac[2], &$$->mac[3], 
&$$->mac[4],
-                           &$$->mac[5]) != 6) {
-                               free($$);
-                               free($1);
-                               yyerror("invalid MAC address");
                                YYERROR;
+               }
+               | string                        {
+                       if (strchr($1, '&')) {
+                               /* mac&mask */
+                               char *mac = strtok($1, "&");
+                               char *mask = strtok(NULL, "&");
+                               $$ = node_mac_from_string_mask(mac, mask);
+                       } else {
+                               $$ = node_mac_from_string($1);
                        }
                        free($1);
-                       $$->next = NULL;
-                       $$->tail = $$;
+                       if ($$ == NULL)
+                               YYERROR;
+
                }
 xmac           : not mac {
                        struct node_mac *n;
@@ -5741,8 +5747,10 @@ expand_eth_rule(struct pfctl_eth_rule *r,
                r->ifnot = interface->not;
                r->proto = proto->proto;
                bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
+               bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN);
                r->src.neg = src->neg;
                bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
+               bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN);
                r->dst.neg = dst->neg;
                r->nr = pf->eastack[pf->asd]->match++;
 
@@ -6899,3 +6907,68 @@ rt_tableid_max(void)
        return (RT_TABLEID_MAX);
 #endif
 }
+
+struct node_mac*
+node_mac_from_string(const char *str)
+{
+       struct node_mac *m;
+
+       m = calloc(1, sizeof(struct node_mac));
+       if (m == NULL)
+               err(1, "mac: calloc");
+
+       if (sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+           &m->mac[0], &m->mac[1], &m->mac[2], &m->mac[3], &m->mac[4],
+           &m->mac[5]) != 6) {
+               free(m);
+               yyerror("invalid MAC address");
+               return (NULL);
+       }
+
+       memset(m->mask, 0xff, ETHER_ADDR_LEN);
+       m->next = NULL;
+       m->tail = m;
+
+       return (m);
+}
+
+struct node_mac*
+node_mac_from_string_masklen(const char *str, int masklen)
+{
+       struct node_mac *m;
+
+       if (masklen < 0 || masklen > (ETHER_ADDR_LEN * 8)) {
+               yyerror("invalid MAC mask length");
+               return (NULL);
+       }
+
+       m = node_mac_from_string(str);
+       if (m == NULL)
+               return (NULL);
+
+       memset(m->mask, 0, ETHER_ADDR_LEN);
+       for (int i = 0; i < masklen; i++)
+               m->mask[i / 8] |= 1 << (i % 8);
+
+       return (m);
+}
+
+struct node_mac*
+node_mac_from_string_mask(const char *str, const char *mask)
+{
+       struct node_mac *m;
+
+       m = node_mac_from_string(str);
+       if (m == NULL)
+               return (NULL);
+
+       if (sscanf(mask, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+           &m->mask[0], &m->mask[1], &m->mask[2], &m->mask[3], &m->mask[4],
+           &m->mask[5]) != 6) {
+               free(m);
+               yyerror("invalid MAC mask");
+               return (NULL);
+       }
+
+       return (m);
+}
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index 0db0ad355cf7..1637d7358d0d 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -694,7 +694,9 @@ print_src_node(struct pf_src_node *sn, int opts)
 static void
 print_eth_addr(const struct pfctl_eth_addr *a)
 {
-       int i;
+       int i, masklen = ETHER_ADDR_LEN * 8;
+       bool seen_unset = false;
+
        for (i = 0; i < ETHER_ADDR_LEN; i++) {
                if (a->addr[i] != 0)
                        break;
@@ -707,6 +709,35 @@ print_eth_addr(const struct pfctl_eth_addr *a)
        printf("%s%02x:%02x:%02x:%02x:%02x:%02x", a->neg ? "! " : "",
            a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->addr[4],
            a->addr[5]);
+
+       for (i = 0; i < (ETHER_ADDR_LEN * 8); i++) {
+               bool isset = a->mask[i / 8] & (1 << i % 8);
+
+               if (! seen_unset) {
+                       if (isset)
+                               continue;
+                       seen_unset = true;
+                       masklen = i;
+               } else {
+                       /* Not actually a continuous mask, so print the whole
+                        * thing. */
+                       if (isset)
+                               break;
+                       continue;
+               }
+       }
+
+       if (masklen == (ETHER_ADDR_LEN * 8))
+               return;
+
+       if (i == (ETHER_ADDR_LEN * 8)) {
+               printf("/%d", masklen);
+               return;
+       }
+
+       printf("&%02x:%02x:%02x:%02x:%02x:%02x",
+           a->mask[0], a->mask[1], a->mask[2], a->mask[3], a->mask[4],
+           a->mask[5]);
 }
 
 void
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
index e60132d15855..60bbae7a3fcd 100644
--- a/sbin/pfctl/pfctl_parser.h
+++ b/sbin/pfctl/pfctl_parser.h
@@ -138,6 +138,7 @@ struct node_host {
 
 struct node_mac {
        u_int8_t         mac[ETHER_ADDR_LEN];
+       u_int8_t         mask[ETHER_ADDR_LEN];
        bool             neg;
        struct node_mac *next;
        struct node_mac *tail;
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index 1bed985901f2..13ff88586d8e 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -3178,6 +3178,8 @@ protospec      = "proto" ( proto-name | proto-number |
 proto-list     = ( proto-name | proto-number ) [ [ "," ] proto-list ]
 
 etherhosts     = "from" macaddress "to" macaddress
+macaddress     = mac | mac "/" masklen | mac "&" mask
+
 hosts          = "all" |
                  "from" ( "any" | "no-route" | "urpf-failed" | "self" | host |
                  "{" host-list "}" ) [ port ] [ os ]
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index a2265e7cc1c5..252b3adbe958 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -579,6 +579,7 @@ union pf_keth_rule_ptr {
 
 struct pf_keth_rule_addr {
        uint8_t addr[ETHER_ADDR_LEN];
+       uint8_t mask[ETHER_ADDR_LEN];
        bool neg;
        uint8_t isset;
 };
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index bd82c2a9904e..fb0abc8cd035 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -3809,14 +3809,20 @@ pf_tcp_iss(struct pf_pdesc *pd)
 static bool
 pf_match_eth_addr(const uint8_t *a, const struct pf_keth_rule_addr *r)
 {
+       bool match = true;
+
        /* Always matches if not set */
        if (! r->isset)
                return (!r->neg);
 
-       if (memcmp(a, r->addr, ETHER_ADDR_LEN) == 0)
-               return (!r->neg);
+       for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+               if ((a[i] & r->mask[i]) != (r->addr[i] & r->mask[i])) {
+                       match = false;
+                       break;
+               }
+       }
 
-       return (r->neg);
+       return (match ^ r->neg);
 }
 
 static int
diff --git a/sys/netpfil/pf/pf_nv.c b/sys/netpfil/pf/pf_nv.c
index 5fc222ff3b79..42434dabf565 100644
--- a/sys/netpfil/pf/pf_nv.c
+++ b/sys/netpfil/pf/pf_nv.c
@@ -1013,6 +1013,9 @@ pf_nveth_rule_addr_to_keth_rule_addr(const nvlist_t *nvl,
 
        PFNV_CHK(pf_nvbinary(nvl, "addr", &krule->addr, sizeof(krule->addr)));
        PFNV_CHK(pf_nvbool(nvl, "neg", &krule->neg));
+       if (nvlist_exists_binary(nvl, "mask"))
+               PFNV_CHK(pf_nvbinary(nvl, "mask", &krule->mask,
+                   sizeof(krule->mask)));
 
        /* To make checks for 'is this address set?' easier. */
        if (memcmp(krule->addr, EMPTY_MAC, ETHER_ADDR_LEN) != 0)
@@ -1032,6 +1035,7 @@ pf_keth_rule_addr_to_nveth_rule_addr(const struct 
pf_keth_rule_addr *krule)
                return (NULL);
 
        nvlist_add_binary(nvl, "addr", &krule->addr, sizeof(krule->addr));
+       nvlist_add_binary(nvl, "mask", &krule->mask, sizeof(krule->mask));
        nvlist_add_bool(nvl, "neg", krule->neg);
 
        return (nvl);

Reply via email to