diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 17576066fcfd..e739e55033e2 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1,3577 +1,3672 @@ /*- * 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); } int pfctl_fd(struct pfctl_handle *h) { return (h->fd); } static int pfctl_do_netlink_cmd(struct pfctl_handle *h, uint cmd) { 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, cmd); 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 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) { return (pfctl_do_netlink_cmd(h, start ? PFNL_CMD_START : PFNL_CMD_STOP)); } 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); } } #define _OUT(_field) offsetof(struct pfctl_status_counter, _field) static const struct snl_attr_parser ap_counter[] = { { .type = PF_C_COUNTER, .off = _OUT(counter), .cb = snl_attr_get_uint64 }, { .type = PF_C_NAME, .off = _OUT(name), .cb = snl_attr_get_string }, { .type = PF_C_ID, .off = _OUT(id), .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_ATTR_PARSER(counter_parser, ap_counter); #undef _OUT static bool snl_attr_get_counters(struct snl_state *ss, struct nlattr *nla, const void *arg __unused, void *target) { struct pfctl_status_counter counter = {}; struct pfctl_status_counter *c; bool error; error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &counter_parser, &counter); if (! error) return (error); c = malloc(sizeof(*c)); if (c == NULL) return (false); c->id = counter.id; c->counter = counter.counter; c->name = strdup(counter.name); TAILQ_INSERT_TAIL((struct pfctl_status_counters *)target, c, entry); return (error); } struct snl_uint64_array { uint64_t *array; size_t count; size_t max; }; static bool snl_attr_get_uint64_element(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) { bool error; uint64_t value; struct snl_uint64_array *t = (struct snl_uint64_array *)target; if (t->count >= t->max) return (false); error = snl_attr_get_uint64(ss, nla, arg, &value); if (! error) return (error); t->array[t->count++] = value; return (true); } static const struct snl_attr_parser ap_array[] = { { .cb = snl_attr_get_uint64_element }, }; SNL_DECLARE_ATTR_PARSER(array_parser, ap_array); static bool snl_attr_get_uint64_array(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) { struct snl_uint64_array a = { .array = target, .count = 0, .max = (size_t)arg, }; bool error; error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &array_parser, &a); if (! error) return (error); return (true); } #define _OUT(_field) offsetof(struct pfctl_status, _field) static const struct snl_attr_parser ap_getstatus[] = { { .type = PF_GS_IFNAME, .off = _OUT(ifname), .arg_u32 = IFNAMSIZ, .cb = snl_attr_copy_string }, { .type = PF_GS_RUNNING, .off = _OUT(running), .cb = snl_attr_get_bool }, { .type = PF_GS_SINCE, .off = _OUT(since), .cb = snl_attr_get_uint32 }, { .type = PF_GS_DEBUG, .off = _OUT(debug), .cb = snl_attr_get_uint32 }, { .type = PF_GS_HOSTID, .off = _OUT(hostid), .cb = snl_attr_get_uint32 }, { .type = PF_GS_STATES, .off = _OUT(states), .cb = snl_attr_get_uint32 }, { .type = PF_GS_SRC_NODES, .off = _OUT(src_nodes), .cb = snl_attr_get_uint32 }, { .type = PF_GS_REASSEMBLE, .off = _OUT(reass), .cb = snl_attr_get_uint32 }, { .type = PF_GS_SYNCOOKIES_ACTIVE, .off = _OUT(syncookies_active), .cb = snl_attr_get_uint32 }, { .type = PF_GS_COUNTERS, .off = _OUT(counters), .cb = snl_attr_get_counters }, { .type = PF_GS_LCOUNTERS, .off = _OUT(lcounters), .cb = snl_attr_get_counters }, { .type = PF_GS_FCOUNTERS, .off = _OUT(fcounters), .cb = snl_attr_get_counters }, { .type = PF_GS_SCOUNTERS, .off = _OUT(scounters), .cb = snl_attr_get_counters }, { .type = PF_GS_CHKSUM, .off = _OUT(pf_chksum), .arg_u32 = PF_MD5_DIGEST_LENGTH, .cb = snl_attr_get_bytes }, { .type = PF_GS_BCOUNTERS, .off = _OUT(bcounters), .arg_u32 = 2 * 2, .cb = snl_attr_get_uint64_array }, { .type = PF_GS_PCOUNTERS, .off = _OUT(pcounters), .arg_u32 = 2 * 2 * 2, .cb = snl_attr_get_uint64_array }, { .type = PF_GS_NCOUNTERS, .off = _OUT(ncounters), .cb = snl_attr_get_counters }, { .type = PF_GS_FRAGMENTS, .off = _OUT(fragments), .cb = snl_attr_get_uint64 }, }; SNL_DECLARE_PARSER(getstatus_parser, struct genlmsghdr, snl_f_p_empty, ap_getstatus); #undef _OUT struct pfctl_status * pfctl_get_status_h(struct pfctl_handle *h) { struct pfctl_status *status; 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 (NULL); snl_init_writer(&h->ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GET_STATUS); hdr->nlmsg_flags |= NLM_F_DUMP; hdr = snl_finalize_msg(&nw); if (hdr == NULL) { return (NULL); } seq_id = hdr->nlmsg_seq; if (! snl_send_message(&h->ss, hdr)) return (NULL); status = calloc(1, sizeof(*status)); if (status == NULL) return (NULL); TAILQ_INIT(&status->counters); TAILQ_INIT(&status->lcounters); TAILQ_INIT(&status->fcounters); TAILQ_INIT(&status->scounters); TAILQ_INIT(&status->ncounters); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { if (! snl_parse_nlmsg(&h->ss, hdr, &getstatus_parser, status)) continue; } return (status); } 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 * 2, (uint64_t *)status->pcounters, NULL); pf_nvuint_64_array(nvl, "bcounters", 2 * 2, (uint64_t *)status->bcounters, NULL); nvlist_destroy(nvl); return (status); } int pfctl_clear_status(struct pfctl_handle *h) { return (pfctl_do_netlink_cmd(h, PFNL_CMD_CLEAR_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); } TAILQ_FOREACH_SAFE(c, &status->ncounters, 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->rdr); 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_pool_addr(struct snl_writer *nw, uint32_t type, const struct pf_pooladdr *pa) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_string(nw, PF_PA_IFNAME, pa->ifname); snl_add_msg_attr_addr_wrap(nw, PF_PA_ADDR, &pa->addr); 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_threshold(struct snl_writer *nw, uint32_t type, const struct pfctl_threshold *th) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_u32(nw, PF_TH_LIMIT, th->limit); snl_add_msg_attr_u32(nw, PF_TH_SECONDS, th->seconds); 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_RDR, &r->rdr); snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_NAT, &r->nat); snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RT, &r->route); snl_add_msg_attr_threshold(nw, PF_RT_PKTRATE, &r->pktrate); 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, r->max_src_conn); 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_MAX_PKT_SIZE, r->max_pkt_size); 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_string(nw, PF_RT_RCV_IFNAME, r->rcv_ifname); snl_add_msg_attr_bool(nw, PF_RT_RCV_IFNOT, r->rcvifnot); 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_u16(nw, PF_RT_TYPE_2, r->type); snl_add_msg_attr_u16(nw, PF_RT_CODE_2, 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_u8(nw, PF_RT_NAF, r->naf); 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 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, snl_f_p_empty, ap_getrules); int pfctl_get_rules_info_h(struct pfctl_handle *h, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path) { 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_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(&h->ss, hdr)) return (ENXIO); while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { if (! snl_parse_nlmsg(&h->ss, hdr, &getrules_parser, rules)) continue; } return (e.error); } int pfctl_get_rules_info(int dev __unused, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path) { struct pfctl_handle *h; int error; h = pfctl_open(PF_DEVICE); if (h == NULL) return (ENOTSUP); error = pfctl_get_rules_info_h(h, rules, ruleset, path); pfctl_close(h); return (error); } int pfctl_get_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) { return (pfctl_get_clear_rule_h(h, nr, ticket, anchor, ruleset, rule, anchor_call, false)); } 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.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 #define _OUT(_field) offsetof(struct pfctl_threshold, _field) static const struct snl_attr_parser ap_pfctl_threshold[] = { { .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 }, { .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 }, { .type = PF_TH_COUNT, .off = _OUT(count), .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_ATTR_PARSER(pfctl_threshold_parser, ap_pfctl_threshold); #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_RDR, .off = _OUT(r.rdr), .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 }, { .type = PF_RT_RCV_IFNAME, .off = _OUT(r.rcv_ifname), .arg = (void*)IFNAMSIZ, .cb = snl_attr_copy_string }, { .type = PF_RT_MAX_SRC_CONN, .off = _OUT(r.max_src_conn), .cb = snl_attr_get_uint32 }, { .type = PF_RT_RPOOL_NAT, .off = _OUT(r.nat), .arg = &pool_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_NAF, .off = _OUT(r.naf), .cb = snl_attr_get_uint8 }, { .type = PF_RT_RPOOL_RT, .off = _OUT(r.route), .arg = &pool_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_RCV_IFNOT, .off = _OUT(r.rcvifnot),.cb = snl_attr_get_bool }, { .type = PF_RT_SRC_NODES_LIMIT, .off = _OUT(r.src_nodes_type[PF_SN_LIMIT]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_SRC_NODES_NAT, .off = _OUT(r.src_nodes_type[PF_SN_NAT]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_SRC_NODES_ROUTE, .off = _OUT(r.src_nodes_type[PF_SN_ROUTE]), .cb = snl_attr_get_uint64 }, { .type = PF_RT_PKTRATE, .off = _OUT(r.pktrate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested }, { .type = PF_RT_MAX_PKT_SIZE, .off =_OUT(r.max_pkt_size), .cb = snl_attr_get_uint16 }, { .type = PF_RT_TYPE_2, .off = _OUT(r.type), .cb = snl_attr_get_uint16 }, { .type = PF_RT_CODE_2, .off = _OUT(r.code), .cb = snl_attr_get_uint16 }, { .type = PF_RT_EXPTIME, .off = _OUT(r.exptime), .cb = snl_attr_get_time_t }, }; #undef _OUT SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, 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 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(creator_parser, struct genlmsghdr, snl_f_p_empty, 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 pfctl_state_key, _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 }, { .type = PF_STK_AF, .off = _OUT(af), .cb = snl_attr_get_uint8 }, { .type = PF_STK_PROTO, .off = _OUT(proto), .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_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 }, { .type = PF_ST_SRC_NODE_FLAGS, .off = _OUT(src_node_flags), .cb = snl_attr_get_uint8 }, { .type = PF_ST_RT_AF, .off = _OUT(rt_af), .cb = snl_attr_get_uint8 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(state_parser, struct genlmsghdr, snl_f_p_empty, 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; 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 }, }; #undef _OUT SNL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, snl_f_p_empty, 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 (errno); ret = ioctl(dev, DIOCXCOMMIT, &trans); if (ret != 0) return (errno); return (0); } 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 (errno); ret = ioctl(dev, DIOCXCOMMIT, &trans); if (ret != 0) return (errno); return (0); } 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 (errno); ret = ioctl(dev, DIOCXCOMMIT, &trans); if (ret != 0) return (errno); return (0); } 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); if (state_limit == 0) state_limit = INT_MAX; 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); if (ret != 0) return (errno); return (0); } 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); if (state_limit == 0) state_limit = INT_MAX; 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); } static void snl_add_msg_attr_table(struct snl_writer *nw, uint32_t type, const struct pfr_table *tbl) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_string(nw, PF_T_ANCHOR, tbl->pfrt_anchor); snl_add_msg_attr_string(nw, PF_T_NAME, tbl->pfrt_name); snl_add_msg_attr_u32(nw, PF_T_TABLE_FLAGS, tbl->pfrt_flags); snl_end_attr_nested(nw, off); } static void snl_add_msg_attr_pfr_addr(struct snl_writer *nw, uint32_t type, const struct pfr_addr *addr) { int off; off = snl_add_msg_attr_nested(nw, type); snl_add_msg_attr_u8(nw, PFR_A_AF, addr->pfra_af); snl_add_msg_attr_u8(nw, PFR_A_NET, addr->pfra_net); snl_add_msg_attr_bool(nw, PFR_A_NOT, addr->pfra_not); snl_add_msg_attr_ip6(nw, PFR_A_ADDR, &addr->pfra_ip6addr); snl_end_attr_nested(nw, off); } static struct snl_attr_parser ap_table_add_addr[] = { { .type = PF_TA_NBR_ADDED, .off = 0, .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_PARSER(table_add_addr_parser, struct genlmsghdr, snl_f_p_empty, ap_table_add_addr); static int _pfctl_table_add_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addrs, int size, int *nadd, int flags) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint32_t seq_id; uint32_t added; 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_TABLE_ADD_ADDR); snl_add_msg_attr_table(&nw, PF_TA_TABLE, tbl); snl_add_msg_attr_u32(&nw, PF_TA_FLAGS, flags); for (int i = 0; i < size; i++) snl_add_msg_attr_pfr_addr(&nw, PF_TA_ADDR, &addrs[i]); 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, &table_add_addr_parser, &added)) continue; } if (nadd) *nadd = added; return (e.error); } int pfctl_table_add_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags) { int ret; int off = 0; int partial_added; int chunk_size; do { chunk_size = MIN(size - off, 256); ret = _pfctl_table_add_addrs_h(h, tbl, &addr[off], chunk_size, &partial_added, flags); if (ret != 0) break; if (nadd) *nadd += partial_added; off += chunk_size; } while (off < size); return (ret); } static struct snl_attr_parser ap_table_del_addr[] = { { .type = PF_TA_NBR_DELETED, .off = 0, .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_PARSER(table_del_addr_parser, struct genlmsghdr, snl_f_p_empty, ap_table_del_addr); static int _pfctl_table_del_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addrs, int size, int *ndel, int flags) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint32_t seq_id; uint32_t deleted; 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_TABLE_DEL_ADDR); snl_add_msg_attr_table(&nw, PF_TA_TABLE, tbl); snl_add_msg_attr_u32(&nw, PF_TA_FLAGS, flags); for (int i = 0; i < size; i++) snl_add_msg_attr_pfr_addr(&nw, PF_TA_ADDR, &addrs[i]); 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, &table_del_addr_parser, &deleted)) continue; } if (ndel) *ndel = deleted; return (e.error); } 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_del_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags) { int ret; int off = 0; int partial_deleted; int chunk_size; do { chunk_size = MIN(size - off, 256); ret = _pfctl_table_del_addrs_h(h, tbl, &addr[off], chunk_size, &partial_deleted, flags); if (ret != 0) break; if (ndel) *ndel += partial_deleted; off += chunk_size; } while (off < size); return (ret); } +struct pfctl_change { + int add; + int del; + int change; +}; +#define _OUT(_field) offsetof(struct pfctl_change, _field) +static struct snl_attr_parser ap_table_set_addr[] = { + { .type = PF_TA_NBR_ADDED, .off = _OUT(add), .cb = snl_attr_get_uint32 }, + { .type = PF_TA_NBR_DELETED, .off = _OUT(del), .cb = snl_attr_get_uint32 }, + { .type = PF_TA_NBR_CHANGED, .off = _OUT(change), .cb = snl_attr_get_uint32 }, +}; +#undef _OUT +SNL_DECLARE_PARSER(table_set_addr_parser, struct genlmsghdr, snl_f_p_empty, ap_table_set_addr); + +static int +_pfctl_table_set_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr + *addrs, int size, int *nadd, int *ndel, int *nchange, int flags) +{ + struct snl_writer nw; + struct snl_errmsg_data e = {}; + struct nlmsghdr *hdr; + struct pfctl_change change = { 0 }; + 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_TABLE_SET_ADDR); + + snl_add_msg_attr_table(&nw, PF_TA_TABLE, tbl); + snl_add_msg_attr_u32(&nw, PF_TA_FLAGS, flags); + for (int i = 0; i < size; i++) + snl_add_msg_attr_pfr_addr(&nw, PF_TA_ADDR, &addrs[i]); + + 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, &table_set_addr_parser, &change)) + continue; + } + + if (nadd) + *nadd = change.add; + if (ndel) + *ndel = change.del; + if (nchange) + *nchange = change.change; + + return (e.error); +} + +int +pfctl_table_set_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, + struct pfr_addr *addr, int size, int *nadd, int *ndel, + int *nchange, int flags) +{ + int ret; + int off = 0; + int partial_add, partial_del, partial_change; + int chunk_size; + + do { + flags &= ~(PFR_FLAG_START | PFR_FLAG_DONE); + if (off == 0) + flags |= PFR_FLAG_START; + chunk_size = MIN(size - off, 256); + if ((chunk_size + off) == size) + flags |= PFR_FLAG_DONE; + ret = _pfctl_table_set_addrs_h(h, tbl, &addr[off], chunk_size, + &partial_add, &partial_del, &partial_change, flags); + if (ret != 0) + break; + if (! (flags & PFR_FLAG_DONE)) { + assert(partial_del == 0); + } + if (nadd) + *nadd += partial_add; + if (ndel) + *ndel += partial_del; + if (nchange) + *nchange += partial_change; + off += chunk_size; + } while (off < size); + + return (ret); +} + 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 (errno); 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 (errno); *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); } #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfctl_natlook, _field) static struct snl_attr_parser ap_natlook[] = { { .type = PF_NL_SRC_ADDR, .off = _OUT(saddr), .cb = snl_attr_get_in6_addr }, { .type = PF_NL_DST_ADDR, .off = _OUT(daddr), .cb = snl_attr_get_in6_addr }, { .type = PF_NL_SRC_PORT, .off = _OUT(sport), .cb = snl_attr_get_uint16 }, { .type = PF_NL_DST_PORT, .off = _OUT(dport), .cb = snl_attr_get_uint16 }, }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(natlook_parser, struct genlmsghdr, snl_f_p_empty, ap_natlook); int pfctl_natlook(struct pfctl_handle *h, const struct pfctl_natlook_key *k, struct pfctl_natlook *r) { 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_NATLOOK); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_u8(&nw, PF_NL_AF, k->af); snl_add_msg_attr_u8(&nw, PF_NL_DIRECTION, k->direction); snl_add_msg_attr_u8(&nw, PF_NL_PROTO, k->proto); snl_add_msg_attr_ip6(&nw, PF_NL_SRC_ADDR, &k->saddr.v6); snl_add_msg_attr_ip6(&nw, PF_NL_DST_ADDR, &k->daddr.v6); snl_add_msg_attr_u16(&nw, PF_NL_SRC_PORT, k->sport); snl_add_msg_attr_u16(&nw, PF_NL_DST_PORT, k->dport); 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, &natlook_parser, r)) continue; } return (e.error); } int pfctl_set_debug(struct pfctl_handle *h, uint32_t level) { 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_DEBUG); snl_add_msg_attr_u32(&nw, PF_SD_LEVEL, level); 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); } int pfctl_set_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t seconds) { 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_TIMEOUT); snl_add_msg_attr_u32(&nw, PF_TO_TIMEOUT, timeout); snl_add_msg_attr_u32(&nw, PF_TO_SECONDS, seconds); 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); } struct pfctl_nl_timeout { uint32_t seconds; }; #define _OUT(_field) offsetof(struct pfctl_nl_timeout, _field) static struct snl_attr_parser ap_get_timeout[] = { { .type = PF_TO_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 }, }; #undef _OUT SNL_DECLARE_PARSER(get_timeout_parser, struct genlmsghdr, snl_f_p_empty, ap_get_timeout); int pfctl_get_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t *seconds) { struct snl_writer nw; struct pfctl_nl_timeout to = {}; 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_GET_TIMEOUT); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_u32(&nw, PF_TO_TIMEOUT, timeout); 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, &get_timeout_parser, &to)) continue; } if (seconds != NULL) *seconds = to.seconds; return (e.error); } int pfctl_set_limit(struct pfctl_handle *h, const int index, const uint limit) { 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_LIMIT); snl_add_msg_attr_u32(&nw, PF_LI_INDEX, index); snl_add_msg_attr_u32(&nw, PF_LI_LIMIT, limit); 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); } struct pfctl_nl_limit { unsigned int limit; }; #define _OUT(_field) offsetof(struct pfctl_nl_limit, _field) static struct snl_attr_parser ap_get_limit[] = { { .type = PF_LI_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 }, }; #undef _OUT SNL_DECLARE_PARSER(get_limit_parser, struct genlmsghdr, snl_f_p_empty, ap_get_limit); int pfctl_get_limit(struct pfctl_handle *h, const int index, uint *limit) { struct snl_writer nw; struct pfctl_nl_limit li = {}; 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_GET_LIMIT); hdr->nlmsg_flags |= NLM_F_DUMP; snl_add_msg_attr_u32(&nw, PF_LI_INDEX, index); 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, &get_limit_parser, &li)) continue; } if (limit != NULL) *limit = li.limit; return (e.error); } struct pfctl_nl_begin_addrs { uint32_t ticket; }; #define _OUT(_field) offsetof(struct pfctl_nl_begin_addrs, _field) static struct snl_attr_parser ap_begin_addrs[] = { { .type = PF_BA_TICKET, .off = _OUT(ticket), .cb = snl_attr_get_uint32 }, }; #undef _OUT SNL_DECLARE_PARSER(begin_addrs_parser, struct genlmsghdr, snl_f_p_empty, ap_begin_addrs); int pfctl_begin_addrs(struct pfctl_handle *h, uint32_t *ticket) { struct snl_writer nw; struct pfctl_nl_begin_addrs attrs = {}; 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_BEGIN_ADDRS); hdr->nlmsg_flags |= NLM_F_DUMP; 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, &begin_addrs_parser, &attrs)) continue; } if (ticket != NULL) *ticket = attrs.ticket; return (e.error); } int pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which) { 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_ADD_ADDR); snl_add_msg_attr_u32(&nw, PF_AA_ACTION, pa->action); snl_add_msg_attr_u32(&nw, PF_AA_TICKET, pa->ticket); snl_add_msg_attr_u32(&nw, PF_AA_NR, pa->nr); snl_add_msg_attr_u32(&nw, PF_AA_R_NUM, pa->r_num); snl_add_msg_attr_u8(&nw, PF_AA_R_ACTION, pa->r_action); snl_add_msg_attr_u8(&nw, PF_AA_R_LAST, pa->r_last); snl_add_msg_attr_u8(&nw, PF_AA_AF, pa->af); snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, pa->anchor); snl_add_msg_attr_pool_addr(&nw, PF_AA_ADDR, &pa->addr); snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which); 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); } static const struct snl_attr_parser ap_get_addrs[] = { { .type = PF_AA_NR, .off = 0, .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_PARSER(get_addrs_parser, struct genlmsghdr, snl_f_p_empty, ap_get_addrs); int pfctl_get_addrs(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num, uint8_t r_action, const char *anchor, uint32_t *nr, int which) { 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_GET_ADDRS); snl_add_msg_attr_u32(&nw, PF_AA_TICKET, ticket); snl_add_msg_attr_u32(&nw, PF_AA_R_NUM, r_num); snl_add_msg_attr_u8(&nw, PF_AA_R_ACTION, r_action); snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, anchor); snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which); 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, &get_addrs_parser, nr)) continue; } return (e.error); } #define _OUT(_field) offsetof(struct pf_pooladdr, _field) static const struct snl_attr_parser ap_pool_addr[] = { { .type = PF_PA_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = snl_attr_get_nested }, { .type = PF_PA_IFNAME, .off = _OUT(ifname), .arg_u32 = IFNAMSIZ, .cb = snl_attr_copy_string }, }; SNL_DECLARE_ATTR_PARSER(pool_addr_parser, ap_pool_addr); #undef _OUT #define _OUT(_field) offsetof(struct pfioc_pooladdr, _field) static const struct snl_attr_parser ap_get_addr[] = { { .type = PF_AA_ACTION, .off = _OUT(action), .cb = snl_attr_get_uint32 }, { .type = PF_AA_TICKET, .off = _OUT(ticket), .cb = snl_attr_get_uint32 }, { .type = PF_AA_NR, .off = _OUT(nr), .cb = snl_attr_get_uint32 }, { .type = PF_AA_R_NUM, .off = _OUT(r_num), .cb = snl_attr_get_uint32 }, { .type = PF_AA_R_ACTION, .off = _OUT(r_action), .cb = snl_attr_get_uint8 }, { .type = PF_AA_R_LAST, .off = _OUT(r_last), .cb = snl_attr_get_uint8 }, { .type = PF_AA_AF, .off = _OUT(af), .cb = snl_attr_get_uint8 }, { .type = PF_AA_ANCHOR, .off = _OUT(anchor), .arg_u32 = MAXPATHLEN, .cb = snl_attr_copy_string }, { .type = PF_AA_ADDR, .off = _OUT(addr), .arg = &pool_addr_parser, .cb = snl_attr_get_nested }, }; SNL_DECLARE_PARSER(get_addr_parser, struct genlmsghdr, snl_f_p_empty, ap_get_addr); #undef _OUT int pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num, uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa, int which) { 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_GET_ADDR); snl_add_msg_attr_u32(&nw, PF_AA_TICKET, ticket); snl_add_msg_attr_u32(&nw, PF_AA_R_NUM, r_num); snl_add_msg_attr_u8(&nw, PF_AA_R_ACTION, r_action); snl_add_msg_attr_string(&nw, PF_AA_ANCHOR, anchor); snl_add_msg_attr_u32(&nw, PF_AA_NR, nr); snl_add_msg_attr_u32(&nw, PF_AA_WHICH, which); 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, &get_addr_parser, pa)) continue; } return (e.error); } #define _OUT(_field) offsetof(struct pfioc_ruleset, _field) static const struct snl_attr_parser ap_ruleset[] = { { .type = PF_RS_NR, .off = _OUT(nr), .cb = snl_attr_get_uint32 }, { .type = PF_RS_NAME, .off = _OUT(name), .arg = (void *)PF_ANCHOR_NAME_SIZE, .cb = snl_attr_copy_string }, }; SNL_DECLARE_PARSER(ruleset_parser, struct genlmsghdr, snl_f_p_empty, ap_ruleset); #undef _OUT int pfctl_get_rulesets(struct pfctl_handle *h, const char *path, uint32_t *nr) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; struct pfioc_ruleset rs = {}; 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_GET_RULESETS); snl_add_msg_attr_string(&nw, PF_RS_PATH, path); 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, &ruleset_parser, &rs)) continue; } *nr = rs.nr; return (e.error); } int pfctl_get_ruleset(struct pfctl_handle *h, const char *path, uint32_t nr, struct pfioc_ruleset *rs) { 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_GET_RULESET); snl_add_msg_attr_string(&nw, PF_RS_PATH, path); snl_add_msg_attr_u32(&nw, PF_RS_NR, nr); 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, &ruleset_parser, rs)) continue; } rs->nr = nr; strlcpy(rs->path, path, sizeof(rs->path)); return (e.error); } #define _OUT(_field) offsetof(struct pfctl_src_node, _field) static struct snl_attr_parser ap_srcnode[] = { { .type = PF_SN_ADDR, .off = _OUT(addr), .cb = snl_attr_get_in6_addr }, { .type = PF_SN_RADDR, .off = _OUT(raddr), .cb = snl_attr_get_in6_addr }, { .type = PF_SN_RULE_NR, .off = _OUT(rule), .cb = snl_attr_get_uint32 }, { .type = PF_SN_BYTES_IN, .off = _OUT(bytes[0]), .cb = snl_attr_get_uint64 }, { .type = PF_SN_BYTES_OUT, .off = _OUT(bytes[1]), .cb = snl_attr_get_uint64 }, { .type = PF_SN_PACKETS_IN, .off = _OUT(packets[0]), .cb = snl_attr_get_uint64 }, { .type = PF_SN_PACKETS_OUT, .off = _OUT(packets[1]), .cb = snl_attr_get_uint64 }, { .type = PF_SN_STATES, .off = _OUT(states), .cb = snl_attr_get_uint32 }, { .type = PF_SN_CONNECTIONS, .off = _OUT(conn), .cb = snl_attr_get_uint32 }, { .type = PF_SN_AF, .off = _OUT(af), .cb = snl_attr_get_uint8 }, { .type = PF_SN_RULE_TYPE, .off = _OUT(ruletype), .cb = snl_attr_get_uint8 }, { .type = PF_SN_CREATION, .off = _OUT(creation), .cb = snl_attr_get_uint64 }, { .type = PF_SN_EXPIRE, .off = _OUT(expire), .cb = snl_attr_get_uint64 }, { .type = PF_SN_CONNECTION_RATE, .off = _OUT(conn_rate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested }, { .type = PF_SN_RAF, .off = _OUT(raf), .cb = snl_attr_get_uint8 }, { .type = PF_SN_NODE_TYPE, .off = _OUT(type), .cb = snl_attr_get_uint8 }, }; #undef _OUT SNL_DECLARE_PARSER(srcnode_parser, struct genlmsghdr, snl_f_p_empty, ap_srcnode); int pfctl_get_srcnodes(struct pfctl_handle *h, pfctl_get_srcnode_fn fn, void *arg) { struct snl_writer nw; struct pfctl_src_node sn; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; int ret; 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_GET_SRCNODES); 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) { bzero(&sn, sizeof(sn)); if (!snl_parse_nlmsg(&h->ss, hdr, &srcnode_parser, &sn)) continue; ret = fn(&sn, arg); if (ret != 0) return (ret); } return (e.error); } static struct snl_attr_parser ap_ndel[] = { { .type = PF_T_NBR_DELETED, .off = 0, .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_PARSER(ndel_parser, struct genlmsghdr, snl_f_p_empty, ap_ndel); int pfctl_clear_tables(struct pfctl_handle *h, struct pfr_table *filter, int *ndel, int flags) { 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_CLEAR_TABLES); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, filter->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, filter->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, filter->pfrt_flags); snl_add_msg_attr_u32(&nw, PF_T_FLAGS, flags); 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, &ndel_parser, ndel)) continue; } return (e.error); } static struct snl_attr_parser ap_nadd[] = { { .type = PF_T_NBR_ADDED, .off = 0, .cb = snl_attr_get_uint32 }, }; SNL_DECLARE_PARSER(nadd_parser, struct genlmsghdr, snl_f_p_empty, ap_nadd); int pfctl_add_table(struct pfctl_handle *h, struct pfr_table *table, int *nadd, int flags) { 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_ADD_TABLE); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, table->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, table->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, table->pfrt_flags); snl_add_msg_attr_u32(&nw, PF_T_FLAGS, flags); 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, &nadd_parser, nadd)) continue; } return (e.error); } int pfctl_del_table(struct pfctl_handle *h, struct pfr_table *table, int *ndel, int flags) { 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_DEL_TABLE); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, table->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, table->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, table->pfrt_flags); snl_add_msg_attr_u32(&nw, PF_T_FLAGS, flags); 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, &ndel_parser, ndel)) continue; } return (e.error); } static bool snl_attr_get_uint64_into_int_array(struct snl_state *ss, struct nlattr *nla, const void *arg, void *target) { uint64_t *u64target; struct snl_uint64_array a = { .count = 0, .max = (size_t)arg, }; bool error; u64target = malloc(a.max * sizeof(uint64_t)); a.array = u64target; error = snl_parse_header(ss, NLA_DATA(nla), NLA_DATA_LEN(nla), &array_parser, &a); if (! error) return (error); for (size_t i = 0; i < a.count; i++) ((int *)target)[i] = (int)u64target[i]; free(u64target); return (true); } #define _OUT(_field) offsetof(struct pfr_table, _field) static const struct snl_attr_parser ap_table[] = { { .type = PF_T_ANCHOR, .off = _OUT(pfrt_anchor), .arg = (void *)MAXPATHLEN, .cb = snl_attr_copy_string }, { .type = PF_T_NAME, .off = _OUT(pfrt_name), .arg = (void *)PF_TABLE_NAME_SIZE, .cb =snl_attr_copy_string }, { .type = PF_T_TABLE_FLAGS, .off = _OUT(pfrt_flags), .cb = snl_attr_get_uint32 }, }; #undef _OUT SNL_DECLARE_ATTR_PARSER(table_parser, ap_table); #define _OUT(_field) offsetof(struct pfr_tstats, _field) static struct snl_attr_parser ap_tstats[] = { { .type = PF_TS_TABLE, .off = _OUT(pfrts_t), .arg = &table_parser, .cb = snl_attr_get_nested }, { .type = PF_TS_PACKETS, .off = _OUT(pfrts_packets), .arg = (void *)(PFR_DIR_MAX * PFR_OP_TABLE_MAX), .cb = snl_attr_get_uint64_array}, { .type = PF_TS_BYTES, .off = _OUT(pfrts_bytes), .arg = (void *)(PFR_DIR_MAX * PFR_OP_TABLE_MAX), .cb = snl_attr_get_uint64_array }, { .type = PF_TS_MATCH, .off = _OUT(pfrts_match), .cb = snl_attr_get_uint64 }, {. type = PF_TS_NOMATCH, .off = _OUT(pfrts_nomatch), .cb = snl_attr_get_uint64 }, { .type = PF_TS_TZERO, .off = _OUT(pfrts_tzero), .cb = snl_attr_get_uint64 }, { .type = PF_TS_REFCNT, .off = _OUT(pfrts_cnt), . arg = (void *)PFR_REFCNT_MAX, .cb = snl_attr_get_uint64_into_int_array }, }; #undef _OUT SNL_DECLARE_PARSER(tstats_parser, struct genlmsghdr, snl_f_p_empty, ap_tstats); int pfctl_get_tstats(struct pfctl_handle *h, const struct pfr_table *filter, pfctl_get_tstats_fn fn, void *arg) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; int ret; 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_GET_TSTATS); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, filter->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, filter->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, filter->pfrt_flags); 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) { struct pfr_tstats tstats = {}; if (!snl_parse_nlmsg(&h->ss, hdr, &tstats_parser, &tstats)) continue; ret = fn(&tstats, arg); if (ret != 0) break; } return (e.error); } static struct snl_attr_parser ap_tstats_clr[] = { { .type = PF_TS_NZERO, .off = 0, .cb = snl_attr_get_uint64 }, }; SNL_DECLARE_PARSER(tstats_clr_parser, struct genlmsghdr, snl_f_p_empty, ap_tstats_clr); int pfctl_clear_tstats(struct pfctl_handle *h, const struct pfr_table *filter, int *nzero, int flags) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint64_t zero; 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_CLR_TSTATS); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, filter->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, filter->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, filter->pfrt_flags); snl_add_msg_attr_u32(&nw, PF_T_FLAGS, flags); 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, &tstats_clr_parser, &zero)) continue; if (nzero) *nzero = (uint32_t)zero; } return (e.error); } static struct snl_attr_parser ap_clr_addrs[] = { { .type = PF_T_NBR_DELETED, .off = 0, .cb = snl_attr_get_uint64 }, }; SNL_DECLARE_PARSER(clr_addrs_parser, struct genlmsghdr, snl_f_p_empty, ap_clr_addrs); int pfctl_clear_addrs(struct pfctl_handle *h, const struct pfr_table *filter, int *ndel, int flags) { struct snl_writer nw; struct snl_errmsg_data e = {}; struct nlmsghdr *hdr; uint64_t del; 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_CLR_ADDRS); snl_add_msg_attr_string(&nw, PF_T_ANCHOR, filter->pfrt_anchor); snl_add_msg_attr_string(&nw, PF_T_NAME, filter->pfrt_name); snl_add_msg_attr_u32(&nw, PF_T_TABLE_FLAGS, filter->pfrt_flags); snl_add_msg_attr_u32(&nw, PF_T_FLAGS, flags); 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, &clr_addrs_parser, &del)) continue; if (ndel) *ndel = (uint32_t)del; } return (e.error); } diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 5880e1a88371..ae4b18dabe75 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -1,582 +1,585 @@ /*- * 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; struct pfctl_status_counters ncounters; uint64_t fragments; uint64_t pcounters[2][2][2]; 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_pooladdr { struct pf_addr_wrap addr; TAILQ_ENTRY(pfctl_pooladdr) entries; char ifname[IFNAMSIZ]; sa_family_t af; }; TAILQ_HEAD(pfctl_palist, pfctl_pooladdr); struct pfctl_pool { struct pfctl_palist list; struct pfctl_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_threshold { uint32_t limit; uint32_t seconds; uint32_t count; uint32_t last; }; 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 nat; union { /* Alias old and new names. */ struct pfctl_pool rpool; struct pfctl_pool rdr; }; struct pfctl_pool route; struct pfctl_threshold pktrate; 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; uint16_t max_pkt_size; 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; uint64_t src_nodes_type[PF_SN_MAX]; 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; char rcv_ifname[IFNAMSIZ]; bool rcvifnot; 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; uint16_t type; uint16_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]; sa_family_t naf; struct { struct pf_addr addr; uint16_t port; } divert; time_t exptime; }; 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]; sa_family_t rt_af; uint8_t src_node_flags; }; 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; }; struct pfctl_src_node { struct pf_addr addr; struct pf_addr raddr; int rule; uint64_t bytes[2]; uint64_t packets[2]; uint32_t states; uint32_t conn; sa_family_t af; sa_family_t raf; uint8_t ruletype; uint64_t creation; uint64_t expire; struct pfctl_threshold conn_rate; pf_sn_types_t type; }; #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_fd(struct pfctl_handle *); int pfctl_startstop(struct pfctl_handle *h, int start); struct pfctl_status* pfctl_get_status_h(struct pfctl_handle *h); struct pfctl_status* pfctl_get_status(int dev); int pfctl_clear_status(struct pfctl_handle *h); 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_h(struct pfctl_handle *h, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path); 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_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); 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_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags); 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_h(struct pfctl_handle *h, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, 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 +int pfctl_table_set_addrs_h(struct pfctl_handle *h, struct pfr_table *tbl, + struct pfr_addr *addr, int size, int *nadd, int *ndel, + int *nchange, 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); struct pfctl_natlook_key { sa_family_t af; uint8_t direction; uint8_t proto; struct pf_addr saddr; struct pf_addr daddr; uint16_t sport; uint16_t dport; }; struct pfctl_natlook { struct pf_addr saddr; struct pf_addr daddr; uint16_t sport; uint16_t dport; }; int pfctl_natlook(struct pfctl_handle *h, const struct pfctl_natlook_key *k, struct pfctl_natlook *r); int pfctl_set_debug(struct pfctl_handle *h, uint32_t level); int pfctl_set_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t seconds); int pfctl_get_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t *seconds); int pfctl_set_limit(struct pfctl_handle *h, const int index, const uint limit); int pfctl_get_limit(struct pfctl_handle *h, const int index, uint *limit); int pfctl_begin_addrs(struct pfctl_handle *h, uint32_t *ticket); int pfctl_add_addr(struct pfctl_handle *h, const struct pfioc_pooladdr *pa, int which); int pfctl_get_addrs(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num, uint8_t r_action, const char *anchor, uint32_t *nr, int which); int pfctl_get_addr(struct pfctl_handle *h, uint32_t ticket, uint32_t r_num, uint8_t r_action, const char *anchor, uint32_t nr, struct pfioc_pooladdr *pa, int which); int pfctl_get_rulesets(struct pfctl_handle *h, const char *path, uint32_t *nr); int pfctl_get_ruleset(struct pfctl_handle *h, const char *path, uint32_t nr, struct pfioc_ruleset *rs); typedef int (*pfctl_get_srcnode_fn)(struct pfctl_src_node*, void *); int pfctl_get_srcnodes(struct pfctl_handle *h, pfctl_get_srcnode_fn fn, void *arg); int pfctl_clear_tables(struct pfctl_handle *h, struct pfr_table *filter, int *ndel, int flags); int pfctl_add_table(struct pfctl_handle *h, struct pfr_table *table, int *nadd, int flags); int pfctl_del_table(struct pfctl_handle *h, struct pfr_table *table, int *ndel, int flags); typedef int (*pfctl_get_tstats_fn)(const struct pfr_tstats *t, void *arg); int pfctl_get_tstats(struct pfctl_handle *h, const struct pfr_table *filter, pfctl_get_tstats_fn fn, void *arg); int pfctl_clear_tstats(struct pfctl_handle *h, const struct pfr_table *filter, int *nzero, int flags); int pfctl_clear_addrs(struct pfctl_handle *h, const struct pfr_table *filter, int *ndel, int flags); #endif diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index 136f51ea08f9..c540c6348d84 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -1,202 +1,202 @@ /* $OpenBSD: pfctl.h,v 1.42 2007/12/05 12:01:47 chl Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * 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_H_ #define _PFCTL_H_ #include #ifdef PFCTL_DEBUG #define DBGPRINT(...) fprintf(stderr, __VA_ARGS__) #else #define DBGPRINT(...) (void)(0) #endif extern struct pfctl_handle *pfh; struct pfctl; enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, PFRB_IFACES, PFRB_TRANS, PFRB_MAX }; struct pfr_buffer { int pfrb_type; /* type of content, see enum above */ int pfrb_size; /* number of objects in buffer */ int pfrb_msize; /* maximum number of objects in buffer */ void *pfrb_caddr; /* malloc'ated memory area */ }; #define PFRB_FOREACH(var, buf) \ for ((var) = pfr_buf_next((buf), NULL); \ (var) != NULL; \ (var) = pfr_buf_next((buf), (var))) RB_HEAD(pfr_ktablehead, pfr_ktable); struct pfr_ktable { struct pfr_tstats pfrkt_ts; RB_ENTRY(pfr_ktable) pfrkt_tree; SLIST_ENTRY(pfr_ktable) pfrkt_workq; struct radix_node_head *pfrkt_ip4; struct radix_node_head *pfrkt_ip6; struct pfr_ktable *pfrkt_shadow; struct pfr_ktable *pfrkt_root; struct pf_kruleset *pfrkt_rs; long pfrkt_larg; int pfrkt_nflags; }; #define pfrkt_t pfrkt_ts.pfrts_t #define pfrkt_name pfrkt_t.pfrt_name #define pfrkt_anchor pfrkt_t.pfrt_anchor #define pfrkt_ruleset pfrkt_t.pfrt_ruleset #define pfrkt_flags pfrkt_t.pfrt_flags #define pfrkt_cnt pfrkt_kts.pfrkts_cnt #define pfrkt_refcnt pfrkt_kts.pfrkts_refcnt #define pfrkt_tzero pfrkt_kts.pfrkts_tzero struct pfr_uktable { struct pfr_ktable pfrukt_kt; struct pfr_buffer pfrukt_addrs; int pfrukt_init_addr; SLIST_ENTRY(pfr_uktable) pfrukt_entry; }; #define pfrukt_t pfrukt_kt.pfrkt_ts.pfrts_t #define pfrukt_name pfrukt_kt.pfrkt_t.pfrt_name #define pfrukt_anchor pfrukt_kt.pfrkt_t.pfrt_anchor extern struct pfr_ktablehead pfr_ktables; struct pfr_anchoritem { SLIST_ENTRY(pfr_anchoritem) pfra_sle; char *pfra_anchorname; }; SLIST_HEAD(pfr_anchors, pfr_anchoritem); int pfr_add_table(struct pfr_table *, int *, int); int pfr_del_table(struct pfr_table *, int *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); -int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, +int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int *, int *, int); int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, int, int); void pfr_buf_clear(struct pfr_buffer *); int pfr_buf_add(struct pfr_buffer *, const void *); void *pfr_buf_next(struct pfr_buffer *, const void *); int pfr_buf_grow(struct pfr_buffer *, int); int pfr_buf_load(struct pfr_buffer *, char *, int, int (*)(struct pfr_buffer *, char *, int, int), int); char *pf_strerror(int); int pfi_get_ifaces(const char *, struct pfi_kif *, int *); void pfctl_print_title(char *); int pfctl_do_clear_tables(const char *, int); void pfctl_show_tables(const char *, int); int pfctl_table(int, char *[], char *, const char *, char *, const char *, int); int pfctl_show_altq(int, const char *, int, int); void warn_duplicate_tables(const char *, const char *); void pfctl_show_ifaces(const char *, int); void pfctl_show_creators(int); FILE *pfctl_fopen(const char *, const char *); #ifdef __FreeBSD__ extern int altqsupport; extern int dummynetsupport; #define HTONL(x) (x) = htonl((__uint32_t)(x)) #endif #ifndef DEFAULT_PRIORITY #define DEFAULT_PRIORITY 1 #endif #ifndef DEFAULT_QLIMIT #define DEFAULT_QLIMIT 50 #endif /* * generalized service curve used for admission control */ struct segment { LIST_ENTRY(segment) _next; double x, y, d, m; }; extern int loadopt; int check_commit_altq(int, int); void pfaltq_store(struct pf_altq *); char *rate2str(double); void print_addr(struct pf_addr_wrap *, sa_family_t, int); void print_addr_str(sa_family_t, struct pf_addr *); void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int); void print_seq(struct pfctl_state_peer *); void print_state(struct pfctl_state *, int); int pfctl_cmdline_symset(char *); int pfctl_add_trans(struct pfr_buffer *, int, const char *); u_int32_t pfctl_get_ticket(struct pfr_buffer *, int, const char *); int pfctl_trans(int, struct pfr_buffer *, u_long, int); int pf_get_ruleset_number(u_int8_t); void pf_init_ruleset(struct pfctl_ruleset *); int pfctl_anchor_setup(struct pfctl_rule *, const struct pfctl_ruleset *, const char *); void pf_remove_if_empty_ruleset(struct pfctl_ruleset *); struct pfctl_ruleset *pf_find_ruleset(const char *); struct pfctl_ruleset *pf_find_or_create_ruleset(const char *); void pf_init_eth_ruleset(struct pfctl_eth_ruleset *); int pfctl_eth_anchor_setup(struct pfctl *, struct pfctl_eth_rule *, const struct pfctl_eth_ruleset *, const char *); struct pfctl_eth_ruleset *pf_find_or_create_eth_ruleset(const char *); void pf_remove_if_empty_eth_ruleset( struct pfctl_eth_ruleset *); void expand_label(char *, size_t, struct pfctl_rule *); const char *pfctl_proto2name(int); void pfctl_err(int, int, const char *, ...); void pfctl_errx(int, int, const char *, ...); #endif /* _PFCTL_H_ */ diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c index 98f907738d95..3b7161420e33 100644 --- a/sbin/pfctl/pfctl_radix.c +++ b/sbin/pfctl/pfctl_radix.c @@ -1,481 +1,481 @@ /* $OpenBSD: pfctl_radix.c,v 1.27 2005/05/21 21:03:58 henning Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002 Cedric Berger * 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 "pfctl.h" #include "pfctl_parser.h" #define BUF_SIZE 256 extern int dev; static int pfr_next_token(char buf[BUF_SIZE], FILE *); struct pfr_ktablehead pfr_ktables = { 0 }; RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); int pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q) { int d; if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE))) return (d); return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor)); } static void pfr_report_error(struct pfr_table *tbl, struct pfioc_table *io, const char *err) { unsigned long maxcount; size_t s; s = sizeof(maxcount); if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, NULL, 0) == -1) return; if (io->pfrio_size > maxcount || io->pfrio_size2 > maxcount) fprintf(stderr, "cannot %s %s: too many elements.\n" "Consider increasing net.pf.request_maxcount.", err, tbl->pfrt_name); } int pfr_add_table(struct pfr_table *tbl, int *nadd, int flags) { return (pfctl_add_table(pfh, tbl, nadd, flags)); } int pfr_del_table(struct pfr_table *tbl, int *ndel, int flags) { return (pfctl_del_table(pfh, tbl, ndel, flags)); } int pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, int flags) { struct pfioc_table io; if (size == NULL || *size < 0 || (*size && tbl == NULL)) { errno = EINVAL; return (-1); } bzero(&io, sizeof io); io.pfrio_flags = flags; if (filter != NULL) io.pfrio_table = *filter; io.pfrio_buffer = tbl; io.pfrio_esize = sizeof(*tbl); io.pfrio_size = *size; if (ioctl(dev, DIOCRGETTABLES, &io)) { pfr_report_error(tbl, &io, "get table"); return (-1); } *size = io.pfrio_size; return (0); } int pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) { return (pfctl_clear_addrs(pfh, tbl, ndel, flags)); } int pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags) { int ret; if (*nadd) *nadd = 0; ret = pfctl_table_add_addrs_h(pfh, tbl, addr, size, nadd, flags); if (ret) { errno = ret; return (-1); } return (0); } int pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags) { int ret; ret = pfctl_table_del_addrs_h(pfh, tbl, addr, size, ndel, flags); if (ret) { errno = ret; return (-1); } return (0); } int pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, - int *size2, int *nadd, int *ndel, int *nchange, int flags) + int *nadd, int *ndel, int *nchange, int flags) { int ret; - ret = pfctl_table_set_addrs(dev, tbl, addr, size, size2, nadd, ndel, + ret = pfctl_table_set_addrs_h(pfh, tbl, addr, size, nadd, ndel, nchange, flags); if (ret) { errno = ret; return (-1); } return (0); } int pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags) { int ret; ret = pfctl_table_get_addrs(dev, tbl, addr, size, flags); if (ret) { errno = ret; return (-1); } return (0); } int pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, int flags) { struct pfioc_table io; if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { errno = EINVAL; return (-1); } 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, DIOCRGETASTATS, &io)) { pfr_report_error(tbl, &io, "get astats from"); return (-1); } *size = io.pfrio_size; return (0); } int pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nzero, int flags) { struct pfioc_table io; if (size < 0 || !tbl || (size && !addr)) { errno = EINVAL; return (-1); } 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, DIOCRCLRASTATS, &io) == -1) return (-1); if (nzero) *nzero = io.pfrio_nzero; return (0); } int pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nmatch, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { errno = EINVAL; return (-1); } 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, DIOCRTSTADDRS, &io)) { pfr_report_error(tbl, &io, "test addresses in"); return (-1); } if (nmatch) *nmatch = io.pfrio_nmatch; return (0); } int pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int *naddr, int ticket, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { DBGPRINT("%s %p %d %p\n", __func__, tbl, size, addr); errno = EINVAL; return (-1); } 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_ticket = ticket; if (ioctl(dev, DIOCRINADEFINE, &io)) { pfr_report_error(tbl, &io, "define inactive set table"); return (-1); } if (nadd != NULL) *nadd = io.pfrio_nadd; if (naddr != NULL) *naddr = io.pfrio_naddr; return (0); } /* interface management code */ int pfi_get_ifaces(const char *filter, struct pfi_kif *buf, int *size) { struct pfioc_iface io; if (size == NULL || *size < 0 || (*size && buf == NULL)) { errno = EINVAL; return (-1); } bzero(&io, sizeof io); if (filter != NULL) if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >= sizeof(io.pfiio_name)) { errno = EINVAL; return (-1); } io.pfiio_buffer = buf; io.pfiio_esize = sizeof(*buf); io.pfiio_size = *size; if (ioctl(dev, DIOCIGETIFACES, &io)) return (-1); *size = io.pfiio_size; return (0); } /* buffer management code */ const size_t buf_esize[PFRB_MAX] = { 0, sizeof(struct pfr_table), sizeof(struct pfr_tstats), sizeof(struct pfr_addr), sizeof(struct pfr_astats), sizeof(struct pfi_kif), sizeof(struct pfioc_trans_e) }; /* * add one element to the buffer */ int pfr_buf_add(struct pfr_buffer *b, const void *e) { size_t bs; if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX || e == NULL) { errno = EINVAL; return (-1); } bs = buf_esize[b->pfrb_type]; if (b->pfrb_size == b->pfrb_msize) if (pfr_buf_grow(b, 0)) return (-1); memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs); b->pfrb_size++; return (0); } /* * return next element of the buffer (or first one if prev is NULL) * see PFRB_FOREACH macro */ void * pfr_buf_next(struct pfr_buffer *b, const void *prev) { size_t bs; if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) return (NULL); if (b->pfrb_size == 0) return (NULL); if (prev == NULL) return (b->pfrb_caddr); bs = buf_esize[b->pfrb_type]; if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1) return (NULL); return (((caddr_t)prev) + bs); } /* * minsize: * 0: make the buffer somewhat bigger * n: make room for "n" entries in the buffer */ int pfr_buf_grow(struct pfr_buffer *b, int minsize) { caddr_t p; size_t bs; if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) { errno = EINVAL; return (-1); } if (minsize != 0 && minsize <= b->pfrb_msize) return (0); bs = buf_esize[b->pfrb_type]; if (!b->pfrb_msize) { if (minsize < 64) minsize = 64; } if (minsize == 0) minsize = b->pfrb_msize * 2; p = reallocarray(b->pfrb_caddr, minsize, bs); if (p == NULL) return (-1); bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs); b->pfrb_caddr = p; b->pfrb_msize = minsize; return (0); } /* * reset buffer and free memory. */ void pfr_buf_clear(struct pfr_buffer *b) { if (b == NULL) return; free(b->pfrb_caddr); b->pfrb_caddr = NULL; b->pfrb_size = b->pfrb_msize = 0; } int pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork, int (*append_addr)(struct pfr_buffer *, char *, int, int), int opts) { FILE *fp; char buf[BUF_SIZE]; int rv; if (file == NULL) return (0); if (!strcmp(file, "-")) fp = stdin; else { fp = pfctl_fopen(file, "r"); if (fp == NULL) return (-1); } while ((rv = pfr_next_token(buf, fp)) == 1) if (append_addr(b, buf, nonetwork, opts)) { rv = -1; break; } if (fp != stdin) fclose(fp); return (rv); } int pfr_next_token(char buf[BUF_SIZE], FILE *fp) { static char next_ch = ' '; int i = 0; for (;;) { /* skip spaces */ while (isspace(next_ch) && !feof(fp)) next_ch = fgetc(fp); /* remove from '#' or ';' until end of line */ if (next_ch == '#' || next_ch == ';') while (!feof(fp)) { next_ch = fgetc(fp); if (next_ch == '\n') break; } else break; } if (feof(fp)) { next_ch = ' '; return (0); } do { if (i < BUF_SIZE) buf[i++] = next_ch; next_ch = fgetc(fp); } while (!feof(fp) && !isspace(next_ch)); if (i >= BUF_SIZE) { errno = EINVAL; return (-1); } buf[i] = '\0'; return (1); } diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c index 4955e1791fd7..aae347712547 100644 --- a/sbin/pfctl/pfctl_table.c +++ b/sbin/pfctl/pfctl_table.c @@ -1,725 +1,716 @@ /* $OpenBSD: pfctl_table.c,v 1.67 2008/06/10 20:55:02 mcbride Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002 Cedric Berger * 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 "pfctl_parser.h" #include "pfctl.h" extern void usage(void); static void print_table(const struct pfr_table *, int, int); static int print_tstats(const struct pfr_tstats *, int); static int load_addr(struct pfr_buffer *, int, char *[], char *, int, int); static void print_addrx(struct pfr_addr *, struct pfr_addr *, int); static int nonzero_astats(struct pfr_astats *); static void print_astats(struct pfr_astats *, int); static void xprintf(int, const char *, ...); static void print_iface(struct pfi_kif *, int); static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = { { "In/Block:", "In/Pass:", "In/XPass:" }, { "Out/Block:", "Out/Pass:", "Out/XPass:" } }; static const char *istats_text[2][2][2] = { { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } }, { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } } }; #define RVTEST(fct) do { \ if ((!(opts & PF_OPT_NOACTION) || \ (opts & PF_OPT_DUMMYACTION)) && \ (fct)) { \ if ((opts & PF_OPT_RECURSE) == 0) \ warnx("%s", pf_strerror(errno)); \ goto _error; \ } \ } while (0) #define CREATE_TABLE do { \ warn_duplicate_tables(table.pfrt_name, \ table.pfrt_anchor); \ table.pfrt_flags |= PFR_TFLAG_PERSIST; \ if ((!(opts & PF_OPT_NOACTION) || \ (opts & PF_OPT_DUMMYACTION)) && \ (pfr_add_table(&table, &nadd, flags)) && \ (errno != EPERM)) { \ warnx("%s", pf_strerror(errno)); \ goto _error; \ } \ if (nadd) { \ xprintf(opts, "%d table created", nadd); \ if (opts & PF_OPT_NOACTION) \ return (0); \ } \ table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \ } while(0) int pfctl_do_clear_tables(const char *anchor, int opts) { int rv; if ((rv = pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts)) == -1) { if ((opts & PF_OPT_IGNFAIL) == 0) exit(1); } return (rv); } void pfctl_show_tables(const char *anchor, int opts) { if (pfctl_table(0, NULL, NULL, "-s", NULL, anchor, opts)) exit(1); } int pfctl_table(int argc, char *argv[], char *tname, const char *command, char *file, const char *anchor, int opts) { struct pfr_table table; struct pfr_buffer b, b2; struct pfr_addr *a, *a2; int nadd = 0, ndel = 0, nchange = 0, nzero = 0; int rv = 0, flags = 0, nmatch = 0; void *p; if (command == NULL) usage(); if (opts & PF_OPT_NOACTION) flags |= PFR_FLAG_DUMMY; bzero(&b, sizeof(b)); bzero(&b2, sizeof(b2)); bzero(&table, sizeof(table)); if (tname != NULL) { if (strlen(tname) >= PF_TABLE_NAME_SIZE) usage(); if (strlcpy(table.pfrt_name, tname, sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) errx(1, "pfctl_table: strlcpy"); } if (strlcpy(table.pfrt_anchor, anchor, sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor)) errx(1, "pfctl_table: strlcpy"); if (!strcmp(command, "-F")) { if (argc || file != NULL) usage(); RVTEST(pfctl_clear_tables(pfh, &table, &ndel, flags)); xprintf(opts, "%d tables deleted", ndel); } else if (!strcmp(command, "-s")) { b.pfrb_type = (opts & PF_OPT_VERBOSE2) ? PFRB_TSTATS : PFRB_TABLES; if (argc || file != NULL) usage(); if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0) pfctl_print_title("TABLES:"); if (opts & PF_OPT_VERBOSE2) { uintptr_t arg = opts & PF_OPT_DEBUG; pfctl_get_tstats(pfh, &table, (pfctl_get_tstats_fn)print_tstats, (void *)arg); } else { for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; RVTEST(pfr_get_tables(&table, b.pfrb_caddr, &b.pfrb_size, flags)); if (b.pfrb_size <= b.pfrb_msize) break; } if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0) pfctl_print_title("TABLES:"); PFRB_FOREACH(p, &b) print_table(p, opts & PF_OPT_VERBOSE, opts & PF_OPT_DEBUG); } } else if (!strcmp(command, "kill")) { if (argc || file != NULL) usage(); RVTEST(pfr_del_table(&table, &ndel, flags)); xprintf(opts, "%d table deleted", ndel); } else if (!strcmp(command, "flush")) { if (argc || file != NULL) usage(); RVTEST(pfr_clr_addrs(&table, &ndel, flags)); xprintf(opts, "%d addresses deleted", ndel); } else if (!strcmp(command, "add")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) goto _error; CREATE_TABLE; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size, &nadd, flags)); xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback != PFR_FB_NONE) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "delete")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) goto _error; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size, &ndel, flags)); xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback != PFR_FB_NONE) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "replace")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) goto _error; CREATE_TABLE; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; - for (;;) { - int sz2 = b.pfrb_msize; - - RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, - &sz2, &nadd, &ndel, &nchange, flags)); - if (sz2 <= b.pfrb_msize) { - b.pfrb_size = sz2; - break; - } else - pfr_buf_grow(&b, sz2); - } + RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size, + &nadd, &ndel, &nchange, flags)); if (nadd) xprintf(opts, "%d addresses added", nadd); if (ndel) xprintf(opts, "%d addresses deleted", ndel); if (nchange) xprintf(opts, "%d addresses changed", nchange); if (!nadd && !ndel && !nchange) xprintf(opts, "no changes"); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback != PFR_FB_NONE) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "expire")) { const char *errstr; u_int lifetime; b.pfrb_type = PFRB_ASTATS; b2.pfrb_type = PFRB_ADDRS; if (argc != 1 || file != NULL) usage(); lifetime = strtonum(*argv, 0, UINT_MAX, &errstr); if (errstr) errx(1, "expiry time: %s", errstr); for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; RVTEST(pfr_get_astats(&table, b.pfrb_caddr, &b.pfrb_size, flags)); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(p, &b) { ((struct pfr_astats *)p)->pfras_a.pfra_fback = PFR_FB_NONE; if (time(NULL) - ((struct pfr_astats *)p)->pfras_tzero > lifetime) if (pfr_buf_add(&b2, &((struct pfr_astats *)p)->pfras_a)) err(1, "duplicate buffer"); } if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_del_addrs(&table, b2.pfrb_caddr, b2.pfrb_size, &ndel, flags)); xprintf(opts, "%d/%d addresses expired", ndel, b2.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b2) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback != PFR_FB_NONE) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "reset")) { struct pfr_astats *as; b.pfrb_type = PFRB_ASTATS; b2.pfrb_type = PFRB_ADDRS; if (argc || file != NULL) usage(); do { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; RVTEST(pfr_get_astats(&table, b.pfrb_caddr, &b.pfrb_size, flags)); } while (b.pfrb_size > b.pfrb_msize); PFRB_FOREACH(as, &b) { as->pfras_a.pfra_fback = 0; if (nonzero_astats(as)) if (pfr_buf_add(&b2, &as->pfras_a)) err(1, "duplicate buffer"); } if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_clr_astats(&table, b2.pfrb_caddr, b2.pfrb_size, &nzero, flags)); xprintf(opts, "%d/%d stats cleared", nzero, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b2) if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "show")) { b.pfrb_type = (opts & PF_OPT_VERBOSE) ? PFRB_ASTATS : PFRB_ADDRS; if (argc || file != NULL) usage(); for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (opts & PF_OPT_VERBOSE) RVTEST(pfr_get_astats(&table, b.pfrb_caddr, &b.pfrb_size, flags)); else RVTEST(pfr_get_addrs(&table, b.pfrb_caddr, &b.pfrb_size, flags)); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(p, &b) if (opts & PF_OPT_VERBOSE) print_astats(p, opts & PF_OPT_USEDNS); else print_addrx(p, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "test")) { b.pfrb_type = PFRB_ADDRS; b2.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 1, opts)) goto _error; if (opts & PF_OPT_VERBOSE2) { flags |= PFR_FLAG_REPLACE; PFRB_FOREACH(a, &b) if (pfr_buf_add(&b2, a)) err(1, "duplicate buffer"); } RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size, &nmatch, flags)); xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size); if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2)) PFRB_FOREACH(a, &b) if (a->pfra_fback == PFR_FB_MATCH) print_addrx(a, NULL, opts & PF_OPT_USEDNS); if (opts & PF_OPT_VERBOSE2) { a2 = NULL; PFRB_FOREACH(a, &b) { a2 = pfr_buf_next(&b2, a2); print_addrx(a2, a, opts & PF_OPT_USEDNS); } } if (nmatch < b.pfrb_size) rv = 2; } else if (!strcmp(command, "zero") && (argc || file != NULL)) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) goto _error; if (opts & PF_OPT_VERBOSE) flags |= PFR_FLAG_FEEDBACK; RVTEST(pfr_clr_astats(&table, b.pfrb_caddr, b.pfrb_size, &nzero, flags)); xprintf(opts, "%d/%d addresses cleared", nzero, b.pfrb_size); if (opts & PF_OPT_VERBOSE) PFRB_FOREACH(a, &b) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) print_addrx(a, NULL, opts & PF_OPT_USEDNS); } else if (!strcmp(command, "zero")) { flags |= PFR_FLAG_ADDRSTOO; RVTEST(pfctl_clear_tstats(pfh, &table, &nzero, flags)); xprintf(opts, "%d table/stats cleared", nzero); } else warnx("pfctl_table: unknown command '%s'", command); goto _cleanup; _error: rv = -1; _cleanup: pfr_buf_clear(&b); pfr_buf_clear(&b2); return (rv); } void print_table(const struct pfr_table *ta, int verbose, int debug) { if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) return; if (verbose) printf("%c%c%c%c%c%c%c\t", (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-'); printf("%s", ta->pfrt_name); if (ta->pfrt_anchor[0] != '\0') printf("@%s", ta->pfrt_anchor); printf("\n"); } int print_tstats(const struct pfr_tstats *ts, int debug) { time_t time = ts->pfrts_tzero; int dir, op; char *ct; if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) return (0); ct = ctime(&time); print_table(&ts->pfrts_t, 1, debug); printf("\tAddresses: %d\n", ts->pfrts_cnt); if (ct) printf("\tCleared: %s", ct); else printf("\tCleared: %lld\n", (long long)time); printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], ts->pfrts_refcnt[PFR_REFCNT_RULE]); printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n", (unsigned long long)ts->pfrts_nomatch, (unsigned long long)ts->pfrts_match); for (dir = 0; dir < PFR_DIR_MAX; dir++) for (op = 0; op < PFR_OP_TABLE_MAX; op++) printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", stats_text[dir][op], (unsigned long long)ts->pfrts_packets[dir][op], (unsigned long long)ts->pfrts_bytes[dir][op]); return (0); } int load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, int nonetwork, int opts) { while (argc--) if (append_addr(b, *argv++, nonetwork, opts)) { if (errno) warn("cannot decode %s", argv[-1]); return (-1); } if (pfr_buf_load(b, file, nonetwork, append_addr, opts)) { warn("cannot load %s", file); return (-1); } return (0); } void print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) { char ch, buf[256] = "{error}"; char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' }; unsigned int fback, hostnet; fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32; inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf)); printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf); if (ad->pfra_net < hostnet) printf("/%d", ad->pfra_net); if (rad != NULL && fback != PFR_FB_NONE) { if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf)) errx(1, "print_addrx: strlcpy"); inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf)); printf("\t%c%s", (rad->pfra_not?'!':' '), buf); if (rad->pfra_net < hostnet) printf("/%d", rad->pfra_net); } if (rad != NULL && fback == PFR_FB_NONE) printf("\t nomatch"); if (dns && ad->pfra_net == hostnet) { char host[NI_MAXHOST]; struct sockaddr_storage ss; strlcpy(host, "?", sizeof(host)); bzero(&ss, sizeof(ss)); ss.ss_family = ad->pfra_af; if (ss.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&ss; sin->sin_len = sizeof(*sin); sin->sin_addr = ad->pfra_ip4addr; } else { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; sin6->sin6_len = sizeof(*sin6); sin6->sin6_addr = ad->pfra_ip6addr; } if (getnameinfo((struct sockaddr *)&ss, ss.ss_len, host, sizeof(host), NULL, 0, NI_NAMEREQD) == 0) printf("\t(%s)", host); } printf("\n"); } int nonzero_astats(struct pfr_astats *as) { uint64_t s = 0; for (int dir = 0; dir < PFR_DIR_MAX; dir++) for (int op = 0; op < PFR_OP_ADDR_MAX; op++) s |= as->pfras_packets[dir][op] | as->pfras_bytes[dir][op]; return (!!s); } void print_astats(struct pfr_astats *as, int dns) { time_t time = as->pfras_tzero; int dir, op; char *ct; ct = ctime(&time); print_addrx(&as->pfras_a, NULL, dns); if (ct) printf("\tCleared: %s", ct); else printf("\tCleared: %lld\n", (long long)time); if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT) return; for (dir = 0; dir < PFR_DIR_MAX; dir++) for (op = 0; op < PFR_OP_ADDR_MAX; op++) printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", stats_text[dir][op], (unsigned long long)as->pfras_packets[dir][op], (unsigned long long)as->pfras_bytes[dir][op]); } int pfctl_define_table(char *name, int flags, int addrs, const char *anchor, struct pfr_buffer *ab, u_int32_t ticket, struct pfr_uktable *ukt) { struct pfr_table tbl_buf; struct pfr_table *tbl; if (ukt == NULL) { bzero(&tbl_buf, sizeof(tbl_buf)); tbl = &tbl_buf; } else { if (ab->pfrb_size != 0) { /* * copy IP addresses which come with table from * temporal buffer to buffer attached to table. */ ukt->pfrukt_addrs = *ab; ab->pfrb_size = 0; ab->pfrb_msize = 0; ab->pfrb_caddr = NULL; } else memset(&ukt->pfrukt_addrs, 0, sizeof(struct pfr_buffer)); tbl = &ukt->pfrukt_t; } if (strlcpy(tbl->pfrt_name, name, sizeof(tbl->pfrt_name)) >= sizeof(tbl->pfrt_name) || strlcpy(tbl->pfrt_anchor, anchor, sizeof(tbl->pfrt_anchor)) >= sizeof(tbl->pfrt_anchor)) errx(1, "%s: strlcpy", __func__); tbl->pfrt_flags = flags; DBGPRINT("%s %s@%s [%x]\n", __func__, tbl->pfrt_name, tbl->pfrt_anchor, tbl->pfrt_flags); /* * non-root anchors processed by parse.y are loaded to kernel later. * Here we load tables, which are either created for root anchor * or by 'pfctl -t ... -T ...' command. */ if (ukt != NULL) return (0); return (pfr_ina_define(tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0)); } void warn_duplicate_tables(const char *tablename, const char *anchorname) { struct pfr_buffer b; struct pfr_table *t; bzero(&b, sizeof(b)); b.pfrb_type = PFRB_TABLES; for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (pfr_get_tables(NULL, b.pfrb_caddr, &b.pfrb_size, PFR_FLAG_ALLRSETS)) err(1, "pfr_get_tables"); if (b.pfrb_size <= b.pfrb_msize) break; } PFRB_FOREACH(t, &b) { if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE)) continue; if (!strcmp(anchorname, t->pfrt_anchor)) continue; if (!strcmp(tablename, t->pfrt_name)) warnx("warning: table <%s> already defined" " in anchor \"%s\"", tablename, t->pfrt_anchor[0] ? t->pfrt_anchor : "/"); } pfr_buf_clear(&b); } void xprintf(int opts, const char *fmt, ...) { va_list args; if (opts & PF_OPT_QUIET) return; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); if (opts & PF_OPT_DUMMYACTION) fprintf(stderr, " (dummy).\n"); else if (opts & PF_OPT_NOACTION) fprintf(stderr, " (syntax only).\n"); else fprintf(stderr, ".\n"); } /* interface stuff */ void pfctl_show_ifaces(const char *filter, int opts) { struct pfr_buffer b; struct pfi_kif *p; bzero(&b, sizeof(b)); b.pfrb_type = PFRB_IFACES; for (;;) { pfr_buf_grow(&b, b.pfrb_size); b.pfrb_size = b.pfrb_msize; if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size)) errx(1, "%s", pf_strerror(errno)); if (b.pfrb_size <= b.pfrb_msize) break; } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INTERFACES:"); PFRB_FOREACH(p, &b) print_iface(p, opts); } void print_iface(struct pfi_kif *p, int opts) { time_t tzero = p->pfik_tzero; int i, af, dir, act; char *ct; printf("%s", p->pfik_name); if (opts & PF_OPT_VERBOSE) { if (p->pfik_flags & PFI_IFLAG_SKIP) printf(" (skip)"); } printf("\n"); if (!(opts & PF_OPT_VERBOSE2)) return; ct = ctime(&tzero); if (ct) printf("\tCleared: %s", ct); else printf("\tCleared: %lld\n", (long long)tzero); printf("\tReferences: %-18d\n", p->pfik_rulerefs); for (i = 0; i < 8; i++) { af = (i>>2) & 1; dir = (i>>1) &1; act = i & 1; printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n", istats_text[af][dir][act], (unsigned long long)p->pfik_packets[af][dir][act], (unsigned long long)p->pfik_bytes[af][dir][act]); } } diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 52db00f6ce0b..ce266a267f3c 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1,2787 +1,2788 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * 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. * * $OpenBSD: pfvar.h,v 1.282 2009/01/29 15:12:28 pyr Exp $ */ #ifndef _NET_PFVAR_H_ #define _NET_PFVAR_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _KERNEL #include #include #include #include #include #include #endif #include #include #include #ifdef _KERNEL #define PF_PFIL_NOREFRAGMENT 0x80000000 #if defined(__arm__) #define PF_WANT_32_TO_64_COUNTER #endif /* * A hybrid of 32-bit and 64-bit counters which can be used on platforms where * counter(9) is very expensive. * * As 32-bit counters are expected to overflow, a periodic job sums them up to * a saved 64-bit state. Fetching the value still walks all CPUs to get the most * current snapshot. */ #ifdef PF_WANT_32_TO_64_COUNTER struct pf_counter_u64_pcpu { u_int32_t current; u_int32_t snapshot; }; struct pf_counter_u64 { struct pf_counter_u64_pcpu *pfcu64_pcpu; u_int64_t pfcu64_value; seqc_t pfcu64_seqc; }; static inline int pf_counter_u64_init(struct pf_counter_u64 *pfcu64, int flags) { pfcu64->pfcu64_value = 0; pfcu64->pfcu64_seqc = 0; pfcu64->pfcu64_pcpu = uma_zalloc_pcpu(pcpu_zone_8, flags | M_ZERO); if (__predict_false(pfcu64->pfcu64_pcpu == NULL)) return (ENOMEM); return (0); } static inline void pf_counter_u64_deinit(struct pf_counter_u64 *pfcu64) { uma_zfree_pcpu(pcpu_zone_8, pfcu64->pfcu64_pcpu); } static inline void pf_counter_u64_critical_enter(void) { critical_enter(); } static inline void pf_counter_u64_critical_exit(void) { critical_exit(); } static inline void pf_counter_u64_rollup_protected(struct pf_counter_u64 *pfcu64, uint64_t n) { MPASS(curthread->td_critnest > 0); pfcu64->pfcu64_value += n; } static inline void pf_counter_u64_add_protected(struct pf_counter_u64 *pfcu64, uint32_t n) { struct pf_counter_u64_pcpu *pcpu; u_int32_t val; MPASS(curthread->td_critnest > 0); pcpu = zpcpu_get(pfcu64->pfcu64_pcpu); val = atomic_load_int(&pcpu->current); atomic_store_int(&pcpu->current, val + n); } static inline void pf_counter_u64_add(struct pf_counter_u64 *pfcu64, uint32_t n) { critical_enter(); pf_counter_u64_add_protected(pfcu64, n); critical_exit(); } static inline u_int64_t pf_counter_u64_periodic(struct pf_counter_u64 *pfcu64) { struct pf_counter_u64_pcpu *pcpu; u_int64_t sum; u_int32_t val; int cpu; MPASS(curthread->td_critnest > 0); seqc_write_begin(&pfcu64->pfcu64_seqc); sum = pfcu64->pfcu64_value; CPU_FOREACH(cpu) { pcpu = zpcpu_get_cpu(pfcu64->pfcu64_pcpu, cpu); val = atomic_load_int(&pcpu->current); sum += (uint32_t)(val - pcpu->snapshot); pcpu->snapshot = val; } pfcu64->pfcu64_value = sum; seqc_write_end(&pfcu64->pfcu64_seqc); return (sum); } static inline u_int64_t pf_counter_u64_fetch(const struct pf_counter_u64 *pfcu64) { struct pf_counter_u64_pcpu *pcpu; u_int64_t sum; seqc_t seqc; int cpu; for (;;) { seqc = seqc_read(&pfcu64->pfcu64_seqc); sum = 0; CPU_FOREACH(cpu) { pcpu = zpcpu_get_cpu(pfcu64->pfcu64_pcpu, cpu); sum += (uint32_t)(atomic_load_int(&pcpu->current) -pcpu->snapshot); } sum += pfcu64->pfcu64_value; if (seqc_consistent(&pfcu64->pfcu64_seqc, seqc)) break; } return (sum); } static inline void pf_counter_u64_zero_protected(struct pf_counter_u64 *pfcu64) { struct pf_counter_u64_pcpu *pcpu; int cpu; MPASS(curthread->td_critnest > 0); seqc_write_begin(&pfcu64->pfcu64_seqc); CPU_FOREACH(cpu) { pcpu = zpcpu_get_cpu(pfcu64->pfcu64_pcpu, cpu); pcpu->snapshot = atomic_load_int(&pcpu->current); } pfcu64->pfcu64_value = 0; seqc_write_end(&pfcu64->pfcu64_seqc); } static inline void pf_counter_u64_zero(struct pf_counter_u64 *pfcu64) { critical_enter(); pf_counter_u64_zero_protected(pfcu64); critical_exit(); } #else struct pf_counter_u64 { counter_u64_t counter; }; static inline int pf_counter_u64_init(struct pf_counter_u64 *pfcu64, int flags) { pfcu64->counter = counter_u64_alloc(flags); if (__predict_false(pfcu64->counter == NULL)) return (ENOMEM); return (0); } static inline void pf_counter_u64_deinit(struct pf_counter_u64 *pfcu64) { counter_u64_free(pfcu64->counter); } static inline void pf_counter_u64_critical_enter(void) { } static inline void pf_counter_u64_critical_exit(void) { } static inline void pf_counter_u64_rollup_protected(struct pf_counter_u64 *pfcu64, uint64_t n) { counter_u64_add(pfcu64->counter, n); } static inline void pf_counter_u64_add_protected(struct pf_counter_u64 *pfcu64, uint32_t n) { counter_u64_add(pfcu64->counter, n); } static inline void pf_counter_u64_add(struct pf_counter_u64 *pfcu64, uint32_t n) { pf_counter_u64_add_protected(pfcu64, n); } static inline u_int64_t pf_counter_u64_fetch(const struct pf_counter_u64 *pfcu64) { return (counter_u64_fetch(pfcu64->counter)); } static inline void pf_counter_u64_zero_protected(struct pf_counter_u64 *pfcu64) { counter_u64_zero(pfcu64->counter); } static inline void pf_counter_u64_zero(struct pf_counter_u64 *pfcu64) { pf_counter_u64_zero_protected(pfcu64); } #endif #define pf_get_timestamp(prule)({ \ uint32_t _ts = 0; \ uint32_t __ts; \ int cpu; \ CPU_FOREACH(cpu) { \ __ts = *zpcpu_get_cpu(prule->timestamp, cpu); \ if (__ts > _ts) \ _ts = __ts; \ } \ _ts; \ }) #define pf_update_timestamp(prule) \ do { \ critical_enter(); \ *zpcpu_get((prule)->timestamp) = time_second; \ critical_exit(); \ } while (0) #define pf_timestamp_pcpu_zone (sizeof(time_t) == 4 ? pcpu_zone_4 : pcpu_zone_8) _Static_assert(sizeof(time_t) == 4 || sizeof(time_t) == 8, "unexpected time_t size"); SYSCTL_DECL(_net_pf); MALLOC_DECLARE(M_PF); MALLOC_DECLARE(M_PFHASH); MALLOC_DECLARE(M_PF_RULE_ITEM); SDT_PROVIDER_DECLARE(pf); SDT_PROBE_DECLARE(pf, , test, reason_set); SDT_PROBE_DECLARE(pf, , log, log); #define DPFPRINTF(n, fmt, x...) \ do { \ SDT_PROBE2(pf, , log, log, (n), fmt); \ if (V_pf_status.debug >= (n)) \ printf(fmt "\n", ##x); \ } while (0) struct pfi_dynaddr { TAILQ_ENTRY(pfi_dynaddr) entry; struct pf_addr pfid_addr4; struct pf_addr pfid_mask4; struct pf_addr pfid_addr6; struct pf_addr pfid_mask6; struct pfr_ktable *pfid_kt; struct pfi_kkif *pfid_kif; int pfid_net; /* mask or 128 */ int pfid_acnt4; /* address count IPv4 */ int pfid_acnt6; /* address count IPv6 */ sa_family_t pfid_af; /* rule af */ u_int8_t pfid_iflags; /* PFI_AFLAG_* */ }; #define PF_NAME "pf" #define PF_HASHROW_ASSERT(h) mtx_assert(&(h)->lock, MA_OWNED) #define PF_HASHROW_LOCK(h) mtx_lock(&(h)->lock) #define PF_HASHROW_UNLOCK(h) mtx_unlock(&(h)->lock) #ifdef INVARIANTS #define PF_STATE_LOCK(s) \ do { \ struct pf_kstate *_s = (s); \ struct pf_idhash *_ih = &V_pf_idhash[PF_IDHASH(_s)]; \ MPASS(_s->lock == &_ih->lock); \ mtx_lock(_s->lock); \ } while (0) #define PF_STATE_UNLOCK(s) \ do { \ struct pf_kstate *_s = (s); \ struct pf_idhash *_ih = &V_pf_idhash[PF_IDHASH(_s)]; \ MPASS(_s->lock == &_ih->lock); \ mtx_unlock(_s->lock); \ } while (0) #else #define PF_STATE_LOCK(s) mtx_lock((s)->lock) #define PF_STATE_UNLOCK(s) mtx_unlock((s)->lock) #endif #ifdef INVARIANTS #define PF_STATE_LOCK_ASSERT(s) \ do { \ struct pf_kstate *_s = (s); \ struct pf_idhash *_ih = &V_pf_idhash[PF_IDHASH(_s)]; \ MPASS(_s->lock == &_ih->lock); \ PF_HASHROW_ASSERT(_ih); \ } while (0) #else /* !INVARIANTS */ #define PF_STATE_LOCK_ASSERT(s) do {} while (0) #endif /* INVARIANTS */ #ifdef INVARIANTS #define PF_SRC_NODE_LOCK(sn) \ do { \ struct pf_ksrc_node *_sn = (sn); \ struct pf_srchash *_sh = &V_pf_srchash[ \ pf_hashsrc(&_sn->addr, _sn->af)]; \ MPASS(_sn->lock == &_sh->lock); \ mtx_lock(_sn->lock); \ } while (0) #define PF_SRC_NODE_UNLOCK(sn) \ do { \ struct pf_ksrc_node *_sn = (sn); \ struct pf_srchash *_sh = &V_pf_srchash[ \ pf_hashsrc(&_sn->addr, _sn->af)]; \ MPASS(_sn->lock == &_sh->lock); \ mtx_unlock(_sn->lock); \ } while (0) #else #define PF_SRC_NODE_LOCK(sn) mtx_lock((sn)->lock) #define PF_SRC_NODE_UNLOCK(sn) mtx_unlock((sn)->lock) #endif #ifdef INVARIANTS #define PF_SRC_NODE_LOCK_ASSERT(sn) \ do { \ struct pf_ksrc_node *_sn = (sn); \ struct pf_srchash *_sh = &V_pf_srchash[ \ pf_hashsrc(&_sn->addr, _sn->af)]; \ MPASS(_sn->lock == &_sh->lock); \ PF_HASHROW_ASSERT(_sh); \ } while (0) #else /* !INVARIANTS */ #define PF_SRC_NODE_LOCK_ASSERT(sn) do {} while (0) #endif /* INVARIANTS */ extern struct mtx_padalign pf_unlnkdrules_mtx; #define PF_UNLNKDRULES_LOCK() mtx_lock(&pf_unlnkdrules_mtx) #define PF_UNLNKDRULES_UNLOCK() mtx_unlock(&pf_unlnkdrules_mtx) #define PF_UNLNKDRULES_ASSERT() mtx_assert(&pf_unlnkdrules_mtx, MA_OWNED) extern struct sx pf_config_lock; #define PF_CONFIG_LOCK() sx_xlock(&pf_config_lock) #define PF_CONFIG_UNLOCK() sx_xunlock(&pf_config_lock) #define PF_CONFIG_ASSERT() sx_assert(&pf_config_lock, SA_XLOCKED) VNET_DECLARE(struct rmlock, pf_rules_lock); #define V_pf_rules_lock VNET(pf_rules_lock) #define PF_RULES_RLOCK_TRACKER struct rm_priotracker _pf_rules_tracker #define PF_RULES_RLOCK() rm_rlock(&V_pf_rules_lock, &_pf_rules_tracker) #define PF_RULES_RUNLOCK() rm_runlock(&V_pf_rules_lock, &_pf_rules_tracker) #define PF_RULES_WLOCK() rm_wlock(&V_pf_rules_lock) #define PF_RULES_WUNLOCK() rm_wunlock(&V_pf_rules_lock) #define PF_RULES_WOWNED() rm_wowned(&V_pf_rules_lock) #define PF_RULES_ASSERT() rm_assert(&V_pf_rules_lock, RA_LOCKED) #define PF_RULES_RASSERT() rm_assert(&V_pf_rules_lock, RA_RLOCKED) #define PF_RULES_WASSERT() rm_assert(&V_pf_rules_lock, RA_WLOCKED) VNET_DECLARE(struct rmlock, pf_tags_lock); #define V_pf_tags_lock VNET(pf_tags_lock) #define PF_TAGS_RLOCK_TRACKER struct rm_priotracker _pf_tags_tracker #define PF_TAGS_RLOCK() rm_rlock(&V_pf_tags_lock, &_pf_tags_tracker) #define PF_TAGS_RUNLOCK() rm_runlock(&V_pf_tags_lock, &_pf_tags_tracker) #define PF_TAGS_WLOCK() rm_wlock(&V_pf_tags_lock) #define PF_TAGS_WUNLOCK() rm_wunlock(&V_pf_tags_lock) #define PF_TAGS_WASSERT() rm_assert(&V_pf_tags_lock, RA_WLOCKED) extern struct mtx_padalign pf_table_stats_lock; #define PF_TABLE_STATS_LOCK() mtx_lock(&pf_table_stats_lock) #define PF_TABLE_STATS_UNLOCK() mtx_unlock(&pf_table_stats_lock) #define PF_TABLE_STATS_OWNED() mtx_owned(&pf_table_stats_lock) #define PF_TABLE_STATS_ASSERT() mtx_assert(&pf_table_stats_lock, MA_OWNED) extern struct sx pf_end_lock; #define PF_MODVER 1 #define PFLOG_MODVER 1 #define PFSYNC_MODVER 1 #define PFLOG_MINVER 1 #define PFLOG_PREFVER PFLOG_MODVER #define PFLOG_MAXVER 1 #define PFSYNC_MINVER 1 #define PFSYNC_PREFVER PFSYNC_MODVER #define PFSYNC_MAXVER 1 #ifdef INET #ifndef INET6 #define PF_INET_ONLY #endif /* ! INET6 */ #endif /* INET */ #ifdef INET6 #ifndef INET #define PF_INET6_ONLY #endif /* ! INET */ #endif /* INET6 */ #ifdef INET #ifdef INET6 #define PF_INET_INET6 #endif /* INET6 */ #endif /* INET */ #else #define PF_INET_INET6 #endif /* _KERNEL */ /* Both IPv4 and IPv6 */ #ifdef PF_INET_INET6 #define PF_AEQ(a, b, c) \ ((c == AF_INET && (a)->addr32[0] == (b)->addr32[0]) || \ (c == AF_INET6 && (a)->addr32[3] == (b)->addr32[3] && \ (a)->addr32[2] == (b)->addr32[2] && \ (a)->addr32[1] == (b)->addr32[1] && \ (a)->addr32[0] == (b)->addr32[0])) \ #define PF_ANEQ(a, b, c) \ ((c == AF_INET && (a)->addr32[0] != (b)->addr32[0]) || \ (c == AF_INET6 && ((a)->addr32[0] != (b)->addr32[0] || \ (a)->addr32[1] != (b)->addr32[1] || \ (a)->addr32[2] != (b)->addr32[2] || \ (a)->addr32[3] != (b)->addr32[3]))) \ #define PF_AZERO(a, c) \ ((c == AF_INET && !(a)->addr32[0]) || \ (c == AF_INET6 && !(a)->addr32[0] && !(a)->addr32[1] && \ !(a)->addr32[2] && !(a)->addr32[3] )) \ #else /* Just IPv6 */ #ifdef PF_INET6_ONLY #define PF_AEQ(a, b, c) \ ((a)->addr32[3] == (b)->addr32[3] && \ (a)->addr32[2] == (b)->addr32[2] && \ (a)->addr32[1] == (b)->addr32[1] && \ (a)->addr32[0] == (b)->addr32[0]) \ #define PF_ANEQ(a, b, c) \ ((a)->addr32[3] != (b)->addr32[3] || \ (a)->addr32[2] != (b)->addr32[2] || \ (a)->addr32[1] != (b)->addr32[1] || \ (a)->addr32[0] != (b)->addr32[0]) \ #define PF_AZERO(a, c) \ (!(a)->addr32[0] && \ !(a)->addr32[1] && \ !(a)->addr32[2] && \ !(a)->addr32[3] ) \ #else /* Just IPv4 */ #ifdef PF_INET_ONLY #define PF_AEQ(a, b, c) \ ((a)->addr32[0] == (b)->addr32[0]) #define PF_ANEQ(a, b, c) \ ((a)->addr32[0] != (b)->addr32[0]) #define PF_AZERO(a, c) \ (!(a)->addr32[0]) #endif /* PF_INET_ONLY */ #endif /* PF_INET6_ONLY */ #endif /* PF_INET_INET6 */ #ifdef _KERNEL void unhandled_af(int) __dead2; static void inline pf_addrcpy(struct pf_addr *dst, const struct pf_addr *src, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: memcpy(&dst->v4, &src->v4, sizeof(dst->v4)); break; #endif /* INET */ #ifdef INET6 case AF_INET6: memcpy(&dst->v6, &src->v6, sizeof(dst->v6)); break; #endif /* INET6 */ default: unhandled_af(af); } } #endif /* * XXX callers not FIB-aware in our version of pf yet. * OpenBSD fixed it later it seems, 2010/05/07 13:33:16 claudio. */ #define PF_MISMATCHAW(aw, x, af, neg, ifp, rtid) \ ( \ (((aw)->type == PF_ADDR_NOROUTE && \ pf_routable((x), (af), NULL, (rtid))) || \ (((aw)->type == PF_ADDR_URPFFAILED && (ifp) != NULL && \ pf_routable((x), (af), (ifp), (rtid))) || \ ((aw)->type == PF_ADDR_TABLE && \ !pfr_match_addr((aw)->p.tbl, (x), (af))) || \ ((aw)->type == PF_ADDR_DYNIFTL && \ !pfi_match_addr((aw)->p.dyn, (x), (af))) || \ ((aw)->type == PF_ADDR_RANGE && \ !pf_match_addr_range(&(aw)->v.a.addr, \ &(aw)->v.a.mask, (x), (af))) || \ ((aw)->type == PF_ADDR_ADDRMASK && \ !PF_AZERO(&(aw)->v.a.mask, (af)) && \ !pf_match_addr(0, &(aw)->v.a.addr, \ &(aw)->v.a.mask, (x), (af))))) != \ (neg) \ ) #define PF_ALGNMNT(off) (((off) % 2) == 0) /* * At the moment there are no rules which have both NAT and RDR actions, * apart from af-to rules, but those don't to source tracking for address * translation. And the r->rdr pool is used for both NAT and RDR. * So there is no PF_SN_RDR. */ enum pf_sn_types { PF_SN_LIMIT, PF_SN_NAT, PF_SN_ROUTE, PF_SN_MAX }; typedef enum pf_sn_types pf_sn_types_t; #define PF_SN_TYPE_NAMES { \ "limit source-track", \ "NAT/RDR sticky-address", \ "route sticky-address", \ NULL \ } #ifdef _KERNEL struct pf_kpooladdr { struct pf_addr_wrap addr; TAILQ_ENTRY(pf_kpooladdr) entries; char ifname[IFNAMSIZ]; sa_family_t af; struct pfi_kkif *kif; }; TAILQ_HEAD(pf_kpalist, pf_kpooladdr); struct pf_kpool { struct mtx mtx; struct pf_kpalist list; struct pf_kpooladdr *cur; struct pf_poolhashkey key; struct pf_addr counter; struct pf_mape_portset mape; int tblidx; u_int16_t proxy_port[2]; u_int8_t opts; sa_family_t ipv6_nexthop_af; }; struct pf_rule_actions { struct pf_addr rt_addr; struct pfi_kkif *rt_kif; int32_t rtableid; uint32_t flags; uint16_t qid; uint16_t pqid; uint16_t max_mss; uint16_t dnpipe; uint16_t dnrpipe; /* Reverse direction pipe */ sa_family_t rt_af; uint8_t log; uint8_t set_tos; uint8_t min_ttl; uint8_t set_prio[2]; uint8_t rt; uint8_t allow_opts; uint16_t max_pkt_size; }; union pf_keth_rule_ptr { struct pf_keth_rule *ptr; uint32_t nr; }; struct pf_keth_rule_addr { uint8_t addr[ETHER_ADDR_LEN]; uint8_t mask[ETHER_ADDR_LEN]; bool neg; uint8_t isset; }; struct pf_keth_anchor; TAILQ_HEAD(pf_keth_ruleq, pf_keth_rule); struct pf_keth_ruleset { struct pf_keth_ruleq rules[2]; struct pf_keth_rules { struct pf_keth_ruleq *rules; int open; uint32_t ticket; } active, inactive; struct vnet *vnet; struct pf_keth_anchor *anchor; }; RB_HEAD(pf_keth_anchor_global, pf_keth_anchor); RB_HEAD(pf_keth_anchor_node, pf_keth_anchor); struct pf_keth_anchor { RB_ENTRY(pf_keth_anchor) entry_node; RB_ENTRY(pf_keth_anchor) entry_global; struct pf_keth_anchor *parent; struct pf_keth_anchor_node children; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pf_keth_ruleset ruleset; int refcnt; /* anchor rules */ uint8_t anchor_relative; uint8_t anchor_wildcard; }; RB_PROTOTYPE(pf_keth_anchor_node, pf_keth_anchor, entry_node, pf_keth_anchor_compare); RB_PROTOTYPE(pf_keth_anchor_global, pf_keth_anchor, entry_global, pf_keth_anchor_compare); struct pf_keth_rule { #define PFE_SKIP_IFP 0 #define PFE_SKIP_DIR 1 #define PFE_SKIP_PROTO 2 #define PFE_SKIP_SRC_ADDR 3 #define PFE_SKIP_DST_ADDR 4 #define PFE_SKIP_SRC_IP_ADDR 5 #define PFE_SKIP_DST_IP_ADDR 6 #define PFE_SKIP_COUNT 7 union pf_keth_rule_ptr skip[PFE_SKIP_COUNT]; TAILQ_ENTRY(pf_keth_rule) entries; struct pf_keth_anchor *anchor; u_int8_t anchor_relative; u_int8_t anchor_wildcard; uint32_t nr; bool quick; /* Filter */ char ifname[IFNAMSIZ]; struct pfi_kkif *kif; bool ifnot; uint8_t direction; uint16_t proto; struct pf_keth_rule_addr src, dst; struct pf_rule_addr ipsrc, ipdst; char match_tagname[PF_TAG_NAME_SIZE]; uint16_t match_tag; bool match_tag_not; /* Stats */ counter_u64_t evaluations; counter_u64_t packets[2]; counter_u64_t bytes[2]; time_t *timestamp; /* Action */ char qname[PF_QNAME_SIZE]; int qid; char tagname[PF_TAG_NAME_SIZE]; uint16_t tag; char bridge_to_name[IFNAMSIZ]; struct pfi_kkif *bridge_to; uint8_t action; uint16_t dnpipe; uint32_t dnflags; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; }; struct pf_kthreshold { uint32_t limit; uint32_t seconds; struct counter_rate *cr; }; RB_HEAD(pf_krule_global, pf_krule); RB_PROTOTYPE(pf_krule_global, pf_krule, entry_global, pf_krule_compare); struct pf_krule { struct pf_rule_addr src; struct pf_rule_addr dst; struct pf_krule *skip[PF_SKIP_COUNT]; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; char ifname[IFNAMSIZ]; char rcv_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(pf_krule) entries; struct pf_kpool nat; struct pf_kpool rdr; struct pf_kpool route; struct pf_kthreshold pktrate; struct pf_counter_u64 evaluations; struct pf_counter_u64 packets[2]; struct pf_counter_u64 bytes[2]; time_t *timestamp; struct pfi_kkif *kif; struct pfi_kkif *rcv_kif; struct pf_kanchor *anchor; struct pfr_ktable *overload_tbl; pf_osfp_t os_fingerprint; int32_t rtableid; u_int32_t timeout[PFTM_MAX]; u_int32_t max_states; u_int32_t max_src_nodes; u_int32_t max_src_states; u_int32_t max_src_conn; struct { u_int32_t limit; u_int32_t seconds; } max_src_conn_rate; uint16_t max_pkt_size; u_int16_t qid; u_int16_t pqid; u_int16_t dnpipe; u_int16_t dnrpipe; u_int32_t free_flags; u_int32_t nr; u_int32_t prob; uid_t cuid; pid_t cpid; counter_u64_t states_cur; counter_u64_t states_tot; counter_u64_t src_nodes[PF_SN_MAX]; u_int16_t return_icmp; u_int16_t return_icmp6; u_int16_t max_mss; u_int16_t tag; u_int16_t match_tag; u_int16_t scrub_flags; struct pf_rule_uid uid; struct pf_rule_gid gid; u_int32_t rule_flag; uint32_t rule_ref; u_int8_t action; u_int8_t direction; u_int8_t log; u_int8_t logif; u_int8_t quick; u_int8_t ifnot; u_int8_t match_tag_not; u_int8_t natpass; u_int8_t keep_state; sa_family_t af; u_int8_t proto; uint16_t type; uint16_t code; u_int8_t flags; u_int8_t flagset; u_int8_t min_ttl; u_int8_t allow_opts; u_int8_t rt; u_int8_t return_ttl; u_int8_t tos; u_int8_t set_tos; u_int8_t anchor_relative; u_int8_t anchor_wildcard; u_int8_t flush; u_int8_t prio; u_int8_t set_prio[2]; sa_family_t naf; u_int8_t rcvifnot; struct { struct pf_addr addr; u_int16_t port; } divert; u_int8_t md5sum[PF_MD5_DIGEST_LENGTH]; RB_ENTRY(pf_krule) entry_global; #ifdef PF_WANT_32_TO_64_COUNTER LIST_ENTRY(pf_krule) allrulelist; bool allrulelinked; #endif time_t exptime; }; struct pf_krule_item { SLIST_ENTRY(pf_krule_item) entry; struct pf_krule *r; }; SLIST_HEAD(pf_krule_slist, pf_krule_item); struct pf_ksrc_node { LIST_ENTRY(pf_ksrc_node) entry; struct pf_addr addr; struct pf_addr raddr; struct pf_krule_slist match_rules; struct pf_krule *rule; struct pfi_kkif *rkif; counter_u64_t bytes[2]; counter_u64_t packets[2]; u_int32_t states; u_int32_t conn; struct pf_kthreshold conn_rate; u_int32_t creation; u_int32_t expire; sa_family_t af; sa_family_t raf; u_int8_t ruletype; pf_sn_types_t type; struct mtx *lock; }; #endif struct pf_state_scrub { struct timeval pfss_last; /* time received last packet */ u_int32_t pfss_tsecr; /* last echoed timestamp */ u_int32_t pfss_tsval; /* largest timestamp */ u_int32_t pfss_tsval0; /* original timestamp */ u_int16_t pfss_flags; #define PFSS_TIMESTAMP 0x0001 /* modulate timestamp */ #define PFSS_PAWS 0x0010 /* stricter PAWS checks */ #define PFSS_PAWS_IDLED 0x0020 /* was idle too long. no PAWS */ #define PFSS_DATA_TS 0x0040 /* timestamp on data packets */ #define PFSS_DATA_NOTS 0x0080 /* no timestamp on data packets */ u_int8_t pfss_ttl; /* stashed TTL */ u_int8_t pad; union { u_int32_t pfss_ts_mod; /* timestamp modulation */ u_int32_t pfss_v_tag; /* SCTP verification tag */ }; }; struct pf_state_host { struct pf_addr addr; u_int16_t port; u_int16_t pad; }; struct pf_state_peer { struct pf_state_scrub *scrub; /* state is scrubbed */ u_int32_t seqlo; /* Max sequence number sent */ u_int32_t seqhi; /* Max the other end ACKd + win */ u_int32_t seqdiff; /* Sequence number modulator */ u_int16_t max_win; /* largest window (pre scaling) */ u_int16_t mss; /* Maximum segment size option */ u_int8_t state; /* active state level */ u_int8_t wscale; /* window scaling factor */ u_int8_t tcp_est; /* Did we reach TCPS_ESTABLISHED */ u_int8_t pad[1]; }; /* Keep synced with struct pf_udp_endpoint. */ struct pf_udp_endpoint_cmp { struct pf_addr addr; uint16_t port; sa_family_t af; uint8_t pad[1]; }; struct pf_udp_endpoint { struct pf_addr addr; uint16_t port; sa_family_t af; uint8_t pad[1]; struct pf_udp_mapping *mapping; LIST_ENTRY(pf_udp_endpoint) entry; }; struct pf_udp_mapping { struct pf_udp_endpoint endpoints[2]; u_int refs; }; /* Keep synced with struct pf_state_key. */ struct pf_state_key_cmp { struct pf_addr addr[2]; u_int16_t port[2]; sa_family_t af; u_int8_t proto; u_int8_t pad[2]; }; struct pf_state_key { struct pf_addr addr[2]; u_int16_t port[2]; sa_family_t af; u_int8_t proto; u_int8_t pad[2]; LIST_ENTRY(pf_state_key) entry; TAILQ_HEAD(, pf_kstate) states[2]; }; #define PF_REVERSED_KEY(state, family) \ (((state)->key[PF_SK_WIRE]->af != (state)->key[PF_SK_STACK]->af) && \ ((state)->key[PF_SK_WIRE]->af != (family)) && \ ((state)->direction == PF_IN)) /* Keep synced with struct pf_kstate. */ struct pf_state_cmp { u_int64_t id; u_int32_t creatorid; u_int8_t direction; u_int8_t pad[3]; }; struct pf_state_scrub_export { uint16_t pfss_flags; uint8_t pfss_ttl; /* stashed TTL */ #define PF_SCRUB_FLAG_VALID 0x01 uint8_t scrub_flag; uint32_t pfss_ts_mod; /* timestamp modulation */ } __packed; struct pf_state_key_export { struct pf_addr addr[2]; uint16_t port[2]; }; struct pf_state_peer_export { struct pf_state_scrub_export scrub; /* state is scrubbed */ uint32_t seqlo; /* Max sequence number sent */ uint32_t seqhi; /* Max the other end ACKd + win */ uint32_t seqdiff; /* Sequence number modulator */ uint16_t max_win; /* largest window (pre scaling) */ uint16_t mss; /* Maximum segment size option */ uint8_t state; /* active state level */ uint8_t wscale; /* window scaling factor */ uint8_t dummy[6]; } __packed; _Static_assert(sizeof(struct pf_state_peer_export) == 32, "size incorrect"); struct pf_state_export { uint64_t version; #define PF_STATE_VERSION 20230404 uint64_t id; char ifname[IFNAMSIZ]; char orig_ifname[IFNAMSIZ]; struct pf_state_key_export key[2]; struct pf_state_peer_export src; struct pf_state_peer_export dst; struct pf_addr rt_addr; uint32_t rule; uint32_t anchor; uint32_t nat_rule; uint32_t creation; uint32_t expire; uint32_t spare0; uint64_t packets[2]; uint64_t bytes[2]; uint32_t creatorid; uint32_t spare1; sa_family_t af; uint8_t proto; uint8_t direction; uint8_t log; uint8_t state_flags_compat; uint8_t timeout; uint8_t sync_flags; uint8_t updates; uint16_t state_flags; uint16_t qid; uint16_t pqid; uint16_t dnpipe; uint16_t dnrpipe; 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]; uint8_t spare[72]; }; _Static_assert(sizeof(struct pf_state_export) == 384, "size incorrect"); #ifdef _KERNEL struct pf_kstate { /* * Area shared with pf_state_cmp */ u_int64_t id; u_int32_t creatorid; u_int8_t direction; u_int8_t pad[3]; /* * end of the area */ u_int16_t state_flags; u_int8_t timeout; u_int8_t sync_state; /* PFSYNC_S_x */ u_int8_t sync_updates; u_int refs; struct mtx *lock; TAILQ_ENTRY(pf_kstate) sync_list; TAILQ_ENTRY(pf_kstate) key_list[2]; LIST_ENTRY(pf_kstate) entry; struct pf_state_peer src; struct pf_state_peer dst; struct pf_krule_slist match_rules; struct pf_krule *rule; struct pf_krule *anchor; struct pf_krule *nat_rule; struct pf_state_key *key[2]; /* addresses stack and wire */ struct pf_udp_mapping *udp_mapping; struct pfi_kkif *kif; struct pfi_kkif *orig_kif; /* The real kif, even if we're a floating state (i.e. if == V_pfi_all). */ struct pf_ksrc_node *sns[PF_SN_MAX];/* source nodes */ u_int64_t packets[2]; u_int64_t bytes[2]; u_int64_t creation; u_int64_t expire; u_int32_t pfsync_time; struct pf_rule_actions act; u_int16_t tag; u_int16_t if_index_in; u_int16_t if_index_out; }; /* * 6 cache lines per struct, 10 structs per page. * Try to not grow the struct beyond that. */ _Static_assert(sizeof(struct pf_kstate) <= 384, "pf_kstate size crosses 384 bytes"); enum pf_test_status { PF_TEST_FAIL = -1, PF_TEST_OK, PF_TEST_QUICK }; struct pf_test_ctx { enum pf_test_status test_status; struct pf_pdesc *pd; struct pf_rule_actions act; uint8_t icmpcode; uint8_t icmptype; int icmp_dir; int state_icmp; int tag; int rewrite; u_short reason; struct pf_src_node *sns[PF_SN_MAX]; struct pf_krule *nr; struct pf_krule *tr; struct pf_krule **rm; struct pf_krule *a; struct pf_krule **am; struct pf_kruleset **rsm; struct pf_kruleset *arsm; struct pf_kruleset *aruleset; struct pf_state_key *sk; struct pf_state_key *nk; struct tcphdr *th; struct pf_udp_mapping *udp_mapping; struct pf_kpool *nat_pool; uint16_t virtual_type; uint16_t virtual_id; int depth; }; #define PF_ANCHOR_STACK_MAX 32 #endif /* * Unified state structures for pulling states out of the kernel * used by pfsync(4) and the pf(4) ioctl. */ struct pfsync_state_key { struct pf_addr addr[2]; u_int16_t port[2]; }; struct pfsync_state_1301 { u_int64_t id; char ifname[IFNAMSIZ]; struct pfsync_state_key key[2]; struct pf_state_peer_export src; struct pf_state_peer_export dst; struct pf_addr rt_addr; u_int32_t rule; u_int32_t anchor; u_int32_t nat_rule; u_int32_t creation; u_int32_t expire; u_int32_t packets[2][2]; u_int32_t bytes[2][2]; u_int32_t creatorid; sa_family_t af; u_int8_t proto; u_int8_t direction; u_int8_t __spare[2]; u_int8_t log; u_int8_t state_flags; u_int8_t timeout; u_int8_t sync_flags; u_int8_t updates; /* unused */ } __packed; struct pfsync_state_1400 { /* The beginning of the struct is compatible with pfsync_state_1301 */ u_int64_t id; char ifname[IFNAMSIZ]; struct pfsync_state_key key[2]; struct pf_state_peer_export src; struct pf_state_peer_export dst; struct pf_addr rt_addr; u_int32_t rule; u_int32_t anchor; u_int32_t nat_rule; u_int32_t creation; u_int32_t expire; u_int32_t packets[2][2]; u_int32_t bytes[2][2]; u_int32_t creatorid; sa_family_t af; u_int8_t proto; u_int8_t direction; u_int16_t state_flags; u_int8_t log; u_int8_t __spare; u_int8_t timeout; u_int8_t sync_flags; u_int8_t updates; /* unused */ /* The rest is not */ u_int16_t qid; u_int16_t pqid; u_int16_t dnpipe; u_int16_t dnrpipe; int32_t rtableid; u_int8_t min_ttl; u_int8_t set_tos; u_int16_t max_mss; u_int8_t set_prio[2]; u_int8_t rt; char rt_ifname[IFNAMSIZ]; } __packed; struct pfsync_state_1500 { /* The beginning of the struct is compatible with pfsync_state_1301 */ u_int64_t id; char ifname[IFNAMSIZ]; struct pfsync_state_key key[2]; struct pf_state_peer_export src; struct pf_state_peer_export dst; struct pf_addr rt_addr; u_int32_t rule; u_int32_t anchor; u_int32_t nat_rule; u_int32_t creation; u_int32_t expire; u_int32_t packets[2][2]; u_int32_t bytes[2][2]; u_int32_t creatorid; /* The rest is not, use the opportunity to fix alignment */ char tagname[PF_TAG_NAME_SIZE]; char rt_ifname[IFNAMSIZ]; char orig_ifname[IFNAMSIZ]; int32_t rtableid; u_int16_t state_flags; u_int16_t qid; u_int16_t pqid; u_int16_t dnpipe; u_int16_t dnrpipe; u_int16_t max_mss; sa_family_t wire_af; sa_family_t stack_af; sa_family_t rt_af; u_int8_t wire_proto; u_int8_t stack_proto; u_int8_t log; u_int8_t timeout; u_int8_t direction; u_int8_t rt; u_int8_t min_ttl; u_int8_t set_tos; u_int8_t set_prio[2]; u_int8_t spare[3]; /* Improve struct alignment */ } __packed; union pfsync_state_union { struct pfsync_state_1301 pfs_1301; struct pfsync_state_1400 pfs_1400; struct pfsync_state_1500 pfs_1500; } __packed; #ifdef _KERNEL /* pfsync */ typedef int pfsync_state_import_t(union pfsync_state_union *, int, int); typedef void pfsync_insert_state_t(struct pf_kstate *); typedef void pfsync_update_state_t(struct pf_kstate *); typedef void pfsync_delete_state_t(struct pf_kstate *); typedef void pfsync_clear_states_t(u_int32_t, const char *); typedef int pfsync_defer_t(struct pf_kstate *, struct mbuf *); typedef void pfsync_detach_ifnet_t(struct ifnet *); typedef void pflow_export_state_t(const struct pf_kstate *); typedef bool pf_addr_filter_func_t(const sa_family_t, const struct pf_addr *); VNET_DECLARE(pfsync_state_import_t *, pfsync_state_import_ptr); #define V_pfsync_state_import_ptr VNET(pfsync_state_import_ptr) VNET_DECLARE(pfsync_insert_state_t *, pfsync_insert_state_ptr); #define V_pfsync_insert_state_ptr VNET(pfsync_insert_state_ptr) VNET_DECLARE(pfsync_update_state_t *, pfsync_update_state_ptr); #define V_pfsync_update_state_ptr VNET(pfsync_update_state_ptr) VNET_DECLARE(pfsync_delete_state_t *, pfsync_delete_state_ptr); #define V_pfsync_delete_state_ptr VNET(pfsync_delete_state_ptr) VNET_DECLARE(pfsync_clear_states_t *, pfsync_clear_states_ptr); #define V_pfsync_clear_states_ptr VNET(pfsync_clear_states_ptr) VNET_DECLARE(pfsync_defer_t *, pfsync_defer_ptr); #define V_pfsync_defer_ptr VNET(pfsync_defer_ptr) VNET_DECLARE(pflow_export_state_t *, pflow_export_state_ptr); #define V_pflow_export_state_ptr VNET(pflow_export_state_ptr) extern pfsync_detach_ifnet_t *pfsync_detach_ifnet_ptr; void pfsync_state_export(union pfsync_state_union *, struct pf_kstate *, int); void pf_state_export(struct pf_state_export *, struct pf_kstate *); /* pflog */ struct pf_kruleset; struct pf_pdesc; typedef int pflog_packet_t(uint8_t, u_int8_t, struct pf_krule *, struct pf_krule *, struct pf_kruleset *, struct pf_pdesc *, int, struct pf_krule *); extern pflog_packet_t *pflog_packet_ptr; #endif /* _KERNEL */ #define PFSYNC_FLAG_SRCNODE 0x04 #define PFSYNC_FLAG_NATSRCNODE 0x08 /* for copies to/from network byte order */ /* ioctl interface also uses network byte order */ void pf_state_peer_hton(const struct pf_state_peer *, struct pf_state_peer_export *); void pf_state_peer_ntoh(const struct pf_state_peer_export *, struct pf_state_peer *); #define pf_state_counter_hton(s,d) do { \ d[0] = htonl((s>>32)&0xffffffff); \ d[1] = htonl(s&0xffffffff); \ } while (0) #define pf_state_counter_from_pfsync(s) \ (((u_int64_t)(s[0])<<32) | (u_int64_t)(s[1])) #define pf_state_counter_ntoh(s,d) do { \ d = ntohl(s[0]); \ d = d<<32; \ d += ntohl(s[1]); \ } while (0) TAILQ_HEAD(pf_krulequeue, pf_krule); struct pf_kanchor; struct pf_kruleset { struct { struct pf_krulequeue queues[2]; struct { struct pf_krulequeue *ptr; u_int32_t rcount; u_int32_t ticket; int open; struct pf_krule_global *tree; } active, inactive; } rules[PF_RULESET_MAX]; struct pf_kanchor *anchor; u_int32_t tticket; int tables; int topen; }; RB_HEAD(pf_kanchor_global, pf_kanchor); RB_HEAD(pf_kanchor_node, pf_kanchor); struct pf_kanchor { RB_ENTRY(pf_kanchor) entry_global; RB_ENTRY(pf_kanchor) entry_node; struct pf_kanchor *parent; struct pf_kanchor_node children; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pf_kruleset ruleset; int refcnt; /* anchor rules */ }; RB_PROTOTYPE(pf_kanchor_global, pf_kanchor, entry_global, pf_anchor_compare); RB_PROTOTYPE(pf_kanchor_node, pf_kanchor, entry_node, pf_kanchor_compare); #define PF_RESERVED_ANCHOR "_pf" #define PFR_TFLAG_PERSIST 0x00000001 #define PFR_TFLAG_CONST 0x00000002 #define PFR_TFLAG_ACTIVE 0x00000004 #define PFR_TFLAG_INACTIVE 0x00000008 #define PFR_TFLAG_REFERENCED 0x00000010 #define PFR_TFLAG_REFDANCHOR 0x00000020 #define PFR_TFLAG_COUNTERS 0x00000040 /* Adjust masks below when adding flags. */ #define PFR_TFLAG_USRMASK (PFR_TFLAG_PERSIST | \ PFR_TFLAG_CONST | \ PFR_TFLAG_COUNTERS) #define PFR_TFLAG_SETMASK (PFR_TFLAG_ACTIVE | \ PFR_TFLAG_INACTIVE | \ PFR_TFLAG_REFERENCED | \ PFR_TFLAG_REFDANCHOR) #define PFR_TFLAG_ALLMASK (PFR_TFLAG_PERSIST | \ PFR_TFLAG_CONST | \ PFR_TFLAG_ACTIVE | \ PFR_TFLAG_INACTIVE | \ PFR_TFLAG_REFERENCED | \ PFR_TFLAG_REFDANCHOR | \ PFR_TFLAG_COUNTERS) struct pf_keth_anchor_stackframe; struct pfr_table { char pfrt_anchor[MAXPATHLEN]; char pfrt_name[PF_TABLE_NAME_SIZE]; u_int32_t pfrt_flags; u_int8_t pfrt_fback; }; enum { PFR_FB_NONE, PFR_FB_MATCH, PFR_FB_ADDED, PFR_FB_DELETED, PFR_FB_CHANGED, PFR_FB_CLEARED, PFR_FB_DUPLICATE, PFR_FB_NOTMATCH, PFR_FB_CONFLICT, PFR_FB_NOCOUNT, PFR_FB_MAX }; struct pfr_addr { union { struct in_addr _pfra_ip4addr; struct in6_addr _pfra_ip6addr; } pfra_u; u_int8_t pfra_af; u_int8_t pfra_net; u_int8_t pfra_not; u_int8_t pfra_fback; }; #define pfra_ip4addr pfra_u._pfra_ip4addr #define pfra_ip6addr pfra_u._pfra_ip6addr enum { PFR_DIR_IN, PFR_DIR_OUT, PFR_DIR_MAX }; enum { PFR_OP_BLOCK, PFR_OP_PASS, PFR_OP_ADDR_MAX, PFR_OP_TABLE_MAX }; enum { PFR_TYPE_PACKETS, PFR_TYPE_BYTES, PFR_TYPE_MAX }; #define PFR_NUM_COUNTERS (PFR_DIR_MAX * PFR_OP_ADDR_MAX * PFR_TYPE_MAX) #define PFR_OP_XPASS PFR_OP_ADDR_MAX struct pfr_astats { struct pfr_addr pfras_a; u_int64_t pfras_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; u_int64_t pfras_bytes[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; time_t pfras_tzero; }; enum { PFR_REFCNT_RULE, PFR_REFCNT_ANCHOR, PFR_REFCNT_MAX }; struct pfr_tstats { struct pfr_table pfrts_t; u_int64_t pfrts_packets[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; u_int64_t pfrts_bytes[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; u_int64_t pfrts_match; u_int64_t pfrts_nomatch; time_t pfrts_tzero; int pfrts_cnt; int pfrts_refcnt[PFR_REFCNT_MAX]; }; #ifdef _KERNEL struct pfr_kstate_counter { counter_u64_t pkc_pcpu; u_int64_t pkc_zero; }; static inline int pfr_kstate_counter_init(struct pfr_kstate_counter *pfrc, int flags) { pfrc->pkc_zero = 0; pfrc->pkc_pcpu = counter_u64_alloc(flags); if (pfrc->pkc_pcpu == NULL) return (ENOMEM); return (0); } static inline void pfr_kstate_counter_deinit(struct pfr_kstate_counter *pfrc) { counter_u64_free(pfrc->pkc_pcpu); } static inline u_int64_t pfr_kstate_counter_fetch(struct pfr_kstate_counter *pfrc) { u_int64_t c; c = counter_u64_fetch(pfrc->pkc_pcpu); c -= pfrc->pkc_zero; return (c); } static inline void pfr_kstate_counter_zero(struct pfr_kstate_counter *pfrc) { u_int64_t c; c = counter_u64_fetch(pfrc->pkc_pcpu); pfrc->pkc_zero = c; } static inline void pfr_kstate_counter_add(struct pfr_kstate_counter *pfrc, int64_t n) { counter_u64_add(pfrc->pkc_pcpu, n); } struct pfr_ktstats { struct pfr_table pfrts_t; struct pfr_kstate_counter pfrkts_packets[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; struct pfr_kstate_counter pfrkts_bytes[PFR_DIR_MAX][PFR_OP_TABLE_MAX]; struct pfr_kstate_counter pfrkts_match; struct pfr_kstate_counter pfrkts_nomatch; time_t pfrkts_tzero; int pfrkts_cnt; int pfrkts_refcnt[PFR_REFCNT_MAX]; }; #endif /* _KERNEL */ #define pfrts_name pfrts_t.pfrt_name #define pfrts_flags pfrts_t.pfrt_flags #ifndef _SOCKADDR_UNION_DEFINED #define _SOCKADDR_UNION_DEFINED union sockaddr_union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; }; #endif /* _SOCKADDR_UNION_DEFINED */ struct pfr_kcounters { counter_u64_t pfrkc_counters; time_t pfrkc_tzero; }; #define pfr_kentry_counter(kc, dir, op, t) \ ((kc)->pfrkc_counters + \ (dir) * PFR_OP_ADDR_MAX * PFR_TYPE_MAX + (op) * PFR_TYPE_MAX + (t)) #ifdef _KERNEL SLIST_HEAD(pfr_kentryworkq, pfr_kentry); struct pfr_kentry { struct radix_node pfrke_node[2]; union sockaddr_union pfrke_sa; SLIST_ENTRY(pfr_kentry) pfrke_workq; struct pfr_kcounters pfrke_counters; u_int8_t pfrke_af; u_int8_t pfrke_net; u_int8_t pfrke_not; u_int8_t pfrke_mark; }; SLIST_HEAD(pfr_ktableworkq, pfr_ktable); RB_HEAD(pfr_ktablehead, pfr_ktable); struct pfr_ktable { struct pfr_ktstats pfrkt_kts; RB_ENTRY(pfr_ktable) pfrkt_tree; SLIST_ENTRY(pfr_ktable) pfrkt_workq; struct radix_node_head *pfrkt_ip4; struct radix_node_head *pfrkt_ip6; struct pfr_ktable *pfrkt_shadow; struct pfr_ktable *pfrkt_root; struct pf_kruleset *pfrkt_rs; long pfrkt_larg; int pfrkt_nflags; }; #define pfrkt_t pfrkt_kts.pfrts_t #define pfrkt_name pfrkt_t.pfrt_name #define pfrkt_anchor pfrkt_t.pfrt_anchor #define pfrkt_ruleset pfrkt_t.pfrt_ruleset #define pfrkt_flags pfrkt_t.pfrt_flags #define pfrkt_cnt pfrkt_kts.pfrkts_cnt #define pfrkt_refcnt pfrkt_kts.pfrkts_refcnt #define pfrkt_packets pfrkt_kts.pfrkts_packets #define pfrkt_bytes pfrkt_kts.pfrkts_bytes #define pfrkt_match pfrkt_kts.pfrkts_match #define pfrkt_nomatch pfrkt_kts.pfrkts_nomatch #define pfrkt_tzero pfrkt_kts.pfrkts_tzero #endif #ifdef _KERNEL struct pfi_kkif { char pfik_name[IFNAMSIZ]; union { RB_ENTRY(pfi_kkif) _pfik_tree; LIST_ENTRY(pfi_kkif) _pfik_list; } _pfik_glue; #define pfik_tree _pfik_glue._pfik_tree #define pfik_list _pfik_glue._pfik_list struct pf_counter_u64 pfik_packets[2][2][2]; struct pf_counter_u64 pfik_bytes[2][2][2]; time_t pfik_tzero; u_int pfik_flags; struct ifnet *pfik_ifp; struct ifg_group *pfik_group; u_int pfik_rulerefs; TAILQ_HEAD(, pfi_dynaddr) pfik_dynaddrs; #ifdef PF_WANT_32_TO_64_COUNTER LIST_ENTRY(pfi_kkif) pfik_allkiflist; #endif }; #endif #define PFI_IFLAG_REFS 0x0001 /* has state references */ #define PFI_IFLAG_SKIP 0x0100 /* skip filtering on interface */ #define PFI_IFLAG_ANY 0x0200 /* match any non-loopback interface */ #ifdef _KERNEL struct pf_sctp_multihome_job; TAILQ_HEAD(pf_sctp_multihome_jobs, pf_sctp_multihome_job); struct pf_pdesc { struct { int done; uid_t uid; gid_t gid; } lookup; u_int64_t tot_len; /* Make Mickey money */ union pf_headers { struct tcphdr tcp; struct udphdr udp; struct sctphdr sctp; struct icmp icmp; #ifdef INET6 struct icmp6_hdr icmp6; #endif /* INET6 */ char any[0]; } hdr; struct pf_addr nsaddr; /* src address after NAT */ struct pf_addr ndaddr; /* dst address after NAT */ struct pfi_kkif *kif; /* incomming interface */ struct mbuf *m; struct pf_addr *src; /* src address */ struct pf_addr *dst; /* dst address */ struct pf_addr osrc; struct pf_addr odst; u_int16_t *pcksum; /* proto cksum */ u_int16_t *sport; u_int16_t *dport; u_int16_t osport; u_int16_t odport; u_int16_t nsport; /* src port after NAT */ u_int16_t ndport; /* dst port after NAT */ struct pf_mtag *pf_mtag; struct pf_rule_actions act; u_int32_t off; /* protocol header offset */ bool df; /* IPv4 Don't fragment flag. */ u_int32_t hdrlen; /* protocol header length */ u_int32_t p_len; /* total length of protocol payload */ u_int32_t extoff; /* extentsion header offset */ u_int32_t fragoff; /* fragment header offset */ u_int32_t jumbolen; /* length from v6 jumbo header */ u_int32_t badopts; /* v4 options or v6 routing headers */ #define PF_OPT_OTHER 0x0001 #define PF_OPT_JUMBO 0x0002 #define PF_OPT_ROUTER_ALERT 0x0004 u_int16_t *ip_sum; u_int16_t flags; /* Let SCRUB trigger behavior in * state code. Easier than tags */ #define PFDESC_TCP_NORM 0x0001 /* TCP shall be statefully scrubbed */ u_int16_t virtual_proto; #define PF_VPROTO_FRAGMENT 256 sa_family_t af; sa_family_t naf; u_int8_t proto; u_int8_t tos; u_int8_t ttl; u_int8_t dir; /* direction */ u_int8_t sidx; /* key index for source */ u_int8_t didx; /* key index for destination */ #define PFDESC_SCTP_INIT 0x0001 #define PFDESC_SCTP_INIT_ACK 0x0002 #define PFDESC_SCTP_COOKIE 0x0004 #define PFDESC_SCTP_COOKIE_ACK 0x0008 #define PFDESC_SCTP_ABORT 0x0010 #define PFDESC_SCTP_SHUTDOWN 0x0020 #define PFDESC_SCTP_SHUTDOWN_COMPLETE 0x0040 #define PFDESC_SCTP_DATA 0x0080 #define PFDESC_SCTP_ASCONF 0x0100 #define PFDESC_SCTP_HEARTBEAT 0x0200 #define PFDESC_SCTP_HEARTBEAT_ACK 0x0400 #define PFDESC_SCTP_OTHER 0x0800 #define PFDESC_SCTP_ADD_IP 0x1000 u_int16_t sctp_flags; u_int32_t sctp_initiate_tag; u_int16_t sctp_dummy_sum; struct pf_krule *related_rule; struct pf_sctp_multihome_jobs sctp_multihome_jobs; }; struct pf_sctp_multihome_job { TAILQ_ENTRY(pf_sctp_multihome_job) next; struct pf_pdesc pd; struct pf_addr src; struct pf_addr dst; int op; }; #endif /* flags for RDR options */ #define PF_DPORT_RANGE 0x01 /* Dest port uses range */ #define PF_RPORT_RANGE 0x02 /* RDR'ed port uses range */ /* UDP state enumeration */ #define PFUDPS_NO_TRAFFIC 0 #define PFUDPS_SINGLE 1 #define PFUDPS_MULTIPLE 2 #define PFUDPS_NSTATES 3 /* number of state levels */ #define PFUDPS_NAMES { \ "NO_TRAFFIC", \ "SINGLE", \ "MULTIPLE", \ NULL \ } /* Other protocol state enumeration */ #define PFOTHERS_NO_TRAFFIC 0 #define PFOTHERS_SINGLE 1 #define PFOTHERS_MULTIPLE 2 #define PFOTHERS_NSTATES 3 /* number of state levels */ #define PFOTHERS_NAMES { \ "NO_TRAFFIC", \ "SINGLE", \ "MULTIPLE", \ NULL \ } #define ACTION_SET(a, x) \ do { \ if ((a) != NULL) \ *(a) = (x); \ } while (0) #define REASON_SET(a, x) \ do { \ SDT_PROBE2(pf, , test, reason_set, x, __LINE__); \ if ((a) != NULL) \ *(a) = (x); \ if (x < PFRES_MAX) \ counter_u64_add(V_pf_status.counters[x], 1); \ } while (0) enum pf_syncookies_mode { PF_SYNCOOKIES_NEVER = 0, PF_SYNCOOKIES_ALWAYS = 1, PF_SYNCOOKIES_ADAPTIVE = 2, PF_SYNCOOKIES_MODE_MAX = PF_SYNCOOKIES_ADAPTIVE }; #define PF_SYNCOOKIES_HIWATPCT 25 #define PF_SYNCOOKIES_LOWATPCT (PF_SYNCOOKIES_HIWATPCT / 2) #ifdef _KERNEL struct pf_kstatus { counter_u64_t counters[PFRES_MAX]; /* reason for passing/dropping */ counter_u64_t lcounters[KLCNT_MAX]; /* limit counters */ struct pf_counter_u64 fcounters[FCNT_MAX]; /* state operation counters */ counter_u64_t scounters[SCNT_MAX]; /* src_node operation counters */ counter_u64_t ncounters[NCNT_MAX]; uint32_t states; uint32_t src_nodes; uint32_t running; uint32_t since; uint32_t debug; uint32_t hostid; char ifname[IFNAMSIZ]; uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; bool keep_counters; enum pf_syncookies_mode syncookies_mode; bool syncookies_active; uint64_t syncookies_inflight[2]; uint32_t states_halfopen; uint32_t reass; }; #endif struct pf_divert { union { struct in_addr ipv4; struct in6_addr ipv6; } addr; u_int16_t port; }; #define PFFRAG_FRENT_HIWAT 5000 /* Number of fragment entries */ #define PFR_KENTRY_HIWAT 200000 /* Number of table entries */ struct pf_fragment_tag { uint16_t ft_hdrlen; /* header length of reassembled pkt */ uint16_t ft_extoff; /* last extension header offset or 0 */ uint16_t ft_maxlen; /* maximum fragment payload length */ uint32_t ft_id; /* fragment id */ }; /* * Limit the length of the fragment queue traversal. Remember * search entry points based on the fragment offset. */ #define PF_FRAG_ENTRY_POINTS 16 /* * The number of entries in the fragment queue must be limited * to avoid DoS by linear searching. Instead of a global limit, * use a limit per entry point. For large packets these sum up. */ #define PF_FRAG_ENTRY_LIMIT 64 /* * ioctl parameter structures */ struct pfioc_pooladdr { u_int32_t action; u_int32_t ticket; u_int32_t nr; u_int32_t r_num; u_int8_t r_action; u_int8_t r_last; u_int8_t af; char anchor[MAXPATHLEN]; struct pf_pooladdr addr; }; struct pfioc_rule { u_int32_t action; u_int32_t ticket; u_int32_t pool_ticket; u_int32_t nr; char anchor[MAXPATHLEN]; char anchor_call[MAXPATHLEN]; struct pf_rule rule; }; struct pfioc_natlook { struct pf_addr saddr; struct pf_addr daddr; struct pf_addr rsaddr; struct pf_addr rdaddr; u_int16_t sport; u_int16_t dport; u_int16_t rsport; u_int16_t rdport; sa_family_t af; u_int8_t proto; u_int8_t direction; }; struct pfioc_state { struct pfsync_state_1301 state; }; struct pfioc_src_node_kill { sa_family_t psnk_af; struct pf_rule_addr psnk_src; struct pf_rule_addr psnk_dst; u_int psnk_killed; }; #ifdef _KERNEL struct pf_kstate_kill { struct pf_state_cmp psk_pfcmp; sa_family_t psk_af; int psk_proto; struct pf_rule_addr psk_src; struct pf_rule_addr psk_dst; struct pf_rule_addr psk_rt_addr; char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; u_int psk_killed; bool psk_kill_match; bool psk_nat; }; #endif struct pfioc_state_kill { struct pf_state_cmp psk_pfcmp; sa_family_t psk_af; int psk_proto; struct pf_rule_addr psk_src; struct pf_rule_addr psk_dst; char psk_ifname[IFNAMSIZ]; char psk_label[PF_RULE_LABEL_SIZE]; u_int psk_killed; }; struct pfioc_states { int ps_len; union { void *ps_buf; struct pfsync_state_1301 *ps_states; }; }; struct pfioc_states_v2 { int ps_len; uint64_t ps_req_version; union { void *ps_buf; struct pf_state_export *ps_states; }; }; struct pfioc_src_nodes { int psn_len; union { void *psn_buf; struct pf_src_node *psn_src_nodes; }; }; struct pfioc_if { char ifname[IFNAMSIZ]; }; struct pfioc_tm { int timeout; int seconds; }; struct pfioc_limit { int index; unsigned limit; }; struct pfioc_altq_v0 { u_int32_t action; u_int32_t ticket; u_int32_t nr; struct pf_altq_v0 altq; }; struct pfioc_altq_v1 { u_int32_t action; u_int32_t ticket; u_int32_t nr; /* * Placed here so code that only uses the above parameters can be * written entirely in terms of the v0 or v1 type. */ u_int32_t version; struct pf_altq_v1 altq; }; /* * Latest version of struct pfioc_altq_vX. This must move in lock-step with * the latest version of struct pf_altq_vX as it has that struct as a * member. */ #define PFIOC_ALTQ_VERSION PF_ALTQ_VERSION struct pfioc_qstats_v0 { u_int32_t ticket; u_int32_t nr; void *buf; int nbytes; u_int8_t scheduler; }; struct pfioc_qstats_v1 { u_int32_t ticket; u_int32_t nr; void *buf; int nbytes; u_int8_t scheduler; /* * Placed here so code that only uses the above parameters can be * written entirely in terms of the v0 or v1 type. */ u_int32_t version; /* Requested version of stats struct */ }; /* Latest version of struct pfioc_qstats_vX */ #define PFIOC_QSTATS_VERSION 1 struct pfioc_ruleset { u_int32_t nr; char path[MAXPATHLEN]; char name[PF_ANCHOR_NAME_SIZE]; }; #define PF_RULESET_ALTQ (PF_RULESET_MAX) #define PF_RULESET_TABLE (PF_RULESET_MAX+1) #define PF_RULESET_ETH (PF_RULESET_MAX+2) struct pfioc_trans { int size; /* number of elements */ int esize; /* size of each element in bytes */ struct pfioc_trans_e { int rs_num; char anchor[MAXPATHLEN]; u_int32_t ticket; } *array; }; -#define PFR_FLAG_ATOMIC 0x00000001 /* unused */ +#define PFR_FLAG_START 0x00000001 #define PFR_FLAG_DUMMY 0x00000002 #define PFR_FLAG_FEEDBACK 0x00000004 #define PFR_FLAG_CLSTATS 0x00000008 #define PFR_FLAG_ADDRSTOO 0x00000010 #define PFR_FLAG_REPLACE 0x00000020 #define PFR_FLAG_ALLRSETS 0x00000040 -#define PFR_FLAG_ALLMASK 0x0000007F +#define PFR_FLAG_DONE 0x00000080 +#define PFR_FLAG_ALLMASK 0x000000FF #ifdef _KERNEL #define PFR_FLAG_USERIOCTL 0x10000000 #endif struct pfioc_table { struct pfr_table pfrio_table; void *pfrio_buffer; int pfrio_esize; int pfrio_size; int pfrio_size2; int pfrio_nadd; int pfrio_ndel; int pfrio_nchange; int pfrio_flags; u_int32_t pfrio_ticket; }; #define pfrio_exists pfrio_nadd #define pfrio_nzero pfrio_nadd #define pfrio_nmatch pfrio_nadd #define pfrio_naddr pfrio_size2 #define pfrio_setflag pfrio_size2 #define pfrio_clrflag pfrio_nadd struct pfioc_iface { char pfiio_name[IFNAMSIZ]; void *pfiio_buffer; int pfiio_esize; int pfiio_size; int pfiio_nzero; int pfiio_flags; }; /* * ioctl operations */ #define DIOCSTART _IO ('D', 1) #define DIOCSTOP _IO ('D', 2) #define DIOCADDRULE _IOWR('D', 4, struct pfioc_rule) #define DIOCADDRULENV _IOWR('D', 4, struct pfioc_nv) #define DIOCGETRULES _IOWR('D', 6, struct pfioc_rule) #define DIOCGETRULENV _IOWR('D', 7, struct pfioc_nv) #define DIOCCLRSTATESNV _IOWR('D', 18, struct pfioc_nv) #define DIOCGETSTATE _IOWR('D', 19, struct pfioc_state) #define DIOCGETSTATENV _IOWR('D', 19, struct pfioc_nv) #define DIOCSETSTATUSIF _IOWR('D', 20, struct pfioc_if) #define DIOCGETSTATUSNV _IOWR('D', 21, struct pfioc_nv) #define DIOCCLRSTATUS _IO ('D', 22) #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) #define DIOCSETDEBUG _IOWR('D', 24, u_int32_t) #ifdef COMPAT_FREEBSD14 #define DIOCGETSTATES _IOWR('D', 25, struct pfioc_states) #endif #define DIOCCHANGERULE _IOWR('D', 26, struct pfioc_rule) #define DIOCSETTIMEOUT _IOWR('D', 29, struct pfioc_tm) #define DIOCGETTIMEOUT _IOWR('D', 30, struct pfioc_tm) #define DIOCADDSTATE _IOWR('D', 37, struct pfioc_state) #define DIOCCLRRULECTRS _IO ('D', 38) #define DIOCGETLIMIT _IOWR('D', 39, struct pfioc_limit) #define DIOCSETLIMIT _IOWR('D', 40, struct pfioc_limit) #define DIOCKILLSTATESNV _IOWR('D', 41, struct pfioc_nv) #define DIOCSTARTALTQ _IO ('D', 42) #define DIOCSTOPALTQ _IO ('D', 43) #define DIOCADDALTQV0 _IOWR('D', 45, struct pfioc_altq_v0) #define DIOCADDALTQV1 _IOWR('D', 45, struct pfioc_altq_v1) #define DIOCGETALTQSV0 _IOWR('D', 47, struct pfioc_altq_v0) #define DIOCGETALTQSV1 _IOWR('D', 47, struct pfioc_altq_v1) #define DIOCGETALTQV0 _IOWR('D', 48, struct pfioc_altq_v0) #define DIOCGETALTQV1 _IOWR('D', 48, struct pfioc_altq_v1) #define DIOCCHANGEALTQV0 _IOWR('D', 49, struct pfioc_altq_v0) #define DIOCCHANGEALTQV1 _IOWR('D', 49, struct pfioc_altq_v1) #define DIOCGETQSTATSV0 _IOWR('D', 50, struct pfioc_qstats_v0) #define DIOCGETQSTATSV1 _IOWR('D', 50, struct pfioc_qstats_v1) #define DIOCBEGINADDRS _IOWR('D', 51, struct pfioc_pooladdr) #define DIOCADDADDR _IOWR('D', 52, struct pfioc_pooladdr) #define DIOCGETADDRS _IOWR('D', 53, struct pfioc_pooladdr) #define DIOCGETADDR _IOWR('D', 54, struct pfioc_pooladdr) #define DIOCCHANGEADDR _IOWR('D', 55, struct pfioc_pooladdr) #define DIOCGETRULESETS _IOWR('D', 58, struct pfioc_ruleset) #define DIOCGETRULESET _IOWR('D', 59, struct pfioc_ruleset) #define DIOCRCLRTABLES _IOWR('D', 60, struct pfioc_table) #define DIOCRADDTABLES _IOWR('D', 61, struct pfioc_table) #define DIOCRDELTABLES _IOWR('D', 62, struct pfioc_table) #define DIOCRGETTABLES _IOWR('D', 63, struct pfioc_table) #define DIOCRGETTSTATS _IOWR('D', 64, struct pfioc_table) #define DIOCRCLRTSTATS _IOWR('D', 65, struct pfioc_table) #define DIOCRCLRADDRS _IOWR('D', 66, struct pfioc_table) #define DIOCRADDADDRS _IOWR('D', 67, struct pfioc_table) #define DIOCRDELADDRS _IOWR('D', 68, struct pfioc_table) #define DIOCRSETADDRS _IOWR('D', 69, struct pfioc_table) #define DIOCRGETADDRS _IOWR('D', 70, struct pfioc_table) #define DIOCRGETASTATS _IOWR('D', 71, struct pfioc_table) #define DIOCRCLRASTATS _IOWR('D', 72, struct pfioc_table) #define DIOCRTSTADDRS _IOWR('D', 73, struct pfioc_table) #define DIOCRSETTFLAGS _IOWR('D', 74, struct pfioc_table) #define DIOCRINADEFINE _IOWR('D', 77, struct pfioc_table) #define DIOCOSFPFLUSH _IO('D', 78) #define DIOCOSFPADD _IOWR('D', 79, struct pf_osfp_ioctl) #define DIOCOSFPGET _IOWR('D', 80, struct pf_osfp_ioctl) #define DIOCXBEGIN _IOWR('D', 81, struct pfioc_trans) #define DIOCXCOMMIT _IOWR('D', 82, struct pfioc_trans) #define DIOCXROLLBACK _IOWR('D', 83, struct pfioc_trans) #define DIOCGETSRCNODES _IOWR('D', 84, struct pfioc_src_nodes) #define DIOCCLRSRCNODES _IO('D', 85) #define DIOCSETHOSTID _IOWR('D', 86, u_int32_t) #define DIOCIGETIFACES _IOWR('D', 87, struct pfioc_iface) #define DIOCSETIFFLAG _IOWR('D', 89, struct pfioc_iface) #define DIOCCLRIFFLAG _IOWR('D', 90, struct pfioc_iface) #define DIOCKILLSRCNODES _IOWR('D', 91, struct pfioc_src_node_kill) #define DIOCGIFSPEEDV0 _IOWR('D', 92, struct pf_ifspeed_v0) #define DIOCGIFSPEEDV1 _IOWR('D', 92, struct pf_ifspeed_v1) #ifdef COMPAT_FREEBSD14 #define DIOCGETSTATESV2 _IOWR('D', 93, struct pfioc_states_v2) #endif #define DIOCGETSYNCOOKIES _IOWR('D', 94, struct pfioc_nv) #define DIOCSETSYNCOOKIES _IOWR('D', 95, struct pfioc_nv) #define DIOCKEEPCOUNTERS _IOWR('D', 96, struct pfioc_nv) #define DIOCKEEPCOUNTERS_FREEBSD13 _IOWR('D', 92, struct pfioc_nv) #define DIOCADDETHRULE _IOWR('D', 97, struct pfioc_nv) #define DIOCGETETHRULE _IOWR('D', 98, struct pfioc_nv) #define DIOCGETETHRULES _IOWR('D', 99, struct pfioc_nv) #define DIOCGETETHRULESETS _IOWR('D', 100, struct pfioc_nv) #define DIOCGETETHRULESET _IOWR('D', 101, struct pfioc_nv) #define DIOCSETREASS _IOWR('D', 102, u_int32_t) struct pf_ifspeed_v0 { char ifname[IFNAMSIZ]; u_int32_t baudrate; }; struct pf_ifspeed_v1 { char ifname[IFNAMSIZ]; u_int32_t baudrate32; /* layout identical to struct pf_ifspeed_v0 up to this point */ u_int64_t baudrate; }; /* Latest version of struct pf_ifspeed_vX */ #define PF_IFSPEED_VERSION 1 /* * Compatibility and convenience macros */ #ifndef _KERNEL #ifdef PFIOC_USE_LATEST /* * Maintaining in-tree consumers of the ioctl interface is easier when that * code can be written in terms old names that refer to the latest interface * version as that reduces the required changes in the consumers to those * that are functionally necessary to accommodate a new interface version. */ #define pfioc_altq __CONCAT(pfioc_altq_v, PFIOC_ALTQ_VERSION) #define pfioc_qstats __CONCAT(pfioc_qstats_v, PFIOC_QSTATS_VERSION) #define pf_ifspeed __CONCAT(pf_ifspeed_v, PF_IFSPEED_VERSION) #define DIOCADDALTQ __CONCAT(DIOCADDALTQV, PFIOC_ALTQ_VERSION) #define DIOCGETALTQS __CONCAT(DIOCGETALTQSV, PFIOC_ALTQ_VERSION) #define DIOCGETALTQ __CONCAT(DIOCGETALTQV, PFIOC_ALTQ_VERSION) #define DIOCCHANGEALTQ __CONCAT(DIOCCHANGEALTQV, PFIOC_ALTQ_VERSION) #define DIOCGETQSTATS __CONCAT(DIOCGETQSTATSV, PFIOC_QSTATS_VERSION) #define DIOCGIFSPEED __CONCAT(DIOCGIFSPEEDV, PF_IFSPEED_VERSION) #else /* * When building out-of-tree code that is written for the old interface, * such as may exist in ports for example, resolve the old struct tags and * ioctl command names to the v0 versions. */ #define pfioc_altq __CONCAT(pfioc_altq_v, 0) #define pfioc_qstats __CONCAT(pfioc_qstats_v, 0) #define pf_ifspeed __CONCAT(pf_ifspeed_v, 0) #define DIOCADDALTQ __CONCAT(DIOCADDALTQV, 0) #define DIOCGETALTQS __CONCAT(DIOCGETALTQSV, 0) #define DIOCGETALTQ __CONCAT(DIOCGETALTQV, 0) #define DIOCCHANGEALTQ __CONCAT(DIOCCHANGEALTQV, 0) #define DIOCGETQSTATS __CONCAT(DIOCGETQSTATSV, 0) #define DIOCGIFSPEED __CONCAT(DIOCGIFSPEEDV, 0) #endif /* PFIOC_USE_LATEST */ #endif /* _KERNEL */ #ifdef _KERNEL LIST_HEAD(pf_ksrc_node_list, pf_ksrc_node); struct pf_srchash { struct pf_ksrc_node_list nodes; struct mtx lock; }; struct pf_keyhash { LIST_HEAD(, pf_state_key) keys; struct mtx lock; }; struct pf_idhash { LIST_HEAD(, pf_kstate) states; struct mtx lock; }; struct pf_udpendpointhash { LIST_HEAD(, pf_udp_endpoint) endpoints; /* refcont is synchronized on the source endpoint's row lock */ struct mtx lock; }; extern u_long pf_ioctl_maxcount; VNET_DECLARE(u_long, pf_hashmask); #define V_pf_hashmask VNET(pf_hashmask) VNET_DECLARE(u_long, pf_srchashmask); #define V_pf_srchashmask VNET(pf_srchashmask) VNET_DECLARE(u_long, pf_udpendpointhashmask); #define V_pf_udpendpointhashmask VNET(pf_udpendpointhashmask) #define PF_HASHSIZ (131072) #define PF_SRCHASHSIZ (PF_HASHSIZ/4) #define PF_UDPENDHASHSIZ (PF_HASHSIZ/4) VNET_DECLARE(struct pf_keyhash *, pf_keyhash); VNET_DECLARE(struct pf_idhash *, pf_idhash); VNET_DECLARE(struct pf_udpendpointhash *, pf_udpendpointhash); #define V_pf_keyhash VNET(pf_keyhash) #define V_pf_idhash VNET(pf_idhash) #define V_pf_udpendpointhash VNET(pf_udpendpointhash) VNET_DECLARE(struct pf_srchash *, pf_srchash); #define V_pf_srchash VNET(pf_srchash) #define PF_IDHASHID(id) (be64toh(id) % (V_pf_hashmask + 1)) #define PF_IDHASH(s) PF_IDHASHID((s)->id) VNET_DECLARE(void *, pf_swi_cookie); #define V_pf_swi_cookie VNET(pf_swi_cookie) VNET_DECLARE(struct intr_event *, pf_swi_ie); #define V_pf_swi_ie VNET(pf_swi_ie) VNET_DECLARE(struct unrhdr64, pf_stateid); #define V_pf_stateid VNET(pf_stateid) TAILQ_HEAD(pf_altqqueue, pf_altq); VNET_DECLARE(struct pf_altqqueue, pf_altqs[4]); #define V_pf_altqs VNET(pf_altqs) VNET_DECLARE(struct pf_kpalist, pf_pabuf[3]); #define V_pf_pabuf VNET(pf_pabuf) VNET_DECLARE(u_int32_t, ticket_altqs_active); #define V_ticket_altqs_active VNET(ticket_altqs_active) VNET_DECLARE(u_int32_t, ticket_altqs_inactive); #define V_ticket_altqs_inactive VNET(ticket_altqs_inactive) VNET_DECLARE(int, altqs_inactive_open); #define V_altqs_inactive_open VNET(altqs_inactive_open) VNET_DECLARE(u_int32_t, ticket_pabuf); #define V_ticket_pabuf VNET(ticket_pabuf) VNET_DECLARE(struct pf_altqqueue *, pf_altqs_active); #define V_pf_altqs_active VNET(pf_altqs_active) VNET_DECLARE(struct pf_altqqueue *, pf_altq_ifs_active); #define V_pf_altq_ifs_active VNET(pf_altq_ifs_active) VNET_DECLARE(struct pf_altqqueue *, pf_altqs_inactive); #define V_pf_altqs_inactive VNET(pf_altqs_inactive) VNET_DECLARE(struct pf_altqqueue *, pf_altq_ifs_inactive); #define V_pf_altq_ifs_inactive VNET(pf_altq_ifs_inactive) VNET_DECLARE(struct pf_krulequeue, pf_unlinked_rules); #define V_pf_unlinked_rules VNET(pf_unlinked_rules) #ifdef PF_WANT_32_TO_64_COUNTER LIST_HEAD(allkiflist_head, pfi_kkif); VNET_DECLARE(struct allkiflist_head, pf_allkiflist); #define V_pf_allkiflist VNET(pf_allkiflist) VNET_DECLARE(size_t, pf_allkifcount); #define V_pf_allkifcount VNET(pf_allkifcount) VNET_DECLARE(struct pfi_kkif *, pf_kifmarker); #define V_pf_kifmarker VNET(pf_kifmarker) LIST_HEAD(allrulelist_head, pf_krule); VNET_DECLARE(struct allrulelist_head, pf_allrulelist); #define V_pf_allrulelist VNET(pf_allrulelist) VNET_DECLARE(size_t, pf_allrulecount); #define V_pf_allrulecount VNET(pf_allrulecount) VNET_DECLARE(struct pf_krule *, pf_rulemarker); #define V_pf_rulemarker VNET(pf_rulemarker) #endif int pf_start(void); int pf_stop(void); void pf_initialize(void); void pf_mtag_initialize(void); void pf_mtag_cleanup(void); void pf_cleanup(void); struct pf_mtag *pf_get_mtag(struct mbuf *); extern void pf_calc_skip_steps(struct pf_krulequeue *); #ifdef ALTQ extern void pf_altq_ifnet_event(struct ifnet *, int); #endif VNET_DECLARE(uma_zone_t, pf_state_z); #define V_pf_state_z VNET(pf_state_z) VNET_DECLARE(uma_zone_t, pf_state_key_z); #define V_pf_state_key_z VNET(pf_state_key_z) VNET_DECLARE(uma_zone_t, pf_udp_mapping_z); #define V_pf_udp_mapping_z VNET(pf_udp_mapping_z) VNET_DECLARE(uma_zone_t, pf_state_scrub_z); #define V_pf_state_scrub_z VNET(pf_state_scrub_z) VNET_DECLARE(uma_zone_t, pf_anchor_z); #define V_pf_anchor_z VNET(pf_anchor_z) VNET_DECLARE(uma_zone_t, pf_eth_anchor_z); #define V_pf_eth_anchor_z VNET(pf_eth_anchor_z) extern void pf_purge_thread(void *); extern void pf_unload_vnet_purge(void); extern void pf_intr(void *); extern void pf_purge_expired_src_nodes(void); extern int pf_remove_state(struct pf_kstate *); extern int pf_state_insert(struct pfi_kkif *, struct pfi_kkif *, struct pf_state_key *, struct pf_state_key *, struct pf_kstate *); extern struct pf_kstate *pf_alloc_state(int); extern void pf_free_state(struct pf_kstate *); extern void pf_killstates(struct pf_kstate_kill *, unsigned int *); extern unsigned int pf_clear_states(const struct pf_kstate_kill *); static __inline void pf_ref_state(struct pf_kstate *s) { refcount_acquire(&s->refs); } static __inline int pf_release_state(struct pf_kstate *s) { if (refcount_release(&s->refs)) { pf_free_state(s); return (1); } else return (0); } static __inline int pf_release_staten(struct pf_kstate *s, u_int n) { if (refcount_releasen(&s->refs, n)) { pf_free_state(s); return (1); } else return (0); } static __inline uint64_t pf_get_uptime(void) { struct timeval t; microuptime(&t); return ((t.tv_sec * 1000) + (t.tv_usec / 1000)); } static __inline uint64_t pf_get_time(void) { struct timeval t; microtime(&t); return ((t.tv_sec * 1000) + (t.tv_usec / 1000)); } extern struct pf_kstate *pf_find_state_byid(uint64_t, uint32_t); extern struct pf_kstate *pf_find_state_all( const struct pf_state_key_cmp *, u_int, int *); extern bool pf_find_state_all_exists( const struct pf_state_key_cmp *, u_int); extern struct pf_udp_mapping *pf_udp_mapping_find(struct pf_udp_endpoint_cmp *endpoint); extern struct pf_udp_mapping *pf_udp_mapping_create(sa_family_t af, struct pf_addr *src_addr, uint16_t src_port, struct pf_addr *nat_addr, uint16_t nat_port); extern int pf_udp_mapping_insert(struct pf_udp_mapping *mapping); extern void pf_udp_mapping_release(struct pf_udp_mapping *mapping); uint32_t pf_hashsrc(struct pf_addr *, sa_family_t); extern bool pf_src_node_exists(struct pf_ksrc_node **, struct pf_srchash *); extern struct pf_ksrc_node *pf_find_src_node(struct pf_addr *, struct pf_krule *, sa_family_t, struct pf_srchash **, pf_sn_types_t, bool); extern void pf_unlink_src_node(struct pf_ksrc_node *); extern u_int pf_free_src_nodes(struct pf_ksrc_node_list *); extern void pf_free_src_node(struct pf_ksrc_node *); extern void pf_print_state(struct pf_kstate *); extern void pf_print_flags(uint16_t); extern int pf_addr_wrap_neq(struct pf_addr_wrap *, struct pf_addr_wrap *); extern u_int16_t pf_cksum_fixup(u_int16_t, u_int16_t, u_int16_t, u_int8_t); extern u_int16_t pf_proto_cksum_fixup(struct mbuf *, u_int16_t, u_int16_t, u_int16_t, u_int8_t); VNET_DECLARE(struct pf_krule, pf_default_rule); #define V_pf_default_rule VNET(pf_default_rule) extern void pf_addrcpy(struct pf_addr *, const struct pf_addr *, sa_family_t); void pf_free_rule(struct pf_krule *); int pf_test_eth(int, int, struct ifnet *, struct mbuf **, struct inpcb *); int pf_scan_sctp(struct pf_pdesc *); #if defined(INET) || defined(INET6) int pf_test(sa_family_t, int, int, struct ifnet *, struct mbuf **, struct inpcb *, struct pf_rule_actions *); #endif #ifdef INET int pf_normalize_ip(u_short *, struct pf_pdesc *); #endif /* INET */ void pf_poolmask(struct pf_addr *, struct pf_addr*, struct pf_addr *, struct pf_addr *, sa_family_t); void pf_addr_inc(struct pf_addr *, sa_family_t); #ifdef INET6 int pf_normalize_ip6(int, u_short *, struct pf_pdesc *); int pf_max_frag_size(struct mbuf *); int pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *, struct ifnet *, bool); #endif /* INET6 */ int pf_multihome_scan_init(int, int, struct pf_pdesc *); int pf_multihome_scan_asconf(int, int, struct pf_pdesc *); u_int32_t pf_new_isn(struct pf_kstate *); void *pf_pull_hdr(const struct mbuf *, int, void *, int, u_short *, sa_family_t); void pf_change_a(void *, u_int16_t *, u_int32_t, u_int8_t); void pf_change_proto_a(struct mbuf *, void *, u_int16_t *, u_int32_t, u_int8_t); void pf_change_tcp_a(struct mbuf *, void *, u_int16_t *, u_int32_t); int pf_patch_16(struct pf_pdesc *, void *, u_int16_t, bool); int pf_patch_32(struct pf_pdesc *, void *, u_int32_t, bool); void pf_send_deferred_syn(struct pf_kstate *); int pf_match_addr(u_int8_t, const struct pf_addr *, const struct pf_addr *, const struct pf_addr *, sa_family_t); int pf_match_addr_range(const struct pf_addr *, const struct pf_addr *, const struct pf_addr *, sa_family_t); int pf_match_port(u_int8_t, u_int16_t, u_int16_t, u_int16_t); void pf_normalize_init(void); void pf_normalize_cleanup(void); uint64_t pf_normalize_get_frag_count(void); int pf_normalize_tcp(struct pf_pdesc *); void pf_normalize_tcp_cleanup(struct pf_kstate *); int pf_normalize_tcp_init(struct pf_pdesc *, struct tcphdr *, struct pf_state_peer *); int pf_normalize_tcp_stateful(struct pf_pdesc *, u_short *, struct tcphdr *, struct pf_kstate *, struct pf_state_peer *, struct pf_state_peer *, int *); int pf_normalize_sctp_init(struct pf_pdesc *, struct pf_state_peer *, struct pf_state_peer *); int pf_normalize_sctp(struct pf_pdesc *); u_int32_t pf_state_expires(const struct pf_kstate *); void pf_purge_expired_fragments(void); void pf_purge_fragments(uint32_t); int pf_routable(struct pf_addr *addr, sa_family_t af, struct pfi_kkif *, int); int pf_socket_lookup(struct pf_pdesc *); struct pf_state_key *pf_alloc_state_key(int); int pf_translate(struct pf_pdesc *, struct pf_addr *, u_int16_t, struct pf_addr *, u_int16_t, u_int16_t, int); int pf_translate_af(struct pf_pdesc *); bool pf_init_threshold(struct pf_kthreshold *, uint32_t, uint32_t); uint16_t pf_tagname2tag(const char *); #ifdef ALTQ uint16_t pf_qname2qid(const char *, bool); #endif /* ALTQ */ void pfr_initialize(void); void pfr_cleanup(void); struct pfr_kentry * pfr_kentry_byaddr(struct pfr_ktable *, struct pf_addr *, sa_family_t, int); int pfr_match_addr(struct pfr_ktable *, struct pf_addr *, sa_family_t); void pfr_update_stats(struct pfr_ktable *, struct pf_addr *, sa_family_t, u_int64_t, int, int, int); int pfr_pool_get(struct pfr_ktable *, int *, struct pf_addr *, sa_family_t, pf_addr_filter_func_t, bool); void pfr_dynaddr_update(struct pfr_ktable *, struct pfi_dynaddr *); struct pfr_ktable * pfr_attach_table(struct pf_kruleset *, char *); struct pfr_ktable * pfr_eth_attach_table(struct pf_keth_ruleset *, char *); void pfr_detach_table(struct pfr_ktable *); int pfr_clr_tables(struct pfr_table *, int *, int); int pfr_add_tables(struct pfr_table *, int, int *, int); int pfr_del_tables(struct pfr_table *, int, int *, int); int pfr_table_count(struct pfr_table *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); int pfr_clr_tstats(struct pfr_table *, int, int *, int); int pfr_set_tflags(struct pfr_table *, int, int, int, int *, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_insert_kentry(struct pfr_ktable *, struct pfr_addr *, time_t); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int *, int *, int *, int, u_int32_t); int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_ina_begin(struct pfr_table *, u_int32_t *, int *, int); int pfr_ina_rollback(struct pfr_table *, u_int32_t, int *, int); int pfr_ina_commit(struct pfr_table *, u_int32_t, int *, int *, int); int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, u_int32_t, int); struct pfr_ktable *pfr_ktable_select_active(struct pfr_ktable *); MALLOC_DECLARE(PFI_MTYPE); VNET_DECLARE(struct pfi_kkif *, pfi_all); #define V_pfi_all VNET(pfi_all) void pfi_initialize(void); void pfi_initialize_vnet(void); void pfi_cleanup(void); void pfi_cleanup_vnet(void); void pfi_kkif_ref(struct pfi_kkif *); void pfi_kkif_unref(struct pfi_kkif *); struct pfi_kkif *pfi_kkif_find(const char *); struct pfi_kkif *pfi_kkif_attach(struct pfi_kkif *, const char *); int pfi_kkif_match(struct pfi_kkif *, struct pfi_kkif *); void pfi_kkif_purge(void); int pfi_match_addr(struct pfi_dynaddr *, struct pf_addr *, sa_family_t); int pfi_dynaddr_setup(struct pf_addr_wrap *, sa_family_t); void pfi_dynaddr_remove(struct pfi_dynaddr *); void pfi_dynaddr_copyout(struct pf_addr_wrap *); void pfi_update_status(const char *, struct pf_status *); void pfi_get_ifaces(const char *, struct pfi_kif *, int *); int pfi_set_flags(const char *, int); int pfi_clear_flags(const char *, int); int pf_match_tag(struct mbuf *, struct pf_krule *, int *, int); int pf_tag_packet(struct pf_pdesc *, int); int pf_addr_cmp(struct pf_addr *, struct pf_addr *, sa_family_t); uint8_t* pf_find_tcpopt(u_int8_t *, u_int8_t *, size_t, u_int8_t, u_int8_t); u_int16_t pf_get_mss(struct pf_pdesc *); u_int8_t pf_get_wscale(struct pf_pdesc *); struct mbuf *pf_build_tcp(const struct pf_krule *, sa_family_t, const struct pf_addr *, const struct pf_addr *, u_int16_t, u_int16_t, u_int32_t, u_int32_t, u_int8_t, u_int16_t, u_int16_t, u_int8_t, int, u_int16_t, u_int16_t, u_int, int, u_short *); void pf_send_tcp(const struct pf_krule *, sa_family_t, const struct pf_addr *, const struct pf_addr *, u_int16_t, u_int16_t, u_int32_t, u_int32_t, u_int8_t, u_int16_t, u_int16_t, u_int8_t, int, u_int16_t, u_int16_t, int, u_short *); void pf_syncookies_init(void); void pf_syncookies_cleanup(void); int pf_get_syncookies(struct pfioc_nv *); int pf_set_syncookies(struct pfioc_nv *); int pf_synflood_check(struct pf_pdesc *); void pf_syncookie_send(struct pf_pdesc *, u_short *); bool pf_syncookie_check(struct pf_pdesc *); u_int8_t pf_syncookie_validate(struct pf_pdesc *); struct mbuf * pf_syncookie_recreate_syn(struct pf_pdesc *, u_short *); VNET_DECLARE(struct pf_kstatus, pf_status); #define V_pf_status VNET(pf_status) struct pf_limit { uma_zone_t zone; u_int limit; }; VNET_DECLARE(struct pf_limit, pf_limits[PF_LIMIT_MAX]); #define V_pf_limits VNET(pf_limits) #endif /* _KERNEL */ #ifdef _KERNEL struct pf_nl_pooladdr { u_int32_t action; u_int32_t ticket; u_int32_t nr; u_int32_t r_num; u_int8_t r_action; u_int8_t r_last; u_int8_t af; char anchor[MAXPATHLEN]; struct pf_pooladdr addr; /* Above this is identical to pfioc_pooladdr */ int which; }; VNET_DECLARE(struct pf_kanchor_global, pf_anchors); #define V_pf_anchors VNET(pf_anchors) VNET_DECLARE(struct pf_kanchor, pf_main_anchor); #define V_pf_main_anchor VNET(pf_main_anchor) VNET_DECLARE(struct pf_keth_anchor_global, pf_keth_anchors); #define V_pf_keth_anchors VNET(pf_keth_anchors) #define pf_main_ruleset V_pf_main_anchor.ruleset VNET_DECLARE(struct pf_keth_anchor, pf_main_keth_anchor); #define V_pf_main_keth_anchor VNET(pf_main_keth_anchor) VNET_DECLARE(struct pf_keth_ruleset*, pf_keth); #define V_pf_keth VNET(pf_keth) void pf_init_kruleset(struct pf_kruleset *); void pf_init_keth(struct pf_keth_ruleset *); int pf_kanchor_setup(struct pf_krule *, const struct pf_kruleset *, const char *); int pf_kanchor_copyout(const struct pf_kruleset *, const struct pf_krule *, char *, size_t); int pf_kanchor_nvcopyout(const struct pf_kruleset *, const struct pf_krule *, nvlist_t *); void pf_remove_kanchor(struct pf_krule *); void pf_remove_if_empty_kruleset(struct pf_kruleset *); struct pf_kruleset *pf_find_kruleset(const char *); struct pf_kruleset *pf_get_leaf_kruleset(char *, char **); struct pf_kruleset *pf_find_or_create_kruleset(const char *); void pf_rs_initialize(void); void pf_rule_tree_free(struct pf_krule_global *); struct pf_krule *pf_krule_alloc(void); void pf_remove_if_empty_keth_ruleset( struct pf_keth_ruleset *); struct pf_keth_ruleset *pf_find_keth_ruleset(const char *); struct pf_keth_anchor *pf_find_keth_anchor(const char *); int pf_keth_anchor_setup(struct pf_keth_rule *, const struct pf_keth_ruleset *, const char *); int pf_keth_anchor_nvcopyout( const struct pf_keth_ruleset *, const struct pf_keth_rule *, nvlist_t *); struct pf_keth_ruleset *pf_find_or_create_keth_ruleset(const char *); void pf_keth_anchor_remove(struct pf_keth_rule *); int pf_ioctl_getrules(struct pfioc_rule *); int pf_ioctl_addrule(struct pf_krule *, uint32_t, uint32_t, const char *, const char *, uid_t uid, pid_t); void pf_ioctl_clear_status(void); int pf_ioctl_get_timeout(int, int *); int pf_ioctl_set_timeout(int, int, int *); int pf_ioctl_get_limit(int, unsigned int *); int pf_ioctl_set_limit(int, unsigned int, unsigned int *); int pf_ioctl_begin_addrs(uint32_t *); int pf_ioctl_add_addr(struct pf_nl_pooladdr *); int pf_ioctl_get_addrs(struct pf_nl_pooladdr *); int pf_ioctl_get_addr(struct pf_nl_pooladdr *); int pf_ioctl_get_rulesets(struct pfioc_ruleset *); int pf_ioctl_get_ruleset(struct pfioc_ruleset *); int pf_ioctl_natlook(struct pfioc_natlook *); void pf_krule_free(struct pf_krule *); void pf_krule_clear_counters(struct pf_krule *); void pf_addr_copyout(struct pf_addr_wrap *); #endif /* The fingerprint functions can be linked into userland programs (tcpdump) */ int pf_osfp_add(struct pf_osfp_ioctl *); #ifdef _KERNEL struct pf_osfp_enlist * pf_osfp_fingerprint(struct pf_pdesc *, const struct tcphdr *); #endif /* _KERNEL */ void pf_osfp_flush(void); int pf_osfp_get(struct pf_osfp_ioctl *); int pf_osfp_match(struct pf_osfp_enlist *, pf_osfp_t); #ifdef _KERNEL void pf_print_host(struct pf_addr *, u_int16_t, sa_family_t); enum pf_test_status pf_step_into_anchor(struct pf_test_ctx *, struct pf_krule *, struct pf_krule_slist *match_rules); enum pf_test_status pf_match_rule(struct pf_test_ctx *, struct pf_kruleset *, struct pf_krule_slist *); void pf_step_into_keth_anchor(struct pf_keth_anchor_stackframe *, int *, struct pf_keth_ruleset **, struct pf_keth_rule **, struct pf_keth_rule **, int *); int pf_step_out_of_keth_anchor(struct pf_keth_anchor_stackframe *, int *, struct pf_keth_ruleset **, struct pf_keth_rule **, struct pf_keth_rule **, int *); u_short pf_map_addr(sa_family_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, struct pfi_kkif **nkif, sa_family_t *, struct pf_addr *, struct pf_kpool *); u_short pf_map_addr_sn(u_int8_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, sa_family_t *, struct pfi_kkif **, struct pf_addr *, struct pf_kpool *, pf_sn_types_t); int pf_get_transaddr_af(struct pf_krule *, struct pf_pdesc *); u_short pf_get_translation(struct pf_test_ctx *); u_short pf_get_transaddr(struct pf_test_ctx *, struct pf_krule *, u_int8_t, struct pf_kpool *); int pf_translate_compat(struct pf_test_ctx *); int pf_state_key_setup(struct pf_pdesc *, u_int16_t, u_int16_t, struct pf_state_key **sk, struct pf_state_key **nk); struct pf_state_key *pf_state_key_clone(const struct pf_state_key *); void pf_rule_to_actions(struct pf_krule *, struct pf_rule_actions *); int pf_normalize_mss(struct pf_pdesc *pd); #if defined(INET) || defined(INET6) void pf_scrub(struct pf_pdesc *); #endif struct pfi_kkif *pf_kkif_create(int); void pf_kkif_free(struct pfi_kkif *); void pf_kkif_zero(struct pfi_kkif *); /* NAT64 functions. */ int inet_nat64(int, const void *, void *, const void *, u_int8_t); int inet_nat64_inet(const void *, void *, const void *, u_int8_t); int inet_nat64_inet6(const void *, void *, const void *, u_int8_t); int inet_nat46(int, const void *, void *, const void *, u_int8_t); int inet_nat46_inet(const void *, void *, const void *, u_int8_t); int inet_nat46_inet6(const void *, void *, const void *, u_int8_t); #endif /* _KERNEL */ #endif /* _NET_PFVAR_H_ */ diff --git a/sys/netpfil/pf/pf_if.c b/sys/netpfil/pf/pf_if.c index f3be036ef745..6f41d453a7d1 100644 --- a/sys/netpfil/pf/pf_if.c +++ b/sys/netpfil/pf/pf_if.c @@ -1,1192 +1,1192 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2003 Cedric Berger * Copyright (c) 2005 Henning Brauer * Copyright (c) 2005 Ryan McBride * Copyright (c) 2012 Gleb Smirnoff * 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. * * $OpenBSD: pf_if.c,v 1.54 2008/06/14 16:55:28 mk Exp $ */ #include #include "opt_inet.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include VNET_DEFINE(struct pfi_kkif *, pfi_all); VNET_DEFINE_STATIC(long, pfi_update); #define V_pfi_update VNET(pfi_update) #define PFI_BUFFER_MAX 0x10000 VNET_DECLARE(int, pf_vnet_active); #define V_pf_vnet_active VNET(pf_vnet_active) VNET_DEFINE_STATIC(struct pfr_addr *, pfi_buffer); VNET_DEFINE_STATIC(int, pfi_buffer_cnt); VNET_DEFINE_STATIC(int, pfi_buffer_max); #define V_pfi_buffer VNET(pfi_buffer) #define V_pfi_buffer_cnt VNET(pfi_buffer_cnt) #define V_pfi_buffer_max VNET(pfi_buffer_max) #ifdef PF_WANT_32_TO_64_COUNTER VNET_DEFINE(struct allkiflist_head, pf_allkiflist); VNET_DEFINE(size_t, pf_allkifcount); VNET_DEFINE(struct pfi_kkif *, pf_kifmarker); #endif eventhandler_tag pfi_attach_cookie; eventhandler_tag pfi_detach_cookie; eventhandler_tag pfi_attach_group_cookie; eventhandler_tag pfi_change_group_cookie; eventhandler_tag pfi_detach_group_cookie; eventhandler_tag pfi_ifaddr_event_cookie; static void pfi_attach_ifnet(struct ifnet *, struct pfi_kkif *); static void pfi_attach_ifgroup(struct ifg_group *, struct pfi_kkif *); static void pfi_kkif_update(struct pfi_kkif *); static void pfi_dynaddr_update(struct pfi_dynaddr *dyn); static void pfi_table_update(struct pfr_ktable *, struct pfi_kkif *, uint8_t, int); static void pfi_instance_add(struct ifnet *, uint8_t, int); static void pfi_address_add(struct sockaddr *, sa_family_t, uint8_t); static int pfi_kkif_compare(struct pfi_kkif *, struct pfi_kkif *); static int pfi_skip_if(const char *, struct pfi_kkif *); static int pfi_unmask(void *); static void pfi_attach_ifnet_event(void * __unused, struct ifnet *); static void pfi_detach_ifnet_event(void * __unused, struct ifnet *); static void pfi_attach_group_event(void * __unused, struct ifg_group *); static void pfi_change_group_event(void * __unused, char *); static void pfi_detach_group_event(void * __unused, struct ifg_group *); static void pfi_ifaddr_event(void * __unused, struct ifnet *); RB_HEAD(pfi_ifhead, pfi_kkif); static RB_PROTOTYPE(pfi_ifhead, pfi_kkif, pfik_tree, pfi_kkif_compare); static RB_GENERATE(pfi_ifhead, pfi_kkif, pfik_tree, pfi_kkif_compare); VNET_DEFINE_STATIC(struct pfi_ifhead, pfi_ifs); #define V_pfi_ifs VNET(pfi_ifs) #define PFI_BUFFER_MAX 0x10000 MALLOC_DEFINE(PFI_MTYPE, "pf_ifnet", "pf(4) interface database"); LIST_HEAD(pfi_list, pfi_kkif); VNET_DEFINE_STATIC(struct pfi_list, pfi_unlinked_kifs); #define V_pfi_unlinked_kifs VNET(pfi_unlinked_kifs) static struct mtx pfi_unlnkdkifs_mtx; MTX_SYSINIT(pfi_unlnkdkifs_mtx, &pfi_unlnkdkifs_mtx, "pf unlinked interfaces", MTX_DEF); void pfi_initialize_vnet(void) { struct pfi_list kifs = LIST_HEAD_INITIALIZER(); struct epoch_tracker et; struct pfi_kkif *kif; struct ifg_group *ifg; struct ifnet *ifp; int nkifs; V_pfi_buffer_max = 64; V_pfi_buffer = malloc(V_pfi_buffer_max * sizeof(*V_pfi_buffer), PFI_MTYPE, M_WAITOK); nkifs = 1; /* one for V_pfi_all */ IFNET_RLOCK(); CK_STAILQ_FOREACH(ifg, &V_ifg_head, ifg_next) nkifs++; CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) nkifs++; for (int n = 0; n < nkifs; n++) { kif = pf_kkif_create(M_WAITOK); LIST_INSERT_HEAD(&kifs, kif, pfik_list); } NET_EPOCH_ENTER(et); PF_RULES_WLOCK(); kif = LIST_FIRST(&kifs); LIST_REMOVE(kif, pfik_list); V_pfi_all = pfi_kkif_attach(kif, IFG_ALL); CK_STAILQ_FOREACH(ifg, &V_ifg_head, ifg_next) { kif = LIST_FIRST(&kifs); LIST_REMOVE(kif, pfik_list); pfi_attach_ifgroup(ifg, kif); } CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { kif = LIST_FIRST(&kifs); LIST_REMOVE(kif, pfik_list); pfi_attach_ifnet(ifp, kif); } PF_RULES_WUNLOCK(); NET_EPOCH_EXIT(et); IFNET_RUNLOCK(); MPASS(LIST_EMPTY(&kifs)); } void pfi_initialize(void) { pfi_attach_cookie = EVENTHANDLER_REGISTER(ifnet_arrival_event, pfi_attach_ifnet_event, NULL, EVENTHANDLER_PRI_ANY); pfi_detach_cookie = EVENTHANDLER_REGISTER(ifnet_departure_event, pfi_detach_ifnet_event, NULL, EVENTHANDLER_PRI_ANY); pfi_attach_group_cookie = EVENTHANDLER_REGISTER(group_attach_event, pfi_attach_group_event, NULL, EVENTHANDLER_PRI_ANY); pfi_change_group_cookie = EVENTHANDLER_REGISTER(group_change_event, pfi_change_group_event, NULL, EVENTHANDLER_PRI_ANY); pfi_detach_group_cookie = EVENTHANDLER_REGISTER(group_detach_event, pfi_detach_group_event, NULL, EVENTHANDLER_PRI_ANY); pfi_ifaddr_event_cookie = EVENTHANDLER_REGISTER(ifaddr_event, pfi_ifaddr_event, NULL, EVENTHANDLER_PRI_ANY); } void pfi_cleanup_vnet(void) { struct pfi_kkif *kif; PF_RULES_WASSERT(); V_pfi_all = NULL; while ((kif = RB_MIN(pfi_ifhead, &V_pfi_ifs))) { RB_REMOVE(pfi_ifhead, &V_pfi_ifs, kif); if (kif->pfik_group) kif->pfik_group->ifg_pf_kif = NULL; if (kif->pfik_ifp) { if_rele(kif->pfik_ifp); kif->pfik_ifp->if_pf_kif = NULL; } pf_kkif_free(kif); } mtx_lock(&pfi_unlnkdkifs_mtx); while ((kif = LIST_FIRST(&V_pfi_unlinked_kifs))) { LIST_REMOVE(kif, pfik_list); pf_kkif_free(kif); } mtx_unlock(&pfi_unlnkdkifs_mtx); free(V_pfi_buffer, PFI_MTYPE); } void pfi_cleanup(void) { EVENTHANDLER_DEREGISTER(ifnet_arrival_event, pfi_attach_cookie); EVENTHANDLER_DEREGISTER(ifnet_departure_event, pfi_detach_cookie); EVENTHANDLER_DEREGISTER(group_attach_event, pfi_attach_group_cookie); EVENTHANDLER_DEREGISTER(group_change_event, pfi_change_group_cookie); EVENTHANDLER_DEREGISTER(group_detach_event, pfi_detach_group_cookie); EVENTHANDLER_DEREGISTER(ifaddr_event, pfi_ifaddr_event_cookie); } struct pfi_kkif* pf_kkif_create(int flags) { struct pfi_kkif *kif; #ifdef PF_WANT_32_TO_64_COUNTER bool wowned; #endif kif = malloc(sizeof(*kif), PFI_MTYPE, flags | M_ZERO); if (! kif) return (kif); for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { if (pf_counter_u64_init(&kif->pfik_packets[i][j][k], flags) != 0) { pf_kkif_free(kif); return (NULL); } if (pf_counter_u64_init(&kif->pfik_bytes[i][j][k], flags) != 0) { pf_kkif_free(kif); return (NULL); } } } } #ifdef PF_WANT_32_TO_64_COUNTER wowned = PF_RULES_WOWNED(); if (!wowned) PF_RULES_WLOCK(); LIST_INSERT_HEAD(&V_pf_allkiflist, kif, pfik_allkiflist); V_pf_allkifcount++; if (!wowned) PF_RULES_WUNLOCK(); #endif return (kif); } void pf_kkif_free(struct pfi_kkif *kif) { #ifdef PF_WANT_32_TO_64_COUNTER bool wowned; #endif if (! kif) return; #ifdef INVARIANTS if (kif->pfik_ifp) { struct ifnet *ifp = kif->pfik_ifp; MPASS(ifp->if_pf_kif == NULL || ifp->if_pf_kif == kif); } #endif #ifdef PF_WANT_32_TO_64_COUNTER wowned = PF_RULES_WOWNED(); if (!wowned) PF_RULES_WLOCK(); LIST_REMOVE(kif, pfik_allkiflist); V_pf_allkifcount--; if (!wowned) PF_RULES_WUNLOCK(); #endif for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { pf_counter_u64_deinit(&kif->pfik_packets[i][j][k]); pf_counter_u64_deinit(&kif->pfik_bytes[i][j][k]); } } } free(kif, PFI_MTYPE); } void pf_kkif_zero(struct pfi_kkif *kif) { for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { pf_counter_u64_zero(&kif->pfik_packets[i][j][k]); pf_counter_u64_zero(&kif->pfik_bytes[i][j][k]); } } } kif->pfik_tzero = time_second; } struct pfi_kkif * pfi_kkif_find(const char *kif_name) { struct pfi_kif_cmp s; PF_RULES_ASSERT(); memset(&s, 0, sizeof(s)); strlcpy(s.pfik_name, kif_name, sizeof(s.pfik_name)); return (RB_FIND(pfi_ifhead, &V_pfi_ifs, (struct pfi_kkif *)&s)); } struct pfi_kkif * pfi_kkif_attach(struct pfi_kkif *kif, const char *kif_name) { struct pfi_kkif *kif1; PF_RULES_WASSERT(); KASSERT(kif != NULL, ("%s: null kif", __func__)); kif1 = pfi_kkif_find(kif_name); if (kif1 != NULL) { pf_kkif_free(kif); return (kif1); } pf_kkif_zero(kif); strlcpy(kif->pfik_name, kif_name, sizeof(kif->pfik_name)); /* * It seems that the value of time_second is in unintialzied state * when pf sets interface statistics clear time in boot phase if pf * was statically linked to kernel. Instead of setting the bogus * time value have pfi_get_ifaces handle this case. In * pfi_get_ifaces it uses time_second if it sees the time is 0. */ kif->pfik_tzero = time_second > 1 ? time_second : 0; TAILQ_INIT(&kif->pfik_dynaddrs); if (!strcmp(kif->pfik_name, "any")) { /* both so it works in the ioctl and the regular case */ kif->pfik_flags |= PFI_IFLAG_ANY; } RB_INSERT(pfi_ifhead, &V_pfi_ifs, kif); return (kif); } void pfi_kkif_ref(struct pfi_kkif *kif) { PF_RULES_WASSERT(); kif->pfik_rulerefs++; } static void pfi_kkif_remove_if_unref(struct pfi_kkif *kif) { PF_RULES_WASSERT(); if (kif->pfik_rulerefs > 0) return; /* kif referencing an existing ifnet or group or holding flags should * exist. */ if (kif->pfik_ifp != NULL || kif->pfik_group != NULL || kif == V_pfi_all || kif->pfik_flags != 0) return; /* * We can get here in at least two distinct paths: * - when the struct ifnet is removed, via pfi_detach_ifnet_event() * - when a rule referencing us is removed, via pfi_kkif_unref(). * These two events can race against each other, leading us to free this kif * twice. That leads to a loop in V_pfi_unlinked_kifs, and an eventual * deadlock. * * Avoid this by making sure we only ever insert the kif into * V_pfi_unlinked_kifs once. * If we don't find it in V_pfi_ifs it's already been removed. Check that it * exists in V_pfi_unlinked_kifs. */ if (! RB_FIND(pfi_ifhead, &V_pfi_ifs, kif)) { #ifdef INVARIANTS struct pfi_kkif *tmp; bool found = false; mtx_lock(&pfi_unlnkdkifs_mtx); LIST_FOREACH(tmp, &V_pfi_unlinked_kifs, pfik_list) { if (tmp == kif) { found = true; break; } } mtx_unlock(&pfi_unlnkdkifs_mtx); MPASS(found); #endif return; } RB_REMOVE(pfi_ifhead, &V_pfi_ifs, kif); kif->pfik_flags |= PFI_IFLAG_REFS; mtx_lock(&pfi_unlnkdkifs_mtx); LIST_INSERT_HEAD(&V_pfi_unlinked_kifs, kif, pfik_list); mtx_unlock(&pfi_unlnkdkifs_mtx); } void pfi_kkif_unref(struct pfi_kkif *kif) { PF_RULES_WASSERT(); KASSERT(kif->pfik_rulerefs > 0, ("%s: %p has zero refs", __func__, kif)); kif->pfik_rulerefs--; pfi_kkif_remove_if_unref(kif); } void pfi_kkif_purge(void) { struct pfi_kkif *kif, *kif1; /* * Do naive mark-and-sweep garbage collecting of old kifs. * Reference flag is raised by pf_purge_expired_states(). */ mtx_lock(&pfi_unlnkdkifs_mtx); LIST_FOREACH_SAFE(kif, &V_pfi_unlinked_kifs, pfik_list, kif1) { if (!(kif->pfik_flags & PFI_IFLAG_REFS)) { LIST_REMOVE(kif, pfik_list); pf_kkif_free(kif); } else kif->pfik_flags &= ~PFI_IFLAG_REFS; } mtx_unlock(&pfi_unlnkdkifs_mtx); } int pfi_kkif_match(struct pfi_kkif *rule_kif, struct pfi_kkif *packet_kif) { struct ifg_list *p; NET_EPOCH_ASSERT(); MPASS(packet_kif != NULL); MPASS(packet_kif->pfik_ifp != NULL); if (rule_kif == NULL || rule_kif == packet_kif) return (1); if (rule_kif->pfik_group != NULL) { CK_STAILQ_FOREACH(p, &packet_kif->pfik_ifp->if_groups, ifgl_next) if (p->ifgl_group == rule_kif->pfik_group) return (1); } if (rule_kif->pfik_flags & PFI_IFLAG_ANY && packet_kif->pfik_ifp && !(packet_kif->pfik_ifp->if_flags & IFF_LOOPBACK)) return (1); return (0); } static void pfi_attach_ifnet(struct ifnet *ifp, struct pfi_kkif *kif) { PF_RULES_WASSERT(); V_pfi_update++; kif = pfi_kkif_attach(kif, ifp->if_xname); if_ref(ifp); kif->pfik_ifp = ifp; ifp->if_pf_kif = kif; pfi_kkif_update(kif); } static void pfi_attach_ifgroup(struct ifg_group *ifg, struct pfi_kkif *kif) { PF_RULES_WASSERT(); V_pfi_update++; kif = pfi_kkif_attach(kif, ifg->ifg_group); kif->pfik_group = ifg; ifg->ifg_pf_kif = kif; } int pfi_match_addr(struct pfi_dynaddr *dyn, struct pf_addr *a, sa_family_t af) { switch (af) { #ifdef INET case AF_INET: switch (dyn->pfid_acnt4) { case 0: return (0); case 1: return (pf_match_addr(0, &dyn->pfid_addr4, &dyn->pfid_mask4, a, AF_INET)); default: return (pfr_match_addr(dyn->pfid_kt, a, AF_INET)); } break; #endif /* INET */ #ifdef INET6 case AF_INET6: switch (dyn->pfid_acnt6) { case 0: return (0); case 1: return (pf_match_addr(0, &dyn->pfid_addr6, &dyn->pfid_mask6, a, AF_INET6)); default: return (pfr_match_addr(dyn->pfid_kt, a, AF_INET6)); } break; #endif /* INET6 */ default: return (0); } } int pfi_dynaddr_setup(struct pf_addr_wrap *aw, sa_family_t af) { struct epoch_tracker et; struct pfi_dynaddr *dyn; char tblname[PF_TABLE_NAME_SIZE]; struct pf_kruleset *ruleset = NULL; struct pfi_kkif *kif; int rv = 0; PF_RULES_WASSERT(); KASSERT(aw->type == PF_ADDR_DYNIFTL, ("%s: type %u", __func__, aw->type)); KASSERT(aw->p.dyn == NULL, ("%s: dyn is %p", __func__, aw->p.dyn)); if ((dyn = malloc(sizeof(*dyn), PFI_MTYPE, M_NOWAIT | M_ZERO)) == NULL) return (ENOMEM); if ((kif = pf_kkif_create(M_NOWAIT)) == NULL) { free(dyn, PFI_MTYPE); return (ENOMEM); } if (!strcmp(aw->v.ifname, "self")) dyn->pfid_kif = pfi_kkif_attach(kif, IFG_ALL); else dyn->pfid_kif = pfi_kkif_attach(kif, aw->v.ifname); kif = NULL; pfi_kkif_ref(dyn->pfid_kif); dyn->pfid_net = pfi_unmask(&aw->v.a.mask); if (af == AF_INET && dyn->pfid_net == 32) dyn->pfid_net = 128; strlcpy(tblname, aw->v.ifname, sizeof(tblname)); if (aw->iflags & PFI_AFLAG_NETWORK) strlcat(tblname, ":network", sizeof(tblname)); if (aw->iflags & PFI_AFLAG_BROADCAST) strlcat(tblname, ":broadcast", sizeof(tblname)); if (aw->iflags & PFI_AFLAG_PEER) strlcat(tblname, ":peer", sizeof(tblname)); if (aw->iflags & PFI_AFLAG_NOALIAS) strlcat(tblname, ":0", sizeof(tblname)); if (dyn->pfid_net != 128) snprintf(tblname + strlen(tblname), sizeof(tblname) - strlen(tblname), "/%d", dyn->pfid_net); if ((ruleset = pf_find_or_create_kruleset(PF_RESERVED_ANCHOR)) == NULL) { rv = ENOMEM; goto _bad; } if ((dyn->pfid_kt = pfr_attach_table(ruleset, tblname)) == NULL) { rv = ENOMEM; goto _bad; } dyn->pfid_kt->pfrkt_flags |= PFR_TFLAG_ACTIVE; dyn->pfid_iflags = aw->iflags; dyn->pfid_af = af; TAILQ_INSERT_TAIL(&dyn->pfid_kif->pfik_dynaddrs, dyn, entry); aw->p.dyn = dyn; NET_EPOCH_ENTER(et); pfi_kkif_update(dyn->pfid_kif); NET_EPOCH_EXIT(et); return (0); _bad: if (dyn->pfid_kt != NULL) pfr_detach_table(dyn->pfid_kt); if (ruleset != NULL) pf_remove_if_empty_kruleset(ruleset); pfi_kkif_unref(dyn->pfid_kif); free(dyn, PFI_MTYPE); return (rv); } static void pfi_kkif_update(struct pfi_kkif *kif) { struct ifg_list *ifgl; struct ifg_member *ifgm; struct pfi_dynaddr *p; struct pfi_kkif *tmpkif; NET_EPOCH_ASSERT(); PF_RULES_WASSERT(); /* update all dynaddr */ TAILQ_FOREACH(p, &kif->pfik_dynaddrs, entry) pfi_dynaddr_update(p); /* Apply group flags to new members. */ if (kif->pfik_group != NULL) { CK_STAILQ_FOREACH(ifgm, &kif->pfik_group->ifg_members, ifgm_next) { tmpkif = (struct pfi_kkif *)ifgm->ifgm_ifp->if_pf_kif; if (tmpkif == NULL) continue; tmpkif->pfik_flags |= kif->pfik_flags; } } /* again for all groups kif is member of */ if (kif->pfik_ifp != NULL) { CK_STAILQ_FOREACH(ifgl, &kif->pfik_ifp->if_groups, ifgl_next) if (ifgl->ifgl_group->ifg_pf_kif) { pfi_kkif_update((struct pfi_kkif *) ifgl->ifgl_group->ifg_pf_kif); } } } static void pfi_dynaddr_update(struct pfi_dynaddr *dyn) { struct pfi_kkif *kif; struct pfr_ktable *kt; PF_RULES_WASSERT(); KASSERT(dyn && dyn->pfid_kif && dyn->pfid_kt, ("%s: bad argument", __func__)); kif = dyn->pfid_kif; kt = dyn->pfid_kt; if (kt->pfrkt_larg != V_pfi_update) { /* this table needs to be brought up-to-date */ pfi_table_update(kt, kif, dyn->pfid_net, dyn->pfid_iflags); kt->pfrkt_larg = V_pfi_update; } pfr_dynaddr_update(kt, dyn); } static void pfi_table_update(struct pfr_ktable *kt, struct pfi_kkif *kif, uint8_t net, int flags) { int e, size2 = 0; struct ifg_member *ifgm; NET_EPOCH_ASSERT(); V_pfi_buffer_cnt = 0; if (kif->pfik_ifp != NULL) pfi_instance_add(kif->pfik_ifp, net, flags); else if (kif->pfik_group != NULL) { CK_STAILQ_FOREACH(ifgm, &kif->pfik_group->ifg_members, ifgm_next) pfi_instance_add(ifgm->ifgm_ifp, net, flags); } if ((e = pfr_set_addrs(&kt->pfrkt_t, V_pfi_buffer, V_pfi_buffer_cnt, &size2, - NULL, NULL, NULL, 0, PFR_TFLAG_ALLMASK))) + NULL, NULL, NULL, PFR_FLAG_START | PFR_FLAG_DONE, PFR_TFLAG_ALLMASK))) printf("%s: cannot set %d new addresses into table %s: %d\n", __func__, V_pfi_buffer_cnt, kt->pfrkt_name, e); } static void pfi_instance_add(struct ifnet *ifp, uint8_t net, int flags) { struct ifaddr *ia; int got4 = 0, got6 = 0; sa_family_t af; uint8_t net2; NET_EPOCH_ASSERT(); CK_STAILQ_FOREACH(ia, &ifp->if_addrhead, ifa_link) { if (ia->ifa_addr == NULL) continue; af = ia->ifa_addr->sa_family; if (af != AF_INET && af != AF_INET6) continue; /* * XXX: For point-to-point interfaces, (ifname:0) and IPv4, * jump over addresses without a proper route to work * around a problem with ppp not fully removing the * address used during IPCP. */ if ((ifp->if_flags & IFF_POINTOPOINT) && !(ia->ifa_flags & IFA_ROUTE) && (flags & PFI_AFLAG_NOALIAS) && (af == AF_INET)) continue; if ((flags & PFI_AFLAG_BROADCAST) && af == AF_INET6) continue; if ((flags & PFI_AFLAG_BROADCAST) && !(ifp->if_flags & IFF_BROADCAST)) continue; if ((flags & PFI_AFLAG_PEER) && !(ifp->if_flags & IFF_POINTOPOINT)) continue; if ((flags & (PFI_AFLAG_NETWORK | PFI_AFLAG_NOALIAS)) && af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL( &((struct sockaddr_in6 *)ia->ifa_addr)->sin6_addr)) continue; if (flags & PFI_AFLAG_NOALIAS) { if (af == AF_INET && got4) continue; if (af == AF_INET6 && got6) continue; } if (af == AF_INET) got4 = 1; else if (af == AF_INET6) got6 = 1; net2 = net; if (net2 == 128 && (flags & PFI_AFLAG_NETWORK)) { if (af == AF_INET) net2 = pfi_unmask(&((struct sockaddr_in *) ia->ifa_netmask)->sin_addr); else if (af == AF_INET6) net2 = pfi_unmask(&((struct sockaddr_in6 *) ia->ifa_netmask)->sin6_addr); } if (af == AF_INET && net2 > 32) net2 = 32; if (flags & PFI_AFLAG_BROADCAST) pfi_address_add(ia->ifa_broadaddr, af, net2); else if (flags & PFI_AFLAG_PEER) pfi_address_add(ia->ifa_dstaddr, af, net2); else pfi_address_add(ia->ifa_addr, af, net2); } } static void pfi_address_add(struct sockaddr *sa, sa_family_t af, uint8_t net) { struct pfr_addr *p; int i; if (V_pfi_buffer_cnt >= V_pfi_buffer_max) { int new_max = V_pfi_buffer_max * 2; if (new_max > PFI_BUFFER_MAX) { printf("%s: address buffer full (%d/%d)\n", __func__, V_pfi_buffer_cnt, PFI_BUFFER_MAX); return; } p = malloc(new_max * sizeof(*V_pfi_buffer), PFI_MTYPE, M_NOWAIT); if (p == NULL) { printf("%s: no memory to grow buffer (%d/%d)\n", __func__, V_pfi_buffer_cnt, PFI_BUFFER_MAX); return; } memcpy(p, V_pfi_buffer, V_pfi_buffer_max * sizeof(*V_pfi_buffer)); /* no need to zero buffer */ free(V_pfi_buffer, PFI_MTYPE); V_pfi_buffer = p; V_pfi_buffer_max = new_max; } if (af == AF_INET && net > 32) net = 128; p = V_pfi_buffer + V_pfi_buffer_cnt++; memset(p, 0, sizeof(*p)); p->pfra_af = af; p->pfra_net = net; if (af == AF_INET) p->pfra_ip4addr = ((struct sockaddr_in *)sa)->sin_addr; else if (af == AF_INET6) { p->pfra_ip6addr = ((struct sockaddr_in6 *)sa)->sin6_addr; if (IN6_IS_SCOPE_EMBED(&p->pfra_ip6addr)) p->pfra_ip6addr.s6_addr16[1] = 0; } /* mask network address bits */ if (net < 128) ((caddr_t)p)[p->pfra_net/8] &= ~(0xFF >> (p->pfra_net%8)); for (i = (p->pfra_net+7)/8; i < sizeof(p->pfra_u); i++) ((caddr_t)p)[i] = 0; } void pfi_dynaddr_remove(struct pfi_dynaddr *dyn) { KASSERT(dyn->pfid_kif != NULL, ("%s: null pfid_kif", __func__)); KASSERT(dyn->pfid_kt != NULL, ("%s: null pfid_kt", __func__)); TAILQ_REMOVE(&dyn->pfid_kif->pfik_dynaddrs, dyn, entry); pfi_kkif_unref(dyn->pfid_kif); pfr_detach_table(dyn->pfid_kt); free(dyn, PFI_MTYPE); } void pfi_dynaddr_copyout(struct pf_addr_wrap *aw) { KASSERT(aw->type == PF_ADDR_DYNIFTL, ("%s: type %u", __func__, aw->type)); if (aw->p.dyn == NULL || aw->p.dyn->pfid_kif == NULL) return; aw->p.dyncnt = aw->p.dyn->pfid_acnt4 + aw->p.dyn->pfid_acnt6; } static int pfi_kkif_compare(struct pfi_kkif *p, struct pfi_kkif *q) { return (strncmp(p->pfik_name, q->pfik_name, IFNAMSIZ)); } void pfi_update_status(const char *name, struct pf_status *pfs) { struct pfi_kkif *p; struct pfi_kif_cmp key; struct ifg_member p_member, *ifgm; CK_STAILQ_HEAD(, ifg_member) ifg_members; int i, j, k; if (pfs) { memset(pfs->pcounters, 0, sizeof(pfs->pcounters)); memset(pfs->bcounters, 0, sizeof(pfs->bcounters)); } strlcpy(key.pfik_name, name, sizeof(key.pfik_name)); p = RB_FIND(pfi_ifhead, &V_pfi_ifs, (struct pfi_kkif *)&key); if (p == NULL) { return; } if (p->pfik_group != NULL) { memcpy(&ifg_members, &p->pfik_group->ifg_members, sizeof(ifg_members)); } else { /* build a temporary list for p only */ memset(&p_member, 0, sizeof(p_member)); p_member.ifgm_ifp = p->pfik_ifp; CK_STAILQ_INIT(&ifg_members); CK_STAILQ_INSERT_TAIL(&ifg_members, &p_member, ifgm_next); } CK_STAILQ_FOREACH(ifgm, &ifg_members, ifgm_next) { if (ifgm->ifgm_ifp == NULL || ifgm->ifgm_ifp->if_pf_kif == NULL) continue; p = (struct pfi_kkif *)ifgm->ifgm_ifp->if_pf_kif; /* just clear statistics */ if (pfs == NULL) { pf_kkif_zero(p); continue; } for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) { pfs->pcounters[i][j][k] += pf_counter_u64_fetch(&p->pfik_packets[i][j][k]); pfs->bcounters[i][j] += pf_counter_u64_fetch(&p->pfik_bytes[i][j][k]); } } } static void pf_kkif_to_kif(struct pfi_kkif *kkif, struct pfi_kif *kif) { memset(kif, 0, sizeof(*kif)); strlcpy(kif->pfik_name, kkif->pfik_name, sizeof(kif->pfik_name)); for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { kif->pfik_packets[i][j][k] = pf_counter_u64_fetch(&kkif->pfik_packets[i][j][k]); kif->pfik_bytes[i][j][k] = pf_counter_u64_fetch(&kkif->pfik_bytes[i][j][k]); } } } kif->pfik_flags = kkif->pfik_flags; kif->pfik_tzero = kkif->pfik_tzero; kif->pfik_rulerefs = kkif->pfik_rulerefs; /* * Userspace relies on this pointer to decide if this is a group or * not. We don't want to share the actual pointer, because it's * useless to userspace and leaks kernel memory layout information. * So instead we provide 0xfeedcode as 'true' and NULL as 'false'. */ kif->pfik_group = kkif->pfik_group ? (struct ifg_group *)0xfeedc0de : NULL; } void pfi_get_ifaces(const char *name, struct pfi_kif *buf, int *size) { struct epoch_tracker et; struct pfi_kkif *p, *nextp; int n = 0; NET_EPOCH_ENTER(et); for (p = RB_MIN(pfi_ifhead, &V_pfi_ifs); p; p = nextp) { nextp = RB_NEXT(pfi_ifhead, &V_pfi_ifs, p); if (pfi_skip_if(name, p)) continue; if (*size <= n++) break; if (!p->pfik_tzero) p->pfik_tzero = time_second; pf_kkif_to_kif(p, buf++); nextp = RB_NEXT(pfi_ifhead, &V_pfi_ifs, p); } *size = n; NET_EPOCH_EXIT(et); } static int pfi_skip_if(const char *filter, struct pfi_kkif *p) { struct ifg_list *i; int n; NET_EPOCH_ASSERT(); if (filter == NULL || !*filter) return (0); if (!strcmp(p->pfik_name, filter)) return (0); /* exact match */ n = strlen(filter); if (n < 1 || n >= IFNAMSIZ) return (1); /* sanity check */ if (filter[n-1] >= '0' && filter[n-1] <= '9') return (1); /* group names may not end in a digit */ if (p->pfik_ifp == NULL) return (1); CK_STAILQ_FOREACH(i, &p->pfik_ifp->if_groups, ifgl_next) if (!strncmp(i->ifgl_group->ifg_group, filter, IFNAMSIZ)) return (0); /* iface is in group "filter" */ return (1); } int pfi_set_flags(const char *name, int flags) { struct epoch_tracker et; struct pfi_kkif *p, *kif; kif = pf_kkif_create(M_NOWAIT); if (kif == NULL) return (ENOMEM); NET_EPOCH_ENTER(et); kif = pfi_kkif_attach(kif, name); RB_FOREACH(p, pfi_ifhead, &V_pfi_ifs) { if (pfi_skip_if(name, p)) continue; p->pfik_flags |= flags; } NET_EPOCH_EXIT(et); return (0); } int pfi_clear_flags(const char *name, int flags) { struct epoch_tracker et; struct pfi_kkif *p, *tmp; NET_EPOCH_ENTER(et); RB_FOREACH_SAFE(p, pfi_ifhead, &V_pfi_ifs, tmp) { if (pfi_skip_if(name, p)) continue; p->pfik_flags &= ~flags; if (p->pfik_ifp == NULL && p->pfik_group == NULL && p->pfik_flags == 0 && p->pfik_rulerefs == 0) { /* Delete this kif. */ RB_REMOVE(pfi_ifhead, &V_pfi_ifs, p); pf_kkif_free(p); } } NET_EPOCH_EXIT(et); return (0); } /* from pf_print_state.c */ static int pfi_unmask(void *addr) { struct pf_addr *m = addr; int i = 31, j = 0, b = 0; u_int32_t tmp; while (j < 4 && m->addr32[j] == 0xffffffff) { b += 32; j++; } if (j < 4) { tmp = ntohl(m->addr32[j]); for (i = 31; tmp & (1 << i); --i) b++; } return (b); } static void pfi_attach_ifnet_event(void *arg __unused, struct ifnet *ifp) { struct epoch_tracker et; struct pfi_kkif *kif; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } kif = pf_kkif_create(M_NOWAIT); NET_EPOCH_ENTER(et); PF_RULES_WLOCK(); pfi_attach_ifnet(ifp, kif); #ifdef ALTQ pf_altq_ifnet_event(ifp, 0); #endif PF_RULES_WUNLOCK(); NET_EPOCH_EXIT(et); } static void pfi_detach_ifnet_event(void *arg __unused, struct ifnet *ifp) { struct epoch_tracker et; struct pfi_kkif *kif = (struct pfi_kkif *)ifp->if_pf_kif; if (pfsync_detach_ifnet_ptr) pfsync_detach_ifnet_ptr(ifp); if (kif == NULL) return; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } NET_EPOCH_ENTER(et); PF_RULES_WLOCK(); V_pfi_update++; pfi_kkif_update(kif); if (kif->pfik_ifp) if_rele(kif->pfik_ifp); kif->pfik_ifp = NULL; ifp->if_pf_kif = NULL; #ifdef ALTQ pf_altq_ifnet_event(ifp, 1); #endif pfi_kkif_remove_if_unref(kif); PF_RULES_WUNLOCK(); NET_EPOCH_EXIT(et); } static void pfi_attach_group_event(void *arg __unused, struct ifg_group *ifg) { struct epoch_tracker et; struct pfi_kkif *kif; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } kif = pf_kkif_create(M_WAITOK); NET_EPOCH_ENTER(et); PF_RULES_WLOCK(); pfi_attach_ifgroup(ifg, kif); PF_RULES_WUNLOCK(); NET_EPOCH_EXIT(et); } static void pfi_change_group_event(void *arg __unused, char *gname) { struct epoch_tracker et; struct pfi_kkif *kif; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } kif = pf_kkif_create(M_WAITOK); NET_EPOCH_ENTER(et); PF_RULES_WLOCK(); V_pfi_update++; kif = pfi_kkif_attach(kif, gname); pfi_kkif_update(kif); PF_RULES_WUNLOCK(); NET_EPOCH_EXIT(et); } static void pfi_detach_group_event(void *arg __unused, struct ifg_group *ifg) { struct pfi_kkif *kif = (struct pfi_kkif *)ifg->ifg_pf_kif; if (kif == NULL) return; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } PF_RULES_WLOCK(); V_pfi_update++; kif->pfik_group = NULL; ifg->ifg_pf_kif = NULL; pfi_kkif_remove_if_unref(kif); PF_RULES_WUNLOCK(); } static void pfi_ifaddr_event(void *arg __unused, struct ifnet *ifp) { KASSERT(ifp, ("ifp == NULL")); if (ifp->if_pf_kif == NULL) return; if (V_pf_vnet_active == 0) { /* Avoid teardown race in the least expensive way. */ return; } PF_RULES_WLOCK(); if (ifp->if_pf_kif) { struct epoch_tracker et; V_pfi_update++; NET_EPOCH_ENTER(et); pfi_kkif_update(ifp->if_pf_kif); NET_EPOCH_EXIT(et); } PF_RULES_WUNLOCK(); } diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index 703ecf446fad..5ec67021068b 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -1,7167 +1,7167 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * Copyright (c) 2012 Gleb Smirnoff * 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. * * Effort sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F30602-01-2-0537. * * $OpenBSD: pf_ioctl.c,v 1.213 2009/02/15 21:46:12 mbalmer Exp $ */ #include #include "opt_inet.h" #include "opt_inet6.h" #include "opt_bpf.h" #include "opt_pf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif /* INET6 */ #ifdef ALTQ #include #endif SDT_PROBE_DEFINE3(pf, ioctl, ioctl, error, "int", "int", "int"); SDT_PROBE_DEFINE3(pf, ioctl, function, error, "char *", "int", "int"); SDT_PROBE_DEFINE2(pf, ioctl, addrule, error, "int", "int"); SDT_PROBE_DEFINE2(pf, ioctl, nvchk, error, "int", "int"); static struct pf_kpool *pf_get_kpool(const char *, u_int32_t, u_int8_t, u_int32_t, u_int8_t, u_int8_t, u_int8_t, int); static void pf_mv_kpool(struct pf_kpalist *, struct pf_kpalist *); static void pf_empty_kpool(struct pf_kpalist *); static int pfioctl(struct cdev *, u_long, caddr_t, int, struct thread *); static int pf_begin_eth(uint32_t *, const char *); static int pf_rollback_eth(uint32_t, const char *); static int pf_commit_eth(uint32_t, const char *); static void pf_free_eth_rule(struct pf_keth_rule *); #ifdef ALTQ static int pf_begin_altq(u_int32_t *); static int pf_rollback_altq(u_int32_t); static int pf_commit_altq(u_int32_t); static int pf_enable_altq(struct pf_altq *); static int pf_disable_altq(struct pf_altq *); static void pf_qid_unref(uint16_t); #endif /* ALTQ */ static int pf_begin_rules(u_int32_t *, int, const char *); static int pf_rollback_rules(u_int32_t, int, char *); static int pf_setup_pfsync_matching(struct pf_kruleset *); static void pf_hash_rule_rolling(MD5_CTX *, struct pf_krule *); static void pf_hash_rule(struct pf_krule *); static void pf_hash_rule_addr(MD5_CTX *, struct pf_rule_addr *); static int pf_commit_rules(u_int32_t, int, char *); static int pf_addr_setup(struct pf_kruleset *, struct pf_addr_wrap *, sa_family_t); static void pf_src_node_copy(const struct pf_ksrc_node *, struct pf_src_node *); #ifdef ALTQ static int pf_export_kaltq(struct pf_altq *, struct pfioc_altq_v1 *, size_t); static int pf_import_kaltq(struct pfioc_altq_v1 *, struct pf_altq *, size_t); #endif /* ALTQ */ VNET_DEFINE(struct pf_krule, pf_default_rule); static __inline int pf_krule_compare(struct pf_krule *, struct pf_krule *); RB_GENERATE(pf_krule_global, pf_krule, entry_global, pf_krule_compare); #ifdef ALTQ VNET_DEFINE_STATIC(int, pf_altq_running); #define V_pf_altq_running VNET(pf_altq_running) #endif #define TAGID_MAX 50000 struct pf_tagname { TAILQ_ENTRY(pf_tagname) namehash_entries; TAILQ_ENTRY(pf_tagname) taghash_entries; char name[PF_TAG_NAME_SIZE]; uint16_t tag; int ref; }; struct pf_tagset { TAILQ_HEAD(, pf_tagname) *namehash; TAILQ_HEAD(, pf_tagname) *taghash; unsigned int mask; uint32_t seed; BITSET_DEFINE(, TAGID_MAX) avail; }; VNET_DEFINE(struct pf_tagset, pf_tags); #define V_pf_tags VNET(pf_tags) static unsigned int pf_rule_tag_hashsize; #define PF_RULE_TAG_HASH_SIZE_DEFAULT 128 SYSCTL_UINT(_net_pf, OID_AUTO, rule_tag_hashsize, CTLFLAG_RDTUN, &pf_rule_tag_hashsize, PF_RULE_TAG_HASH_SIZE_DEFAULT, "Size of pf(4) rule tag hashtable"); #ifdef ALTQ VNET_DEFINE(struct pf_tagset, pf_qids); #define V_pf_qids VNET(pf_qids) static unsigned int pf_queue_tag_hashsize; #define PF_QUEUE_TAG_HASH_SIZE_DEFAULT 128 SYSCTL_UINT(_net_pf, OID_AUTO, queue_tag_hashsize, CTLFLAG_RDTUN, &pf_queue_tag_hashsize, PF_QUEUE_TAG_HASH_SIZE_DEFAULT, "Size of pf(4) queue tag hashtable"); #endif VNET_DEFINE(uma_zone_t, pf_tag_z); #define V_pf_tag_z VNET(pf_tag_z) static MALLOC_DEFINE(M_PFALTQ, "pf_altq", "pf(4) altq configuration db"); static MALLOC_DEFINE(M_PFRULE, "pf_rule", "pf(4) rules"); MALLOC_DEFINE(M_PF, "pf", "pf(4)"); #if (PF_QNAME_SIZE != PF_TAG_NAME_SIZE) #error PF_QNAME_SIZE must be equal to PF_TAG_NAME_SIZE #endif VNET_DEFINE_STATIC(bool, pf_filter_local) = false; #define V_pf_filter_local VNET(pf_filter_local) SYSCTL_BOOL(_net_pf, OID_AUTO, filter_local, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(pf_filter_local), false, "Enable filtering for packets delivered to local network stack"); #ifdef PF_DEFAULT_TO_DROP VNET_DEFINE_STATIC(bool, default_to_drop) = true; #else VNET_DEFINE_STATIC(bool, default_to_drop); #endif #define V_default_to_drop VNET(default_to_drop) SYSCTL_BOOL(_net_pf, OID_AUTO, default_to_drop, CTLFLAG_RDTUN | CTLFLAG_VNET, &VNET_NAME(default_to_drop), false, "Make the default rule drop all packets."); static void pf_init_tagset(struct pf_tagset *, unsigned int *, unsigned int); static void pf_cleanup_tagset(struct pf_tagset *); static uint16_t tagname2hashindex(const struct pf_tagset *, const char *); static uint16_t tag2hashindex(const struct pf_tagset *, uint16_t); static u_int16_t tagname2tag(struct pf_tagset *, const char *, bool); static void tag_unref(struct pf_tagset *, u_int16_t); struct cdev *pf_dev; /* * XXX - These are new and need to be checked when moveing to a new version */ static void pf_clear_all_states(void); static int pf_killstates_row(struct pf_kstate_kill *, struct pf_idhash *); static int pf_killstates_nv(struct pfioc_nv *); static int pf_clearstates_nv(struct pfioc_nv *); static int pf_getstate(struct pfioc_nv *); static int pf_getstatus(struct pfioc_nv *); static int pf_clear_tables(void); static void pf_kill_srcnodes(struct pfioc_src_node_kill *); static int pf_keepcounters(struct pfioc_nv *); static void pf_tbladdr_copyout(struct pf_addr_wrap *); /* * Wrapper functions for pfil(9) hooks */ static pfil_return_t pf_eth_check_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); static pfil_return_t pf_eth_check_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); #ifdef INET static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); static pfil_return_t pf_check_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); #endif #ifdef INET6 static pfil_return_t pf_check6_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); static pfil_return_t pf_check6_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp); #endif static void hook_pf_eth(void); static void hook_pf(void); static void dehook_pf_eth(void); static void dehook_pf(void); static int shutdown_pf(void); static int pf_load(void); static void pf_unload(void *); static struct cdevsw pf_cdevsw = { .d_ioctl = pfioctl, .d_name = PF_NAME, .d_version = D_VERSION, }; VNET_DEFINE_STATIC(bool, pf_pfil_hooked); #define V_pf_pfil_hooked VNET(pf_pfil_hooked) VNET_DEFINE_STATIC(bool, pf_pfil_eth_hooked); #define V_pf_pfil_eth_hooked VNET(pf_pfil_eth_hooked) /* * We need a flag that is neither hooked nor running to know when * the VNET is "valid". We primarily need this to control (global) * external event, e.g., eventhandlers. */ VNET_DEFINE(int, pf_vnet_active); #define V_pf_vnet_active VNET(pf_vnet_active) int pf_end_threads; struct proc *pf_purge_proc; VNET_DEFINE(struct rmlock, pf_rules_lock); VNET_DEFINE(struct rmlock, pf_tags_lock); VNET_DEFINE_STATIC(struct sx, pf_ioctl_lock); #define V_pf_ioctl_lock VNET(pf_ioctl_lock) struct sx pf_end_lock; /* pfsync */ VNET_DEFINE(pfsync_state_import_t *, pfsync_state_import_ptr); VNET_DEFINE(pfsync_insert_state_t *, pfsync_insert_state_ptr); VNET_DEFINE(pfsync_update_state_t *, pfsync_update_state_ptr); VNET_DEFINE(pfsync_delete_state_t *, pfsync_delete_state_ptr); VNET_DEFINE(pfsync_clear_states_t *, pfsync_clear_states_ptr); VNET_DEFINE(pfsync_defer_t *, pfsync_defer_ptr); VNET_DEFINE(pflow_export_state_t *, pflow_export_state_ptr); pfsync_detach_ifnet_t *pfsync_detach_ifnet_ptr; /* pflog */ pflog_packet_t *pflog_packet_ptr = NULL; /* * Copy a user-provided string, returning an error if truncation would occur. * Avoid scanning past "sz" bytes in the source string since there's no * guarantee that it's nul-terminated. */ static int pf_user_strcpy(char *dst, const char *src, size_t sz) { if (strnlen(src, sz) == sz) return (EINVAL); (void)strlcpy(dst, src, sz); return (0); } static void pfattach_vnet(void) { u_int32_t *my_timeout = V_pf_default_rule.timeout; bzero(&V_pf_status, sizeof(V_pf_status)); pf_initialize(); pfr_initialize(); pfi_initialize_vnet(); pf_normalize_init(); pf_syncookies_init(); V_pf_limits[PF_LIMIT_STATES].limit = PFSTATE_HIWAT; V_pf_limits[PF_LIMIT_SRC_NODES].limit = PFSNODE_HIWAT; V_pf_limits[PF_LIMIT_ANCHORS].limit = PF_ANCHOR_HIWAT; V_pf_limits[PF_LIMIT_ETH_ANCHORS].limit = PF_ANCHOR_HIWAT; RB_INIT(&V_pf_anchors); pf_init_kruleset(&pf_main_ruleset); pf_init_keth(V_pf_keth); /* default rule should never be garbage collected */ V_pf_default_rule.entries.tqe_prev = &V_pf_default_rule.entries.tqe_next; V_pf_default_rule.action = V_default_to_drop ? PF_DROP : PF_PASS; V_pf_default_rule.nr = (uint32_t)-1; V_pf_default_rule.rtableid = -1; pf_counter_u64_init(&V_pf_default_rule.evaluations, M_WAITOK); for (int i = 0; i < 2; i++) { pf_counter_u64_init(&V_pf_default_rule.packets[i], M_WAITOK); pf_counter_u64_init(&V_pf_default_rule.bytes[i], M_WAITOK); } V_pf_default_rule.states_cur = counter_u64_alloc(M_WAITOK); V_pf_default_rule.states_tot = counter_u64_alloc(M_WAITOK); for (pf_sn_types_t sn_type = 0; sn_type= PF_RULESET_MAX) return (NULL); if (active) { if (check_ticket && ticket != ruleset->rules[rs_num].active.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_krulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); } else { if (check_ticket && ticket != ruleset->rules[rs_num].inactive.ticket) return (NULL); if (r_last) rule = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_krulequeue); else rule = TAILQ_FIRST(ruleset->rules[rs_num].inactive.ptr); } if (!r_last) { while ((rule != NULL) && (rule->nr != rule_number)) rule = TAILQ_NEXT(rule, entries); } if (rule == NULL) return (NULL); switch (which) { case PF_RDR: return (&rule->rdr); case PF_NAT: return (&rule->nat); case PF_RT: return (&rule->route); default: panic("Unknow pool type %d", which); } } static void pf_mv_kpool(struct pf_kpalist *poola, struct pf_kpalist *poolb) { struct pf_kpooladdr *mv_pool_pa; while ((mv_pool_pa = TAILQ_FIRST(poola)) != NULL) { TAILQ_REMOVE(poola, mv_pool_pa, entries); TAILQ_INSERT_TAIL(poolb, mv_pool_pa, entries); } } static void pf_empty_kpool(struct pf_kpalist *poola) { struct pf_kpooladdr *pa; while ((pa = TAILQ_FIRST(poola)) != NULL) { switch (pa->addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(pa->addr.p.dyn); break; case PF_ADDR_TABLE: /* XXX: this could be unfinished pooladdr on pabuf */ if (pa->addr.p.tbl != NULL) pfr_detach_table(pa->addr.p.tbl); break; } if (pa->kif) pfi_kkif_unref(pa->kif); TAILQ_REMOVE(poola, pa, entries); free(pa, M_PFRULE); } } static void pf_unlink_rule_locked(struct pf_krulequeue *rulequeue, struct pf_krule *rule) { PF_RULES_WASSERT(); PF_UNLNKDRULES_ASSERT(); TAILQ_REMOVE(rulequeue, rule, entries); rule->rule_ref |= PFRULE_REFS; TAILQ_INSERT_TAIL(&V_pf_unlinked_rules, rule, entries); } static void pf_unlink_rule(struct pf_krulequeue *rulequeue, struct pf_krule *rule) { PF_RULES_WASSERT(); PF_UNLNKDRULES_LOCK(); pf_unlink_rule_locked(rulequeue, rule); PF_UNLNKDRULES_UNLOCK(); } static void pf_free_eth_rule(struct pf_keth_rule *rule) { PF_RULES_WASSERT(); if (rule == NULL) return; if (rule->tag) tag_unref(&V_pf_tags, rule->tag); if (rule->match_tag) tag_unref(&V_pf_tags, rule->match_tag); #ifdef ALTQ pf_qid_unref(rule->qid); #endif if (rule->bridge_to) pfi_kkif_unref(rule->bridge_to); if (rule->kif) pfi_kkif_unref(rule->kif); if (rule->ipsrc.addr.type == PF_ADDR_TABLE) pfr_detach_table(rule->ipsrc.addr.p.tbl); if (rule->ipdst.addr.type == PF_ADDR_TABLE) pfr_detach_table(rule->ipdst.addr.p.tbl); counter_u64_free(rule->evaluations); for (int i = 0; i < 2; i++) { counter_u64_free(rule->packets[i]); counter_u64_free(rule->bytes[i]); } uma_zfree_pcpu(pf_timestamp_pcpu_zone, rule->timestamp); pf_keth_anchor_remove(rule); free(rule, M_PFRULE); } void pf_free_rule(struct pf_krule *rule) { PF_RULES_WASSERT(); PF_CONFIG_ASSERT(); if (rule->tag) tag_unref(&V_pf_tags, rule->tag); if (rule->match_tag) tag_unref(&V_pf_tags, rule->match_tag); #ifdef ALTQ if (rule->pqid != rule->qid) pf_qid_unref(rule->pqid); pf_qid_unref(rule->qid); #endif switch (rule->src.addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(rule->src.addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(rule->src.addr.p.tbl); break; } switch (rule->dst.addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(rule->dst.addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(rule->dst.addr.p.tbl); break; } if (rule->overload_tbl) pfr_detach_table(rule->overload_tbl); if (rule->kif) pfi_kkif_unref(rule->kif); if (rule->rcv_kif) pfi_kkif_unref(rule->rcv_kif); pf_remove_kanchor(rule); pf_empty_kpool(&rule->rdr.list); pf_empty_kpool(&rule->nat.list); pf_empty_kpool(&rule->route.list); pf_krule_free(rule); } static void pf_init_tagset(struct pf_tagset *ts, unsigned int *tunable_size, unsigned int default_size) { unsigned int i; unsigned int hashsize; if (*tunable_size == 0 || !powerof2(*tunable_size)) *tunable_size = default_size; hashsize = *tunable_size; ts->namehash = mallocarray(hashsize, sizeof(*ts->namehash), M_PFHASH, M_WAITOK); ts->taghash = mallocarray(hashsize, sizeof(*ts->taghash), M_PFHASH, M_WAITOK); ts->mask = hashsize - 1; ts->seed = arc4random(); for (i = 0; i < hashsize; i++) { TAILQ_INIT(&ts->namehash[i]); TAILQ_INIT(&ts->taghash[i]); } BIT_FILL(TAGID_MAX, &ts->avail); } static void pf_cleanup_tagset(struct pf_tagset *ts) { unsigned int i; unsigned int hashsize; struct pf_tagname *t, *tmp; /* * Only need to clean up one of the hashes as each tag is hashed * into each table. */ hashsize = ts->mask + 1; for (i = 0; i < hashsize; i++) TAILQ_FOREACH_SAFE(t, &ts->namehash[i], namehash_entries, tmp) uma_zfree(V_pf_tag_z, t); free(ts->namehash, M_PFHASH); free(ts->taghash, M_PFHASH); } static uint16_t tagname2hashindex(const struct pf_tagset *ts, const char *tagname) { size_t len; len = strnlen(tagname, PF_TAG_NAME_SIZE - 1); return (murmur3_32_hash(tagname, len, ts->seed) & ts->mask); } static uint16_t tag2hashindex(const struct pf_tagset *ts, uint16_t tag) { return (tag & ts->mask); } static u_int16_t tagname2tag(struct pf_tagset *ts, const char *tagname, bool add_new) { struct pf_tagname *tag; u_int32_t index; u_int16_t new_tagid; PF_TAGS_RLOCK_TRACKER; PF_TAGS_RLOCK(); index = tagname2hashindex(ts, tagname); TAILQ_FOREACH(tag, &ts->namehash[index], namehash_entries) if (strcmp(tagname, tag->name) == 0) { tag->ref++; new_tagid = tag->tag; PF_TAGS_RUNLOCK(); return (new_tagid); } /* * When used for pfsync with queues we must not create new entries. * Pf tags can be created just fine by this function, but queues * require additional configuration. If they are missing on the target * system we just ignore them */ if (add_new == false) { printf("%s: Not creating a new tag\n", __func__); PF_TAGS_RUNLOCK(); return (0); } /* * If a new entry must be created do it under a write lock. * But first search again, somebody could have created the tag * between unlocking the read lock and locking the write lock. */ PF_TAGS_RUNLOCK(); PF_TAGS_WLOCK(); TAILQ_FOREACH(tag, &ts->namehash[index], namehash_entries) if (strcmp(tagname, tag->name) == 0) { tag->ref++; new_tagid = tag->tag; PF_TAGS_WUNLOCK(); return (new_tagid); } /* * new entry * * to avoid fragmentation, we do a linear search from the beginning * and take the first free slot we find. */ new_tagid = BIT_FFS(TAGID_MAX, &ts->avail); /* * Tags are 1-based, with valid tags in the range [1..TAGID_MAX]. * BIT_FFS() returns a 1-based bit number, with 0 indicating no bits * set. It may also return a bit number greater than TAGID_MAX due * to rounding of the number of bits in the vector up to a multiple * of the vector word size at declaration/allocation time. */ if ((new_tagid == 0) || (new_tagid > TAGID_MAX)) { PF_TAGS_WUNLOCK(); return (0); } /* Mark the tag as in use. Bits are 0-based for BIT_CLR() */ BIT_CLR(TAGID_MAX, new_tagid - 1, &ts->avail); /* allocate and fill new struct pf_tagname */ tag = uma_zalloc(V_pf_tag_z, M_NOWAIT); if (tag == NULL) { PF_TAGS_WUNLOCK(); return (0); } strlcpy(tag->name, tagname, sizeof(tag->name)); tag->tag = new_tagid; tag->ref = 1; /* Insert into namehash */ TAILQ_INSERT_TAIL(&ts->namehash[index], tag, namehash_entries); /* Insert into taghash */ index = tag2hashindex(ts, new_tagid); TAILQ_INSERT_TAIL(&ts->taghash[index], tag, taghash_entries); PF_TAGS_WUNLOCK(); return (new_tagid); } static char * tag2tagname(struct pf_tagset *ts, u_int16_t tag) { struct pf_tagname *t; uint16_t index; PF_TAGS_RLOCK_TRACKER; PF_TAGS_RLOCK(); index = tag2hashindex(ts, tag); TAILQ_FOREACH(t, &ts->taghash[index], taghash_entries) if (tag == t->tag) { PF_TAGS_RUNLOCK(); return (t->name); } PF_TAGS_RUNLOCK(); return (NULL); } static void tag_unref(struct pf_tagset *ts, u_int16_t tag) { struct pf_tagname *t; uint16_t index; PF_TAGS_WLOCK(); index = tag2hashindex(ts, tag); TAILQ_FOREACH(t, &ts->taghash[index], taghash_entries) if (tag == t->tag) { if (--t->ref == 0) { TAILQ_REMOVE(&ts->taghash[index], t, taghash_entries); index = tagname2hashindex(ts, t->name); TAILQ_REMOVE(&ts->namehash[index], t, namehash_entries); /* Bits are 0-based for BIT_SET() */ BIT_SET(TAGID_MAX, tag - 1, &ts->avail); uma_zfree(V_pf_tag_z, t); } break; } PF_TAGS_WUNLOCK(); } uint16_t pf_tagname2tag(const char *tagname) { return (tagname2tag(&V_pf_tags, tagname, true)); } static const char * pf_tag2tagname(uint16_t tag) { return (tag2tagname(&V_pf_tags, tag)); } static int pf_begin_eth(uint32_t *ticket, const char *anchor) { struct pf_keth_rule *rule, *tmp; struct pf_keth_ruleset *rs; PF_RULES_WASSERT(); rs = pf_find_or_create_keth_ruleset(anchor); if (rs == NULL) return (EINVAL); /* Purge old inactive rules. */ TAILQ_FOREACH_SAFE(rule, rs->inactive.rules, entries, tmp) { TAILQ_REMOVE(rs->inactive.rules, rule, entries); pf_free_eth_rule(rule); } *ticket = ++rs->inactive.ticket; rs->inactive.open = 1; return (0); } static int pf_rollback_eth(uint32_t ticket, const char *anchor) { struct pf_keth_rule *rule, *tmp; struct pf_keth_ruleset *rs; PF_RULES_WASSERT(); rs = pf_find_keth_ruleset(anchor); if (rs == NULL) return (EINVAL); if (!rs->inactive.open || ticket != rs->inactive.ticket) return (0); /* Purge old inactive rules. */ TAILQ_FOREACH_SAFE(rule, rs->inactive.rules, entries, tmp) { TAILQ_REMOVE(rs->inactive.rules, rule, entries); pf_free_eth_rule(rule); } rs->inactive.open = 0; pf_remove_if_empty_keth_ruleset(rs); return (0); } #define PF_SET_SKIP_STEPS(i) \ do { \ while (head[i] != cur) { \ head[i]->skip[i].ptr = cur; \ head[i] = TAILQ_NEXT(head[i], entries); \ } \ } while (0) static void pf_eth_calc_skip_steps(struct pf_keth_ruleq *rules) { struct pf_keth_rule *cur, *prev, *head[PFE_SKIP_COUNT]; int i; cur = TAILQ_FIRST(rules); prev = cur; for (i = 0; i < PFE_SKIP_COUNT; ++i) head[i] = cur; while (cur != NULL) { if (cur->kif != prev->kif || cur->ifnot != prev->ifnot) PF_SET_SKIP_STEPS(PFE_SKIP_IFP); if (cur->direction != prev->direction) PF_SET_SKIP_STEPS(PFE_SKIP_DIR); if (cur->proto != prev->proto) PF_SET_SKIP_STEPS(PFE_SKIP_PROTO); if (memcmp(&cur->src, &prev->src, sizeof(cur->src)) != 0) PF_SET_SKIP_STEPS(PFE_SKIP_SRC_ADDR); if (memcmp(&cur->dst, &prev->dst, sizeof(cur->dst)) != 0) PF_SET_SKIP_STEPS(PFE_SKIP_DST_ADDR); if (cur->ipsrc.neg != prev->ipsrc.neg || pf_addr_wrap_neq(&cur->ipsrc.addr, &prev->ipsrc.addr)) PF_SET_SKIP_STEPS(PFE_SKIP_SRC_IP_ADDR); if (cur->ipdst.neg != prev->ipdst.neg || pf_addr_wrap_neq(&cur->ipdst.addr, &prev->ipdst.addr)) PF_SET_SKIP_STEPS(PFE_SKIP_DST_IP_ADDR); prev = cur; cur = TAILQ_NEXT(cur, entries); } for (i = 0; i < PFE_SKIP_COUNT; ++i) PF_SET_SKIP_STEPS(i); } static int pf_commit_eth(uint32_t ticket, const char *anchor) { struct pf_keth_ruleq *rules; struct pf_keth_ruleset *rs; rs = pf_find_keth_ruleset(anchor); if (rs == NULL) { return (EINVAL); } if (!rs->inactive.open || ticket != rs->inactive.ticket) return (EBUSY); PF_RULES_WASSERT(); pf_eth_calc_skip_steps(rs->inactive.rules); rules = rs->active.rules; atomic_store_ptr(&rs->active.rules, rs->inactive.rules); rs->inactive.rules = rules; rs->inactive.ticket = rs->active.ticket; return (pf_rollback_eth(rs->inactive.ticket, rs->anchor ? rs->anchor->path : "")); } #ifdef ALTQ uint16_t pf_qname2qid(const char *qname, bool add_new) { return (tagname2tag(&V_pf_qids, qname, add_new)); } static const char * pf_qid2qname(uint16_t qid) { return (tag2tagname(&V_pf_qids, qid)); } static void pf_qid_unref(uint16_t qid) { tag_unref(&V_pf_qids, qid); } static int pf_begin_altq(u_int32_t *ticket) { struct pf_altq *altq, *tmp; int error = 0; PF_RULES_WASSERT(); /* Purge the old altq lists */ TAILQ_FOREACH_SAFE(altq, V_pf_altq_ifs_inactive, entries, tmp) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ error = altq_remove(altq); } free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altq_ifs_inactive); TAILQ_FOREACH_SAFE(altq, V_pf_altqs_inactive, entries, tmp) { pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altqs_inactive); if (error) return (error); *ticket = ++V_ticket_altqs_inactive; V_altqs_inactive_open = 1; return (0); } static int pf_rollback_altq(u_int32_t ticket) { struct pf_altq *altq, *tmp; int error = 0; PF_RULES_WASSERT(); if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) return (0); /* Purge the old altq lists */ TAILQ_FOREACH_SAFE(altq, V_pf_altq_ifs_inactive, entries, tmp) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ error = altq_remove(altq); } free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altq_ifs_inactive); TAILQ_FOREACH_SAFE(altq, V_pf_altqs_inactive, entries, tmp) { pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altqs_inactive); V_altqs_inactive_open = 0; return (error); } static int pf_commit_altq(u_int32_t ticket) { struct pf_altqqueue *old_altqs, *old_altq_ifs; struct pf_altq *altq, *tmp; int err, error = 0; PF_RULES_WASSERT(); if (!V_altqs_inactive_open || ticket != V_ticket_altqs_inactive) return (EBUSY); /* swap altqs, keep the old. */ old_altqs = V_pf_altqs_active; old_altq_ifs = V_pf_altq_ifs_active; V_pf_altqs_active = V_pf_altqs_inactive; V_pf_altq_ifs_active = V_pf_altq_ifs_inactive; V_pf_altqs_inactive = old_altqs; V_pf_altq_ifs_inactive = old_altq_ifs; V_ticket_altqs_active = V_ticket_altqs_inactive; /* Attach new disciplines */ TAILQ_FOREACH(altq, V_pf_altq_ifs_active, entries) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* attach the discipline */ error = altq_pfattach(altq); if (error == 0 && V_pf_altq_running) error = pf_enable_altq(altq); if (error != 0) return (error); } } /* Purge the old altq lists */ TAILQ_FOREACH_SAFE(altq, V_pf_altq_ifs_inactive, entries, tmp) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { /* detach and destroy the discipline */ if (V_pf_altq_running) error = pf_disable_altq(altq); err = altq_pfdetach(altq); if (err != 0 && error == 0) error = err; err = altq_remove(altq); if (err != 0 && error == 0) error = err; } free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altq_ifs_inactive); TAILQ_FOREACH_SAFE(altq, V_pf_altqs_inactive, entries, tmp) { pf_qid_unref(altq->qid); free(altq, M_PFALTQ); } TAILQ_INIT(V_pf_altqs_inactive); V_altqs_inactive_open = 0; return (error); } static int pf_enable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int error = 0; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); if (ifp->if_snd.altq_type != ALTQT_NONE) error = altq_enable(&ifp->if_snd); /* set tokenbucket regulator */ if (error == 0 && ifp != NULL && ALTQ_IS_ENABLED(&ifp->if_snd)) { tb.rate = altq->ifbandwidth; tb.depth = altq->tbrsize; error = tbr_set(&ifp->if_snd, &tb); } return (error); } static int pf_disable_altq(struct pf_altq *altq) { struct ifnet *ifp; struct tb_profile tb; int error; if ((ifp = ifunit(altq->ifname)) == NULL) return (EINVAL); /* * when the discipline is no longer referenced, it was overridden * by a new one. if so, just return. */ if (altq->altq_disc != ifp->if_snd.altq_disc) return (0); error = altq_disable(&ifp->if_snd); if (error == 0) { /* clear tokenbucket regulator */ tb.rate = 0; error = tbr_set(&ifp->if_snd, &tb); } return (error); } static int pf_altq_ifnet_event_add(struct ifnet *ifp, int remove, u_int32_t ticket, struct pf_altq *altq) { struct ifnet *ifp1; int error = 0; /* Deactivate the interface in question */ altq->local_flags &= ~PFALTQ_FLAG_IF_REMOVED; if ((ifp1 = ifunit(altq->ifname)) == NULL || (remove && ifp1 == ifp)) { altq->local_flags |= PFALTQ_FLAG_IF_REMOVED; } else { error = altq_add(ifp1, altq); if (ticket != V_ticket_altqs_inactive) error = EBUSY; if (error) free(altq, M_PFALTQ); } return (error); } void pf_altq_ifnet_event(struct ifnet *ifp, int remove) { struct pf_altq *a1, *a2, *a3; u_int32_t ticket; int error = 0; /* * No need to re-evaluate the configuration for events on interfaces * that do not support ALTQ, as it's not possible for such * interfaces to be part of the configuration. */ if (!ALTQ_IS_READY(&ifp->if_snd)) return; /* Interrupt userland queue modifications */ if (V_altqs_inactive_open) pf_rollback_altq(V_ticket_altqs_inactive); /* Start new altq ruleset */ if (pf_begin_altq(&ticket)) return; /* Copy the current active set */ TAILQ_FOREACH(a1, V_pf_altq_ifs_active, entries) { a2 = malloc(sizeof(*a2), M_PFALTQ, M_NOWAIT); if (a2 == NULL) { error = ENOMEM; break; } bcopy(a1, a2, sizeof(struct pf_altq)); error = pf_altq_ifnet_event_add(ifp, remove, ticket, a2); if (error) break; TAILQ_INSERT_TAIL(V_pf_altq_ifs_inactive, a2, entries); } if (error) goto out; TAILQ_FOREACH(a1, V_pf_altqs_active, entries) { a2 = malloc(sizeof(*a2), M_PFALTQ, M_NOWAIT); if (a2 == NULL) { error = ENOMEM; break; } bcopy(a1, a2, sizeof(struct pf_altq)); if ((a2->qid = pf_qname2qid(a2->qname, true)) == 0) { error = EBUSY; free(a2, M_PFALTQ); break; } a2->altq_disc = NULL; TAILQ_FOREACH(a3, V_pf_altq_ifs_inactive, entries) { if (strncmp(a3->ifname, a2->ifname, IFNAMSIZ) == 0) { a2->altq_disc = a3->altq_disc; break; } } error = pf_altq_ifnet_event_add(ifp, remove, ticket, a2); if (error) break; TAILQ_INSERT_TAIL(V_pf_altqs_inactive, a2, entries); } out: if (error != 0) pf_rollback_altq(ticket); else pf_commit_altq(ticket); } #endif /* ALTQ */ static struct pf_krule_global * pf_rule_tree_alloc(int flags) { struct pf_krule_global *tree; tree = malloc(sizeof(struct pf_krule_global), M_PF, flags); if (tree == NULL) return (NULL); RB_INIT(tree); return (tree); } void pf_rule_tree_free(struct pf_krule_global *tree) { free(tree, M_PF); } static int pf_begin_rules(u_int32_t *ticket, int rs_num, const char *anchor) { struct pf_krule_global *tree; struct pf_kruleset *rs; struct pf_krule *rule; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); tree = pf_rule_tree_alloc(M_NOWAIT); if (tree == NULL) return (ENOMEM); rs = pf_find_or_create_kruleset(anchor); if (rs == NULL) { pf_rule_tree_free(tree); return (EINVAL); } pf_rule_tree_free(rs->rules[rs_num].inactive.tree); rs->rules[rs_num].inactive.tree = tree; while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_unlink_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } *ticket = ++rs->rules[rs_num].inactive.ticket; rs->rules[rs_num].inactive.open = 1; return (0); } static int pf_rollback_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_kruleset *rs; struct pf_krule *rule; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_kruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || rs->rules[rs_num].inactive.ticket != ticket) return (0); while ((rule = TAILQ_FIRST(rs->rules[rs_num].inactive.ptr)) != NULL) { pf_unlink_rule(rs->rules[rs_num].inactive.ptr, rule); rs->rules[rs_num].inactive.rcount--; } rs->rules[rs_num].inactive.open = 0; return (0); } #define PF_MD5_UPD(st, elm) \ MD5Update(ctx, (u_int8_t *) &(st)->elm, sizeof((st)->elm)) #define PF_MD5_UPD_STR(st, elm) \ MD5Update(ctx, (u_int8_t *) (st)->elm, strlen((st)->elm)) #define PF_MD5_UPD_HTONL(st, elm, stor) do { \ (stor) = htonl((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int32_t));\ } while (0) #define PF_MD5_UPD_HTONS(st, elm, stor) do { \ (stor) = htons((st)->elm); \ MD5Update(ctx, (u_int8_t *) &(stor), sizeof(u_int16_t));\ } while (0) static void pf_hash_rule_addr(MD5_CTX *ctx, struct pf_rule_addr *pfr) { PF_MD5_UPD(pfr, addr.type); switch (pfr->addr.type) { case PF_ADDR_DYNIFTL: PF_MD5_UPD(pfr, addr.v.ifname); PF_MD5_UPD(pfr, addr.iflags); break; case PF_ADDR_TABLE: if (strncmp(pfr->addr.v.tblname, PF_OPTIMIZER_TABLE_PFX, strlen(PF_OPTIMIZER_TABLE_PFX))) PF_MD5_UPD(pfr, addr.v.tblname); break; case PF_ADDR_ADDRMASK: /* XXX ignore af? */ PF_MD5_UPD(pfr, addr.v.a.addr.addr32); PF_MD5_UPD(pfr, addr.v.a.mask.addr32); break; } PF_MD5_UPD(pfr, port[0]); PF_MD5_UPD(pfr, port[1]); PF_MD5_UPD(pfr, neg); PF_MD5_UPD(pfr, port_op); } static void pf_hash_rule_rolling(MD5_CTX *ctx, struct pf_krule *rule) { u_int16_t x; u_int32_t y; pf_hash_rule_addr(ctx, &rule->src); pf_hash_rule_addr(ctx, &rule->dst); for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) PF_MD5_UPD_STR(rule, label[i]); PF_MD5_UPD_STR(rule, ifname); PF_MD5_UPD_STR(rule, rcv_ifname); PF_MD5_UPD_STR(rule, match_tagname); PF_MD5_UPD_HTONS(rule, match_tag, x); /* dup? */ PF_MD5_UPD_HTONL(rule, os_fingerprint, y); PF_MD5_UPD_HTONL(rule, prob, y); PF_MD5_UPD_HTONL(rule, uid.uid[0], y); PF_MD5_UPD_HTONL(rule, uid.uid[1], y); PF_MD5_UPD(rule, uid.op); PF_MD5_UPD_HTONL(rule, gid.gid[0], y); PF_MD5_UPD_HTONL(rule, gid.gid[1], y); PF_MD5_UPD(rule, gid.op); PF_MD5_UPD_HTONL(rule, rule_flag, y); PF_MD5_UPD(rule, action); PF_MD5_UPD(rule, direction); PF_MD5_UPD(rule, af); PF_MD5_UPD(rule, quick); PF_MD5_UPD(rule, ifnot); PF_MD5_UPD(rule, rcvifnot); PF_MD5_UPD(rule, match_tag_not); PF_MD5_UPD(rule, natpass); PF_MD5_UPD(rule, keep_state); PF_MD5_UPD(rule, proto); PF_MD5_UPD(rule, type); PF_MD5_UPD(rule, code); PF_MD5_UPD(rule, flags); PF_MD5_UPD(rule, flagset); PF_MD5_UPD(rule, allow_opts); PF_MD5_UPD(rule, rt); PF_MD5_UPD(rule, tos); PF_MD5_UPD(rule, scrub_flags); PF_MD5_UPD(rule, min_ttl); PF_MD5_UPD(rule, set_tos); if (rule->anchor != NULL) PF_MD5_UPD_STR(rule, anchor->path); } static void pf_hash_rule(struct pf_krule *rule) { MD5_CTX ctx; MD5Init(&ctx); pf_hash_rule_rolling(&ctx, rule); MD5Final(rule->md5sum, &ctx); } static int pf_krule_compare(struct pf_krule *a, struct pf_krule *b) { return (memcmp(a->md5sum, b->md5sum, PF_MD5_DIGEST_LENGTH)); } static int pf_commit_rules(u_int32_t ticket, int rs_num, char *anchor) { struct pf_kruleset *rs; struct pf_krule *rule, *old_rule; struct pf_krulequeue *old_rules; struct pf_krule_global *old_tree; int error; u_int32_t old_rcount; PF_RULES_WASSERT(); if (rs_num < 0 || rs_num >= PF_RULESET_MAX) return (EINVAL); rs = pf_find_kruleset(anchor); if (rs == NULL || !rs->rules[rs_num].inactive.open || ticket != rs->rules[rs_num].inactive.ticket) return (EBUSY); /* Calculate checksum for the main ruleset */ if (rs == &pf_main_ruleset) { error = pf_setup_pfsync_matching(rs); if (error != 0) return (error); } /* Swap rules, keep the old. */ old_rules = rs->rules[rs_num].active.ptr; old_rcount = rs->rules[rs_num].active.rcount; old_tree = rs->rules[rs_num].active.tree; rs->rules[rs_num].active.ptr = rs->rules[rs_num].inactive.ptr; rs->rules[rs_num].active.tree = rs->rules[rs_num].inactive.tree; rs->rules[rs_num].active.rcount = rs->rules[rs_num].inactive.rcount; /* Attempt to preserve counter information. */ if (V_pf_status.keep_counters && old_tree != NULL) { TAILQ_FOREACH(rule, rs->rules[rs_num].active.ptr, entries) { old_rule = RB_FIND(pf_krule_global, old_tree, rule); if (old_rule == NULL) { continue; } pf_counter_u64_critical_enter(); pf_counter_u64_rollup_protected(&rule->evaluations, pf_counter_u64_fetch(&old_rule->evaluations)); pf_counter_u64_rollup_protected(&rule->packets[0], pf_counter_u64_fetch(&old_rule->packets[0])); pf_counter_u64_rollup_protected(&rule->packets[1], pf_counter_u64_fetch(&old_rule->packets[1])); pf_counter_u64_rollup_protected(&rule->bytes[0], pf_counter_u64_fetch(&old_rule->bytes[0])); pf_counter_u64_rollup_protected(&rule->bytes[1], pf_counter_u64_fetch(&old_rule->bytes[1])); pf_counter_u64_critical_exit(); } } rs->rules[rs_num].inactive.ptr = old_rules; rs->rules[rs_num].inactive.tree = NULL; /* important for pf_ioctl_addrule */ rs->rules[rs_num].inactive.rcount = old_rcount; rs->rules[rs_num].active.ticket = rs->rules[rs_num].inactive.ticket; pf_calc_skip_steps(rs->rules[rs_num].active.ptr); /* Purge the old rule list. */ PF_UNLNKDRULES_LOCK(); while ((rule = TAILQ_FIRST(old_rules)) != NULL) pf_unlink_rule_locked(old_rules, rule); PF_UNLNKDRULES_UNLOCK(); rs->rules[rs_num].inactive.rcount = 0; rs->rules[rs_num].inactive.open = 0; pf_remove_if_empty_kruleset(rs); pf_rule_tree_free(old_tree); return (0); } static int pf_setup_pfsync_matching(struct pf_kruleset *rs) { MD5_CTX ctx; struct pf_krule *rule; int rs_cnt; u_int8_t digest[PF_MD5_DIGEST_LENGTH]; MD5Init(&ctx); for (rs_cnt = 0; rs_cnt < PF_RULESET_MAX; rs_cnt++) { /* XXX PF_RULESET_SCRUB as well? */ if (rs_cnt == PF_RULESET_SCRUB) continue; if (rs->rules[rs_cnt].inactive.rcount) { TAILQ_FOREACH(rule, rs->rules[rs_cnt].inactive.ptr, entries) { pf_hash_rule_rolling(&ctx, rule); } } } MD5Final(digest, &ctx); memcpy(V_pf_status.pf_chksum, digest, sizeof(V_pf_status.pf_chksum)); return (0); } static int pf_eth_addr_setup(struct pf_keth_ruleset *ruleset, struct pf_addr_wrap *addr) { int error = 0; switch (addr->type) { case PF_ADDR_TABLE: addr->p.tbl = pfr_eth_attach_table(ruleset, addr->v.tblname); if (addr->p.tbl == NULL) error = ENOMEM; break; default: error = EINVAL; } return (error); } static int pf_addr_setup(struct pf_kruleset *ruleset, struct pf_addr_wrap *addr, sa_family_t af) { int error = 0; switch (addr->type) { case PF_ADDR_TABLE: addr->p.tbl = pfr_attach_table(ruleset, addr->v.tblname); if (addr->p.tbl == NULL) error = ENOMEM; break; case PF_ADDR_DYNIFTL: error = pfi_dynaddr_setup(addr, af); break; } return (error); } void pf_addr_copyout(struct pf_addr_wrap *addr) { switch (addr->type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_copyout(addr); break; case PF_ADDR_TABLE: pf_tbladdr_copyout(addr); break; } } static void pf_src_node_copy(const struct pf_ksrc_node *in, struct pf_src_node *out) { int secs = time_uptime; bzero(out, sizeof(struct pf_src_node)); bcopy(&in->addr, &out->addr, sizeof(struct pf_addr)); bcopy(&in->raddr, &out->raddr, sizeof(struct pf_addr)); if (in->rule != NULL) out->rule.nr = in->rule->nr; for (int i = 0; i < 2; i++) { out->bytes[i] = counter_u64_fetch(in->bytes[i]); out->packets[i] = counter_u64_fetch(in->packets[i]); } out->states = in->states; out->conn = in->conn; out->af = in->af; out->ruletype = in->ruletype; out->creation = secs - in->creation; if (out->expire > secs) out->expire -= secs; else out->expire = 0; /* Adjust the connection rate estimate. */ out->conn_rate.limit = in->conn_rate.limit; out->conn_rate.seconds = in->conn_rate.seconds; /* If there's no limit there's no counter_rate. */ if (in->conn_rate.cr != NULL) out->conn_rate.count = counter_rate_get(in->conn_rate.cr); } #ifdef ALTQ /* * Handle export of struct pf_kaltq to user binaries that may be using any * version of struct pf_altq. */ static int pf_export_kaltq(struct pf_altq *q, struct pfioc_altq_v1 *pa, size_t ioc_size) { u_int32_t version; if (ioc_size == sizeof(struct pfioc_altq_v0)) version = 0; else version = pa->version; if (version > PFIOC_ALTQ_VERSION) return (EINVAL); #define ASSIGN(x) exported_q->x = q->x #define COPY(x) \ bcopy(&q->x, &exported_q->x, min(sizeof(q->x), sizeof(exported_q->x))) #define SATU16(x) (u_int32_t)uqmin((x), USHRT_MAX) #define SATU32(x) (u_int32_t)uqmin((x), UINT_MAX) switch (version) { case 0: { struct pf_altq_v0 *exported_q = &((struct pfioc_altq_v0 *)pa)->altq; COPY(ifname); ASSIGN(scheduler); ASSIGN(tbrsize); exported_q->tbrsize = SATU16(q->tbrsize); exported_q->ifbandwidth = SATU32(q->ifbandwidth); COPY(qname); COPY(parent); ASSIGN(parent_qid); exported_q->bandwidth = SATU32(q->bandwidth); ASSIGN(priority); ASSIGN(local_flags); ASSIGN(qlimit); ASSIGN(flags); if (q->scheduler == ALTQT_HFSC) { #define ASSIGN_OPT(x) exported_q->pq_u.hfsc_opts.x = q->pq_u.hfsc_opts.x #define ASSIGN_OPT_SATU32(x) exported_q->pq_u.hfsc_opts.x = \ SATU32(q->pq_u.hfsc_opts.x) ASSIGN_OPT_SATU32(rtsc_m1); ASSIGN_OPT(rtsc_d); ASSIGN_OPT_SATU32(rtsc_m2); ASSIGN_OPT_SATU32(lssc_m1); ASSIGN_OPT(lssc_d); ASSIGN_OPT_SATU32(lssc_m2); ASSIGN_OPT_SATU32(ulsc_m1); ASSIGN_OPT(ulsc_d); ASSIGN_OPT_SATU32(ulsc_m2); ASSIGN_OPT(flags); #undef ASSIGN_OPT #undef ASSIGN_OPT_SATU32 } else COPY(pq_u); ASSIGN(qid); break; } case 1: { struct pf_altq_v1 *exported_q = &((struct pfioc_altq_v1 *)pa)->altq; COPY(ifname); ASSIGN(scheduler); ASSIGN(tbrsize); ASSIGN(ifbandwidth); COPY(qname); COPY(parent); ASSIGN(parent_qid); ASSIGN(bandwidth); ASSIGN(priority); ASSIGN(local_flags); ASSIGN(qlimit); ASSIGN(flags); COPY(pq_u); ASSIGN(qid); break; } default: panic("%s: unhandled struct pfioc_altq version", __func__); break; } #undef ASSIGN #undef COPY #undef SATU16 #undef SATU32 return (0); } /* * Handle import to struct pf_kaltq of struct pf_altq from user binaries * that may be using any version of it. */ static int pf_import_kaltq(struct pfioc_altq_v1 *pa, struct pf_altq *q, size_t ioc_size) { u_int32_t version; if (ioc_size == sizeof(struct pfioc_altq_v0)) version = 0; else version = pa->version; if (version > PFIOC_ALTQ_VERSION) return (EINVAL); #define ASSIGN(x) q->x = imported_q->x #define COPY(x) \ bcopy(&imported_q->x, &q->x, min(sizeof(imported_q->x), sizeof(q->x))) switch (version) { case 0: { struct pf_altq_v0 *imported_q = &((struct pfioc_altq_v0 *)pa)->altq; COPY(ifname); ASSIGN(scheduler); ASSIGN(tbrsize); /* 16-bit -> 32-bit */ ASSIGN(ifbandwidth); /* 32-bit -> 64-bit */ COPY(qname); COPY(parent); ASSIGN(parent_qid); ASSIGN(bandwidth); /* 32-bit -> 64-bit */ ASSIGN(priority); ASSIGN(local_flags); ASSIGN(qlimit); ASSIGN(flags); if (imported_q->scheduler == ALTQT_HFSC) { #define ASSIGN_OPT(x) q->pq_u.hfsc_opts.x = imported_q->pq_u.hfsc_opts.x /* * The m1 and m2 parameters are being copied from * 32-bit to 64-bit. */ ASSIGN_OPT(rtsc_m1); ASSIGN_OPT(rtsc_d); ASSIGN_OPT(rtsc_m2); ASSIGN_OPT(lssc_m1); ASSIGN_OPT(lssc_d); ASSIGN_OPT(lssc_m2); ASSIGN_OPT(ulsc_m1); ASSIGN_OPT(ulsc_d); ASSIGN_OPT(ulsc_m2); ASSIGN_OPT(flags); #undef ASSIGN_OPT } else COPY(pq_u); ASSIGN(qid); break; } case 1: { struct pf_altq_v1 *imported_q = &((struct pfioc_altq_v1 *)pa)->altq; COPY(ifname); ASSIGN(scheduler); ASSIGN(tbrsize); ASSIGN(ifbandwidth); COPY(qname); COPY(parent); ASSIGN(parent_qid); ASSIGN(bandwidth); ASSIGN(priority); ASSIGN(local_flags); ASSIGN(qlimit); ASSIGN(flags); COPY(pq_u); ASSIGN(qid); break; } default: panic("%s: unhandled struct pfioc_altq version", __func__); break; } #undef ASSIGN #undef COPY return (0); } static struct pf_altq * pf_altq_get_nth_active(u_int32_t n) { struct pf_altq *altq; u_int32_t nr; nr = 0; TAILQ_FOREACH(altq, V_pf_altq_ifs_active, entries) { if (nr == n) return (altq); nr++; } TAILQ_FOREACH(altq, V_pf_altqs_active, entries) { if (nr == n) return (altq); nr++; } return (NULL); } #endif /* ALTQ */ struct pf_krule * pf_krule_alloc(void) { struct pf_krule *rule; rule = malloc(sizeof(struct pf_krule), M_PFRULE, M_WAITOK | M_ZERO); mtx_init(&rule->nat.mtx, "pf_krule_nat_pool", NULL, MTX_DEF); mtx_init(&rule->rdr.mtx, "pf_krule_rdr_pool", NULL, MTX_DEF); mtx_init(&rule->route.mtx, "pf_krule_route_pool", NULL, MTX_DEF); rule->timestamp = uma_zalloc_pcpu(pf_timestamp_pcpu_zone, M_WAITOK | M_ZERO); return (rule); } void pf_krule_free(struct pf_krule *rule) { #ifdef PF_WANT_32_TO_64_COUNTER bool wowned; #endif if (rule == NULL) return; #ifdef PF_WANT_32_TO_64_COUNTER if (rule->allrulelinked) { wowned = PF_RULES_WOWNED(); if (!wowned) PF_RULES_WLOCK(); LIST_REMOVE(rule, allrulelist); V_pf_allrulecount--; if (!wowned) PF_RULES_WUNLOCK(); } #endif pf_counter_u64_deinit(&rule->evaluations); for (int i = 0; i < 2; i++) { pf_counter_u64_deinit(&rule->packets[i]); pf_counter_u64_deinit(&rule->bytes[i]); } counter_u64_free(rule->states_cur); counter_u64_free(rule->states_tot); for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type]); uma_zfree_pcpu(pf_timestamp_pcpu_zone, rule->timestamp); mtx_destroy(&rule->nat.mtx); mtx_destroy(&rule->rdr.mtx); mtx_destroy(&rule->route.mtx); free(rule, M_PFRULE); } void pf_krule_clear_counters(struct pf_krule *rule) { pf_counter_u64_zero(&rule->evaluations); for (int i = 0; i < 2; i++) { pf_counter_u64_zero(&rule->packets[i]); pf_counter_u64_zero(&rule->bytes[i]); } counter_u64_zero(rule->states_tot); } static void pf_kpooladdr_to_pooladdr(const struct pf_kpooladdr *kpool, struct pf_pooladdr *pool) { bzero(pool, sizeof(*pool)); bcopy(&kpool->addr, &pool->addr, sizeof(pool->addr)); strlcpy(pool->ifname, kpool->ifname, sizeof(pool->ifname)); } static int pf_pooladdr_to_kpooladdr(const struct pf_pooladdr *pool, struct pf_kpooladdr *kpool) { int ret; bzero(kpool, sizeof(*kpool)); bcopy(&pool->addr, &kpool->addr, sizeof(kpool->addr)); ret = pf_user_strcpy(kpool->ifname, pool->ifname, sizeof(kpool->ifname)); return (ret); } static void pf_pool_to_kpool(const struct pf_pool *pool, struct pf_kpool *kpool) { _Static_assert(sizeof(pool->key) == sizeof(kpool->key), ""); _Static_assert(sizeof(pool->counter) == sizeof(kpool->counter), ""); bcopy(&pool->key, &kpool->key, sizeof(kpool->key)); bcopy(&pool->counter, &kpool->counter, sizeof(kpool->counter)); kpool->tblidx = pool->tblidx; kpool->proxy_port[0] = pool->proxy_port[0]; kpool->proxy_port[1] = pool->proxy_port[1]; kpool->opts = pool->opts; } static int pf_rule_to_krule(const struct pf_rule *rule, struct pf_krule *krule) { int ret; #ifndef INET if (rule->af == AF_INET) { return (EAFNOSUPPORT); } #endif /* INET */ #ifndef INET6 if (rule->af == AF_INET6) { return (EAFNOSUPPORT); } #endif /* INET6 */ ret = pf_check_rule_addr(&rule->src); if (ret != 0) return (ret); ret = pf_check_rule_addr(&rule->dst); if (ret != 0) return (ret); bcopy(&rule->src, &krule->src, sizeof(rule->src)); bcopy(&rule->dst, &krule->dst, sizeof(rule->dst)); ret = pf_user_strcpy(krule->label[0], rule->label, sizeof(rule->label)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->ifname, rule->ifname, sizeof(rule->ifname)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->qname, rule->qname, sizeof(rule->qname)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->pqname, rule->pqname, sizeof(rule->pqname)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->tagname, rule->tagname, sizeof(rule->tagname)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->match_tagname, rule->match_tagname, sizeof(rule->match_tagname)); if (ret != 0) return (ret); ret = pf_user_strcpy(krule->overload_tblname, rule->overload_tblname, sizeof(rule->overload_tblname)); if (ret != 0) return (ret); pf_pool_to_kpool(&rule->rpool, &krule->rdr); /* Don't allow userspace to set evaluations, packets or bytes. */ /* kif, anchor, overload_tbl are not copied over. */ krule->os_fingerprint = rule->os_fingerprint; krule->rtableid = rule->rtableid; /* pf_rule->timeout is smaller than pf_krule->timeout */ bcopy(rule->timeout, krule->timeout, sizeof(rule->timeout)); krule->max_states = rule->max_states; krule->max_src_nodes = rule->max_src_nodes; krule->max_src_states = rule->max_src_states; krule->max_src_conn = rule->max_src_conn; krule->max_src_conn_rate.limit = rule->max_src_conn_rate.limit; krule->max_src_conn_rate.seconds = rule->max_src_conn_rate.seconds; krule->qid = rule->qid; krule->pqid = rule->pqid; krule->nr = rule->nr; krule->prob = rule->prob; krule->cuid = rule->cuid; krule->cpid = rule->cpid; krule->return_icmp = rule->return_icmp; krule->return_icmp6 = rule->return_icmp6; krule->max_mss = rule->max_mss; krule->tag = rule->tag; krule->match_tag = rule->match_tag; krule->scrub_flags = rule->scrub_flags; bcopy(&rule->uid, &krule->uid, sizeof(krule->uid)); bcopy(&rule->gid, &krule->gid, sizeof(krule->gid)); krule->rule_flag = rule->rule_flag; krule->action = rule->action; krule->direction = rule->direction; krule->log = rule->log; krule->logif = rule->logif; krule->quick = rule->quick; krule->ifnot = rule->ifnot; krule->match_tag_not = rule->match_tag_not; krule->natpass = rule->natpass; krule->keep_state = rule->keep_state; krule->af = rule->af; krule->proto = rule->proto; krule->type = rule->type; krule->code = rule->code; krule->flags = rule->flags; krule->flagset = rule->flagset; krule->min_ttl = rule->min_ttl; krule->allow_opts = rule->allow_opts; krule->rt = rule->rt; krule->return_ttl = rule->return_ttl; krule->tos = rule->tos; krule->set_tos = rule->set_tos; krule->flush = rule->flush; krule->prio = rule->prio; krule->set_prio[0] = rule->set_prio[0]; krule->set_prio[1] = rule->set_prio[1]; bcopy(&rule->divert, &krule->divert, sizeof(krule->divert)); return (0); } int pf_ioctl_getrules(struct pfioc_rule *pr) { struct pf_kruleset *ruleset; struct pf_krule *tail; int rs_num; PF_RULES_WLOCK(); ruleset = pf_find_kruleset(pr->anchor); if (ruleset == NULL) { PF_RULES_WUNLOCK(); return (EINVAL); } rs_num = pf_get_ruleset_number(pr->rule.action); if (rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); return (EINVAL); } tail = TAILQ_LAST(ruleset->rules[rs_num].active.ptr, pf_krulequeue); if (tail) pr->nr = tail->nr + 1; else pr->nr = 0; pr->ticket = ruleset->rules[rs_num].active.ticket; PF_RULES_WUNLOCK(); return (0); } static int pf_rule_checkaf(struct pf_krule *r) { switch (r->af) { case 0: if (r->rule_flag & PFRULE_AFTO) return (EPFNOSUPPORT); break; case AF_INET: if ((r->rule_flag & PFRULE_AFTO) && r->naf != AF_INET6) return (EPFNOSUPPORT); break; #ifdef INET6 case AF_INET6: if ((r->rule_flag & PFRULE_AFTO) && r->naf != AF_INET) return (EPFNOSUPPORT); break; #endif /* INET6 */ default: return (EPFNOSUPPORT); } if ((r->rule_flag & PFRULE_AFTO) == 0 && r->naf != 0) return (EPFNOSUPPORT); return (0); } static int pf_validate_range(uint8_t op, uint16_t port[2]) { uint16_t a = ntohs(port[0]); uint16_t b = ntohs(port[1]); if ((op == PF_OP_RRG && a > b) || /* 34:12, i.e. none */ (op == PF_OP_IRG && a >= b) || /* 34><12, i.e. none */ (op == PF_OP_XRG && a > b)) /* 34<>22, i.e. all */ return 1; return 0; } int pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket, uint32_t pool_ticket, const char *anchor, const char *anchor_call, uid_t uid, pid_t pid) { struct pf_kruleset *ruleset; struct pf_krule *tail; struct pf_kpooladdr *pa; struct pfi_kkif *kif = NULL, *rcv_kif = NULL; int rs_num; int error = 0; #define ERROUT(x) ERROUT_FUNCTION(errout, x) #define ERROUT_UNLOCKED(x) ERROUT_FUNCTION(errout_unlocked, x) if ((rule->return_icmp >> 8) > ICMP_MAXTYPE) ERROUT_UNLOCKED(EINVAL); if ((error = pf_rule_checkaf(rule))) ERROUT_UNLOCKED(error); if (pf_validate_range(rule->src.port_op, rule->src.port)) ERROUT_UNLOCKED(EINVAL); if (pf_validate_range(rule->dst.port_op, rule->dst.port)) ERROUT_UNLOCKED(EINVAL); if (rule->ifname[0]) kif = pf_kkif_create(M_WAITOK); if (rule->rcv_ifname[0]) rcv_kif = pf_kkif_create(M_WAITOK); pf_counter_u64_init(&rule->evaluations, M_WAITOK); for (int i = 0; i < 2; i++) { pf_counter_u64_init(&rule->packets[i], M_WAITOK); pf_counter_u64_init(&rule->bytes[i], M_WAITOK); } rule->states_cur = counter_u64_alloc(M_WAITOK); rule->states_tot = counter_u64_alloc(M_WAITOK); for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type] = counter_u64_alloc(M_WAITOK); rule->cuid = uid; rule->cpid = pid; TAILQ_INIT(&rule->rdr.list); TAILQ_INIT(&rule->nat.list); TAILQ_INIT(&rule->route.list); PF_CONFIG_LOCK(); PF_RULES_WLOCK(); #ifdef PF_WANT_32_TO_64_COUNTER LIST_INSERT_HEAD(&V_pf_allrulelist, rule, allrulelist); MPASS(!rule->allrulelinked); rule->allrulelinked = true; V_pf_allrulecount++; #endif ruleset = pf_find_kruleset(anchor); if (ruleset == NULL) ERROUT(EINVAL); rs_num = pf_get_ruleset_number(rule->action); if (rs_num >= PF_RULESET_MAX) ERROUT(EINVAL); if (ticket != ruleset->rules[rs_num].inactive.ticket) { DPFPRINTF(PF_DEBUG_MISC, "ticket: %d != [%d]%d", ticket, rs_num, ruleset->rules[rs_num].inactive.ticket); ERROUT(EBUSY); } if (pool_ticket != V_ticket_pabuf) { DPFPRINTF(PF_DEBUG_MISC, "pool_ticket: %d != %d", pool_ticket, V_ticket_pabuf); ERROUT(EBUSY); } /* * XXXMJG hack: there is no mechanism to ensure they started the * transaction. Ticket checked above may happen to match by accident, * even if nobody called DIOCXBEGIN, let alone this process. * Partially work around it by checking if the RB tree got allocated, * see pf_begin_rules. */ if (ruleset->rules[rs_num].inactive.tree == NULL) { ERROUT(EINVAL); } tail = TAILQ_LAST(ruleset->rules[rs_num].inactive.ptr, pf_krulequeue); if (tail) rule->nr = tail->nr + 1; else rule->nr = 0; if (rule->ifname[0]) { rule->kif = pfi_kkif_attach(kif, rule->ifname); kif = NULL; pfi_kkif_ref(rule->kif); } else rule->kif = NULL; if (rule->rcv_ifname[0]) { rule->rcv_kif = pfi_kkif_attach(rcv_kif, rule->rcv_ifname); rcv_kif = NULL; pfi_kkif_ref(rule->rcv_kif); } else rule->rcv_kif = NULL; if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs) ERROUT(EBUSY); #ifdef ALTQ /* set queue IDs */ if (rule->qname[0] != 0) { if ((rule->qid = pf_qname2qid(rule->qname, true)) == 0) ERROUT(EBUSY); else if (rule->pqname[0] != 0) { if ((rule->pqid = pf_qname2qid(rule->pqname, true)) == 0) ERROUT(EBUSY); } else rule->pqid = rule->qid; } #endif if (rule->tagname[0]) if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0) ERROUT(EBUSY); if (rule->match_tagname[0]) if ((rule->match_tag = pf_tagname2tag(rule->match_tagname)) == 0) ERROUT(EBUSY); if (rule->rt && !rule->direction) ERROUT(EINVAL); if (!rule->log) rule->logif = 0; if (! pf_init_threshold(&rule->pktrate, rule->pktrate.limit, rule->pktrate.seconds)) ERROUT(ENOMEM); if (pf_addr_setup(ruleset, &rule->src.addr, rule->af)) ERROUT(ENOMEM); if (pf_addr_setup(ruleset, &rule->dst.addr, rule->af)) ERROUT(ENOMEM); if (pf_kanchor_setup(rule, ruleset, anchor_call)) ERROUT(EINVAL); if (rule->scrub_flags & PFSTATE_SETPRIO && (rule->set_prio[0] > PF_PRIO_MAX || rule->set_prio[1] > PF_PRIO_MAX)) ERROUT(EINVAL); for (int i = 0; i < 3; i++) { TAILQ_FOREACH(pa, &V_pf_pabuf[i], entries) if (pa->addr.type == PF_ADDR_TABLE) { pa->addr.p.tbl = pfr_attach_table(ruleset, pa->addr.v.tblname); if (pa->addr.p.tbl == NULL) ERROUT(ENOMEM); } } rule->overload_tbl = NULL; if (rule->overload_tblname[0]) { if ((rule->overload_tbl = pfr_attach_table(ruleset, rule->overload_tblname)) == NULL) ERROUT(EINVAL); else rule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } pf_mv_kpool(&V_pf_pabuf[0], &rule->nat.list); /* * Old version of pfctl provide route redirection pools in single * common redirection pool rdr. New versions use rdr only for * rdr-to rules. */ if (rule->rt > PF_NOPFROUTE && TAILQ_EMPTY(&V_pf_pabuf[2])) { pf_mv_kpool(&V_pf_pabuf[1], &rule->route.list); } else { pf_mv_kpool(&V_pf_pabuf[1], &rule->rdr.list); pf_mv_kpool(&V_pf_pabuf[2], &rule->route.list); } if (((rule->action == PF_NAT) || (rule->action == PF_RDR) || (rule->action == PF_BINAT)) && rule->anchor == NULL && TAILQ_FIRST(&rule->rdr.list) == NULL) { ERROUT(EINVAL); } if (rule->rt > PF_NOPFROUTE && (TAILQ_FIRST(&rule->route.list) == NULL)) { ERROUT(EINVAL); } if (rule->action == PF_PASS && (rule->rdr.opts & PF_POOL_STICKYADDR || rule->nat.opts & PF_POOL_STICKYADDR) && !rule->keep_state) { ERROUT(EINVAL); } MPASS(error == 0); rule->nat.cur = TAILQ_FIRST(&rule->nat.list); rule->rdr.cur = TAILQ_FIRST(&rule->rdr.list); rule->route.cur = TAILQ_FIRST(&rule->route.list); rule->route.ipv6_nexthop_af = AF_INET6; TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount++; PF_RULES_WUNLOCK(); pf_hash_rule(rule); if (RB_INSERT(pf_krule_global, ruleset->rules[rs_num].inactive.tree, rule) != NULL) { PF_RULES_WLOCK(); TAILQ_REMOVE(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount--; pf_free_rule(rule); rule = NULL; ERROUT(EEXIST); } PF_CONFIG_UNLOCK(); return (0); #undef ERROUT #undef ERROUT_UNLOCKED errout: PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); errout_unlocked: pf_kkif_free(rcv_kif); pf_kkif_free(kif); pf_krule_free(rule); return (error); } static bool pf_label_match(const struct pf_krule *rule, const char *label) { int i = 0; while (*rule->label[i]) { if (strcmp(rule->label[i], label) == 0) return (true); i++; } return (false); } static unsigned int pf_kill_matching_state(struct pf_state_key_cmp *key, int dir) { struct pf_kstate *s; int more = 0; s = pf_find_state_all(key, dir, &more); if (s == NULL) return (0); if (more) { PF_STATE_UNLOCK(s); return (0); } pf_remove_state(s); return (1); } static int pf_killstates_row(struct pf_kstate_kill *psk, struct pf_idhash *ih) { struct pf_kstate *s; struct pf_state_key *sk; struct pf_addr *srcaddr, *dstaddr; struct pf_state_key_cmp match_key; int idx, killed = 0; unsigned int dir; u_int16_t srcport, dstport; struct pfi_kkif *kif; relock_DIOCKILLSTATES: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { /* For floating states look at the original kif. */ kif = s->kif == V_pfi_all ? s->orig_kif : s->kif; sk = s->key[psk->psk_nat ? PF_SK_STACK : PF_SK_WIRE]; if (s->direction == PF_OUT) { srcaddr = &sk->addr[1]; dstaddr = &sk->addr[0]; srcport = sk->port[1]; dstport = sk->port[0]; } else { srcaddr = &sk->addr[0]; dstaddr = &sk->addr[1]; srcport = sk->port[0]; dstport = sk->port[1]; } if (psk->psk_af && sk->af != psk->psk_af) continue; if (psk->psk_proto && psk->psk_proto != sk->proto) continue; if (! pf_match_addr(psk->psk_src.neg, &psk->psk_src.addr.v.a.addr, &psk->psk_src.addr.v.a.mask, srcaddr, sk->af)) continue; if (! pf_match_addr(psk->psk_dst.neg, &psk->psk_dst.addr.v.a.addr, &psk->psk_dst.addr.v.a.mask, dstaddr, sk->af)) continue; if (! pf_match_addr(psk->psk_rt_addr.neg, &psk->psk_rt_addr.addr.v.a.addr, &psk->psk_rt_addr.addr.v.a.mask, &s->act.rt_addr, sk->af)) continue; if (psk->psk_src.port_op != 0 && ! pf_match_port(psk->psk_src.port_op, psk->psk_src.port[0], psk->psk_src.port[1], srcport)) continue; if (psk->psk_dst.port_op != 0 && ! pf_match_port(psk->psk_dst.port_op, psk->psk_dst.port[0], psk->psk_dst.port[1], dstport)) continue; if (psk->psk_label[0] && ! pf_label_match(s->rule, psk->psk_label)) continue; if (psk->psk_ifname[0] && strcmp(psk->psk_ifname, kif->pfik_name)) continue; if (psk->psk_kill_match) { /* Create the key to find matching states, with lock * held. */ bzero(&match_key, sizeof(match_key)); if (s->direction == PF_OUT) { dir = PF_IN; idx = psk->psk_nat ? PF_SK_WIRE : PF_SK_STACK; } else { dir = PF_OUT; idx = psk->psk_nat ? PF_SK_STACK : PF_SK_WIRE; } match_key.af = s->key[idx]->af; match_key.proto = s->key[idx]->proto; pf_addrcpy(&match_key.addr[0], &s->key[idx]->addr[1], match_key.af); match_key.port[0] = s->key[idx]->port[1]; pf_addrcpy(&match_key.addr[1], &s->key[idx]->addr[0], match_key.af); match_key.port[1] = s->key[idx]->port[0]; } pf_remove_state(s); killed++; if (psk->psk_kill_match) killed += pf_kill_matching_state(&match_key, dir); goto relock_DIOCKILLSTATES; } PF_HASHROW_UNLOCK(ih); return (killed); } void unhandled_af(int af) { panic("unhandled af %d", af); } int pf_start(void) { int error = 0; sx_xlock(&V_pf_ioctl_lock); if (V_pf_status.running) error = EEXIST; else { hook_pf(); if (! TAILQ_EMPTY(V_pf_keth->active.rules)) hook_pf_eth(); V_pf_status.running = 1; V_pf_status.since = time_uptime; new_unrhdr64(&V_pf_stateid, time_second); DPFPRINTF(PF_DEBUG_MISC, "pf: started"); } sx_xunlock(&V_pf_ioctl_lock); return (error); } int pf_stop(void) { int error = 0; sx_xlock(&V_pf_ioctl_lock); if (!V_pf_status.running) error = ENOENT; else { V_pf_status.running = 0; dehook_pf(); dehook_pf_eth(); V_pf_status.since = time_uptime; DPFPRINTF(PF_DEBUG_MISC, "pf: stopped"); } sx_xunlock(&V_pf_ioctl_lock); return (error); } void pf_ioctl_clear_status(void) { PF_RULES_WLOCK(); for (int i = 0; i < PFRES_MAX; i++) counter_u64_zero(V_pf_status.counters[i]); for (int i = 0; i < FCNT_MAX; i++) pf_counter_u64_zero(&V_pf_status.fcounters[i]); for (int i = 0; i < SCNT_MAX; i++) counter_u64_zero(V_pf_status.scounters[i]); for (int i = 0; i < NCNT_MAX; i++) counter_u64_zero(V_pf_status.ncounters[i]); for (int i = 0; i < KLCNT_MAX; i++) counter_u64_zero(V_pf_status.lcounters[i]); V_pf_status.since = time_uptime; if (*V_pf_status.ifname) pfi_update_status(V_pf_status.ifname, NULL); PF_RULES_WUNLOCK(); } int pf_ioctl_set_timeout(int timeout, int seconds, int *prev_seconds) { uint32_t old; if (timeout < 0 || timeout >= PFTM_MAX || seconds < 0) return (EINVAL); PF_RULES_WLOCK(); old = V_pf_default_rule.timeout[timeout]; if (timeout == PFTM_INTERVAL && seconds == 0) seconds = 1; V_pf_default_rule.timeout[timeout] = seconds; if (timeout == PFTM_INTERVAL && seconds < old) wakeup(pf_purge_thread); if (prev_seconds != NULL) *prev_seconds = old; PF_RULES_WUNLOCK(); return (0); } int pf_ioctl_get_timeout(int timeout, int *seconds) { PF_RULES_RLOCK_TRACKER; if (timeout < 0 || timeout >= PFTM_MAX) return (EINVAL); PF_RULES_RLOCK(); *seconds = V_pf_default_rule.timeout[timeout]; PF_RULES_RUNLOCK(); return (0); } int pf_ioctl_set_limit(int index, unsigned int limit, unsigned int *old_limit) { PF_RULES_WLOCK(); if (index < 0 || index >= PF_LIMIT_MAX || V_pf_limits[index].zone == NULL) { PF_RULES_WUNLOCK(); return (EINVAL); } uma_zone_set_max(V_pf_limits[index].zone, limit == 0 ? INT_MAX : limit); if (old_limit != NULL) *old_limit = V_pf_limits[index].limit; V_pf_limits[index].limit = limit; PF_RULES_WUNLOCK(); return (0); } int pf_ioctl_get_limit(int index, unsigned int *limit) { PF_RULES_RLOCK_TRACKER; if (index < 0 || index >= PF_LIMIT_MAX) return (EINVAL); PF_RULES_RLOCK(); *limit = V_pf_limits[index].limit; PF_RULES_RUNLOCK(); return (0); } int pf_ioctl_begin_addrs(uint32_t *ticket) { PF_RULES_WLOCK(); pf_empty_kpool(&V_pf_pabuf[0]); pf_empty_kpool(&V_pf_pabuf[1]); pf_empty_kpool(&V_pf_pabuf[2]); *ticket = ++V_ticket_pabuf; PF_RULES_WUNLOCK(); return (0); } int pf_ioctl_add_addr(struct pf_nl_pooladdr *pp) { struct pf_kpooladdr *pa = NULL; struct pfi_kkif *kif = NULL; int error; if (pp->which != PF_RDR && pp->which != PF_NAT && pp->which != PF_RT) return (EINVAL); switch (pp->af) { #ifdef INET case AF_INET: /* FALLTHROUGH */ #endif /* INET */ #ifdef INET6 case AF_INET6: /* FALLTHROUGH */ #endif /* INET6 */ case AF_UNSPEC: break; default: return (EAFNOSUPPORT); } if (pp->addr.addr.type != PF_ADDR_ADDRMASK && pp->addr.addr.type != PF_ADDR_DYNIFTL && pp->addr.addr.type != PF_ADDR_TABLE) return (EINVAL); if (pp->addr.addr.p.dyn != NULL) return (EINVAL); pa = malloc(sizeof(*pa), M_PFRULE, M_WAITOK); error = pf_pooladdr_to_kpooladdr(&pp->addr, pa); if (error != 0) goto out; if (pa->ifname[0]) kif = pf_kkif_create(M_WAITOK); PF_RULES_WLOCK(); if (pp->ticket != V_ticket_pabuf) { PF_RULES_WUNLOCK(); if (pa->ifname[0]) pf_kkif_free(kif); error = EBUSY; goto out; } if (pa->ifname[0]) { pa->kif = pfi_kkif_attach(kif, pa->ifname); kif = NULL; pfi_kkif_ref(pa->kif); } else pa->kif = NULL; if (pa->addr.type == PF_ADDR_DYNIFTL && ((error = pfi_dynaddr_setup(&pa->addr, pp->af)) != 0)) { if (pa->ifname[0]) pfi_kkif_unref(pa->kif); PF_RULES_WUNLOCK(); goto out; } pa->af = pp->af; switch (pp->which) { case PF_NAT: TAILQ_INSERT_TAIL(&V_pf_pabuf[0], pa, entries); break; case PF_RDR: TAILQ_INSERT_TAIL(&V_pf_pabuf[1], pa, entries); break; case PF_RT: TAILQ_INSERT_TAIL(&V_pf_pabuf[2], pa, entries); break; } PF_RULES_WUNLOCK(); return (0); out: free(pa, M_PFRULE); return (error); } int pf_ioctl_get_addrs(struct pf_nl_pooladdr *pp) { struct pf_kpool *pool; struct pf_kpooladdr *pa; PF_RULES_RLOCK_TRACKER; if (pp->which != PF_RDR && pp->which != PF_NAT && pp->which != PF_RT) return (EINVAL); pp->anchor[sizeof(pp->anchor) - 1] = 0; pp->nr = 0; PF_RULES_RLOCK(); pool = pf_get_kpool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 0, pp->which); if (pool == NULL) { PF_RULES_RUNLOCK(); return (EBUSY); } TAILQ_FOREACH(pa, &pool->list, entries) pp->nr++; PF_RULES_RUNLOCK(); return (0); } int pf_ioctl_get_addr(struct pf_nl_pooladdr *pp) { struct pf_kpool *pool; struct pf_kpooladdr *pa; u_int32_t nr = 0; if (pp->which != PF_RDR && pp->which != PF_NAT && pp->which != PF_RT) return (EINVAL); PF_RULES_RLOCK_TRACKER; pp->anchor[sizeof(pp->anchor) - 1] = '\0'; PF_RULES_RLOCK(); pool = pf_get_kpool(pp->anchor, pp->ticket, pp->r_action, pp->r_num, 0, 1, 1, pp->which); if (pool == NULL) { PF_RULES_RUNLOCK(); return (EBUSY); } pa = TAILQ_FIRST(&pool->list); while ((pa != NULL) && (nr < pp->nr)) { pa = TAILQ_NEXT(pa, entries); nr++; } if (pa == NULL) { PF_RULES_RUNLOCK(); return (EBUSY); } pf_kpooladdr_to_pooladdr(pa, &pp->addr); pp->af = pa->af; pf_addr_copyout(&pp->addr.addr); PF_RULES_RUNLOCK(); return (0); } int pf_ioctl_get_rulesets(struct pfioc_ruleset *pr) { struct pf_kruleset *ruleset; struct pf_kanchor *anchor; PF_RULES_RLOCK_TRACKER; pr->path[sizeof(pr->path) - 1] = '\0'; PF_RULES_RLOCK(); if ((ruleset = pf_find_kruleset(pr->path)) == NULL) { PF_RULES_RUNLOCK(); return (ENOENT); } pr->nr = 0; if (ruleset == &pf_main_ruleset) { /* XXX kludge for pf_main_ruleset */ RB_FOREACH(anchor, pf_kanchor_global, &V_pf_anchors) if (anchor->parent == NULL) pr->nr++; } else { RB_FOREACH(anchor, pf_kanchor_node, &ruleset->anchor->children) pr->nr++; } PF_RULES_RUNLOCK(); return (0); } int pf_ioctl_get_ruleset(struct pfioc_ruleset *pr) { struct pf_kruleset *ruleset; struct pf_kanchor *anchor; u_int32_t nr = 0; int error = 0; PF_RULES_RLOCK_TRACKER; PF_RULES_RLOCK(); if ((ruleset = pf_find_kruleset(pr->path)) == NULL) { PF_RULES_RUNLOCK(); return (ENOENT); } pr->name[0] = '\0'; if (ruleset == &pf_main_ruleset) { /* XXX kludge for pf_main_ruleset */ RB_FOREACH(anchor, pf_kanchor_global, &V_pf_anchors) if (anchor->parent == NULL && nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } else { RB_FOREACH(anchor, pf_kanchor_node, &ruleset->anchor->children) if (nr++ == pr->nr) { strlcpy(pr->name, anchor->name, sizeof(pr->name)); break; } } if (!pr->name[0]) error = EBUSY; PF_RULES_RUNLOCK(); return (error); } int pf_ioctl_natlook(struct pfioc_natlook *pnl) { struct pf_state_key *sk; struct pf_kstate *state; struct pf_state_key_cmp key; int m = 0, direction = pnl->direction; int sidx, didx; /* NATLOOK src and dst are reversed, so reverse sidx/didx */ sidx = (direction == PF_IN) ? 1 : 0; didx = (direction == PF_IN) ? 0 : 1; if (!pnl->proto || PF_AZERO(&pnl->saddr, pnl->af) || PF_AZERO(&pnl->daddr, pnl->af) || ((pnl->proto == IPPROTO_TCP || pnl->proto == IPPROTO_UDP) && (!pnl->dport || !pnl->sport))) return (EINVAL); switch (pnl->direction) { case PF_IN: case PF_OUT: case PF_INOUT: break; default: return (EINVAL); } switch (pnl->af) { #ifdef INET case AF_INET: break; #endif /* INET */ #ifdef INET6 case AF_INET6: break; #endif /* INET6 */ default: return (EAFNOSUPPORT); } bzero(&key, sizeof(key)); key.af = pnl->af; key.proto = pnl->proto; pf_addrcpy(&key.addr[sidx], &pnl->saddr, pnl->af); key.port[sidx] = pnl->sport; pf_addrcpy(&key.addr[didx], &pnl->daddr, pnl->af); key.port[didx] = pnl->dport; state = pf_find_state_all(&key, direction, &m); if (state == NULL) return (ENOENT); if (m > 1) { PF_STATE_UNLOCK(state); return (E2BIG); /* more than one state */ } sk = state->key[sidx]; pf_addrcpy(&pnl->rsaddr, &sk->addr[sidx], sk->af); pnl->rsport = sk->port[sidx]; pf_addrcpy(&pnl->rdaddr, &sk->addr[didx], sk->af); pnl->rdport = sk->port[didx]; PF_STATE_UNLOCK(state); return (0); } static int pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { int error = 0; PF_RULES_RLOCK_TRACKER; #define ERROUT_IOCTL(target, x) \ do { \ error = (x); \ SDT_PROBE3(pf, ioctl, ioctl, error, cmd, error, __LINE__); \ goto target; \ } while (0) /* XXX keep in sync with switch() below */ if (securelevel_gt(td->td_ucred, 2)) switch (cmd) { case DIOCGETRULES: case DIOCGETRULENV: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCGETSTATENV: case DIOCSETSTATUSIF: case DIOCGETSTATUSNV: case DIOCCLRSTATUS: case DIOCNATLOOK: case DIOCSETDEBUG: #ifdef COMPAT_FREEBSD14 case DIOCGETSTATES: case DIOCGETSTATESV2: #endif case DIOCGETTIMEOUT: case DIOCCLRRULECTRS: case DIOCGETLIMIT: case DIOCGETALTQSV0: case DIOCGETALTQSV1: case DIOCGETALTQV0: case DIOCGETALTQV1: case DIOCGETQSTATSV0: case DIOCGETQSTATSV1: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRCLRASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCCLRSRCNODES: case DIOCGETSYNCOOKIES: case DIOCIGETIFACES: case DIOCGIFSPEEDV0: case DIOCGIFSPEEDV1: case DIOCSETIFFLAG: case DIOCCLRIFFLAG: case DIOCGETETHRULES: case DIOCGETETHRULE: case DIOCGETETHRULESETS: case DIOCGETETHRULESET: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) break; /* dummy operation ok */ return (EPERM); default: return (EPERM); } if (!(flags & FWRITE)) switch (cmd) { case DIOCGETRULES: case DIOCGETADDRS: case DIOCGETADDR: case DIOCGETSTATE: case DIOCGETSTATENV: case DIOCGETSTATUSNV: #ifdef COMPAT_FREEBSD14 case DIOCGETSTATES: case DIOCGETSTATESV2: #endif case DIOCGETTIMEOUT: case DIOCGETLIMIT: case DIOCGETALTQSV0: case DIOCGETALTQSV1: case DIOCGETALTQV0: case DIOCGETALTQV1: case DIOCGETQSTATSV0: case DIOCGETQSTATSV1: case DIOCGETRULESETS: case DIOCGETRULESET: case DIOCNATLOOK: case DIOCRGETTABLES: case DIOCRGETTSTATS: case DIOCRGETADDRS: case DIOCRGETASTATS: case DIOCRTSTADDRS: case DIOCOSFPGET: case DIOCGETSRCNODES: case DIOCGETSYNCOOKIES: case DIOCIGETIFACES: case DIOCGIFSPEEDV1: case DIOCGIFSPEEDV0: case DIOCGETRULENV: case DIOCGETETHRULES: case DIOCGETETHRULE: case DIOCGETETHRULESETS: case DIOCGETETHRULESET: break; case DIOCRCLRTABLES: case DIOCRADDTABLES: case DIOCRDELTABLES: case DIOCRCLRTSTATS: case DIOCRCLRADDRS: case DIOCRADDADDRS: case DIOCRDELADDRS: case DIOCRSETADDRS: case DIOCRSETTFLAGS: if (((struct pfioc_table *)addr)->pfrio_flags & PFR_FLAG_DUMMY) { flags |= FWRITE; /* need write lock for dummy */ break; /* dummy operation ok */ } return (EACCES); default: return (EACCES); } CURVNET_SET(TD_TO_VNET(td)); switch (cmd) { #ifdef COMPAT_FREEBSD14 case DIOCSTART: error = pf_start(); break; case DIOCSTOP: error = pf_stop(); break; #endif case DIOCGETETHRULES: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl; void *packed; struct pf_keth_rule *tail; struct pf_keth_ruleset *rs; u_int32_t ticket, nr; const char *anchor = ""; nvl = NULL; packed = NULL; #define ERROUT(x) ERROUT_IOCTL(DIOCGETETHRULES_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); /* Copy the request in */ packed = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, packed, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(packed, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_string(nvl, "anchor")) ERROUT(EBADMSG); anchor = nvlist_get_string(nvl, "anchor"); rs = pf_find_keth_ruleset(anchor); nvlist_destroy(nvl); nvl = NULL; free(packed, M_NVLIST); packed = NULL; if (rs == NULL) ERROUT(ENOENT); /* Reply */ nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); PF_RULES_RLOCK(); ticket = rs->active.ticket; tail = TAILQ_LAST(rs->active.rules, pf_keth_ruleq); if (tail) nr = tail->nr + 1; else nr = 0; PF_RULES_RUNLOCK(); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_number(nvl, "nr", nr); packed = nvlist_pack(nvl, &nv->len); if (packed == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(packed, nv->data, nv->len); #undef ERROUT DIOCGETETHRULES_error: free(packed, M_NVLIST); nvlist_destroy(nvl); break; } case DIOCGETETHRULE: { struct epoch_tracker et; struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_keth_rule *rule = NULL; struct pf_keth_ruleset *rs; u_int32_t ticket, nr; bool clear = false; const char *anchor; #define ERROUT(x) ERROUT_IOCTL(DIOCGETETHRULE_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EBADMSG); ticket = nvlist_get_number(nvl, "ticket"); if (! nvlist_exists_string(nvl, "anchor")) ERROUT(EBADMSG); anchor = nvlist_get_string(nvl, "anchor"); if (nvlist_exists_bool(nvl, "clear")) clear = nvlist_get_bool(nvl, "clear"); if (clear && !(flags & FWRITE)) ERROUT(EACCES); if (! nvlist_exists_number(nvl, "nr")) ERROUT(EBADMSG); nr = nvlist_get_number(nvl, "nr"); PF_RULES_RLOCK(); rs = pf_find_keth_ruleset(anchor); if (rs == NULL) { PF_RULES_RUNLOCK(); ERROUT(ENOENT); } if (ticket != rs->active.ticket) { PF_RULES_RUNLOCK(); ERROUT(EBUSY); } nvlist_destroy(nvl); nvl = NULL; free(nvlpacked, M_NVLIST); nvlpacked = NULL; rule = TAILQ_FIRST(rs->active.rules); while ((rule != NULL) && (rule->nr != nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { PF_RULES_RUNLOCK(); ERROUT(ENOENT); } /* Make sure rule can't go away. */ NET_EPOCH_ENTER(et); PF_RULES_RUNLOCK(); nvl = pf_keth_rule_to_nveth_rule(rule); if (pf_keth_anchor_nvcopyout(rs, rule, nvl)) { NET_EPOCH_EXIT(et); ERROUT(EBUSY); } NET_EPOCH_EXIT(et); if (nvl == NULL) ERROUT(ENOMEM); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); if (error == 0 && clear) { counter_u64_zero(rule->evaluations); for (int i = 0; i < 2; i++) { counter_u64_zero(rule->packets[i]); counter_u64_zero(rule->bytes[i]); } } #undef ERROUT DIOCGETETHRULE_error: free(nvlpacked, M_NVLIST); nvlist_destroy(nvl); break; } case DIOCADDETHRULE: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_keth_rule *rule = NULL, *tail = NULL; struct pf_keth_ruleset *ruleset = NULL; struct pfi_kkif *kif = NULL, *bridge_to_kif = NULL; const char *anchor = "", *anchor_call = ""; #define ERROUT(x) ERROUT_IOCTL(DIOCADDETHRULE_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EBADMSG); if (nvlist_exists_string(nvl, "anchor")) anchor = nvlist_get_string(nvl, "anchor"); if (nvlist_exists_string(nvl, "anchor_call")) anchor_call = nvlist_get_string(nvl, "anchor_call"); ruleset = pf_find_keth_ruleset(anchor); if (ruleset == NULL) ERROUT(EINVAL); if (nvlist_get_number(nvl, "ticket") != ruleset->inactive.ticket) { DPFPRINTF(PF_DEBUG_MISC, "ticket: %d != %d", (u_int32_t)nvlist_get_number(nvl, "ticket"), ruleset->inactive.ticket); ERROUT(EBUSY); } rule = malloc(sizeof(*rule), M_PFRULE, M_WAITOK); rule->timestamp = NULL; error = pf_nveth_rule_to_keth_rule(nvl, rule); if (error != 0) ERROUT(error); if (rule->ifname[0]) kif = pf_kkif_create(M_WAITOK); if (rule->bridge_to_name[0]) bridge_to_kif = pf_kkif_create(M_WAITOK); rule->evaluations = counter_u64_alloc(M_WAITOK); for (int i = 0; i < 2; i++) { rule->packets[i] = counter_u64_alloc(M_WAITOK); rule->bytes[i] = counter_u64_alloc(M_WAITOK); } rule->timestamp = uma_zalloc_pcpu(pf_timestamp_pcpu_zone, M_WAITOK | M_ZERO); PF_RULES_WLOCK(); if (rule->ifname[0]) { rule->kif = pfi_kkif_attach(kif, rule->ifname); pfi_kkif_ref(rule->kif); } else rule->kif = NULL; if (rule->bridge_to_name[0]) { rule->bridge_to = pfi_kkif_attach(bridge_to_kif, rule->bridge_to_name); pfi_kkif_ref(rule->bridge_to); } else rule->bridge_to = NULL; #ifdef ALTQ /* set queue IDs */ if (rule->qname[0] != 0) { if ((rule->qid = pf_qname2qid(rule->qname, true)) == 0) error = EBUSY; else rule->qid = rule->qid; } #endif if (rule->tagname[0]) if ((rule->tag = pf_tagname2tag(rule->tagname)) == 0) error = EBUSY; if (rule->match_tagname[0]) if ((rule->match_tag = pf_tagname2tag( rule->match_tagname)) == 0) error = EBUSY; if (error == 0 && rule->ipdst.addr.type == PF_ADDR_TABLE) error = pf_eth_addr_setup(ruleset, &rule->ipdst.addr); if (error == 0 && rule->ipsrc.addr.type == PF_ADDR_TABLE) error = pf_eth_addr_setup(ruleset, &rule->ipsrc.addr); if (error) { pf_free_eth_rule(rule); PF_RULES_WUNLOCK(); ERROUT(error); } if (pf_keth_anchor_setup(rule, ruleset, anchor_call)) { pf_free_eth_rule(rule); PF_RULES_WUNLOCK(); ERROUT(EINVAL); } tail = TAILQ_LAST(ruleset->inactive.rules, pf_keth_ruleq); if (tail) rule->nr = tail->nr + 1; else rule->nr = 0; TAILQ_INSERT_TAIL(ruleset->inactive.rules, rule, entries); PF_RULES_WUNLOCK(); #undef ERROUT DIOCADDETHRULE_error: nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); break; } case DIOCGETETHRULESETS: { struct epoch_tracker et; struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_keth_ruleset *ruleset; struct pf_keth_anchor *anchor; int nr = 0; #define ERROUT(x) ERROUT_IOCTL(DIOCGETETHRULESETS_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_string(nvl, "path")) ERROUT(EBADMSG); NET_EPOCH_ENTER(et); if ((ruleset = pf_find_keth_ruleset( nvlist_get_string(nvl, "path"))) == NULL) { NET_EPOCH_EXIT(et); ERROUT(ENOENT); } if (ruleset->anchor == NULL) { RB_FOREACH(anchor, pf_keth_anchor_global, &V_pf_keth_anchors) if (anchor->parent == NULL) nr++; } else { RB_FOREACH(anchor, pf_keth_anchor_node, &ruleset->anchor->children) nr++; } NET_EPOCH_EXIT(et); nvlist_destroy(nvl); nvl = NULL; free(nvlpacked, M_NVLIST); nvlpacked = NULL; nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); nvlist_add_number(nvl, "nr", nr); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); #undef ERROUT DIOCGETETHRULESETS_error: free(nvlpacked, M_NVLIST); nvlist_destroy(nvl); break; } case DIOCGETETHRULESET: { struct epoch_tracker et; struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_keth_ruleset *ruleset; struct pf_keth_anchor *anchor; int nr = 0, req_nr = 0; bool found = false; #define ERROUT(x) ERROUT_IOCTL(DIOCGETETHRULESET_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_string(nvl, "path")) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "nr")) ERROUT(EBADMSG); req_nr = nvlist_get_number(nvl, "nr"); NET_EPOCH_ENTER(et); if ((ruleset = pf_find_keth_ruleset( nvlist_get_string(nvl, "path"))) == NULL) { NET_EPOCH_EXIT(et); ERROUT(ENOENT); } nvlist_destroy(nvl); nvl = NULL; free(nvlpacked, M_NVLIST); nvlpacked = NULL; nvl = nvlist_create(0); if (nvl == NULL) { NET_EPOCH_EXIT(et); ERROUT(ENOMEM); } if (ruleset->anchor == NULL) { RB_FOREACH(anchor, pf_keth_anchor_global, &V_pf_keth_anchors) { if (anchor->parent == NULL && nr++ == req_nr) { found = true; break; } } } else { RB_FOREACH(anchor, pf_keth_anchor_node, &ruleset->anchor->children) { if (nr++ == req_nr) { found = true; break; } } } NET_EPOCH_EXIT(et); if (found) { nvlist_add_number(nvl, "nr", nr); nvlist_add_string(nvl, "name", anchor->name); if (ruleset->anchor) nvlist_add_string(nvl, "path", ruleset->anchor->path); else nvlist_add_string(nvl, "path", ""); } else { ERROUT(EBUSY); } nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); #undef ERROUT DIOCGETETHRULESET_error: free(nvlpacked, M_NVLIST); nvlist_destroy(nvl); break; } case DIOCADDRULENV: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvl = NULL; void *nvlpacked = NULL; struct pf_krule *rule = NULL; const char *anchor = "", *anchor_call = ""; uint32_t ticket = 0, pool_ticket = 0; #define ERROUT(x) ERROUT_IOCTL(DIOCADDRULENV_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EINVAL); ticket = nvlist_get_number(nvl, "ticket"); if (! nvlist_exists_number(nvl, "pool_ticket")) ERROUT(EINVAL); pool_ticket = nvlist_get_number(nvl, "pool_ticket"); if (! nvlist_exists_nvlist(nvl, "rule")) ERROUT(EINVAL); rule = pf_krule_alloc(); error = pf_nvrule_to_krule(nvlist_get_nvlist(nvl, "rule"), rule); if (error) ERROUT(error); if (nvlist_exists_string(nvl, "anchor")) anchor = nvlist_get_string(nvl, "anchor"); if (nvlist_exists_string(nvl, "anchor_call")) anchor_call = nvlist_get_string(nvl, "anchor_call"); if ((error = nvlist_error(nvl))) ERROUT(error); /* Frees rule on error */ error = pf_ioctl_addrule(rule, ticket, pool_ticket, anchor, anchor_call, td->td_ucred->cr_ruid, td->td_proc ? td->td_proc->p_pid : 0); nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); break; #undef ERROUT DIOCADDRULENV_error: pf_krule_free(rule); nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); break; } case DIOCADDRULE: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; struct pf_krule *rule; rule = pf_krule_alloc(); error = pf_rule_to_krule(&pr->rule, rule); if (error != 0) { pf_krule_free(rule); goto fail; } pr->anchor[sizeof(pr->anchor) - 1] = '\0'; /* Frees rule on error */ error = pf_ioctl_addrule(rule, pr->ticket, pr->pool_ticket, pr->anchor, pr->anchor_call, td->td_ucred->cr_ruid, td->td_proc ? td->td_proc->p_pid : 0); break; } case DIOCGETRULES: { struct pfioc_rule *pr = (struct pfioc_rule *)addr; pr->anchor[sizeof(pr->anchor) - 1] = '\0'; error = pf_ioctl_getrules(pr); break; } case DIOCGETRULENV: { struct pfioc_nv *nv = (struct pfioc_nv *)addr; nvlist_t *nvrule = NULL; nvlist_t *nvl = NULL; struct pf_kruleset *ruleset; struct pf_krule *rule; void *nvlpacked = NULL; int rs_num, nr; bool clear_counter = false; #define ERROUT(x) ERROUT_IOCTL(DIOCGETRULENV_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); /* Copy the request in */ nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_string(nvl, "anchor")) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "ruleset")) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "ticket")) ERROUT(EBADMSG); if (! nvlist_exists_number(nvl, "nr")) ERROUT(EBADMSG); if (nvlist_exists_bool(nvl, "clear_counter")) clear_counter = nvlist_get_bool(nvl, "clear_counter"); if (clear_counter && !(flags & FWRITE)) ERROUT(EACCES); nr = nvlist_get_number(nvl, "nr"); PF_RULES_WLOCK(); ruleset = pf_find_kruleset(nvlist_get_string(nvl, "anchor")); if (ruleset == NULL) { PF_RULES_WUNLOCK(); ERROUT(ENOENT); } rs_num = pf_get_ruleset_number(nvlist_get_number(nvl, "ruleset")); if (rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); ERROUT(EINVAL); } if (nvlist_get_number(nvl, "ticket") != ruleset->rules[rs_num].active.ticket) { PF_RULES_WUNLOCK(); ERROUT(EBUSY); } if ((error = nvlist_error(nvl))) { PF_RULES_WUNLOCK(); ERROUT(error); } rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr); while ((rule != NULL) && (rule->nr != nr)) rule = TAILQ_NEXT(rule, entries); if (rule == NULL) { PF_RULES_WUNLOCK(); ERROUT(EBUSY); } nvrule = pf_krule_to_nvrule(rule); nvlist_destroy(nvl); nvl = nvlist_create(0); if (nvl == NULL) { PF_RULES_WUNLOCK(); ERROUT(ENOMEM); } nvlist_add_number(nvl, "nr", nr); nvlist_add_nvlist(nvl, "rule", nvrule); nvlist_destroy(nvrule); nvrule = NULL; if (pf_kanchor_nvcopyout(ruleset, rule, nvl)) { PF_RULES_WUNLOCK(); ERROUT(EBUSY); } free(nvlpacked, M_NVLIST); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) { PF_RULES_WUNLOCK(); ERROUT(ENOMEM); } if (nv->size == 0) { PF_RULES_WUNLOCK(); ERROUT(0); } else if (nv->size < nv->len) { PF_RULES_WUNLOCK(); ERROUT(ENOSPC); } if (clear_counter) pf_krule_clear_counters(rule); PF_RULES_WUNLOCK(); error = copyout(nvlpacked, nv->data, nv->len); #undef ERROUT DIOCGETRULENV_error: free(nvlpacked, M_NVLIST); nvlist_destroy(nvrule); nvlist_destroy(nvl); break; } case DIOCCHANGERULE: { struct pfioc_rule *pcr = (struct pfioc_rule *)addr; struct pf_kruleset *ruleset; struct pf_krule *oldrule = NULL, *newrule = NULL; struct pfi_kkif *kif = NULL; struct pf_kpooladdr *pa; u_int32_t nr = 0; int rs_num; pcr->anchor[sizeof(pcr->anchor) - 1] = '\0'; if (pcr->action < PF_CHANGE_ADD_HEAD || pcr->action > PF_CHANGE_GET_TICKET) { error = EINVAL; goto fail; } if (pcr->rule.return_icmp >> 8 > ICMP_MAXTYPE) { error = EINVAL; goto fail; } if (pcr->action != PF_CHANGE_REMOVE) { newrule = pf_krule_alloc(); error = pf_rule_to_krule(&pcr->rule, newrule); if (error != 0) { pf_krule_free(newrule); goto fail; } if ((error = pf_rule_checkaf(newrule))) { pf_krule_free(newrule); goto fail; } if (newrule->ifname[0]) kif = pf_kkif_create(M_WAITOK); pf_counter_u64_init(&newrule->evaluations, M_WAITOK); for (int i = 0; i < 2; i++) { pf_counter_u64_init(&newrule->packets[i], M_WAITOK); pf_counter_u64_init(&newrule->bytes[i], M_WAITOK); } newrule->states_cur = counter_u64_alloc(M_WAITOK); newrule->states_tot = counter_u64_alloc(M_WAITOK); for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type] = counter_u64_alloc(M_WAITOK); newrule->cuid = td->td_ucred->cr_ruid; newrule->cpid = td->td_proc ? td->td_proc->p_pid : 0; TAILQ_INIT(&newrule->nat.list); TAILQ_INIT(&newrule->rdr.list); TAILQ_INIT(&newrule->route.list); } #define ERROUT(x) ERROUT_IOCTL(DIOCCHANGERULE_error, x) PF_CONFIG_LOCK(); PF_RULES_WLOCK(); #ifdef PF_WANT_32_TO_64_COUNTER if (newrule != NULL) { LIST_INSERT_HEAD(&V_pf_allrulelist, newrule, allrulelist); newrule->allrulelinked = true; V_pf_allrulecount++; } #endif if (!(pcr->action == PF_CHANGE_REMOVE || pcr->action == PF_CHANGE_GET_TICKET) && pcr->pool_ticket != V_ticket_pabuf) ERROUT(EBUSY); ruleset = pf_find_kruleset(pcr->anchor); if (ruleset == NULL) ERROUT(EINVAL); rs_num = pf_get_ruleset_number(pcr->rule.action); if (rs_num >= PF_RULESET_MAX) ERROUT(EINVAL); /* * XXXMJG: there is no guarantee that the ruleset was * created by the usual route of calling DIOCXBEGIN. * As a result it is possible the rule tree will not * be allocated yet. Hack around it by doing it here. * Note it is fine to let the tree persist in case of * error as it will be freed down the road on future * updates (if need be). */ if (ruleset->rules[rs_num].active.tree == NULL) { ruleset->rules[rs_num].active.tree = pf_rule_tree_alloc(M_NOWAIT); if (ruleset->rules[rs_num].active.tree == NULL) { ERROUT(ENOMEM); } } if (pcr->action == PF_CHANGE_GET_TICKET) { pcr->ticket = ++ruleset->rules[rs_num].active.ticket; ERROUT(0); } else if (pcr->ticket != ruleset->rules[rs_num].active.ticket) ERROUT(EINVAL); if (pcr->action != PF_CHANGE_REMOVE) { if (newrule->ifname[0]) { newrule->kif = pfi_kkif_attach(kif, newrule->ifname); kif = NULL; pfi_kkif_ref(newrule->kif); } else newrule->kif = NULL; if (newrule->rtableid > 0 && newrule->rtableid >= rt_numfibs) error = EBUSY; #ifdef ALTQ /* set queue IDs */ if (newrule->qname[0] != 0) { if ((newrule->qid = pf_qname2qid(newrule->qname, true)) == 0) error = EBUSY; else if (newrule->pqname[0] != 0) { if ((newrule->pqid = pf_qname2qid(newrule->pqname, true)) == 0) error = EBUSY; } else newrule->pqid = newrule->qid; } #endif /* ALTQ */ if (newrule->tagname[0]) if ((newrule->tag = pf_tagname2tag(newrule->tagname)) == 0) error = EBUSY; if (newrule->match_tagname[0]) if ((newrule->match_tag = pf_tagname2tag( newrule->match_tagname)) == 0) error = EBUSY; if (newrule->rt && !newrule->direction) error = EINVAL; if (!newrule->log) newrule->logif = 0; if (pf_addr_setup(ruleset, &newrule->src.addr, newrule->af)) error = ENOMEM; if (pf_addr_setup(ruleset, &newrule->dst.addr, newrule->af)) error = ENOMEM; if (pf_kanchor_setup(newrule, ruleset, pcr->anchor_call)) error = EINVAL; for (int i = 0; i < 3; i++) { TAILQ_FOREACH(pa, &V_pf_pabuf[i], entries) if (pa->addr.type == PF_ADDR_TABLE) { pa->addr.p.tbl = pfr_attach_table(ruleset, pa->addr.v.tblname); if (pa->addr.p.tbl == NULL) error = ENOMEM; } } newrule->overload_tbl = NULL; if (newrule->overload_tblname[0]) { if ((newrule->overload_tbl = pfr_attach_table( ruleset, newrule->overload_tblname)) == NULL) error = EINVAL; else newrule->overload_tbl->pfrkt_flags |= PFR_TFLAG_ACTIVE; } pf_mv_kpool(&V_pf_pabuf[0], &newrule->nat.list); pf_mv_kpool(&V_pf_pabuf[1], &newrule->rdr.list); pf_mv_kpool(&V_pf_pabuf[2], &newrule->route.list); if (((((newrule->action == PF_NAT) || (newrule->action == PF_RDR) || (newrule->action == PF_BINAT) || (newrule->rt > PF_NOPFROUTE)) && !newrule->anchor)) && (TAILQ_FIRST(&newrule->rdr.list) == NULL)) error = EINVAL; if (error) { pf_free_rule(newrule); PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); goto fail; } newrule->nat.cur = TAILQ_FIRST(&newrule->nat.list); newrule->rdr.cur = TAILQ_FIRST(&newrule->rdr.list); } pf_empty_kpool(&V_pf_pabuf[0]); pf_empty_kpool(&V_pf_pabuf[1]); pf_empty_kpool(&V_pf_pabuf[2]); if (pcr->action == PF_CHANGE_ADD_HEAD) oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); else if (pcr->action == PF_CHANGE_ADD_TAIL) oldrule = TAILQ_LAST( ruleset->rules[rs_num].active.ptr, pf_krulequeue); else { oldrule = TAILQ_FIRST( ruleset->rules[rs_num].active.ptr); while ((oldrule != NULL) && (oldrule->nr != pcr->nr)) oldrule = TAILQ_NEXT(oldrule, entries); if (oldrule == NULL) { if (newrule != NULL) pf_free_rule(newrule); PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); error = EINVAL; goto fail; } } if (pcr->action == PF_CHANGE_REMOVE) { pf_unlink_rule(ruleset->rules[rs_num].active.ptr, oldrule); RB_REMOVE(pf_krule_global, ruleset->rules[rs_num].active.tree, oldrule); ruleset->rules[rs_num].active.rcount--; } else { pf_hash_rule(newrule); if (RB_INSERT(pf_krule_global, ruleset->rules[rs_num].active.tree, newrule) != NULL) { pf_free_rule(newrule); PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); error = EEXIST; goto fail; } if (oldrule == NULL) TAILQ_INSERT_TAIL( ruleset->rules[rs_num].active.ptr, newrule, entries); else if (pcr->action == PF_CHANGE_ADD_HEAD || pcr->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldrule, newrule, entries); else TAILQ_INSERT_AFTER( ruleset->rules[rs_num].active.ptr, oldrule, newrule, entries); ruleset->rules[rs_num].active.rcount++; } nr = 0; TAILQ_FOREACH(oldrule, ruleset->rules[rs_num].active.ptr, entries) oldrule->nr = nr++; ruleset->rules[rs_num].active.ticket++; pf_calc_skip_steps(ruleset->rules[rs_num].active.ptr); pf_remove_if_empty_kruleset(ruleset); PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); break; #undef ERROUT DIOCCHANGERULE_error: PF_RULES_WUNLOCK(); PF_CONFIG_UNLOCK(); pf_krule_free(newrule); pf_kkif_free(kif); break; } case DIOCCLRSTATESNV: { error = pf_clearstates_nv((struct pfioc_nv *)addr); break; } case DIOCKILLSTATESNV: { error = pf_killstates_nv((struct pfioc_nv *)addr); break; } case DIOCADDSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pfsync_state_1301 *sp = &ps->state; if (sp->timeout >= PFTM_MAX) { error = EINVAL; goto fail; } if (V_pfsync_state_import_ptr != NULL) { PF_RULES_RLOCK(); error = V_pfsync_state_import_ptr( (union pfsync_state_union *)sp, PFSYNC_SI_IOCTL, PFSYNC_MSG_VERSION_1301); PF_RULES_RUNLOCK(); } else error = EOPNOTSUPP; break; } case DIOCGETSTATE: { struct pfioc_state *ps = (struct pfioc_state *)addr; struct pf_kstate *s; s = pf_find_state_byid(ps->state.id, ps->state.creatorid); if (s == NULL) { error = ENOENT; goto fail; } pfsync_state_export((union pfsync_state_union*)&ps->state, s, PFSYNC_MSG_VERSION_1301); PF_STATE_UNLOCK(s); break; } case DIOCGETSTATENV: { error = pf_getstate((struct pfioc_nv *)addr); break; } #ifdef COMPAT_FREEBSD14 case DIOCGETSTATES: { struct pfioc_states *ps = (struct pfioc_states *)addr; struct pf_kstate *s; struct pfsync_state_1301 *pstore, *p; int i, nr; size_t slice_count = 16, count; void *out; if (ps->ps_len <= 0) { nr = uma_zone_get_cur(V_pf_state_z); ps->ps_len = sizeof(struct pfsync_state_1301) * nr; break; } out = ps->ps_states; pstore = mallocarray(slice_count, sizeof(struct pfsync_state_1301), M_PF, M_WAITOK | M_ZERO); nr = 0; for (i = 0; i <= V_pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; DIOCGETSTATES_retry: p = pstore; if (LIST_EMPTY(&ih->states)) continue; PF_HASHROW_LOCK(ih); count = 0; LIST_FOREACH(s, &ih->states, entry) { if (s->timeout == PFTM_UNLINKED) continue; count++; } if (count > slice_count) { PF_HASHROW_UNLOCK(ih); free(pstore, M_PF); slice_count = count * 2; pstore = mallocarray(slice_count, sizeof(struct pfsync_state_1301), M_PF, M_WAITOK | M_ZERO); goto DIOCGETSTATES_retry; } if ((nr+count) * sizeof(*p) > ps->ps_len) { PF_HASHROW_UNLOCK(ih); goto DIOCGETSTATES_full; } LIST_FOREACH(s, &ih->states, entry) { if (s->timeout == PFTM_UNLINKED) continue; pfsync_state_export((union pfsync_state_union*)p, s, PFSYNC_MSG_VERSION_1301); p++; nr++; } PF_HASHROW_UNLOCK(ih); error = copyout(pstore, out, sizeof(struct pfsync_state_1301) * count); if (error) { free(pstore, M_PF); goto fail; } out = ps->ps_states + nr; } DIOCGETSTATES_full: ps->ps_len = sizeof(struct pfsync_state_1301) * nr; free(pstore, M_PF); break; } case DIOCGETSTATESV2: { struct pfioc_states_v2 *ps = (struct pfioc_states_v2 *)addr; struct pf_kstate *s; struct pf_state_export *pstore, *p; int i, nr; size_t slice_count = 16, count; void *out; if (ps->ps_req_version > PF_STATE_VERSION) { error = ENOTSUP; goto fail; } if (ps->ps_len <= 0) { nr = uma_zone_get_cur(V_pf_state_z); ps->ps_len = sizeof(struct pf_state_export) * nr; break; } out = ps->ps_states; pstore = mallocarray(slice_count, sizeof(struct pf_state_export), M_PF, M_WAITOK | M_ZERO); nr = 0; for (i = 0; i <= V_pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; DIOCGETSTATESV2_retry: p = pstore; if (LIST_EMPTY(&ih->states)) continue; PF_HASHROW_LOCK(ih); count = 0; LIST_FOREACH(s, &ih->states, entry) { if (s->timeout == PFTM_UNLINKED) continue; count++; } if (count > slice_count) { PF_HASHROW_UNLOCK(ih); free(pstore, M_PF); slice_count = count * 2; pstore = mallocarray(slice_count, sizeof(struct pf_state_export), M_PF, M_WAITOK | M_ZERO); goto DIOCGETSTATESV2_retry; } if ((nr+count) * sizeof(*p) > ps->ps_len) { PF_HASHROW_UNLOCK(ih); goto DIOCGETSTATESV2_full; } LIST_FOREACH(s, &ih->states, entry) { if (s->timeout == PFTM_UNLINKED) continue; pf_state_export(p, s); p++; nr++; } PF_HASHROW_UNLOCK(ih); error = copyout(pstore, out, sizeof(struct pf_state_export) * count); if (error) { free(pstore, M_PF); goto fail; } out = ps->ps_states + nr; } DIOCGETSTATESV2_full: ps->ps_len = nr * sizeof(struct pf_state_export); free(pstore, M_PF); break; } #endif case DIOCGETSTATUSNV: { error = pf_getstatus((struct pfioc_nv *)addr); break; } case DIOCSETSTATUSIF: { struct pfioc_if *pi = (struct pfioc_if *)addr; if (pi->ifname[0] == 0) { bzero(V_pf_status.ifname, IFNAMSIZ); break; } PF_RULES_WLOCK(); error = pf_user_strcpy(V_pf_status.ifname, pi->ifname, IFNAMSIZ); PF_RULES_WUNLOCK(); break; } case DIOCCLRSTATUS: { pf_ioctl_clear_status(); break; } case DIOCNATLOOK: { struct pfioc_natlook *pnl = (struct pfioc_natlook *)addr; error = pf_ioctl_natlook(pnl); break; } case DIOCSETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; error = pf_ioctl_set_timeout(pt->timeout, pt->seconds, &pt->seconds); break; } case DIOCGETTIMEOUT: { struct pfioc_tm *pt = (struct pfioc_tm *)addr; error = pf_ioctl_get_timeout(pt->timeout, &pt->seconds); break; } case DIOCGETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; error = pf_ioctl_get_limit(pl->index, &pl->limit); break; } case DIOCSETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; unsigned int old_limit; error = pf_ioctl_set_limit(pl->index, pl->limit, &old_limit); pl->limit = old_limit; break; } case DIOCSETDEBUG: { u_int32_t *level = (u_int32_t *)addr; PF_RULES_WLOCK(); V_pf_status.debug = *level; PF_RULES_WUNLOCK(); break; } case DIOCCLRRULECTRS: { /* obsoleted by DIOCGETRULE with action=PF_GET_CLR_CNTR */ struct pf_kruleset *ruleset = &pf_main_ruleset; struct pf_krule *rule; PF_RULES_WLOCK(); TAILQ_FOREACH(rule, ruleset->rules[PF_RULESET_FILTER].active.ptr, entries) { pf_counter_u64_zero(&rule->evaluations); for (int i = 0; i < 2; i++) { pf_counter_u64_zero(&rule->packets[i]); pf_counter_u64_zero(&rule->bytes[i]); } } PF_RULES_WUNLOCK(); break; } case DIOCGIFSPEEDV0: case DIOCGIFSPEEDV1: { struct pf_ifspeed_v1 *psp = (struct pf_ifspeed_v1 *)addr; struct pf_ifspeed_v1 ps; struct ifnet *ifp; if (psp->ifname[0] == '\0') { error = EINVAL; goto fail; } error = pf_user_strcpy(ps.ifname, psp->ifname, IFNAMSIZ); if (error != 0) goto fail; ifp = ifunit(ps.ifname); if (ifp != NULL) { psp->baudrate32 = (u_int32_t)uqmin(ifp->if_baudrate, UINT_MAX); if (cmd == DIOCGIFSPEEDV1) psp->baudrate = ifp->if_baudrate; } else { error = EINVAL; } break; } #ifdef ALTQ case DIOCSTARTALTQ: { struct pf_altq *altq; PF_RULES_WLOCK(); /* enable all altq interfaces on active list */ TAILQ_FOREACH(altq, V_pf_altq_ifs_active, entries) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { error = pf_enable_altq(altq); if (error != 0) break; } } if (error == 0) V_pf_altq_running = 1; PF_RULES_WUNLOCK(); DPFPRINTF(PF_DEBUG_MISC, "altq: started"); break; } case DIOCSTOPALTQ: { struct pf_altq *altq; PF_RULES_WLOCK(); /* disable all altq interfaces on active list */ TAILQ_FOREACH(altq, V_pf_altq_ifs_active, entries) { if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) == 0) { error = pf_disable_altq(altq); if (error != 0) break; } } if (error == 0) V_pf_altq_running = 0; PF_RULES_WUNLOCK(); DPFPRINTF(PF_DEBUG_MISC, "altq: stopped"); break; } case DIOCADDALTQV0: case DIOCADDALTQV1: { struct pfioc_altq_v1 *pa = (struct pfioc_altq_v1 *)addr; struct pf_altq *altq, *a; struct ifnet *ifp; altq = malloc(sizeof(*altq), M_PFALTQ, M_WAITOK | M_ZERO); error = pf_import_kaltq(pa, altq, IOCPARM_LEN(cmd)); if (error) goto fail; altq->local_flags = 0; PF_RULES_WLOCK(); if (pa->ticket != V_ticket_altqs_inactive) { PF_RULES_WUNLOCK(); free(altq, M_PFALTQ); error = EBUSY; goto fail; } /* * if this is for a queue, find the discipline and * copy the necessary fields */ if (altq->qname[0] != 0) { if ((altq->qid = pf_qname2qid(altq->qname, true)) == 0) { PF_RULES_WUNLOCK(); error = EBUSY; free(altq, M_PFALTQ); goto fail; } altq->altq_disc = NULL; TAILQ_FOREACH(a, V_pf_altq_ifs_inactive, entries) { if (strncmp(a->ifname, altq->ifname, IFNAMSIZ) == 0) { altq->altq_disc = a->altq_disc; break; } } } if ((ifp = ifunit(altq->ifname)) == NULL) altq->local_flags |= PFALTQ_FLAG_IF_REMOVED; else error = altq_add(ifp, altq); if (error) { PF_RULES_WUNLOCK(); free(altq, M_PFALTQ); goto fail; } if (altq->qname[0] != 0) TAILQ_INSERT_TAIL(V_pf_altqs_inactive, altq, entries); else TAILQ_INSERT_TAIL(V_pf_altq_ifs_inactive, altq, entries); /* version error check done on import above */ pf_export_kaltq(altq, pa, IOCPARM_LEN(cmd)); PF_RULES_WUNLOCK(); break; } case DIOCGETALTQSV0: case DIOCGETALTQSV1: { struct pfioc_altq_v1 *pa = (struct pfioc_altq_v1 *)addr; struct pf_altq *altq; PF_RULES_RLOCK(); pa->nr = 0; TAILQ_FOREACH(altq, V_pf_altq_ifs_active, entries) pa->nr++; TAILQ_FOREACH(altq, V_pf_altqs_active, entries) pa->nr++; pa->ticket = V_ticket_altqs_active; PF_RULES_RUNLOCK(); break; } case DIOCGETALTQV0: case DIOCGETALTQV1: { struct pfioc_altq_v1 *pa = (struct pfioc_altq_v1 *)addr; struct pf_altq *altq; PF_RULES_RLOCK(); if (pa->ticket != V_ticket_altqs_active) { PF_RULES_RUNLOCK(); error = EBUSY; goto fail; } altq = pf_altq_get_nth_active(pa->nr); if (altq == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; goto fail; } pf_export_kaltq(altq, pa, IOCPARM_LEN(cmd)); PF_RULES_RUNLOCK(); break; } case DIOCCHANGEALTQV0: case DIOCCHANGEALTQV1: /* CHANGEALTQ not supported yet! */ error = ENODEV; break; case DIOCGETQSTATSV0: case DIOCGETQSTATSV1: { struct pfioc_qstats_v1 *pq = (struct pfioc_qstats_v1 *)addr; struct pf_altq *altq; int nbytes; u_int32_t version; PF_RULES_RLOCK(); if (pq->ticket != V_ticket_altqs_active) { PF_RULES_RUNLOCK(); error = EBUSY; goto fail; } nbytes = pq->nbytes; altq = pf_altq_get_nth_active(pq->nr); if (altq == NULL) { PF_RULES_RUNLOCK(); error = EBUSY; goto fail; } if ((altq->local_flags & PFALTQ_FLAG_IF_REMOVED) != 0) { PF_RULES_RUNLOCK(); error = ENXIO; goto fail; } PF_RULES_RUNLOCK(); if (cmd == DIOCGETQSTATSV0) version = 0; /* DIOCGETQSTATSV0 means stats struct v0 */ else version = pq->version; error = altq_getqstats(altq, pq->buf, &nbytes, version); if (error == 0) { pq->scheduler = altq->scheduler; pq->nbytes = nbytes; } break; } #endif /* ALTQ */ case DIOCBEGINADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; error = pf_ioctl_begin_addrs(&pp->ticket); break; } case DIOCADDADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_nl_pooladdr npp = {}; npp.which = PF_RDR; memcpy(&npp, pp, sizeof(*pp)); error = pf_ioctl_add_addr(&npp); break; } case DIOCGETADDRS: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_nl_pooladdr npp = {}; npp.which = PF_RDR; memcpy(&npp, pp, sizeof(*pp)); error = pf_ioctl_get_addrs(&npp); memcpy(pp, &npp, sizeof(*pp)); break; } case DIOCGETADDR: { struct pfioc_pooladdr *pp = (struct pfioc_pooladdr *)addr; struct pf_nl_pooladdr npp = {}; npp.which = PF_RDR; memcpy(&npp, pp, sizeof(*pp)); error = pf_ioctl_get_addr(&npp); memcpy(pp, &npp, sizeof(*pp)); break; } case DIOCCHANGEADDR: { struct pfioc_pooladdr *pca = (struct pfioc_pooladdr *)addr; struct pf_kpool *pool; struct pf_kpooladdr *oldpa = NULL, *newpa = NULL; struct pf_kruleset *ruleset; struct pfi_kkif *kif = NULL; pca->anchor[sizeof(pca->anchor) - 1] = '\0'; if (pca->action < PF_CHANGE_ADD_HEAD || pca->action > PF_CHANGE_REMOVE) { error = EINVAL; goto fail; } if (pca->addr.addr.type != PF_ADDR_ADDRMASK && pca->addr.addr.type != PF_ADDR_DYNIFTL && pca->addr.addr.type != PF_ADDR_TABLE) { error = EINVAL; goto fail; } if (pca->addr.addr.p.dyn != NULL) { error = EINVAL; goto fail; } if (pca->action != PF_CHANGE_REMOVE) { #ifndef INET if (pca->af == AF_INET) { error = EAFNOSUPPORT; goto fail; } #endif /* INET */ #ifndef INET6 if (pca->af == AF_INET6) { error = EAFNOSUPPORT; goto fail; } #endif /* INET6 */ newpa = malloc(sizeof(*newpa), M_PFRULE, M_WAITOK); bcopy(&pca->addr, newpa, sizeof(struct pf_pooladdr)); if (newpa->ifname[0]) kif = pf_kkif_create(M_WAITOK); newpa->kif = NULL; } #define ERROUT(x) ERROUT_IOCTL(DIOCCHANGEADDR_error, x) PF_RULES_WLOCK(); ruleset = pf_find_kruleset(pca->anchor); if (ruleset == NULL) ERROUT(EBUSY); pool = pf_get_kpool(pca->anchor, pca->ticket, pca->r_action, pca->r_num, pca->r_last, 1, 1, PF_RDR); if (pool == NULL) ERROUT(EBUSY); if (pca->action != PF_CHANGE_REMOVE) { if (newpa->ifname[0]) { newpa->kif = pfi_kkif_attach(kif, newpa->ifname); pfi_kkif_ref(newpa->kif); kif = NULL; } switch (newpa->addr.type) { case PF_ADDR_DYNIFTL: error = pfi_dynaddr_setup(&newpa->addr, pca->af); break; case PF_ADDR_TABLE: newpa->addr.p.tbl = pfr_attach_table(ruleset, newpa->addr.v.tblname); if (newpa->addr.p.tbl == NULL) error = ENOMEM; break; } if (error) goto DIOCCHANGEADDR_error; } switch (pca->action) { case PF_CHANGE_ADD_HEAD: oldpa = TAILQ_FIRST(&pool->list); break; case PF_CHANGE_ADD_TAIL: oldpa = TAILQ_LAST(&pool->list, pf_kpalist); break; default: oldpa = TAILQ_FIRST(&pool->list); for (int i = 0; oldpa && i < pca->nr; i++) oldpa = TAILQ_NEXT(oldpa, entries); if (oldpa == NULL) ERROUT(EINVAL); } if (pca->action == PF_CHANGE_REMOVE) { TAILQ_REMOVE(&pool->list, oldpa, entries); switch (oldpa->addr.type) { case PF_ADDR_DYNIFTL: pfi_dynaddr_remove(oldpa->addr.p.dyn); break; case PF_ADDR_TABLE: pfr_detach_table(oldpa->addr.p.tbl); break; } if (oldpa->kif) pfi_kkif_unref(oldpa->kif); free(oldpa, M_PFRULE); } else { if (oldpa == NULL) TAILQ_INSERT_TAIL(&pool->list, newpa, entries); else if (pca->action == PF_CHANGE_ADD_HEAD || pca->action == PF_CHANGE_ADD_BEFORE) TAILQ_INSERT_BEFORE(oldpa, newpa, entries); else TAILQ_INSERT_AFTER(&pool->list, oldpa, newpa, entries); } pool->cur = TAILQ_FIRST(&pool->list); pf_addrcpy(&pool->counter, &pool->cur->addr.v.a.addr, pca->af); PF_RULES_WUNLOCK(); break; #undef ERROUT DIOCCHANGEADDR_error: if (newpa != NULL) { if (newpa->kif) pfi_kkif_unref(newpa->kif); free(newpa, M_PFRULE); } PF_RULES_WUNLOCK(); pf_kkif_free(kif); break; } case DIOCGETRULESETS: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; pr->path[sizeof(pr->path) - 1] = '\0'; error = pf_ioctl_get_rulesets(pr); break; } case DIOCGETRULESET: { struct pfioc_ruleset *pr = (struct pfioc_ruleset *)addr; pr->path[sizeof(pr->path) - 1] = '\0'; error = pf_ioctl_get_ruleset(pr); break; } case DIOCRCLRTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; goto fail; } if (strnlen(io->pfrio_table.pfrt_anchor, MAXPATHLEN) == MAXPATHLEN) { error = EINVAL; goto fail; } if (strnlen(io->pfrio_table.pfrt_name, PF_TABLE_NAME_SIZE) == PF_TABLE_NAME_SIZE) { error = EINVAL; goto fail; } PF_RULES_WLOCK(); error = pfr_clr_tables(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); break; } case DIOCRADDTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_table))) { error = ENOMEM; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = mallocarray(io->pfrio_size, sizeof(struct pfr_table), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_add_tables(pfrts, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_PF); break; } case DIOCRDELTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_table))) { error = ENOMEM; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = mallocarray(io->pfrio_size, sizeof(struct pfr_table), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_del_tables(pfrts, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_PF); break; } case DIOCRGETTABLES: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; int n; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; goto fail; } PF_RULES_RLOCK(); n = pfr_table_count(&io->pfrio_table, io->pfrio_flags); if (n < 0) { PF_RULES_RUNLOCK(); error = EINVAL; goto fail; } io->pfrio_size = min(io->pfrio_size, n); totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = mallocarray(io->pfrio_size, sizeof(struct pfr_table), M_PF, M_NOWAIT | M_ZERO); if (pfrts == NULL) { error = ENOMEM; PF_RULES_RUNLOCK(); goto fail; } error = pfr_get_tables(&io->pfrio_table, pfrts, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfrts, io->pfrio_buffer, totlen); free(pfrts, M_PF); break; } case DIOCRGETTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_tstats *pfrtstats; size_t totlen; int n; if (io->pfrio_esize != sizeof(struct pfr_tstats)) { error = ENODEV; goto fail; } PF_TABLE_STATS_LOCK(); PF_RULES_RLOCK(); n = pfr_table_count(&io->pfrio_table, io->pfrio_flags); if (n < 0) { PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); error = EINVAL; goto fail; } io->pfrio_size = min(io->pfrio_size, n); totlen = io->pfrio_size * sizeof(struct pfr_tstats); pfrtstats = mallocarray(io->pfrio_size, sizeof(struct pfr_tstats), M_PF, M_NOWAIT | M_ZERO); if (pfrtstats == NULL) { error = ENOMEM; PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); goto fail; } error = pfr_get_tstats(&io->pfrio_table, pfrtstats, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); if (error == 0) error = copyout(pfrtstats, io->pfrio_buffer, totlen); free(pfrtstats, M_PF); break; } case DIOCRCLRTSTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_table))) { /* We used to count tables and use the minimum required * size, so we didn't fail on overly large requests. * Keep doing so. */ io->pfrio_size = pf_ioctl_maxcount; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = mallocarray(io->pfrio_size, sizeof(struct pfr_table), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_PF); goto fail; } PF_TABLE_STATS_LOCK(); PF_RULES_RLOCK(); error = pfr_clr_tstats(pfrts, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); free(pfrts, M_PF); break; } case DIOCRSETTFLAGS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_table *pfrts; size_t totlen; int n; if (io->pfrio_esize != sizeof(struct pfr_table)) { error = ENODEV; goto fail; } PF_RULES_RLOCK(); n = pfr_table_count(&io->pfrio_table, io->pfrio_flags); if (n < 0) { PF_RULES_RUNLOCK(); error = EINVAL; goto fail; } io->pfrio_size = min(io->pfrio_size, n); PF_RULES_RUNLOCK(); totlen = io->pfrio_size * sizeof(struct pfr_table); pfrts = mallocarray(io->pfrio_size, sizeof(struct pfr_table), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfrts, totlen); if (error) { free(pfrts, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_set_tflags(pfrts, io->pfrio_size, io->pfrio_setflag, io->pfrio_clrflag, &io->pfrio_nchange, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfrts, M_PF); break; } case DIOCRCLRADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; if (io->pfrio_esize != 0) { error = ENODEV; goto fail; } PF_RULES_WLOCK(); error = pfr_clr_addrs(&io->pfrio_table, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); break; } case DIOCRADDADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_WLOCK(); io->pfrio_nadd = 0; error = pfr_add_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nadd, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRDELADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_del_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_ndel, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRSETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen, count; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size2 < 0) { error = EINVAL; goto fail; } count = max(io->pfrio_size, io->pfrio_size2); if (count > pf_ioctl_maxcount || WOULD_OVERFLOW(count, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = count * sizeof(struct pfr_addr); pfras = mallocarray(count, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_set_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_size2, &io->pfrio_nadd, &io->pfrio_ndel, &io->pfrio_nchange, io->pfrio_flags | - PFR_FLAG_USERIOCTL, 0); + PFR_FLAG_START | PFR_FLAG_DONE | PFR_FLAG_USERIOCTL, 0); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRGETADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK | M_ZERO); PF_RULES_RLOCK(); error = pfr_get_addrs(&io->pfrio_table, pfras, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRGETASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_astats *pfrastats; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_astats)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_astats))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_astats); pfrastats = mallocarray(io->pfrio_size, sizeof(struct pfr_astats), M_PF, M_WAITOK | M_ZERO); PF_RULES_RLOCK(); error = pfr_get_astats(&io->pfrio_table, pfrastats, &io->pfrio_size, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfrastats, io->pfrio_buffer, totlen); free(pfrastats, M_PF); break; } case DIOCRCLRASTATS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_clr_astats(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nzero, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error == 0 && io->pfrio_flags & PFR_FLAG_FEEDBACK) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRTSTADDRS: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_RLOCK(); error = pfr_tst_addrs(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nmatch, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); if (error == 0) error = copyout(pfras, io->pfrio_buffer, totlen); free(pfras, M_PF); break; } case DIOCRINADEFINE: { struct pfioc_table *io = (struct pfioc_table *)addr; struct pfr_addr *pfras; size_t totlen; if (io->pfrio_esize != sizeof(struct pfr_addr)) { error = ENODEV; goto fail; } if (io->pfrio_size < 0 || io->pfrio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfrio_size, sizeof(struct pfr_addr))) { error = EINVAL; goto fail; } totlen = io->pfrio_size * sizeof(struct pfr_addr); pfras = mallocarray(io->pfrio_size, sizeof(struct pfr_addr), M_PF, M_WAITOK); error = copyin(io->pfrio_buffer, pfras, totlen); if (error) { free(pfras, M_PF); goto fail; } PF_RULES_WLOCK(); error = pfr_ina_define(&io->pfrio_table, pfras, io->pfrio_size, &io->pfrio_nadd, &io->pfrio_naddr, io->pfrio_ticket, io->pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); free(pfras, M_PF); break; } case DIOCOSFPADD: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; PF_RULES_WLOCK(); error = pf_osfp_add(io); PF_RULES_WUNLOCK(); break; } case DIOCOSFPGET: { struct pf_osfp_ioctl *io = (struct pf_osfp_ioctl *)addr; PF_RULES_RLOCK(); error = pf_osfp_get(io); PF_RULES_RUNLOCK(); break; } case DIOCXBEGIN: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioes, *ioe; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } if (io->size < 0 || io->size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->size, sizeof(struct pfioc_trans_e))) { error = EINVAL; goto fail; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = mallocarray(io->size, sizeof(struct pfioc_trans_e), M_PF, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_PF); goto fail; } PF_RULES_WLOCK(); for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { case PF_RULESET_ETH: if ((error = pf_begin_eth(&ioe->ticket, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; } break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EINVAL; goto fail; } if ((error = pf_begin_altq(&ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_begin(&table, &ioe->ticket, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; } break; } default: if ((error = pf_begin_rules(&ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; } break; } } PF_RULES_WUNLOCK(); error = copyout(ioes, io->array, totlen); free(ioes, M_PF); break; } case DIOCXROLLBACK: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe, *ioes; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } if (io->size < 0 || io->size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->size, sizeof(struct pfioc_trans_e))) { error = EINVAL; goto fail; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = mallocarray(io->size, sizeof(struct pfioc_trans_e), M_PF, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_PF); goto fail; } PF_RULES_WLOCK(); for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { case PF_RULESET_ETH: if ((error = pf_rollback_eth(ioe->ticket, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EINVAL; goto fail; } if ((error = pf_rollback_altq(ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_rollback(&table, ioe->ticket, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; } default: if ((error = pf_rollback_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; } } PF_RULES_WUNLOCK(); free(ioes, M_PF); break; } case DIOCXCOMMIT: { struct pfioc_trans *io = (struct pfioc_trans *)addr; struct pfioc_trans_e *ioe, *ioes; struct pf_kruleset *rs; struct pf_keth_ruleset *ers; size_t totlen; int i; if (io->esize != sizeof(*ioe)) { error = ENODEV; goto fail; } if (io->size < 0 || io->size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->size, sizeof(struct pfioc_trans_e))) { error = EINVAL; goto fail; } totlen = sizeof(struct pfioc_trans_e) * io->size; ioes = mallocarray(io->size, sizeof(struct pfioc_trans_e), M_PF, M_WAITOK); error = copyin(io->array, ioes, totlen); if (error) { free(ioes, M_PF); goto fail; } PF_RULES_WLOCK(); /* First makes sure everything will succeed. */ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { ioe->anchor[sizeof(ioe->anchor) - 1] = '\0'; switch (ioe->rs_num) { case PF_RULESET_ETH: ers = pf_find_keth_ruleset(ioe->anchor); if (ers == NULL || ioe->ticket == 0 || ioe->ticket != ers->inactive.ticket) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EINVAL; goto fail; } break; #ifdef ALTQ case PF_RULESET_ALTQ: if (ioe->anchor[0]) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EINVAL; goto fail; } if (!V_altqs_inactive_open || ioe->ticket != V_ticket_altqs_inactive) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EBUSY; goto fail; } break; #endif /* ALTQ */ case PF_RULESET_TABLE: rs = pf_find_kruleset(ioe->anchor); if (rs == NULL || !rs->topen || ioe->ticket != rs->tticket) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EBUSY; goto fail; } break; default: if (ioe->rs_num < 0 || ioe->rs_num >= PF_RULESET_MAX) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EINVAL; goto fail; } rs = pf_find_kruleset(ioe->anchor); if (rs == NULL || !rs->rules[ioe->rs_num].inactive.open || rs->rules[ioe->rs_num].inactive.ticket != ioe->ticket) { PF_RULES_WUNLOCK(); free(ioes, M_PF); error = EBUSY; goto fail; } break; } } /* Now do the commit - no errors should happen here. */ for (i = 0, ioe = ioes; i < io->size; i++, ioe++) { switch (ioe->rs_num) { case PF_RULESET_ETH: if ((error = pf_commit_eth(ioe->ticket, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; #ifdef ALTQ case PF_RULESET_ALTQ: if ((error = pf_commit_altq(ioe->ticket))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; #endif /* ALTQ */ case PF_RULESET_TABLE: { struct pfr_table table; bzero(&table, sizeof(table)); (void)strlcpy(table.pfrt_anchor, ioe->anchor, sizeof(table.pfrt_anchor)); if ((error = pfr_ina_commit(&table, ioe->ticket, NULL, NULL, 0))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; } default: if ((error = pf_commit_rules(ioe->ticket, ioe->rs_num, ioe->anchor))) { PF_RULES_WUNLOCK(); free(ioes, M_PF); goto fail; /* really bad */ } break; } } PF_RULES_WUNLOCK(); /* Only hook into EtherNet taffic if we've got rules for it. */ if (! TAILQ_EMPTY(V_pf_keth->active.rules)) hook_pf_eth(); else dehook_pf_eth(); free(ioes, M_PF); break; } case DIOCGETSRCNODES: { struct pfioc_src_nodes *psn = (struct pfioc_src_nodes *)addr; struct pf_srchash *sh; struct pf_ksrc_node *n; struct pf_src_node *p, *pstore; uint32_t i, nr = 0; for (i = 0, sh = V_pf_srchash; i <= V_pf_srchashmask; i++, sh++) { PF_HASHROW_LOCK(sh); LIST_FOREACH(n, &sh->nodes, entry) nr++; PF_HASHROW_UNLOCK(sh); } psn->psn_len = min(psn->psn_len, sizeof(struct pf_src_node) * nr); if (psn->psn_len == 0) { psn->psn_len = sizeof(struct pf_src_node) * nr; goto fail; } nr = 0; p = pstore = malloc(psn->psn_len, M_PF, M_WAITOK | M_ZERO); for (i = 0, sh = V_pf_srchash; i <= V_pf_srchashmask; i++, sh++) { PF_HASHROW_LOCK(sh); LIST_FOREACH(n, &sh->nodes, entry) { if ((nr + 1) * sizeof(*p) > (unsigned)psn->psn_len) break; pf_src_node_copy(n, p); p++; nr++; } PF_HASHROW_UNLOCK(sh); } error = copyout(pstore, psn->psn_src_nodes, sizeof(struct pf_src_node) * nr); if (error) { free(pstore, M_PF); goto fail; } psn->psn_len = sizeof(struct pf_src_node) * nr; free(pstore, M_PF); break; } case DIOCCLRSRCNODES: { pf_kill_srcnodes(NULL); break; } case DIOCKILLSRCNODES: pf_kill_srcnodes((struct pfioc_src_node_kill *)addr); break; #ifdef COMPAT_FREEBSD13 case DIOCKEEPCOUNTERS_FREEBSD13: #endif case DIOCKEEPCOUNTERS: error = pf_keepcounters((struct pfioc_nv *)addr); break; case DIOCGETSYNCOOKIES: error = pf_get_syncookies((struct pfioc_nv *)addr); break; case DIOCSETSYNCOOKIES: error = pf_set_syncookies((struct pfioc_nv *)addr); break; case DIOCSETHOSTID: { u_int32_t *hostid = (u_int32_t *)addr; PF_RULES_WLOCK(); if (*hostid == 0) V_pf_status.hostid = arc4random(); else V_pf_status.hostid = *hostid; PF_RULES_WUNLOCK(); break; } case DIOCOSFPFLUSH: PF_RULES_WLOCK(); pf_osfp_flush(); PF_RULES_WUNLOCK(); break; case DIOCIGETIFACES: { struct pfioc_iface *io = (struct pfioc_iface *)addr; struct pfi_kif *ifstore; size_t bufsiz; if (io->pfiio_esize != sizeof(struct pfi_kif)) { error = ENODEV; goto fail; } if (io->pfiio_size < 0 || io->pfiio_size > pf_ioctl_maxcount || WOULD_OVERFLOW(io->pfiio_size, sizeof(struct pfi_kif))) { error = EINVAL; goto fail; } io->pfiio_name[sizeof(io->pfiio_name) - 1] = '\0'; bufsiz = io->pfiio_size * sizeof(struct pfi_kif); ifstore = mallocarray(io->pfiio_size, sizeof(struct pfi_kif), M_PF, M_WAITOK | M_ZERO); PF_RULES_RLOCK(); pfi_get_ifaces(io->pfiio_name, ifstore, &io->pfiio_size); PF_RULES_RUNLOCK(); error = copyout(ifstore, io->pfiio_buffer, bufsiz); free(ifstore, M_PF); break; } case DIOCSETIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; io->pfiio_name[sizeof(io->pfiio_name) - 1] = '\0'; PF_RULES_WLOCK(); error = pfi_set_flags(io->pfiio_name, io->pfiio_flags); PF_RULES_WUNLOCK(); break; } case DIOCCLRIFFLAG: { struct pfioc_iface *io = (struct pfioc_iface *)addr; io->pfiio_name[sizeof(io->pfiio_name) - 1] = '\0'; PF_RULES_WLOCK(); error = pfi_clear_flags(io->pfiio_name, io->pfiio_flags); PF_RULES_WUNLOCK(); break; } case DIOCSETREASS: { u_int32_t *reass = (u_int32_t *)addr; V_pf_status.reass = *reass & (PF_REASS_ENABLED|PF_REASS_NODF); /* Removal of DF flag without reassembly enabled is not a * valid combination. Disable reassembly in such case. */ if (!(V_pf_status.reass & PF_REASS_ENABLED)) V_pf_status.reass = 0; break; } default: error = ENODEV; break; } fail: CURVNET_RESTORE(); #undef ERROUT_IOCTL return (error); } void pfsync_state_export(union pfsync_state_union *sp, struct pf_kstate *st, int msg_version) { const char *tagname; bzero(sp, sizeof(union pfsync_state_union)); /* copy from state key */ sp->pfs_1301.key[PF_SK_WIRE].addr[0] = st->key[PF_SK_WIRE]->addr[0]; sp->pfs_1301.key[PF_SK_WIRE].addr[1] = st->key[PF_SK_WIRE]->addr[1]; sp->pfs_1301.key[PF_SK_WIRE].port[0] = st->key[PF_SK_WIRE]->port[0]; sp->pfs_1301.key[PF_SK_WIRE].port[1] = st->key[PF_SK_WIRE]->port[1]; sp->pfs_1301.key[PF_SK_STACK].addr[0] = st->key[PF_SK_STACK]->addr[0]; sp->pfs_1301.key[PF_SK_STACK].addr[1] = st->key[PF_SK_STACK]->addr[1]; sp->pfs_1301.key[PF_SK_STACK].port[0] = st->key[PF_SK_STACK]->port[0]; sp->pfs_1301.key[PF_SK_STACK].port[1] = st->key[PF_SK_STACK]->port[1]; /* copy from state */ strlcpy(sp->pfs_1301.ifname, st->kif->pfik_name, sizeof(sp->pfs_1301.ifname)); bcopy(&st->act.rt_addr, &sp->pfs_1301.rt_addr, sizeof(sp->pfs_1301.rt_addr)); sp->pfs_1301.creation = htonl(time_uptime - (st->creation / 1000)); sp->pfs_1301.expire = pf_state_expires(st); if (sp->pfs_1301.expire <= time_uptime) sp->pfs_1301.expire = htonl(0); else sp->pfs_1301.expire = htonl(sp->pfs_1301.expire - time_uptime); switch (msg_version) { case PFSYNC_MSG_VERSION_1301: sp->pfs_1301.state_flags = st->state_flags; sp->pfs_1301.direction = st->direction; sp->pfs_1301.log = st->act.log; sp->pfs_1301.timeout = st->timeout; sp->pfs_1301.proto = st->key[PF_SK_WIRE]->proto; sp->pfs_1301.af = st->key[PF_SK_WIRE]->af; /* * XXX Why do we bother pfsyncing source node information if source * nodes are not synced? Showing users that there is source tracking * when there is none seems useless. */ if (st->sns[PF_SN_LIMIT] != NULL) sp->pfs_1301.sync_flags |= PFSYNC_FLAG_SRCNODE; if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE]) sp->pfs_1301.sync_flags |= PFSYNC_FLAG_NATSRCNODE; break; case PFSYNC_MSG_VERSION_1400: sp->pfs_1400.state_flags = htons(st->state_flags); sp->pfs_1400.direction = st->direction; sp->pfs_1400.log = st->act.log; sp->pfs_1400.timeout = st->timeout; sp->pfs_1400.proto = st->key[PF_SK_WIRE]->proto; sp->pfs_1400.af = st->key[PF_SK_WIRE]->af; sp->pfs_1400.qid = htons(st->act.qid); sp->pfs_1400.pqid = htons(st->act.pqid); sp->pfs_1400.dnpipe = htons(st->act.dnpipe); sp->pfs_1400.dnrpipe = htons(st->act.dnrpipe); sp->pfs_1400.rtableid = htonl(st->act.rtableid); sp->pfs_1400.min_ttl = st->act.min_ttl; sp->pfs_1400.set_tos = st->act.set_tos; sp->pfs_1400.max_mss = htons(st->act.max_mss); sp->pfs_1400.set_prio[0] = st->act.set_prio[0]; sp->pfs_1400.set_prio[1] = st->act.set_prio[1]; sp->pfs_1400.rt = st->act.rt; if (st->act.rt_kif) strlcpy(sp->pfs_1400.rt_ifname, st->act.rt_kif->pfik_name, sizeof(sp->pfs_1400.rt_ifname)); /* * XXX Why do we bother pfsyncing source node information if source * nodes are not synced? Showing users that there is source tracking * when there is none seems useless. */ if (st->sns[PF_SN_LIMIT] != NULL) sp->pfs_1400.sync_flags |= PFSYNC_FLAG_SRCNODE; if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE]) sp->pfs_1400.sync_flags |= PFSYNC_FLAG_NATSRCNODE; break; case PFSYNC_MSG_VERSION_1500: sp->pfs_1500.state_flags = htons(st->state_flags); sp->pfs_1500.direction = st->direction; sp->pfs_1500.log = st->act.log; sp->pfs_1500.timeout = st->timeout; sp->pfs_1500.wire_proto = st->key[PF_SK_WIRE]->proto; sp->pfs_1500.wire_af = st->key[PF_SK_WIRE]->af; sp->pfs_1500.stack_proto = st->key[PF_SK_STACK]->proto; sp->pfs_1500.stack_af = st->key[PF_SK_STACK]->af; sp->pfs_1500.qid = htons(st->act.qid); sp->pfs_1500.pqid = htons(st->act.pqid); sp->pfs_1500.dnpipe = htons(st->act.dnpipe); sp->pfs_1500.dnrpipe = htons(st->act.dnrpipe); sp->pfs_1500.rtableid = htonl(st->act.rtableid); sp->pfs_1500.min_ttl = st->act.min_ttl; sp->pfs_1500.set_tos = st->act.set_tos; sp->pfs_1500.max_mss = htons(st->act.max_mss); sp->pfs_1500.set_prio[0] = st->act.set_prio[0]; sp->pfs_1500.set_prio[1] = st->act.set_prio[1]; sp->pfs_1500.rt = st->act.rt; sp->pfs_1500.rt_af = st->act.rt_af; if (st->act.rt_kif) strlcpy(sp->pfs_1500.rt_ifname, st->act.rt_kif->pfik_name, sizeof(sp->pfs_1500.rt_ifname)); strlcpy(sp->pfs_1500.orig_ifname, st->orig_kif->pfik_name, sizeof(sp->pfs_1500.orig_ifname)); if ((tagname = pf_tag2tagname(st->tag)) != NULL) strlcpy(sp->pfs_1500.tagname, tagname, sizeof(sp->pfs_1500.tagname)); break; default: panic("%s: Unsupported pfsync_msg_version %d", __func__, msg_version); } sp->pfs_1301.id = st->id; sp->pfs_1301.creatorid = st->creatorid; pf_state_peer_hton(&st->src, &sp->pfs_1301.src); pf_state_peer_hton(&st->dst, &sp->pfs_1301.dst); if (st->rule == NULL) sp->pfs_1301.rule = htonl(-1); else sp->pfs_1301.rule = htonl(st->rule->nr); if (st->anchor == NULL) sp->pfs_1301.anchor = htonl(-1); else sp->pfs_1301.anchor = htonl(st->anchor->nr); if (st->nat_rule == NULL) sp->pfs_1301.nat_rule = htonl(-1); else sp->pfs_1301.nat_rule = htonl(st->nat_rule->nr); pf_state_counter_hton(st->packets[0], sp->pfs_1301.packets[0]); pf_state_counter_hton(st->packets[1], sp->pfs_1301.packets[1]); pf_state_counter_hton(st->bytes[0], sp->pfs_1301.bytes[0]); pf_state_counter_hton(st->bytes[1], sp->pfs_1301.bytes[1]); } void pf_state_export(struct pf_state_export *sp, struct pf_kstate *st) { bzero(sp, sizeof(*sp)); sp->version = PF_STATE_VERSION; /* copy from state key */ sp->key[PF_SK_WIRE].addr[0] = st->key[PF_SK_WIRE]->addr[0]; sp->key[PF_SK_WIRE].addr[1] = st->key[PF_SK_WIRE]->addr[1]; sp->key[PF_SK_WIRE].port[0] = st->key[PF_SK_WIRE]->port[0]; sp->key[PF_SK_WIRE].port[1] = st->key[PF_SK_WIRE]->port[1]; sp->key[PF_SK_STACK].addr[0] = st->key[PF_SK_STACK]->addr[0]; sp->key[PF_SK_STACK].addr[1] = st->key[PF_SK_STACK]->addr[1]; sp->key[PF_SK_STACK].port[0] = st->key[PF_SK_STACK]->port[0]; sp->key[PF_SK_STACK].port[1] = st->key[PF_SK_STACK]->port[1]; sp->proto = st->key[PF_SK_WIRE]->proto; sp->af = st->key[PF_SK_WIRE]->af; /* copy from state */ strlcpy(sp->ifname, st->kif->pfik_name, sizeof(sp->ifname)); strlcpy(sp->orig_ifname, st->orig_kif->pfik_name, sizeof(sp->orig_ifname)); memcpy(&sp->rt_addr, &st->act.rt_addr, sizeof(sp->rt_addr)); sp->creation = htonl(time_uptime - (st->creation / 1000)); sp->expire = pf_state_expires(st); if (sp->expire <= time_uptime) sp->expire = htonl(0); else sp->expire = htonl(sp->expire - time_uptime); sp->direction = st->direction; sp->log = st->act.log; sp->timeout = st->timeout; /* 8 bits for the old libpfctl, 16 bits for the new libpfctl */ sp->state_flags_compat = st->state_flags; sp->state_flags = htons(st->state_flags); if (st->sns[PF_SN_LIMIT] != NULL) sp->sync_flags |= PFSYNC_FLAG_SRCNODE; if (st->sns[PF_SN_NAT] != NULL || st->sns[PF_SN_ROUTE] != NULL) sp->sync_flags |= PFSYNC_FLAG_NATSRCNODE; sp->id = st->id; sp->creatorid = st->creatorid; pf_state_peer_hton(&st->src, &sp->src); pf_state_peer_hton(&st->dst, &sp->dst); if (st->rule == NULL) sp->rule = htonl(-1); else sp->rule = htonl(st->rule->nr); if (st->anchor == NULL) sp->anchor = htonl(-1); else sp->anchor = htonl(st->anchor->nr); if (st->nat_rule == NULL) sp->nat_rule = htonl(-1); else sp->nat_rule = htonl(st->nat_rule->nr); sp->packets[0] = st->packets[0]; sp->packets[1] = st->packets[1]; sp->bytes[0] = st->bytes[0]; sp->bytes[1] = st->bytes[1]; sp->qid = htons(st->act.qid); sp->pqid = htons(st->act.pqid); sp->dnpipe = htons(st->act.dnpipe); sp->dnrpipe = htons(st->act.dnrpipe); sp->rtableid = htonl(st->act.rtableid); sp->min_ttl = st->act.min_ttl; sp->set_tos = st->act.set_tos; sp->max_mss = htons(st->act.max_mss); sp->rt = st->act.rt; if (st->act.rt_kif) strlcpy(sp->rt_ifname, st->act.rt_kif->pfik_name, sizeof(sp->rt_ifname)); sp->set_prio[0] = st->act.set_prio[0]; sp->set_prio[1] = st->act.set_prio[1]; } static void pf_tbladdr_copyout(struct pf_addr_wrap *aw) { struct pfr_ktable *kt; KASSERT(aw->type == PF_ADDR_TABLE, ("%s: type %u", __func__, aw->type)); kt = aw->p.tbl; if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE) && kt->pfrkt_root != NULL) kt = kt->pfrkt_root; aw->p.tbl = NULL; aw->p.tblcnt = (kt->pfrkt_flags & PFR_TFLAG_ACTIVE) ? kt->pfrkt_cnt : -1; } static int pf_add_status_counters(nvlist_t *nvl, const char *name, counter_u64_t *counters, size_t number, char **names) { nvlist_t *nvc; nvc = nvlist_create(0); if (nvc == NULL) return (ENOMEM); for (int i = 0; i < number; i++) { nvlist_append_number_array(nvc, "counters", counter_u64_fetch(counters[i])); nvlist_append_string_array(nvc, "names", names[i]); nvlist_append_number_array(nvc, "ids", i); } nvlist_add_nvlist(nvl, name, nvc); nvlist_destroy(nvc); return (0); } static int pf_getstatus(struct pfioc_nv *nv) { nvlist_t *nvl = NULL, *nvc = NULL; void *nvlpacked = NULL; int error; struct pf_status s; char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; char *pf_lcounter[KLCNT_MAX+1] = KLCNT_NAMES; char *pf_fcounter[FCNT_MAX+1] = FCNT_NAMES; time_t since; PF_RULES_RLOCK_TRACKER; #define ERROUT(x) ERROUT_FUNCTION(errout, x) PF_RULES_RLOCK(); nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); since = time_second - (time_uptime - V_pf_status.since); nvlist_add_bool(nvl, "running", V_pf_status.running); nvlist_add_number(nvl, "since", since); nvlist_add_number(nvl, "debug", V_pf_status.debug); nvlist_add_number(nvl, "hostid", V_pf_status.hostid); nvlist_add_number(nvl, "states", V_pf_status.states); nvlist_add_number(nvl, "src_nodes", V_pf_status.src_nodes); nvlist_add_number(nvl, "reass", V_pf_status.reass); nvlist_add_bool(nvl, "syncookies_active", V_pf_status.syncookies_active); nvlist_add_number(nvl, "halfopen_states", V_pf_status.states_halfopen); /* counters */ error = pf_add_status_counters(nvl, "counters", V_pf_status.counters, PFRES_MAX, pf_reasons); if (error != 0) ERROUT(error); /* lcounters */ error = pf_add_status_counters(nvl, "lcounters", V_pf_status.lcounters, KLCNT_MAX, pf_lcounter); if (error != 0) ERROUT(error); /* fcounters */ nvc = nvlist_create(0); if (nvc == NULL) ERROUT(ENOMEM); for (int i = 0; i < FCNT_MAX; i++) { nvlist_append_number_array(nvc, "counters", pf_counter_u64_fetch(&V_pf_status.fcounters[i])); nvlist_append_string_array(nvc, "names", pf_fcounter[i]); nvlist_append_number_array(nvc, "ids", i); } nvlist_add_nvlist(nvl, "fcounters", nvc); nvlist_destroy(nvc); nvc = NULL; /* scounters */ error = pf_add_status_counters(nvl, "scounters", V_pf_status.scounters, SCNT_MAX, pf_fcounter); if (error != 0) ERROUT(error); nvlist_add_string(nvl, "ifname", V_pf_status.ifname); nvlist_add_binary(nvl, "chksum", V_pf_status.pf_chksum, PF_MD5_DIGEST_LENGTH); pfi_update_status(V_pf_status.ifname, &s); /* pcounters / bcounters */ for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { nvlist_append_number_array(nvl, "pcounters", s.pcounters[i][j][k]); } nvlist_append_number_array(nvl, "bcounters", s.bcounters[i][j]); } } nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); PF_RULES_RUNLOCK(); error = copyout(nvlpacked, nv->data, nv->len); goto done; #undef ERROUT errout: PF_RULES_RUNLOCK(); done: free(nvlpacked, M_NVLIST); nvlist_destroy(nvc); nvlist_destroy(nvl); return (error); } /* * XXX - Check for version mismatch!!! */ static void pf_clear_all_states(void) { struct epoch_tracker et; struct pf_kstate *s; u_int i; NET_EPOCH_ENTER(et); for (i = 0; i <= V_pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { s->timeout = PFTM_PURGE; /* Don't send out individual delete messages. */ s->state_flags |= PFSTATE_NOSYNC; pf_remove_state(s); goto relock; } PF_HASHROW_UNLOCK(ih); } NET_EPOCH_EXIT(et); } static int pf_clear_tables(void) { struct pfioc_table io; int error; bzero(&io, sizeof(io)); io.pfrio_flags |= PFR_FLAG_ALLRSETS; error = pfr_clr_tables(&io.pfrio_table, &io.pfrio_ndel, io.pfrio_flags); return (error); } static void pf_kill_srcnodes(struct pfioc_src_node_kill *psnk) { struct pf_ksrc_node_list kill; u_int killed; LIST_INIT(&kill); for (int i = 0; i <= V_pf_srchashmask; i++) { struct pf_srchash *sh = &V_pf_srchash[i]; struct pf_ksrc_node *sn, *tmp; PF_HASHROW_LOCK(sh); LIST_FOREACH_SAFE(sn, &sh->nodes, entry, tmp) if (psnk == NULL || (pf_match_addr(psnk->psnk_src.neg, &psnk->psnk_src.addr.v.a.addr, &psnk->psnk_src.addr.v.a.mask, &sn->addr, sn->af) && pf_match_addr(psnk->psnk_dst.neg, &psnk->psnk_dst.addr.v.a.addr, &psnk->psnk_dst.addr.v.a.mask, &sn->raddr, sn->af))) { pf_unlink_src_node(sn); LIST_INSERT_HEAD(&kill, sn, entry); sn->expire = 1; } PF_HASHROW_UNLOCK(sh); } for (int i = 0; i <= V_pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_kstate *s; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { for(pf_sn_types_t sn_type=0; sn_typesns[sn_type] && s->sns[sn_type]->expire == 1) { s->sns[sn_type] = NULL; } } } PF_HASHROW_UNLOCK(ih); } killed = pf_free_src_nodes(&kill); if (psnk != NULL) psnk->psnk_killed = killed; } static int pf_keepcounters(struct pfioc_nv *nv) { nvlist_t *nvl = NULL; void *nvlpacked = NULL; int error = 0; #define ERROUT(x) ERROUT_FUNCTION(on_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); if (! nvlist_exists_bool(nvl, "keep_counters")) ERROUT(EBADMSG); V_pf_status.keep_counters = nvlist_get_bool(nvl, "keep_counters"); on_error: nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); return (error); } unsigned int pf_clear_states(const struct pf_kstate_kill *kill) { struct pf_state_key_cmp match_key; struct pf_kstate *s; struct pfi_kkif *kif; int idx; unsigned int killed = 0, dir; NET_EPOCH_ASSERT(); for (unsigned int i = 0; i <= V_pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock_DIOCCLRSTATES: PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { /* For floating states look at the original kif. */ kif = s->kif == V_pfi_all ? s->orig_kif : s->kif; if (kill->psk_ifname[0] && strcmp(kill->psk_ifname, kif->pfik_name)) continue; if (kill->psk_kill_match) { bzero(&match_key, sizeof(match_key)); if (s->direction == PF_OUT) { dir = PF_IN; idx = PF_SK_STACK; } else { dir = PF_OUT; idx = PF_SK_WIRE; } match_key.af = s->key[idx]->af; match_key.proto = s->key[idx]->proto; pf_addrcpy(&match_key.addr[0], &s->key[idx]->addr[1], match_key.af); match_key.port[0] = s->key[idx]->port[1]; pf_addrcpy(&match_key.addr[1], &s->key[idx]->addr[0], match_key.af); match_key.port[1] = s->key[idx]->port[0]; } /* * Don't send out individual * delete messages. */ s->state_flags |= PFSTATE_NOSYNC; pf_remove_state(s); killed++; if (kill->psk_kill_match) killed += pf_kill_matching_state(&match_key, dir); goto relock_DIOCCLRSTATES; } PF_HASHROW_UNLOCK(ih); } if (V_pfsync_clear_states_ptr != NULL) V_pfsync_clear_states_ptr(V_pf_status.hostid, kill->psk_ifname); return (killed); } void pf_killstates(struct pf_kstate_kill *kill, unsigned int *killed) { struct pf_kstate *s; NET_EPOCH_ASSERT(); if (kill->psk_pfcmp.id) { if (kill->psk_pfcmp.creatorid == 0) kill->psk_pfcmp.creatorid = V_pf_status.hostid; if ((s = pf_find_state_byid(kill->psk_pfcmp.id, kill->psk_pfcmp.creatorid))) { pf_remove_state(s); *killed = 1; } return; } for (unsigned int i = 0; i <= V_pf_hashmask; i++) *killed += pf_killstates_row(kill, &V_pf_idhash[i]); } static int pf_killstates_nv(struct pfioc_nv *nv) { struct pf_kstate_kill kill; struct epoch_tracker et; nvlist_t *nvl = NULL; void *nvlpacked = NULL; int error = 0; unsigned int killed = 0; #define ERROUT(x) ERROUT_FUNCTION(on_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); error = pf_nvstate_kill_to_kstate_kill(nvl, &kill); if (error) ERROUT(error); NET_EPOCH_ENTER(et); pf_killstates(&kill, &killed); NET_EPOCH_EXIT(et); free(nvlpacked, M_NVLIST); nvlpacked = NULL; nvlist_destroy(nvl); nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); nvlist_add_number(nvl, "killed", killed); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); on_error: nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); return (error); } static int pf_clearstates_nv(struct pfioc_nv *nv) { struct pf_kstate_kill kill; struct epoch_tracker et; nvlist_t *nvl = NULL; void *nvlpacked = NULL; int error = 0; unsigned int killed; #define ERROUT(x) ERROUT_FUNCTION(on_error, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); error = pf_nvstate_kill_to_kstate_kill(nvl, &kill); if (error) ERROUT(error); NET_EPOCH_ENTER(et); killed = pf_clear_states(&kill); NET_EPOCH_EXIT(et); free(nvlpacked, M_NVLIST); nvlpacked = NULL; nvlist_destroy(nvl); nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); nvlist_add_number(nvl, "killed", killed); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); #undef ERROUT on_error: nvlist_destroy(nvl); free(nvlpacked, M_NVLIST); return (error); } static int pf_getstate(struct pfioc_nv *nv) { nvlist_t *nvl = NULL, *nvls; void *nvlpacked = NULL; struct pf_kstate *s = NULL; int error = 0; uint64_t id, creatorid; #define ERROUT(x) ERROUT_FUNCTION(errout, x) if (nv->len > pf_ioctl_maxcount) ERROUT(ENOMEM); nvlpacked = malloc(nv->len, M_NVLIST, M_WAITOK); error = copyin(nv->data, nvlpacked, nv->len); if (error) ERROUT(error); nvl = nvlist_unpack(nvlpacked, nv->len, 0); if (nvl == NULL) ERROUT(EBADMSG); PFNV_CHK(pf_nvuint64(nvl, "id", &id)); PFNV_CHK(pf_nvuint64(nvl, "creatorid", &creatorid)); s = pf_find_state_byid(id, creatorid); if (s == NULL) ERROUT(ENOENT); free(nvlpacked, M_NVLIST); nvlpacked = NULL; nvlist_destroy(nvl); nvl = nvlist_create(0); if (nvl == NULL) ERROUT(ENOMEM); nvls = pf_state_to_nvstate(s); if (nvls == NULL) ERROUT(ENOMEM); nvlist_add_nvlist(nvl, "state", nvls); nvlist_destroy(nvls); nvlpacked = nvlist_pack(nvl, &nv->len); if (nvlpacked == NULL) ERROUT(ENOMEM); if (nv->size == 0) ERROUT(0); else if (nv->size < nv->len) ERROUT(ENOSPC); error = copyout(nvlpacked, nv->data, nv->len); #undef ERROUT errout: if (s != NULL) PF_STATE_UNLOCK(s); free(nvlpacked, M_NVLIST); nvlist_destroy(nvl); return (error); } /* * XXX - Check for version mismatch!!! */ /* * Duplicate pfctl -Fa operation to get rid of as much as we can. */ static int shutdown_pf(void) { int error = 0; u_int32_t t[5]; char nn = '\0'; struct pf_kanchor *anchor, *tmp_anchor; struct pf_keth_anchor *eth_anchor, *tmp_eth_anchor; int rs_num; do { /* Unlink rules of all user defined anchors */ RB_FOREACH_SAFE(anchor, pf_kanchor_global, &V_pf_anchors, tmp_anchor) { for (rs_num = 0; rs_num < PF_RULESET_MAX; ++rs_num) { if ((error = pf_begin_rules(&t[rs_num], rs_num, anchor->path)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: " "anchor.path=%s rs_num=%d", __func__, anchor->path, rs_num); goto error; /* XXX: rollback? */ } } for (rs_num = 0; rs_num < PF_RULESET_MAX; ++rs_num) { error = pf_commit_rules(t[rs_num], rs_num, anchor->path); MPASS(error == 0); } } /* Unlink rules of all user defined ether anchors */ RB_FOREACH_SAFE(eth_anchor, pf_keth_anchor_global, &V_pf_keth_anchors, tmp_eth_anchor) { if ((error = pf_begin_eth(&t[0], eth_anchor->path)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: eth " "anchor.path=%s", __func__, eth_anchor->path); goto error; } error = pf_commit_eth(t[0], eth_anchor->path); MPASS(error == 0); } if ((error = pf_begin_rules(&t[0], PF_RULESET_SCRUB, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: SCRUB", __func__); break; } if ((error = pf_begin_rules(&t[1], PF_RULESET_FILTER, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: FILTER", __func__); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[2], PF_RULESET_NAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: NAT", __func__); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[3], PF_RULESET_BINAT, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: BINAT", __func__); break; /* XXX: rollback? */ } if ((error = pf_begin_rules(&t[4], PF_RULESET_RDR, &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: RDR", __func__); break; /* XXX: rollback? */ } error = pf_commit_rules(t[0], PF_RULESET_SCRUB, &nn); MPASS(error == 0); error = pf_commit_rules(t[1], PF_RULESET_FILTER, &nn); MPASS(error == 0); error = pf_commit_rules(t[2], PF_RULESET_NAT, &nn); MPASS(error == 0); error = pf_commit_rules(t[3], PF_RULESET_BINAT, &nn); MPASS(error == 0); error = pf_commit_rules(t[4], PF_RULESET_RDR, &nn); MPASS(error == 0); if ((error = pf_clear_tables()) != 0) break; if ((error = pf_begin_eth(&t[0], &nn)) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: eth", __func__); break; } error = pf_commit_eth(t[0], &nn); MPASS(error == 0); #ifdef ALTQ if ((error = pf_begin_altq(&t[0])) != 0) { DPFPRINTF(PF_DEBUG_MISC, "%s: ALTQ", __func__); break; } pf_commit_altq(t[0]); #endif pf_clear_all_states(); pf_kill_srcnodes(NULL); for (int i = 0; i < PF_RULESET_MAX; i++) { pf_rule_tree_free(pf_main_ruleset.rules[i].active.tree); pf_rule_tree_free(pf_main_ruleset.rules[i].inactive.tree); } /* status does not use malloced mem so no need to cleanup */ /* fingerprints and interfaces have their own cleanup code */ } while(0); error: return (error); } static pfil_return_t pf_check_return(int chk, struct mbuf **m) { switch (chk) { case PF_PASS: if (*m == NULL) return (PFIL_CONSUMED); else return (PFIL_PASS); break; default: if (*m != NULL) { m_freem(*m); *m = NULL; } return (PFIL_DROPPED); } } static pfil_return_t pf_eth_check_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); chk = pf_test_eth(PF_IN, flags, ifp, m, inp); return (pf_check_return(chk, m)); } static pfil_return_t pf_eth_check_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); chk = pf_test_eth(PF_OUT, flags, ifp, m, inp); return (pf_check_return(chk, m)); } #ifdef INET static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); chk = pf_test(AF_INET, PF_IN, flags, ifp, m, inp, NULL); return (pf_check_return(chk, m)); } static pfil_return_t pf_check_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); chk = pf_test(AF_INET, PF_OUT, flags, ifp, m, inp, NULL); return (pf_check_return(chk, m)); } #endif #ifdef INET6 static pfil_return_t pf_check6_in(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); /* * In case of loopback traffic IPv6 uses the real interface in * order to support scoped addresses. In order to support stateful * filtering we have change this to lo0 as it is the case in IPv4. */ chk = pf_test(AF_INET6, PF_IN, flags, (*m)->m_flags & M_LOOP ? V_loif : ifp, m, inp, NULL); return (pf_check_return(chk, m)); } static pfil_return_t pf_check6_out(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { int chk; CURVNET_ASSERT_SET(); chk = pf_test(AF_INET6, PF_OUT, flags, ifp, m, inp, NULL); return (pf_check_return(chk, m)); } #endif /* INET6 */ VNET_DEFINE_STATIC(pfil_hook_t, pf_eth_in_hook); VNET_DEFINE_STATIC(pfil_hook_t, pf_eth_out_hook); #define V_pf_eth_in_hook VNET(pf_eth_in_hook) #define V_pf_eth_out_hook VNET(pf_eth_out_hook) #ifdef INET VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_in_hook); VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_out_hook); #define V_pf_ip4_in_hook VNET(pf_ip4_in_hook) #define V_pf_ip4_out_hook VNET(pf_ip4_out_hook) #endif #ifdef INET6 VNET_DEFINE_STATIC(pfil_hook_t, pf_ip6_in_hook); VNET_DEFINE_STATIC(pfil_hook_t, pf_ip6_out_hook); #define V_pf_ip6_in_hook VNET(pf_ip6_in_hook) #define V_pf_ip6_out_hook VNET(pf_ip6_out_hook) #endif static void hook_pf_eth(void) { struct pfil_hook_args pha = { .pa_version = PFIL_VERSION, .pa_modname = "pf", .pa_type = PFIL_TYPE_ETHERNET, }; struct pfil_link_args pla = { .pa_version = PFIL_VERSION, }; int ret __diagused; if (atomic_load_bool(&V_pf_pfil_eth_hooked)) return; pha.pa_mbuf_chk = pf_eth_check_in; pha.pa_flags = PFIL_IN; pha.pa_rulname = "eth-in"; V_pf_eth_in_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_link_pfil_head; pla.pa_hook = V_pf_eth_in_hook; ret = pfil_link(&pla); MPASS(ret == 0); pha.pa_mbuf_chk = pf_eth_check_out; pha.pa_flags = PFIL_OUT; pha.pa_rulname = "eth-out"; V_pf_eth_out_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_link_pfil_head; pla.pa_hook = V_pf_eth_out_hook; ret = pfil_link(&pla); MPASS(ret == 0); atomic_store_bool(&V_pf_pfil_eth_hooked, true); } static void hook_pf(void) { struct pfil_hook_args pha = { .pa_version = PFIL_VERSION, .pa_modname = "pf", }; struct pfil_link_args pla = { .pa_version = PFIL_VERSION, }; int ret __diagused; if (atomic_load_bool(&V_pf_pfil_hooked)) return; #ifdef INET pha.pa_type = PFIL_TYPE_IP4; pha.pa_mbuf_chk = pf_check_in; pha.pa_flags = PFIL_IN; pha.pa_rulname = "default-in"; V_pf_ip4_in_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet_pfil_head; pla.pa_hook = V_pf_ip4_in_hook; ret = pfil_link(&pla); MPASS(ret == 0); pha.pa_mbuf_chk = pf_check_out; pha.pa_flags = PFIL_OUT; pha.pa_rulname = "default-out"; V_pf_ip4_out_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet_pfil_head; pla.pa_hook = V_pf_ip4_out_hook; ret = pfil_link(&pla); MPASS(ret == 0); if (V_pf_filter_local) { pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet_local_pfil_head; pla.pa_hook = V_pf_ip4_out_hook; ret = pfil_link(&pla); MPASS(ret == 0); } #endif #ifdef INET6 pha.pa_type = PFIL_TYPE_IP6; pha.pa_mbuf_chk = pf_check6_in; pha.pa_flags = PFIL_IN; pha.pa_rulname = "default-in6"; V_pf_ip6_in_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet6_pfil_head; pla.pa_hook = V_pf_ip6_in_hook; ret = pfil_link(&pla); MPASS(ret == 0); pha.pa_mbuf_chk = pf_check6_out; pha.pa_rulname = "default-out6"; pha.pa_flags = PFIL_OUT; V_pf_ip6_out_hook = pfil_add_hook(&pha); pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet6_pfil_head; pla.pa_hook = V_pf_ip6_out_hook; ret = pfil_link(&pla); MPASS(ret == 0); if (V_pf_filter_local) { pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; pla.pa_head = V_inet6_local_pfil_head; pla.pa_hook = V_pf_ip6_out_hook; ret = pfil_link(&pla); MPASS(ret == 0); } #endif atomic_store_bool(&V_pf_pfil_hooked, true); } static void dehook_pf_eth(void) { if (!atomic_load_bool(&V_pf_pfil_eth_hooked)) return; pfil_remove_hook(V_pf_eth_in_hook); pfil_remove_hook(V_pf_eth_out_hook); atomic_store_bool(&V_pf_pfil_eth_hooked, false); } static void dehook_pf(void) { if (!atomic_load_bool(&V_pf_pfil_hooked)) return; #ifdef INET pfil_remove_hook(V_pf_ip4_in_hook); pfil_remove_hook(V_pf_ip4_out_hook); #endif #ifdef INET6 pfil_remove_hook(V_pf_ip6_in_hook); pfil_remove_hook(V_pf_ip6_out_hook); #endif atomic_store_bool(&V_pf_pfil_hooked, false); } static void pf_load_vnet(void) { V_pf_tag_z = uma_zcreate("pf tags", sizeof(struct pf_tagname), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); rm_init_flags(&V_pf_rules_lock, "pf rulesets", RM_RECURSE); rm_init_flags(&V_pf_tags_lock, "pf tags and queues", RM_RECURSE); sx_init(&V_pf_ioctl_lock, "pf ioctl"); pf_init_tagset(&V_pf_tags, &pf_rule_tag_hashsize, PF_RULE_TAG_HASH_SIZE_DEFAULT); #ifdef ALTQ pf_init_tagset(&V_pf_qids, &pf_queue_tag_hashsize, PF_QUEUE_TAG_HASH_SIZE_DEFAULT); #endif V_pf_keth = &V_pf_main_keth_anchor.ruleset; pfattach_vnet(); V_pf_vnet_active = 1; } static int pf_load(void) { int error; sx_init(&pf_end_lock, "pf end thread"); pf_mtag_initialize(); pf_dev = make_dev(&pf_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, PF_NAME); if (pf_dev == NULL) return (ENOMEM); pf_end_threads = 0; error = kproc_create(pf_purge_thread, NULL, &pf_purge_proc, 0, 0, "pf purge"); if (error != 0) return (error); pfi_initialize(); return (0); } static void pf_unload_vnet(void) { int ret __diagused; V_pf_vnet_active = 0; V_pf_status.running = 0; dehook_pf(); dehook_pf_eth(); PF_RULES_WLOCK(); pf_syncookies_cleanup(); shutdown_pf(); PF_RULES_WUNLOCK(); ret = swi_remove(V_pf_swi_cookie); MPASS(ret == 0); ret = intr_event_destroy(V_pf_swi_ie); MPASS(ret == 0); pf_unload_vnet_purge(); pf_normalize_cleanup(); PF_RULES_WLOCK(); pfi_cleanup_vnet(); PF_RULES_WUNLOCK(); pfr_cleanup(); pf_osfp_flush(); pf_cleanup(); if (IS_DEFAULT_VNET(curvnet)) pf_mtag_cleanup(); pf_cleanup_tagset(&V_pf_tags); #ifdef ALTQ pf_cleanup_tagset(&V_pf_qids); #endif uma_zdestroy(V_pf_tag_z); #ifdef PF_WANT_32_TO_64_COUNTER PF_RULES_WLOCK(); LIST_REMOVE(V_pf_kifmarker, pfik_allkiflist); MPASS(LIST_EMPTY(&V_pf_allkiflist)); MPASS(V_pf_allkifcount == 0); LIST_REMOVE(&V_pf_default_rule, allrulelist); V_pf_allrulecount--; LIST_REMOVE(V_pf_rulemarker, allrulelist); MPASS(LIST_EMPTY(&V_pf_allrulelist)); MPASS(V_pf_allrulecount == 0); PF_RULES_WUNLOCK(); free(V_pf_kifmarker, PFI_MTYPE); free(V_pf_rulemarker, M_PFRULE); #endif /* Free counters last as we updated them during shutdown. */ pf_counter_u64_deinit(&V_pf_default_rule.evaluations); for (int i = 0; i < 2; i++) { pf_counter_u64_deinit(&V_pf_default_rule.packets[i]); pf_counter_u64_deinit(&V_pf_default_rule.bytes[i]); } counter_u64_free(V_pf_default_rule.states_cur); counter_u64_free(V_pf_default_rule.states_tot); for (pf_sn_types_t sn_type=0; sn_type * 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 "opt_inet.h" #include "opt_inet6.h" #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); static bool nlattr_add_pf_threshold(struct nl_writer *, int, struct pf_kthreshold *); 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(af), .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, PF_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_add_u8(nw, PF_STK_AF, key->af); nlattr_add_u16(nw, PF_STK_PROTO, key->proto); 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; PF_STATE_LOCK_ASSERT(s); 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->act.rt_addr, s->act.rt_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->sns[PF_SN_LIMIT] != NULL) sync_flags |= PFSYNC_FLAG_SRCNODE; if (s->sns[PF_SN_NAT] != NULL || s->sns[PF_SN_ROUTE]) 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 ? s->rule->nr : -1); nlattr_add_u32(nw, PF_ST_ANCHOR, s->anchor ? s->anchor->nr : -1); nlattr_add_u32(nw, PF_ST_NAT_RULE, s->nat_rule ? s->nat_rule->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->act.rt); if (s->act.rt_kif != NULL) nlattr_add_string(nw, PF_ST_RT_IFNAME, s->act.rt_kif->pfik_name); uint8_t src_node_flags = 0; if (s->sns[PF_SN_LIMIT] != NULL) { src_node_flags |= PFSTATE_SRC_NODE_LIMIT; if (s->sns[PF_SN_LIMIT]->rule == &V_pf_default_rule) src_node_flags |= PFSTATE_SRC_NODE_LIMIT_GLOBAL; } if (s->sns[PF_SN_NAT] != NULL) src_node_flags |= PFSTATE_SRC_NODE_NAT; if (s->sns[PF_SN_ROUTE] != NULL) src_node_flags |= PFSTATE_SRC_NODE_ROUTE; nlattr_add_u8(nw, PF_ST_SRC_NODE_FLAGS, src_node_flags); nlattr_add_u8(nw, PF_ST_RT_AF, s->act.rt_af); 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 <= V_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; int ret; s = pf_find_state_byid(attrs->id, attrs->creatorid); if (s == NULL) return (ENOENT); ret = dump_state(nlp, hdr, s, npt); PF_STATE_UNLOCK(s); return (ret); } 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 < V_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); 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); nlattr_add_u32(nw, PF_AT_DYNCNT, a->p.dyncnt); } else if (a->type == PF_ADDR_TABLE) { nlattr_add_string(nw, PF_AT_TABLENAME, a->v.tblname); nlattr_add_u32(nw, PF_AT_TBLCNT, a->p.tblcnt); } 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) { struct pf_addr_wrap aw = {0}; int off = nlattr_add_nested(nw, attrtype); bcopy(&(r->addr), &aw, sizeof(struct pf_addr_wrap)); pf_addr_copyout(&aw); nlattr_add_addr_wrap(nw, PF_RAT_ADDR, &aw); 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.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_kthreshold, _field) static const struct nlattr_parser nla_p_threshold[] = { { .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = nlattr_get_uint32 }, { .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = nlattr_get_uint32 }, }; NL_DECLARE_ATTR_PARSER(threshold_parser, nla_p_threshold); #undef _OUT #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_RDR, .off = _OUT(rdr), .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 }, { .type = PF_RT_RCV_IFNAME, .off = _OUT(rcv_ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara }, { .type = PF_RT_MAX_SRC_CONN, .off = _OUT(max_src_conn), .cb = nlattr_get_uint32 }, { .type = PF_RT_RPOOL_NAT, .off = _OUT(nat), .arg = &pool_parser, .cb = nlattr_get_nested }, { .type = PF_RT_NAF, .off = _OUT(naf), .cb = nlattr_get_uint8 }, { .type = PF_RT_RPOOL_RT, .off = _OUT(route), .arg = &pool_parser, .cb = nlattr_get_nested }, { .type = PF_RT_RCV_IFNOT, .off = _OUT(rcvifnot), .cb = nlattr_get_bool }, { .type = PF_RT_PKTRATE, .off = _OUT(pktrate), .arg = &threshold_parser, .cb = nlattr_get_nested }, { .type = PF_RT_MAX_PKT_SIZE, .off = _OUT(max_pkt_size), .cb = nlattr_get_uint16 }, { .type = PF_RT_TYPE_2, .off = _OUT(type), .cb = nlattr_get_uint16 }, { .type = PF_RT_CODE_2, .off = _OUT(code), .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 _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 } }; #undef _OUT NL_DECLARE_PARSER(addrule_parser, struct genlmsghdr, nlf_p_empty, 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 _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 }, }; #undef _OUT NL_DECLARE_PARSER(getrules_parser, struct genlmsghdr, nlf_p_empty, 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 _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 }, }; #undef _OUT NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_empty, 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; u_int64_t src_nodes_total = 0; 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_RDR, &rule->rdr); nlattr_add_pool(nw, PF_RT_RPOOL_NAT, &rule->nat); nlattr_add_pool(nw, PF_RT_RPOOL_RT, &rule->route); 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, rule->max_src_conn); 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_MAX_PKT_SIZE, rule->max_pkt_size); 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_string(nw, PF_RT_RCV_IFNAME, rule->rcv_ifname); nlattr_add_bool(nw, PF_RT_RCV_IFNOT, rule->rcvifnot); 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_NAF, rule->naf); 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_u16(nw, PF_RT_TYPE_2, rule->type); nlattr_add_u16(nw, PF_RT_CODE_2, 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)); for (pf_sn_types_t sn_type=0; sn_typesrc_nodes[sn_type]); nlattr_add_u64(nw, PF_RT_SRC_NODES, src_nodes_total); nlattr_add_u64(nw, PF_RT_SRC_NODES_LIMIT, counter_u64_fetch(rule->src_nodes[PF_SN_LIMIT])); nlattr_add_u64(nw, PF_RT_SRC_NODES_NAT, counter_u64_fetch(rule->src_nodes[PF_SN_NAT])); nlattr_add_u64(nw, PF_RT_SRC_NODES_ROUTE, counter_u64_fetch(rule->src_nodes[PF_SN_ROUTE])); nlattr_add_pf_threshold(nw, PF_RT_PKTRATE, &rule->pktrate); nlattr_add_time_t(nw, PF_RT_EXPTIME, time_second - (time_uptime - rule->exptime)); error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(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 _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 }, }; #undef _OUT NL_DECLARE_PARSER(clear_states_parser, struct genlmsghdr, nlf_p_empty, 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 _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 }, }; #undef _OUT NL_DECLARE_PARSER(set_statusif_parser, struct genlmsghdr, nlf_p_empty, 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 bool nlattr_add_counters(struct nl_writer *nw, int attr, size_t number, char **names, counter_u64_t *counters) { for (int i = 0; i < number; i++) { int off = nlattr_add_nested(nw, attr); nlattr_add_u32(nw, PF_C_ID, i); nlattr_add_string(nw, PF_C_NAME, names[i]); nlattr_add_u64(nw, PF_C_COUNTER, counter_u64_fetch(counters[i])); nlattr_set_len(nw, off); } return (true); } static bool nlattr_add_fcounters(struct nl_writer *nw, int attr, size_t number, char **names, struct pf_counter_u64 *counters) { for (int i = 0; i < number; i++) { int off = nlattr_add_nested(nw, attr); nlattr_add_u32(nw, PF_C_ID, i); nlattr_add_string(nw, PF_C_NAME, names[i]); nlattr_add_u64(nw, PF_C_COUNTER, pf_counter_u64_fetch(&counters[i])); nlattr_set_len(nw, off); } return (true); } static bool nlattr_add_u64_array(struct nl_writer *nw, int attr, size_t number, uint64_t *array) { int off = nlattr_add_nested(nw, attr); for (size_t i = 0; i < number; i++) nlattr_add_u64(nw, 0, array[i]); nlattr_set_len(nw, off); return (true); } static int pf_handle_get_status(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_status s; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; char *pf_reasons[PFRES_MAX+1] = PFRES_NAMES; char *pf_lcounter[KLCNT_MAX+1] = KLCNT_NAMES; char *pf_fcounter[FCNT_MAX+1] = FCNT_NAMES; time_t since; int error; PF_RULES_RLOCK_TRACKER; if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GET_STATUS; ghdr_new->version = 0; ghdr_new->reserved = 0; since = time_second - (time_uptime - V_pf_status.since); PF_RULES_RLOCK(); nlattr_add_string(nw, PF_GS_IFNAME, V_pf_status.ifname); nlattr_add_bool(nw, PF_GS_RUNNING, V_pf_status.running); nlattr_add_u32(nw, PF_GS_SINCE, since); nlattr_add_u32(nw, PF_GS_DEBUG, V_pf_status.debug); nlattr_add_u32(nw, PF_GS_HOSTID, ntohl(V_pf_status.hostid)); nlattr_add_u32(nw, PF_GS_STATES, V_pf_status.states); nlattr_add_u32(nw, PF_GS_SRC_NODES, V_pf_status.src_nodes); nlattr_add_u32(nw, PF_GS_REASSEMBLE, V_pf_status.reass); nlattr_add_u32(nw, PF_GS_SYNCOOKIES_ACTIVE, V_pf_status.syncookies_active); nlattr_add_counters(nw, PF_GS_COUNTERS, PFRES_MAX, pf_reasons, V_pf_status.counters); nlattr_add_counters(nw, PF_GS_LCOUNTERS, KLCNT_MAX, pf_lcounter, V_pf_status.lcounters); nlattr_add_fcounters(nw, PF_GS_FCOUNTERS, FCNT_MAX, pf_fcounter, V_pf_status.fcounters); nlattr_add_counters(nw, PF_GS_SCOUNTERS, SCNT_MAX, pf_fcounter, V_pf_status.scounters); nlattr_add_counters(nw, PF_GS_NCOUNTERS, NCNT_MAX, pf_fcounter, V_pf_status.ncounters); nlattr_add_u64(nw, PF_GS_FRAGMENTS, pf_normalize_get_frag_count()); pfi_update_status(V_pf_status.ifname, &s); nlattr_add_u64_array(nw, PF_GS_BCOUNTERS, 2 * 2, (uint64_t *)s.bcounters); nlattr_add_u64_array(nw, PF_GS_PCOUNTERS, 2 * 2 * 2, (uint64_t *)s.pcounters); nlattr_add(nw, PF_GS_CHKSUM, PF_MD5_DIGEST_LENGTH, V_pf_status.pf_chksum); PF_RULES_RUNLOCK(); if (!nlmsg_end(nw)) { error = ENOMEM; goto out; } return (0); out: nlmsg_abort(nw); return (error); } static int pf_handle_clear_status(struct nlmsghdr *hdr, struct nl_pstate *npt) { pf_ioctl_clear_status(); return (0); } #define _OUT(_field) offsetof(struct pfioc_natlook, _field) static const struct nlattr_parser nla_p_natlook[] = { { .type = PF_NL_AF, .off = _OUT(af), .cb = nlattr_get_uint8 }, { .type = PF_NL_DIRECTION, .off = _OUT(direction), .cb = nlattr_get_uint8 }, { .type = PF_NL_PROTO, .off = _OUT(proto), .cb = nlattr_get_uint8 }, { .type = PF_NL_SRC_ADDR, .off = _OUT(saddr), .cb = nlattr_get_in6_addr }, { .type = PF_NL_DST_ADDR, .off = _OUT(daddr), .cb = nlattr_get_in6_addr }, { .type = PF_NL_SRC_PORT, .off = _OUT(sport), .cb = nlattr_get_uint16 }, { .type = PF_NL_DST_PORT, .off = _OUT(dport), .cb = nlattr_get_uint16 }, }; #undef _OUT NL_DECLARE_PARSER(natlook_parser, struct genlmsghdr, nlf_p_empty, nla_p_natlook); static int pf_handle_natlook(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_natlook attrs = {}; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &natlook_parser, npt, &attrs); if (error != 0) return (error); error = pf_ioctl_natlook(&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_NATLOOK; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_in6_addr(nw, PF_NL_SRC_ADDR, &attrs.rsaddr.v6); nlattr_add_in6_addr(nw, PF_NL_DST_ADDR, &attrs.rdaddr.v6); nlattr_add_u16(nw, PF_NL_SRC_PORT, attrs.rsport); nlattr_add_u16(nw, PF_NL_DST_PORT, attrs.rdport); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } struct pf_nl_set_debug { uint32_t level; }; #define _OUT(_field) offsetof(struct pf_nl_set_debug, _field) static const struct nlattr_parser nla_p_set_debug[] = { { .type = PF_SD_LEVEL, .off = _OUT(level), .cb = nlattr_get_uint32 }, }; #undef _OUT NL_DECLARE_PARSER(set_debug_parser, struct genlmsghdr, nlf_p_empty, nla_p_set_debug); static int pf_handle_set_debug(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_set_debug attrs = {}; int error; error = nl_parse_nlmsg(hdr, &set_debug_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); V_pf_status.debug = attrs.level; PF_RULES_WUNLOCK(); return (0); } struct pf_nl_set_timeout { uint32_t timeout; uint32_t seconds; }; #define _OUT(_field) offsetof(struct pf_nl_set_timeout, _field) static const struct nlattr_parser nla_p_set_timeout[] = { { .type = PF_TO_TIMEOUT, .off = _OUT(timeout), .cb = nlattr_get_uint32 }, { .type = PF_TO_SECONDS, .off = _OUT(seconds), .cb = nlattr_get_uint32 }, }; #undef _OUT NL_DECLARE_PARSER(set_timeout_parser, struct genlmsghdr, nlf_p_empty, nla_p_set_timeout); static int pf_handle_set_timeout(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_set_timeout attrs = {}; int error; error = nl_parse_nlmsg(hdr, &set_timeout_parser, npt, &attrs); if (error != 0) return (error); return (pf_ioctl_set_timeout(attrs.timeout, attrs.seconds, NULL)); } static int pf_handle_get_timeout(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_set_timeout attrs = {}; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &set_timeout_parser, npt, &attrs); if (error != 0) return (error); error = pf_ioctl_get_timeout(attrs.timeout, &attrs.seconds); 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_GET_TIMEOUT; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_TO_SECONDS, attrs.seconds); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } struct pf_nl_set_limit { uint32_t index; uint32_t limit; }; #define _OUT(_field) offsetof(struct pf_nl_set_limit, _field) static const struct nlattr_parser nla_p_set_limit[] = { { .type = PF_LI_INDEX, .off = _OUT(index), .cb = nlattr_get_uint32 }, { .type = PF_LI_LIMIT, .off = _OUT(limit), .cb = nlattr_get_uint32 }, }; #undef _OUT NL_DECLARE_PARSER(set_limit_parser, struct genlmsghdr, nlf_p_empty, nla_p_set_limit); static int pf_handle_set_limit(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_set_limit attrs = {}; int error; error = nl_parse_nlmsg(hdr, &set_limit_parser, npt, &attrs); if (error != 0) return (error); return (pf_ioctl_set_limit(attrs.index, attrs.limit, NULL)); } static int pf_handle_get_limit(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_set_limit attrs = {}; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &set_limit_parser, npt, &attrs); if (error != 0) return (error); error = pf_ioctl_get_limit(attrs.index, &attrs.limit); 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_GET_LIMIT; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_LI_LIMIT, attrs.limit); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static int pf_handle_begin_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; uint32_t ticket; int error; error = pf_ioctl_begin_addrs(&ticket); 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_BEGIN_ADDRS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_BA_TICKET, ticket); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static bool nlattr_add_pool_addr(struct nl_writer *nw, int attrtype, struct pf_pooladdr *a) { int off; off = nlattr_add_nested(nw, attrtype); nlattr_add_addr_wrap(nw, PF_PA_ADDR, &a->addr); nlattr_add_string(nw, PF_PA_IFNAME, a->ifname); nlattr_set_len(nw, off); return (true); } #define _OUT(_field) offsetof(struct pf_pooladdr, _field) static const struct nlattr_parser nla_p_pool_addr[] = { { .type = PF_PA_ADDR, .off = _OUT(addr), .arg = &addr_wrap_parser, .cb = nlattr_get_nested }, { .type = PF_PA_IFNAME, .off = _OUT(ifname), .arg = (void *)IFNAMSIZ, .cb = nlattr_get_chara }, }; NL_DECLARE_ATTR_PARSER(pool_addr_parser, nla_p_pool_addr); #undef _OUT #define _OUT(_field) offsetof(struct pf_nl_pooladdr, _field) static const struct nlattr_parser nla_p_add_addr[] = { { .type = PF_AA_ACTION, .off = _OUT(action), .cb = nlattr_get_uint32 }, { .type = PF_AA_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 }, { .type = PF_AA_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 }, { .type = PF_AA_R_NUM, .off = _OUT(r_num), .cb = nlattr_get_uint32 }, { .type = PF_AA_R_ACTION, .off = _OUT(r_action), .cb = nlattr_get_uint8 }, { .type = PF_AA_R_LAST, .off = _OUT(r_last), .cb = nlattr_get_uint8 }, { .type = PF_AA_AF, .off = _OUT(af), .cb = nlattr_get_uint8 }, { .type = PF_AA_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara }, { .type = PF_AA_ADDR, .off = _OUT(addr), .arg = &pool_addr_parser, .cb = nlattr_get_nested }, { .type = PF_AA_WHICH, .off = _OUT(which), .cb = nlattr_get_uint32 }, }; #undef _OUT NL_DECLARE_PARSER(add_addr_parser, struct genlmsghdr, nlf_p_empty, nla_p_add_addr); static int pf_handle_add_addr(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_pooladdr attrs = { 0 }; int error; error = nl_parse_nlmsg(hdr, &add_addr_parser, npt, &attrs); if (error != 0) return (error); if (attrs.which == 0) attrs.which = PF_RDR; error = pf_ioctl_add_addr(&attrs); return (error); } static int pf_handle_get_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_pooladdr attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &add_addr_parser, npt, &attrs); if (error != 0) return (error); if (attrs.which == 0) attrs.which = PF_RDR; error = pf_ioctl_get_addrs(&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_GET_ADDRS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_AA_NR, attrs.nr); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (error); } static int pf_handle_get_addr(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_nl_pooladdr attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &add_addr_parser, npt, &attrs); if (error != 0) return (error); if (attrs.which == 0) attrs.which = PF_RDR; error = pf_ioctl_get_addr(&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_GET_ADDR; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_AA_ACTION, attrs.action); nlattr_add_u32(nw, PF_AA_TICKET, attrs.ticket); nlattr_add_u32(nw, PF_AA_NR, attrs.nr); nlattr_add_u32(nw, PF_AA_R_NUM, attrs.r_num); nlattr_add_u8(nw, PF_AA_R_ACTION, attrs.r_action); nlattr_add_u8(nw, PF_AA_R_LAST, attrs.r_last); nlattr_add_u8(nw, PF_AA_AF, attrs.af); nlattr_add_string(nw, PF_AA_ANCHOR, attrs.anchor); nlattr_add_pool_addr(nw, PF_AA_ADDR, &attrs.addr); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } #define _OUT(_field) offsetof(struct pfioc_ruleset, _field) static const struct nlattr_parser nla_p_ruleset[] = { { .type = PF_RS_PATH, .off = _OUT(path), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara }, { .type = PF_RS_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 }, }; NL_DECLARE_PARSER(ruleset_parser, struct genlmsghdr, nlf_p_empty, nla_p_ruleset); #undef _OUT static int pf_handle_get_rulesets(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_ruleset attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &ruleset_parser, npt, &attrs); if (error != 0) return (error); error = pf_ioctl_get_rulesets(&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_GET_RULESETS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_RS_NR, attrs.nr); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static int pf_handle_get_ruleset(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_ruleset attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &ruleset_parser, npt, &attrs); if (error) return (error); error = pf_ioctl_get_ruleset(&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_GET_RULESET; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_string(nw, PF_RS_NAME, attrs.name); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static bool nlattr_add_pf_threshold(struct nl_writer *nw, int attrtype, struct pf_kthreshold *t) { int off = nlattr_add_nested(nw, attrtype); int conn_rate_count = 0; /* Adjust the connection rate estimate. */ if (t->cr != NULL) conn_rate_count = counter_rate_get(t->cr); nlattr_add_u32(nw, PF_TH_LIMIT, t->limit); nlattr_add_u32(nw, PF_TH_SECONDS, t->seconds); nlattr_add_u32(nw, PF_TH_COUNT, conn_rate_count); nlattr_set_len(nw, off); return (true); } static int pf_handle_get_srcnodes(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; struct pf_ksrc_node *n; struct pf_srchash *sh; int i; int secs; hdr->nlmsg_flags |= NLM_F_MULTI; for (i = 0, sh = V_pf_srchash; i <= V_pf_srchashmask; i++, sh++) { /* Avoid locking empty rows. */ if (LIST_EMPTY(&sh->nodes)) continue; PF_HASHROW_LOCK(sh); secs = time_uptime; LIST_FOREACH(n, &sh->nodes, entry) { if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { nlmsg_abort(nw); return (ENOMEM); } ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GET_SRCNODES; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_in6_addr(nw, PF_SN_ADDR, &n->addr.v6); nlattr_add_in6_addr(nw, PF_SN_RADDR, &n->raddr.v6); nlattr_add_u32(nw, PF_SN_RULE_NR, n->rule->nr); nlattr_add_u64(nw, PF_SN_BYTES_IN, counter_u64_fetch(n->bytes[0])); nlattr_add_u64(nw, PF_SN_BYTES_OUT, counter_u64_fetch(n->bytes[1])); nlattr_add_u64(nw, PF_SN_PACKETS_IN, counter_u64_fetch(n->packets[0])); nlattr_add_u64(nw, PF_SN_PACKETS_OUT, counter_u64_fetch(n->packets[1])); nlattr_add_u32(nw, PF_SN_STATES, n->states); nlattr_add_u32(nw, PF_SN_CONNECTIONS, n->conn); nlattr_add_u8(nw, PF_SN_AF, n->af); nlattr_add_u8(nw, PF_SN_RAF, n->raf); nlattr_add_u8(nw, PF_SN_RULE_TYPE, n->ruletype); nlattr_add_u64(nw, PF_SN_CREATION, secs - n->creation); if (n->expire > secs) nlattr_add_u64(nw, PF_SN_EXPIRE, n->expire - secs); else nlattr_add_u64(nw, PF_SN_EXPIRE, 0); nlattr_add_pf_threshold(nw, PF_SN_CONNECTION_RATE, &n->conn_rate); nlattr_add_u8(nw, PF_SN_NODE_TYPE, n->type); if (!nlmsg_end(nw)) { PF_HASHROW_UNLOCK(sh); nlmsg_abort(nw); return (ENOMEM); } } PF_HASHROW_UNLOCK(sh); } return (0); } #define _OUT(_field) offsetof(struct pfioc_table, _field) static const struct nlattr_parser nla_p_table[] = { { .type = PF_T_ANCHOR, .off = _OUT(pfrio_table.pfrt_anchor), .arg = (void *)MAXPATHLEN, .cb = nlattr_get_chara }, { .type = PF_T_NAME, .off = _OUT(pfrio_table.pfrt_name), .arg = (void *)PF_TABLE_NAME_SIZE, .cb = nlattr_get_chara }, { .type = PF_T_TABLE_FLAGS, .off = _OUT(pfrio_table.pfrt_flags), .cb = nlattr_get_uint32 }, { .type = PF_T_FLAGS, .off = _OUT(pfrio_flags), .cb = nlattr_get_uint32 }, }; static const struct nlfield_parser nlf_p_table[] = {}; NL_DECLARE_PARSER(table_parser, struct genlmsghdr, nlf_p_table, nla_p_table); #undef _OUT static int pf_handle_clear_tables(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int ndel = 0; int error; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_clr_tables(&attrs.pfrio_table, &ndel, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); 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_CLEAR_TABLES; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_T_NBR_DELETED, ndel); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static int pf_handle_add_table(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_add_tables(&attrs.pfrio_table, 1, &attrs.pfrio_nadd, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); 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_ADD_TABLE; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_T_NBR_ADDED, attrs.pfrio_nadd); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static int pf_handle_del_table(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_del_tables(&attrs.pfrio_table, 1, &attrs.pfrio_ndel, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); 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_ADD_TABLE; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_T_NBR_DELETED, attrs.pfrio_ndel); if (!nlmsg_end(nw)) { nlmsg_abort(nw); return (ENOMEM); } return (0); } static bool nlattr_add_pfr_table(struct nl_writer *nw, int attrtype, struct pfr_table *t) { int off = nlattr_add_nested(nw, attrtype); nlattr_add_string(nw, PF_T_ANCHOR, t->pfrt_anchor); nlattr_add_string(nw, PF_T_NAME, t->pfrt_name); nlattr_add_u32(nw, PF_T_TABLE_FLAGS, t->pfrt_flags); nlattr_set_len(nw, off); return (true); } static int pf_handle_get_tstats(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; struct pfr_tstats *pfrtstats; int error; int n; PF_RULES_RLOCK_TRACKER; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_TABLE_STATS_LOCK(); PF_RULES_RLOCK(); n = pfr_table_count(&attrs.pfrio_table, attrs.pfrio_flags); pfrtstats = mallocarray(n, sizeof(struct pfr_tstats), M_PF, M_NOWAIT | M_ZERO); error = pfr_get_tstats(&attrs.pfrio_table, pfrtstats, &n, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); if (error == 0) { hdr->nlmsg_flags |= NLM_F_MULTI; for (int i = 0; i < n; i++) { uint64_t refcnt[PFR_REFCNT_MAX]; if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { error = ENOMEM; break; } ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GET_TSTATS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_pfr_table(nw, PF_TS_TABLE, &pfrtstats[i].pfrts_t); nlattr_add_u64_array(nw, PF_TS_PACKETS, PFR_DIR_MAX * PFR_OP_TABLE_MAX, (uint64_t *)pfrtstats[i].pfrts_packets); nlattr_add_u64_array(nw, PF_TS_BYTES, PFR_DIR_MAX * PFR_OP_TABLE_MAX, (uint64_t *)pfrtstats[i].pfrts_bytes); nlattr_add_u64(nw, PF_TS_MATCH, pfrtstats[i].pfrts_match); nlattr_add_u64(nw, PF_TS_NOMATCH, pfrtstats[i].pfrts_nomatch); nlattr_add_u64(nw, PF_TS_TZERO, pfrtstats[i].pfrts_tzero); nlattr_add_u64(nw, PF_TS_CNT, pfrtstats[i].pfrts_cnt); for (int j = 0; j < PFR_REFCNT_MAX; j++) refcnt[j] = pfrtstats[i].pfrts_refcnt[j]; nlattr_add_u64_array(nw, PF_TS_REFCNT, PFR_REFCNT_MAX, refcnt); if (! nlmsg_end(nw)) { error = ENOMEM; break; } } } free(pfrtstats, M_PF); 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_clear_tstats(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; int nzero; PF_RULES_RLOCK_TRACKER; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_TABLE_STATS_LOCK(); PF_RULES_RLOCK(); error = pfr_clr_tstats(&attrs.pfrio_table, 1, &nzero, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_RUNLOCK(); PF_TABLE_STATS_UNLOCK(); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_CLR_TSTATS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u64(nw, PF_TS_NZERO, nzero); if (! nlmsg_end(nw)) error = ENOMEM; return (error); } static int pf_handle_clear_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pfioc_table attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; int ndel; error = nl_parse_nlmsg(hdr, &table_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_clr_addrs(&attrs.pfrio_table, &ndel, attrs.pfrio_flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (error) 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_CLR_ADDRS; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u64(nw, PF_T_NBR_DELETED, ndel); if (!nlmsg_end(nw)) return (ENOMEM); return (error); } TAILQ_HEAD(pfr_addrq, pfr_addr_item); struct nl_parsed_table_addrs { struct pfr_table table; uint32_t flags; struct pfr_addr addrs[256]; size_t addr_count; int nadd; int ndel; + int nchange; }; #define _OUT(_field) offsetof(struct pfr_addr, _field) static const struct nlattr_parser nla_p_pfr_addr[] = { { .type = PFR_A_AF, .off = _OUT(pfra_af), .cb = nlattr_get_uint8 }, { .type = PFR_A_NET, .off = _OUT(pfra_net), .cb = nlattr_get_uint8 }, { .type = PFR_A_NOT, .off = _OUT(pfra_not), .cb = nlattr_get_bool }, { .type = PFR_A_ADDR, .off = _OUT(pfra_u), .cb = nlattr_get_in6_addr }, }; #undef _OUT NL_DECLARE_ATTR_PARSER(pfra_addr_parser, nla_p_pfr_addr); static int nlattr_get_pfr_addr(struct nlattr *nla, struct nl_pstate *npt, const void *arg, void *target) { struct nl_parsed_table_addrs *attrs = target; struct pfr_addr addr = { 0 }; int error; if (attrs->addr_count >= nitems(attrs->addrs)) return (E2BIG); error = nlattr_get_nested(nla, npt, &pfra_addr_parser, &addr); if (error != 0) return (error); memcpy(&attrs->addrs[attrs->addr_count], &addr, sizeof(addr)); attrs->addr_count++; return (0); } NL_DECLARE_ATTR_PARSER(nested_table_parser, nla_p_table); #define _OUT(_field) offsetof(struct nl_parsed_table_addrs, _field) static const struct nlattr_parser nla_p_table_addr[] = { { .type = PF_TA_TABLE, .off = _OUT(table), .arg = &nested_table_parser, .cb = nlattr_get_nested }, { .type = PF_TA_ADDR, .cb = nlattr_get_pfr_addr }, { .type = PF_TA_FLAGS, .off = _OUT(flags), .cb = nlattr_get_uint32 }, }; NL_DECLARE_PARSER(table_addr_parser, struct genlmsghdr, nlf_p_empty, nla_p_table_addr); #undef _OUT static int pf_handle_table_add_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct nl_parsed_table_addrs attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &table_addr_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_add_addrs(&attrs.table, &attrs.addrs[0], attrs.addr_count, &attrs.nadd, attrs.flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_TABLE_ADD_ADDR; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_TA_NBR_ADDED, attrs.nadd); if (!nlmsg_end(nw)) return (ENOMEM); return (error); } static int pf_handle_table_del_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) { struct nl_parsed_table_addrs attrs = { 0 }; struct nl_writer *nw = npt->nw; struct genlmsghdr *ghdr_new; int error; error = nl_parse_nlmsg(hdr, &table_addr_parser, npt, &attrs); if (error != 0) return (error); PF_RULES_WLOCK(); error = pfr_del_addrs(&attrs.table, &attrs.addrs[0], attrs.addr_count, &attrs.ndel, attrs.flags | PFR_FLAG_USERIOCTL); PF_RULES_WUNLOCK(); if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) return (ENOMEM); ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_TABLE_DEL_ADDR; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u32(nw, PF_TA_NBR_DELETED, attrs.ndel); if (!nlmsg_end(nw)) return (ENOMEM); return (error); } +static int +pf_handle_table_set_addrs(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct nl_parsed_table_addrs attrs = { 0 }; + struct nl_writer *nw = npt->nw; + struct genlmsghdr *ghdr_new; + int error; + + error = nl_parse_nlmsg(hdr, &table_addr_parser, npt, &attrs); + if (error != 0) + return (error); + + PF_RULES_WLOCK(); + error = pfr_set_addrs(&attrs.table, &attrs.addrs[0], + attrs.addr_count, NULL, &attrs.nadd, &attrs.ndel, &attrs.nchange, + attrs.flags | PFR_FLAG_USERIOCTL, 0); + PF_RULES_WUNLOCK(); + + if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) + return (ENOMEM); + + ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + ghdr_new->cmd = PFNL_CMD_TABLE_DEL_ADDR; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + nlattr_add_u32(nw, PF_TA_NBR_ADDED, attrs.nadd); + nlattr_add_u32(nw, PF_TA_NBR_DELETED, attrs.ndel); + nlattr_add_u32(nw, PF_TA_NBR_CHANGED, attrs.nchange); + + if (!nlmsg_end(nw)) + return (ENOMEM); + + return (error); +} + static const struct nlhdr_parser *all_parsers[] = { &state_parser, &addrule_parser, &getrules_parser, &clear_states_parser, &set_statusif_parser, &natlook_parser, &set_debug_parser, &set_timeout_parser, &set_limit_parser, &pool_addr_parser, &add_addr_parser, &ruleset_parser, &table_parser, &table_addr_parser, }; static uint16_t 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, }, { .cmd_num = PFNL_CMD_GET_STATUS, .cmd_name = "GETSTATUS", .cmd_cb = pf_handle_get_status, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_CLEAR_STATUS, .cmd_name = "CLEARSTATUS", .cmd_cb = pf_handle_clear_status, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_NATLOOK, .cmd_name = "NATLOOK", .cmd_cb = pf_handle_natlook, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_SET_DEBUG, .cmd_name = "SET_DEBUG", .cmd_cb = pf_handle_set_debug, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_SET_TIMEOUT, .cmd_name = "SET_TIMEOUT", .cmd_cb = pf_handle_set_timeout, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_TIMEOUT, .cmd_name = "GET_TIMEOUT", .cmd_cb = pf_handle_get_timeout, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_SET_LIMIT, .cmd_name = "SET_LIMIT", .cmd_cb = pf_handle_set_limit, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_LIMIT, .cmd_name = "GET_LIMIT", .cmd_cb = pf_handle_get_limit, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_BEGIN_ADDRS, .cmd_name = "BEGIN_ADDRS", .cmd_cb = pf_handle_begin_addrs, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_ADD_ADDR, .cmd_name = "ADD_ADDR", .cmd_cb = pf_handle_add_addr, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_ADDRS, .cmd_name = "GET_ADDRS", .cmd_cb = pf_handle_get_addrs, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_ADDR, .cmd_name = "GET_ADDRS", .cmd_cb = pf_handle_get_addr, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_RULESETS, .cmd_name = "GET_RULESETS", .cmd_cb = pf_handle_get_rulesets, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_RULESET, .cmd_name = "GET_RULESET", .cmd_cb = pf_handle_get_ruleset, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_SRCNODES, .cmd_name = "GET_SRCNODES", .cmd_cb = pf_handle_get_srcnodes, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_CLEAR_TABLES, .cmd_name = "CLEAR_TABLES", .cmd_cb = pf_handle_clear_tables, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_ADD_TABLE, .cmd_name = "ADD_TABLE", .cmd_cb = pf_handle_add_table, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_DEL_TABLE, .cmd_name = "DEL_TABLE", .cmd_cb = pf_handle_del_table, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_GET_TSTATS, .cmd_name = "GET_TSTATS", .cmd_cb = pf_handle_get_tstats, .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_CLR_TSTATS, .cmd_name = "CLR_TSTATS", .cmd_cb = pf_handle_clear_tstats, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_CLR_ADDRS, .cmd_name = "CRL_ADDRS", .cmd_cb = pf_handle_clear_addrs, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_TABLE_ADD_ADDR, .cmd_name = "TABLE_ADD_ADDRS", .cmd_cb = pf_handle_table_add_addrs, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, { .cmd_num = PFNL_CMD_TABLE_DEL_ADDR, .cmd_name = "TABLE_DEL_ADDRS", .cmd_cb = pf_handle_table_del_addrs, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, + { + .cmd_num = PFNL_CMD_TABLE_SET_ADDR, + .cmd_name = "TABLE_SET_ADDRS", + .cmd_cb = pf_handle_table_set_addrs, + .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(family_id, pf_cmds, nitems(pf_cmds)); } void pf_nl_unregister(void) { genl_unregister_family(family_id); } diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h index c46c8f2b2592..d1538ab4ff5b 100644 --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -1,495 +1,497 @@ /*- * 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_GET_STATUS = 11, PFNL_CMD_CLEAR_STATUS = 12, PFNL_CMD_NATLOOK = 13, PFNL_CMD_SET_DEBUG = 14, PFNL_CMD_SET_TIMEOUT = 15, PFNL_CMD_GET_TIMEOUT = 16, PFNL_CMD_SET_LIMIT = 17, PFNL_CMD_GET_LIMIT = 18, PFNL_CMD_BEGIN_ADDRS = 19, PFNL_CMD_ADD_ADDR = 20, PFNL_CMD_GET_ADDRS = 21, PFNL_CMD_GET_ADDR = 22, PFNL_CMD_GET_RULESETS = 23, PFNL_CMD_GET_RULESET = 24, PFNL_CMD_GET_SRCNODES = 25, PFNL_CMD_CLEAR_TABLES = 26, PFNL_CMD_ADD_TABLE = 27, PFNL_CMD_DEL_TABLE = 28, PFNL_CMD_GET_TSTATS = 29, PFNL_CMD_CLR_TSTATS = 30, PFNL_CMD_CLR_ADDRS = 31, PFNL_CMD_TABLE_ADD_ADDR = 32, PFNL_CMD_TABLE_DEL_ADDR = 33, + PFNL_CMD_TABLE_SET_ADDR = 34, __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 */ PF_STK_AF = 5, /* u8 */ PF_STK_PROTO = 6, /* 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 */ PF_ST_SRC_NODE_FLAGS = 38, /* u8 */ PF_ST_RT_AF = 39, /* u8 */ }; 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_RDR = 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 */ PF_RT_RCV_IFNAME = 73, /* string */ PF_RT_MAX_SRC_CONN = 74, /* u32 */ PF_RT_RPOOL_NAT = 75, /* nested, pf_rpool_type_t */ PF_RT_NAF = 76, /* u8 */ PF_RT_RPOOL_RT = 77, /* nested, pf_rpool_type_t */ PF_RT_RCV_IFNOT = 78, /* bool */ PF_RT_SRC_NODES_LIMIT = 79, /* u64 */ PF_RT_SRC_NODES_NAT = 80, /* u64 */ PF_RT_SRC_NODES_ROUTE = 81, /* u64 */ PF_RT_PKTRATE = 82, /* nested, pf_threshold_type_t */ PF_RT_MAX_PKT_SIZE = 83, /* u16 */ PF_RT_TYPE_2 = 84, /* u16 */ PF_RT_CODE_2 = 85, /* u16 */ PF_RT_EXPTIME = 86, /* time_t */ }; 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 */ }; enum pf_counter_types_t { PF_C_UNSPEC, PF_C_COUNTER = 1, /* u64 */ PF_C_NAME = 2, /* string */ PF_C_ID = 3, /* u32 */ }; enum pf_get_status_types_t { PF_GS_UNSPEC, PF_GS_IFNAME = 1, /* string */ PF_GS_RUNNING = 2, /* bool */ PF_GS_SINCE = 3, /* u32 */ PF_GS_DEBUG = 4, /* u32 */ PF_GS_HOSTID = 5, /* u32 */ PF_GS_STATES = 6, /* u32 */ PF_GS_SRC_NODES = 7, /* u32 */ PF_GS_REASSEMBLE = 8, /* u32 */ PF_GS_SYNCOOKIES_ACTIVE = 9, /* bool */ PF_GS_COUNTERS = 10, /* nested, */ PF_GS_LCOUNTERS = 11, /* nested, */ PF_GS_FCOUNTERS = 12, /* nested, */ PF_GS_SCOUNTERS = 13, /* nested, */ PF_GS_CHKSUM = 14, /* byte array */ PF_GS_PCOUNTERS = 15, /* u64 array */ PF_GS_BCOUNTERS = 16, /* u64 array */ PF_GS_NCOUNTERS = 17, /* nested, */ PF_GS_FRAGMENTS = 18, /* u64, */ }; enum pf_natlook_types_t { PF_NL_UNSPEC, PF_NL_AF = 1, /* u8 */ PF_NL_DIRECTION = 2, /* u8 */ PF_NL_PROTO = 3, /* u8 */ PF_NL_SRC_ADDR = 4, /* in6_addr */ PF_NL_DST_ADDR = 5, /* in6_addr */ PF_NL_SRC_PORT = 6, /* u16 */ PF_NL_DST_PORT = 7, /* u16 */ }; enum pf_set_debug_types_t { PF_SD_UNSPEC, PF_SD_LEVEL = 1, /* u32 */ }; enum pf_timeout_types_t { PF_TO_UNSPEC, PF_TO_TIMEOUT = 1, /* u32 */ PF_TO_SECONDS = 2, /* u32 */ }; enum pf_limit_types_t { PF_LI_UNSPEC, PF_LI_INDEX = 1, /* u32 */ PF_LI_LIMIT = 2, /* u32 */ }; enum pf_begin_addrs_types_t { PF_BA_UNSPEC, PF_BA_TICKET = 1, /* u32 */ }; enum pf_pool_addr_types_t { PF_PA_UNSPEC, PF_PA_ADDR = 1, /* nested, pf_addr_wrap */ PF_PA_IFNAME = 2, /* string */ }; enum pf_add_addr_types_t { PF_AA_UNSPEC, PF_AA_ACTION = 1, /* u32 */ PF_AA_TICKET = 2, /* u32 */ PF_AA_NR = 3, /* u32 */ PF_AA_R_NUM = 4, /* u32 */ PF_AA_R_ACTION = 5, /* u8 */ PF_AA_R_LAST = 6, /* u8 */ PF_AA_AF = 7, /* u8 */ PF_AA_ANCHOR = 8, /* string */ PF_AA_ADDR = 9, /* nested, pf_pooladdr */ PF_AA_WHICH = 10, /* u32 */ }; enum pf_get_rulesets_types_t { PF_RS_UNSPEC, PF_RS_PATH = 1, /* string */ PF_RS_NR = 2, /* u32 */ PF_RS_NAME = 3, /* string */ }; enum pf_threshold_types_t { PF_TH_UNSPEC, PF_TH_LIMIT = 1, /* u32 */ PF_TH_SECONDS = 2, /* u32 */ PF_TH_COUNT = 3, /* u32 */ PF_TH_LAST = 4, /* u32 */ }; enum pf_srcnodes_types_t { PF_SN_UNSPEC, PF_SN_ADDR = 1, /* nested, pf_addr */ PF_SN_RADDR = 2, /* nested, pf_addr */ PF_SN_RULE_NR = 3, /* u32 */ PF_SN_BYTES_IN = 4, /* u64 */ PF_SN_BYTES_OUT = 5, /* u64 */ PF_SN_PACKETS_IN = 6, /* u64 */ PF_SN_PACKETS_OUT = 7, /* u64 */ PF_SN_STATES = 8, /* u32 */ PF_SN_CONNECTIONS = 9, /* u32 */ PF_SN_AF = 10, /* u8 */ PF_SN_RULE_TYPE = 11, /* u8 */ PF_SN_CREATION = 12, /* u64 */ PF_SN_EXPIRE = 13, /* u64 */ PF_SN_CONNECTION_RATE = 14, /* nested, pf_threshold */ PF_SN_RAF = 15, /* u8 */ PF_SN_NODE_TYPE = 16, /* u8 */ }; enum pf_tables_t { PF_T_UNSPEC, PF_T_ANCHOR = 1, /* string */ PF_T_NAME = 2, /* string */ PF_T_TABLE_FLAGS = 3, /* u32 */ PF_T_FLAGS = 4, /* u32 */ PF_T_NBR_DELETED = 5, /* u32 */ PF_T_NBR_ADDED = 6, /* u32 */ }; enum pf_tstats_t { PF_TS_UNSPEC, PF_TS_TABLE = 1, /* nested, pfr_table */ PF_TS_PACKETS = 2, /* u64 array */ PF_TS_BYTES = 3, /* u64 array */ PF_TS_MATCH = 4, /* u64 */ PF_TS_NOMATCH = 5, /* u64 */ PF_TS_TZERO = 6, /* u64 */ PF_TS_CNT = 7, /* u64 */ PF_TS_REFCNT = 8, /* u64 array */ PF_TS_NZERO = 9, /* u64 */ }; enum pfr_addr_t { PFR_A_UNSPEC, PFR_A_AF = 1, /* uint8_t */ PFR_A_NET = 2, /* uint8_t */ PFR_A_NOT = 3, /* bool */ PFR_A_ADDR = 4, /* in6_addr */ }; enum pf_table_addrs_t { PF_TA_UNSPEC, PF_TA_TABLE = 1, /* nested, pf_table_t */ PF_TA_ADDR = 2, /* nested, pfr_addr_t */ PF_TA_FLAGS = 3, /* u32 */ PF_TA_NBR_ADDED = 4, /* u32 */ PF_TA_NBR_DELETED = 5, /* u32 */ + PF_TA_NBR_CHANGED = 6, /* u32 */ }; #ifdef _KERNEL void pf_nl_register(void); void pf_nl_unregister(void); #endif #endif diff --git a/sys/netpfil/pf/pf_table.c b/sys/netpfil/pf/pf_table.c index cf752ce0de18..0e2b9fe1cac8 100644 --- a/sys/netpfil/pf/pf_table.c +++ b/sys/netpfil/pf/pf_table.c @@ -1,2477 +1,2481 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2002 Cedric Berger * 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. * * $OpenBSD: pf_table.c,v 1.79 2008/10/08 06:24:50 mcbride Exp $ */ #include #include "opt_inet.h" #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #define ACCEPT_FLAGS(flags, oklist) \ do { \ if ((flags & ~(oklist)) & \ PFR_FLAG_ALLMASK) \ return (EINVAL); \ } while (0) #define FILLIN_SIN(sin, addr) \ do { \ (sin).sin_len = sizeof(sin); \ (sin).sin_family = AF_INET; \ (sin).sin_addr = (addr); \ } while (0) #define FILLIN_SIN6(sin6, addr) \ do { \ (sin6).sin6_len = sizeof(sin6); \ (sin6).sin6_family = AF_INET6; \ (sin6).sin6_addr = (addr); \ } while (0) #define SWAP(type, a1, a2) \ do { \ type tmp = a1; \ a1 = a2; \ a2 = tmp; \ } while (0) #define AF_BITS(af) (((af)==AF_INET)?32:128) #define ADDR_NETWORK(ad) ((ad)->pfra_net < AF_BITS((ad)->pfra_af)) #define KENTRY_NETWORK(ke) ((ke)->pfrke_net < AF_BITS((ke)->pfrke_af)) #define KENTRY_RNF_ROOT(ke) \ ((((struct radix_node *)(ke))->rn_flags & RNF_ROOT) != 0) #define NO_ADDRESSES (-1) #define ENQUEUE_UNMARKED_ONLY (1) #define INVERT_NEG_FLAG (1) struct pfr_walktree { enum pfrw_op { PFRW_MARK, PFRW_SWEEP, PFRW_ENQUEUE, PFRW_GET_ADDRS, PFRW_GET_ASTATS, PFRW_POOL_GET, PFRW_DYNADDR_UPDATE, PFRW_COUNTERS } pfrw_op; union { struct pfr_addr *pfrw_addr; struct pfr_astats *pfrw_astats; struct pfr_kentryworkq *pfrw_workq; struct pfr_kentry *pfrw_kentry; struct pfi_dynaddr *pfrw_dyn; }; int pfrw_free; int pfrw_flags; }; #define senderr(e) do { rv = (e); goto _bad; } while (0) static MALLOC_DEFINE(M_PFTABLE, "pf_table", "pf(4) tables structures"); VNET_DEFINE_STATIC(uma_zone_t, pfr_kentry_z); #define V_pfr_kentry_z VNET(pfr_kentry_z) VNET_DEFINE_STATIC(uma_zone_t, pfr_kentry_counter_z); #define V_pfr_kentry_counter_z VNET(pfr_kentry_counter_z) static struct pf_addr pfr_ffaddr = { .addr32 = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff } }; static void pfr_copyout_astats(struct pfr_astats *, const struct pfr_kentry *, const struct pfr_walktree *); static void pfr_copyout_addr(struct pfr_addr *, const struct pfr_kentry *ke); static int pfr_validate_addr(struct pfr_addr *); static void pfr_enqueue_addrs(struct pfr_ktable *, struct pfr_kentryworkq *, int *, int); static void pfr_mark_addrs(struct pfr_ktable *); static struct pfr_kentry *pfr_lookup_addr(struct pfr_ktable *, struct pfr_addr *, int); static struct pfr_kentry *pfr_create_kentry(struct pfr_addr *, bool); static void pfr_destroy_kentries(struct pfr_kentryworkq *); static void pfr_destroy_kentry(struct pfr_kentry *); static void pfr_insert_kentries(struct pfr_ktable *, struct pfr_kentryworkq *, time_t); static void pfr_remove_kentries(struct pfr_ktable *, struct pfr_kentryworkq *); static void pfr_clstats_kentries(struct pfr_ktable *, struct pfr_kentryworkq *, time_t, int); static void pfr_reset_feedback(struct pfr_addr *, int); static void pfr_prepare_network(union sockaddr_union *, int, int); static int pfr_route_kentry(struct pfr_ktable *, struct pfr_kentry *); static int pfr_unroute_kentry(struct pfr_ktable *, struct pfr_kentry *); static int pfr_walktree(struct radix_node *, void *); static int pfr_validate_table(struct pfr_table *, int, int); static int pfr_fix_anchor(char *); static void pfr_commit_ktable(struct pfr_ktable *, time_t); static void pfr_insert_ktables(struct pfr_ktableworkq *); static void pfr_insert_ktable(struct pfr_ktable *); static void pfr_setflags_ktables(struct pfr_ktableworkq *); static void pfr_setflags_ktable(struct pfr_ktable *, int); static void pfr_clstats_ktables(struct pfr_ktableworkq *, time_t, int); static void pfr_clstats_ktable(struct pfr_ktable *, time_t, int); static struct pfr_ktable *pfr_create_ktable(struct pfr_table *, time_t, int); static void pfr_destroy_ktables(struct pfr_ktableworkq *, int); static void pfr_destroy_ktable(struct pfr_ktable *, int); static int pfr_ktable_compare(struct pfr_ktable *, struct pfr_ktable *); static struct pfr_ktable *pfr_lookup_table(struct pfr_table *); static void pfr_clean_node_mask(struct pfr_ktable *, struct pfr_kentryworkq *); static int pfr_skip_table(struct pfr_table *, struct pfr_ktable *, int); static struct pfr_kentry *pfr_kentry_byidx(struct pfr_ktable *, int, int); static RB_PROTOTYPE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); static RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); VNET_DEFINE_STATIC(struct pfr_ktablehead, pfr_ktables); #define V_pfr_ktables VNET(pfr_ktables) VNET_DEFINE_STATIC(struct pfr_table, pfr_nulltable); #define V_pfr_nulltable VNET(pfr_nulltable) VNET_DEFINE_STATIC(int, pfr_ktable_cnt); #define V_pfr_ktable_cnt VNET(pfr_ktable_cnt) void pfr_initialize(void) { V_pfr_kentry_counter_z = uma_zcreate("pf table entry counters", PFR_NUM_COUNTERS * sizeof(uint64_t), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_PCPU); V_pfr_kentry_z = uma_zcreate("pf table entries", sizeof(struct pfr_kentry), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); uma_zone_set_max(V_pfr_kentry_z, PFR_KENTRY_HIWAT); V_pf_limits[PF_LIMIT_TABLE_ENTRIES].zone = V_pfr_kentry_z; V_pf_limits[PF_LIMIT_TABLE_ENTRIES].limit = PFR_KENTRY_HIWAT; } void pfr_cleanup(void) { uma_zdestroy(V_pfr_kentry_z); uma_zdestroy(V_pfr_kentry_counter_z); } int pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags) { struct pfr_ktable *kt; struct pfr_kentryworkq workq; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); if (pfr_validate_table(tbl, 0, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_flags & PFR_TFLAG_CONST) return (EPERM); pfr_enqueue_addrs(kt, &workq, ndel, 0); if (!(flags & PFR_FLAG_DUMMY)) { pfr_remove_kentries(kt, &workq); KASSERT(kt->pfrkt_cnt == 0, ("%s: non-null pfrkt_cnt", __func__)); } return (0); } int pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags) { struct pfr_ktable *kt, *tmpkt; struct pfr_kentryworkq workq; struct pfr_kentry *p, *q; struct pfr_addr *ad; int i, rv, xadd = 0; time_t tzero = time_second; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_FEEDBACK); if (pfr_validate_table(tbl, 0, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_flags & PFR_TFLAG_CONST) return (EPERM); tmpkt = pfr_create_ktable(&V_pfr_nulltable, 0, 0); if (tmpkt == NULL) return (ENOMEM); SLIST_INIT(&workq); for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) senderr(EINVAL); p = pfr_lookup_addr(kt, ad, 1); q = pfr_lookup_addr(tmpkt, ad, 1); if (flags & PFR_FLAG_FEEDBACK) { if (q != NULL) ad->pfra_fback = PFR_FB_DUPLICATE; else if (p == NULL) ad->pfra_fback = PFR_FB_ADDED; else if (p->pfrke_not != ad->pfra_not) ad->pfra_fback = PFR_FB_CONFLICT; else ad->pfra_fback = PFR_FB_NONE; } if (p == NULL && q == NULL) { p = pfr_create_kentry(ad, (kt->pfrkt_flags & PFR_TFLAG_COUNTERS) != 0); if (p == NULL) senderr(ENOMEM); if (pfr_route_kentry(tmpkt, p)) { pfr_destroy_kentry(p); ad->pfra_fback = PFR_FB_NONE; } else { SLIST_INSERT_HEAD(&workq, p, pfrke_workq); xadd++; } } } pfr_clean_node_mask(tmpkt, &workq); if (!(flags & PFR_FLAG_DUMMY)) pfr_insert_kentries(kt, &workq, tzero); else pfr_destroy_kentries(&workq); if (nadd != NULL) *nadd += xadd; pfr_destroy_ktable(tmpkt, 0); return (0); _bad: pfr_clean_node_mask(tmpkt, &workq); pfr_destroy_kentries(&workq); if (flags & PFR_FLAG_FEEDBACK) pfr_reset_feedback(addr, size); pfr_destroy_ktable(tmpkt, 0); return (rv); } int pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags) { struct pfr_ktable *kt; struct pfr_kentryworkq workq; struct pfr_kentry *p; struct pfr_addr *ad; int i, rv, xdel = 0, log = 1; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_FEEDBACK); if (pfr_validate_table(tbl, 0, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_flags & PFR_TFLAG_CONST) return (EPERM); /* * there are two algorithms to choose from here. * with: * n: number of addresses to delete * N: number of addresses in the table * * one is O(N) and is better for large 'n' * one is O(n*LOG(N)) and is better for small 'n' * * following code try to decide which one is best. */ for (i = kt->pfrkt_cnt; i > 0; i >>= 1) log++; if (size > kt->pfrkt_cnt/log) { /* full table scan */ pfr_mark_addrs(kt); } else { /* iterate over addresses to delete */ for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) return (EINVAL); p = pfr_lookup_addr(kt, ad, 1); if (p != NULL) p->pfrke_mark = 0; } } SLIST_INIT(&workq); for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) senderr(EINVAL); p = pfr_lookup_addr(kt, ad, 1); if (flags & PFR_FLAG_FEEDBACK) { if (p == NULL) ad->pfra_fback = PFR_FB_NONE; else if (p->pfrke_not != ad->pfra_not) ad->pfra_fback = PFR_FB_CONFLICT; else if (p->pfrke_mark) ad->pfra_fback = PFR_FB_DUPLICATE; else ad->pfra_fback = PFR_FB_DELETED; } if (p != NULL && p->pfrke_not == ad->pfra_not && !p->pfrke_mark) { p->pfrke_mark = 1; SLIST_INSERT_HEAD(&workq, p, pfrke_workq); xdel++; } } if (!(flags & PFR_FLAG_DUMMY)) pfr_remove_kentries(kt, &workq); if (ndel != NULL) *ndel = xdel; return (0); _bad: if (flags & PFR_FLAG_FEEDBACK) pfr_reset_feedback(addr, size); return (rv); } int pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *size2, int *nadd, int *ndel, int *nchange, int flags, u_int32_t ignore_pfrt_flags) { struct pfr_ktable *kt, *tmpkt; struct pfr_kentryworkq addq, delq, changeq; struct pfr_kentry *p, *q; struct pfr_addr ad; int i, rv, xadd = 0, xdel = 0, xchange = 0; time_t tzero = time_second; PF_RULES_WASSERT(); - ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_FEEDBACK); + ACCEPT_FLAGS(flags, PFR_FLAG_START | PFR_FLAG_DONE | + PFR_FLAG_DUMMY | PFR_FLAG_FEEDBACK); if (pfr_validate_table(tbl, ignore_pfrt_flags, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_flags & PFR_TFLAG_CONST) return (EPERM); tmpkt = pfr_create_ktable(&V_pfr_nulltable, 0, 0); if (tmpkt == NULL) return (ENOMEM); - pfr_mark_addrs(kt); + if (flags & PFR_FLAG_START) + pfr_mark_addrs(kt); SLIST_INIT(&addq); SLIST_INIT(&delq); SLIST_INIT(&changeq); for (i = 0; i < size; i++) { /* * XXXGL: undertand pf_if usage of this function * and make ad a moving pointer */ bcopy(addr + i, &ad, sizeof(ad)); if (pfr_validate_addr(&ad)) senderr(EINVAL); ad.pfra_fback = PFR_FB_NONE; p = pfr_lookup_addr(kt, &ad, 1); if (p != NULL) { if (p->pfrke_mark) { ad.pfra_fback = PFR_FB_DUPLICATE; goto _skip; } p->pfrke_mark = 1; if (p->pfrke_not != ad.pfra_not) { SLIST_INSERT_HEAD(&changeq, p, pfrke_workq); ad.pfra_fback = PFR_FB_CHANGED; xchange++; } } else { q = pfr_lookup_addr(tmpkt, &ad, 1); if (q != NULL) { ad.pfra_fback = PFR_FB_DUPLICATE; goto _skip; } p = pfr_create_kentry(&ad, (kt->pfrkt_flags & PFR_TFLAG_COUNTERS) != 0); + p->pfrke_mark = PFR_FB_ADDED; if (p == NULL) senderr(ENOMEM); if (pfr_route_kentry(tmpkt, p)) { pfr_destroy_kentry(p); ad.pfra_fback = PFR_FB_NONE; } else { SLIST_INSERT_HEAD(&addq, p, pfrke_workq); ad.pfra_fback = PFR_FB_ADDED; xadd++; } } _skip: if (flags & PFR_FLAG_FEEDBACK) bcopy(&ad, addr + i, sizeof(ad)); } - pfr_enqueue_addrs(kt, &delq, &xdel, ENQUEUE_UNMARKED_ONLY); + if (flags & PFR_FLAG_DONE) + pfr_enqueue_addrs(kt, &delq, &xdel, ENQUEUE_UNMARKED_ONLY); if ((flags & PFR_FLAG_FEEDBACK) && *size2) { if (*size2 < size+xdel) { *size2 = size+xdel; senderr(0); } i = 0; SLIST_FOREACH(p, &delq, pfrke_workq) { pfr_copyout_addr(&ad, p); ad.pfra_fback = PFR_FB_DELETED; bcopy(&ad, addr + size + i, sizeof(ad)); i++; } } pfr_clean_node_mask(tmpkt, &addq); if (!(flags & PFR_FLAG_DUMMY)) { pfr_insert_kentries(kt, &addq, tzero); pfr_remove_kentries(kt, &delq); pfr_clstats_kentries(kt, &changeq, tzero, INVERT_NEG_FLAG); } else pfr_destroy_kentries(&addq); if (nadd != NULL) *nadd = xadd; if (ndel != NULL) *ndel = xdel; if (nchange != NULL) *nchange = xchange; if ((flags & PFR_FLAG_FEEDBACK) && size2) *size2 = size+xdel; pfr_destroy_ktable(tmpkt, 0); return (0); _bad: pfr_clean_node_mask(tmpkt, &addq); pfr_destroy_kentries(&addq); if (flags & PFR_FLAG_FEEDBACK) pfr_reset_feedback(addr, size); pfr_destroy_ktable(tmpkt, 0); return (rv); } int pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nmatch, int flags) { struct pfr_ktable *kt; struct pfr_kentry *p; struct pfr_addr *ad; int i, xmatch = 0; PF_RULES_RASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_REPLACE); if (pfr_validate_table(tbl, 0, 0)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) return (EINVAL); if (ADDR_NETWORK(ad)) return (EINVAL); p = pfr_lookup_addr(kt, ad, 0); if (flags & PFR_FLAG_REPLACE) pfr_copyout_addr(ad, p); ad->pfra_fback = (p == NULL) ? PFR_FB_NONE : (p->pfrke_not ? PFR_FB_NOTMATCH : PFR_FB_MATCH); if (p != NULL && !p->pfrke_not) xmatch++; } if (nmatch != NULL) *nmatch = xmatch; return (0); } int pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags) { struct pfr_ktable *kt; struct pfr_walktree w; int rv; PF_RULES_RASSERT(); ACCEPT_FLAGS(flags, 0); if (pfr_validate_table(tbl, 0, 0)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_cnt > *size) { *size = kt->pfrkt_cnt; return (0); } bzero(&w, sizeof(w)); w.pfrw_op = PFRW_GET_ADDRS; w.pfrw_addr = addr; w.pfrw_free = kt->pfrkt_cnt; rv = kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); if (!rv) rv = kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); if (rv) return (rv); KASSERT(w.pfrw_free == 0, ("%s: corruption detected (%d)", __func__, w.pfrw_free)); *size = kt->pfrkt_cnt; return (0); } int pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size, int flags) { struct pfr_ktable *kt; struct pfr_walktree w; struct pfr_kentryworkq workq; int rv; time_t tzero = time_second; PF_RULES_RASSERT(); /* XXX PFR_FLAG_CLSTATS disabled */ ACCEPT_FLAGS(flags, 0); if (pfr_validate_table(tbl, 0, 0)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); if (kt->pfrkt_cnt > *size) { *size = kt->pfrkt_cnt; return (0); } bzero(&w, sizeof(w)); w.pfrw_op = PFRW_GET_ASTATS; w.pfrw_astats = addr; w.pfrw_free = kt->pfrkt_cnt; /* * Flags below are for backward compatibility. It was possible to have * a table without per-entry counters. Now they are always allocated, * we just discard data when reading it if table is not configured to * have counters. */ w.pfrw_flags = kt->pfrkt_flags; rv = kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); if (!rv) rv = kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); if (!rv && (flags & PFR_FLAG_CLSTATS)) { pfr_enqueue_addrs(kt, &workq, NULL, 0); pfr_clstats_kentries(kt, &workq, tzero, 0); } if (rv) return (rv); if (w.pfrw_free) { printf("pfr_get_astats: corruption detected (%d).\n", w.pfrw_free); return (ENOTTY); } *size = kt->pfrkt_cnt; return (0); } int pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nzero, int flags) { struct pfr_ktable *kt; struct pfr_kentryworkq workq; struct pfr_kentry *p; struct pfr_addr *ad; int i, rv, xzero = 0; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_FEEDBACK); if (pfr_validate_table(tbl, 0, 0)) return (EINVAL); kt = pfr_lookup_table(tbl); if (kt == NULL || !(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (ESRCH); SLIST_INIT(&workq); for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) senderr(EINVAL); p = pfr_lookup_addr(kt, ad, 1); if (flags & PFR_FLAG_FEEDBACK) { ad->pfra_fback = (p != NULL) ? PFR_FB_CLEARED : PFR_FB_NONE; } if (p != NULL) { SLIST_INSERT_HEAD(&workq, p, pfrke_workq); xzero++; } } if (!(flags & PFR_FLAG_DUMMY)) pfr_clstats_kentries(kt, &workq, time_second, 0); if (nzero != NULL) *nzero = xzero; return (0); _bad: if (flags & PFR_FLAG_FEEDBACK) pfr_reset_feedback(addr, size); return (rv); } static int pfr_validate_addr(struct pfr_addr *ad) { int i; switch (ad->pfra_af) { #ifdef INET case AF_INET: if (ad->pfra_net > 32) return (-1); break; #endif /* INET */ #ifdef INET6 case AF_INET6: if (ad->pfra_net > 128) return (-1); break; #endif /* INET6 */ default: return (-1); } if (ad->pfra_net < 128 && (((caddr_t)ad)[ad->pfra_net/8] & (0xFF >> (ad->pfra_net%8)))) return (-1); for (i = (ad->pfra_net+7)/8; i < sizeof(ad->pfra_u); i++) if (((caddr_t)ad)[i]) return (-1); if (ad->pfra_not && ad->pfra_not != 1) return (-1); if (ad->pfra_fback != PFR_FB_NONE) return (-1); return (0); } static void pfr_enqueue_addrs(struct pfr_ktable *kt, struct pfr_kentryworkq *workq, int *naddr, int sweep) { struct pfr_walktree w; SLIST_INIT(workq); bzero(&w, sizeof(w)); w.pfrw_op = sweep ? PFRW_SWEEP : PFRW_ENQUEUE; w.pfrw_workq = workq; if (kt->pfrkt_ip4 != NULL) if (kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w)) printf("pfr_enqueue_addrs: IPv4 walktree failed.\n"); if (kt->pfrkt_ip6 != NULL) if (kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w)) printf("pfr_enqueue_addrs: IPv6 walktree failed.\n"); if (naddr != NULL) *naddr = w.pfrw_free; } static void pfr_mark_addrs(struct pfr_ktable *kt) { struct pfr_walktree w; bzero(&w, sizeof(w)); w.pfrw_op = PFRW_MARK; if (kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w)) printf("pfr_mark_addrs: IPv4 walktree failed.\n"); if (kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w)) printf("pfr_mark_addrs: IPv6 walktree failed.\n"); } static struct pfr_kentry * pfr_lookup_addr(struct pfr_ktable *kt, struct pfr_addr *ad, int exact) { union sockaddr_union sa, mask; struct radix_head *head = NULL; struct pfr_kentry *ke; PF_RULES_ASSERT(); bzero(&sa, sizeof(sa)); switch (ad->pfra_af) { case AF_INET: FILLIN_SIN(sa.sin, ad->pfra_ip4addr); head = &kt->pfrkt_ip4->rh; break; case AF_INET6: FILLIN_SIN6(sa.sin6, ad->pfra_ip6addr); head = &kt->pfrkt_ip6->rh; break; default: unhandled_af(ad->pfra_af); } if (ADDR_NETWORK(ad)) { pfr_prepare_network(&mask, ad->pfra_af, ad->pfra_net); ke = (struct pfr_kentry *)rn_lookup(&sa, &mask, head); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; } else { ke = (struct pfr_kentry *)rn_match(&sa, head); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; if (exact && ke && KENTRY_NETWORK(ke)) ke = NULL; } return (ke); } static struct pfr_kentry * pfr_create_kentry(struct pfr_addr *ad, bool counters) { struct pfr_kentry *ke; counter_u64_t c; ke = uma_zalloc(V_pfr_kentry_z, M_NOWAIT | M_ZERO); if (ke == NULL) return (NULL); switch (ad->pfra_af) { case AF_INET: FILLIN_SIN(ke->pfrke_sa.sin, ad->pfra_ip4addr); break; case AF_INET6: FILLIN_SIN6(ke->pfrke_sa.sin6, ad->pfra_ip6addr); break; default: unhandled_af(ad->pfra_af); } ke->pfrke_af = ad->pfra_af; ke->pfrke_net = ad->pfra_net; ke->pfrke_not = ad->pfra_not; ke->pfrke_counters.pfrkc_tzero = 0; if (counters) { c = uma_zalloc_pcpu(V_pfr_kentry_counter_z, M_NOWAIT | M_ZERO); if (c == NULL) { pfr_destroy_kentry(ke); return (NULL); } ke->pfrke_counters.pfrkc_counters = c; } return (ke); } static void pfr_destroy_kentries(struct pfr_kentryworkq *workq) { struct pfr_kentry *p; while ((p = SLIST_FIRST(workq)) != NULL) { SLIST_REMOVE_HEAD(workq, pfrke_workq); pfr_destroy_kentry(p); } } static void pfr_destroy_kentry(struct pfr_kentry *ke) { counter_u64_t c; if ((c = ke->pfrke_counters.pfrkc_counters) != NULL) uma_zfree_pcpu(V_pfr_kentry_counter_z, c); uma_zfree(V_pfr_kentry_z, ke); } static void pfr_insert_kentries(struct pfr_ktable *kt, struct pfr_kentryworkq *workq, time_t tzero) { struct pfr_kentry *p; int rv, n = 0; SLIST_FOREACH(p, workq, pfrke_workq) { rv = pfr_route_kentry(kt, p); if (rv) { printf("pfr_insert_kentries: cannot route entry " "(code=%d).\n", rv); break; } p->pfrke_counters.pfrkc_tzero = tzero; n++; } kt->pfrkt_cnt += n; } int pfr_insert_kentry(struct pfr_ktable *kt, struct pfr_addr *ad, time_t tzero) { struct pfr_kentry *p; int rv; p = pfr_lookup_addr(kt, ad, 1); if (p != NULL) return (0); p = pfr_create_kentry(ad, (kt->pfrkt_flags & PFR_TFLAG_COUNTERS) != 0); if (p == NULL) return (ENOMEM); rv = pfr_route_kentry(kt, p); if (rv) return (rv); p->pfrke_counters.pfrkc_tzero = tzero; kt->pfrkt_cnt++; return (0); } static void pfr_remove_kentries(struct pfr_ktable *kt, struct pfr_kentryworkq *workq) { struct pfr_kentry *p; int n = 0; SLIST_FOREACH(p, workq, pfrke_workq) { pfr_unroute_kentry(kt, p); n++; } kt->pfrkt_cnt -= n; pfr_destroy_kentries(workq); } static void pfr_clean_node_mask(struct pfr_ktable *kt, struct pfr_kentryworkq *workq) { struct pfr_kentry *p; SLIST_FOREACH(p, workq, pfrke_workq) pfr_unroute_kentry(kt, p); } static void pfr_clstats_kentries(struct pfr_ktable *kt, struct pfr_kentryworkq *workq, time_t tzero, int negchange) { struct pfr_kentry *p; int i; SLIST_FOREACH(p, workq, pfrke_workq) { if (negchange) p->pfrke_not = !p->pfrke_not; if ((kt->pfrkt_flags & PFR_TFLAG_COUNTERS) != 0) for (i = 0; i < PFR_NUM_COUNTERS; i++) counter_u64_zero( p->pfrke_counters.pfrkc_counters + i); p->pfrke_counters.pfrkc_tzero = tzero; } } static void pfr_reset_feedback(struct pfr_addr *addr, int size) { struct pfr_addr *ad; int i; for (i = 0, ad = addr; i < size; i++, ad++) ad->pfra_fback = PFR_FB_NONE; } static void pfr_prepare_network(union sockaddr_union *sa, int af, int net) { int i; bzero(sa, sizeof(*sa)); switch (af) { case AF_INET: sa->sin.sin_len = sizeof(sa->sin); sa->sin.sin_family = AF_INET; sa->sin.sin_addr.s_addr = net ? htonl(-1 << (32-net)) : 0; break; case AF_INET6: sa->sin6.sin6_len = sizeof(sa->sin6); sa->sin6.sin6_family = AF_INET6; for (i = 0; i < 4; i++) { if (net <= 32) { sa->sin6.sin6_addr.s6_addr32[i] = net ? htonl(-1 << (32-net)) : 0; break; } sa->sin6.sin6_addr.s6_addr32[i] = 0xFFFFFFFF; net -= 32; } break; default: unhandled_af(af); } } static int pfr_route_kentry(struct pfr_ktable *kt, struct pfr_kentry *ke) { union sockaddr_union mask; struct radix_node *rn; struct radix_head *head = NULL; PF_RULES_WASSERT(); bzero(ke->pfrke_node, sizeof(ke->pfrke_node)); switch (ke->pfrke_af) { case AF_INET: head = &kt->pfrkt_ip4->rh; break; case AF_INET6: head = &kt->pfrkt_ip6->rh; break; default: unhandled_af(ke->pfrke_af); } if (KENTRY_NETWORK(ke)) { pfr_prepare_network(&mask, ke->pfrke_af, ke->pfrke_net); rn = rn_addroute(&ke->pfrke_sa, &mask, head, ke->pfrke_node); } else rn = rn_addroute(&ke->pfrke_sa, NULL, head, ke->pfrke_node); return (rn == NULL ? -1 : 0); } static int pfr_unroute_kentry(struct pfr_ktable *kt, struct pfr_kentry *ke) { union sockaddr_union mask; struct radix_node *rn; struct radix_head *head = NULL; switch (ke->pfrke_af) { case AF_INET: head = &kt->pfrkt_ip4->rh; break; case AF_INET6: head = &kt->pfrkt_ip6->rh; break; default: unhandled_af(ke->pfrke_af); } if (KENTRY_NETWORK(ke)) { pfr_prepare_network(&mask, ke->pfrke_af, ke->pfrke_net); rn = rn_delete(&ke->pfrke_sa, &mask, head); } else rn = rn_delete(&ke->pfrke_sa, NULL, head); if (rn == NULL) { printf("pfr_unroute_kentry: delete failed.\n"); return (-1); } return (0); } static void pfr_copyout_addr(struct pfr_addr *ad, const struct pfr_kentry *ke) { bzero(ad, sizeof(*ad)); if (ke == NULL) return; ad->pfra_af = ke->pfrke_af; ad->pfra_net = ke->pfrke_net; ad->pfra_not = ke->pfrke_not; switch (ad->pfra_af) { case AF_INET: ad->pfra_ip4addr = ke->pfrke_sa.sin.sin_addr; break; case AF_INET6: ad->pfra_ip6addr = ke->pfrke_sa.sin6.sin6_addr; break; default: unhandled_af(ad->pfra_af); } } static void pfr_copyout_astats(struct pfr_astats *as, const struct pfr_kentry *ke, const struct pfr_walktree *w) { int dir, op; const struct pfr_kcounters *kc = &ke->pfrke_counters; bzero(as, sizeof(*as)); pfr_copyout_addr(&as->pfras_a, ke); as->pfras_tzero = kc->pfrkc_tzero; if (! (w->pfrw_flags & PFR_TFLAG_COUNTERS) || kc->pfrkc_counters == NULL) { bzero(as->pfras_packets, sizeof(as->pfras_packets)); bzero(as->pfras_bytes, sizeof(as->pfras_bytes)); as->pfras_a.pfra_fback = PFR_FB_NOCOUNT; return; } for (dir = 0; dir < PFR_DIR_MAX; dir++) { for (op = 0; op < PFR_OP_ADDR_MAX; op ++) { as->pfras_packets[dir][op] = counter_u64_fetch( pfr_kentry_counter(kc, dir, op, PFR_TYPE_PACKETS)); as->pfras_bytes[dir][op] = counter_u64_fetch( pfr_kentry_counter(kc, dir, op, PFR_TYPE_BYTES)); } } } static void pfr_sockaddr_to_pf_addr(const union sockaddr_union *sa, struct pf_addr *a) { switch (sa->sa.sa_family) { case AF_INET: memcpy(&a->v4, &sa->sin.sin_addr, sizeof(a->v4)); break; case AF_INET6: memcpy(&a->v6, &sa->sin6.sin6_addr, sizeof(a->v6)); break; default: unhandled_af(sa->sa.sa_family); } } static int pfr_walktree(struct radix_node *rn, void *arg) { struct pfr_kentry *ke = (struct pfr_kentry *)rn; struct pfr_walktree *w = arg; switch (w->pfrw_op) { case PFRW_MARK: ke->pfrke_mark = 0; break; case PFRW_SWEEP: if (ke->pfrke_mark) break; /* FALLTHROUGH */ case PFRW_ENQUEUE: SLIST_INSERT_HEAD(w->pfrw_workq, ke, pfrke_workq); w->pfrw_free++; break; case PFRW_GET_ADDRS: if (w->pfrw_free-- > 0) { pfr_copyout_addr(w->pfrw_addr, ke); w->pfrw_addr++; } break; case PFRW_GET_ASTATS: if (w->pfrw_free-- > 0) { struct pfr_astats as; pfr_copyout_astats(&as, ke, w); bcopy(&as, w->pfrw_astats, sizeof(as)); w->pfrw_astats++; } break; case PFRW_POOL_GET: if (ke->pfrke_not) break; /* negative entries are ignored */ if (!w->pfrw_free--) { w->pfrw_kentry = ke; return (1); /* finish search */ } break; case PFRW_DYNADDR_UPDATE: { union sockaddr_union pfr_mask; switch (ke->pfrke_af) { case AF_INET: if (w->pfrw_dyn->pfid_acnt4++ > 0) break; pfr_prepare_network(&pfr_mask, AF_INET, ke->pfrke_net); pfr_sockaddr_to_pf_addr(&ke->pfrke_sa, &w->pfrw_dyn->pfid_addr4); pfr_sockaddr_to_pf_addr(&pfr_mask, &w->pfrw_dyn->pfid_mask4); break; case AF_INET6: if (w->pfrw_dyn->pfid_acnt6++ > 0) break; pfr_prepare_network(&pfr_mask, AF_INET6, ke->pfrke_net); pfr_sockaddr_to_pf_addr(&ke->pfrke_sa, &w->pfrw_dyn->pfid_addr6); pfr_sockaddr_to_pf_addr(&pfr_mask, &w->pfrw_dyn->pfid_mask6); break; default: unhandled_af(ke->pfrke_af); } break; } case PFRW_COUNTERS: { if (w->pfrw_flags & PFR_TFLAG_COUNTERS) { if (ke->pfrke_counters.pfrkc_counters != NULL) break; ke->pfrke_counters.pfrkc_counters = uma_zalloc_pcpu(V_pfr_kentry_counter_z, M_NOWAIT | M_ZERO); } else { uma_zfree_pcpu(V_pfr_kentry_counter_z, ke->pfrke_counters.pfrkc_counters); ke->pfrke_counters.pfrkc_counters = NULL; } break; } } return (0); } int pfr_clr_tables(struct pfr_table *filter, int *ndel, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p; int xdel = 0; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_ALLRSETS); if (pfr_fix_anchor(filter->pfrt_anchor)) return (EINVAL); if (pfr_table_count(filter, flags) < 0) return (ENOENT); SLIST_INIT(&workq); RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (pfr_skip_table(filter, p, flags)) continue; if (!strcmp(p->pfrkt_anchor, PF_RESERVED_ANCHOR)) continue; if (!(p->pfrkt_flags & PFR_TFLAG_ACTIVE)) continue; p->pfrkt_nflags = p->pfrkt_flags & ~PFR_TFLAG_ACTIVE; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); xdel++; } if (!(flags & PFR_FLAG_DUMMY)) pfr_setflags_ktables(&workq); if (ndel != NULL) *ndel = xdel; return (0); } int pfr_add_tables(struct pfr_table *tbl, int size, int *nadd, int flags) { struct pfr_ktableworkq addq, changeq; struct pfr_ktable *p, *q, *r, key; int i, rv, xadd = 0; time_t tzero = time_second; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); SLIST_INIT(&addq); SLIST_INIT(&changeq); for (i = 0; i < size; i++) { bcopy(tbl+i, &key.pfrkt_t, sizeof(key.pfrkt_t)); if (pfr_validate_table(&key.pfrkt_t, PFR_TFLAG_USRMASK, flags & PFR_FLAG_USERIOCTL)) senderr(EINVAL); key.pfrkt_flags |= PFR_TFLAG_ACTIVE; p = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (p == NULL) { p = pfr_create_ktable(&key.pfrkt_t, tzero, 1); if (p == NULL) senderr(ENOMEM); SLIST_FOREACH(q, &addq, pfrkt_workq) { if (!pfr_ktable_compare(p, q)) { pfr_destroy_ktable(p, 0); goto _skip; } } SLIST_INSERT_HEAD(&addq, p, pfrkt_workq); xadd++; if (!key.pfrkt_anchor[0]) goto _skip; /* find or create root table */ bzero(key.pfrkt_anchor, sizeof(key.pfrkt_anchor)); r = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (r != NULL) { p->pfrkt_root = r; goto _skip; } SLIST_FOREACH(q, &addq, pfrkt_workq) { if (!pfr_ktable_compare(&key, q)) { p->pfrkt_root = q; goto _skip; } } key.pfrkt_flags = 0; r = pfr_create_ktable(&key.pfrkt_t, 0, 1); if (r == NULL) senderr(ENOMEM); SLIST_INSERT_HEAD(&addq, r, pfrkt_workq); p->pfrkt_root = r; } else if (!(p->pfrkt_flags & PFR_TFLAG_ACTIVE)) { SLIST_FOREACH(q, &changeq, pfrkt_workq) if (!pfr_ktable_compare(&key, q)) goto _skip; p->pfrkt_nflags = (p->pfrkt_flags & ~PFR_TFLAG_USRMASK) | key.pfrkt_flags; SLIST_INSERT_HEAD(&changeq, p, pfrkt_workq); xadd++; } _skip: ; } if (!(flags & PFR_FLAG_DUMMY)) { pfr_insert_ktables(&addq); pfr_setflags_ktables(&changeq); } else pfr_destroy_ktables(&addq, 0); if (nadd != NULL) *nadd = xadd; return (0); _bad: pfr_destroy_ktables(&addq, 0); return (rv); } int pfr_del_tables(struct pfr_table *tbl, int size, int *ndel, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p, *q, key; int i, xdel = 0; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); SLIST_INIT(&workq); for (i = 0; i < size; i++) { bcopy(tbl+i, &key.pfrkt_t, sizeof(key.pfrkt_t)); if (pfr_validate_table(&key.pfrkt_t, 0, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); p = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (p != NULL && (p->pfrkt_flags & PFR_TFLAG_ACTIVE)) { SLIST_FOREACH(q, &workq, pfrkt_workq) if (!pfr_ktable_compare(p, q)) goto _skip; p->pfrkt_nflags = p->pfrkt_flags & ~PFR_TFLAG_ACTIVE; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); xdel++; } _skip: ; } if (!(flags & PFR_FLAG_DUMMY)) pfr_setflags_ktables(&workq); if (ndel != NULL) *ndel = xdel; return (0); } int pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size, int flags) { struct pfr_ktable *p; int n, nn; PF_RULES_RASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_ALLRSETS); if (pfr_fix_anchor(filter->pfrt_anchor)) return (EINVAL); n = nn = pfr_table_count(filter, flags); if (n < 0) return (ENOENT); if (n > *size) { *size = n; return (0); } RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (pfr_skip_table(filter, p, flags)) continue; if (n-- <= 0) continue; bcopy(&p->pfrkt_t, tbl++, sizeof(*tbl)); } KASSERT(n == 0, ("%s: corruption detected (%d)", __func__, n)); *size = nn; return (0); } int pfr_get_tstats(struct pfr_table *filter, struct pfr_tstats *tbl, int *size, int flags) { struct pfr_ktable *p; struct pfr_ktableworkq workq; int n, nn; time_t tzero = time_second; int pfr_dir, pfr_op; /* XXX PFR_FLAG_CLSTATS disabled */ ACCEPT_FLAGS(flags, PFR_FLAG_ALLRSETS); if (pfr_fix_anchor(filter->pfrt_anchor)) return (EINVAL); n = nn = pfr_table_count(filter, flags); if (n < 0) return (ENOENT); if (n > *size) { *size = n; return (0); } SLIST_INIT(&workq); RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (pfr_skip_table(filter, p, flags)) continue; if (n-- <= 0) continue; bcopy(&p->pfrkt_kts.pfrts_t, &tbl->pfrts_t, sizeof(struct pfr_table)); for (pfr_dir = 0; pfr_dir < PFR_DIR_MAX; pfr_dir ++) { for (pfr_op = 0; pfr_op < PFR_OP_TABLE_MAX; pfr_op ++) { tbl->pfrts_packets[pfr_dir][pfr_op] = pfr_kstate_counter_fetch( &p->pfrkt_packets[pfr_dir][pfr_op]); tbl->pfrts_bytes[pfr_dir][pfr_op] = pfr_kstate_counter_fetch( &p->pfrkt_bytes[pfr_dir][pfr_op]); } } tbl->pfrts_match = pfr_kstate_counter_fetch(&p->pfrkt_match); tbl->pfrts_nomatch = pfr_kstate_counter_fetch(&p->pfrkt_nomatch); tbl->pfrts_tzero = p->pfrkt_tzero; tbl->pfrts_cnt = p->pfrkt_cnt; for (pfr_op = 0; pfr_op < PFR_REFCNT_MAX; pfr_op++) tbl->pfrts_refcnt[pfr_op] = p->pfrkt_refcnt[pfr_op]; tbl++; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); } if (flags & PFR_FLAG_CLSTATS) pfr_clstats_ktables(&workq, tzero, flags & PFR_FLAG_ADDRSTOO); KASSERT(n == 0, ("%s: corruption detected (%d)", __func__, n)); *size = nn; return (0); } int pfr_clr_tstats(struct pfr_table *tbl, int size, int *nzero, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p, key; int i, xzero = 0; time_t tzero = time_second; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_ADDRSTOO); SLIST_INIT(&workq); for (i = 0; i < size; i++) { bcopy(tbl + i, &key.pfrkt_t, sizeof(key.pfrkt_t)); if (pfr_validate_table(&key.pfrkt_t, 0, 0)) return (EINVAL); p = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (p != NULL) { SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); xzero++; } } if (!(flags & PFR_FLAG_DUMMY)) pfr_clstats_ktables(&workq, tzero, flags & PFR_FLAG_ADDRSTOO); if (nzero != NULL) *nzero = xzero; return (0); } int pfr_set_tflags(struct pfr_table *tbl, int size, int setflag, int clrflag, int *nchange, int *ndel, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p, *q, key; int i, xchange = 0, xdel = 0; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); if ((setflag & ~PFR_TFLAG_USRMASK) || (clrflag & ~PFR_TFLAG_USRMASK) || (setflag & clrflag)) return (EINVAL); SLIST_INIT(&workq); for (i = 0; i < size; i++) { bcopy(tbl + i, &key.pfrkt_t, sizeof(key.pfrkt_t)); if (pfr_validate_table(&key.pfrkt_t, 0, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); p = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (p != NULL && (p->pfrkt_flags & PFR_TFLAG_ACTIVE)) { p->pfrkt_nflags = (p->pfrkt_flags | setflag) & ~clrflag; if (p->pfrkt_nflags == p->pfrkt_flags) goto _skip; SLIST_FOREACH(q, &workq, pfrkt_workq) if (!pfr_ktable_compare(p, q)) goto _skip; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); if ((p->pfrkt_flags & PFR_TFLAG_PERSIST) && (clrflag & PFR_TFLAG_PERSIST) && !(p->pfrkt_flags & PFR_TFLAG_REFERENCED)) xdel++; else xchange++; } _skip: ; } if (!(flags & PFR_FLAG_DUMMY)) pfr_setflags_ktables(&workq); if (nchange != NULL) *nchange = xchange; if (ndel != NULL) *ndel = xdel; return (0); } int pfr_ina_begin(struct pfr_table *trs, u_int32_t *ticket, int *ndel, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p; struct pf_kruleset *rs; int xdel = 0; ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); rs = pf_find_or_create_kruleset(trs->pfrt_anchor); if (rs == NULL) return (ENOMEM); SLIST_INIT(&workq); RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (!(p->pfrkt_flags & PFR_TFLAG_INACTIVE) || pfr_skip_table(trs, p, 0)) continue; p->pfrkt_nflags = p->pfrkt_flags & ~PFR_TFLAG_INACTIVE; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); xdel++; } if (!(flags & PFR_FLAG_DUMMY)) { pfr_setflags_ktables(&workq); if (ticket != NULL) *ticket = ++rs->tticket; rs->topen = 1; } else pf_remove_if_empty_kruleset(rs); if (ndel != NULL) *ndel = xdel; return (0); } int pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int *naddr, u_int32_t ticket, int flags) { struct pfr_ktableworkq tableq; struct pfr_kentryworkq addrq; struct pfr_ktable *kt, *rt, *shadow, key; struct pfr_kentry *p; struct pfr_addr *ad; struct pf_kruleset *rs; int i, rv, xadd = 0, xaddr = 0; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY | PFR_FLAG_ADDRSTOO); if (size && !(flags & PFR_FLAG_ADDRSTOO)) return (EINVAL); if (pfr_validate_table(tbl, PFR_TFLAG_USRMASK, flags & PFR_FLAG_USERIOCTL)) return (EINVAL); rs = pf_find_kruleset(tbl->pfrt_anchor); if (rs == NULL || !rs->topen || ticket != rs->tticket) return (EBUSY); tbl->pfrt_flags |= PFR_TFLAG_INACTIVE; SLIST_INIT(&tableq); kt = RB_FIND(pfr_ktablehead, &V_pfr_ktables, (struct pfr_ktable *)tbl); if (kt == NULL) { kt = pfr_create_ktable(tbl, 0, 1); if (kt == NULL) return (ENOMEM); SLIST_INSERT_HEAD(&tableq, kt, pfrkt_workq); xadd++; if (!tbl->pfrt_anchor[0]) goto _skip; /* find or create root table */ bzero(&key, sizeof(key)); strlcpy(key.pfrkt_name, tbl->pfrt_name, sizeof(key.pfrkt_name)); rt = RB_FIND(pfr_ktablehead, &V_pfr_ktables, &key); if (rt != NULL) { kt->pfrkt_root = rt; goto _skip; } rt = pfr_create_ktable(&key.pfrkt_t, 0, 1); if (rt == NULL) { pfr_destroy_ktables(&tableq, 0); return (ENOMEM); } SLIST_INSERT_HEAD(&tableq, rt, pfrkt_workq); kt->pfrkt_root = rt; } else if (!(kt->pfrkt_flags & PFR_TFLAG_INACTIVE)) xadd++; _skip: shadow = pfr_create_ktable(tbl, 0, 0); if (shadow == NULL) { pfr_destroy_ktables(&tableq, 0); return (ENOMEM); } SLIST_INIT(&addrq); for (i = 0, ad = addr; i < size; i++, ad++) { if (pfr_validate_addr(ad)) senderr(EINVAL); if (pfr_lookup_addr(shadow, ad, 1) != NULL) continue; p = pfr_create_kentry(ad, (shadow->pfrkt_flags & PFR_TFLAG_COUNTERS) != 0); if (p == NULL) senderr(ENOMEM); if (pfr_route_kentry(shadow, p)) { pfr_destroy_kentry(p); continue; } SLIST_INSERT_HEAD(&addrq, p, pfrke_workq); xaddr++; } if (!(flags & PFR_FLAG_DUMMY)) { if (kt->pfrkt_shadow != NULL) pfr_destroy_ktable(kt->pfrkt_shadow, 1); kt->pfrkt_flags |= PFR_TFLAG_INACTIVE; pfr_insert_ktables(&tableq); shadow->pfrkt_cnt = (flags & PFR_FLAG_ADDRSTOO) ? xaddr : NO_ADDRESSES; kt->pfrkt_shadow = shadow; } else { pfr_clean_node_mask(shadow, &addrq); pfr_destroy_ktable(shadow, 0); pfr_destroy_ktables(&tableq, 0); pfr_destroy_kentries(&addrq); } if (nadd != NULL) *nadd = xadd; if (naddr != NULL) *naddr = xaddr; return (0); _bad: pfr_destroy_ktable(shadow, 0); pfr_destroy_ktables(&tableq, 0); pfr_destroy_kentries(&addrq); return (rv); } int pfr_ina_rollback(struct pfr_table *trs, u_int32_t ticket, int *ndel, int flags) { struct pfr_ktableworkq workq; struct pfr_ktable *p; struct pf_kruleset *rs; int xdel = 0; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); rs = pf_find_kruleset(trs->pfrt_anchor); if (rs == NULL || !rs->topen || ticket != rs->tticket) return (0); SLIST_INIT(&workq); RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (!(p->pfrkt_flags & PFR_TFLAG_INACTIVE) || pfr_skip_table(trs, p, 0)) continue; p->pfrkt_nflags = p->pfrkt_flags & ~PFR_TFLAG_INACTIVE; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); xdel++; } if (!(flags & PFR_FLAG_DUMMY)) { pfr_setflags_ktables(&workq); rs->topen = 0; pf_remove_if_empty_kruleset(rs); } if (ndel != NULL) *ndel = xdel; return (0); } int pfr_ina_commit(struct pfr_table *trs, u_int32_t ticket, int *nadd, int *nchange, int flags) { struct pfr_ktable *p, *q; struct pfr_ktableworkq workq; struct pf_kruleset *rs; int xadd = 0, xchange = 0; time_t tzero = time_second; PF_RULES_WASSERT(); ACCEPT_FLAGS(flags, PFR_FLAG_DUMMY); rs = pf_find_kruleset(trs->pfrt_anchor); if (rs == NULL || !rs->topen || ticket != rs->tticket) return (EBUSY); SLIST_INIT(&workq); RB_FOREACH(p, pfr_ktablehead, &V_pfr_ktables) { if (!(p->pfrkt_flags & PFR_TFLAG_INACTIVE) || pfr_skip_table(trs, p, 0)) continue; SLIST_INSERT_HEAD(&workq, p, pfrkt_workq); if (p->pfrkt_flags & PFR_TFLAG_ACTIVE) xchange++; else xadd++; } if (!(flags & PFR_FLAG_DUMMY)) { SLIST_FOREACH_SAFE(p, &workq, pfrkt_workq, q) { pfr_commit_ktable(p, tzero); } rs->topen = 0; pf_remove_if_empty_kruleset(rs); } if (nadd != NULL) *nadd = xadd; if (nchange != NULL) *nchange = xchange; return (0); } static void pfr_commit_ktable(struct pfr_ktable *kt, time_t tzero) { counter_u64_t *pkc, *qkc; struct pfr_ktable *shadow = kt->pfrkt_shadow; int nflags; PF_RULES_WASSERT(); if (shadow->pfrkt_cnt == NO_ADDRESSES) { if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) pfr_clstats_ktable(kt, tzero, 1); } else if (kt->pfrkt_flags & PFR_TFLAG_ACTIVE) { /* kt might contain addresses */ struct pfr_kentryworkq addrq, addq, changeq, delq, garbageq; struct pfr_kentry *p, *q; struct pfr_addr ad; pfr_enqueue_addrs(shadow, &addrq, NULL, 0); pfr_mark_addrs(kt); SLIST_INIT(&addq); SLIST_INIT(&changeq); SLIST_INIT(&delq); SLIST_INIT(&garbageq); pfr_clean_node_mask(shadow, &addrq); while ((p = SLIST_FIRST(&addrq)) != NULL) { SLIST_REMOVE_HEAD(&addrq, pfrke_workq); pfr_copyout_addr(&ad, p); q = pfr_lookup_addr(kt, &ad, 1); if (q != NULL) { if (q->pfrke_not != p->pfrke_not) SLIST_INSERT_HEAD(&changeq, q, pfrke_workq); pkc = &p->pfrke_counters.pfrkc_counters; qkc = &q->pfrke_counters.pfrkc_counters; if ((*pkc == NULL) != (*qkc == NULL)) SWAP(counter_u64_t, *pkc, *qkc); q->pfrke_mark = 1; SLIST_INSERT_HEAD(&garbageq, p, pfrke_workq); } else { p->pfrke_counters.pfrkc_tzero = tzero; SLIST_INSERT_HEAD(&addq, p, pfrke_workq); } } pfr_enqueue_addrs(kt, &delq, NULL, ENQUEUE_UNMARKED_ONLY); pfr_insert_kentries(kt, &addq, tzero); pfr_remove_kentries(kt, &delq); pfr_clstats_kentries(kt, &changeq, tzero, INVERT_NEG_FLAG); pfr_destroy_kentries(&garbageq); } else { /* kt cannot contain addresses */ SWAP(struct radix_node_head *, kt->pfrkt_ip4, shadow->pfrkt_ip4); SWAP(struct radix_node_head *, kt->pfrkt_ip6, shadow->pfrkt_ip6); SWAP(int, kt->pfrkt_cnt, shadow->pfrkt_cnt); pfr_clstats_ktable(kt, tzero, 1); } nflags = ((shadow->pfrkt_flags & PFR_TFLAG_USRMASK) | (kt->pfrkt_flags & PFR_TFLAG_SETMASK) | PFR_TFLAG_ACTIVE) & ~PFR_TFLAG_INACTIVE; pfr_destroy_ktable(shadow, 0); kt->pfrkt_shadow = NULL; pfr_setflags_ktable(kt, nflags); } static int pfr_validate_table(struct pfr_table *tbl, int allowedflags, int no_reserved) { int i; if (!tbl->pfrt_name[0]) return (-1); if (no_reserved && !strcmp(tbl->pfrt_anchor, PF_RESERVED_ANCHOR)) return (-1); if (tbl->pfrt_name[PF_TABLE_NAME_SIZE-1]) return (-1); for (i = strlen(tbl->pfrt_name); i < PF_TABLE_NAME_SIZE; i++) if (tbl->pfrt_name[i]) return (-1); if (pfr_fix_anchor(tbl->pfrt_anchor)) return (-1); if (tbl->pfrt_flags & ~allowedflags) return (-1); return (0); } /* * Rewrite anchors referenced by tables to remove slashes * and check for validity. */ static int pfr_fix_anchor(char *anchor) { size_t siz = MAXPATHLEN; int i; if (anchor[0] == '/') { char *path; int off; path = anchor; off = 1; while (*++path == '/') off++; bcopy(path, anchor, siz - off); memset(anchor + siz - off, 0, off); } if (anchor[siz - 1]) return (-1); for (i = strlen(anchor); i < siz; i++) if (anchor[i]) return (-1); return (0); } int pfr_table_count(struct pfr_table *filter, int flags) { struct pf_kruleset *rs; PF_RULES_ASSERT(); if (flags & PFR_FLAG_ALLRSETS) return (V_pfr_ktable_cnt); if (filter->pfrt_anchor[0]) { rs = pf_find_kruleset(filter->pfrt_anchor); return ((rs != NULL) ? rs->tables : -1); } return (pf_main_ruleset.tables); } static int pfr_skip_table(struct pfr_table *filter, struct pfr_ktable *kt, int flags) { if (flags & PFR_FLAG_ALLRSETS) return (0); if (strcmp(filter->pfrt_anchor, kt->pfrkt_anchor)) return (1); return (0); } static void pfr_insert_ktables(struct pfr_ktableworkq *workq) { struct pfr_ktable *p; SLIST_FOREACH(p, workq, pfrkt_workq) pfr_insert_ktable(p); } static void pfr_insert_ktable(struct pfr_ktable *kt) { PF_RULES_WASSERT(); RB_INSERT(pfr_ktablehead, &V_pfr_ktables, kt); V_pfr_ktable_cnt++; if (kt->pfrkt_root != NULL) if (!kt->pfrkt_root->pfrkt_refcnt[PFR_REFCNT_ANCHOR]++) pfr_setflags_ktable(kt->pfrkt_root, kt->pfrkt_root->pfrkt_flags|PFR_TFLAG_REFDANCHOR); } static void pfr_setflags_ktables(struct pfr_ktableworkq *workq) { struct pfr_ktable *p, *q; SLIST_FOREACH_SAFE(p, workq, pfrkt_workq, q) { pfr_setflags_ktable(p, p->pfrkt_nflags); } } static void pfr_setflags_ktable(struct pfr_ktable *kt, int newf) { struct pfr_kentryworkq addrq; struct pfr_walktree w; PF_RULES_WASSERT(); if (!(newf & PFR_TFLAG_REFERENCED) && !(newf & PFR_TFLAG_REFDANCHOR) && !(newf & PFR_TFLAG_PERSIST)) newf &= ~PFR_TFLAG_ACTIVE; if (!(newf & PFR_TFLAG_ACTIVE)) newf &= ~PFR_TFLAG_USRMASK; if (!(newf & PFR_TFLAG_SETMASK)) { RB_REMOVE(pfr_ktablehead, &V_pfr_ktables, kt); if (kt->pfrkt_root != NULL) if (!--kt->pfrkt_root->pfrkt_refcnt[PFR_REFCNT_ANCHOR]) pfr_setflags_ktable(kt->pfrkt_root, kt->pfrkt_root->pfrkt_flags & ~PFR_TFLAG_REFDANCHOR); pfr_destroy_ktable(kt, 1); V_pfr_ktable_cnt--; return; } if (newf & PFR_TFLAG_COUNTERS && ! (kt->pfrkt_flags & PFR_TFLAG_COUNTERS)) { bzero(&w, sizeof(w)); w.pfrw_op = PFRW_COUNTERS; w.pfrw_flags |= PFR_TFLAG_COUNTERS; kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); } if (! (newf & PFR_TFLAG_COUNTERS) && (kt->pfrkt_flags & PFR_TFLAG_COUNTERS)) { bzero(&w, sizeof(w)); w.pfrw_op = PFRW_COUNTERS; w.pfrw_flags |= 0; kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); } if (!(newf & PFR_TFLAG_ACTIVE) && kt->pfrkt_cnt) { pfr_enqueue_addrs(kt, &addrq, NULL, 0); pfr_remove_kentries(kt, &addrq); } if (!(newf & PFR_TFLAG_INACTIVE) && kt->pfrkt_shadow != NULL) { pfr_destroy_ktable(kt->pfrkt_shadow, 1); kt->pfrkt_shadow = NULL; } kt->pfrkt_flags = newf; } static void pfr_clstats_ktables(struct pfr_ktableworkq *workq, time_t tzero, int recurse) { struct pfr_ktable *p; SLIST_FOREACH(p, workq, pfrkt_workq) pfr_clstats_ktable(p, tzero, recurse); } static void pfr_clstats_ktable(struct pfr_ktable *kt, time_t tzero, int recurse) { struct pfr_kentryworkq addrq; int pfr_dir, pfr_op; MPASS(PF_TABLE_STATS_OWNED() || PF_RULES_WOWNED()); if (recurse) { pfr_enqueue_addrs(kt, &addrq, NULL, 0); pfr_clstats_kentries(kt, &addrq, tzero, 0); } for (pfr_dir = 0; pfr_dir < PFR_DIR_MAX; pfr_dir ++) { for (pfr_op = 0; pfr_op < PFR_OP_TABLE_MAX; pfr_op ++) { pfr_kstate_counter_zero(&kt->pfrkt_packets[pfr_dir][pfr_op]); pfr_kstate_counter_zero(&kt->pfrkt_bytes[pfr_dir][pfr_op]); } } pfr_kstate_counter_zero(&kt->pfrkt_match); pfr_kstate_counter_zero(&kt->pfrkt_nomatch); kt->pfrkt_tzero = tzero; } static struct pfr_ktable * pfr_create_ktable(struct pfr_table *tbl, time_t tzero, int attachruleset) { struct pfr_ktable *kt; struct pf_kruleset *rs; int pfr_dir, pfr_op; PF_RULES_WASSERT(); kt = malloc(sizeof(*kt), M_PFTABLE, M_NOWAIT|M_ZERO); if (kt == NULL) return (NULL); kt->pfrkt_t = *tbl; if (attachruleset) { rs = pf_find_or_create_kruleset(tbl->pfrt_anchor); if (!rs) { pfr_destroy_ktable(kt, 0); return (NULL); } kt->pfrkt_rs = rs; rs->tables++; } for (pfr_dir = 0; pfr_dir < PFR_DIR_MAX; pfr_dir ++) { for (pfr_op = 0; pfr_op < PFR_OP_TABLE_MAX; pfr_op ++) { if (pfr_kstate_counter_init( &kt->pfrkt_packets[pfr_dir][pfr_op], M_NOWAIT) != 0) { pfr_destroy_ktable(kt, 0); return (NULL); } if (pfr_kstate_counter_init( &kt->pfrkt_bytes[pfr_dir][pfr_op], M_NOWAIT) != 0) { pfr_destroy_ktable(kt, 0); return (NULL); } } } if (pfr_kstate_counter_init(&kt->pfrkt_match, M_NOWAIT) != 0) { pfr_destroy_ktable(kt, 0); return (NULL); } if (pfr_kstate_counter_init(&kt->pfrkt_nomatch, M_NOWAIT) != 0) { pfr_destroy_ktable(kt, 0); return (NULL); } if (!rn_inithead((void **)&kt->pfrkt_ip4, offsetof(struct sockaddr_in, sin_addr) * 8) || !rn_inithead((void **)&kt->pfrkt_ip6, offsetof(struct sockaddr_in6, sin6_addr) * 8)) { pfr_destroy_ktable(kt, 0); return (NULL); } kt->pfrkt_tzero = tzero; return (kt); } static void pfr_destroy_ktables(struct pfr_ktableworkq *workq, int flushaddr) { struct pfr_ktable *p; while ((p = SLIST_FIRST(workq)) != NULL) { SLIST_REMOVE_HEAD(workq, pfrkt_workq); pfr_destroy_ktable(p, flushaddr); } } static void pfr_destroy_ktable(struct pfr_ktable *kt, int flushaddr) { struct pfr_kentryworkq addrq; int pfr_dir, pfr_op; if (flushaddr) { pfr_enqueue_addrs(kt, &addrq, NULL, 0); pfr_clean_node_mask(kt, &addrq); pfr_destroy_kentries(&addrq); } if (kt->pfrkt_ip4 != NULL) rn_detachhead((void **)&kt->pfrkt_ip4); if (kt->pfrkt_ip6 != NULL) rn_detachhead((void **)&kt->pfrkt_ip6); if (kt->pfrkt_shadow != NULL) pfr_destroy_ktable(kt->pfrkt_shadow, flushaddr); if (kt->pfrkt_rs != NULL) { kt->pfrkt_rs->tables--; pf_remove_if_empty_kruleset(kt->pfrkt_rs); } for (pfr_dir = 0; pfr_dir < PFR_DIR_MAX; pfr_dir ++) { for (pfr_op = 0; pfr_op < PFR_OP_TABLE_MAX; pfr_op ++) { pfr_kstate_counter_deinit(&kt->pfrkt_packets[pfr_dir][pfr_op]); pfr_kstate_counter_deinit(&kt->pfrkt_bytes[pfr_dir][pfr_op]); } } pfr_kstate_counter_deinit(&kt->pfrkt_match); pfr_kstate_counter_deinit(&kt->pfrkt_nomatch); free(kt, M_PFTABLE); } static int pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q) { int d; if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE))) return (d); return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor)); } static struct pfr_ktable * pfr_lookup_table(struct pfr_table *tbl) { /* struct pfr_ktable start like a struct pfr_table */ return (RB_FIND(pfr_ktablehead, &V_pfr_ktables, (struct pfr_ktable *)tbl)); } struct pfr_kentry * pfr_kentry_byaddr(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af, int exact) { struct pfr_kentry *ke = NULL; PF_RULES_RASSERT(); kt = pfr_ktable_select_active(kt); if (kt == NULL) return (0); switch (af) { #ifdef INET case AF_INET: { struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = a->addr32[0]; ke = (struct pfr_kentry *)rn_match(&sin, &kt->pfrkt_ip4->rh); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { struct sockaddr_in6 sin6; bzero(&sin6, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; bcopy(a, &sin6.sin6_addr, sizeof(sin6.sin6_addr)); ke = (struct pfr_kentry *)rn_match(&sin6, &kt->pfrkt_ip6->rh); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; break; } #endif /* INET6 */ default: unhandled_af(af); } if (exact && ke && KENTRY_NETWORK(ke)) ke = NULL; return (ke); } int pfr_match_addr(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af) { struct pfr_kentry *ke = NULL; int match; ke = pfr_kentry_byaddr(kt, a, af, 0); match = (ke && !ke->pfrke_not); if (match) pfr_kstate_counter_add(&kt->pfrkt_match, 1); else pfr_kstate_counter_add(&kt->pfrkt_nomatch, 1); return (match); } void pfr_update_stats(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af, u_int64_t len, int dir_out, int op_pass, int notrule) { struct pfr_kentry *ke = NULL; kt = pfr_ktable_select_active(kt); if (kt == NULL) return; switch (af) { #ifdef INET case AF_INET: { struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = a->addr32[0]; ke = (struct pfr_kentry *)rn_match(&sin, &kt->pfrkt_ip4->rh); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; break; } #endif /* INET */ #ifdef INET6 case AF_INET6: { struct sockaddr_in6 sin6; bzero(&sin6, sizeof(sin6)); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; bcopy(a, &sin6.sin6_addr, sizeof(sin6.sin6_addr)); ke = (struct pfr_kentry *)rn_match(&sin6, &kt->pfrkt_ip6->rh); if (ke && KENTRY_RNF_ROOT(ke)) ke = NULL; break; } #endif /* INET6 */ default: unhandled_af(af); } if ((ke == NULL || ke->pfrke_not) != notrule) { if (op_pass != PFR_OP_PASS) DPFPRINTF(PF_DEBUG_URGENT, "pfr_update_stats: assertion failed."); op_pass = PFR_OP_XPASS; } pfr_kstate_counter_add(&kt->pfrkt_packets[dir_out][op_pass], 1); pfr_kstate_counter_add(&kt->pfrkt_bytes[dir_out][op_pass], len); if (ke != NULL && op_pass != PFR_OP_XPASS && (kt->pfrkt_flags & PFR_TFLAG_COUNTERS)) { counter_u64_add(pfr_kentry_counter(&ke->pfrke_counters, dir_out, op_pass, PFR_TYPE_PACKETS), 1); counter_u64_add(pfr_kentry_counter(&ke->pfrke_counters, dir_out, op_pass, PFR_TYPE_BYTES), len); } } struct pfr_ktable * pfr_eth_attach_table(struct pf_keth_ruleset *rs, char *name) { struct pfr_ktable *kt, *rt; struct pfr_table tbl; struct pf_keth_anchor *ac = rs->anchor; PF_RULES_WASSERT(); bzero(&tbl, sizeof(tbl)); strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)); if (ac != NULL) strlcpy(tbl.pfrt_anchor, ac->path, sizeof(tbl.pfrt_anchor)); kt = pfr_lookup_table(&tbl); if (kt == NULL) { kt = pfr_create_ktable(&tbl, time_second, 1); if (kt == NULL) return (NULL); if (ac != NULL) { bzero(tbl.pfrt_anchor, sizeof(tbl.pfrt_anchor)); rt = pfr_lookup_table(&tbl); if (rt == NULL) { rt = pfr_create_ktable(&tbl, 0, 1); if (rt == NULL) { pfr_destroy_ktable(kt, 0); return (NULL); } pfr_insert_ktable(rt); } kt->pfrkt_root = rt; } pfr_insert_ktable(kt); } if (!kt->pfrkt_refcnt[PFR_REFCNT_RULE]++) pfr_setflags_ktable(kt, kt->pfrkt_flags|PFR_TFLAG_REFERENCED); return (kt); } struct pfr_ktable * pfr_attach_table(struct pf_kruleset *rs, char *name) { struct pfr_ktable *kt, *rt; struct pfr_table tbl; struct pf_kanchor *ac = rs->anchor; PF_RULES_WASSERT(); bzero(&tbl, sizeof(tbl)); strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)); if (ac != NULL) strlcpy(tbl.pfrt_anchor, ac->path, sizeof(tbl.pfrt_anchor)); kt = pfr_lookup_table(&tbl); if (kt == NULL) { kt = pfr_create_ktable(&tbl, time_second, 1); if (kt == NULL) return (NULL); if (ac != NULL) { bzero(tbl.pfrt_anchor, sizeof(tbl.pfrt_anchor)); rt = pfr_lookup_table(&tbl); if (rt == NULL) { rt = pfr_create_ktable(&tbl, 0, 1); if (rt == NULL) { pfr_destroy_ktable(kt, 0); return (NULL); } pfr_insert_ktable(rt); } kt->pfrkt_root = rt; } pfr_insert_ktable(kt); } if (!kt->pfrkt_refcnt[PFR_REFCNT_RULE]++) pfr_setflags_ktable(kt, kt->pfrkt_flags|PFR_TFLAG_REFERENCED); return (kt); } void pfr_detach_table(struct pfr_ktable *kt) { PF_RULES_WASSERT(); KASSERT(kt->pfrkt_refcnt[PFR_REFCNT_RULE] > 0, ("%s: refcount %d\n", __func__, kt->pfrkt_refcnt[PFR_REFCNT_RULE])); if (!--kt->pfrkt_refcnt[PFR_REFCNT_RULE]) pfr_setflags_ktable(kt, kt->pfrkt_flags&~PFR_TFLAG_REFERENCED); } int pfr_pool_get(struct pfr_ktable *kt, int *pidx, struct pf_addr *counter, sa_family_t af, pf_addr_filter_func_t filter, bool loop_once) { struct pf_addr *addr, cur, mask, umask_addr; union sockaddr_union uaddr, umask; struct pfr_kentry *ke, *ke2 = NULL; int startidx, idx = -1, loop = 0, use_counter = 0; MPASS(pidx != NULL); MPASS(counter != NULL); switch (af) { case AF_INET: uaddr.sin.sin_len = sizeof(struct sockaddr_in); uaddr.sin.sin_family = AF_INET; addr = (struct pf_addr *)&uaddr.sin.sin_addr; break; case AF_INET6: uaddr.sin6.sin6_len = sizeof(struct sockaddr_in6); uaddr.sin6.sin6_family = AF_INET6; addr = (struct pf_addr *)&uaddr.sin6.sin6_addr; break; default: unhandled_af(af); } kt = pfr_ktable_select_active(kt); if (kt == NULL) return (-1); idx = *pidx; if (idx < 0 || idx >= kt->pfrkt_cnt) idx = 0; else if (counter != NULL) use_counter = 1; startidx = idx; _next_block: if (loop && startidx == idx) { pfr_kstate_counter_add(&kt->pfrkt_nomatch, 1); return (1); } ke = pfr_kentry_byidx(kt, idx, af); if (ke == NULL) { /* we don't have this idx, try looping */ if ((loop || loop_once) || (ke = pfr_kentry_byidx(kt, 0, af)) == NULL) { pfr_kstate_counter_add(&kt->pfrkt_nomatch, 1); return (1); } idx = 0; loop++; } pfr_prepare_network(&umask, af, ke->pfrke_net); pfr_sockaddr_to_pf_addr(&ke->pfrke_sa, &cur); pfr_sockaddr_to_pf_addr(&umask, &mask); if (use_counter && !PF_AZERO(counter, af)) { /* is supplied address within block? */ if (!pf_match_addr(0, &cur, &mask, counter, af)) { /* no, go to next block in table */ idx++; use_counter = 0; goto _next_block; } pf_addrcpy(addr, counter, af); } else { /* use first address of block */ pf_addrcpy(addr, &cur, af); } if (!KENTRY_NETWORK(ke)) { /* this is a single IP address - no possible nested block */ if (filter && filter(af, addr)) { idx++; goto _next_block; } pf_addrcpy(counter, addr, af); *pidx = idx; pfr_kstate_counter_add(&kt->pfrkt_match, 1); return (0); } for (;;) { /* we don't want to use a nested block */ switch (af) { case AF_INET: ke2 = (struct pfr_kentry *)rn_match(&uaddr, &kt->pfrkt_ip4->rh); break; case AF_INET6: ke2 = (struct pfr_kentry *)rn_match(&uaddr, &kt->pfrkt_ip6->rh); break; default: unhandled_af(af); } /* no need to check KENTRY_RNF_ROOT() here */ if (ke2 == ke) { /* lookup return the same block - perfect */ if (filter && filter(af, addr)) goto _next_entry; pf_addrcpy(counter, addr, af); *pidx = idx; pfr_kstate_counter_add(&kt->pfrkt_match, 1); return (0); } _next_entry: /* we need to increase the counter past the nested block */ pfr_prepare_network(&umask, AF_INET, ke2->pfrke_net); pfr_sockaddr_to_pf_addr(&umask, &umask_addr); pf_poolmask(addr, addr, &umask_addr, &pfr_ffaddr, af); pf_addr_inc(addr, af); if (!pf_match_addr(0, &cur, &mask, addr, af)) { /* ok, we reached the end of our main block */ /* go to next block in table */ idx++; use_counter = 0; goto _next_block; } } } static struct pfr_kentry * pfr_kentry_byidx(struct pfr_ktable *kt, int idx, int af) { struct pfr_walktree w; bzero(&w, sizeof(w)); w.pfrw_op = PFRW_POOL_GET; w.pfrw_free = idx; switch (af) { #ifdef INET case AF_INET: kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); return (w.pfrw_kentry); #endif /* INET */ #ifdef INET6 case AF_INET6: kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); return (w.pfrw_kentry); #endif /* INET6 */ default: return (NULL); } } void pfr_dynaddr_update(struct pfr_ktable *kt, struct pfi_dynaddr *dyn) { struct pfr_walktree w; bzero(&w, sizeof(w)); w.pfrw_op = PFRW_DYNADDR_UPDATE; w.pfrw_dyn = dyn; dyn->pfid_acnt4 = 0; dyn->pfid_acnt6 = 0; switch (dyn->pfid_af) { case AF_UNSPEC: /* look up all both addresses IPv4 + IPv6 */ kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); break; case AF_INET: kt->pfrkt_ip4->rnh_walktree(&kt->pfrkt_ip4->rh, pfr_walktree, &w); break; case AF_INET6: kt->pfrkt_ip6->rnh_walktree(&kt->pfrkt_ip6->rh, pfr_walktree, &w); break; default: unhandled_af(dyn->pfid_af); } } struct pfr_ktable * pfr_ktable_select_active(struct pfr_ktable *kt) { if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE) && kt->pfrkt_root != NULL) kt = kt->pfrkt_root; if (!(kt->pfrkt_flags & PFR_TFLAG_ACTIVE)) return (NULL); return (kt); }