diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -94,6 +94,9 @@ 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/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -629,6 +629,10 @@ 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 @@ 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/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -1197,6 +1197,14 @@ 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 @@ | 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 @@ 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 @@ 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 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -791,6 +791,11 @@ 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 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -744,6 +744,11 @@ 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 @@ 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 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -672,6 +672,10 @@ 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 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -3835,6 +3835,16 @@ 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 @@ 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 @@ "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 @@ 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 @@ 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 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -515,6 +515,8 @@ 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 @@ 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 --- a/sys/netpfil/pf/pf_nv.c +++ b/sys/netpfil/pf/pf_nv.c @@ -1057,6 +1057,9 @@ 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 @@ 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)));