diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index f12e45291fc1..e732c8eef854 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1,2220 +1,2252 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libpfctl.h" struct pfctl_handle { int fd; struct snl_state ss; }; const char* PFCTL_SYNCOOKIES_MODE_NAMES[] = { "never", "always", "adaptive" }; static int _pfctl_clear_states(int , const struct pfctl_kill *, unsigned int *, uint64_t); struct pfctl_handle * pfctl_open(const char *pf_device) { struct pfctl_handle *h; h = calloc(1, sizeof(struct pfctl_handle)); h->fd = -1; h->fd = open(pf_device, O_RDWR); if (h->fd < 0) goto error; if (!snl_init(&h->ss, NETLINK_GENERIC)) goto error; return (h); error: close(h->fd); snl_free(&h->ss); free(h); return (NULL); } void pfctl_close(struct pfctl_handle *h) { close(h->fd); snl_free(&h->ss); free(h); } static int pfctl_do_ioctl(int dev, uint cmd, size_t size, nvlist_t **nvl) { struct pfioc_nv nv; void *data; size_t nvlen; int ret; data = nvlist_pack(*nvl, &nvlen); if (nvlen > size) size = nvlen; retry: nv.data = malloc(size); if (nv.data == NULL) { ret = ENOMEM; goto out; } memcpy(nv.data, data, nvlen); nv.len = nvlen; nv.size = size; ret = ioctl(dev, cmd, &nv); if (ret == -1 && errno == ENOSPC) { size *= 2; free(nv.data); goto retry; } nvlist_destroy(*nvl); *nvl = NULL; if (ret == 0) { *nvl = nvlist_unpack(nv.data, nv.len, 0); if (*nvl == NULL) { ret = EIO; goto out; } } else { ret = errno; } out: free(data); free(nv.data); return (ret); } static void pf_nvuint_8_array(const nvlist_t *nvl, const char *name, size_t maxelems, uint8_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_16_array(const nvlist_t *nvl, const char *name, size_t maxelems, uint16_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_32_array(const nvlist_t *nvl, const char *name, size_t maxelems, uint32_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); for (size_t i = 0; i < elems && i < maxelems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_64_array(const nvlist_t *nvl, const char *name, size_t maxelems, uint64_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } int pfctl_startstop(struct pfctl_handle *h, int start) { struct snl_errmsg_data e = {}; struct snl_writer nw; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); if (family_id == 0) return (ENOTSUP); snl_init_writer(&h->ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, start ? PFNL_CMD_START : PFNL_CMD_STOP); hdr = snl_finalize_msg(&nw); if (hdr == NULL) return (ENOMEM); seq_id = hdr->nlmsg_seq; snl_send_message(&h->ss, hdr); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { } return (e.error); } static void _pfctl_get_status_counters(const nvlist_t *nvl, struct pfctl_status_counters *counters) { const uint64_t *ids, *counts; const char *const *names; size_t id_len, counter_len, names_len; ids = nvlist_get_number_array(nvl, "ids", &id_len); counts = nvlist_get_number_array(nvl, "counters", &counter_len); names = nvlist_get_string_array(nvl, "names", &names_len); assert(id_len == counter_len); assert(counter_len == names_len); TAILQ_INIT(counters); for (size_t i = 0; i < id_len; i++) { struct pfctl_status_counter *c; c = malloc(sizeof(*c)); if (c == NULL) continue; c->id = ids[i]; c->counter = counts[i]; c->name = strdup(names[i]); TAILQ_INSERT_TAIL(counters, c, entry); } } struct pfctl_status * pfctl_get_status(int dev) { struct pfctl_status *status; nvlist_t *nvl; size_t len; const void *chksum; status = calloc(1, sizeof(*status)); if (status == NULL) return (NULL); nvl = nvlist_create(0); if (pfctl_do_ioctl(dev, DIOCGETSTATUSNV, 4096, &nvl)) { nvlist_destroy(nvl); free(status); return (NULL); } status->running = nvlist_get_bool(nvl, "running"); status->since = nvlist_get_number(nvl, "since"); status->debug = nvlist_get_number(nvl, "debug"); status->hostid = ntohl(nvlist_get_number(nvl, "hostid")); status->states = nvlist_get_number(nvl, "states"); status->src_nodes = nvlist_get_number(nvl, "src_nodes"); status->syncookies_active = nvlist_get_bool(nvl, "syncookies_active"); status->reass = nvlist_get_number(nvl, "reass"); strlcpy(status->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); chksum = nvlist_get_binary(nvl, "chksum", &len); assert(len == PF_MD5_DIGEST_LENGTH); memcpy(status->pf_chksum, chksum, len); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "counters"), &status->counters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "lcounters"), &status->lcounters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "fcounters"), &status->fcounters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "scounters"), &status->scounters); pf_nvuint_64_array(nvl, "pcounters", 2 * 2 * 3, (uint64_t *)status->pcounters, NULL); pf_nvuint_64_array(nvl, "bcounters", 2 * 2, (uint64_t *)status->bcounters, NULL); nvlist_destroy(nvl); return (status); } static uint64_t _pfctl_status_counter(struct pfctl_status_counters *counters, uint64_t id) { struct pfctl_status_counter *c; TAILQ_FOREACH(c, counters, entry) { if (c->id == id) return (c->counter); } return (0); } uint64_t pfctl_status_counter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->counters, id)); } uint64_t pfctl_status_lcounter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->lcounters, id)); } uint64_t pfctl_status_fcounter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->fcounters, id)); } uint64_t pfctl_status_scounter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->scounters, id)); } void pfctl_free_status(struct pfctl_status *status) { struct pfctl_status_counter *c, *tmp; if (status == NULL) return; TAILQ_FOREACH_SAFE(c, &status->counters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->lcounters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->fcounters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->scounters, entry, tmp) { free(c->name); free(c); } free(status); } static void pfctl_nv_add_addr(nvlist_t *nvparent, const char *name, const struct pf_addr *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "addr", addr, sizeof(*addr)); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvaddr_to_addr(const nvlist_t *nvl, struct pf_addr *addr) { size_t len; const void *data; data = nvlist_get_binary(nvl, "addr", &len); assert(len == sizeof(struct pf_addr)); memcpy(addr, data, len); } static void pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name, const struct pf_addr_wrap *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_number(nvl, "type", addr->type); nvlist_add_number(nvl, "iflags", addr->iflags); if (addr->type == PF_ADDR_DYNIFTL) nvlist_add_string(nvl, "ifname", addr->v.ifname); if (addr->type == PF_ADDR_TABLE) nvlist_add_string(nvl, "tblname", addr->v.tblname); pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr); pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvaddr_wrap_to_addr_wrap(const nvlist_t *nvl, struct pf_addr_wrap *addr) { bzero(addr, sizeof(*addr)); addr->type = nvlist_get_number(nvl, "type"); addr->iflags = nvlist_get_number(nvl, "iflags"); if (addr->type == PF_ADDR_DYNIFTL) { strlcpy(addr->v.ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); addr->p.dyncnt = nvlist_get_number(nvl, "dyncnt"); } if (addr->type == PF_ADDR_TABLE) { strlcpy(addr->v.tblname, nvlist_get_string(nvl, "tblname"), PF_TABLE_NAME_SIZE); addr->p.tblcnt = nvlist_get_number(nvl, "tblcnt"); } pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &addr->v.a.addr); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "mask"), &addr->v.a.mask); } static void pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name, const struct pf_rule_addr *addr) { uint64_t ports[2]; nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr); ports[0] = addr->port[0]; ports[1] = addr->port[1]; nvlist_add_number_array(nvl, "port", ports, 2); nvlist_add_number(nvl, "neg", addr->neg); nvlist_add_number(nvl, "port_op", addr->port_op); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvrule_addr_to_rule_addr(const nvlist_t *nvl, struct pf_rule_addr *addr) { pf_nvaddr_wrap_to_addr_wrap(nvlist_get_nvlist(nvl, "addr"), &addr->addr); pf_nvuint_16_array(nvl, "port", 2, addr->port, NULL); addr->neg = nvlist_get_number(nvl, "neg"); addr->port_op = nvlist_get_number(nvl, "port_op"); } static void pf_nvmape_to_mape(const nvlist_t *nvl, struct pf_mape_portset *mape) { mape->offset = nvlist_get_number(nvl, "offset"); mape->psidlen = nvlist_get_number(nvl, "psidlen"); mape->psid = nvlist_get_number(nvl, "psid"); } static void pf_nvpool_to_pool(const nvlist_t *nvl, struct pfctl_pool *pool) { size_t len; const void *data; data = nvlist_get_binary(nvl, "key", &len); assert(len == sizeof(pool->key)); memcpy(&pool->key, data, len); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "counter"), &pool->counter); pool->tblidx = nvlist_get_number(nvl, "tblidx"); pf_nvuint_16_array(nvl, "proxy_port", 2, pool->proxy_port, NULL); pool->opts = nvlist_get_number(nvl, "opts"); if (nvlist_exists_nvlist(nvl, "mape")) pf_nvmape_to_mape(nvlist_get_nvlist(nvl, "mape"), &pool->mape); } static void pf_nvrule_uid_to_rule_uid(const nvlist_t *nvl, struct pf_rule_uid *uid) { pf_nvuint_32_array(nvl, "uid", 2, uid->uid, NULL); uid->op = nvlist_get_number(nvl, "op"); } static void pf_nvdivert_to_divert(const nvlist_t *nvl, struct pfctl_rule *rule) { pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &rule->divert.addr); rule->divert.port = nvlist_get_number(nvl, "port"); } static void pf_nvrule_to_rule(const nvlist_t *nvl, struct pfctl_rule *rule) { const uint64_t *skip; const char *const *labels; size_t skipcount, labelcount; rule->nr = nvlist_get_number(nvl, "nr"); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); skip = nvlist_get_number_array(nvl, "skip", &skipcount); assert(skip); assert(skipcount == PF_SKIP_COUNT); for (int i = 0; i < PF_SKIP_COUNT; i++) rule->skip[i].nr = skip[i]; labels = nvlist_get_string_array(nvl, "labels", &labelcount); assert(labelcount <= PF_RULE_MAX_LABEL_COUNT); for (size_t i = 0; i < labelcount; i++) strlcpy(rule->label[i], labels[i], PF_RULE_LABEL_SIZE); rule->ridentifier = nvlist_get_number(nvl, "ridentifier"); strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); strlcpy(rule->pqname, nvlist_get_string(nvl, "pqname"), PF_QNAME_SIZE); strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"), PF_TABLE_NAME_SIZE); pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool); rule->evaluations = nvlist_get_number(nvl, "evaluations"); pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL); pf_nvuint_64_array(nvl, "bytes", 2, rule->bytes, NULL); if (nvlist_exists_number(nvl, "timestamp")) { rule->last_active_timestamp = nvlist_get_number(nvl, "timestamp"); } rule->os_fingerprint = nvlist_get_number(nvl, "os_fingerprint"); rule->rtableid = nvlist_get_number(nvl, "rtableid"); pf_nvuint_32_array(nvl, "timeout", PFTM_MAX, rule->timeout, NULL); rule->max_states = nvlist_get_number(nvl, "max_states"); rule->max_src_nodes = nvlist_get_number(nvl, "max_src_nodes"); rule->max_src_states = nvlist_get_number(nvl, "max_src_states"); rule->max_src_conn = nvlist_get_number(nvl, "max_src_conn"); rule->max_src_conn_rate.limit = nvlist_get_number(nvl, "max_src_conn_rate.limit"); rule->max_src_conn_rate.seconds = nvlist_get_number(nvl, "max_src_conn_rate.seconds"); rule->qid = nvlist_get_number(nvl, "qid"); rule->pqid = nvlist_get_number(nvl, "pqid"); rule->dnpipe = nvlist_get_number(nvl, "dnpipe"); rule->dnrpipe = nvlist_get_number(nvl, "dnrpipe"); rule->free_flags = nvlist_get_number(nvl, "dnflags"); rule->prob = nvlist_get_number(nvl, "prob"); rule->cuid = nvlist_get_number(nvl, "cuid"); rule->cpid = nvlist_get_number(nvl, "cpid"); rule->return_icmp = nvlist_get_number(nvl, "return_icmp"); rule->return_icmp6 = nvlist_get_number(nvl, "return_icmp6"); rule->max_mss = nvlist_get_number(nvl, "max_mss"); rule->scrub_flags = nvlist_get_number(nvl, "scrub_flags"); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "uid"), &rule->uid); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "gid"), (struct pf_rule_uid *)&rule->gid); rule->rule_flag = nvlist_get_number(nvl, "rule_flag"); rule->action = nvlist_get_number(nvl, "action"); rule->direction = nvlist_get_number(nvl, "direction"); rule->log = nvlist_get_number(nvl, "log"); rule->logif = nvlist_get_number(nvl, "logif"); rule->quick = nvlist_get_number(nvl, "quick"); rule->ifnot = nvlist_get_number(nvl, "ifnot"); rule->match_tag_not = nvlist_get_number(nvl, "match_tag_not"); rule->natpass = nvlist_get_number(nvl, "natpass"); rule->keep_state = nvlist_get_number(nvl, "keep_state"); rule->af = nvlist_get_number(nvl, "af"); rule->proto = nvlist_get_number(nvl, "proto"); rule->type = nvlist_get_number(nvl, "type"); rule->code = nvlist_get_number(nvl, "code"); rule->flags = nvlist_get_number(nvl, "flags"); rule->flagset = nvlist_get_number(nvl, "flagset"); rule->min_ttl = nvlist_get_number(nvl, "min_ttl"); rule->allow_opts = nvlist_get_number(nvl, "allow_opts"); rule->rt = nvlist_get_number(nvl, "rt"); rule->return_ttl = nvlist_get_number(nvl, "return_ttl"); rule->tos = nvlist_get_number(nvl, "tos"); rule->set_tos = nvlist_get_number(nvl, "set_tos"); rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); rule->flush = nvlist_get_number(nvl, "flush"); rule->prio = nvlist_get_number(nvl, "prio"); pf_nvuint_8_array(nvl, "set_prio", 2, rule->set_prio, NULL); pf_nvdivert_to_divert(nvlist_get_nvlist(nvl, "divert"), rule); rule->states_cur = nvlist_get_number(nvl, "states_cur"); rule->states_tot = nvlist_get_number(nvl, "states_tot"); rule->src_nodes = nvlist_get_number(nvl, "src_nodes"); } static void pfctl_nveth_addr_to_eth_addr(const nvlist_t *nvl, struct pfctl_eth_addr *addr) { static const u_int8_t EMPTY_MAC[ETHER_ADDR_LEN] = { 0 }; size_t len; const void *data; data = nvlist_get_binary(nvl, "addr", &len); assert(len == sizeof(addr->addr)); memcpy(addr->addr, data, sizeof(addr->addr)); data = nvlist_get_binary(nvl, "mask", &len); assert(len == sizeof(addr->mask)); memcpy(addr->mask, data, sizeof(addr->mask)); addr->neg = nvlist_get_bool(nvl, "neg"); /* To make checks for 'is this address set?' easier. */ addr->isset = memcmp(addr->addr, EMPTY_MAC, ETHER_ADDR_LEN) != 0; } static nvlist_t * pfctl_eth_addr_to_nveth_addr(const struct pfctl_eth_addr *addr) { nvlist_t *nvl; nvl = nvlist_create(0); if (nvl == NULL) return (NULL); nvlist_add_bool(nvl, "neg", addr->neg); nvlist_add_binary(nvl, "addr", &addr->addr, ETHER_ADDR_LEN); nvlist_add_binary(nvl, "mask", &addr->mask, ETHER_ADDR_LEN); return (nvl); } static void pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule) { const char *const *labels; size_t labelcount, i; rule->nr = nvlist_get_number(nvl, "nr"); rule->quick = nvlist_get_bool(nvl, "quick"); strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); 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"); labels = nvlist_get_string_array(nvl, "labels", &labelcount); assert(labelcount <= PF_RULE_MAX_LABEL_COUNT); for (i = 0; i < labelcount; i++) strlcpy(rule->label[i], labels[i], PF_RULE_LABEL_SIZE); rule->ridentifier = nvlist_get_number(nvl, "ridentifier"); pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipsrc"), &rule->ipsrc); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipdst"), &rule->ipdst); rule->evaluations = nvlist_get_number(nvl, "evaluations"); rule->packets[0] = nvlist_get_number(nvl, "packets-in"); rule->packets[1] = nvlist_get_number(nvl, "packets-out"); rule->bytes[0] = nvlist_get_number(nvl, "bytes-in"); rule->bytes[1] = nvlist_get_number(nvl, "bytes-out"); if (nvlist_exists_number(nvl, "timestamp")) { rule->last_active_timestamp = nvlist_get_number(nvl, "timestamp"); } strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), PF_TAG_NAME_SIZE); 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"); strlcpy(rule->bridge_to, nvlist_get_string(nvl, "bridge_to"), IFNAMSIZ); rule->action = nvlist_get_number(nvl, "action"); } int pfctl_get_eth_rulesets_info(int dev, struct pfctl_eth_rulesets_info *ri, const char *path) { nvlist_t *nvl; int ret; bzero(ri, sizeof(*ri)); nvl = nvlist_create(0); nvlist_add_string(nvl, "path", path); if ((ret = pfctl_do_ioctl(dev, DIOCGETETHRULESETS, 256, &nvl)) != 0) goto out; ri->nr = nvlist_get_number(nvl, "nr"); out: nvlist_destroy(nvl); return (ret); } int pfctl_get_eth_ruleset(int dev, const char *path, int nr, struct pfctl_eth_ruleset_info *ri) { nvlist_t *nvl; int ret; bzero(ri, sizeof(*ri)); nvl = nvlist_create(0); nvlist_add_string(nvl, "path", path); nvlist_add_number(nvl, "nr", nr); if ((ret = pfctl_do_ioctl(dev, DIOCGETETHRULESET, 1024, &nvl)) != 0) goto out; 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); out: nvlist_destroy(nvl); return (ret); } int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, const char *path) { nvlist_t *nvl; int ret; bzero(rules, sizeof(*rules)); nvl = nvlist_create(0); nvlist_add_string(nvl, "anchor", path); if ((ret = pfctl_do_ioctl(dev, DIOCGETETHRULES, 1024, &nvl)) != 0) goto out; rules->nr = nvlist_get_number(nvl, "nr"); rules->ticket = nvlist_get_number(nvl, "ticket"); out: nvlist_destroy(nvl); return (ret); } int pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket, const char *path, struct pfctl_eth_rule *rule, bool clear, char *anchor_call) { nvlist_t *nvl; int ret; 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); if ((ret = pfctl_do_ioctl(dev, DIOCGETETHRULE, 4096, &nvl)) != 0) goto out; pfctl_nveth_rule_to_eth_rule(nvl, rule); if (anchor_call) strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), MAXPATHLEN); out: nvlist_destroy(nvl); return (ret); } int 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; void *packed; int error = 0; size_t labelcount, size; 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); nvlist_add_string(nvl, "ifname", r->ifname); 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) { nvlist_destroy(nvl); return (ENOMEM); } nvlist_add_nvlist(nvl, "src", addr); nvlist_destroy(addr); addr = pfctl_eth_addr_to_nveth_addr(&r->dst); if (addr == NULL) { nvlist_destroy(nvl); return (ENOMEM); } nvlist_add_nvlist(nvl, "dst", addr); nvlist_destroy(addr); pfctl_nv_add_rule_addr(nvl, "ipsrc", &r->ipsrc); pfctl_nv_add_rule_addr(nvl, "ipdst", &r->ipdst); labelcount = 0; while (labelcount < PF_RULE_MAX_LABEL_COUNT && r->label[labelcount][0] != 0) { nvlist_append_string_array(nvl, "labels", r->label[labelcount]); labelcount++; } nvlist_add_number(nvl, "ridentifier", r->ridentifier); nvlist_add_string(nvl, "qname", r->qname); nvlist_add_string(nvl, "tagname", r->tagname); nvlist_add_number(nvl, "dnpipe", r->dnpipe); nvlist_add_number(nvl, "dnflags", r->dnflags); nvlist_add_string(nvl, "bridge_to", r->bridge_to); nvlist_add_number(nvl, "action", r->action); packed = nvlist_pack(nvl, &size); if (packed == NULL) { nvlist_destroy(nvl); return (ENOMEM); } nv.len = size; nv.size = size; nv.data = packed; if (ioctl(dev, DIOCADDETHRULE, &nv) != 0) error = errno; free(packed); nvlist_destroy(nvl); return (error); } static void snl_add_msg_attr_addr_wrap(struct snl_writer *nw, uint32_t type, const struct pf_addr_wrap *addr) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_ip6(nw, PF_AT_ADDR, &addr->v.a.addr.v6); snl_add_msg_attr_ip6(nw, PF_AT_MASK, &addr->v.a.mask.v6); if (addr->type == PF_ADDR_DYNIFTL) snl_add_msg_attr_string(nw, PF_AT_IFNAME, addr->v.ifname); if (addr->type == PF_ADDR_TABLE) snl_add_msg_attr_string(nw, PF_AT_TABLENAME, addr->v.tblname); snl_add_msg_attr_u8(nw, PF_AT_TYPE, addr->type); snl_add_msg_attr_u8(nw, PF_AT_IFLAGS, addr->iflags); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_rule_addr(struct snl_writer *nw, uint32_t type, const struct pf_rule_addr *addr) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_addr_wrap(nw, PF_RAT_ADDR, &addr->addr); snl_add_msg_attr_u16(nw, PF_RAT_SRC_PORT, addr->port[0]); snl_add_msg_attr_u16(nw, PF_RAT_DST_PORT, addr->port[1]); snl_add_msg_attr_u8(nw, PF_RAT_NEG, addr->neg); snl_add_msg_attr_u8(nw, PF_RAT_OP, addr->port_op); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_rule_labels(struct snl_writer *nw, uint32_t type, const char labels[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]) { int off, i = 0; off = snl_add_msg_attr_nested(nw, type); while (i < PF_RULE_MAX_LABEL_COUNT && labels[i][0] != 0) { snl_add_msg_attr_string(nw, PF_LT_LABEL, labels[i]); i++; } snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_mape(struct snl_writer *nw, uint32_t type, const struct pf_mape_portset *me) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_u8(nw, PF_MET_OFFSET, me->offset); snl_add_msg_attr_u8(nw, PF_MET_PSID_LEN, me->psidlen); snl_add_msg_attr_u16(nw, PF_MET_PSID, me->psid); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_rpool(struct snl_writer *nw, uint32_t type, const struct pfctl_pool *pool) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr(nw, PF_PT_KEY, sizeof(pool->key), &pool->key); snl_add_msg_attr_ip6(nw, PF_PT_COUNTER, &pool->counter.v6); snl_add_msg_attr_u32(nw, PF_PT_TBLIDX, pool->tblidx); snl_add_msg_attr_u16(nw, PF_PT_PROXY_SRC_PORT, pool->proxy_port[0]); snl_add_msg_attr_u16(nw, PF_PT_PROXY_DST_PORT, pool->proxy_port[1]); snl_add_msg_attr_u8(nw, PF_PT_OPTS, pool->opts); snl_add_msg_attr_mape(nw, PF_PT_MAPE, &pool->mape); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_timeouts(struct snl_writer *nw, uint32_t type, const uint32_t *timeouts) { int off; off = snl_add_msg_attr_nested(nw, type); for (int i = 0; i < PFTM_MAX; i++) snl_add_msg_attr_u32(nw, PF_TT_TIMEOUT, timeouts[i]); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_uid(struct snl_writer *nw, uint32_t type, const struct pf_rule_uid *uid) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_u32(nw, PF_RUT_UID_LOW, uid->uid[0]); snl_add_msg_attr_u32(nw, PF_RUT_UID_HIGH, uid->uid[1]); snl_add_msg_attr_u8(nw, PF_RUT_OP, uid->op); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfctl_rule *r) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_rule_addr(nw, PF_RT_SRC, &r->src); snl_add_msg_attr_rule_addr(nw, PF_RT_DST, &r->dst); snl_add_msg_attr_rule_labels(nw, PF_RT_LABELS, r->label); snl_add_msg_attr_u32(nw, PF_RT_RIDENTIFIER, r->ridentifier); snl_add_msg_attr_string(nw, PF_RT_IFNAME, r->ifname); snl_add_msg_attr_string(nw, PF_RT_QNAME, r->qname); snl_add_msg_attr_string(nw, PF_RT_PQNAME, r->pqname); snl_add_msg_attr_string(nw, PF_RT_TAGNAME, r->tagname); snl_add_msg_attr_string(nw, PF_RT_MATCH_TAGNAME, r->match_tagname); snl_add_msg_attr_string(nw, PF_RT_OVERLOAD_TBLNAME, r->overload_tblname); snl_add_msg_attr_rpool(nw, PF_RT_RPOOL, &r->rpool); snl_add_msg_attr_u32(nw, PF_RT_OS_FINGERPRINT, r->os_fingerprint); snl_add_msg_attr_u32(nw, PF_RT_RTABLEID, r->rtableid); snl_add_msg_attr_timeouts(nw, PF_RT_TIMEOUT, r->timeout); snl_add_msg_attr_u32(nw, PF_RT_MAX_STATES, r->max_states); snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_NODES, r->max_src_nodes); snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_STATES, r->max_src_states); snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_CONN_RATE_LIMIT, r->max_src_conn_rate.limit); snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_CONN_RATE_SECS, r->max_src_conn_rate.seconds); snl_add_msg_attr_u16(nw, PF_RT_DNPIPE, r->dnpipe); snl_add_msg_attr_u16(nw, PF_RT_DNRPIPE, r->dnrpipe); snl_add_msg_attr_u32(nw, PF_RT_DNFLAGS, r->free_flags); snl_add_msg_attr_u32(nw, PF_RT_NR, r->nr); snl_add_msg_attr_u32(nw, PF_RT_PROB, r->prob); snl_add_msg_attr_u32(nw, PF_RT_CUID, r->cuid); snl_add_msg_attr_u32(nw, PF_RT_CPID, r->cpid); snl_add_msg_attr_u16(nw, PF_RT_RETURN_ICMP, r->return_icmp); snl_add_msg_attr_u16(nw, PF_RT_RETURN_ICMP6, r->return_icmp6); snl_add_msg_attr_u16(nw, PF_RT_MAX_MSS, r->max_mss); snl_add_msg_attr_u16(nw, PF_RT_SCRUB_FLAGS, r->scrub_flags); snl_add_msg_attr_uid(nw, PF_RT_UID, &r->uid); snl_add_msg_attr_uid(nw, PF_RT_GID, (const struct pf_rule_uid *)&r->gid); snl_add_msg_attr_u32(nw, PF_RT_RULE_FLAG, r->rule_flag); snl_add_msg_attr_u8(nw, PF_RT_ACTION, r->action); snl_add_msg_attr_u8(nw, PF_RT_DIRECTION, r->direction); snl_add_msg_attr_u8(nw, PF_RT_LOG, r->log); snl_add_msg_attr_u8(nw, PF_RT_LOGIF, r->logif); snl_add_msg_attr_u8(nw, PF_RT_QUICK, r->quick); snl_add_msg_attr_u8(nw, PF_RT_IF_NOT, r->ifnot); snl_add_msg_attr_u8(nw, PF_RT_MATCH_TAG_NOT, r->match_tag_not); snl_add_msg_attr_u8(nw, PF_RT_NATPASS, r->natpass); snl_add_msg_attr_u8(nw, PF_RT_KEEP_STATE, r->keep_state); snl_add_msg_attr_u8(nw, PF_RT_AF, r->af); snl_add_msg_attr_u8(nw, PF_RT_PROTO, r->proto); snl_add_msg_attr_u8(nw, PF_RT_TYPE, r->type); snl_add_msg_attr_u8(nw, PF_RT_CODE, r->code); snl_add_msg_attr_u8(nw, PF_RT_FLAGS, r->flags); snl_add_msg_attr_u8(nw, PF_RT_FLAGSET, r->flagset); snl_add_msg_attr_u8(nw, PF_RT_MIN_TTL, r->min_ttl); snl_add_msg_attr_u8(nw, PF_RT_ALLOW_OPTS, r->allow_opts); snl_add_msg_attr_u8(nw, PF_RT_RT, r->rt); snl_add_msg_attr_u8(nw, PF_RT_RETURN_TTL, r->return_ttl); snl_add_msg_attr_u8(nw, PF_RT_TOS, r->tos); snl_add_msg_attr_u8(nw, PF_RT_SET_TOS, r->set_tos); snl_add_msg_attr_u8(nw, PF_RT_ANCHOR_RELATIVE, r->anchor_relative); snl_add_msg_attr_u8(nw, PF_RT_ANCHOR_WILDCARD, r->anchor_wildcard); snl_add_msg_attr_u8(nw, PF_RT_FLUSH, r->flush); snl_add_msg_attr_u8(nw, PF_RT_PRIO, r->prio); snl_add_msg_attr_u8(nw, PF_RT_SET_PRIO, r->set_prio[0]); snl_add_msg_attr_u8(nw, PF_RT_SET_PRIO_REPLY, r->set_prio[1]); snl_add_msg_attr_ip6(nw, PF_RT_DIVERT_ADDRESS, &r->divert.addr.v6); snl_add_msg_attr_u16(nw, PF_RT_DIVERT_PORT, r->divert.port); snl_end_attr_nested(nw, off); } int pfctl_add_rule(int dev __unused, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, uint32_t ticket, uint32_t pool_ticket) { struct pfctl_handle *h; int ret; h = pfctl_open(PF_DEVICE); if (h == NULL) return (ENODEV); ret = pfctl_add_rule_h(h, r, anchor, anchor_call, ticket, pool_ticket); pfctl_close(h); return (ret); } int pfctl_add_rule_h(struct pfctl_handle *h, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, uint32_t ticket, uint32_t pool_ticket) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); if (family_id == 0) return (ENOTSUP); snl_init_writer(&h->ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_ADDRULE); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_u32(&nw, PF_ART_TICKET, ticket); snl_add_msg_attr_u32(&nw, PF_ART_POOL_TICKET, pool_ticket); snl_add_msg_attr_string(&nw, PF_ART_ANCHOR, anchor); snl_add_msg_attr_string(&nw, PF_ART_ANCHOR_CALL, anchor_call); snl_add_msg_attr_pf_rule(&nw, PF_ART_RULE, r); if ((hdr = snl_finalize_msg(&nw)) == NULL) return (ENXIO); seq_id = hdr->nlmsg_seq; if (! snl_send_message(&h->ss, hdr)) return (ENXIO); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { } return (e.error); } #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfctl_rules_info, _field) static struct snl_attr_parser ap_getrules[] = { { .type = PF_GR_NR, .off = _OUT(nr), .cb = snl_attr_get_uint32 }, { .type = PF_GR_TICKET, .off = _OUT(ticket), .cb = snl_attr_get_uint32 }, }; static struct snl_field_parser fp_getrules[] = { }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, fp_getrules, ap_getrules); int pfctl_get_rules_info(int dev __unused, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path) { struct snl_state ss = {}; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; struct snl_writer nw; uint32_t seq_id; int family_id; snl_init(&ss, NETLINK_GENERIC); family_id = snl_get_genl_family(&ss, PFNL_FAMILY_NAME); if (family_id == 0) return (ENOTSUP); snl_init_writer(&ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETRULES); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_string(&nw, PF_GR_ANCHOR, path); snl_add_msg_attr_u8(&nw, PF_GR_ACTION, ruleset); hdr = snl_finalize_msg(&nw); if (hdr == NULL) return (ENOMEM); seq_id = hdr->nlmsg_seq; if (! snl_send_message(&ss, hdr)) return (ENXIO); while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { if (! snl_parse_nlmsg(&ss, hdr, &getrules_parser, rules)) continue; } return (e.error); } 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) { return (pfctl_get_clear_rule(dev, nr, ticket, anchor, ruleset, rule, anchor_call, false)); } #define _OUT(_field) offsetof(struct pf_addr_wrap, _field) static const struct snl_attr_parser ap_addr_wrap[] = { { .type = PF_AT_ADDR, .off = _OUT(v.a.addr), .cb = snl_attr_get_in6_addr }, { .type = PF_AT_MASK, .off = _OUT(v.a.mask), .cb = snl_attr_get_in6_addr }, { .type = PF_AT_IFNAME, .off = _OUT(v.ifname), .arg = (void *)IFNAMSIZ,.cb = snl_attr_copy_string }, { .type = PF_AT_TABLENAME, .off = _OUT(v.tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_AT_TYPE, .off = _OUT(type), .cb = snl_attr_get_uint8 }, { .type = PF_AT_IFLAGS, .off = _OUT(iflags), .cb = snl_attr_get_uint8 }, { .type = PF_AT_TBLCNT, .off = _OUT(p.tblcnt), .cb = snl_attr_get_uint32 }, { .type = PF_AT_DYNCNT, .off = _OUT(p.dyncnt), .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_ATTR_PARSER(addr_wrap_parser, ap_addr_wrap); #undef _OUT #define _OUT(_field) offsetof(struct pf_rule_addr, _field) static struct snl_attr_parser ap_rule_addr[] = { { .type = PF_RAT_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = snl_attr_get_nested }, { .type = PF_RAT_SRC_PORT, .off = _OUT(port[0]), .cb = snl_attr_get_uint16 }, { .type = PF_RAT_DST_PORT, .off = _OUT(port[1]), .cb = snl_attr_get_uint16 }, { .type = PF_RAT_NEG, .off = _OUT(neg), .cb = snl_attr_get_uint8 }, { .type = PF_RAT_OP, .off = _OUT(port_op), .cb = snl_attr_get_uint8 }, }; #undef _OUT SNL_DECLARE_ATTR_PARSER(rule_addr_parser, ap_rule_addr); struct snl_parsed_labels { char labels[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t i; }; static bool snl_attr_get_pf_rule_labels(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, void *target) { struct snl_parsed_labels *l = (struct snl_parsed_labels *)target; bool ret; if (l->i >= PF_RULE_MAX_LABEL_COUNT) return (E2BIG); ret = snl_attr_copy_string(ss, nla, (void *)PF_RULE_LABEL_SIZE, l->labels[l->i]); if (ret) l->i++; return (ret); } #define _OUT(_field) offsetof(struct nl_parsed_labels, _field) static const struct snl_attr_parser ap_labels[] = { { .type = PF_LT_LABEL, .off = 0, .cb = snl_attr_get_pf_rule_labels }, }; SNL_DECLARE_ATTR_PARSER(rule_labels_parser, ap_labels); #undef _OUT static bool snl_attr_get_nested_pf_rule_labels(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, void *target) { struct snl_parsed_labels parsed_labels = { }; bool error; /* Assumes target points to the beginning of the structure */ error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &rule_labels_parser, &parsed_labels); if (! error) return (error); memcpy(target, parsed_labels.labels, sizeof(parsed_labels)); return (true); } #define _OUT(_field) offsetof(struct pf_mape_portset, _field) static const struct snl_attr_parser ap_mape_portset[] = { { .type = PF_MET_OFFSET, .off = _OUT(offset), .cb = snl_attr_get_uint8 }, { .type = PF_MET_PSID_LEN, .off = _OUT(psidlen), .cb = snl_attr_get_uint8 }, {. type = PF_MET_PSID, .off = _OUT(psid), .cb = snl_attr_get_uint16 }, }; SNL_DECLARE_ATTR_PARSER(mape_portset_parser, ap_mape_portset); #undef _OUT #define _OUT(_field) offsetof(struct pfctl_pool, _field) static const struct snl_attr_parser ap_pool[] = { { .type = PF_PT_KEY, .off = _OUT(key), .arg = (void *)sizeof(struct pf_poolhashkey), .cb = snl_attr_get_bytes }, { .type = PF_PT_COUNTER, .off = _OUT(counter), .cb = snl_attr_get_in6_addr }, { .type = PF_PT_TBLIDX, .off = _OUT(tblidx), .cb = snl_attr_get_uint32 }, { .type = PF_PT_PROXY_SRC_PORT, .off = _OUT(proxy_port[0]), .cb = snl_attr_get_uint16 }, { .type = PF_PT_PROXY_DST_PORT, .off = _OUT(proxy_port[1]), .cb = snl_attr_get_uint16 }, { .type = PF_PT_OPTS, .off = _OUT(opts), .cb = snl_attr_get_uint8 }, { .type = PF_PT_MAPE, .off = _OUT(mape), .arg = &mape_portset_parser, .cb = snl_attr_get_nested }, }; SNL_DECLARE_ATTR_PARSER(pool_parser, ap_pool); #undef _OUT struct nl_parsed_timeouts { uint32_t timeouts[PFTM_MAX]; uint32_t i; }; static bool snl_attr_get_pf_timeout(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, void *target) { struct nl_parsed_timeouts *t = (struct nl_parsed_timeouts *)target; bool ret; if (t->i >= PFTM_MAX) return (E2BIG); ret = snl_attr_get_uint32(ss, nla, NULL, &t->timeouts[t->i]); if (ret) t->i++; return (ret); } #define _OUT(_field) offsetof(struct nl_parsed_timeout, _field) static const struct snl_attr_parser ap_timeouts[] = { { .type = PF_TT_TIMEOUT, .off = 0, .cb = snl_attr_get_pf_timeout }, }; SNL_DECLARE_ATTR_PARSER(timeout_parser, ap_timeouts); #undef _OUT static bool snl_attr_get_nested_timeouts(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, void *target) { struct nl_parsed_timeouts parsed_timeouts = { }; bool error; /* Assumes target points to the beginning of the structure */ error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &timeout_parser, &parsed_timeouts); if (! error) return (error); memcpy(target, parsed_timeouts.timeouts, sizeof(parsed_timeouts.timeouts)); return (true); } #define _OUT(_field) offsetof(struct pf_rule_uid, _field) static const struct snl_attr_parser ap_rule_uid[] = { { .type = PF_RUT_UID_LOW, .off = _OUT(uid[0]), .cb = snl_attr_get_uint32 }, { .type = PF_RUT_UID_HIGH, .off = _OUT(uid[1]), .cb = snl_attr_get_uint32 }, { .type = PF_RUT_OP, .off = _OUT(op), .cb = snl_attr_get_uint8 }, }; SNL_DECLARE_ATTR_PARSER(rule_uid_parser, ap_rule_uid); #undef _OUT struct pfctl_nl_get_rule { struct pfctl_rule r; char anchor_call[MAXPATHLEN]; }; #define _OUT(_field) offsetof(struct pfctl_nl_get_rule, _field) static struct snl_attr_parser ap_getrule[] = { { .type = PF_RT_SRC, .off = _OUT(r.src), .arg = &rule_addr_parser,.cb = snl_attr_get_nested }, { .type = PF_RT_DST, .off = _OUT(r.dst), .arg = &rule_addr_parser,.cb = snl_attr_get_nested }, { .type = PF_RT_RIDENTIFIER, .off = _OUT(r.ridentifier), .cb = snl_attr_get_uint32 }, { .type = PF_RT_LABELS, .off = _OUT(r.label), .arg = &rule_labels_parser,.cb = snl_attr_get_nested_pf_rule_labels }, { .type = PF_RT_IFNAME, .off = _OUT(r.ifname), .arg = (void *)IFNAMSIZ, .cb = snl_attr_copy_string }, { .type = PF_RT_QNAME, .off = _OUT(r.qname), .arg = (void *)PF_QNAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_RT_PQNAME, .off = _OUT(r.pqname), .arg = (void *)PF_QNAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_RT_TAGNAME, .off = _OUT(r.tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_RT_MATCH_TAGNAME, .off = _OUT(r.match_tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_RT_OVERLOAD_TBLNAME, .off = _OUT(r.overload_tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = snl_attr_copy_string }, { .type = PF_RT_RPOOL, .off = _OUT(r.rpool), .arg = &pool_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_OS_FINGERPRINT, .off = _OUT(r.os_fingerprint), .cb = snl_attr_get_uint32 }, { .type = PF_RT_RTABLEID, .off = _OUT(r.rtableid), .cb = snl_attr_get_uint32 }, { .type = PF_RT_TIMEOUT, .off = _OUT(r.timeout), .arg = &timeout_parser, .cb = snl_attr_get_nested_timeouts }, { .type = PF_RT_MAX_STATES, .off = _OUT(r.max_states), .cb = snl_attr_get_uint32 }, { .type = PF_RT_MAX_SRC_NODES, .off = _OUT(r.max_src_nodes), .cb = snl_attr_get_uint32 }, { .type = PF_RT_MAX_SRC_STATES, .off = _OUT(r.max_src_states), .cb = snl_attr_get_uint32 }, { .type = PF_RT_MAX_SRC_CONN_RATE_LIMIT, .off = _OUT(r.max_src_conn_rate.limit), .cb = snl_attr_get_uint32 }, { .type = PF_RT_MAX_SRC_CONN_RATE_SECS, .off = _OUT(r.max_src_conn_rate.seconds), .cb = snl_attr_get_uint32 }, { .type = PF_RT_DNPIPE, .off = _OUT(r.dnpipe), .cb = snl_attr_get_uint16 }, { .type = PF_RT_DNRPIPE, .off = _OUT(r.dnrpipe), .cb = snl_attr_get_uint16 }, { .type = PF_RT_DNFLAGS, .off = _OUT(r.free_flags), .cb = snl_attr_get_uint32 }, { .type = PF_RT_NR, .off = _OUT(r.nr), .cb = snl_attr_get_uint32 }, { .type = PF_RT_PROB, .off = _OUT(r.prob), .cb = snl_attr_get_uint32 }, { .type = PF_RT_CUID, .off = _OUT(r.cuid), .cb = snl_attr_get_uint32 }, {. type = PF_RT_CPID, .off = _OUT(r.cpid), .cb = snl_attr_get_uint32 }, { .type = PF_RT_RETURN_ICMP, .off = _OUT(r.return_icmp), .cb = snl_attr_get_uint16 }, { .type = PF_RT_RETURN_ICMP6, .off = _OUT(r.return_icmp6), .cb = snl_attr_get_uint16 }, { .type = PF_RT_MAX_MSS, .off = _OUT(r.max_mss), .cb = snl_attr_get_uint16 }, { .type = PF_RT_SCRUB_FLAGS, .off = _OUT(r.scrub_flags), .cb = snl_attr_get_uint16 }, { .type = PF_RT_UID, .off = _OUT(r.uid), .arg = &rule_uid_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_GID, .off = _OUT(r.gid), .arg = &rule_uid_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_RULE_FLAG, .off = _OUT(r.rule_flag), .cb = snl_attr_get_uint32 }, { .type = PF_RT_ACTION, .off = _OUT(r.action), .cb = snl_attr_get_uint8 }, { .type = PF_RT_DIRECTION, .off = _OUT(r.direction), .cb = snl_attr_get_uint8 }, { .type = PF_RT_LOG, .off = _OUT(r.log), .cb = snl_attr_get_uint8 }, { .type = PF_RT_LOGIF, .off = _OUT(r.logif), .cb = snl_attr_get_uint8 }, { .type = PF_RT_QUICK, .off = _OUT(r.quick), .cb = snl_attr_get_uint8 }, { .type = PF_RT_IF_NOT, .off = _OUT(r.ifnot), .cb = snl_attr_get_uint8 }, { .type = PF_RT_MATCH_TAG_NOT, .off = _OUT(r.match_tag_not), .cb = snl_attr_get_uint8 }, { .type = PF_RT_NATPASS, .off = _OUT(r.natpass), .cb = snl_attr_get_uint8 }, { .type = PF_RT_KEEP_STATE, .off = _OUT(r.keep_state), .cb = snl_attr_get_uint8 }, { .type = PF_RT_AF, .off = _OUT(r.af), .cb = snl_attr_get_uint8 }, { .type = PF_RT_PROTO, .off = _OUT(r.proto), .cb = snl_attr_get_uint8 }, { .type = PF_RT_TYPE, .off = _OUT(r.type), .cb = snl_attr_get_uint8 }, { .type = PF_RT_CODE, .off = _OUT(r.code), .cb = snl_attr_get_uint8 }, { .type = PF_RT_FLAGS, .off = _OUT(r.flags), .cb = snl_attr_get_uint8 }, { .type = PF_RT_FLAGSET, .off = _OUT(r.flagset), .cb = snl_attr_get_uint8 }, { .type = PF_RT_MIN_TTL, .off = _OUT(r.min_ttl), .cb = snl_attr_get_uint8 }, { .type = PF_RT_ALLOW_OPTS, .off = _OUT(r.allow_opts), .cb = snl_attr_get_uint8 }, { .type = PF_RT_RT, .off = _OUT(r.rt), .cb = snl_attr_get_uint8 }, { .type = PF_RT_RETURN_TTL, .off = _OUT(r.return_ttl), .cb = snl_attr_get_uint8 }, { .type = PF_RT_TOS, .off = _OUT(r.tos), .cb = snl_attr_get_uint8 }, { .type = PF_RT_SET_TOS, .off = _OUT(r.set_tos), .cb = snl_attr_get_uint8 }, { .type = PF_RT_ANCHOR_RELATIVE, .off = _OUT(r.anchor_relative), .cb = snl_attr_get_uint8 }, { .type = PF_RT_ANCHOR_WILDCARD, .off = _OUT(r.anchor_wildcard), .cb = snl_attr_get_uint8 }, { .type = PF_RT_FLUSH, .off = _OUT(r.flush), .cb = snl_attr_get_uint8 }, { .type = PF_RT_PRIO, .off = _OUT(r.prio), .cb = snl_attr_get_uint8 }, { .type = PF_RT_SET_PRIO, .off = _OUT(r.set_prio[0]), .cb = snl_attr_get_uint8 }, { .type = PF_RT_SET_PRIO_REPLY, .off = _OUT(r.set_prio[1]), .cb = snl_attr_get_uint8 }, { .type = PF_RT_DIVERT_ADDRESS, .off = _OUT(r.divert.addr), .cb = snl_attr_get_in6_addr }, { .type = PF_RT_DIVERT_PORT, .off = _OUT(r.divert.port), .cb = snl_attr_get_uint16 }, { .type = PF_RT_PACKETS_IN, .off = _OUT(r.packets[0]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_PACKETS_OUT, .off = _OUT(r.packets[1]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_BYTES_IN, .off = _OUT(r.bytes[0]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_BYTES_OUT, .off = _OUT(r.bytes[1]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_EVALUATIONS, .off = _OUT(r.evaluations), .cb = snl_attr_get_uint64 }, { .type = PF_RT_TIMESTAMP, .off = _OUT(r.last_active_timestamp), .cb = snl_attr_get_uint64 }, { .type = PF_RT_STATES_CUR, .off = _OUT(r.states_cur), .cb = snl_attr_get_uint64 }, { .type = PF_RT_STATES_TOTAL, .off = _OUT(r.states_tot), .cb = snl_attr_get_uint64 }, { .type = PF_RT_SRC_NODES, .off = _OUT(r.src_nodes), .cb = snl_attr_get_uint64 }, { .type = PF_RT_ANCHOR_CALL, .off = _OUT(anchor_call), .arg = (void*)MAXPATHLEN, .cb = snl_attr_copy_string }, }; static struct snl_field_parser fp_getrule[] = {}; #undef _OUT SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, fp_getrule, ap_getrule); int pfctl_get_clear_rule_h(struct pfctl_handle *h, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call, bool clear) { struct pfctl_nl_get_rule attrs = {}; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; struct snl_writer nw; uint32_t seq_id; int family_id; family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); if (family_id == 0) return (ENOTSUP); snl_init_writer(&h->ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETRULE); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_string(&nw, PF_GR_ANCHOR, anchor); snl_add_msg_attr_u8(&nw, PF_GR_ACTION, ruleset); snl_add_msg_attr_u32(&nw, PF_GR_NR, nr); snl_add_msg_attr_u32(&nw, PF_GR_TICKET, ticket); snl_add_msg_attr_u8(&nw, PF_GR_CLEAR, clear); hdr = snl_finalize_msg(&nw); if (hdr == NULL) return (ENOMEM); seq_id = hdr->nlmsg_seq; if (! snl_send_message(&h->ss, hdr)) return (ENXIO); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { if (! snl_parse_nlmsg(&h->ss, hdr, &getrule_parser, &attrs)) continue; } memcpy(rule, &attrs.r, sizeof(attrs.r)); strlcpy(anchor_call, attrs.anchor_call, MAXPATHLEN); return (e.error); } int pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call, bool clear) { nvlist_t *nvl; int ret; nvl = nvlist_create(0); if (nvl == 0) return (ENOMEM); nvlist_add_number(nvl, "nr", nr); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_string(nvl, "anchor", anchor); nvlist_add_number(nvl, "ruleset", ruleset); if (clear) nvlist_add_bool(nvl, "clear_counter", true); if ((ret = pfctl_do_ioctl(dev, DIOCGETRULENV, 8192, &nvl)) != 0) goto out; pf_nvrule_to_rule(nvlist_get_nvlist(nvl, "rule"), rule); if (anchor_call) strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), MAXPATHLEN); out: nvlist_destroy(nvl); return (ret); } int pfctl_set_keepcounters(int dev, bool keep) { struct pfioc_nv nv; nvlist_t *nvl; int ret; nvl = nvlist_create(0); nvlist_add_bool(nvl, "keep_counters", keep); nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; nvlist_destroy(nvl); ret = ioctl(dev, DIOCKEEPCOUNTERS, &nv); free(nv.data); return (ret); } struct pfctl_creator { uint32_t id; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfctl_creator, _field) static struct snl_attr_parser ap_creators[] = { { .type = PF_ST_CREATORID, .off = _OUT(id), .cb = snl_attr_get_uint32 }, }; static struct snl_field_parser fp_creators[] = { }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(creator_parser, struct genlmsghdr, fp_creators, ap_creators); static int pfctl_get_creators_nl(struct snl_state *ss, uint32_t *creators, size_t *len) { int family_id = snl_get_genl_family(ss, PFNL_FAMILY_NAME); size_t i = 0; struct nlmsghdr *hdr; struct snl_writer nw; if (family_id == 0) return (ENOTSUP); snl_init_writer(ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETCREATORS); hdr->nlmsg_flags |= NLM_F_DUMP; hdr = snl_finalize_msg(&nw); if (hdr == NULL) return (ENOMEM); uint32_t seq_id = hdr->nlmsg_seq; snl_send_message(ss, hdr); struct snl_errmsg_data e = {}; while ((hdr = snl_read_reply_multi(ss, seq_id, &e)) != NULL) { struct pfctl_creator c; bzero(&c, sizeof(c)); if (!snl_parse_nlmsg(ss, hdr, &creator_parser, &c)) continue; creators[i] = c.id; i++; if (i > *len) return (E2BIG); } *len = i; return (0); } int pfctl_get_creatorids(struct pfctl_handle *h, uint32_t *creators, size_t *len) { int error; error = pfctl_get_creators_nl(&h->ss, creators, len); return (error); } static inline bool snl_attr_get_pfaddr(struct snl_state *ss __unused, struct nlattr *nla, const void *arg __unused, void *target) { memcpy(target, NLA_DATA(nla), NLA_DATA_LEN(nla)); return (true); } static inline bool snl_attr_store_ifname(struct snl_state *ss __unused, struct nlattr *nla, const void *arg __unused, void *target) { size_t maxlen = NLA_DATA_LEN(nla); if (strnlen((char *)NLA_DATA(nla), maxlen) < maxlen) { strlcpy(target, (char *)NLA_DATA(nla), maxlen); return (true); } return (false); } #define _OUT(_field) offsetof(struct pfctl_state_peer, _field) static const struct snl_attr_parser nla_p_speer[] = { { .type = PF_STP_SEQLO, .off = _OUT(seqlo), .cb = snl_attr_get_uint32 }, { .type = PF_STP_SEQHI, .off = _OUT(seqhi), .cb = snl_attr_get_uint32 }, { .type = PF_STP_SEQDIFF, .off = _OUT(seqdiff), .cb = snl_attr_get_uint32 }, { .type = PF_STP_STATE, .off = _OUT(state), .cb = snl_attr_get_uint8 }, { .type = PF_STP_WSCALE, .off = _OUT(wscale), .cb = snl_attr_get_uint8 }, }; SNL_DECLARE_ATTR_PARSER(speer_parser, nla_p_speer); #undef _OUT #define _OUT(_field) offsetof(struct pf_state_key_export, _field) static const struct snl_attr_parser nla_p_skey[] = { { .type = PF_STK_ADDR0, .off = _OUT(addr[0]), .cb = snl_attr_get_pfaddr }, { .type = PF_STK_ADDR1, .off = _OUT(addr[1]), .cb = snl_attr_get_pfaddr }, { .type = PF_STK_PORT0, .off = _OUT(port[0]), .cb = snl_attr_get_uint16 }, { .type = PF_STK_PORT1, .off = _OUT(port[1]), .cb = snl_attr_get_uint16 }, }; SNL_DECLARE_ATTR_PARSER(skey_parser, nla_p_skey); #undef _OUT #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfctl_state, _field) static struct snl_attr_parser ap_state[] = { { .type = PF_ST_ID, .off = _OUT(id), .cb = snl_attr_get_uint64 }, { .type = PF_ST_CREATORID, .off = _OUT(creatorid), .cb = snl_attr_get_uint32 }, { .type = PF_ST_IFNAME, .off = _OUT(ifname), .cb = snl_attr_store_ifname }, { .type = PF_ST_ORIG_IFNAME, .off = _OUT(orig_ifname), .cb = snl_attr_store_ifname }, { .type = PF_ST_KEY_WIRE, .off = _OUT(key[0]), .arg = &skey_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_KEY_STACK, .off = _OUT(key[1]), .arg = &skey_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_PEER_SRC, .off = _OUT(src), .arg = &speer_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_PEER_DST, .off = _OUT(dst), .arg = &speer_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_RT_ADDR, .off = _OUT(rt_addr), .cb = snl_attr_get_pfaddr }, { .type = PF_ST_RULE, .off = _OUT(rule), .cb = snl_attr_get_uint32 }, { .type = PF_ST_ANCHOR, .off = _OUT(anchor), .cb = snl_attr_get_uint32 }, { .type = PF_ST_NAT_RULE, .off = _OUT(nat_rule), .cb = snl_attr_get_uint32 }, { .type = PF_ST_CREATION, .off = _OUT(creation), .cb = snl_attr_get_uint32 }, { .type = PF_ST_EXPIRE, .off = _OUT(expire), .cb = snl_attr_get_uint32 }, { .type = PF_ST_PACKETS0, .off = _OUT(packets[0]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_PACKETS1, .off = _OUT(packets[1]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_BYTES0, .off = _OUT(bytes[0]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_BYTES1, .off = _OUT(bytes[1]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_AF, .off = _OUT(key[0].af), .cb = snl_attr_get_uint8 }, { .type = PF_ST_PROTO, .off = _OUT(key[0].proto), .cb = snl_attr_get_uint8 }, { .type = PF_ST_DIRECTION, .off = _OUT(direction), .cb = snl_attr_get_uint8 }, { .type = PF_ST_LOG, .off = _OUT(log), .cb = snl_attr_get_uint8 }, { .type = PF_ST_STATE_FLAGS, .off = _OUT(state_flags), .cb = snl_attr_get_uint16 }, { .type = PF_ST_SYNC_FLAGS, .off = _OUT(sync_flags), .cb = snl_attr_get_uint8 }, { .type = PF_ST_RTABLEID, .off = _OUT(rtableid), .cb = snl_attr_get_int32 }, { .type = PF_ST_MIN_TTL, .off = _OUT(min_ttl), .cb = snl_attr_get_uint8 }, { .type = PF_ST_MAX_MSS, .off = _OUT(max_mss), .cb = snl_attr_get_uint16 }, { .type = PF_ST_DNPIPE, .off = _OUT(dnpipe), .cb = snl_attr_get_uint16 }, { .type = PF_ST_DNRPIPE, .off = _OUT(dnrpipe), .cb = snl_attr_get_uint16 }, { .type = PF_ST_RT, .off = _OUT(rt), .cb = snl_attr_get_uint8 }, { .type = PF_ST_RT_IFNAME, .off = _OUT(rt_ifname), .cb = snl_attr_store_ifname }, }; static struct snl_field_parser fp_state[] = { }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(state_parser, struct genlmsghdr, fp_state, ap_state); static const struct snl_hdr_parser *all_parsers[] = { &state_parser, &skey_parser, &speer_parser, &creator_parser, &getrules_parser }; static int pfctl_get_states_nl(struct pfctl_state_filter *filter, struct snl_state *ss, pfctl_get_state_fn f, void *arg) { SNL_VERIFY_PARSERS(all_parsers); int family_id = snl_get_genl_family(ss, PFNL_FAMILY_NAME); int ret; struct nlmsghdr *hdr; struct snl_writer nw; if (family_id == 0) return (ENOTSUP); snl_init_writer(ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETSTATES); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_string(&nw, PF_ST_IFNAME, filter->ifname); snl_add_msg_attr_u16(&nw, PF_ST_PROTO, filter->proto); snl_add_msg_attr_u8(&nw, PF_ST_AF, filter->af); snl_add_msg_attr_ip6(&nw, PF_ST_FILTER_ADDR, &filter->addr.v6); snl_add_msg_attr_ip6(&nw, PF_ST_FILTER_MASK, &filter->mask.v6); hdr = snl_finalize_msg(&nw); if (hdr == NULL) return (ENOMEM); uint32_t seq_id = hdr->nlmsg_seq; snl_send_message(ss, hdr); struct snl_errmsg_data e = {}; while ((hdr = snl_read_reply_multi(ss, seq_id, &e)) != NULL) { struct pfctl_state s; bzero(&s, sizeof(s)); if (!snl_parse_nlmsg(ss, hdr, &state_parser, &s)) continue; s.key[1].af = s.key[0].af; s.key[1].proto = s.key[0].proto; ret = f(&s, arg); if (ret != 0) return (ret); } return (0); } int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg) { struct pfctl_state_filter filter = {}; return (pfctl_get_filtered_states_iter(&filter, f, arg)); } int pfctl_get_filtered_states_iter(struct pfctl_state_filter *filter, pfctl_get_state_fn f, void *arg) { struct snl_state ss = {}; int error; snl_init(&ss, NETLINK_GENERIC); error = pfctl_get_states_nl(filter, &ss, f, arg); snl_free(&ss); return (error); } static int pfctl_append_states(struct pfctl_state *s, void *arg) { struct pfctl_state *new; struct pfctl_states *states = (struct pfctl_states *)arg; new = malloc(sizeof(*s)); if (new == NULL) return (ENOMEM); memcpy(new, s, sizeof(*s)); TAILQ_INSERT_TAIL(&states->states, new, entry); return (0); } int pfctl_get_states(int dev __unused, struct pfctl_states *states) { int ret; bzero(states, sizeof(*states)); TAILQ_INIT(&states->states); ret = pfctl_get_states_iter(pfctl_append_states, states); if (ret != 0) { pfctl_free_states(states); return (ret); } return (0); } void pfctl_free_states(struct pfctl_states *states) { struct pfctl_state *s, *tmp; TAILQ_FOREACH_SAFE(s, &states->states, entry, tmp) { free(s); } bzero(states, sizeof(*states)); } struct pfctl_nl_clear_states { uint32_t killed; }; #define _OUT(_field) offsetof(struct pfctl_nl_clear_states, _field) static struct snl_attr_parser ap_clear_states[] = { { .type = PF_CS_KILLED, .off = _OUT(killed), .cb = snl_attr_get_uint32 }, }; static struct snl_field_parser fp_clear_states[] = {}; #undef _OUT SNL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, fp_clear_states, ap_clear_states); static int _pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill, unsigned int *killed, int cmd) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct pfctl_nl_clear_states attrs = {}; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); if (family_id == 0) return (ENOTSUP); snl_init_writer(&h->ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, cmd); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_u64(&nw, PF_CS_CMP_ID, kill->cmp.id); snl_add_msg_attr_u32(&nw, PF_CS_CMP_CREATORID, htonl(kill->cmp.creatorid)); snl_add_msg_attr_u8(&nw, PF_CS_CMP_DIR, kill->cmp.direction); snl_add_msg_attr_u8(&nw, PF_CS_AF, kill->af); snl_add_msg_attr_u8(&nw, PF_CS_PROTO, kill->proto); snl_add_msg_attr_rule_addr(&nw, PF_CS_SRC, &kill->src); snl_add_msg_attr_rule_addr(&nw, PF_CS_DST, &kill->dst); snl_add_msg_attr_rule_addr(&nw, PF_CS_RT_ADDR, &kill->rt_addr); snl_add_msg_attr_string(&nw, PF_CS_IFNAME, kill->ifname); snl_add_msg_attr_string(&nw, PF_CS_LABEL, kill->label); snl_add_msg_attr_bool(&nw, PF_CS_KILL_MATCH, kill->kill_match); snl_add_msg_attr_bool(&nw, PF_CS_NAT, kill->nat); if ((hdr = snl_finalize_msg(&nw)) == NULL) return (ENXIO); seq_id = hdr->nlmsg_seq; if (! snl_send_message(&h->ss, hdr)) return (ENXIO); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { if (! snl_parse_nlmsg(&h->ss, hdr, &clear_states_parser, &attrs)) continue; } if (killed) *killed = attrs.killed; return (e.error); } int pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill, unsigned int *killed) { return(_pfctl_clear_states_h(h, kill, killed, PFNL_CMD_CLRSTATES)); } int pfctl_kill_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill, unsigned int *killed) { return(_pfctl_clear_states_h(h, kill, killed, PFNL_CMD_KILLSTATES)); } static int _pfctl_clear_states(int dev __unused, const struct pfctl_kill *kill, unsigned int *killed, uint64_t cmd) { struct pfctl_handle *h; int ret; h = pfctl_open(PF_DEVICE); if (h == NULL) return (ENODEV); ret = _pfctl_clear_states_h(h, kill, killed, cmd); pfctl_close(h); return (ret); } int pfctl_clear_states(int dev __unused, const struct pfctl_kill *kill, unsigned int *killed) { return (_pfctl_clear_states(dev, kill, killed, PFNL_CMD_CLRSTATES)); } int pfctl_kill_states(int dev __unused, const struct pfctl_kill *kill, unsigned int *killed) { return (_pfctl_clear_states(dev, kill, killed, PFNL_CMD_KILLSTATES)); } int pfctl_clear_rules(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe[2]; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe[0].rs_num = PF_RULESET_SCRUB; if (strlcpy(transe[0].anchor, anchorname, sizeof(transe[0].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[1].rs_num = PF_RULESET_FILTER; if (strlcpy(transe[1].anchor, anchorname, sizeof(transe[1].anchor)) >= sizeof(transe[1].anchor)) return (E2BIG); trans.size = 2; trans.esize = sizeof(transe[0]); trans.array = transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } int pfctl_clear_nat(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe[3]; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe[0].rs_num = PF_RULESET_NAT; if (strlcpy(transe[0].anchor, anchorname, sizeof(transe[0].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[1].rs_num = PF_RULESET_BINAT; if (strlcpy(transe[1].anchor, anchorname, sizeof(transe[1].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[2].rs_num = PF_RULESET_RDR; if (strlcpy(transe[2].anchor, anchorname, sizeof(transe[2].anchor)) >= sizeof(transe[2].anchor)) return (E2BIG); trans.size = 3; trans.esize = sizeof(transe[0]); trans.array = transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } int pfctl_clear_eth_rules(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe.rs_num = PF_RULESET_ETH; if (strlcpy(transe.anchor, anchorname, sizeof(transe.anchor)) >= sizeof(transe.anchor)) return (E2BIG); trans.size = 1; trans.esize = sizeof(transe); trans.array = &transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } static int pfctl_get_limit(int dev, const int index, uint *limit) { struct pfioc_limit pl; bzero(&pl, sizeof(pl)); pl.index = index; if (ioctl(dev, DIOCGETLIMIT, &pl) == -1) return (errno); *limit = pl.limit; return (0); } int pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s) { struct pfioc_nv nv; nvlist_t *nvl; int ret; uint state_limit; uint64_t lim, hi, lo; ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); lim = state_limit; hi = lim * s->highwater / 100; lo = lim * s->lowwater / 100; if (lo == hi) hi++; nvl = nvlist_create(0); nvlist_add_bool(nvl, "enabled", s->mode != PFCTL_SYNCOOKIES_NEVER); nvlist_add_bool(nvl, "adaptive", s->mode == PFCTL_SYNCOOKIES_ADAPTIVE); nvlist_add_number(nvl, "highwater", hi); nvlist_add_number(nvl, "lowwater", lo); nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; nvlist_destroy(nvl); nvl = NULL; ret = ioctl(dev, DIOCSETSYNCOOKIES, &nv); free(nv.data); return (ret); } int pfctl_get_syncookies(int dev, struct pfctl_syncookies *s) { nvlist_t *nvl; int ret; uint state_limit; bool enabled, adaptive; ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); bzero(s, sizeof(*s)); nvl = nvlist_create(0); if ((ret = pfctl_do_ioctl(dev, DIOCGETSYNCOOKIES, 256, &nvl)) != 0) { ret = errno; goto out; } enabled = nvlist_get_bool(nvl, "enabled"); adaptive = nvlist_get_bool(nvl, "adaptive"); if (enabled) { if (adaptive) s->mode = PFCTL_SYNCOOKIES_ADAPTIVE; else s->mode = PFCTL_SYNCOOKIES_ALWAYS; } else { s->mode = PFCTL_SYNCOOKIES_NEVER; } s->highwater = nvlist_get_number(nvl, "highwater") * 100 / state_limit; s->lowwater = nvlist_get_number(nvl, "lowwater") * 100 / state_limit; s->halfopen_states = nvlist_get_number(nvl, "halfopen_states"); out: nvlist_destroy(nvl); return (ret); } int pfctl_table_add_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; if (ioctl(dev, DIOCRADDADDRS, &io)) return (errno); if (nadd != NULL) *nadd = io.pfrio_nadd; return (0); } int pfctl_table_del_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; if (ioctl(dev, DIOCRDELADDRS, &io)) return (errno); if (ndel != NULL) *ndel = io.pfrio_ndel; return (0); } int pfctl_table_set_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *size2, int *nadd, int *ndel, int *nchange, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; if (ioctl(dev, DIOCRSETADDRS, &io)) return (-1); if (nadd != NULL) *nadd = io.pfrio_nadd; if (ndel != NULL) *ndel = io.pfrio_ndel; if (nchange != NULL) *nchange = io.pfrio_nchange; if (size2 != NULL) *size2 = io.pfrio_size2; return (0); } int pfctl_table_get_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags) { struct pfioc_table io; if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = *size; if (ioctl(dev, DIOCRGETADDRS, &io)) return (-1); *size = io.pfrio_size; return (0); } + +int +pfctl_set_statusif(struct pfctl_handle *h, const char *ifname) +{ + struct snl_writer nw; + struct snl_errmsg_data e = {}; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); + if (family_id == 0) + return (ENOTSUP); + + snl_init_writer(&h->ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_SET_STATUSIF); + + snl_add_msg_attr_string(&nw, PF_SS_IFNAME, ifname); + + if ((hdr = snl_finalize_msg(&nw)) == NULL) + return (ENXIO); + + seq_id = hdr->nlmsg_seq; + + if (! snl_send_message(&h->ss, hdr)) + return (ENXIO); + + while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { + } + + return (e.error); +} diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index b16caf1f6480..4e06981a79c7 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -1,467 +1,469 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _PFCTL_IOCTL_H_ #define _PFCTL_IOCTL_H_ #include struct pfctl_anchor; struct pfctl_eth_anchor; struct pfctl_status_counter { uint64_t id; uint64_t counter; char *name; TAILQ_ENTRY(pfctl_status_counter) entry; }; TAILQ_HEAD(pfctl_status_counters, pfctl_status_counter); struct pfctl_status { bool running; uint32_t since; uint32_t debug; uint32_t hostid; uint64_t states; uint64_t src_nodes; char ifname[IFNAMSIZ]; uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; bool syncookies_active; uint32_t reass; struct pfctl_status_counters counters; struct pfctl_status_counters lcounters; struct pfctl_status_counters fcounters; struct pfctl_status_counters scounters; uint64_t pcounters[2][2][3]; uint64_t bcounters[2][2]; }; struct pfctl_eth_rulesets_info { uint32_t nr; }; struct pfctl_eth_rules_info { uint32_t nr; uint32_t ticket; }; struct pfctl_eth_addr { uint8_t addr[ETHER_ADDR_LEN]; uint8_t mask[ETHER_ADDR_LEN]; bool neg; bool isset; }; struct pfctl_eth_rule { uint32_t nr; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; bool quick; /* Filter */ char ifname[IFNAMSIZ]; uint8_t ifnot; uint8_t direction; 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; uint64_t packets[2]; uint64_t bytes[2]; time_t last_active_timestamp; /* Action */ char qname[PF_QNAME_SIZE]; char tagname[PF_TAG_NAME_SIZE]; uint16_t dnpipe; uint32_t dnflags; char bridge_to[IFNAMSIZ]; 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_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; }; 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; struct pf_poolhashkey key; struct pf_addr counter; struct pf_mape_portset mape; int tblidx; uint16_t proxy_port[2]; uint8_t opts; }; struct pfctl_rules_info { uint32_t nr; uint32_t ticket; }; struct pfctl_rule { struct pf_rule_addr src; struct pf_rule_addr dst; union pf_rule_ptr skip[PF_SKIP_COUNT]; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; char ifname[IFNAMSIZ]; char qname[PF_QNAME_SIZE]; char pqname[PF_QNAME_SIZE]; char tagname[PF_TAG_NAME_SIZE]; char match_tagname[PF_TAG_NAME_SIZE]; char overload_tblname[PF_TABLE_NAME_SIZE]; TAILQ_ENTRY(pfctl_rule) entries; struct pfctl_pool rpool; uint64_t evaluations; uint64_t packets[2]; uint64_t bytes[2]; time_t last_active_timestamp; struct pfi_kif *kif; struct pfctl_anchor *anchor; struct pfr_ktable *overload_tbl; pf_osfp_t os_fingerprint; int rtableid; uint32_t timeout[PFTM_MAX]; uint32_t max_states; uint32_t max_src_nodes; uint32_t max_src_states; uint32_t max_src_conn; struct { uint32_t limit; uint32_t seconds; } max_src_conn_rate; uint32_t qid; uint32_t pqid; uint16_t dnpipe; uint16_t dnrpipe; uint32_t free_flags; uint32_t nr; uint32_t prob; uid_t cuid; pid_t cpid; uint64_t states_cur; uint64_t states_tot; uint64_t src_nodes; uint16_t return_icmp; uint16_t return_icmp6; uint16_t max_mss; uint16_t tag; uint16_t match_tag; uint16_t scrub_flags; struct pf_rule_uid uid; struct pf_rule_gid gid; uint32_t rule_flag; uint8_t action; uint8_t direction; uint8_t log; uint8_t logif; uint8_t quick; uint8_t ifnot; uint8_t match_tag_not; uint8_t natpass; uint8_t keep_state; sa_family_t af; uint8_t proto; uint8_t type; uint8_t code; uint8_t flags; uint8_t flagset; uint8_t min_ttl; uint8_t allow_opts; uint8_t rt; uint8_t return_ttl; uint8_t tos; uint8_t set_tos; uint8_t anchor_relative; uint8_t anchor_wildcard; uint8_t flush; uint8_t prio; uint8_t set_prio[2]; struct { struct pf_addr addr; uint16_t port; } divert; }; TAILQ_HEAD(pfctl_rulequeue, pfctl_rule); struct pfctl_ruleset { struct { struct pfctl_rulequeue queues[2]; struct { struct pfctl_rulequeue *ptr; struct pfctl_rule **ptr_array; uint32_t rcount; uint32_t ticket; int open; } active, inactive; } rules[PF_RULESET_MAX]; struct pfctl_anchor *anchor; uint32_t tticket; int tables; int topen; }; RB_HEAD(pfctl_anchor_global, pfctl_anchor); RB_HEAD(pfctl_anchor_node, pfctl_anchor); struct pfctl_anchor { RB_ENTRY(pfctl_anchor) entry_global; RB_ENTRY(pfctl_anchor) entry_node; struct pfctl_anchor *parent; struct pfctl_anchor_node children; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pfctl_ruleset ruleset; int refcnt; /* anchor rules */ int match; /* XXX: used for pfctl black magic */ }; RB_PROTOTYPE(pfctl_anchor_global, pfctl_anchor, entry_global, pf_anchor_compare); RB_PROTOTYPE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare); struct pfctl_state_cmp { uint64_t id; uint32_t creatorid; uint8_t direction; }; struct pfctl_kill { struct pfctl_state_cmp cmp; sa_family_t af; int proto; struct pf_rule_addr src; struct pf_rule_addr dst; struct pf_rule_addr rt_addr; char ifname[IFNAMSIZ]; char label[PF_RULE_LABEL_SIZE]; bool kill_match; bool nat; }; struct pfctl_state_peer { uint32_t seqlo; uint32_t seqhi; uint32_t seqdiff; uint8_t state; uint8_t wscale; }; struct pfctl_state_key { struct pf_addr addr[2]; uint16_t port[2]; sa_family_t af; uint8_t proto; }; struct pfctl_state { TAILQ_ENTRY(pfctl_state) entry; uint64_t id; uint32_t creatorid; uint8_t direction; struct pfctl_state_peer src; struct pfctl_state_peer dst; uint32_t rule; uint32_t anchor; uint32_t nat_rule; struct pf_addr rt_addr; struct pfctl_state_key key[2]; /* addresses stack and wire */ char ifname[IFNAMSIZ]; char orig_ifname[IFNAMSIZ]; uint64_t packets[2]; uint64_t bytes[2]; uint32_t creation; uint32_t expire; uint32_t pfsync_time; uint16_t state_flags; uint32_t sync_flags; uint16_t qid; uint16_t pqid; uint16_t dnpipe; uint16_t dnrpipe; uint8_t log; int32_t rtableid; uint8_t min_ttl; uint8_t set_tos; uint16_t max_mss; uint8_t set_prio[2]; uint8_t rt; char rt_ifname[IFNAMSIZ]; }; TAILQ_HEAD(pfctl_statelist, pfctl_state); struct pfctl_states { struct pfctl_statelist states; }; enum pfctl_syncookies_mode { PFCTL_SYNCOOKIES_NEVER, PFCTL_SYNCOOKIES_ALWAYS, PFCTL_SYNCOOKIES_ADAPTIVE }; extern const char* PFCTL_SYNCOOKIES_MODE_NAMES[]; struct pfctl_syncookies { enum pfctl_syncookies_mode mode; uint8_t highwater; /* Percent */ uint8_t lowwater; /* Percent */ uint32_t halfopen_states; }; #define PF_DEVICE "/dev/pf" struct pfctl_handle; struct pfctl_handle *pfctl_open(const char *pf_device); void pfctl_close(struct pfctl_handle *); int pfctl_startstop(struct pfctl_handle *h, int start); struct pfctl_status* pfctl_get_status(int dev); uint64_t pfctl_status_counter(struct pfctl_status *status, int id); uint64_t pfctl_status_lcounter(struct pfctl_status *status, int id); uint64_t pfctl_status_fcounter(struct pfctl_status *status, int id); uint64_t pfctl_status_scounter(struct pfctl_status *status, int id); 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, 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, const char *anchor, const char *anchor_call, uint32_t ticket); int pfctl_get_rules_info(int dev, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path); 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); int pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call, bool clear); int pfctl_get_clear_rule_h(struct pfctl_handle *h, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call, bool clear); int pfctl_add_rule(int dev, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, uint32_t ticket, uint32_t pool_ticket); int pfctl_add_rule_h(struct pfctl_handle *h, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, uint32_t ticket, uint32_t pool_ticket); int pfctl_set_keepcounters(int dev, bool keep); int pfctl_get_creatorids(struct pfctl_handle *h, uint32_t *creators, size_t *len); struct pfctl_state_filter { char ifname[IFNAMSIZ]; uint16_t proto; sa_family_t af; struct pf_addr addr; struct pf_addr mask; }; typedef int (*pfctl_get_state_fn)(struct pfctl_state *, void *); int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg); int pfctl_get_filtered_states_iter(struct pfctl_state_filter *filter, pfctl_get_state_fn f, void *arg); int pfctl_get_states(int dev, struct pfctl_states *states); void pfctl_free_states(struct pfctl_states *states); int pfctl_clear_states(int dev, const struct pfctl_kill *kill, unsigned int *killed); int pfctl_kill_states(int dev, const struct pfctl_kill *kill, unsigned int *killed); int pfctl_clear_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill, unsigned int *killed); int pfctl_kill_states_h(struct pfctl_handle *h, const struct pfctl_kill *kill, unsigned int *killed); int pfctl_clear_rules(int dev, const char *anchorname); int pfctl_clear_nat(int dev, const char *anchorname); int pfctl_clear_eth_rules(int dev, const char *anchorname); int pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s); int pfctl_get_syncookies(int dev, struct pfctl_syncookies *s); int pfctl_table_add_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags); int pfctl_table_del_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags); int pfctl_table_set_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *size2, int *nadd, int *ndel, int *nchange, int flags); int pfctl_table_get_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags); +int pfctl_set_statusif(struct pfctl_handle *h, const char *ifname); + #endif diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index f8ecded066d2..8d59871701f8 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1,3308 +1,3300 @@ /* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #define PFIOC_USE_LATEST #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); int pfctl_clear_stats(int, int); int pfctl_get_skip_ifaces(void); int pfctl_check_skip_ifaces(char *); int pfctl_adjust_skip_ifaces(struct pfctl *); int pfctl_clear_interface_flags(int, int); int pfctl_flush_eth_rules(int, int, char *); int pfctl_flush_rules(int, int, char *); int pfctl_flush_nat(int, int, char *); int pfctl_clear_altq(int, int); int pfctl_clear_src_nodes(int, int); int pfctl_clear_iface_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); int pfctl_kill_src_nodes(int, const char *, int); int pfctl_net_kill_states(int, const char *, int); int pfctl_gateway_kill_states(int, const char *, int); int pfctl_label_kill_states(int, const char *, int); int pfctl_id_kill_states(int, const char *, int); void pfctl_init_options(struct pfctl *); int pfctl_load_options(struct pfctl *); int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); int pfctl_load_debug(struct pfctl *, unsigned int); int pfctl_load_logif(struct pfctl *, char *); int pfctl_load_hostid(struct pfctl *, u_int32_t); int pfctl_load_reassembly(struct pfctl *, u_int32_t); int pfctl_load_syncookies(struct pfctl *, u_int8_t); int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int, 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, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_nat(int, char *, int, char *, int); int pfctl_show_src_nodes(int, int); int pfctl_show_states(int, const char *, int); int pfctl_show_status(int, int); int pfctl_show_running(int); int pfctl_show_timeouts(int, int); 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 *); 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; struct pfctl_anchor pf_main_anchor; struct pfctl_eth_anchor pf_eth_main_anchor; static struct pfr_buffer skip_b; static const char *clearopt; static char *rulesopt; static const char *showopt; static const char *debugopt; static char *anchoropt; static const char *optiopt = NULL; static const char *pf_device = PF_DEVICE; static char *ifaceopt; static char *tableopt; static const char *tblcmdopt; static int src_node_killers; static char *src_node_kill[2]; static int state_killers; static char *state_kill[2]; int loadopt; int altqsupport; int dev = -1; struct pfctl_handle *pfh = NULL; static int first_title = 1; static int labels = 0; #define INDENT(d, o) do { \ if (o) { \ int i; \ for (i=0; i < d; i++) \ printf(" "); \ } \ } while (0); \ static const struct { const char *name; int index; } pf_limits[] = { { "states", PF_LIMIT_STATES }, { "src-nodes", PF_LIMIT_SRC_NODES }, { "frags", PF_LIMIT_FRAGS }, { "table-entries", PF_LIMIT_TABLE_ENTRIES }, { NULL, 0 } }; struct pf_hint { const char *name; int timeout; }; static const struct pf_hint pf_hint_normal[] = { { "tcp.first", 2 * 60 }, { "tcp.opening", 30 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 }, { "tcp.finwait", 45 }, { "tcp.closed", 90 }, { "tcp.tsdiff", 30 }, { NULL, 0 } }; static const struct pf_hint pf_hint_satellite[] = { { "tcp.first", 3 * 60 }, { "tcp.opening", 30 + 5 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 + 5 }, { "tcp.finwait", 45 + 5 }, { "tcp.closed", 90 + 5 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_conservative[] = { { "tcp.first", 60 * 60 }, { "tcp.opening", 15 * 60 }, { "tcp.established", 5 * 24 * 60 * 60 }, { "tcp.closing", 60 * 60 }, { "tcp.finwait", 10 * 60 }, { "tcp.closed", 3 * 60 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_aggressive[] = { { "tcp.first", 30 }, { "tcp.opening", 5 }, { "tcp.established", 5 * 60 * 60 }, { "tcp.closing", 60 }, { "tcp.finwait", 30 }, { "tcp.closed", 30 }, { "tcp.tsdiff", 10 }, { NULL, 0 } }; static const struct { const char *name; const struct pf_hint *hint; } pf_hints[] = { { "normal", pf_hint_normal }, { "satellite", pf_hint_satellite }, { "high-latency", pf_hint_satellite }, { "conservative", pf_hint_conservative }, { "aggressive", pf_hint_aggressive }, { NULL, NULL } }; static const char * const clearopt_list[] = { "nat", "queue", "rules", "Sources", "states", "info", "Tables", "osfp", "all", "ethernet", NULL }; static const char * const showopt_list[] = { "ether", "nat", "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", "Running", "all", "creatorids", NULL }; static const char * const tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", "expire", NULL }; static const char * const debugopt_list[] = { "none", "urgent", "misc", "loud", NULL }; static const char * const optiopt_list[] = { "none", "basic", "profile", NULL }; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-AdeghMmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "\t[-f file] [-i interface] [-K host | network]\n" "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", __progname); exit(1); } /* * Cache protocol number to name translations. * * Translation is performed a lot e.g., when dumping states and * getprotobynumber is incredibly expensive. * * Note from the getprotobynumber(3) manpage: * * These functions use a thread-specific data space; if the data is needed * for future use, it should be copied before any subsequent calls overwrite * it. Only the Internet protocols are currently understood. * * * Consequently we only cache the name and strdup it for safety. * * At the time of writing this comment the last entry in /etc/protocols is: * divert 258 DIVERT # Divert pseudo-protocol [non IANA] */ const char * pfctl_proto2name(int proto) { static const char *pfctl_proto_cache[259]; struct protoent *p; if (proto >= nitems(pfctl_proto_cache)) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } return (p->p_name); } if (pfctl_proto_cache[proto] == NULL) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } pfctl_proto_cache[proto] = strdup(p->p_name); } return (pfctl_proto_cache[proto]); } int pfctl_enable(int dev, int opts) { int ret; if ((ret = pfctl_startstop(pfh, 1)) != 0) { if (ret == EEXIST) errx(1, "pf already enabled"); else if (ret == ESRCH) errx(1, "pfil registeration failed"); else err(1, "DIOCSTART"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf enabled\n"); if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) if (errno != EEXIST) err(1, "DIOCSTARTALTQ"); return (0); } int pfctl_disable(int dev, int opts) { int ret; if ((ret = pfctl_startstop(pfh, 0)) != 0) { if (ret == ENOENT) errx(1, "pf not enabled"); else err(1, "DIOCSTOP"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf disabled\n"); if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) if (errno != ENOENT) err(1, "DIOCSTOPALTQ"); return (0); } int pfctl_clear_stats(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATUS)) err(1, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); return (0); } int pfctl_get_skip_ifaces(void) { bzero(&skip_b, sizeof(skip_b)); skip_b.pfrb_type = PFRB_IFACES; for (;;) { pfr_buf_grow(&skip_b, skip_b.pfrb_size); skip_b.pfrb_size = skip_b.pfrb_msize; if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size)) err(1, "pfi_get_ifaces"); if (skip_b.pfrb_size <= skip_b.pfrb_msize) break; } return (0); } int pfctl_check_skip_ifaces(char *ifname) { struct pfi_kif *p; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (!strcmp(ifname, p->pfik_name) && (p->pfik_flags & PFI_IFLAG_SKIP)) p->pfik_flags &= ~PFI_IFLAG_SKIP; if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) { if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) { if (p->pfik_ifp == NULL) continue; if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) continue; p->pfik_flags &= ~PFI_IFLAG_SKIP; } } } return (0); } int pfctl_adjust_skip_ifaces(struct pfctl *pf) { struct pfi_kif *p, *pp; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) PFRB_FOREACH(pp, &skip_b) { if (pp->pfik_ifp == NULL) continue; if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ)) continue; if (!(pp->pfik_flags & PFI_IFLAG_SKIP)) pfctl_set_interface_flags(pf, pp->pfik_name, PFI_IFLAG_SKIP, 1); if (pp->pfik_flags & PFI_IFLAG_SKIP) pp->pfik_flags &= ~PFI_IFLAG_SKIP; } } PFRB_FOREACH(p, &skip_b) { if (p->pfik_ifp == NULL || ! (p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); } return (0); } int pfctl_clear_interface_flags(int dev, int opts) { struct pfioc_iface pi; if ((opts & PF_OPT_NOACTION) == 0) { bzero(&pi, sizeof(pi)); pi.pfiio_flags = PFI_IFLAG_SKIP; if (ioctl(dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: interface flags reset\n"); } return (0); } int pfctl_flush_eth_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_eth_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_eth_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "Ethernet rules cleared\n"); return (ret); } int pfctl_flush_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } int pfctl_flush_nat(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_nat(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_nat"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } int pfctl_clear_altq(int dev, int opts) { struct pfr_buffer t; if (!altqsupport) return (-1); memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_altq"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "altq cleared\n"); return (0); } int pfctl_clear_src_nodes(int dev, int opts) { if (ioctl(dev, DIOCCLRSRCNODES)) err(1, "DIOCCLRSRCNODES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "source tracking entries cleared\n"); return (0); } int pfctl_clear_iface_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if (pfctl_clear_states_h(pfh, &kill, &killed)) err(1, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "%d states cleared\n", killed); return (0); } void pfctl_addrprefix(char *addr, struct pf_addr *mask) { char *p; const char *errstr; int prefix, ret_ga, q, r; struct addrinfo hints, *res; if ((p = strchr(addr, '/')) == NULL) return; *p++ = '\0'; prefix = strtonum(p, 0, 128, &errstr); if (errstr) errx(1, "prefix is %s: %s", errstr, p); bzero(&hints, sizeof(hints)); /* prefix only with numeric addresses */ hints.ai_flags |= AI_NUMERICHOST; if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } if (res->ai_family == AF_INET && prefix > 32) errx(1, "prefix too long for AF_INET"); else if (res->ai_family == AF_INET6 && prefix > 128) errx(1, "prefix too long for AF_INET6"); q = prefix >> 3; r = prefix & 7; switch (res->ai_family) { case AF_INET: bzero(&mask->v4, sizeof(mask->v4)); mask->v4.s_addr = htonl((u_int32_t) (0xffffffffffULL << (32 - prefix))); break; case AF_INET6: bzero(&mask->v6, sizeof(mask->v6)); if (q > 0) memset((void *)&mask->v6, 0xff, q); if (r > 0) *((u_char *)&mask->v6 + q) = (0xff00 >> r) & 0xff; break; } freeaddrinfo(res); } int pfctl_kill_src_nodes(int dev, const char *iface, int opts) { struct pfioc_src_node_kill psnk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psnk, 0, sizeof(psnk)); memset(&psnk.psnk_src.addr.v.a.mask, 0xff, sizeof(psnk.psnk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psnk.psnk_af = resp[0]->ai_family; sources++; if (psnk.psnk_af == AF_INET) psnk.psnk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (src_node_killers > 1) { dests = 0; memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, sizeof(psnk.psnk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[1], &psnk.psnk_dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psnk.psnk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psnk.psnk_af == AF_INET) psnk.psnk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d src nodes from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_net_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; unsigned int newkilled; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&kill, 0, sizeof(kill)); memset(&kill.src.addr.v.a.mask, 0xff, sizeof(kill.src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (state_killers == 2 && (strcmp(state_kill[0], "nat") == 0)) { kill.nat = true; state_kill[0] = state_kill[1]; state_killers = 1; } pfctl_addrprefix(state_kill[0], &kill.src.addr.v.a.mask); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; kill.af = resp[0]->ai_family; sources++; if (kill.af == AF_INET) kill.src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (state_killers > 1) { dests = 0; memset(&kill.dst.addr.v.a.mask, 0xff, sizeof(kill.dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(state_kill[1], &kill.dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (kill.af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (kill.af == AF_INET) kill.dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (pfctl_kill_states_h(pfh, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res[1]); } else { if (pfctl_kill_states_h(pfh, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_gateway_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res, *resp; struct sockaddr last_src; unsigned int newkilled; int killed = 0; int ret_ga; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no gateway specified"); usage(); } memset(&kill, 0, sizeof(kill)); memset(&kill.rt_addr.addr.v.a.mask, 0xff, sizeof(kill.rt_addr.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp = res; resp; resp = resp->ai_next) { if (resp->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp->ai_addr; kill.af = resp->ai_family; if (kill.af == AF_INET) kill.rt_addr.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.rt_addr.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (pfctl_kill_states_h(pfh, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_label_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no label specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >= sizeof(kill.label)) errx(1, "label too long: %s", state_kill[1]); if (pfctl_kill_states_h(pfh, &kill, &killed)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_id_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no id specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((sscanf(state_kill[1], "%jx/%x", &kill.cmp.id, &kill.cmp.creatorid)) == 2) { } else if ((sscanf(state_kill[1], "%jx", &kill.cmp.id)) == 1) { kill.cmp.creatorid = 0; } else { warnx("wrong id format specified"); usage(); } if (kill.cmp.id == 0) { warnx("cannot kill id 0"); usage(); } if (pfctl_kill_states_h(pfh, &kill, &killed)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, char *anchorname) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; memset(&pp, 0, sizeof(pp)); memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); pp.r_action = r_action; pp.r_num = nr; pp.ticket = ticket; if (ioctl(dev, DIOCGETADDRS, &pp)) { warn("DIOCGETADDRS"); return (-1); } mpnr = pp.nr; TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { pp.nr = pnr; if (ioctl(dev, DIOCGETADDR, &pp)) { warn("DIOCGETADDR"); return (-1); } pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "calloc"); bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); TAILQ_INSERT_TAIL(&pool->list, pa, entries); } return (0); } void pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&src->list)) != NULL) { TAILQ_REMOVE(&src->list, pa, entries); TAILQ_INSERT_TAIL(&dst->list, pa, entries); } } void pfctl_clear_pool(struct pfctl_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts) { if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1])); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "sp", "da", "dp" }; int i; printf(" [ Skip steps: "); for (i = 0; i < PF_SKIP_COUNT; ++i) { if (rule->skip[i].nr == rule->nr + 1) continue; printf("%s=", t[i]); if (rule->skip[i].nr == -1) printf("end "); else printf("%u ", rule->skip[i].nr); } printf("]\n"); printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); } if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu States: %-6ju]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1]), (uintmax_t)rule->states_cur); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", (unsigned)rule->cuid, (unsigned)rule->cpid, (uintmax_t)rule->states_tot); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } int pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { char anchor_call[MAXPATHLEN]; struct pfctl_eth_rules_info info; struct pfctl_eth_rule rule; int brace; int dotitle = opts & PF_OPT_SHOWALL; int len = strlen(path); char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl_rules: calloc"); snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfctl_eth_rulesets_info ri; u_int32_t mnr, nr; if (pfctl_get_eth_rulesets_info(dev, &ri, npath)) { if (errno == EINVAL) { fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); } else { warn("DIOCGETETHRULESETS"); return (-1); } } mnr = ri.nr; pfctl_print_eth_rule_counters(&rule, opts); for (nr = 0; nr < mnr; ++nr) { struct pfctl_eth_ruleset_info rs; if (pfctl_get_eth_ruleset(dev, npath, nr, &rs)) err(1, "DIOCGETETHRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", rs.name); pfctl_show_eth_rules(dev, npath, opts, format, rs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if (pfctl_get_eth_rules_info(dev, &info, path)) { warn("DIOCGETETHRULES"); return (-1); } for (int nr = 0; nr < info.nr; nr++) { 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++; int aclen = strlen(anchor_call); if (anchor_call[aclen - 1] == '*') anchor_call[aclen - 2] = '\0'; } p = &anchor_call[0]; if (dotitle) { pfctl_print_title("ETH RULES:"); dotitle = 0; } 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, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } } path[len] = '\0'; return (0); } int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr, header = 0; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path), ret = 0; char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl_rules: calloc"); snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfioc_ruleset prs; u_int32_t mnr, nr; memset(&prs, 0, sizeof(prs)); memcpy(prs.path, npath, sizeof(prs.path)); if (ioctl(dev, DIOCGETRULESETS, &prs)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); } mnr = prs.nr; pfctl_print_rule_counters(&rule, opts); for (nr = 0; nr < mnr; ++nr) { prs.nr = nr; if (ioctl(dev, DIOCGETRULESET, &prs)) err(1, "DIOCGETRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", prs.name); pfctl_show_rules(dev, npath, opts, format, prs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if (opts & PF_OPT_SHOWALL) { ret = pfctl_get_rules_info(dev, &ri, PF_PASS, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } header++; } ret = pfctl_get_rules_info(dev, &ri, PF_SCRUB, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } if (opts & PF_OPT_SHOWALL) { if (format == PFCTL_SHOW_RULES && (ri.nr > 0 || header)) pfctl_print_title("FILTER RULES:"); else if (format == PFCTL_SHOW_LABELS && labels) pfctl_print_title("LABEL COUNTERS:"); } for (nr = 0; nr < ri.nr; ++nr) { if (pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_SCRUB, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) { warn("DIOCGETRULENV"); goto error; } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, PF_SCRUB, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: break; case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; print_rule(&rule, anchor_call, rule_numbers, numeric); printf("\n"); pfctl_print_rule_counters(&rule, opts); break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rpool); } ret = pfctl_get_rules_info(dev, &ri, PF_PASS, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } for (nr = 0; nr < ri.nr; ++nr) { if (pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_PASS, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) { warn("DIOCGETRULE"); goto error; } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, PF_PASS, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: { bool show = false; int i = 0; while (rule.label[i][0]) { printf("%s ", rule.label[i++]); show = true; } if (show) { printf("%llu %llu %llu %llu" " %llu %llu %llu %ju\n", (unsigned long long)rule.evaluations, (unsigned long long)(rule.packets[0] + rule.packets[1]), (unsigned long long)(rule.bytes[0] + rule.bytes[1]), (unsigned long long)rule.packets[0], (unsigned long long)rule.bytes[0], (unsigned long long)rule.packets[1], (unsigned long long)rule.bytes[1], (uintmax_t)rule.states_tot); } if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { pfctl_show_rules(dev, npath, opts, format, anchor_call, depth, rule.anchor_wildcard); } break; } case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); print_rule(&rule, anchor_call, rule_numbers, numeric); /* * If this is a 'unnamed' brace notation * anchor, OR the user has explicitly requested * recursion, print it recursively. */ if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { printf(" {\n"); pfctl_print_rule_counters(&rule, opts); pfctl_show_rules(dev, npath, opts, format, anchor_call, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } else { printf("\n"); pfctl_print_rule_counters(&rule, opts); } break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rpool); } error: path[len] = '\0'; return (ret); } int pfctl_show_nat(int dev, char *path, int opts, char *anchorname, int depth) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i, dotitle = opts & PF_OPT_SHOWALL; int brace, ret; int len = strlen(path); char *p; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); for (i = 0; i < 3; i++) { ret = pfctl_get_rules_info(dev, &ri, nattype[i], path); if (ret != 0) { warn("DIOCGETRULES"); return (-1); } for (nr = 0; nr < ri.nr; ++nr) { brace = 0; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if (pfctl_get_rule(dev, nr, ri.ticket, path, nattype[i], &rule, anchor_call)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, nattype[i], path) != 0) 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("TRANSLATION RULES:"); dotitle = 0; } print_rule(&rule, anchor_call, opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); if (brace) printf(" {\n"); else printf("\n"); pfctl_print_rule_counters(&rule, opts); pfctl_clear_pool(&rule.rpool); if (brace) { pfctl_show_nat(dev, path, opts, p, depth + 1); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } } } return (0); } int pfctl_show_src_nodes(int dev, int opts) { struct pfioc_src_nodes psn; struct pf_src_node *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i; memset(&psn, 0, sizeof(psn)); for (;;) { psn.psn_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); psn.psn_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) { warn("DIOCGETSRCNODES"); free(inbuf); return (-1); } if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len) break; if (len == 0 && psn.psn_len == 0) goto done; if (len == 0 && psn.psn_len != 0) len = psn.psn_len; if (psn.psn_len == 0) goto done; /* no src_nodes */ len *= 2; } p = psn.psn_src_nodes; if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL)) pfctl_print_title("SOURCE TRACKING NODES:"); for (i = 0; i < psn.psn_len; i += sizeof(*p)) { print_src_node(p, opts); p++; } done: free(inbuf); return (0); } struct pfctl_show_state_arg { int opts; int dotitle; const char *iface; }; static int pfctl_show_state(struct pfctl_state *s, void *arg) { struct pfctl_show_state_arg *a = (struct pfctl_show_state_arg *)arg; if (a->dotitle) { pfctl_print_title("STATES:"); a->dotitle = 0; } print_state(s, a->opts); return (0); } int pfctl_show_states(int dev, const char *iface, int opts) { struct pfctl_show_state_arg arg; struct pfctl_state_filter filter = {}; if (iface != NULL) strncpy(filter.ifname, iface, IFNAMSIZ); arg.opts = opts; arg.dotitle = opts & PF_OPT_SHOWALL; arg.iface = iface; if (pfctl_get_filtered_states_iter(&filter, pfctl_show_state, &arg)) return (-1); return (0); } int pfctl_show_status(int dev, int opts) { struct pfctl_status *status; struct pfctl_syncookies cookies; if ((status = pfctl_get_status(dev)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } if (pfctl_get_syncookies(dev, &cookies)) { pfctl_free_status(status); warn("DIOCGETSYNCOOKIES"); return (-1); } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INFO:"); print_status(status, &cookies, opts); pfctl_free_status(status); return (0); } int pfctl_show_running(int dev) { struct pfctl_status *status; int running; if ((status = pfctl_get_status(dev)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } running = status->running; print_running(status); pfctl_free_status(status); return (!running); } int pfctl_show_timeouts(int dev, int opts) { struct pfioc_tm pt; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("TIMEOUTS:"); memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { pt.timeout = pf_timeouts[i].timeout; if (ioctl(dev, DIOCGETTIMEOUT, &pt)) err(1, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START && pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END) printf(" states"); else printf("s"); printf("\n"); } return (0); } int pfctl_show_limits(int dev, int opts) { struct pfioc_limit pl; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { pl.index = pf_limits[i].index; if (ioctl(dev, DIOCGETLIMIT, &pl)) err(1, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); if (pl.limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %8u\n", pl.limit); } return (0); } void pfctl_show_creators(int opts) { int ret; uint32_t creators[16]; size_t count = nitems(creators); ret = pfctl_get_creatorids(pfh, creators, &count); if (ret != 0) errx(ret, "Failed to retrieve creators"); printf("Creator IDs:\n"); for (size_t i = 0; i < count; i++) printf("%08x\n", creators[i]); } /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) err(1, "DIOCBEGINADDRS"); } pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) err(1, "DIOCADDADDR"); } } return (0); } int pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, const char *anchor_call) { u_int8_t rs_num; struct pfctl_rule *rule; struct pfctl_ruleset *rs; char *p; rs_num = pf_get_ruleset_number(r->action); if (rs_num == PF_RULESET_MAX) errx(1, "Invalid rule type %d", r->action); rs = &pf->anchor->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_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_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_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INIT(&rule->rpool.list); pfctl_move_pool(&r->rpool, &rule->rpool); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); return (0); } int 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 (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) || pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_RDR, path)) return (1); } if (a == pf->astack[0] && ((altqsupport && (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) { if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path)) return (2); } if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) || pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path)) return (3); } if (pf->loadopt & PFCTL_FLAG_TABLE) if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path)) return (4); if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int pfctl_load_eth_ruleset(struct pfctl *pf, char *path, struct pfctl_eth_ruleset *rs, int depth) { struct pfctl_eth_rule *r; int error, len = strlen(path); int brace = 0; 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 (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); } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); 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"); if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_eth_rule(r, r->anchor ? r->anchor->name : "", pf->opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); } path[len] = '\0'; return (0); } int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) { struct pfctl_rule *r; int error, len = strlen(path); int brace = 0; pf->anchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name); if (depth) { if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_ruleset_trans(pf, path, rs->anchor, false))) { printf("pfctl_load_rulesets: " "pfctl_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } if (pf->optimize && rs_num == PF_RULESET_FILTER) pfctl_optimize_ruleset(pf, rs); while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries); for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) expand_label(r->label[i], PF_RULE_LABEL_SIZE, r); expand_label(r->tagname, PF_TAG_NAME_SIZE, r); expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r); if ((error = pfctl_load_rule(pf, path, r, depth))) goto error; if (r->anchor) { if ((error = pfctl_load_ruleset(pf, path, &r->anchor->ruleset, rs_num, depth + 1))) goto error; } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); 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_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth) { u_int8_t rs_num = pf_get_ruleset_number(r->action); char *name; u_int32_t ticket; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); int error; bool was_present; /* set up anchor before adding to path for anchor_call */ if ((pf->opts & PF_OPT_NOACTION) == 0) ticket = pfctl_get_ticket(pf->trans, rs_num, path); if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_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 = ""; was_present = false; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); error = pfctl_add_rule(pf->dev, r, anchor, name, ticket, pf->paddr.ticket); switch (error) { case 0: /* things worked, do nothing */ break; case EEXIST: /* an identical rule is already present */ was_present = true; break; default: err(1, "DIOCADDRULENV"); } } if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_rule(r, name, pf->opts & PF_OPT_VERBOSE2, pf->opts & PF_OPT_NUMERIC); if (was_present) printf(" -- rule was already present"); } path[len] = '\0'; pfctl_clear_pool(&r->rpool); return (0); } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { if (altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0) { memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { if (errno == ENXIO) errx(1, "qtype not configured"); else if (errno == ENODEV) errx(1, "%s: driver does not support " "altq", a->ifname); else err(1, "DIOCADDALTQ"); } } pfaltq_store(&pf->paltq->altq); } return (0); } int pfctl_rules(int dev, char *filename, int opts, int optimize, char *anchorname, struct pfr_buffer *trans) { #define ERR(x) do { warn(x); goto _error; } while(0) #define ERRX(x) do { warnx(x); goto _error; } while(0) struct pfr_buffer *t, buf; struct pfioc_altq pa; struct pfctl pf; struct pfctl_ruleset *rs; struct pfctl_eth_ruleset *ethrs; struct pfr_table trs; char *path; int osize; RB_INIT(&pf_anchors); 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; t = &buf; osize = 0; } else { t = trans; osize = t->pfrb_size; } memset(&pa, 0, sizeof(pa)); pa.version = PFIOC_ALTQ_VERSION; memset(&pf, 0, sizeof(pf)); memset(&trs, 0, sizeof(trs)); if ((path = calloc(1, MAXPATHLEN)) == NULL) ERRX("pfctl_rules: calloc"); if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) ERRX("pfctl_rules: strlcpy"); pf.dev = dev; pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) ERRX("pfctl_rules: calloc"); rs = &pf.anchor->ruleset; pf_init_ruleset(rs); rs->anchor = pf.anchor; if (strlcpy(pf.anchor->path, anchorname, sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) errx(1, "pfctl_rules: strlcpy"); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) errx(1, "pfctl_rules: strlcpy"); pf.astack[0] = pf.anchor; pf.asd = 0; if (anchorname[0]) pf.loadopt &= ~PFCTL_FLAG_ALTQ; pf.paltq = &pa; 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, true)) ERRX("pfctl_rules"); if (pf.loadopt & PFCTL_FLAG_ETH) pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); if (pf.loadopt & PFCTL_FLAG_TABLE) pf.astack[0]->ruleset.tticket = pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname); } if (parse_config(filename, &pf) < 0) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Syntax error in config file: " "pf rules not loaded"); else goto _error; } if (loadopt & PFCTL_FLAG_OPTION) pfctl_adjust_skip_ifaces(&pf); 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, 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) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) || (pf.loadopt & PFCTL_FLAG_FILTER && pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Unable to load rules into kernel"); else goto _error; } if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) if (check_commit_altq(dev, opts) != 0) ERRX("errors in altq config"); /* process "load anchor" directives */ if (!anchorname[0]) if (pfctl_load_anchors(dev, &pf, t) == -1) ERRX("load anchors"); if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) { if (!anchorname[0]) if (pfctl_load_options(&pf)) goto _error; if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) ERR("DIOCXCOMMIT"); } free(path); return (0); _error: if (trans == NULL) { /* main ruleset */ if ((opts & PF_OPT_NOACTION) == 0) if (pfctl_trans(dev, t, DIOCXROLLBACK, osize)) err(1, "DIOCXROLLBACK"); exit(1); } else { /* sub ruleset */ free(path); return (-1); } #undef ERR #undef ERRX } FILE * pfctl_fopen(const char *name, const char *mode) { struct stat st; FILE *fp; fp = fopen(name, mode); if (fp == NULL) return (NULL); if (fstat(fileno(fp), &st)) { fclose(fp); return (NULL); } if (S_ISDIR(st.st_mode)) { fclose(fp); errno = EISDIR; return (NULL); } return (fp); } void pfctl_init_options(struct pfctl *pf) { pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_SCTP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_SCTP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_SCTP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_SCTP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_SCTP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL; pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT; pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT; pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT; pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT; pf->debug = PF_DEBUG_URGENT; pf->reassemble = 0; pf->syncookies = false; pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT; pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT; } int pfctl_load_options(struct pfctl *pf) { int i, error = 0; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); /* load limits */ for (i = 0; i < PF_LIMIT_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i]) continue; if (pfctl_load_limit(pf, i, pf->limit[i])) error = 1; } /* * If we've set the limit, but haven't explicitly set adaptive * timeouts, do it now with a start of 60% and end of 120%. */ if (pf->limit_set[PF_LIMIT_STATES] && !pf->timeout_set[PFTM_ADAPTIVE_START] && !pf->timeout_set[PFTM_ADAPTIVE_END]) { pf->timeout[PFTM_ADAPTIVE_START] = (pf->limit[PF_LIMIT_STATES] / 10) * 6; pf->timeout_set[PFTM_ADAPTIVE_START] = 1; pf->timeout[PFTM_ADAPTIVE_END] = (pf->limit[PF_LIMIT_STATES] / 10) * 12; pf->timeout_set[PFTM_ADAPTIVE_END] = 1; } /* load timeouts */ for (i = 0; i < PFTM_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i]) continue; if (pfctl_load_timeout(pf, i, pf->timeout[i])) error = 1; } /* load debug */ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set) if (pfctl_load_debug(pf, pf->debug)) error = 1; /* load logif */ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set) if (pfctl_load_logif(pf, pf->ifname)) error = 1; /* load hostid */ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set) if (pfctl_load_hostid(pf, pf->hostid)) error = 1; /* load reassembly settings */ if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set) if (pfctl_load_reassembly(pf, pf->reassemble)) error = 1; /* load keepcounters */ if (pfctl_set_keepcounters(pf->dev, pf->keep_counters)) error = 1; /* load syncookies settings */ if (pfctl_load_syncookies(pf, pf->syncookies)) error = 1; return (error); } int pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) { int i; for (i = 0; pf_limits[i].name; i++) { if (strcasecmp(opt, pf_limits[i].name) == 0) { pf->limit[pf_limits[i].index] = limit; pf->limit_set[pf_limits[i].index] = 1; break; } } if (pf_limits[i].name == NULL) { warnx("Bad pool name."); return (1); } if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); return (0); } int pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) { struct pfioc_limit pl; memset(&pl, 0, sizeof(pl)); pl.index = index; pl.limit = limit; if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { if (errno == EBUSY) warnx("Current pool size exceeds requested hard limit"); else warnx("DIOCSETLIMIT"); return (1); } return (0); } int pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_timeouts[i].name; i++) { if (strcasecmp(opt, pf_timeouts[i].name) == 0) { pf->timeout[pf_timeouts[i].timeout] = seconds; pf->timeout_set[pf_timeouts[i].timeout] = 1; break; } } if (pf_timeouts[i].name == NULL) { warnx("Bad timeout name."); return (1); } if (pf->opts & PF_OPT_VERBOSE && ! quiet) printf("set timeout %s %d\n", opt, seconds); return (0); } int pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds) { struct pfioc_tm pt; memset(&pt, 0, sizeof(pt)); pt.timeout = timeout; pt.seconds = seconds; if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) { warnx("DIOCSETTIMEOUT"); return (1); } return (0); } int pfctl_set_reassembly(struct pfctl *pf, int on, int nodf) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); pf->reass_set = 1; if (on) { pf->reassemble = PF_REASS_ENABLED; if (nodf) pf->reassemble |= PF_REASS_NODF; } else { pf->reassemble = 0; } if (pf->opts & PF_OPT_VERBOSE) printf("set reassemble %s %s\n", on ? "yes" : "no", nodf ? "no-df" : ""); return (0); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { const struct pf_hint *hint; int i, r; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_hints[i].name; i++) if (strcasecmp(opt, pf_hints[i].name) == 0) break; hint = pf_hints[i].hint; if (hint == NULL) { warnx("invalid state timeouts optimization"); return (1); } for (i = 0; hint[i].name; i++) if ((r = pfctl_set_timeout(pf, hint[i].name, hint[i].timeout, 1))) return (r); if (pf->opts & PF_OPT_VERBOSE) printf("set optimization %s\n", opt); return (0); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(ifname, "none")) { free(pf->ifname); pf->ifname = NULL; } else { pf->ifname = strdup(ifname); if (!pf->ifname) errx(1, "pfctl_set_logif: strdup"); } pf->ifname_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set loginterface %s\n", ifname); return (0); } int pfctl_load_logif(struct pfctl *pf, char *ifname) { - struct pfioc_if pi; - - memset(&pi, 0, sizeof(pi)); - if (ifname && strlcpy(pi.ifname, ifname, - sizeof(pi.ifname)) >= sizeof(pi.ifname)) { + if (ifname != NULL && strlen(ifname) >= IFNAMSIZ) { warnx("pfctl_load_logif: strlcpy"); return (1); } - if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) { - warnx("DIOCSETSTATUSIF"); - return (1); - } - return (0); + return (pfctl_set_statusif(pfh, ifname ? ifname : "")); } int pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); HTONL(hostid); pf->hostid = hostid; pf->hostid_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set hostid 0x%08x\n", ntohl(hostid)); return (0); } int pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) { if (ioctl(dev, DIOCSETHOSTID, &hostid)) { warnx("DIOCSETHOSTID"); return (1); } return (0); } int pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly) { if (ioctl(dev, DIOCSETREASS, &reassembly)) { warnx("DIOCSETREASS"); return (1); } return (0); } int pfctl_load_syncookies(struct pfctl *pf, u_int8_t val) { struct pfctl_syncookies cookies; bzero(&cookies, sizeof(cookies)); cookies.mode = val; cookies.lowwater = pf->syncookieswat[0]; cookies.highwater = pf->syncookieswat[1]; if (pfctl_set_syncookies(dev, &cookies)) { warnx("DIOCSETSYNCOOKIES"); return (1); } return (0); } int pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w) { if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) { warnx("syncookies start/end only apply to adaptive"); return (1); } if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) { if (!w->hi) w->hi = PF_SYNCOOKIES_HIWATPCT; if (!w->lo) w->lo = w->hi / 2; if (w->lo >= w->hi) { warnx("start must be higher than end"); return (1); } pf->syncookieswat[0] = w->lo; pf->syncookieswat[1] = w->hi; pf->syncookieswat_set = 1; } if (pf->opts & PF_OPT_VERBOSE) { if (val == PF_SYNCOOKIES_NEVER) printf("set syncookies never\n"); else if (val == PF_SYNCOOKIES_ALWAYS) printf("set syncookies always\n"); else if (val == PF_SYNCOOKIES_ADAPTIVE) { if (pf->syncookieswat_set) printf("set syncookies adaptive (start %u%%, " "end %u%%)\n", pf->syncookieswat[1], pf->syncookieswat[0]); else printf("set syncookies adaptive\n"); } else { /* cannot happen */ warnx("king bula ate all syncookies"); return (1); } } pf->syncookies = val; return (0); } int pfctl_set_debug(struct pfctl *pf, char *d) { u_int32_t level; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(d, "none")) pf->debug = PF_DEBUG_NONE; else if (!strcmp(d, "urgent")) pf->debug = PF_DEBUG_URGENT; else if (!strcmp(d, "misc")) pf->debug = PF_DEBUG_MISC; else if (!strcmp(d, "loud")) pf->debug = PF_DEBUG_NOISY; else { warnx("unknown debug level \"%s\"", d); return (-1); } pf->debug_set = 1; level = pf->debug; if ((pf->opts & PF_OPT_NOACTION) == 0) if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if (pf->opts & PF_OPT_VERBOSE) printf("set debug %s\n", d); return (0); } int pfctl_load_debug(struct pfctl *pf, unsigned int level) { if (ioctl(pf->dev, DIOCSETDEBUG, &level)) { warnx("DIOCSETDEBUG"); return (1); } return (0); } int pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) { struct pfioc_iface pi; struct node_host *h = NULL, *n = NULL; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); bzero(&pi, sizeof(pi)); pi.pfiio_flags = flags; /* Make sure our cache matches the kernel. If we set or clear the flag * for a group this applies to all members. */ h = ifa_grouplookup(ifname, 0); for (n = h; n != NULL; n = n->next) pfctl_set_interface_flags(pf, n->ifname, flags, how); if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) errx(1, "pfctl_set_interface_flags: strlcpy"); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (how == 0) { if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); } else { if (ioctl(pf->dev, DIOCSETIFFLAG, &pi)) err(1, "DIOCSETIFFLAG"); pfctl_check_skip_ifaces(ifname); } } return (0); } void pfctl_debug(int dev, u_int32_t level, int opts) { if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if ((opts & PF_OPT_QUIET) == 0) { fprintf(stderr, "debug level set to '"); switch (level) { case PF_DEBUG_NONE: fprintf(stderr, "none"); break; case PF_DEBUG_URGENT: fprintf(stderr, "urgent"); break; case PF_DEBUG_MISC: fprintf(stderr, "misc"); break; case PF_DEBUG_NOISY: fprintf(stderr, "loud"); break; default: fprintf(stderr, ""); break; } fprintf(stderr, "'\n"); } } int pfctl_test_altqsupport(int dev, int opts) { struct pfioc_altq pa; pa.version = PFIOC_ALTQ_VERSION; if (ioctl(dev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { if (opts & PF_OPT_VERBOSE) fprintf(stderr, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else err(1, "DIOCGETALTQS"); } return (1); } int pfctl_show_anchors(int dev, int opts, char *anchorname) { struct pfioc_ruleset pr; u_int32_t mnr, nr; memset(&pr, 0, sizeof(pr)); memcpy(pr.path, anchorname, sizeof(pr.path)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { char sub[MAXPATHLEN]; pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (pr.path[0]) { strlcat(sub, pr.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, pr.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub)) return (-1); } 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) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int mode = O_RDONLY; int opts = 0; int optimize = PF_OPTIMIZE_BASIC; char anchorname[MAXPATHLEN]; char *path; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:t:T:vx:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; break; case 'd': opts |= PF_OPT_DISABLE; mode = O_RDWR; break; case 'D': if (pfctl_cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'e': opts |= PF_OPT_ENABLE; mode = O_RDWR; break; case 'q': opts |= PF_OPT_QUIET; break; case 'F': clearopt = pfctl_lookup_option(optarg, clearopt_list); if (clearopt == NULL) { warnx("Unknown flush modifier '%s'", optarg); usage(); } mode = O_RDWR; break; case 'i': ifaceopt = optarg; break; case 'k': if (state_killers >= 2) { warnx("can only specify -k twice"); usage(); /* NOTREACHED */ } state_kill[state_killers++] = optarg; mode = O_RDWR; break; case 'K': if (src_node_killers >= 2) { warnx("can only specify -K twice"); usage(); /* NOTREACHED */ } src_node_kill[src_node_killers++] = optarg; mode = O_RDWR; break; case 'm': opts |= PF_OPT_MERGE; break; case 'M': opts |= PF_OPT_KILLMATCH; break; case 'n': opts |= PF_OPT_NOACTION; break; case 'N': loadopt |= PFCTL_FLAG_NAT; break; case 'r': opts |= PF_OPT_USEDNS; break; case 'f': rulesopt = optarg; mode = O_RDWR; break; case 'g': opts |= PF_OPT_DEBUG; break; case 'A': loadopt |= PFCTL_FLAG_ALTQ; break; case 'R': loadopt |= PFCTL_FLAG_FILTER; break; case 'o': optiopt = pfctl_lookup_option(optarg, optiopt_list); if (optiopt == NULL) { warnx("Unknown optimization '%s'", optarg); usage(); } opts |= PF_OPT_OPTIMIZE; break; case 'O': loadopt |= PFCTL_FLAG_OPTION; break; case 'p': pf_device = optarg; break; case 'P': opts |= PF_OPT_NUMERIC; break; case 's': showopt = pfctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); } break; case 't': tableopt = optarg; break; case 'T': tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); if (tblcmdopt == NULL) { warnx("Unknown table command '%s'", optarg); usage(); } break; case 'v': if (opts & PF_OPT_VERBOSE) opts |= PF_OPT_VERBOSE2; opts |= PF_OPT_VERBOSE; break; case 'x': debugopt = pfctl_lookup_option(optarg, debugopt_list); if (debugopt == NULL) { warnx("Unknown debug level '%s'", optarg); usage(); } mode = O_RDWR; break; case 'z': opts |= PF_OPT_CLRRULECTRS; mode = O_RDWR; break; case 'h': /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } } if (tblcmdopt != NULL) { argc -= optind; argv += optind; ch = *tblcmdopt; if (ch == 'l') { loadopt |= PFCTL_FLAG_TABLE; tblcmdopt = NULL; } else mode = strchr("acdefkrz", ch) ? O_RDWR : O_RDONLY; } else if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (loadopt == 0) loadopt = ~0; if ((path = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl: calloc"); memset(anchorname, 0, sizeof(anchorname)); if (anchoropt != NULL) { int len = strlen(anchoropt); if (len >= 1 && anchoropt[len - 1] == '*') { if (len >= 2 && anchoropt[len - 2] == '/') anchoropt[len - 2] = '\0'; else anchoropt[len - 1] = '\0'; opts |= PF_OPT_RECURSE; } if (strlcpy(anchorname, anchoropt, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH; } if ((opts & PF_OPT_NOACTION) == 0) { dev = open(pf_device, mode); if (dev == -1) err(1, "%s", pf_device); altqsupport = pfctl_test_altqsupport(dev, opts); } else { dev = open(pf_device, O_RDONLY); if (dev >= 0) opts |= PF_OPT_DUMMYACTION; /* turn off options */ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); clearopt = showopt = debugopt = NULL; #if !defined(ENABLE_ALTQ) altqsupport = 0; #else altqsupport = 1; #endif } pfh = pfctl_open(pf_device); if (pfh == NULL) err(1, "Failed to open netlink"); if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); if (opts & PF_OPT_VERBOSE2) printf("Ethernet:\n"); pfctl_show_eth_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, anchorname, 0, 0); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, anchorname, 0, 0); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, path, opts, anchorname, 0); break; case 'q': pfctl_show_altq(dev, ifaceopt, opts, opts & PF_OPT_VERBOSE2); break; case 's': pfctl_show_states(dev, ifaceopt, opts); break; case 'S': pfctl_show_src_nodes(dev, opts); break; case 'i': pfctl_show_status(dev, opts); break; case 'R': error = pfctl_show_running(dev); break; case 't': pfctl_show_timeouts(dev, opts); break; case 'm': pfctl_show_limits(dev, opts); break; case 'e': pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_nat(dev, path, opts, anchorname, 0); pfctl_show_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_altq(dev, ifaceopt, opts, 0); pfctl_show_states(dev, ifaceopt, opts); pfctl_show_src_nodes(dev, opts); pfctl_show_status(dev, opts); pfctl_show_rules(dev, path, opts, 1, anchorname, 0, 0); pfctl_show_timeouts(dev, opts); pfctl_show_limits(dev, opts); pfctl_show_tables(anchorname, opts); pfctl_show_fingerprints(opts); break; case 'T': pfctl_show_tables(anchorname, opts); break; case 'o': pfctl_load_fingerprints(dev, opts); pfctl_show_fingerprints(opts); break; case 'I': pfctl_show_ifaces(ifaceopt, opts); break; case 'c': pfctl_show_creators(opts); break; } } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) { pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); } if (clearopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); switch (*clearopt) { case 'e': pfctl_flush_eth_rules(dev, opts, anchorname); break; case 'r': pfctl_flush_rules(dev, opts, anchorname); break; case 'n': pfctl_flush_nat(dev, opts, anchorname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_iface_states(dev, ifaceopt, opts); break; case 'S': pfctl_clear_src_nodes(dev, opts); break; case 'i': pfctl_clear_stats(dev, opts); break; case 'a': pfctl_flush_eth_rules(dev, opts, anchorname); pfctl_flush_rules(dev, opts, anchorname); pfctl_flush_nat(dev, opts, anchorname); pfctl_clear_tables(anchorname, opts); if (!*anchorname) { pfctl_clear_altq(dev, opts); pfctl_clear_iface_states(dev, ifaceopt, opts); pfctl_clear_src_nodes(dev, opts); pfctl_clear_stats(dev, opts); pfctl_clear_fingerprints(dev, opts); pfctl_clear_interface_flags(dev, opts); } break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_clear_tables(anchorname, opts); break; } } if (state_killers) { if (!strcmp(state_kill[0], "label")) pfctl_label_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "id")) pfctl_id_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "gateway")) pfctl_gateway_kill_states(dev, ifaceopt, opts); else pfctl_net_kill_states(dev, ifaceopt, opts); } if (src_node_killers) pfctl_kill_src_nodes(dev, ifaceopt, opts); if (tblcmdopt != NULL) { error = pfctl_command_tables(argc, argv, tableopt, tblcmdopt, rulesopt, anchorname, opts); rulesopt = NULL; } if (optiopt != NULL) { switch (*optiopt) { case 'n': optimize = 0; break; case 'b': optimize |= PF_OPTIMIZE_BASIC; break; case 'o': case 'p': optimize |= PF_OPTIMIZE_PROFILE; break; } } if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) && !anchorname[0] && !(opts & PF_OPT_NOACTION)) if (pfctl_get_skip_ifaces()) error = 1; if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) && !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION)) if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) error = 1; if (rulesopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); if (pfctl_rules(dev, rulesopt, opts, optimize, anchorname, NULL)) error = 1; else if (!(opts & PF_OPT_NOACTION) && (loadopt & PFCTL_FLAG_TABLE)) warn_namespace_collision(NULL); } if (opts & PF_OPT_ENABLE) if (pfctl_enable(dev, opts)) error = 1; if (debugopt != NULL) { switch (*debugopt) { case 'n': pfctl_debug(dev, PF_DEBUG_NONE, opts); break; case 'u': pfctl_debug(dev, PF_DEBUG_URGENT, opts); break; case 'm': pfctl_debug(dev, PF_DEBUG_MISC, opts); break; case 'l': pfctl_debug(dev, PF_DEBUG_NOISY, opts); break; } } exit(error); } diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c index e4558c156aef..5f33e49b4a2e 100644 --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -1,1170 +1,1208 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Alexander V. Chernikov * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MOD_NAME nl_pf #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_DEBUG); struct nl_parsed_state { uint8_t version; uint32_t id; uint32_t creatorid; char ifname[IFNAMSIZ]; uint16_t proto; sa_family_t af; struct pf_addr addr; struct pf_addr mask; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct nl_parsed_state, _field) static const struct nlattr_parser nla_p_state[] = { { .type = PF_ST_ID, .off = _OUT(id), .cb = nlattr_get_uint32 }, { .type = PF_ST_CREATORID, .off = _OUT(creatorid), .cb = nlattr_get_uint32 }, { .type = PF_ST_IFNAME, .arg = (const void *)IFNAMSIZ, .off = _OUT(ifname), .cb = nlattr_get_chara }, { .type = PF_ST_AF, .off = _OUT(proto), .cb = nlattr_get_uint8 }, { .type = PF_ST_PROTO, .off = _OUT(proto), .cb = nlattr_get_uint16 }, { .type = PF_ST_FILTER_ADDR, .off = _OUT(addr), .cb = nlattr_get_in6_addr }, { .type = PF_ST_FILTER_MASK, .off = _OUT(mask), .cb = nlattr_get_in6_addr }, }; static const struct nlfield_parser nlf_p_generic[] = { { .off_in = _IN(version), .off_out = _OUT(version), .cb = nlf_get_u8 }, }; #undef _IN #undef _OUT NL_DECLARE_PARSER(state_parser, struct genlmsghdr, nlf_p_generic, nla_p_state); static void dump_addr(struct nl_writer *nw, int attr, const struct pf_addr *addr, int af) { switch (af) { case AF_INET: nlattr_add(nw, attr, 4, &addr->v4); break; case AF_INET6: nlattr_add(nw, attr, 16, &addr->v6); break; }; } static bool dump_state_peer(struct nl_writer *nw, int attr, const struct pf_state_peer *peer) { int off = nlattr_add_nested(nw, attr); if (off == 0) return (false); nlattr_add_u32(nw, PF_STP_SEQLO, peer->seqlo); nlattr_add_u32(nw, PF_STP_SEQHI, peer->seqhi); nlattr_add_u32(nw, PF_STP_SEQDIFF, peer->seqdiff); nlattr_add_u16(nw, PF_STP_MAX_WIN, peer->max_win); nlattr_add_u16(nw, PF_STP_MSS, peer->mss); nlattr_add_u8(nw, PF_STP_STATE, peer->state); nlattr_add_u8(nw, PF_STP_WSCALE, peer->wscale); if (peer->scrub != NULL) { struct pf_state_scrub *sc = peer->scrub; uint16_t pfss_flags = sc->pfss_flags & PFSS_TIMESTAMP; nlattr_add_u16(nw, PF_STP_PFSS_FLAGS, pfss_flags); nlattr_add_u32(nw, PF_STP_PFSS_TS_MOD, sc->pfss_ts_mod); nlattr_add_u8(nw, PF_STP_PFSS_TTL, sc->pfss_ttl); nlattr_add_u8(nw, PF_STP_SCRUB_FLAG, PFSYNC_SCRUB_FLAG_VALID); } nlattr_set_len(nw, off); return (true); } static bool dump_state_key(struct nl_writer *nw, int attr, const struct pf_state_key *key) { int off = nlattr_add_nested(nw, attr); if (off == 0) return (false); dump_addr(nw, PF_STK_ADDR0, &key->addr[0], key->af); dump_addr(nw, PF_STK_ADDR1, &key->addr[1], key->af); nlattr_add_u16(nw, PF_STK_PORT0, key->port[0]); nlattr_add_u16(nw, PF_STK_PORT1, key->port[1]); nlattr_set_len(nw, off); return (true); } static int dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, struct pf_kstate *s, struct nl_pstate *npt) { struct nl_writer *nw = npt->nw; int error = 0; int af; struct pf_state_key *key; if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) goto enomem; struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GETSTATES; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u64(nw, PF_ST_VERSION, PF_STATE_VERSION); key = s->key[PF_SK_WIRE]; if (!dump_state_key(nw, PF_ST_KEY_WIRE, key)) goto enomem; key = s->key[PF_SK_STACK]; if (!dump_state_key(nw, PF_ST_KEY_STACK, key)) goto enomem; af = s->key[PF_SK_WIRE]->af; nlattr_add_u8(nw, PF_ST_PROTO, s->key[PF_SK_WIRE]->proto); nlattr_add_u8(nw, PF_ST_AF, af); nlattr_add_string(nw, PF_ST_IFNAME, s->kif->pfik_name); nlattr_add_string(nw, PF_ST_ORIG_IFNAME, s->orig_kif->pfik_name); dump_addr(nw, PF_ST_RT_ADDR, &s->rt_addr, af); nlattr_add_u32(nw, PF_ST_CREATION, time_uptime - (s->creation / 1000)); uint32_t expire = pf_state_expires(s); if (expire > time_uptime) expire = expire - time_uptime; nlattr_add_u32(nw, PF_ST_EXPIRE, expire); nlattr_add_u8(nw, PF_ST_DIRECTION, s->direction); nlattr_add_u8(nw, PF_ST_LOG, s->act.log); nlattr_add_u8(nw, PF_ST_TIMEOUT, s->timeout); nlattr_add_u16(nw, PF_ST_STATE_FLAGS, s->state_flags); uint8_t sync_flags = 0; if (s->src_node) sync_flags |= PFSYNC_FLAG_SRCNODE; if (s->nat_src_node) sync_flags |= PFSYNC_FLAG_NATSRCNODE; nlattr_add_u8(nw, PF_ST_SYNC_FLAGS, sync_flags); nlattr_add_u64(nw, PF_ST_ID, s->id); nlattr_add_u32(nw, PF_ST_CREATORID, htonl(s->creatorid)); nlattr_add_u32(nw, PF_ST_RULE, s->rule.ptr ? s->rule.ptr->nr : -1); nlattr_add_u32(nw, PF_ST_ANCHOR, s->anchor.ptr ? s->anchor.ptr->nr : -1); nlattr_add_u32(nw, PF_ST_NAT_RULE, s->nat_rule.ptr ? s->nat_rule.ptr->nr : -1); nlattr_add_u64(nw, PF_ST_PACKETS0, s->packets[0]); nlattr_add_u64(nw, PF_ST_PACKETS1, s->packets[1]); nlattr_add_u64(nw, PF_ST_BYTES0, s->bytes[0]); nlattr_add_u64(nw, PF_ST_BYTES1, s->bytes[1]); nlattr_add_u32(nw, PF_ST_RTABLEID, s->act.rtableid); nlattr_add_u8(nw, PF_ST_MIN_TTL, s->act.min_ttl); nlattr_add_u16(nw, PF_ST_MAX_MSS, s->act.max_mss); nlattr_add_u16(nw, PF_ST_DNPIPE, s->act.dnpipe); nlattr_add_u16(nw, PF_ST_DNRPIPE, s->act.dnrpipe); nlattr_add_u8(nw, PF_ST_RT, s->rt); if (s->rt_kif != NULL) nlattr_add_string(nw, PF_ST_RT_IFNAME, s->rt_kif->pfik_name); if (!dump_state_peer(nw, PF_ST_PEER_SRC, &s->src)) goto enomem; if (!dump_state_peer(nw, PF_ST_PEER_DST, &s->dst)) goto enomem; if (nlmsg_end(nw)) return (0); enomem: error = ENOMEM; nlmsg_abort(nw); return (error); } static int handle_dumpstates(struct nlpcb *nlp, struct nl_parsed_state *attrs, struct nlmsghdr *hdr, struct nl_pstate *npt) { int error = 0; hdr->nlmsg_flags |= NLM_F_MULTI; for (int i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_kstate *s; if (LIST_EMPTY(&ih->states)) continue; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { sa_family_t af = s->key[PF_SK_WIRE]->af; if (s->timeout == PFTM_UNLINKED) continue; /* Filter */ if (attrs->creatorid != 0 && s->creatorid != attrs->creatorid) continue; if (attrs->ifname[0] != 0 && strncmp(attrs->ifname, s->kif->pfik_name, IFNAMSIZ) != 0) continue; if (attrs->proto != 0 && s->key[PF_SK_WIRE]->proto != attrs->proto) continue; if (attrs->af != 0 && af != attrs->af) continue; if (pf_match_addr(1, &s->key[PF_SK_WIRE]->addr[0], &attrs->mask, &attrs->addr, af) && pf_match_addr(1, &s->key[PF_SK_WIRE]->addr[1], &attrs->mask, &attrs->addr, af) && pf_match_addr(1, &s->key[PF_SK_STACK]->addr[0], &attrs->mask, &attrs->addr, af) && pf_match_addr(1, &s->key[PF_SK_STACK]->addr[1], &attrs->mask, &attrs->addr, af)) continue; error = dump_state(nlp, hdr, s, npt); if (error != 0) break; } PF_HASHROW_UNLOCK(ih); } if (!nlmsg_end_dump(npt->nw, error, hdr)) { NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); return (ENOMEM); } return (error); } static int handle_getstate(struct nlpcb *nlp, struct nl_parsed_state *attrs, struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_kstate *s = pf_find_state_byid(attrs->id, attrs->creatorid); if (s == NULL) return (ENOENT); return (dump_state(nlp, hdr, s, npt)); } static int dump_creatorid(struct nlpcb *nlp, const struct nlmsghdr *hdr, uint32_t creator, struct nl_pstate *npt) { struct nl_writer *nw = npt->nw; if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) goto enomem; struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GETCREATORS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_ST_CREATORID, htonl(creator)); if (nlmsg_end(nw)) return (0); enomem: nlmsg_abort(nw); return (ENOMEM); } static int pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt) { int error; struct nl_parsed_state attrs = {}; error = nl_parse_nlmsg(hdr, &state_parser, npt, &attrs); if (error != 0) return (error); if (attrs.id != 0) error = handle_getstate(npt->nlp, &attrs, hdr, npt); else error = handle_dumpstates(npt->nlp, &attrs, hdr, npt); return (error); } static int pf_handle_getcreators(struct nlmsghdr *hdr, struct nl_pstate *npt) { uint32_t creators[16]; int error = 0; bzero(creators, sizeof(creators)); for (int i = 0; i < pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_kstate *s; if (LIST_EMPTY(&ih->states)) continue; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { int j; if (s->timeout == PFTM_UNLINKED) continue; for (j = 0; j < nitems(creators); j++) { if (creators[j] == s->creatorid) break; if (creators[j] == 0) { creators[j] = s->creatorid; break; } } if (j == nitems(creators)) printf("Warning: too many creators!\n"); } PF_HASHROW_UNLOCK(ih); } hdr->nlmsg_flags |= NLM_F_MULTI; for (int i = 0; i < nitems(creators); i++) { if (creators[i] == 0) break; error = dump_creatorid(npt->nlp, hdr, creators[i], npt); } if (!nlmsg_end_dump(npt->nw, error, hdr)) { NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); return (ENOMEM); } return (error); } static int pf_handle_start(struct nlmsghdr *hdr __unused, struct nl_pstate *npt __unused) { return (pf_start()); } static int pf_handle_stop(struct nlmsghdr *hdr __unused, struct nl_pstate *npt __unused) { return (pf_stop()); } #define _OUT(_field) offsetof(struct pf_addr_wrap, _field) static const struct nlattr_parser nla_p_addr_wrap[] = { { .type = PF_AT_ADDR, .off = _OUT(v.a.addr), .cb = nlattr_get_in6_addr }, { .type = PF_AT_MASK, .off = _OUT(v.a.mask), .cb = nlattr_get_in6_addr }, { .type = PF_AT_IFNAME, .off = _OUT(v.ifname), .arg = (void *)IFNAMSIZ,.cb = nlattr_get_chara }, { .type = PF_AT_TABLENAME, .off = _OUT(v.tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_AT_TYPE, .off = _OUT(type), .cb = nlattr_get_uint8 }, { .type = PF_AT_IFLAGS, .off = _OUT(iflags), .cb = nlattr_get_uint8 }, }; NL_DECLARE_ATTR_PARSER(addr_wrap_parser, nla_p_addr_wrap); #undef _OUT static bool nlattr_add_addr_wrap(struct nl_writer *nw, int attrtype, struct pf_addr_wrap *a) { int off = nlattr_add_nested(nw, attrtype); int num; nlattr_add_in6_addr(nw, PF_AT_ADDR, &a->v.a.addr.v6); nlattr_add_in6_addr(nw, PF_AT_MASK, &a->v.a.mask.v6); nlattr_add_u8(nw, PF_AT_TYPE, a->type); nlattr_add_u8(nw, PF_AT_IFLAGS, a->iflags); if (a->type == PF_ADDR_DYNIFTL) { nlattr_add_string(nw, PF_AT_IFNAME, a->v.ifname); num = 0; if (a->p.dyn != NULL) num = a->p.dyn->pfid_acnt4 + a->p.dyn->pfid_acnt6; nlattr_add_u32(nw, PF_AT_DYNCNT, num); } else if (a->type == PF_ADDR_TABLE) { struct pfr_ktable *kt; nlattr_add_string(nw, PF_AT_TABLENAME, a->v.tblname); num = -1; kt = a->p.tbl; if ((kt->pfrkt_flags & PFR_TFLAG_ACTIVE) && kt->pfrkt_root != NULL) kt = kt->pfrkt_root; if (kt->pfrkt_flags & PFR_TFLAG_ACTIVE) num = kt->pfrkt_cnt; nlattr_add_u32(nw, PF_AT_TBLCNT, num); } nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_rule_addr, _field) static const struct nlattr_parser nla_p_ruleaddr[] = { { .type = PF_RAT_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = nlattr_get_nested }, { .type = PF_RAT_SRC_PORT, .off = _OUT(port[0]), .cb = nlattr_get_uint16 }, { .type = PF_RAT_DST_PORT, .off = _OUT(port[1]), .cb = nlattr_get_uint16 }, { .type = PF_RAT_NEG, .off = _OUT(neg), .cb = nlattr_get_uint8 }, { .type = PF_RAT_OP, .off = _OUT(port_op), .cb = nlattr_get_uint8 }, }; NL_DECLARE_ATTR_PARSER(rule_addr_parser, nla_p_ruleaddr); #undef _OUT static bool nlattr_add_rule_addr(struct nl_writer *nw, int attrtype, struct pf_rule_addr *r) { int off = nlattr_add_nested(nw, attrtype); nlattr_add_addr_wrap(nw, PF_RAT_ADDR, &r->addr); nlattr_add_u16(nw, PF_RAT_SRC_PORT, r->port[0]); nlattr_add_u16(nw, PF_RAT_DST_PORT, r->port[1]); nlattr_add_u8(nw, PF_RAT_NEG, r->neg); nlattr_add_u8(nw, PF_RAT_OP, r->port_op); nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_mape_portset, _field) static const struct nlattr_parser nla_p_mape_portset[] = { { .type = PF_MET_OFFSET, .off = _OUT(offset), .cb = nlattr_get_uint8 }, { .type = PF_MET_PSID_LEN, .off = _OUT(psidlen), .cb = nlattr_get_uint8 }, {. type = PF_MET_PSID, .off = _OUT(psid), .cb = nlattr_get_uint16 }, }; NL_DECLARE_ATTR_PARSER(mape_portset_parser, nla_p_mape_portset); #undef _OUT static bool nlattr_add_mape_portset(struct nl_writer *nw, int attrtype, const struct pf_mape_portset *m) { int off = nlattr_add_nested(nw, attrtype); nlattr_add_u8(nw, PF_MET_OFFSET, m->offset); nlattr_add_u8(nw, PF_MET_PSID_LEN, m->psidlen); nlattr_add_u16(nw, PF_MET_PSID, m->psid); nlattr_set_len(nw, off); return (true); } struct nl_parsed_labels { char labels[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t i; }; static int nlattr_get_pf_rule_labels(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { struct nl_parsed_labels *l = (struct nl_parsed_labels *)target; int ret; if (l->i >= PF_RULE_MAX_LABEL_COUNT) return (E2BIG); ret = nlattr_get_chara(nla, npt, (void *)PF_RULE_LABEL_SIZE, l->labels[l->i]); if (ret == 0) l->i++; return (ret); } #define _OUT(_field) offsetof(struct nl_parsed_labels, _field) static const struct nlattr_parser nla_p_labels[] = { { .type = PF_LT_LABEL, .off = 0, .cb = nlattr_get_pf_rule_labels }, }; NL_DECLARE_ATTR_PARSER(rule_labels_parser, nla_p_labels); #undef _OUT static int nlattr_get_nested_pf_rule_labels(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { struct nl_parsed_labels parsed_labels = { }; int error; /* Assumes target points to the beginning of the structure */ error = nl_parse_header(NLA_DATA(nla), NLA_DATA_LEN(nla), &rule_labels_parser, npt, &parsed_labels); if (error != 0) return (error); memcpy(target, parsed_labels.labels, sizeof(parsed_labels)); return (0); } static bool nlattr_add_labels(struct nl_writer *nw, int attrtype, const struct pf_krule *r) { int off = nlattr_add_nested(nw, attrtype); int i = 0; while (r->label[i][0] != 0 && i < PF_RULE_MAX_LABEL_COUNT) { nlattr_add_string(nw, PF_LT_LABEL, r->label[i]); i++; } nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_kpool, _field) static const struct nlattr_parser nla_p_pool[] = { { .type = PF_PT_KEY, .off = _OUT(key), .arg = (void *)sizeof(struct pf_poolhashkey), .cb = nlattr_get_bytes }, { .type = PF_PT_COUNTER, .off = _OUT(counter), .cb = nlattr_get_in6_addr }, { .type = PF_PT_TBLIDX, .off = _OUT(tblidx), .cb = nlattr_get_uint32 }, { .type = PF_PT_PROXY_SRC_PORT, .off = _OUT(proxy_port[0]), .cb = nlattr_get_uint16 }, { .type = PF_PT_PROXY_DST_PORT, .off = _OUT(proxy_port[1]), .cb = nlattr_get_uint16 }, { .type = PF_PT_OPTS, .off = _OUT(opts), .cb = nlattr_get_uint8 }, { .type = PF_PT_MAPE, .off = _OUT(mape), .arg = &mape_portset_parser, .cb = nlattr_get_nested }, }; NL_DECLARE_ATTR_PARSER(pool_parser, nla_p_pool); #undef _OUT static bool nlattr_add_pool(struct nl_writer *nw, int attrtype, const struct pf_kpool *pool) { int off = nlattr_add_nested(nw, attrtype); nlattr_add(nw, PF_PT_KEY, sizeof(struct pf_poolhashkey), &pool->key); nlattr_add_in6_addr(nw, PF_PT_COUNTER, (const struct in6_addr *)&pool->counter); nlattr_add_u32(nw, PF_PT_TBLIDX, pool->tblidx); nlattr_add_u16(nw, PF_PT_PROXY_SRC_PORT, pool->proxy_port[0]); nlattr_add_u16(nw, PF_PT_PROXY_DST_PORT, pool->proxy_port[1]); nlattr_add_u8(nw, PF_PT_OPTS, pool->opts); nlattr_add_mape_portset(nw, PF_PT_MAPE, &pool->mape); nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_rule_uid, _field) static const struct nlattr_parser nla_p_rule_uid[] = { { .type = PF_RUT_UID_LOW, .off = _OUT(uid[0]), .cb = nlattr_get_uint32 }, { .type = PF_RUT_UID_HIGH, .off = _OUT(uid[1]), .cb = nlattr_get_uint32 }, { .type = PF_RUT_OP, .off = _OUT(op), .cb = nlattr_get_uint8 }, }; NL_DECLARE_ATTR_PARSER(rule_uid_parser, nla_p_rule_uid); #undef _OUT static bool nlattr_add_rule_uid(struct nl_writer *nw, int attrtype, const struct pf_rule_uid *u) { int off = nlattr_add_nested(nw, attrtype); nlattr_add_u32(nw, PF_RUT_UID_LOW, u->uid[0]); nlattr_add_u32(nw, PF_RUT_UID_HIGH, u->uid[1]); nlattr_add_u8(nw, PF_RUT_OP, u->op); nlattr_set_len(nw, off); return (true); } struct nl_parsed_timeouts { uint32_t timeouts[PFTM_MAX]; uint32_t i; }; static int nlattr_get_pf_timeout(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { struct nl_parsed_timeouts *t = (struct nl_parsed_timeouts *)target; int ret; if (t->i >= PFTM_MAX) return (E2BIG); ret = nlattr_get_uint32(nla, npt, NULL, &t->timeouts[t->i]); if (ret == 0) t->i++; return (ret); } #define _OUT(_field) offsetof(struct nl_parsed_timeout, _field) static const struct nlattr_parser nla_p_timeouts[] = { { .type = PF_TT_TIMEOUT, .off = 0, .cb = nlattr_get_pf_timeout }, }; NL_DECLARE_ATTR_PARSER(timeout_parser, nla_p_timeouts); #undef _OUT static int nlattr_get_nested_timeouts(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { struct nl_parsed_timeouts parsed_timeouts = { }; int error; /* Assumes target points to the beginning of the structure */ error = nl_parse_header(NLA_DATA(nla), NLA_DATA_LEN(nla), &timeout_parser, npt, &parsed_timeouts); if (error != 0) return (error); memcpy(target, parsed_timeouts.timeouts, sizeof(parsed_timeouts.timeouts)); return (0); } static bool nlattr_add_timeout(struct nl_writer *nw, int attrtype, uint32_t *timeout) { int off = nlattr_add_nested(nw, attrtype); for (int i = 0; i < PFTM_MAX; i++) nlattr_add_u32(nw, PF_RT_TIMEOUT, timeout[i]); nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_krule, _field) static const struct nlattr_parser nla_p_rule[] = { { .type = PF_RT_SRC, .off = _OUT(src), .arg = &rule_addr_parser,.cb = nlattr_get_nested }, { .type = PF_RT_DST, .off = _OUT(dst), .arg = &rule_addr_parser,.cb = nlattr_get_nested }, { .type = PF_RT_RIDENTIFIER, .off = _OUT(ridentifier), .cb = nlattr_get_uint32 }, { .type = PF_RT_LABELS, .off = _OUT(label), .arg = &rule_labels_parser,.cb = nlattr_get_nested_pf_rule_labels }, { .type = PF_RT_IFNAME, .off = _OUT(ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara }, { .type = PF_RT_QNAME, .off = _OUT(qname), .arg = (void *)PF_QNAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_RT_PQNAME, .off = _OUT(pqname), .arg = (void *)PF_QNAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_RT_TAGNAME, .off = _OUT(tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_RT_MATCH_TAGNAME, .off = _OUT(match_tagname), .arg = (void *)PF_TAG_NAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_RT_OVERLOAD_TBLNAME, .off = _OUT(overload_tblname), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_RT_RPOOL, .off = _OUT(rpool), .arg = &pool_parser, .cb = nlattr_get_nested }, { .type = PF_RT_OS_FINGERPRINT, .off = _OUT(os_fingerprint), .cb = nlattr_get_uint32 }, { .type = PF_RT_RTABLEID, .off = _OUT(rtableid), .cb = nlattr_get_uint32 }, { .type = PF_RT_TIMEOUT, .off = _OUT(timeout), .arg = &timeout_parser, .cb = nlattr_get_nested_timeouts }, { .type = PF_RT_MAX_STATES, .off = _OUT(max_states), .cb = nlattr_get_uint32 }, { .type = PF_RT_MAX_SRC_NODES, .off = _OUT(max_src_nodes), .cb = nlattr_get_uint32 }, { .type = PF_RT_MAX_SRC_STATES, .off = _OUT(max_src_states), .cb = nlattr_get_uint32 }, { .type = PF_RT_MAX_SRC_CONN_RATE_LIMIT, .off = _OUT(max_src_conn_rate.limit), .cb = nlattr_get_uint32 }, { .type = PF_RT_MAX_SRC_CONN_RATE_SECS, .off = _OUT(max_src_conn_rate.seconds), .cb = nlattr_get_uint32 }, { .type = PF_RT_DNPIPE, .off = _OUT(dnpipe), .cb = nlattr_get_uint16 }, { .type = PF_RT_DNRPIPE, .off = _OUT(dnrpipe), .cb = nlattr_get_uint16 }, { .type = PF_RT_DNFLAGS, .off = _OUT(free_flags), .cb = nlattr_get_uint32 }, { .type = PF_RT_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 }, { .type = PF_RT_PROB, .off = _OUT(prob), .cb = nlattr_get_uint32 }, { .type = PF_RT_CUID, .off = _OUT(cuid), .cb = nlattr_get_uint32 }, {. type = PF_RT_CPID, .off = _OUT(cpid), .cb = nlattr_get_uint32 }, { .type = PF_RT_RETURN_ICMP, .off = _OUT(return_icmp), .cb = nlattr_get_uint16 }, { .type = PF_RT_RETURN_ICMP6, .off = _OUT(return_icmp6), .cb = nlattr_get_uint16 }, { .type = PF_RT_MAX_MSS, .off = _OUT(max_mss), .cb = nlattr_get_uint16 }, { .type = PF_RT_SCRUB_FLAGS, .off = _OUT(scrub_flags), .cb = nlattr_get_uint16 }, { .type = PF_RT_UID, .off = _OUT(uid), .arg = &rule_uid_parser, .cb = nlattr_get_nested }, { .type = PF_RT_GID, .off = _OUT(gid), .arg = &rule_uid_parser, .cb = nlattr_get_nested }, { .type = PF_RT_RULE_FLAG, .off = _OUT(rule_flag), .cb = nlattr_get_uint32 }, { .type = PF_RT_ACTION, .off = _OUT(action), .cb = nlattr_get_uint8 }, { .type = PF_RT_DIRECTION, .off = _OUT(direction), .cb = nlattr_get_uint8 }, { .type = PF_RT_LOG, .off = _OUT(log), .cb = nlattr_get_uint8 }, { .type = PF_RT_LOGIF, .off = _OUT(logif), .cb = nlattr_get_uint8 }, { .type = PF_RT_QUICK, .off = _OUT(quick), .cb = nlattr_get_uint8 }, { .type = PF_RT_IF_NOT, .off = _OUT(ifnot), .cb = nlattr_get_uint8 }, { .type = PF_RT_MATCH_TAG_NOT, .off = _OUT(match_tag_not), .cb = nlattr_get_uint8 }, { .type = PF_RT_NATPASS, .off = _OUT(natpass), .cb = nlattr_get_uint8 }, { .type = PF_RT_KEEP_STATE, .off = _OUT(keep_state), .cb = nlattr_get_uint8 }, { .type = PF_RT_AF, .off = _OUT(af), .cb = nlattr_get_uint8 }, { .type = PF_RT_PROTO, .off = _OUT(proto), .cb = nlattr_get_uint8 }, { .type = PF_RT_TYPE, .off = _OUT(type), .cb = nlattr_get_uint8 }, { .type = PF_RT_CODE, .off = _OUT(code), .cb = nlattr_get_uint8 }, { .type = PF_RT_FLAGS, .off = _OUT(flags), .cb = nlattr_get_uint8 }, { .type = PF_RT_FLAGSET, .off = _OUT(flagset), .cb = nlattr_get_uint8 }, { .type = PF_RT_MIN_TTL, .off = _OUT(min_ttl), .cb = nlattr_get_uint8 }, { .type = PF_RT_ALLOW_OPTS, .off = _OUT(allow_opts), .cb = nlattr_get_uint8 }, { .type = PF_RT_RT, .off = _OUT(rt), .cb = nlattr_get_uint8 }, { .type = PF_RT_RETURN_TTL, .off = _OUT(return_ttl), .cb = nlattr_get_uint8 }, { .type = PF_RT_TOS, .off = _OUT(tos), .cb = nlattr_get_uint8 }, { .type = PF_RT_SET_TOS, .off = _OUT(set_tos), .cb = nlattr_get_uint8 }, { .type = PF_RT_ANCHOR_RELATIVE, .off = _OUT(anchor_relative), .cb = nlattr_get_uint8 }, { .type = PF_RT_ANCHOR_WILDCARD, .off = _OUT(anchor_wildcard), .cb = nlattr_get_uint8 }, { .type = PF_RT_FLUSH, .off = _OUT(flush), .cb = nlattr_get_uint8 }, { .type = PF_RT_PRIO, .off = _OUT(prio), .cb = nlattr_get_uint8 }, { .type = PF_RT_SET_PRIO, .off = _OUT(set_prio[0]), .cb = nlattr_get_uint8 }, { .type = PF_RT_SET_PRIO_REPLY, .off = _OUT(set_prio[1]), .cb = nlattr_get_uint8 }, { .type = PF_RT_DIVERT_ADDRESS, .off = _OUT(divert.addr), .cb = nlattr_get_in6_addr }, { .type = PF_RT_DIVERT_PORT, .off = _OUT(divert.port), .cb = nlattr_get_uint16 }, }; NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule); #undef _OUT struct nl_parsed_addrule { struct pf_krule *rule; uint32_t ticket; uint32_t pool_ticket; char *anchor; char *anchor_call; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct nl_parsed_addrule, _field) static const struct nlattr_parser nla_p_addrule[] = { { .type = PF_ART_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 }, { .type = PF_ART_POOL_TICKET, .off = _OUT(pool_ticket), .cb = nlattr_get_uint32 }, { .type = PF_ART_ANCHOR, .off = _OUT(anchor), .cb = nlattr_get_string }, { .type = PF_ART_ANCHOR_CALL, .off = _OUT(anchor_call), .cb = nlattr_get_string }, { .type = PF_ART_RULE, .off = _OUT(rule), .arg = &rule_parser, .cb = nlattr_get_nested_ptr } }; static const struct nlfield_parser nlf_p_addrule[] = { }; #undef _IN #undef _OUT NL_DECLARE_PARSER(addrule_parser, struct genlmsghdr, nlf_p_addrule, nla_p_addrule); static int pf_handle_addrule(struct nlmsghdr *hdr, struct nl_pstate *npt) { int error; struct nl_parsed_addrule attrs = {}; attrs.rule = pf_krule_alloc(); error = nl_parse_nlmsg(hdr, &addrule_parser, npt, &attrs); if (error != 0) { pf_free_rule(attrs.rule); return (error); } error = pf_ioctl_addrule(attrs.rule, attrs.ticket, attrs.pool_ticket, attrs.anchor, attrs.anchor_call, nlp_get_cred(npt->nlp)->cr_uid, hdr->nlmsg_pid); return (error); } #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfioc_rule, _field) static const struct nlattr_parser nla_p_getrules[] = { { .type = PF_GR_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara }, { .type = PF_GR_ACTION, .off = _OUT(rule.action), .cb = nlattr_get_uint8 }, }; static const struct nlfield_parser nlf_p_getrules[] = { }; #undef _IN #undef _OUT NL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, nlf_p_getrules, nla_p_getrules); static int pf_handle_getrules(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_rule attrs = {}; int error; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; error = nl_parse_nlmsg(hdr, &getrules_parser, npt, &attrs); if (error != 0) return (error); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GETRULES; ghdr_new->version = 0; ghdr_new->reserved = 0; error = pf_ioctl_getrules(&attrs); if (error != 0) goto out; nlattr_add_u32(nw, PF_GR_NR, attrs.nr); nlattr_add_u32(nw, PF_GR_TICKET, attrs.ticket); if (!nlmsg_end(nw)) { error = ENOMEM; goto out; } return (0); out: nlmsg_abort(nw); return (error); } struct nl_parsed_get_rule { char anchor[MAXPATHLEN]; uint8_t action; uint32_t nr; uint32_t ticket; uint8_t clear; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct nl_parsed_get_rule, _field) static const struct nlattr_parser nla_p_getrule[] = { { .type = PF_GR_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara }, { .type = PF_GR_ACTION, .off = _OUT(action), .cb = nlattr_get_uint8 }, { .type = PF_GR_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 }, { .type = PF_GR_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 }, { .type = PF_GR_CLEAR, .off = _OUT(clear), .cb = nlattr_get_uint8 }, }; static const struct nlfield_parser nlf_p_getrule[] = { }; #undef _IN #undef _OUT NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_getrule, nla_p_getrule); static int pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt) { char anchor_call[MAXPATHLEN]; struct nl_parsed_get_rule attrs = {}; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; struct pf_kruleset *ruleset; struct pf_krule *rule; int rs_num; int error; error = nl_parse_nlmsg(hdr, &getrule_parser, npt, &attrs); if (error != 0) return (error); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GETRULE; ghdr_new->version = 0; ghdr_new->reserved = 0; PF_RULES_WLOCK(); ruleset = pf_find_kruleset(attrs.anchor); if (ruleset == NULL) { PF_RULES_WUNLOCK(); error = ENOENT; goto out; } rs_num = pf_get_ruleset_number(attrs.action); if (rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); error = EINVAL; goto out; } if (attrs.ticket != ruleset->rules[rs_num].active.ticket) { PF_RULES_WUNLOCK(); error = EBUSY; goto out; } rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); while ((rule != NULL) && (rule->nr != attrs.nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { PF_RULES_WUNLOCK(); error = EBUSY; goto out; } nlattr_add_rule_addr(nw, PF_RT_SRC, &rule->src); nlattr_add_rule_addr(nw, PF_RT_DST, &rule->dst); nlattr_add_u32(nw, PF_RT_RIDENTIFIER, rule->ridentifier); nlattr_add_labels(nw, PF_RT_LABELS, rule); nlattr_add_string(nw, PF_RT_IFNAME, rule->ifname); nlattr_add_string(nw, PF_RT_QNAME, rule->qname); nlattr_add_string(nw, PF_RT_PQNAME, rule->pqname); nlattr_add_string(nw, PF_RT_TAGNAME, rule->tagname); nlattr_add_string(nw, PF_RT_MATCH_TAGNAME, rule->match_tagname); nlattr_add_string(nw, PF_RT_OVERLOAD_TBLNAME, rule->overload_tblname); nlattr_add_pool(nw, PF_RT_RPOOL, &rule->rpool); nlattr_add_u32(nw, PF_RT_OS_FINGERPRINT, rule->os_fingerprint); nlattr_add_u32(nw, PF_RT_RTABLEID, rule->rtableid); nlattr_add_timeout(nw, PF_RT_TIMEOUT, rule->timeout); nlattr_add_u32(nw, PF_RT_MAX_STATES, rule->max_states); nlattr_add_u32(nw, PF_RT_MAX_SRC_NODES, rule->max_src_nodes); nlattr_add_u32(nw, PF_RT_MAX_SRC_STATES, rule->max_src_states); nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_LIMIT, rule->max_src_conn_rate.limit); nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_SECS, rule->max_src_conn_rate.seconds); nlattr_add_u16(nw, PF_RT_DNPIPE, rule->dnpipe); nlattr_add_u16(nw, PF_RT_DNRPIPE, rule->dnrpipe); nlattr_add_u32(nw, PF_RT_DNFLAGS, rule->free_flags); nlattr_add_u32(nw, PF_RT_NR, rule->nr); nlattr_add_u32(nw, PF_RT_PROB, rule->prob); nlattr_add_u32(nw, PF_RT_CUID, rule->cuid); nlattr_add_u32(nw, PF_RT_CPID, rule->cpid); nlattr_add_u16(nw, PF_RT_RETURN_ICMP, rule->return_icmp); nlattr_add_u16(nw, PF_RT_RETURN_ICMP6, rule->return_icmp6); nlattr_add_u16(nw, PF_RT_RETURN_ICMP6, rule->return_icmp6); nlattr_add_u16(nw, PF_RT_MAX_MSS, rule->max_mss); nlattr_add_u16(nw, PF_RT_SCRUB_FLAGS, rule->scrub_flags); nlattr_add_rule_uid(nw, PF_RT_UID, &rule->uid); nlattr_add_rule_uid(nw, PF_RT_GID, (const struct pf_rule_uid *)&rule->gid); nlattr_add_u32(nw, PF_RT_RULE_FLAG, rule->rule_flag); nlattr_add_u8(nw, PF_RT_ACTION, rule->action); nlattr_add_u8(nw, PF_RT_DIRECTION, rule->direction); nlattr_add_u8(nw, PF_RT_LOG, rule->log); nlattr_add_u8(nw, PF_RT_LOGIF, rule->logif); nlattr_add_u8(nw, PF_RT_QUICK, rule->quick); nlattr_add_u8(nw, PF_RT_IF_NOT, rule->ifnot); nlattr_add_u8(nw, PF_RT_MATCH_TAG_NOT, rule->match_tag_not); nlattr_add_u8(nw, PF_RT_NATPASS, rule->natpass); nlattr_add_u8(nw, PF_RT_KEEP_STATE, rule->keep_state); nlattr_add_u8(nw, PF_RT_AF, rule->af); nlattr_add_u8(nw, PF_RT_PROTO, rule->proto); nlattr_add_u8(nw, PF_RT_TYPE, rule->type); nlattr_add_u8(nw, PF_RT_CODE, rule->code); nlattr_add_u8(nw, PF_RT_FLAGS, rule->flags); nlattr_add_u8(nw, PF_RT_FLAGSET, rule->flagset); nlattr_add_u8(nw, PF_RT_MIN_TTL, rule->min_ttl); nlattr_add_u8(nw, PF_RT_ALLOW_OPTS, rule->allow_opts); nlattr_add_u8(nw, PF_RT_RT, rule->rt); nlattr_add_u8(nw, PF_RT_RETURN_TTL, rule->return_ttl); nlattr_add_u8(nw, PF_RT_TOS, rule->tos); nlattr_add_u8(nw, PF_RT_SET_TOS, rule->set_tos); nlattr_add_u8(nw, PF_RT_ANCHOR_RELATIVE, rule->anchor_relative); nlattr_add_u8(nw, PF_RT_ANCHOR_WILDCARD, rule->anchor_wildcard); nlattr_add_u8(nw, PF_RT_FLUSH, rule->flush); nlattr_add_u8(nw, PF_RT_PRIO, rule->prio); nlattr_add_u8(nw, PF_RT_SET_PRIO, rule->set_prio[0]); nlattr_add_u8(nw, PF_RT_SET_PRIO_REPLY, rule->set_prio[1]); nlattr_add_in6_addr(nw, PF_RT_DIVERT_ADDRESS, &rule->divert.addr.v6); nlattr_add_u16(nw, PF_RT_DIVERT_PORT, rule->divert.port); nlattr_add_u64(nw, PF_RT_PACKETS_IN, pf_counter_u64_fetch(&rule->packets[0])); nlattr_add_u64(nw, PF_RT_PACKETS_OUT, pf_counter_u64_fetch(&rule->packets[1])); nlattr_add_u64(nw, PF_RT_BYTES_IN, pf_counter_u64_fetch(&rule->bytes[0])); nlattr_add_u64(nw, PF_RT_BYTES_OUT, pf_counter_u64_fetch(&rule->bytes[1])); nlattr_add_u64(nw, PF_RT_EVALUATIONS, pf_counter_u64_fetch(&rule->evaluations)); nlattr_add_u64(nw, PF_RT_TIMESTAMP, pf_get_timestamp(rule)); nlattr_add_u64(nw, PF_RT_STATES_CUR, counter_u64_fetch(rule->states_cur)); nlattr_add_u64(nw, PF_RT_STATES_TOTAL, counter_u64_fetch(rule->states_tot)); nlattr_add_u64(nw, PF_RT_SRC_NODES, counter_u64_fetch(rule->src_nodes)); error = pf_kanchor_copyout(ruleset, rule, anchor_call); MPASS(error == 0); nlattr_add_string(nw, PF_RT_ANCHOR_CALL, anchor_call); if (attrs.clear) pf_krule_clear_counters(rule); PF_RULES_WUNLOCK(); if (!nlmsg_end(nw)) { error = ENOMEM; goto out; } return (0); out: nlmsg_abort(nw); return (error); } #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pf_kstate_kill, _field) static const struct nlattr_parser nla_p_clear_states[] = { { .type = PF_CS_CMP_ID, .off = _OUT(psk_pfcmp.id), .cb = nlattr_get_uint64 }, { .type = PF_CS_CMP_CREATORID, .off = _OUT(psk_pfcmp.creatorid), .cb = nlattr_get_uint32 }, { .type = PF_CS_CMP_DIR, .off = _OUT(psk_pfcmp.direction), .cb = nlattr_get_uint8 }, { .type = PF_CS_AF, .off = _OUT(psk_af), .cb = nlattr_get_uint8 }, { .type = PF_CS_PROTO, .off = _OUT(psk_proto), .cb = nlattr_get_uint8 }, { .type = PF_CS_SRC, .off = _OUT(psk_src), .arg = &rule_addr_parser, .cb = nlattr_get_nested }, { .type = PF_CS_DST, .off = _OUT(psk_dst), .arg = &rule_addr_parser, .cb = nlattr_get_nested }, { .type = PF_CS_RT_ADDR, .off = _OUT(psk_rt_addr), .arg = &rule_addr_parser, .cb = nlattr_get_nested }, { .type = PF_CS_IFNAME, .off = _OUT(psk_ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara }, { .type = PF_CS_LABEL, .off = _OUT(psk_label), .arg = (void *)PF_RULE_LABEL_SIZE, .cb = nlattr_get_chara }, { .type = PF_CS_KILL_MATCH, .off = _OUT(psk_kill_match), .cb = nlattr_get_bool }, { .type = PF_CS_NAT, .off = _OUT(psk_nat), .cb = nlattr_get_bool }, }; static const struct nlfield_parser nlf_p_clear_states[] = {}; #undef _IN #undef _OUT NL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, nlf_p_clear_states, nla_p_clear_states); static int pf_handle_killclear_states(struct nlmsghdr *hdr, struct nl_pstate *npt, int cmd) { struct pf_kstate_kill kill = {}; struct epoch_tracker et; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; unsigned int killed = 0; error = nl_parse_nlmsg(hdr, &clear_states_parser, npt, &kill); if (error != 0) return (error); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = cmd; ghdr_new->version = 0; ghdr_new->reserved = 0; NET_EPOCH_ENTER(et); if (cmd == PFNL_CMD_KILLSTATES) pf_killstates(&kill, &killed); else killed = pf_clear_states(&kill); NET_EPOCH_EXIT(et); nlattr_add_u32(nw, PF_CS_KILLED, killed); if (! nlmsg_end(nw)) { error = ENOMEM; goto out; } return (0); out: nlmsg_abort(nw); return (error); } static int pf_handle_clear_states(struct nlmsghdr *hdr, struct nl_pstate *npt) { return (pf_handle_killclear_states(hdr, npt, PFNL_CMD_CLRSTATES)); } static int pf_handle_kill_states(struct nlmsghdr *hdr, struct nl_pstate *npt) { return (pf_handle_killclear_states(hdr, npt, PFNL_CMD_KILLSTATES)); } +struct nl_parsed_set_statusif { + char ifname[IFNAMSIZ]; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct nl_parsed_set_statusif, _field) +static const struct nlattr_parser nla_p_set_statusif[] = { + { .type = PF_SS_IFNAME, .off = _OUT(ifname), .arg = (const void *)IFNAMSIZ, .cb = nlattr_get_chara }, +}; +static const struct nlfield_parser nlf_p_set_statusif[] = {}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(set_statusif_parser, struct genlmsghdr, nlf_p_set_statusif, nla_p_set_statusif); + +static int +pf_handle_set_statusif(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + int error; + struct nl_parsed_set_statusif attrs = {}; + + error = nl_parse_nlmsg(hdr, &set_statusif_parser, npt, &attrs); + if (error != 0) + return (error); + + PF_RULES_WLOCK(); + strlcpy(V_pf_status.ifname, attrs.ifname, IFNAMSIZ); + PF_RULES_WUNLOCK(); + + return (0); +} + static const struct nlhdr_parser *all_parsers[] = { &state_parser, &addrule_parser, &getrules_parser, &clear_states_parser, + &set_statusif_parser, }; static int family_id; static const struct genl_cmd pf_cmds[] = { { .cmd_num = PFNL_CMD_GETSTATES, .cmd_name = "GETSTATES", .cmd_cb = pf_handle_getstates, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GETCREATORS, .cmd_name = "GETCREATORS", .cmd_cb = pf_handle_getcreators, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_START, .cmd_name = "START", .cmd_cb = pf_handle_start, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_STOP, .cmd_name = "STOP", .cmd_cb = pf_handle_stop, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_ADDRULE, .cmd_name = "ADDRULE", .cmd_cb = pf_handle_addrule, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GETRULES, .cmd_name = "GETRULES", .cmd_cb = pf_handle_getrules, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GETRULE, .cmd_name = "GETRULE", .cmd_cb = pf_handle_getrule, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_CLRSTATES, .cmd_name = "CLRSTATES", .cmd_cb = pf_handle_clear_states, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_KILLSTATES, .cmd_name = "KILLSTATES", .cmd_cb = pf_handle_kill_states, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, + { + .cmd_num = PFNL_CMD_SET_STATUSIF, + .cmd_name = "SETSTATUSIF", + .cmd_cb = pf_handle_set_statusif, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + } }; void pf_nl_register(void) { NL_VERIFY_PARSERS(all_parsers); family_id = genl_register_family(PFNL_FAMILY_NAME, 0, 2, PFNL_CMD_MAX); genl_register_cmds(PFNL_FAMILY_NAME, pf_cmds, NL_ARRAY_LEN(pf_cmds)); } void pf_nl_unregister(void) { genl_unregister_family(PFNL_FAMILY_NAME); } diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h index 0ae4a9283072..c44e331722b7 100644 --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -1,291 +1,296 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Alexander V. Chernikov * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef _NETPFIL_PF_PF_NL_H_ #define _NETPFIL_PF_PF_NL_H_ /* Genetlink family */ #define PFNL_FAMILY_NAME "pfctl" /* available commands */ enum { PFNL_CMD_UNSPEC = 0, PFNL_CMD_GETSTATES = 1, PFNL_CMD_GETCREATORS = 2, PFNL_CMD_START = 3, PFNL_CMD_STOP = 4, PFNL_CMD_ADDRULE = 5, PFNL_CMD_GETRULES = 6, PFNL_CMD_GETRULE = 7, PFNL_CMD_CLRSTATES = 8, PFNL_CMD_KILLSTATES = 9, + PFNL_CMD_SET_STATUSIF = 10, __PFNL_CMD_MAX, }; #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1) enum pfstate_key_type_t { PF_STK_UNSPEC, PF_STK_ADDR0 = 1, /* ip */ PF_STK_ADDR1 = 2, /* ip */ PF_STK_PORT0 = 3, /* u16 */ PF_STK_PORT1 = 4, /* u16 */ }; enum pfstate_peer_type_t { PF_STP_UNSPEC, PF_STP_PFSS_FLAGS = 1, /* u16 */ PF_STP_PFSS_TTL = 2, /* u8 */ PF_STP_SCRUB_FLAG = 3, /* u8 */ PF_STP_PFSS_TS_MOD = 4, /* u32 */ PF_STP_SEQLO = 5, /* u32 */ PF_STP_SEQHI = 6, /* u32 */ PF_STP_SEQDIFF = 7, /* u32 */ PF_STP_MAX_WIN = 8, /* u16 */ PF_STP_MSS = 9, /* u16 */ PF_STP_STATE = 10, /* u8 */ PF_STP_WSCALE = 11, /* u8 */ }; enum pfstate_type_t { PF_ST_UNSPEC, PF_ST_ID = 1, /* u32, state id */ PF_ST_CREATORID = 2, /* u32, */ PF_ST_IFNAME = 3, /* string */ PF_ST_ORIG_IFNAME = 4, /* string */ PF_ST_KEY_WIRE = 5, /* nested, pfstate_key_type_t */ PF_ST_KEY_STACK = 6, /* nested, pfstate_key_type_t */ PF_ST_PEER_SRC = 7, /* nested, pfstate_peer_type_t*/ PF_ST_PEER_DST = 8, /* nested, pfstate_peer_type_t */ PF_ST_RT_ADDR = 9, /* ip */ PF_ST_RULE = 10, /* u32 */ PF_ST_ANCHOR = 11, /* u32 */ PF_ST_NAT_RULE = 12, /* u32 */ PF_ST_CREATION = 13, /* u32 */ PF_ST_EXPIRE = 14, /* u32 */ PF_ST_PACKETS0 = 15, /* u64 */ PF_ST_PACKETS1 = 16, /* u64 */ PF_ST_BYTES0 = 17, /* u64 */ PF_ST_BYTES1 = 18, /* u64 */ PF_ST_AF = 19, /* u8 */ PF_ST_PROTO = 21, /* u8 */ PF_ST_DIRECTION = 22, /* u8 */ PF_ST_LOG = 23, /* u8 */ PF_ST_TIMEOUT = 24, /* u8 */ PF_ST_STATE_FLAGS = 25, /* u8 */ PF_ST_SYNC_FLAGS = 26, /* u8 */ PF_ST_UPDATES = 27, /* u8 */ PF_ST_VERSION = 28, /* u64 */ PF_ST_FILTER_ADDR = 29, /* in6_addr */ PF_ST_FILTER_MASK = 30, /* in6_addr */ PF_ST_RTABLEID = 31, /* i32 */ PF_ST_MIN_TTL = 32, /* u8 */ PF_ST_MAX_MSS = 33, /* u16 */ PF_ST_DNPIPE = 34, /* u16 */ PF_ST_DNRPIPE = 35, /* u16 */ PF_ST_RT = 36, /* u8 */ PF_ST_RT_IFNAME = 37, /* string */ }; enum pf_addr_type_t { PF_AT_UNSPEC, PF_AT_ADDR = 1, /* in6_addr */ PF_AT_MASK = 2, /* in6_addr */ PF_AT_IFNAME = 3, /* string */ PF_AT_TABLENAME = 4, /* string */ PF_AT_TYPE = 5, /* u8 */ PF_AT_IFLAGS = 6, /* u8 */ PF_AT_TBLCNT = 7, /* u32 */ PF_AT_DYNCNT = 8, /* u32 */ }; enum pfrule_addr_type_t { PF_RAT_UNSPEC, PF_RAT_ADDR = 1, /* nested, pf_addr_type_t */ PF_RAT_SRC_PORT = 2, /* u16 */ PF_RAT_DST_PORT = 3, /* u16 */ PF_RAT_NEG = 4, /* u8 */ PF_RAT_OP = 5, /* u8 */ }; enum pf_labels_type_t { PF_LT_UNSPEC, PF_LT_LABEL = 1, /* string */ }; enum pf_mape_portset_type_t { PF_MET_UNSPEC, PF_MET_OFFSET = 1, /* u8 */ PF_MET_PSID_LEN = 2, /* u8 */ PF_MET_PSID = 3, /* u16 */ }; enum pf_rpool_type_t { PF_PT_UNSPEC, PF_PT_KEY = 1, /* bytes, sizeof(struct pf_poolhashkey) */ PF_PT_COUNTER = 2, /* in6_addr */ PF_PT_TBLIDX = 3, /* u32 */ PF_PT_PROXY_SRC_PORT = 4, /* u16 */ PF_PT_PROXY_DST_PORT = 5, /* u16 */ PF_PT_OPTS = 6, /* u8 */ PF_PT_MAPE = 7, /* nested, pf_mape_portset_type_t */ }; enum pf_timeout_type_t { PF_TT_UNSPEC, PF_TT_TIMEOUT = 1, /* u32 */ }; enum pf_rule_uid_type_t { PF_RUT_UNSPEC, PF_RUT_UID_LOW = 1, /* u32 */ PF_RUT_UID_HIGH = 2, /* u32 */ PF_RUT_OP = 3, /* u8 */ }; enum pf_rule_type_t { PF_RT_UNSPEC, PF_RT_SRC = 1, /* nested, pf_rule_addr_type_t */ PF_RT_DST = 2, /* nested, pf_rule_addr_type_t */ PF_RT_RIDENTIFIER = 3, /* u32 */ PF_RT_LABELS = 4, /* nested, pf_labels_type_t */ PF_RT_IFNAME = 5, /* string */ PF_RT_QNAME = 6, /* string */ PF_RT_PQNAME = 7, /* string */ PF_RT_TAGNAME = 8, /* string */ PF_RT_MATCH_TAGNAME = 9, /* string */ PF_RT_OVERLOAD_TBLNAME = 10, /* string */ PF_RT_RPOOL = 11, /* nested, pf_rpool_type_t */ PF_RT_OS_FINGERPRINT = 12, /* u32 */ PF_RT_RTABLEID = 13, /* u32 */ PF_RT_TIMEOUT = 14, /* nested, pf_timeout_type_t */ PF_RT_MAX_STATES = 15, /* u32 */ PF_RT_MAX_SRC_NODES = 16, /* u32 */ PF_RT_MAX_SRC_STATES = 17, /* u32 */ PF_RT_MAX_SRC_CONN_RATE_LIMIT = 18, /* u32 */ PF_RT_MAX_SRC_CONN_RATE_SECS = 19, /* u32 */ PF_RT_DNPIPE = 20, /* u16 */ PF_RT_DNRPIPE = 21, /* u16 */ PF_RT_DNFLAGS = 22, /* u32 */ PF_RT_NR = 23, /* u32 */ PF_RT_PROB = 24, /* u32 */ PF_RT_CUID = 25, /* u32 */ PF_RT_CPID = 26, /* u32 */ PF_RT_RETURN_ICMP = 27, /* u16 */ PF_RT_RETURN_ICMP6 = 28, /* u16 */ PF_RT_MAX_MSS = 29, /* u16 */ PF_RT_SCRUB_FLAGS = 30, /* u16 */ PF_RT_UID = 31, /* nested, pf_rule_uid_type_t */ PF_RT_GID = 32, /* nested, pf_rule_uid_type_t */ PF_RT_RULE_FLAG = 33, /* u32 */ PF_RT_ACTION = 34, /* u8 */ PF_RT_DIRECTION = 35, /* u8 */ PF_RT_LOG = 36, /* u8 */ PF_RT_LOGIF = 37, /* u8 */ PF_RT_QUICK = 38, /* u8 */ PF_RT_IF_NOT = 39, /* u8 */ PF_RT_MATCH_TAG_NOT = 40, /* u8 */ PF_RT_NATPASS = 41, /* u8 */ PF_RT_KEEP_STATE = 42, /* u8 */ PF_RT_AF = 43, /* u8 */ PF_RT_PROTO = 44, /* u8 */ PF_RT_TYPE = 45, /* u8 */ PF_RT_CODE = 46, /* u8 */ PF_RT_FLAGS = 47, /* u8 */ PF_RT_FLAGSET = 48, /* u8 */ PF_RT_MIN_TTL = 49, /* u8 */ PF_RT_ALLOW_OPTS = 50, /* u8 */ PF_RT_RT = 51, /* u8 */ PF_RT_RETURN_TTL = 52, /* u8 */ PF_RT_TOS = 53, /* u8 */ PF_RT_SET_TOS = 54, /* u8 */ PF_RT_ANCHOR_RELATIVE = 55, /* u8 */ PF_RT_ANCHOR_WILDCARD = 56, /* u8 */ PF_RT_FLUSH = 57, /* u8 */ PF_RT_PRIO = 58, /* u8 */ PF_RT_SET_PRIO = 59, /* u8 */ PF_RT_SET_PRIO_REPLY = 60, /* u8 */ PF_RT_DIVERT_ADDRESS = 61, /* in6_addr */ PF_RT_DIVERT_PORT = 62, /* u16 */ PF_RT_PACKETS_IN = 63, /* u64 */ PF_RT_PACKETS_OUT = 64, /* u64 */ PF_RT_BYTES_IN = 65, /* u64 */ PF_RT_BYTES_OUT = 66, /* u64 */ PF_RT_EVALUATIONS = 67, /* u64 */ PF_RT_TIMESTAMP = 68, /* u64 */ PF_RT_STATES_CUR = 69, /* u64 */ PF_RT_STATES_TOTAL = 70, /* u64 */ PF_RT_SRC_NODES = 71, /* u64 */ PF_RT_ANCHOR_CALL = 72, /* string */ }; enum pf_addrule_type_t { PF_ART_UNSPEC, PF_ART_TICKET = 1, /* u32 */ PF_ART_POOL_TICKET = 2, /* u32 */ PF_ART_ANCHOR = 3, /* string */ PF_ART_ANCHOR_CALL = 4, /* string */ PF_ART_RULE = 5, /* nested, pfrule_type_t */ }; enum pf_getrules_type_t { PF_GR_UNSPEC, PF_GR_ANCHOR = 1, /* string */ PF_GR_ACTION = 2, /* u8 */ PF_GR_NR = 3, /* u32 */ PF_GR_TICKET = 4, /* u32 */ PF_GR_CLEAR = 5, /* u8 */ }; enum pf_clear_states_type_t { PF_CS_UNSPEC, PF_CS_CMP_ID = 1, /* u64 */ PF_CS_CMP_CREATORID = 2, /* u32 */ PF_CS_CMP_DIR = 3, /* u8 */ PF_CS_AF = 4, /* u8 */ PF_CS_PROTO = 5, /* u8 */ PF_CS_SRC = 6, /* nested, pf_addr_wrap */ PF_CS_DST = 7, /* nested, pf_addr_wrap */ PF_CS_RT_ADDR = 8, /* nested, pf_addr_wrap */ PF_CS_IFNAME = 9, /* string */ PF_CS_LABEL = 10, /* string */ PF_CS_KILL_MATCH = 11, /* bool */ PF_CS_NAT = 12, /* bool */ PF_CS_KILLED = 13, /* u32 */ }; +enum pf_set_statusif_types_t { + PF_SS_UNSPEC, + PF_SS_IFNAME = 1, /* string */ +}; #ifdef _KERNEL void pf_nl_register(void); void pf_nl_unregister(void); #endif #endif diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index ce718c4ba900..867b98e5f6c2 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -1,81 +1,82 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/netpfil/pf BINDIR= ${TESTSDIR} TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= altq \ anchor \ divert-to \ dup \ ether \ forward \ fragmentation_compat \ fragmentation_pass \ fragmentation_no_reassembly \ get_state \ icmp \ + loginterface \ killstate \ macro \ map_e \ match \ modulate \ names \ nat \ pass_block \ pflog \ pflow \ pfsync \ prio \ proxy \ rdr \ ridentifier \ route_to \ rtable \ rules_counter \ scrub_compat \ scrub_pass \ sctp \ set_skip \ set_tos \ src_track \ syncookie \ synproxy \ table \ tcp \ tos ATF_TESTS_PYTEST+= frag6.py ATF_TESTS_PYTEST+= nat66.py ATF_TESTS_PYTEST+= sctp.py # Tests reuse jail names and so cannot run in parallel. TEST_METADATA+= is_exclusive=true PROGS= divapp ${PACKAGE}FILES+= CVE-2019-5597.py \ CVE-2019-5598.py \ daytime_inetd.conf \ echo_inetd.conf \ fragcommon.py \ frag-overindex.py \ frag-overlimit.py \ frag-overreplace.py \ pfsync_defer.py \ pft_ether.py \ pft_read_ipfix.py \ utils.subr ${PACKAGE}FILESMODE_CVE-2019-5597.py= 0555 ${PACKAGE}FILESMODE_CVE-2019-5598.py= 0555 ${PACKAGE}FILESMODE_fragcommon.py= 0555 ${PACKAGE}FILESMODE_frag-overindex.py= 0555 ${PACKAGE}FILESMODE_frag-overlimit.py= 0555 ${PACKAGE}FILESMODE_frag-overreplace.py= 0555 ${PACKAGE}FILESMODE_pfsync_defer.py= 0555 ${PACKAGE}FILESMODE_pft_ether.py= 0555 ${PACKAGE}FILESMODE_pft_read_ipfix.py= 0555 .include diff --git a/tests/sys/netpfil/pf/loginterface.sh b/tests/sys/netpfil/pf/loginterface.sh new file mode 100644 index 000000000000..6decb69fe63d --- /dev/null +++ b/tests/sys/netpfil/pf/loginterface.sh @@ -0,0 +1,87 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2024 Rubicon Communications, LLC (Netgate) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +. $(atf_get_srcdir)/utils.subr + + +atf_test_case "basic" "cleanup" +basic_head() +{ + atf_set descr 'Basic loginterface test' + atf_set require.user root +} + +basic_body() +{ + pft_init + + epair=$(vnet_mkepair) + + ifconfig ${epair}a 192.0.2.2/24 up + + vnet_mkjail alcatraz ${epair}b + jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 + + # No interface stats until we configure a loginterface + atf_check -o not-match:"Interface Stats for" \ + jexec alcatraz pfctl -s info + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "set loginterface ${epair}b" \ + "pass" + + # We do get Interface Stats listed when we've configured a loginterface + atf_check -o match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info + + # And after we've sent traffic there's non-zero counters + atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 + + atf_check -o match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info + atf_check -o match:"Passed 1" \ + jexec alcatraz pfctl -s info + + # And no interface stats once we remove the loginterface + pft_set_rules alcatraz \ + "pass" + atf_check -o not-match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info +} + +basic_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "basic" +}