pf: support matching on tags for Ethernet rules

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D35362
This commit is contained in:
Kristof Provost 2022-05-31 14:00:52 +02:00
parent e417249016
commit 1f61367f8d
9 changed files with 93 additions and 3 deletions

View file

@ -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) {

View file

@ -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;

View file

@ -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);
))))));

View file

@ -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",

View file

@ -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 |

View file

@ -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;

View file

@ -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) {

View file

@ -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 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
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);

View file

@ -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)));