The branch main has been updated by kp:

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

commit 1f61367f8d61fd6963a47296a86f553c403b5f91
Author:     Kristof Provost <k...@freebsd.org>
AuthorDate: 2022-05-31 12:00:52 +0000
Commit:     Kristof Provost <k...@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)));

Reply via email to