diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -37,6 +37,7 @@ #include struct pfctl_anchor; +struct pfctl_eth_anchor; struct pfctl_status_counter { uint64_t id; @@ -100,11 +101,28 @@ uint32_t dnflags; uint8_t action; + struct pfctl_eth_anchor *anchor; + uint8_t anchor_relative; + uint8_t anchor_wildcard; + TAILQ_ENTRY(pfctl_eth_rule) entries; }; - TAILQ_HEAD(pfctl_eth_rules, pfctl_eth_rule); +struct pfctl_eth_ruleset { + struct pfctl_eth_rules rules; + struct pfctl_eth_anchor *anchor; +}; + +struct pfctl_eth_anchor { + struct pfctl_eth_anchor *parent; + char name[PF_ANCHOR_NAME_SIZE]; + char path[MAXPATHLEN]; + struct pfctl_eth_ruleset ruleset; + int refcnt; /* anchor rules */ + int match; /* XXX: used for pfctl black magic */ +}; + struct pfctl_pool { struct pf_palist list; struct pf_pooladdr *cur; @@ -331,11 +349,13 @@ struct pfctl_status* pfctl_get_status(int dev); void pfctl_free_status(struct pfctl_status *status); -int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules); +int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, + const char *path); int pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket, - struct pfctl_eth_rule *rule, bool clear); + const char *path, struct pfctl_eth_rule *rule, bool clear, + char *anchor_call); int pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, - uint32_t ticket); + const char *anchor, const char *anchor_call, uint32_t ticket); int pfctl_get_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call); diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -606,20 +606,34 @@ rule->dnpipe = nvlist_get_number(nvl, "dnpipe"); rule->dnflags = nvlist_get_number(nvl, "dnflags"); + rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); + rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); + rule->action = nvlist_get_number(nvl, "action"); } int -pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules) +pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, + const char *path) { uint8_t buf[1024]; struct pfioc_nv nv; nvlist_t *nvl; + void *packed; + size_t len; bzero(rules, sizeof(*rules)); + nvl = nvlist_create(0); + nvlist_add_string(nvl, "anchor", path); + packed = nvlist_pack(nvl, &len); + memcpy(buf, packed, len); + free(packed); + nvlist_destroy(nvl); + nv.data = buf; - nv.len = nv.size = sizeof(buf); + nv.len = len; + nv.size = sizeof(buf); if (ioctl(dev, DIOCGETETHRULES, &nv) != 0) return (errno); @@ -637,7 +651,8 @@ int pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket, - struct pfctl_eth_rule *rule, bool clear) + const char *path, struct pfctl_eth_rule *rule, bool clear, + char *anchor_call) { uint8_t buf[1024]; struct pfioc_nv nv; @@ -647,6 +662,7 @@ nvl = nvlist_create(0); + nvlist_add_string(nvl, "anchor", path); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_number(nvl, "nr", nr); nvlist_add_bool(nvl, "clear", clear); @@ -670,12 +686,17 @@ pfctl_nveth_rule_to_eth_rule(nvl, rule); + if (anchor_call) + strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), + MAXPATHLEN); + nvlist_destroy(nvl); return (0); } int -pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, uint32_t ticket) +pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor, + const char *anchor_call, uint32_t ticket) { struct pfioc_nv nv; nvlist_t *nvl, *addr; @@ -686,6 +707,8 @@ nvl = nvlist_create(0); nvlist_add_number(nvl, "ticket", ticket); + nvlist_add_string(nvl, "anchor", anchor); + nvlist_add_string(nvl, "anchor_call", anchor_call); nvlist_add_number(nvl, "nr", r->nr); nvlist_add_bool(nvl, "quick", r->quick); diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -350,7 +350,7 @@ struct pfctl_rule *); void expand_eth_rule(struct pfctl_eth_rule *, struct node_if *, struct node_etherproto *, - struct node_mac *, struct node_mac *); + struct node_mac *, struct node_mac *, 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 *, @@ -370,6 +370,7 @@ int rt_tableid_max(void); void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *); +void mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *); void decide_address_family(struct node_host *, sa_family_t *); void remove_invalid_hosts(struct node_host **, sa_family_t *); int invalid_redirect(struct node_host *, sa_family_t); @@ -566,6 +567,7 @@ | ruleset '\n' | ruleset option '\n' | ruleset etherrule '\n' + | ruleset etheranchorrule '\n' | ruleset scrubrule '\n' | ruleset natrule '\n' | ruleset binatrule '\n' @@ -1196,7 +1198,95 @@ r.dnpipe = $8.dnpipe; r.dnflags = $8.free_flags; - expand_eth_rule(&r, $5, $6, $7.src, $7.dst); + expand_eth_rule(&r, $5, $6, $7.src, $7.dst, ""); + } + ; + +etherpfa_anchorlist : /* empty */ + | etherpfa_anchorlist '\n' + | etherpfa_anchorlist etherrule '\n' + | etherpfa_anchorlist etheranchorrule '\n' + ; + +etherpfa_anchor : '{' + { + char ta[PF_ANCHOR_NAME_SIZE]; + struct pfctl_eth_ruleset *rs; + + /* steping into a brace anchor */ + pf->asd++; + pf->bn++; + + /* create a holding ruleset in the root */ + snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); + rs = pf_find_or_create_eth_ruleset(ta); + if (rs == NULL) + err(1, "etherpfa_anchor: pf_find_or_create_eth_ruleset"); + pf->eastack[pf->asd] = rs->anchor; + pf->eanchor = rs->anchor; + } '\n' etherpfa_anchorlist '}' + { + pf->ealast = pf->eanchor; + pf->asd--; + pf->eanchor = pf->eastack[pf->asd]; + } + | /* empty */ + ; + +etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto etherpfa_anchor + { + struct pfctl_eth_rule r; + + if (check_rulestate(PFCTL_STATE_ETHER)) { + free($3); + YYERROR; + } + + if ($3 && ($3[0] == '_' || strstr($3, "/_") != NULL)) { + free($3); + yyerror("anchor names beginning with '_' " + "are reserved for internal use"); + YYERROR; + } + + memset(&r, 0, sizeof(r)); + if (pf->eastack[pf->asd + 1]) { + /* move inline rules into relative location */ + pfctl_eth_anchor_setup(pf, &r, + &pf->eastack[pf->asd]->ruleset, + $3 ? $3 : pf->ealast->name); + if (r.anchor == NULL) + err(1, "etheranchorrule: unable to " + "create ruleset"); + + if (pf->ealast != r.anchor) { + if (r.anchor->match) { + yyerror("inline anchor '%s' " + "already exists", + r.anchor->name); + YYERROR; + } + mv_eth_rules(&pf->ealast->ruleset, + &r.anchor->ruleset); + } + pf_remove_if_empty_eth_ruleset(&pf->ealast->ruleset); + pf->ealast = r.anchor; + } else { + if (!$3) { + yyerror("anchors without explicit " + "rules must specify a name"); + YYERROR; + } + } + + r.direction = $4; + r.quick = $5.quick; + + expand_eth_rule(&r, $6, $7, $8.src, $8.dst, + pf->eastack[pf->asd + 1] ? pf->ealast->name : $3); + + free($3); + pf->eastack[pf->asd + 1] = NULL; } ; @@ -5640,15 +5730,12 @@ 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) + struct node_mac *srcs, struct node_mac *dsts, const char *anchor_call) { - struct pfctl_eth_rule *rule; - 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, - r->nr = pf->eth_nr++; strlcpy(r->ifname, interface->ifname, sizeof(r->ifname)); r->ifnot = interface->not; @@ -5657,12 +5744,9 @@ r->src.neg = src->neg; bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN); r->dst.neg = dst->neg; + r->nr = pf->eastack[pf->asd]->match++; - if ((rule = calloc(1, sizeof(*rule))) == NULL) - err(1, "calloc"); - bcopy(r, rule, sizeof(*rule)); - - TAILQ_INSERT_TAIL(&pf->eth_rules, rule, entries); + pfctl_append_eth_rule(pf, r, anchor_call); )))); FREE_LIST(struct node_if, interfaces); @@ -6525,6 +6609,19 @@ } } +void +mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst) +{ + struct pfctl_eth_rule *r; + + while ((r = TAILQ_FIRST(&src->rules)) != NULL) { + TAILQ_REMOVE(&src->rules, r, entries); + TAILQ_INSERT_TAIL(&dst->rules, r, entries); + dst->anchor->match++; + } + src->anchor->match = 0; +} + void decide_address_family(struct node_host *n, sa_family_t *af) { diff --git a/sbin/pfctl/pf_ruleset.c b/sbin/pfctl/pf_ruleset.c --- a/sbin/pfctl/pf_ruleset.c +++ b/sbin/pfctl/pf_ruleset.c @@ -65,6 +65,7 @@ #define rs_free(x) free(x) #include "pfctl.h" +#include "pfctl_parser.h" #ifdef PFDEBUG #include @@ -74,7 +75,8 @@ #endif /* PFDEBUG */ struct pfctl_anchor_global pf_anchors; -struct pfctl_anchor pf_main_anchor; +extern struct pfctl_anchor pf_main_anchor; +extern struct pfctl_eth_anchor pf_eth_main_anchor; #undef V_pf_anchors #define V_pf_anchors pf_anchors #undef pf_main_ruleset @@ -290,6 +292,148 @@ ruleset = &parent->ruleset; } } + +void +pf_remove_if_empty_eth_ruleset(struct pfctl_eth_ruleset *ruleset) +{ + struct pfctl_eth_anchor *parent; + + return; + while (ruleset != NULL) { + if (ruleset == &pf_eth_main_anchor.ruleset || + ruleset->anchor == NULL || ruleset->anchor->refcnt > 0) + return; + if (!TAILQ_EMPTY(&ruleset->rules)) + return; + rs_free(ruleset->anchor); + if (parent == NULL) + return; + ruleset = &parent->ruleset; + } +} + +void +pf_init_eth_ruleset(struct pfctl_eth_ruleset *ruleset) +{ + + memset(ruleset, 0, sizeof(*ruleset)); + TAILQ_INIT(&ruleset->rules); +} + + +static struct pfctl_eth_anchor* +_pf_find_eth_anchor(struct pfctl_eth_anchor *anchor, const char *path) +{ + struct pfctl_eth_rule *r; + struct pfctl_eth_anchor *a; + + if (strcmp(path, anchor->path) == 0) + return (anchor); + + TAILQ_FOREACH(r, &anchor->ruleset.rules, entries) { + if (! r->anchor) + continue; + + /* Step into anchor */ + a = _pf_find_eth_anchor(r->anchor, path); + if (a) + return (a); + } + + return (NULL); +} + +static struct pfctl_eth_anchor* +pf_find_eth_anchor(const char *path) +{ + return (_pf_find_eth_anchor(&pf_eth_main_anchor, path)); +} + +static struct pfctl_eth_ruleset* +pf_find_eth_ruleset(const char *path) +{ + struct pfctl_eth_anchor *anchor; + + while (*path == '/') + path++; + if (!*path) + return (&pf_eth_main_anchor.ruleset); + anchor = pf_find_eth_anchor(path); + if (anchor == NULL) + return (NULL); + else + return (&anchor->ruleset); +} + +struct pfctl_eth_ruleset * +pf_find_or_create_eth_ruleset(const char *path) +{ + char *p, *q, *r; + struct pfctl_eth_ruleset *ruleset; + struct pfctl_eth_anchor *anchor = NULL, *parent = NULL; + + if (path[0] == 0) + return (&pf_eth_main_anchor.ruleset); + while (*path == '/') + path++; + ruleset = pf_find_eth_ruleset(path); + if (ruleset != NULL) + return (ruleset); + p = (char *)rs_malloc(MAXPATHLEN); + if (p == NULL) + return (NULL); + strlcpy(p, path, MAXPATHLEN); + while (parent == NULL && (q = strrchr(p, '/')) != NULL) { + *q = 0; + if ((ruleset = pf_find_eth_ruleset(p)) != NULL) { + parent = ruleset->anchor; + break; + } + } + if (q == NULL) + q = p; + else + q++; + strlcpy(p, path, MAXPATHLEN); + if (!*q) { + rs_free(p); + return (NULL); + } + while ((r = strchr(q, '/')) != NULL || *q) { + if (r != NULL) + *r = 0; + if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE || + (parent != NULL && strlen(parent->path) >= + MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { + rs_free(p); + return (NULL); + } + anchor = (struct pfctl_eth_anchor *)rs_malloc(sizeof(*anchor)); + if (anchor == NULL) { + rs_free(p); + return (NULL); + } + strlcpy(anchor->name, q, sizeof(anchor->name)); + if (parent != NULL) { + strlcpy(anchor->path, parent->path, + sizeof(anchor->path)); + strlcat(anchor->path, "/", sizeof(anchor->path)); + } + strlcat(anchor->path, anchor->name, sizeof(anchor->path)); + if (parent != NULL) + anchor->parent = parent; + pf_init_eth_ruleset(&anchor->ruleset); + anchor->ruleset.anchor = anchor; + parent = anchor; + if (r != NULL) + q = r + 1; + else + *q = 0; + } + rs_free(p); + return (&anchor->ruleset); +} + int pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s, const char *name) @@ -345,3 +489,54 @@ r->anchor->refcnt++; return (0); } + +int +pfctl_eth_anchor_setup(struct pfctl *pf, struct pfctl_eth_rule *r, + const struct pfctl_eth_ruleset *s, const char *name) +{ + char *p, *path; + struct pfctl_eth_ruleset *ruleset; + + r->anchor = NULL; + if (!name[0]) + return (0); + path = (char *)rs_malloc(MAXPATHLEN); + if (path == NULL) + return (1); + if (name[0] == '/') + strlcpy(path, name + 1, MAXPATHLEN); + else { + /* relative path */ + if (s->anchor == NULL || !s->anchor->path[0]) + path[0] = 0; + else + strlcpy(path, s->anchor->path, MAXPATHLEN); + while (name[0] == '.' && name[1] == '.' && name[2] == '/') { + if (!path[0]) { + printf("%s: .. beyond root\n", __func__); + rs_free(path); + return (1); + } + if ((p = strrchr(path, '/')) != NULL) + *p = 0; + else + path[0] = 0; + name += 3; + } + if (path[0]) + strlcat(path, "/", MAXPATHLEN); + strlcat(path, name, MAXPATHLEN); + } + if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { + *p = 0; + } + ruleset = pf_find_or_create_eth_ruleset(path); + rs_free(path); + if (ruleset == NULL || ruleset->anchor == NULL) { + printf("%s: ruleset\n", __func__); + return (1); + } + r->anchor = ruleset->anchor; + r->anchor->refcnt++; + return (0); +} diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -38,6 +38,8 @@ #include +struct pfctl; + enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, @@ -137,6 +139,13 @@ void pf_remove_if_empty_ruleset(struct pfctl_ruleset *); struct pfctl_ruleset *pf_find_ruleset(const char *); struct pfctl_ruleset *pf_find_or_create_ruleset(const char *); +void pf_init_eth_ruleset(struct pfctl_eth_ruleset *); +int pfctl_eth_anchor_setup(struct pfctl *, + struct pfctl_eth_rule *, + const struct pfctl_eth_ruleset *, const char *); +struct pfctl_eth_ruleset *pf_find_or_create_eth_ruleset(const char *); +void pf_remove_if_empty_eth_ruleset( + struct pfctl_eth_ruleset *); void expand_label(char *, size_t, struct pfctl_rule *); diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -98,7 +98,7 @@ char *); void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int); void pfctl_print_rule_counters(struct pfctl_rule *, int); -int pfctl_show_eth_rules(int, int, enum pfctl_show); +int pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int); int pfctl_show_nat(int, int, char *); int pfctl_show_src_nodes(int, int); @@ -110,15 +110,21 @@ void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); -int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *); -int pfctl_load_eth_ruleset(struct pfctl *); +int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool); +int pfctl_eth_ruleset_trans(struct pfctl *, char *, + struct pfctl_eth_anchor *); +int pfctl_load_eth_ruleset(struct pfctl *, char *, + struct pfctl_eth_ruleset *, int); +int pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *, + int); int pfctl_load_ruleset(struct pfctl *, char *, struct pfctl_ruleset *, int, int); int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); const char *pfctl_lookup_option(char *, const char * const *); static struct pfctl_anchor_global pf_anchors; -static struct pfctl_anchor pf_main_anchor; +struct pfctl_anchor pf_main_anchor; +struct pfctl_eth_anchor pf_eth_main_anchor; static struct pfr_buffer skip_b; static const char *clearopt; @@ -1052,31 +1058,66 @@ } int -pfctl_show_eth_rules(int dev, int opts, enum pfctl_show format) +pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format, + char *anchorname, int depth) { + char anchor_call[MAXPATHLEN]; struct pfctl_eth_rules_info info; struct pfctl_eth_rule rule; int dotitle = opts & PF_OPT_SHOWALL; + int len = strlen(path); + int brace; + char *p; + + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); - if (pfctl_get_eth_rules_info(dev, &info)) { + if (pfctl_get_eth_rules_info(dev, &info, path)) { warn("DIOCGETETHRULES"); return (-1); } for (int nr = 0; nr < info.nr; nr++) { - if (pfctl_get_eth_rule(dev, nr, info.ticket, &rule, - opts & PF_OPT_CLRRULECTRS) != 0) { + brace = 0; + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + if (pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule, + opts & PF_OPT_CLRRULECTRS, anchor_call) != 0) { warn("DIOCGETETHRULE"); return (-1); } + if (anchor_call[0] && + ((((p = strrchr(anchor_call, '_')) != NULL) && + (p == anchor_call || + *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { + brace++; + if ((p = strrchr(anchor_call, '/')) != + NULL) + p++; + else + p = &anchor_call[0]; + } else + p = &anchor_call[0]; if (dotitle) { pfctl_print_title("ETH RULES:"); dotitle = 0; } - print_eth_rule(&rule, opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); - printf("\n"); + print_eth_rule(&rule, anchor_call, + opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); + if (brace) + printf(" {\n"); + else + printf("\n"); pfctl_print_eth_rule_counters(&rule, opts); + if (brace) { + pfctl_show_eth_rules(dev, path, opts, format, + p, depth + 1); + INDENT(depth, !(opts & PF_OPT_VERBOSE)); + printf("}\n"); + } } + path[len] = '\0'; return (0); } @@ -1508,15 +1549,70 @@ } int -pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a) +pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r, + const char *anchor_call) +{ + struct pfctl_eth_rule *rule; + struct pfctl_eth_ruleset *rs; + char *p; + + rs = &pf->eanchor->ruleset; + + if (anchor_call[0] && r->anchor == NULL) { + /* + * Don't make non-brace anchors part of the main anchor pool. + */ + if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) + err(1, "pfctl_append_rule: calloc"); + + pf_init_eth_ruleset(&r->anchor->ruleset); + r->anchor->ruleset.anchor = r->anchor; + if (strlcpy(r->anchor->path, anchor_call, + sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) + errx(1, "pfctl_append_rule: strlcpy"); + if ((p = strrchr(anchor_call, '/')) != NULL) { + if (!strlen(p)) + err(1, "pfctl_append_eth_rule: bad anchor name %s", + anchor_call); + } else + p = (char *)anchor_call; + if (strlcpy(r->anchor->name, p, + sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) + errx(1, "pfctl_append_eth_rule: strlcpy"); + } + + if ((rule = calloc(1, sizeof(*rule))) == NULL) + err(1, "calloc"); + bcopy(r, rule, sizeof(*rule)); + + TAILQ_INSERT_TAIL(&rs->rules, rule, entries); + return (0); +} + +int +pfctl_eth_ruleset_trans(struct pfctl *pf, char *path, + struct pfctl_eth_anchor *a) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) { - if (! path[0]) { - if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) - return (1); - } + if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) + return (1); + } + if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) + return (5); + + return (0); +} + +int +pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth) +{ + int osize = pf->trans->pfrb_size; + + if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) { + if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) + return (1); } if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || @@ -1544,22 +1640,92 @@ } int -pfctl_load_eth_ruleset(struct pfctl *pf) +pfctl_load_eth_ruleset(struct pfctl *pf, char *path, + struct pfctl_eth_ruleset *rs, int depth) { struct pfctl_eth_rule *r; - int error; + int error, len = strlen(path); + int brace = 0; - while ((r = TAILQ_FIRST(&pf->eth_rules)) != NULL) { - TAILQ_REMOVE(&pf->eth_rules, r, entries); + pf->eanchor = rs->anchor; + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name); - if ((pf->opts & PF_OPT_NOACTION) == 0) { - error = pfctl_add_eth_rule(pf->dev, r, pf->eth_ticket); - if (error) + if (depth) { + if (TAILQ_FIRST(&rs->rules) != NULL) { + brace++; + if (pf->opts & PF_OPT_VERBOSE) + printf(" {\n"); + if ((pf->opts & PF_OPT_NOACTION) == 0 && + (error = pfctl_eth_ruleset_trans(pf, + path, rs->anchor))) { + printf("pfctl_load_eth_rulesets: " + "pfctl_eth_ruleset_trans %d\n", error); + goto error; + } + } else if (pf->opts & PF_OPT_VERBOSE) + printf("\n"); + } + + while ((r = TAILQ_FIRST(&rs->rules)) != NULL) { + TAILQ_REMOVE(&rs->rules, r, entries); + + error = pfctl_load_eth_rule(pf, path, r, depth); + if (error) + return (error); + + if (r->anchor) { + if ((error = pfctl_load_eth_ruleset(pf, path, + &r->anchor->ruleset, depth + 1))) return (error); } - free(r); } + if (brace && pf->opts & PF_OPT_VERBOSE) { + INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); + printf("}\n"); + } + path[len] = '\0'; + + return (0); +error: + path[len] = '\0'; + return (error); +} + +int +pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r, + int depth) +{ + char *name; + char anchor[PF_ANCHOR_NAME_SIZE]; + int len = strlen(path); + + if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) + errx(1, "pfctl_load_eth_rule: strlcpy"); + + if (r->anchor) { + if (r->anchor->match) { + if (path[0]) + snprintf(&path[len], MAXPATHLEN - len, + "/%s", r->anchor->name); + else + snprintf(&path[len], MAXPATHLEN - len, + "%s", r->anchor->name); + name = r->anchor->name; + } else + name = r->anchor->path; + } else + name = ""; + + if ((pf->opts & PF_OPT_NOACTION) == 0) + if (pfctl_add_eth_rule(pf->dev, r, anchor, name, + pf->eth_ticket)) + err(1, "DIOCADDETHRULENV"); + + path[len] = '\0'; return (0); } @@ -1586,7 +1752,7 @@ printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_ruleset_trans(pf, - path, rs->anchor))) { + path, rs->anchor, false))) { printf("pfctl_load_rulesets: " "pfctl_ruleset_trans %d\n", error); goto error; @@ -1711,6 +1877,7 @@ struct pfioc_altq pa; struct pfctl pf; struct pfctl_ruleset *rs; + struct pfctl_eth_ruleset *ethrs; struct pfr_table trs; char *path; int osize; @@ -1719,6 +1886,11 @@ memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); pf_main_anchor.ruleset.anchor = &pf_main_anchor; + + memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor)); + pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset); + pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor; + if (trans == NULL) { bzero(&buf, sizeof(buf)); buf.pfrb_type = PFRB_TRANS; @@ -1742,7 +1914,6 @@ pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; - TAILQ_INIT(&pf.eth_rules); /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) @@ -1752,10 +1923,10 @@ rs->anchor = pf.anchor; if (strlcpy(pf.anchor->path, anchorname, sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) - errx(1, "pfctl_add_rule: strlcpy"); + errx(1, "pfctl_rules: strlcpy"); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) - errx(1, "pfctl_add_rule: strlcpy"); + errx(1, "pfctl_rules: strlcpy"); pf.astack[0] = pf.anchor; @@ -1766,13 +1937,29 @@ pf.trans = t; pfctl_init_options(&pf); + /* Set up ethernet anchor */ + if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL) + ERRX("pfctl_rules: calloc"); + + if (strlcpy(pf.eanchor->path, anchorname, + sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path)) + errx(1, "pfctl_rules: strlcpy"); + if (strlcpy(pf.eanchor->name, anchorname, + sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name)) + errx(1, "pfctl_rules: strlcpy"); + + ethrs = &pf.eanchor->ruleset; + pf_init_eth_ruleset(ethrs); + ethrs->anchor = pf.eanchor; + pf.eastack[0] = pf.eanchor; + if ((opts & PF_OPT_NOACTION) == 0) { /* * XXX For the time being we need to open transactions for * the main ruleset before parsing, because tables are still * loaded at parse time. */ - if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor)) + if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true)) ERRX("pfctl_rules"); if (pf.loadopt & PFCTL_FLAG_ETH) pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); @@ -1797,7 +1984,7 @@ if ((pf.loadopt & PFCTL_FLAG_FILTER && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || (pf.loadopt & PFCTL_FLAG_ETH && - (pfctl_load_eth_ruleset(&pf))) || + (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) || (pf.loadopt & PFCTL_FLAG_NAT && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || @@ -2572,7 +2759,7 @@ sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); - loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE; + loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH; } if ((opts & PF_OPT_NOACTION) == 0) { @@ -2640,13 +2827,13 @@ pfctl_show_limits(dev, opts); break; case 'e': - pfctl_show_eth_rules(dev, opts, 0); + pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0); break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); - pfctl_show_eth_rules(dev, opts, 0); + pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0); pfctl_show_nat(dev, opts, anchorname); pfctl_show_rules(dev, path, opts, 0, anchorname, 0); @@ -2674,7 +2861,8 @@ } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) { - pfctl_show_eth_rules(dev, opts, PFCTL_SHOW_NOTHING); + pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING, + anchorname, 0); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0); } diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -91,7 +91,8 @@ struct pfr_buffer *trans; struct pfctl_anchor *anchor, *alast; int eth_nr; - struct pfctl_eth_rules eth_rules; + struct pfctl_eth_anchor *eanchor, *ealast; + struct pfctl_eth_anchor *eastack[PFCTL_ANCHOR_STACK_DEPTH]; u_int32_t eth_ticket; const char *ruleset; @@ -274,6 +275,8 @@ int pfctl_optimize_ruleset(struct pfctl *, struct pfctl_ruleset *); int pfctl_append_rule(struct pfctl *, struct pfctl_rule *, const char *); +int pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *, + const char *); int pfctl_add_altq(struct pfctl *, struct pf_altq *); int pfctl_add_pool(struct pfctl *, struct pfctl_pool *, sa_family_t); void pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *); @@ -294,7 +297,7 @@ void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, sa_family_t, int); void print_src_node(struct pf_src_node *, int); -void print_eth_rule(struct pfctl_eth_rule *, int); +void print_eth_rule(struct pfctl_eth_rule *, const char *, int); void print_rule(struct pfctl_rule *, const char *, int, int); void print_tabledef(const char *, int, int, struct node_tinithead *); void print_status(struct pfctl_status *, struct pfctl_syncookies *, int); 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 @@ -710,14 +710,23 @@ } void -print_eth_rule(struct pfctl_eth_rule *r, int rule_numbers) +print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call, + int rule_numbers) { static const char *actiontypes[] = { "pass", "block" }; if (rule_numbers) printf("@%u ", r->nr); - printf("ether %s", actiontypes[r->action]); + printf("ether "); + if (anchor_call[0]) { + if (anchor_call[0] == '_') { + printf("anchor"); + } else + printf("anchor \"%s\"", anchor_call); + } else { + printf("%s", actiontypes[r->action]); + } if (r->direction == PF_IN) printf(" in"); else if (r->direction == PF_OUT) diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -583,6 +583,41 @@ uint8_t isset; }; +struct pf_keth_anchor; + +TAILQ_HEAD(pf_keth_ruleq, pf_keth_rule); + +struct pf_keth_ruleset { + struct pf_keth_ruleq rules[2]; + struct pf_keth_rules { + struct pf_keth_ruleq *rules; + int open; + uint32_t ticket; + } active, inactive; + struct epoch_context epoch_ctx; + struct vnet *vnet; + struct pf_keth_anchor *anchor; +}; + +RB_HEAD(pf_keth_anchor_global, pf_keth_anchor); +RB_HEAD(pf_keth_anchor_node, pf_keth_anchor); +struct pf_keth_anchor { + RB_ENTRY(pf_keth_anchor) entry_node; + RB_ENTRY(pf_keth_anchor) entry_global; + struct pf_keth_anchor *parent; + struct pf_keth_anchor_node children; + char name[PF_ANCHOR_NAME_SIZE]; + char path[MAXPATHLEN]; + struct pf_keth_ruleset ruleset; + int refcnt; /* anchor rules */ + uint8_t anchor_relative; + uint8_t anchor_wildcard; +}; +RB_PROTOTYPE(pf_keth_anchor_node, pf_keth_anchor, entry_node, + pf_keth_anchor_compare); +RB_PROTOTYPE(pf_keth_anchor_global, pf_keth_anchor, entry_global, + pf_keth_anchor_compare); + struct pf_keth_rule { #define PFE_SKIP_IFP 0 #define PFE_SKIP_DIR 1 @@ -594,6 +629,10 @@ TAILQ_ENTRY(pf_keth_rule) entries; + struct pf_keth_anchor *anchor; + u_int8_t anchor_relative; + u_int8_t anchor_wildcard; + uint32_t nr; bool quick; @@ -621,16 +660,6 @@ uint32_t dnflags; }; -TAILQ_HEAD(pf_keth_rules, pf_keth_rule); - -struct pf_keth_settings { - struct pf_keth_rules rules; - uint32_t ticket; - int open; - struct vnet *vnet; - struct epoch_context epoch_ctx; -}; - union pf_krule_ptr { struct pf_krule *ptr; u_int32_t nr; @@ -1151,6 +1180,7 @@ PFR_TFLAG_COUNTERS) struct pf_kanchor_stackframe; +struct pf_keth_anchor_stackframe; struct pfr_table { char pfrt_anchor[MAXPATHLEN]; @@ -2206,15 +2236,17 @@ #define V_pf_anchors VNET(pf_anchors) VNET_DECLARE(struct pf_kanchor, pf_main_anchor); #define V_pf_main_anchor VNET(pf_main_anchor) +VNET_DECLARE(struct pf_keth_anchor_global, pf_keth_anchors); +#define V_pf_keth_anchors VNET(pf_keth_anchors) #define pf_main_ruleset V_pf_main_anchor.ruleset -VNET_DECLARE(struct pf_keth_settings*, pf_keth); +VNET_DECLARE(struct pf_keth_anchor, pf_main_keth_anchor); +#define V_pf_main_keth_anchor VNET(pf_main_keth_anchor) +VNET_DECLARE(struct pf_keth_ruleset*, pf_keth); #define V_pf_keth VNET(pf_keth) -VNET_DECLARE(struct pf_keth_settings*, pf_keth_inactive); -#define V_pf_keth_inactive VNET(pf_keth_inactive) void pf_init_kruleset(struct pf_kruleset *); -void pf_init_keth(struct pf_keth_settings *); +void pf_init_keth(struct pf_keth_ruleset *); int pf_kanchor_setup(struct pf_krule *, const struct pf_kruleset *, const char *); int pf_kanchor_nvcopyout(const struct pf_kruleset *, @@ -2227,7 +2259,21 @@ struct pf_kruleset *pf_find_or_create_kruleset(const char *); void pf_rs_initialize(void); + struct pf_krule *pf_krule_alloc(void); + +void pf_remove_if_empty_keth_ruleset( + struct pf_keth_ruleset *); +struct pf_keth_ruleset *pf_find_keth_ruleset(const char *); +struct pf_keth_anchor *pf_find_keth_anchor(const char *); +int pf_keth_anchor_setup(struct pf_keth_rule *, + const struct pf_keth_ruleset *, const char *); +int pf_keth_anchor_nvcopyout( + const struct pf_keth_ruleset *, + const struct pf_keth_rule *, nvlist_t *); +struct pf_keth_ruleset *pf_find_or_create_keth_ruleset(const char *); +void pf_keth_anchor_remove(struct pf_keth_rule *); + void pf_krule_free(struct pf_krule *); #endif @@ -2251,6 +2297,14 @@ int pf_step_out_of_anchor(struct pf_kanchor_stackframe *, int *, struct pf_kruleset **, int, struct pf_krule **, struct pf_krule **, int *); +void pf_step_into_keth_anchor(struct pf_keth_anchor_stackframe *, + int *, struct pf_keth_ruleset **, + struct pf_keth_rule **, struct pf_keth_rule **, + int *); +int pf_step_out_of_keth_anchor(struct pf_keth_anchor_stackframe *, + int *, struct pf_keth_ruleset **, + struct pf_keth_rule **, struct pf_keth_rule **, + int *); int pf_map_addr(u_int8_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, 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 @@ -3404,6 +3404,110 @@ return (quick); } +struct pf_keth_anchor_stackframe { + struct pf_keth_ruleset *rs; + struct pf_keth_rule *r; /* XXX: + match bit */ + struct pf_keth_anchor *child; +}; + +#define PF_ETH_ANCHOR_MATCH(f) ((uintptr_t)(f)->r & PF_ANCHORSTACK_MATCH) +#define PF_ETH_ANCHOR_RULE(f) (struct pf_keth_rule *) \ + ((uintptr_t)(f)->r & ~PF_ANCHORSTACK_MASK) +#define PF_ETH_ANCHOR_SET_MATCH(f) do { (f)->r = (void *) \ + ((uintptr_t)(f)->r | PF_ANCHORSTACK_MATCH); \ +} while (0) + +void +pf_step_into_keth_anchor(struct pf_keth_anchor_stackframe *stack, int *depth, + struct pf_keth_ruleset **rs, struct pf_keth_rule **r, + struct pf_keth_rule **a, int *match) +{ + struct pf_keth_anchor_stackframe *f; + + NET_EPOCH_ASSERT(); + + if (match) + *match = 0; + if (*depth >= PF_ANCHOR_STACKSIZE) { + printf("%s: anchor stack overflow on %s\n", + __func__, (*r)->anchor->name); + *r = TAILQ_NEXT(*r, entries); + return; + } else if (*depth == 0 && a != NULL) + *a = *r; + f = stack + (*depth)++; + f->rs = *rs; + f->r = *r; + if ((*r)->anchor_wildcard) { + struct pf_keth_anchor_node *parent = &(*r)->anchor->children; + + if ((f->child = RB_MIN(pf_keth_anchor_node, parent)) == NULL) { + *r = NULL; + return; + } + *rs = &f->child->ruleset; + } else { + f->child = NULL; + *rs = &(*r)->anchor->ruleset; + } + *r = TAILQ_FIRST((*rs)->active.rules); +} + +int +pf_step_out_of_keth_anchor(struct pf_keth_anchor_stackframe *stack, int *depth, + struct pf_keth_ruleset **rs, struct pf_keth_rule **r, + struct pf_keth_rule **a, int *match) +{ + struct pf_keth_anchor_stackframe *f; + struct pf_keth_rule *fr; + int quick = 0; + + NET_EPOCH_ASSERT(); + + do { + if (*depth <= 0) + break; + f = stack + *depth - 1; + fr = PF_ETH_ANCHOR_RULE(f); + if (f->child != NULL) { + struct pf_keth_anchor_node *parent; + /* + * This block traverses through + * a wildcard anchor. + */ + parent = &fr->anchor->children; + if (match != NULL && *match) { + /* + * If any of "*" matched, then + * "foo/ *" matched, mark frame + * appropriately. + */ + PF_ETH_ANCHOR_SET_MATCH(f); + *match = 0; + } + f->child = RB_NEXT(pf_keth_anchor_node, parent, + f->child); + if (f->child != NULL) { + *rs = &f->child->ruleset; + *r = TAILQ_FIRST((*rs)->active.rules); + if (*r == NULL) + continue; + else + break; + } + } + (*depth)--; + if (*depth == 0 && a != NULL) + *a = NULL; + *rs = f->rs; + if (PF_ETH_ANCHOR_MATCH(f) || (match != NULL && *match)) + quick = fr->quick; + *r = TAILQ_NEXT(fr, entries); + } while (*r == NULL); + + return (quick); +} + #ifdef INET6 void pf_poolmask(struct pf_addr *naddr, struct pf_addr *raddr, @@ -3719,10 +3823,13 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf *m) { struct ether_header *e; - struct pf_keth_rule *r, *rm; + struct pf_keth_rule *r, *rm, *a = NULL; + struct pf_keth_ruleset *ruleset = NULL; struct pf_mtag *mtag; - struct pf_keth_settings *settings; + struct pf_keth_ruleq *rules; + int asd = 0, match = 0; uint8_t action; + struct pf_keth_anchor_stackframe anchor_stack[PF_ANCHOR_STACKSIZE]; NET_EPOCH_ASSERT(); @@ -3733,8 +3840,9 @@ e = mtod(m, struct ether_header *); - settings = ck_pr_load_ptr(&V_pf_keth); - r = TAILQ_FIRST(&settings->rules); + ruleset = V_pf_keth; + rules = ck_pr_load_ptr(&ruleset->active.rules); + r = TAILQ_FIRST(rules); rm = NULL; while (r != NULL) { @@ -3767,16 +3875,24 @@ r = TAILQ_NEXT(r, entries); } else { - /* Rule matches */ - rm = r; + if (r->anchor == NULL) { + /* Rule matches */ + rm = r; - SDT_PROBE2(pf, eth, test_rule, match, r->nr, r); + SDT_PROBE2(pf, eth, test_rule, match, r->nr, r); - if (r->quick) - break; + if (r->quick) + break; - r = TAILQ_NEXT(r, entries); + r = TAILQ_NEXT(r, entries); + } else { + pf_step_into_keth_anchor(anchor_stack, &asd, + &ruleset, &r, &a, &match); + } } + if (r == NULL && pf_step_out_of_keth_anchor(anchor_stack, &asd, + &ruleset, &r, &a, &match)) + break; } r = rm; 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 @@ -106,10 +106,10 @@ static void pf_empty_kpool(struct pf_kpalist *); static int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *); -static int pf_begin_eth(uint32_t *); +static int pf_begin_eth(uint32_t *, const char *); static void pf_rollback_eth_cb(struct epoch_context *); -static int pf_rollback_eth(uint32_t); -static int pf_commit_eth(uint32_t); +static int pf_rollback_eth(uint32_t, const char *); +static int pf_commit_eth(uint32_t, const char *); static void pf_free_eth_rule(struct pf_keth_rule *); #ifdef ALTQ static int pf_begin_altq(u_int32_t *); @@ -318,7 +318,6 @@ pf_init_kruleset(&pf_main_ruleset); pf_init_keth(V_pf_keth); - pf_init_keth(V_pf_keth_inactive); /* default rule should never be garbage collected */ V_pf_default_rule.entries.tqe_prev = &V_pf_default_rule.entries.tqe_next; @@ -510,6 +509,7 @@ counter_u64_free(rule->packets[i]); counter_u64_free(rule->bytes[i]); } + pf_keth_anchor_remove(rule); free(rule, M_PFRULE); } @@ -701,26 +701,32 @@ } static int -pf_begin_eth(uint32_t *ticket) +pf_begin_eth(uint32_t *ticket, const char *anchor) { struct pf_keth_rule *rule, *tmp; + struct pf_keth_ruleset *rs; PF_RULES_WASSERT(); - if (V_pf_keth_inactive->open) { + rs = pf_find_or_create_keth_ruleset(anchor); + if (rs == NULL) + return (EINVAL); + + if (rs->inactive.open) /* We may be waiting for NET_EPOCH_CALL(pf_rollback_eth_cb) to * finish. */ return (EBUSY); - } /* Purge old inactive rules. */ - TAILQ_FOREACH_SAFE(rule, &V_pf_keth_inactive->rules, entries, tmp) { - TAILQ_REMOVE(&V_pf_keth_inactive->rules, rule, entries); + TAILQ_FOREACH_SAFE(rule, rs->inactive.rules, entries, + tmp) { + TAILQ_REMOVE(rs->inactive.rules, rule, + entries); pf_free_eth_rule(rule); } - *ticket = ++V_pf_keth_inactive->ticket; - V_pf_keth_inactive->open = 1; + *ticket = ++rs->inactive.ticket; + rs->inactive.open = 1; return (0); } @@ -728,38 +734,46 @@ static void pf_rollback_eth_cb(struct epoch_context *ctx) { - struct pf_keth_settings *settings; - - settings = __containerof(ctx, struct pf_keth_settings, epoch_ctx); + struct pf_keth_ruleset *rs; - CURVNET_SET(settings->vnet); + rs = __containerof(ctx, struct pf_keth_ruleset, epoch_ctx); - MPASS(settings == V_pf_keth_inactive); + CURVNET_SET(rs->vnet); PF_RULES_WLOCK(); - pf_rollback_eth(V_pf_keth_inactive->ticket); + pf_rollback_eth(rs->inactive.ticket, + rs->anchor ? rs->anchor->path : ""); PF_RULES_WUNLOCK(); CURVNET_RESTORE(); } static int -pf_rollback_eth(uint32_t ticket) +pf_rollback_eth(uint32_t ticket, const char *anchor) { struct pf_keth_rule *rule, *tmp; + struct pf_keth_ruleset *rs; PF_RULES_WASSERT(); - if (!V_pf_keth_inactive->open || ticket != V_pf_keth_inactive->ticket) + rs = pf_find_keth_ruleset(anchor); + if (rs == NULL) + return (EINVAL); + + if (!rs->inactive.open || + ticket != rs->inactive.ticket) return (0); /* Purge old inactive rules. */ - TAILQ_FOREACH_SAFE(rule, &V_pf_keth_inactive->rules, entries, tmp) { - TAILQ_REMOVE(&V_pf_keth_inactive->rules, rule, entries); + TAILQ_FOREACH_SAFE(rule, rs->inactive.rules, entries, + tmp) { + TAILQ_REMOVE(rs->inactive.rules, rule, entries); pf_free_eth_rule(rule); } - V_pf_keth_inactive->open = 0; + rs->inactive.open = 0; + + pf_remove_if_empty_keth_ruleset(rs); return (0); } @@ -773,7 +787,7 @@ } while (0) static void -pf_eth_calc_skip_steps(struct pf_keth_rules *rules) +pf_eth_calc_skip_steps(struct pf_keth_ruleq *rules) { struct pf_keth_rule *cur, *prev, *head[PFE_SKIP_COUNT]; int i; @@ -802,26 +816,32 @@ } static int -pf_commit_eth(uint32_t ticket) +pf_commit_eth(uint32_t ticket, const char *anchor) { - struct pf_keth_settings *settings; + struct pf_keth_ruleq *rules; + struct pf_keth_ruleset *rs; + + rs = pf_find_keth_ruleset(anchor); + if (rs == NULL) { + return (EINVAL); + } - if (!V_pf_keth_inactive->open || - ticket != V_pf_keth_inactive->ticket) + if (!rs->inactive.open || + ticket != rs->inactive.ticket) return (EBUSY); PF_RULES_WASSERT(); - pf_eth_calc_skip_steps(&V_pf_keth_inactive->rules); + pf_eth_calc_skip_steps(rs->inactive.rules); - settings = V_pf_keth; - ck_pr_store_ptr(&V_pf_keth, V_pf_keth_inactive); - V_pf_keth_inactive = settings; - V_pf_keth_inactive->ticket = V_pf_keth->ticket; + rules = rs->active.rules; + ck_pr_store_ptr(&rs->active.rules, rs->inactive.rules); + rs->inactive.rules = rules; + rs->inactive.ticket = rs->active.ticket; /* Clean up inactive rules (i.e. previously active rules), only when * we're sure they're no longer used. */ - NET_EPOCH_CALL(pf_rollback_eth_cb, &V_pf_keth_inactive->epoch_ctx); + NET_EPOCH_CALL(pf_rollback_eth_cb, &rs->epoch_ctx); return (0); } @@ -2475,7 +2495,7 @@ int cpu; hook_pf(); - if (! TAILQ_EMPTY(&V_pf_keth->rules)) + if (! TAILQ_EMPTY(V_pf_keth->active.rules)) hook_pf_eth(); V_pf_status.running = 1; V_pf_status.since = time_second; @@ -2505,21 +2525,55 @@ nvlist_t *nvl; void *packed; struct pf_keth_rule *tail; + struct pf_keth_ruleset *rs; u_int32_t ticket, nr; + const char *anchor = ""; nvl = NULL; packed = NULL; #define ERROUT(x) do { error = (x); goto DIOCGETETHRULES_error; } while (0) + if (nv->len > pf_ioctl_maxcount) + ERROUT(ENOMEM); + + /* Copy the request in */ + packed = malloc(nv->len, M_NVLIST, M_WAITOK); + if (packed == NULL) + ERROUT(ENOMEM); + + error = copyin(nv->data, packed, nv->len); + if (error) + ERROUT(error); + + nvl = nvlist_unpack(packed, nv->len, 0); + if (nvl == NULL) + ERROUT(EBADMSG); + + if (! nvlist_exists_string(nvl, "anchor")) + ERROUT(EBADMSG); + + anchor = nvlist_get_string(nvl, "anchor"); + + rs = pf_find_keth_ruleset(anchor); + + nvlist_destroy(nvl); + nvl = NULL; + free(packed, M_NVLIST); + packed = NULL; + + if (rs == NULL) + ERROUT(ENOENT); + + /* Reply */ nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); PF_RULES_RLOCK(); - ticket = V_pf_keth->ticket; - tail = TAILQ_LAST(&V_pf_keth->rules, pf_keth_rules); + ticket = rs->active.ticket; + tail = TAILQ_LAST(rs->active.rules, pf_keth_ruleq); if (tail) nr = tail->nr + 1; else @@ -2543,7 +2597,7 @@ #undef ERROUT DIOCGETETHRULES_error: - free(packed, M_TEMP); + free(packed, M_NVLIST); nvlist_destroy(nvl); break; } @@ -2554,8 +2608,10 @@ nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_keth_rule *rule = NULL; + struct pf_keth_ruleset *rs; u_int32_t ticket, nr; bool clear = false; + const char *anchor; #define ERROUT(x) do { error = (x); goto DIOCGETETHRULE_error; } while (0) @@ -2571,6 +2627,9 @@ if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EBADMSG); ticket = nvlist_get_number(nvl, "ticket"); + if (! nvlist_exists_string(nvl, "anchor")) + ERROUT(EBADMSG); + anchor = nvlist_get_string(nvl, "anchor"); if (nvlist_exists_bool(nvl, "clear")) clear = nvlist_get_bool(nvl, "clear"); @@ -2582,6 +2641,17 @@ ERROUT(EBADMSG); nr = nvlist_get_number(nvl, "nr"); + PF_RULES_RLOCK(); + rs = pf_find_keth_ruleset(anchor); + if (rs == NULL) { + PF_RULES_RUNLOCK(); + ERROUT(ENOENT); + } + if (ticket != rs->active.ticket) { + PF_RULES_RUNLOCK(); + ERROUT(EBUSY); + } + nvlist_destroy(nvl); nvl = NULL; free(nvlpacked, M_TEMP); @@ -2589,12 +2659,7 @@ nvl = nvlist_create(0); - PF_RULES_RLOCK(); - if (ticket != V_pf_keth->ticket) { - PF_RULES_RUNLOCK(); - ERROUT(EBUSY); - } - rule = TAILQ_FIRST(&V_pf_keth->rules); + rule = TAILQ_FIRST(rs->active.rules); while ((rule != NULL) && (rule->nr != nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { @@ -2605,6 +2670,8 @@ NET_EPOCH_ENTER(et); PF_RULES_RUNLOCK(); nvl = pf_keth_rule_to_nveth_rule(rule); + if (pf_keth_anchor_nvcopyout(rs, rule, nvl)) + ERROUT(EBUSY); NET_EPOCH_EXIT(et); if (nvl == NULL) ERROUT(ENOMEM); @@ -2638,8 +2705,10 @@ struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; - struct pf_keth_rule *rule = NULL; + struct pf_keth_rule *rule = NULL, *tail = NULL; + struct pf_keth_ruleset *ruleset = NULL; struct pfi_kkif *kif = NULL; + const char *anchor = "", *anchor_call = ""; #define ERROUT(x) do { error = (x); goto DIOCADDETHRULE_error; } while (0) @@ -2658,12 +2727,21 @@ if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EBADMSG); + if (nvlist_exists_string(nvl, "anchor")) + anchor = nvlist_get_string(nvl, "anchor"); + if (nvlist_exists_string(nvl, "anchor_call")) + anchor_call = nvlist_get_string(nvl, "anchor_call"); + + ruleset = pf_find_keth_ruleset(anchor); + if (ruleset == NULL) + ERROUT(EINVAL); + if (nvlist_get_number(nvl, "ticket") != - V_pf_keth_inactive->ticket) { + ruleset->inactive.ticket) { DPFPRINTF(PF_DEBUG_MISC, ("ticket: %d != %d\n", (u_int32_t)nvlist_get_number(nvl, "ticket"), - V_pf_keth_inactive->ticket)); + ruleset->inactive.ticket)); ERROUT(EBUSY); } @@ -2710,7 +2788,19 @@ ERROUT(error); } - TAILQ_INSERT_TAIL(&V_pf_keth_inactive->rules, rule, entries); + if (pf_keth_anchor_setup(rule, ruleset, anchor_call)) { + pf_free_eth_rule(rule); + PF_RULES_WUNLOCK(); + ERROUT(EINVAL); + } + + tail = TAILQ_LAST(ruleset->inactive.rules, pf_keth_ruleq); + if (tail) + rule->nr = tail->nr + 1; + else + rule->nr = 0; + + TAILQ_INSERT_TAIL(ruleset->inactive.rules, rule, entries); PF_RULES_WUNLOCK(); @@ -4765,13 +4855,7 @@ ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { case PF_RULESET_ETH: - if (ioe->anchor[0]) { - PF_RULES_WUNLOCK(); - free(ioes, M_TEMP); - error = EINVAL; - goto fail; - } - if ((error = pf_begin_eth(&ioe->ticket))) { + if ((error = pf_begin_eth(&ioe->ticket, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; @@ -4852,13 +4936,8 @@ ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { case PF_RULESET_ETH: - if (ioe->anchor[0]) { - PF_RULES_WUNLOCK(); - free(ioes, M_TEMP); - error = EINVAL; - goto fail; - } - if ((error = pf_rollback_eth(ioe->ticket))) { + if ((error = pf_rollback_eth(ioe->ticket, + ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ @@ -4913,6 +4992,7 @@ struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe, *ioes; struct pf_kruleset *rs; + struct pf_keth_ruleset *ers; size_t totlen; int i; @@ -4942,19 +5022,14 @@ ioe->anchor[sizeof(ioe->anchor) - 1] = 0; switch (ioe->rs_num) { case PF_RULESET_ETH: - if (ioe->anchor[0]) { + ers = pf_find_keth_ruleset(ioe->anchor); + if (ers == NULL || ioe->ticket == 0 || + ioe->ticket != ers->inactive.ticket) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); error = EINVAL; goto fail; } - if (!V_pf_keth_inactive->ticket || - ioe->ticket != V_pf_keth_inactive->ticket) { - PF_RULES_WUNLOCK(); - free(ioes, M_TEMP); - error = EBUSY; - goto fail; - } break; #ifdef ALTQ case PF_RULESET_ALTQ: @@ -5008,7 +5083,7 @@ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { case PF_RULESET_ETH: - if ((error = pf_commit_eth(ioe->ticket))) { + if ((error = pf_commit_eth(ioe->ticket, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_TEMP); goto fail; /* really bad */ @@ -5051,7 +5126,7 @@ PF_RULES_WUNLOCK(); /* Only hook into EtherNet taffic if we've got rules for it. */ - if (! TAILQ_EMPTY(&V_pf_keth->rules)) + if (! TAILQ_EMPTY(V_pf_keth->active.rules)) hook_pf_eth(); else dehook_pf_eth(); @@ -5962,11 +6037,11 @@ if ((error = pf_clear_tables()) != 0) break; - if ((error = pf_begin_eth(&t[0])) != 0) { + if ((error = pf_begin_eth(&t[0], &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, ("shutdown_pf: eth\n")); break; } - pf_commit_eth(t[0]); + pf_commit_eth(t[0], &nn); #ifdef ALTQ if ((error = pf_begin_altq(&t[0])) != 0) { @@ -6250,9 +6325,7 @@ PF_QUEUE_TAG_HASH_SIZE_DEFAULT); #endif - V_pf_keth = malloc(sizeof(*V_pf_keth), M_PFRULE, M_WAITOK); - V_pf_keth_inactive = malloc(sizeof(*V_pf_keth_inactive), - M_PFRULE, M_WAITOK); + V_pf_keth = &V_pf_main_keth_anchor.ruleset; pfattach_vnet(); V_pf_vnet_active = 1; @@ -6364,9 +6437,6 @@ pf_counter_u64_deinit(&V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) counter_u64_free(V_pf_status.scounters[i]); - - free(V_pf_keth, M_PFRULE); - free(V_pf_keth_inactive, M_PFRULE); } static void 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 @@ -1084,6 +1084,9 @@ nvlist_add_number(nvl, "dnpipe", krule->dnpipe); nvlist_add_number(nvl, "dnflags", krule->dnflags); + nvlist_add_number(nvl, "anchor_relative", krule->anchor_relative); + nvlist_add_number(nvl, "anchor_wildcard", krule->anchor_wildcard); + nvlist_add_number(nvl, "action", krule->action); return (nvl); diff --git a/sys/netpfil/pf/pf_ruleset.c b/sys/netpfil/pf/pf_ruleset.c --- a/sys/netpfil/pf/pf_ruleset.c +++ b/sys/netpfil/pf/pf_ruleset.c @@ -70,15 +70,22 @@ VNET_DEFINE(struct pf_kanchor_global, pf_anchors); VNET_DEFINE(struct pf_kanchor, pf_main_anchor); -VNET_DEFINE(struct pf_keth_settings*, pf_keth); -VNET_DEFINE(struct pf_keth_settings*, pf_keth_inactive); +VNET_DEFINE(struct pf_keth_ruleset*, pf_keth); +VNET_DEFINE(struct pf_keth_anchor, pf_main_keth_anchor); +VNET_DEFINE(struct pf_keth_anchor_global, pf_keth_anchors); static __inline int pf_kanchor_compare(struct pf_kanchor *, struct pf_kanchor *); +static __inline int pf_keth_anchor_compare(struct pf_keth_anchor *, + struct pf_keth_anchor *); static struct pf_kanchor *pf_find_kanchor(const char *); RB_GENERATE(pf_kanchor_global, pf_kanchor, entry_global, pf_kanchor_compare); RB_GENERATE(pf_kanchor_node, pf_kanchor, entry_node, pf_kanchor_compare); +RB_GENERATE(pf_keth_anchor_global, pf_keth_anchor, entry_global, + pf_keth_anchor_compare); +RB_GENERATE(pf_keth_anchor_node, pf_keth_anchor, entry_node, + pf_keth_anchor_compare); static __inline int pf_kanchor_compare(struct pf_kanchor *a, struct pf_kanchor *b) @@ -88,6 +95,14 @@ return (c ? (c < 0 ? -1 : 1) : 0); } +static __inline int +pf_keth_anchor_compare(struct pf_keth_anchor *a, struct pf_keth_anchor *b) +{ + int c = strcmp(a->path, b->path); + + return (c ? (c < 0 ? -1 : 1) : 0); +} + int pf_get_ruleset_number(u_int8_t action) { @@ -148,13 +163,18 @@ } void -pf_init_keth(struct pf_keth_settings *settings) +pf_init_keth(struct pf_keth_ruleset *rs) { - TAILQ_INIT(&settings->rules); - settings->ticket = 0; - settings->open = 0; - settings->vnet = curvnet; + bzero(rs, sizeof(*rs)); + TAILQ_INIT(&rs->rules[0]); + TAILQ_INIT(&rs->rules[1]); + rs->active.rules = &rs->rules[0]; + rs->active.open = 0; + rs->inactive.rules = &rs->rules[1]; + rs->inactive.open = 0; + + rs->vnet = curvnet; } struct pf_kruleset * @@ -396,6 +416,53 @@ return (0); } +int +pf_keth_anchor_nvcopyout(const struct pf_keth_ruleset *rs, + const struct pf_keth_rule *r, nvlist_t *nvl) +{ + char anchor_call[MAXPATHLEN] = { 0 }; + + if (r->anchor == NULL) + goto done; + if (!r->anchor_relative) { + strlcpy(anchor_call, "/", sizeof(anchor_call)); + strlcat(anchor_call, r->anchor->path, + sizeof(anchor_call)); + } else { + char a[MAXPATHLEN]; + char *p; + int i; + if (rs->anchor == NULL) + a[0] = 0; + else + strlcpy(a, rs->anchor->path, MAXPATHLEN); + for (i = 1; i < r->anchor_relative; ++i) { + if ((p = strrchr(a, '/')) == NULL) + p = a; + *p = 0; + strlcat(anchor_call, "../", + sizeof(anchor_call)); + } + if (strncmp(a, r->anchor->path, strlen(a))) { + printf("%s(): '%s' '%s'\n", __func__, a, + r->anchor->path); + return (1); + } + if (strlen(r->anchor->path) > strlen(a)) + strlcat(anchor_call, r->anchor->path + (a[0] ? + strlen(a) + 1 : 0), sizeof(anchor_call)); + + } + if (r->anchor_wildcard) + strlcat(anchor_call, anchor_call[0] ? "/*" : "*", + sizeof(anchor_call)); + +done: + nvlist_add_string(nvl, "anchor_call", anchor_call); + + return (0); +} + int pf_kanchor_copyout(const struct pf_kruleset *rs, const struct pf_krule *r, struct pfioc_rule *pr) @@ -456,3 +523,229 @@ pf_remove_if_empty_kruleset(&r->anchor->ruleset); r->anchor = NULL; } + +struct pf_keth_ruleset * +pf_find_keth_ruleset(const char *path) +{ + struct pf_keth_anchor *anchor; + + while (*path == '/') + path++; + if (!*path) + return (V_pf_keth); + anchor = pf_find_keth_anchor(path); + if (anchor == NULL) + return (NULL); + else + return (&anchor->ruleset); +} + +static struct pf_keth_anchor * +_pf_find_keth_anchor(struct pf_keth_ruleset *rs, const char *path) +{ + struct pf_keth_anchor *key, *found; + + key = (struct pf_keth_anchor *)rs_malloc(sizeof(*key)); + if (key == NULL) + return (NULL); + strlcpy(key->path, path, sizeof(key->path)); + found = RB_FIND(pf_keth_anchor_global, &V_pf_keth_anchors, key); + rs_free(key); + return (found); +} + +struct pf_keth_anchor * +pf_find_keth_anchor(const char *path) +{ + return (_pf_find_keth_anchor(V_pf_keth, path)); +} + +struct pf_keth_ruleset * +pf_find_or_create_keth_ruleset(const char *path) +{ + char *p, *q, *r; + struct pf_keth_anchor *anchor = NULL, *dup = NULL, *parent = NULL; + struct pf_keth_ruleset *ruleset; + + if (path[0] == 0) + return (V_pf_keth); + while (*path == '/') + path++; + ruleset = pf_find_keth_ruleset(path); + if (ruleset != NULL) + return (ruleset); + p = (char *)rs_malloc(MAXPATHLEN); + if (p == NULL) + return (NULL); + strlcpy(p, path, MAXPATHLEN); + while (parent == NULL && (q = strrchr(p, '/')) != NULL) { + *q = 0; + if ((ruleset = pf_find_keth_ruleset(p)) != NULL) { + parent = ruleset->anchor; + break; + } + } + if (q == NULL) + q = p; + else + q++; + strlcpy(p, path, MAXPATHLEN); + if (!*q) { + rs_free(p); + return (NULL); + } + while ((r = strchr(q, '/')) != NULL || *q) { + if (r != NULL) + *r = 0; + if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE || + (parent != NULL && strlen(parent->path) >= + MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { + rs_free(p); + return (NULL); + } + anchor = (struct pf_keth_anchor *)rs_malloc(sizeof(*anchor)); + if (anchor == NULL) { + rs_free(p); + return (NULL); + } + RB_INIT(&anchor->children); + strlcpy(anchor->name, q, sizeof(anchor->name)); + if (parent != NULL) { + strlcpy(anchor->path, parent->path, + sizeof(anchor->path)); + strlcat(anchor->path, "/", sizeof(anchor->path)); + } + strlcat(anchor->path, anchor->name, sizeof(anchor->path)); + if ((dup = RB_INSERT(pf_keth_anchor_global, &V_pf_keth_anchors, anchor)) != + NULL) { + printf("%s: RB_INSERT1 " + "'%s' '%s' collides with '%s' '%s'\n", __func__, + anchor->path, anchor->name, dup->path, dup->name); + rs_free(anchor); + rs_free(p); + return (NULL); + } + if (parent != NULL) { + anchor->parent = parent; + if ((dup = RB_INSERT(pf_keth_anchor_node, &parent->children, + anchor)) != NULL) { + printf("%s: " + "RB_INSERT2 '%s' '%s' collides with " + "'%s' '%s'\n", __func__, anchor->path, + anchor->name, dup->path, dup->name); + RB_REMOVE(pf_keth_anchor_global, &V_pf_keth_anchors, + anchor); + rs_free(anchor); + rs_free(p); + return (NULL); + } + } + pf_init_keth(&anchor->ruleset); + anchor->ruleset.anchor = anchor; + parent = anchor; + if (r != NULL) + q = r + 1; + else + *q = 0; + } + rs_free(p); + return (&anchor->ruleset); +} + +int +pf_keth_anchor_setup(struct pf_keth_rule *r, const struct pf_keth_ruleset *s, + const char *name) +{ + char *p, *path; + struct pf_keth_ruleset *ruleset; + + r->anchor = NULL; + r->anchor_relative = 0; + r->anchor_wildcard = 0; + if (!name[0]) + return (0); + path = (char *)rs_malloc(MAXPATHLEN); + if (path == NULL) + return (1); + if (name[0] == '/') + strlcpy(path, name + 1, MAXPATHLEN); + else { + /* relative path */ + r->anchor_relative = 1; + if (s->anchor == NULL || !s->anchor->path[0]) + path[0] = 0; + else + strlcpy(path, s->anchor->path, MAXPATHLEN); + while (name[0] == '.' && name[1] == '.' && name[2] == '/') { + if (!path[0]) { + DPFPRINTF("pf_anchor_setup: .. beyond root\n"); + rs_free(path); + return (1); + } + if ((p = strrchr(path, '/')) != NULL) + *p = 0; + else + path[0] = 0; + r->anchor_relative++; + name += 3; + } + if (path[0]) + strlcat(path, "/", MAXPATHLEN); + strlcat(path, name, MAXPATHLEN); + } + if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { + r->anchor_wildcard = 1; + *p = 0; + } + ruleset = pf_find_or_create_keth_ruleset(path); + rs_free(path); + if (ruleset == NULL || ruleset->anchor == NULL) { + DPFPRINTF("pf_anchor_setup: ruleset\n"); + return (1); + } + r->anchor = ruleset->anchor; + r->anchor->refcnt++; + return (0); +} + +void +pf_keth_anchor_remove(struct pf_keth_rule *r) +{ + if (r->anchor == NULL) + return; + if (r->anchor->refcnt <= 0) { + printf("%s: broken refcount\n", __func__); + r->anchor = NULL; + return; + } + if (!--r->anchor->refcnt) + pf_remove_if_empty_keth_ruleset(&r->anchor->ruleset); + r->anchor = NULL; +} + +void +pf_remove_if_empty_keth_ruleset(struct pf_keth_ruleset *ruleset) +{ + struct pf_keth_anchor *parent; + int i; + + while (ruleset != NULL) { + if (ruleset == V_pf_keth || ruleset->anchor == NULL || + !RB_EMPTY(&ruleset->anchor->children) || + ruleset->anchor->refcnt > 0) + return; + for (i = 0; i < PF_RULESET_MAX; ++i) + if (!TAILQ_EMPTY(ruleset->active.rules) || + !TAILQ_EMPTY(ruleset->inactive.rules) || + ruleset->inactive.open) + return; + RB_REMOVE(pf_keth_anchor_global, &V_pf_keth_anchors, ruleset->anchor); + if ((parent = ruleset->anchor->parent) != NULL) + RB_REMOVE(pf_keth_anchor_node, &parent->children, + ruleset->anchor); + rs_free(ruleset->anchor); + if (parent == NULL) + return; + ruleset = &parent->ruleset; + } +}