From 9bb06778f822ad6b47d2a825d47e284ca8dd29a1 Mon Sep 17 00:00:00 2001 From: Kristof Provost Date: Tue, 29 Mar 2022 14:15:10 +0200 Subject: [PATCH] pf: support listing ethernet anchors Sponsored by: Rubicon Communications, LLC ("Netgate") --- lib/libpfctl/libpfctl.c | 75 ++++++++++++++ lib/libpfctl/libpfctl.h | 14 +++ sbin/pfctl/pfctl.c | 40 ++++++++ sys/net/pfvar.h | 2 + sys/netpfil/pf/pf_ioctl.c | 182 ++++++++++++++++++++++++++++++++++ tests/sys/netpfil/pf/ether.sh | 2 + 6 files changed, 315 insertions(+) diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 8f064594260b..1e1a90594210 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -622,6 +622,81 @@ pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule) rule->action = nvlist_get_number(nvl, "action"); } +int +pfctl_get_eth_rulesets_info(int dev, struct pfctl_eth_rulesets_info *ri, + const char *path) +{ + uint8_t buf[1024]; + struct pfioc_nv nv; + nvlist_t *nvl; + void *packed; + size_t len; + + bzero(ri, sizeof(*ri)); + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "path", path); + packed = nvlist_pack(nvl, &len); + memcpy(buf, packed, len); + free(packed); + nvlist_destroy(nvl); + + nv.data = buf; + nv.len = len; + nv.size = sizeof(buf); + + if (ioctl(dev, DIOCGETETHRULESETS, &nv) != 0) + return (errno); + + nvl = nvlist_unpack(buf, nv.len, 0); + if (nvl == NULL) + return (EIO); + + ri->nr = nvlist_get_number(nvl, "nr"); + + nvlist_destroy(nvl); + return (0); +} + +int +pfctl_get_eth_ruleset(int dev, const char *path, int nr, + struct pfctl_eth_ruleset_info *ri) +{ + uint8_t buf[1024]; + struct pfioc_nv nv; + nvlist_t *nvl; + void *packed; + size_t len; + + bzero(ri, sizeof(*ri)); + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "path", path); + nvlist_add_number(nvl, "nr", nr); + packed = nvlist_pack(nvl, &len); + memcpy(buf, packed, len); + free(packed); + nvlist_destroy(nvl); + + nv.data = buf; + nv.len = len; + nv.size = sizeof(buf); + + if (ioctl(dev, DIOCGETETHRULESET, &nv) != 0) + return (errno); + + nvl = nvlist_unpack(buf, nv.len, 0); + if (nvl == NULL) + return (EIO); + + ri->nr = nvlist_get_number(nvl, "nr"); + strlcpy(ri->path, nvlist_get_string(nvl, "path"), MAXPATHLEN); + strlcpy(ri->name, nvlist_get_string(nvl, "name"), + PF_ANCHOR_NAME_SIZE); + + return (0); +} + int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, const char *path) diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index b7f703b64def..92a1ea9b7cef 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -66,6 +66,10 @@ struct pfctl_status { uint64_t bcounters[2][2]; }; +struct pfctl_eth_rulesets_info { + uint32_t nr; +}; + struct pfctl_eth_rules_info { uint32_t nr; uint32_t ticket; @@ -111,6 +115,12 @@ struct pfctl_eth_rule { }; TAILQ_HEAD(pfctl_eth_rules, pfctl_eth_rule); +struct pfctl_eth_ruleset_info { + uint32_t nr; + char name[PF_ANCHOR_NAME_SIZE]; + char path[MAXPATHLEN]; +}; + struct pfctl_eth_ruleset { struct pfctl_eth_rules rules; struct pfctl_eth_anchor *anchor; @@ -356,6 +366,10 @@ struct pfctl_syncookies { struct pfctl_status* pfctl_get_status(int dev); void pfctl_free_status(struct pfctl_status *status); +int pfctl_get_eth_rulesets_info(int dev, + struct pfctl_eth_rulesets_info *ri, const char *path); +int pfctl_get_eth_ruleset(int dev, const char *path, int nr, + struct pfctl_eth_ruleset_info *ri); 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, diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 88a96bd303a0..67358a325f77 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -111,6 +111,7 @@ int pfctl_show_limits(int, int); void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); +int pfctl_show_eth_anchors(int, int, char *); int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool); int pfctl_eth_ruleset_trans(struct pfctl *, char *, struct pfctl_eth_anchor *); @@ -2604,6 +2605,44 @@ pfctl_show_anchors(int dev, int opts, char *anchorname) return (0); } +int +pfctl_show_eth_anchors(int dev, int opts, char *anchorname) +{ + struct pfctl_eth_rulesets_info ri; + struct pfctl_eth_ruleset_info rs; + int ret; + + if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) { + if (ret == ENOENT) + fprintf(stderr, "Anchor '%s' not found.\n", + anchorname); + else + err(1, "DIOCGETETHRULESETS"); + return (-1); + } + + for (int nr = 0; nr < ri.nr; nr++) { + char sub[MAXPATHLEN]; + + if (pfctl_get_eth_ruleset(dev, anchorname, nr, &rs) != 0) + err(1, "DIOCGETETHRULESET"); + + if (!strcmp(rs.name, PF_RESERVED_ANCHOR)) + continue; + sub[0] = 0; + if (rs.path[0]) { + strlcat(sub, rs.path, sizeof(sub)); + strlcat(sub, "/", sizeof(sub)); + } + strlcat(sub, rs.name, sizeof(sub)); + if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) + printf(" %s\n", sub); + if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub)) + return (-1); + } + return (0); +} + const char * pfctl_lookup_option(char *cmd, const char * const *list) { @@ -2830,6 +2869,7 @@ main(int argc, char *argv[]) switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); + pfctl_show_eth_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index ccc81ea137b9..db6b5c22f07f 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1865,6 +1865,8 @@ struct pfioc_iface { #define DIOCADDETHRULE _IOWR('D', 97, struct pfioc_nv) #define DIOCGETETHRULE _IOWR('D', 98, struct pfioc_nv) #define DIOCGETETHRULES _IOWR('D', 99, struct pfioc_nv) +#define DIOCGETETHRULESETS _IOWR('D', 100, struct pfioc_nv) +#define DIOCGETETHRULESET _IOWR('D', 101, struct pfioc_nv) struct pf_ifspeed_v0 { char ifname[IFNAMSIZ]; diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index 6012825ead9b..3cb5552d20c5 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -2463,6 +2463,8 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td case DIOCCLRIFFLAG: case DIOCGETETHRULES: case DIOCGETETHRULE: + case DIOCGETETHRULESETS: + case DIOCGETETHRULESET: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: @@ -2512,6 +2514,8 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td case DIOCGETRULENV: case DIOCGETETHRULES: case DIOCGETETHRULE: + case DIOCGETETHRULESETS: + case DIOCGETETHRULESET: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: @@ -2864,6 +2868,184 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td break; } + case DIOCGETETHRULESETS: { + struct epoch_tracker et; + struct pfioc_nv *nv = (struct pfioc_nv *)addr; + nvlist_t *nvl = NULL; + void *nvlpacked = NULL; + struct pf_keth_ruleset *ruleset; + struct pf_keth_anchor *anchor; + int nr = 0; + +#define ERROUT(x) do { error = (x); goto DIOCGETETHRULESETS_error; } while (0) + + if (nv->len > pf_ioctl_maxcount) + ERROUT(ENOMEM); + + nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + error = copyin(nv->data, nvlpacked, nv->len); + if (error) + ERROUT(error); + + nvl = nvlist_unpack(nvlpacked, nv->len, 0); + if (nvl == NULL) + ERROUT(EBADMSG); + if (! nvlist_exists_string(nvl, "path")) + ERROUT(EBADMSG); + + NET_EPOCH_ENTER(et); + + if ((ruleset = pf_find_keth_ruleset( + nvlist_get_string(nvl, "path"))) == NULL) { + NET_EPOCH_EXIT(et); + ERROUT(ENOENT); + } + + if (ruleset->anchor == NULL) { + RB_FOREACH(anchor, pf_keth_anchor_global, &V_pf_keth_anchors) + if (anchor->parent == NULL) + nr++; + } else { + RB_FOREACH(anchor, pf_keth_anchor_node, + &ruleset->anchor->children) + nr++; + } + + NET_EPOCH_EXIT(et); + + nvlist_destroy(nvl); + nvl = NULL; + free(nvlpacked, M_NVLIST); + nvlpacked = NULL; + + nvl = nvlist_create(0); + if (nvl == NULL) + ERROUT(ENOMEM); + + nvlist_add_number(nvl, "nr", nr); + + nvlpacked = nvlist_pack(nvl, &nv->len); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + if (nv->size == 0) + ERROUT(0); + else if (nv->size < nv->len) + ERROUT(ENOSPC); + + error = copyout(nvlpacked, nv->data, nv->len); + +#undef ERROUT +DIOCGETETHRULESETS_error: + free(nvlpacked, M_NVLIST); + nvlist_destroy(nvl); + break; + } + + case DIOCGETETHRULESET: { + struct epoch_tracker et; + struct pfioc_nv *nv = (struct pfioc_nv *)addr; + nvlist_t *nvl = NULL; + void *nvlpacked = NULL; + struct pf_keth_ruleset *ruleset; + struct pf_keth_anchor *anchor; + int nr = 0, req_nr = 0; + bool found = false; + +#define ERROUT(x) do { error = (x); goto DIOCGETETHRULESET_error; } while (0) + + if (nv->len > pf_ioctl_maxcount) + ERROUT(ENOMEM); + + nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + error = copyin(nv->data, nvlpacked, nv->len); + if (error) + ERROUT(error); + + nvl = nvlist_unpack(nvlpacked, nv->len, 0); + if (nvl == NULL) + ERROUT(EBADMSG); + if (! nvlist_exists_string(nvl, "path")) + ERROUT(EBADMSG); + if (! nvlist_exists_number(nvl, "nr")) + ERROUT(EBADMSG); + + req_nr = nvlist_get_number(nvl, "nr"); + + NET_EPOCH_ENTER(et); + + if ((ruleset = pf_find_keth_ruleset( + nvlist_get_string(nvl, "path"))) == NULL) { + NET_EPOCH_EXIT(et); + ERROUT(ENOENT); + } + + nvlist_destroy(nvl); + nvl = NULL; + free(nvlpacked, M_NVLIST); + nvlpacked = NULL; + + nvl = nvlist_create(0); + if (nvl == NULL) { + NET_EPOCH_EXIT(et); + ERROUT(ENOMEM); + } + + if (ruleset->anchor == NULL) { + RB_FOREACH(anchor, pf_keth_anchor_global, + &V_pf_keth_anchors) { + if (anchor->parent == NULL && nr++ == req_nr) { + found = true; + break; + } + } + } else { + RB_FOREACH(anchor, pf_keth_anchor_node, + &ruleset->anchor->children) { + if (nr++ == req_nr) { + found = true; + break; + } + } + } + + NET_EPOCH_EXIT(et); + if (found) { + nvlist_add_number(nvl, "nr", nr); + nvlist_add_string(nvl, "name", anchor->name); + if (ruleset->anchor) + nvlist_add_string(nvl, "path", + ruleset->anchor->path); + else + nvlist_add_string(nvl, "path", ""); + } else { + ERROUT(EBUSY); + } + + nvlpacked = nvlist_pack(nvl, &nv->len); + if (nvlpacked == NULL) + ERROUT(ENOMEM); + + if (nv->size == 0) + ERROUT(0); + else if (nv->size < nv->len) + ERROUT(ENOSPC); + + error = copyout(nvlpacked, nv->data, nv->len); + +#undef ERROUT +DIOCGETETHRULESET_error: + free(nvlpacked, M_NVLIST); + nvlist_destroy(nvl); + break; + } + case DIOCADDRULENV: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; diff --git a/tests/sys/netpfil/pf/ether.sh b/tests/sys/netpfil/pf/ether.sh index 6e81f07ca26e..7a7f91844148 100644 --- a/tests/sys/netpfil/pf/ether.sh +++ b/tests/sys/netpfil/pf/ether.sh @@ -490,6 +490,8 @@ anchor_body() "}" \ "ether pass in from ${epair_a_mac}" atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2 + + atf_check -s exit:0 -o match:'baz' jexec alcatraz pfctl -sA } anchor_cleanup()