diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 03f80baa2178..ca29645f9c34 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1,1619 +1,1685 @@ /*- * 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 "libpfctl.h" const char* PFCTL_SYNCOOKIES_MODE_NAMES[] = { "never", "always", "adaptive" }; static int _pfctl_clear_states(int , const struct pfctl_kill *, unsigned int *, uint64_t); 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); memcpy(nv.data, data, nvlen); free(data); 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) { free(nv.data); return (EIO); } } else { ret = errno; } 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); assert(elems <= maxelems); for (size_t i = 0; i < elems; 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; } 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)); c->id = ids[i]; c->counter = counts[i]; c->name = strdup(names[i]); TAILQ_INSERT_TAIL(counters, c, entry); } } struct pfctl_status * pfctl_get_status(int dev) { struct pfctl_status *status; nvlist_t *nvl; size_t len; const void *chksum; status = calloc(1, sizeof(*status)); if (status == NULL) return (NULL); nvl = nvlist_create(0); if (pfctl_do_ioctl(dev, DIOCGETSTATUSNV, 4096, &nvl)) { free(status); return (NULL); } status->running = nvlist_get_bool(nvl, "running"); status->since = nvlist_get_number(nvl, "since"); status->debug = nvlist_get_number(nvl, "debug"); status->hostid = ntohl(nvlist_get_number(nvl, "hostid")); status->states = nvlist_get_number(nvl, "states"); status->src_nodes = nvlist_get_number(nvl, "src_nodes"); status->syncookies_active = nvlist_get_bool(nvl, "syncookies_active"); status->reass = nvlist_get_number(nvl, "reass"); strlcpy(status->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); chksum = nvlist_get_binary(nvl, "chksum", &len); assert(len == PF_MD5_DIGEST_LENGTH); memcpy(status->pf_chksum, chksum, len); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "counters"), &status->counters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "lcounters"), &status->lcounters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "fcounters"), &status->fcounters); _pfctl_get_status_counters(nvlist_get_nvlist(nvl, "scounters"), &status->scounters); pf_nvuint_64_array(nvl, "pcounters", 2 * 2 * 3, (uint64_t *)status->pcounters, NULL); pf_nvuint_64_array(nvl, "bcounters", 2 * 2, (uint64_t *)status->bcounters, NULL); nvlist_destroy(nvl); return (status); } static uint64_t _pfctl_status_counter(struct pfctl_status_counters *counters, uint64_t id) { struct pfctl_status_counter *c; TAILQ_FOREACH(c, counters, entry) { if (c->id == id) return (c->counter); } return (0); } uint64_t pfctl_status_counter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->counters, id)); } uint64_t pfctl_status_fcounter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->fcounters, id)); } uint64_t pfctl_status_scounter(struct pfctl_status *status, int id) { return (_pfctl_status_counter(&status->scounters, id)); } void pfctl_free_status(struct pfctl_status *status) { struct pfctl_status_counter *c, *tmp; if (status == NULL) return; TAILQ_FOREACH_SAFE(c, &status->counters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->lcounters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->fcounters, entry, tmp) { free(c->name); free(c); } TAILQ_FOREACH_SAFE(c, &status->scounters, entry, tmp) { free(c->name); free(c); } free(status); } static void pfctl_nv_add_addr(nvlist_t *nvparent, const char *name, const struct pf_addr *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "addr", addr, sizeof(*addr)); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvaddr_to_addr(const nvlist_t *nvl, struct pf_addr *addr) { size_t len; const void *data; data = nvlist_get_binary(nvl, "addr", &len); assert(len == sizeof(struct pf_addr)); memcpy(addr, data, len); } static void pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name, const struct pf_addr_wrap *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_number(nvl, "type", addr->type); nvlist_add_number(nvl, "iflags", addr->iflags); if (addr->type == PF_ADDR_DYNIFTL) nvlist_add_string(nvl, "ifname", addr->v.ifname); if (addr->type == PF_ADDR_TABLE) nvlist_add_string(nvl, "tblname", addr->v.tblname); pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr); pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvaddr_wrap_to_addr_wrap(const nvlist_t *nvl, struct pf_addr_wrap *addr) { bzero(addr, sizeof(*addr)); addr->type = nvlist_get_number(nvl, "type"); addr->iflags = nvlist_get_number(nvl, "iflags"); if (addr->type == PF_ADDR_DYNIFTL) { strlcpy(addr->v.ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); addr->p.dyncnt = nvlist_get_number(nvl, "dyncnt"); } if (addr->type == PF_ADDR_TABLE) { strlcpy(addr->v.tblname, nvlist_get_string(nvl, "tblname"), PF_TABLE_NAME_SIZE); addr->p.tblcnt = nvlist_get_number(nvl, "tblcnt"); } pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &addr->v.a.addr); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "mask"), &addr->v.a.mask); } static void pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name, const struct pf_rule_addr *addr) { uint64_t ports[2]; nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr); ports[0] = addr->port[0]; ports[1] = addr->port[1]; nvlist_add_number_array(nvl, "port", ports, 2); nvlist_add_number(nvl, "neg", addr->neg); nvlist_add_number(nvl, "port_op", addr->port_op); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvrule_addr_to_rule_addr(const nvlist_t *nvl, struct pf_rule_addr *addr) { pf_nvaddr_wrap_to_addr_wrap(nvlist_get_nvlist(nvl, "addr"), &addr->addr); pf_nvuint_16_array(nvl, "port", 2, addr->port, NULL); addr->neg = nvlist_get_number(nvl, "neg"); addr->port_op = nvlist_get_number(nvl, "port_op"); } static void pfctl_nv_add_mape(nvlist_t *nvparent, const char *name, const struct pf_mape_portset *mape) { nvlist_t *nvl = nvlist_create(0); nvlist_add_number(nvl, "offset", mape->offset); nvlist_add_number(nvl, "psidlen", mape->psidlen); nvlist_add_number(nvl, "psid", mape->psid); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pfctl_nv_add_pool(nvlist_t *nvparent, const char *name, const struct pfctl_pool *pool) { uint64_t ports[2]; nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "key", &pool->key, sizeof(pool->key)); pfctl_nv_add_addr(nvl, "counter", &pool->counter); nvlist_add_number(nvl, "tblidx", pool->tblidx); ports[0] = pool->proxy_port[0]; ports[1] = pool->proxy_port[1]; nvlist_add_number_array(nvl, "proxy_port", ports, 2); nvlist_add_number(nvl, "opts", pool->opts); pfctl_nv_add_mape(nvl, "mape", &pool->mape); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } 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 pfctl_nv_add_uid(nvlist_t *nvparent, const char *name, const struct pf_rule_uid *uid) { uint64_t uids[2]; nvlist_t *nvl = nvlist_create(0); uids[0] = uid->uid[0]; uids[1] = uid->uid[1]; nvlist_add_number_array(nvl, "uid", uids, 2); nvlist_add_number(nvl, "op", uid->op); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } 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 pfctl_nv_add_divert(nvlist_t *nvparent, const char *name, const struct pfctl_rule *r) { nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr(nvl, "addr", &r->divert.addr); nvlist_add_number(nvl, "port", r->divert.port); nvlist_add_nvlist(nvparent, name, nvl); nvlist_destroy(nvl); } static void pf_nvdivert_to_divert(const nvlist_t *nvl, struct pfctl_rule *rule) { pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &rule->divert.addr); rule->divert.port = nvlist_get_number(nvl, "port"); } static void pf_nvrule_to_rule(const nvlist_t *nvl, struct pfctl_rule *rule) { const uint64_t *skip; const char *const *labels; size_t skipcount, labelcount; rule->nr = nvlist_get_number(nvl, "nr"); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); skip = nvlist_get_number_array(nvl, "skip", &skipcount); assert(skip); assert(skipcount == PF_SKIP_COUNT); for (int i = 0; i < PF_SKIP_COUNT; i++) rule->skip[i].nr = skip[i]; labels = nvlist_get_string_array(nvl, "labels", &labelcount); assert(labelcount <= PF_RULE_MAX_LABEL_COUNT); for (size_t i = 0; i < labelcount; i++) strlcpy(rule->label[i], labels[i], PF_RULE_LABEL_SIZE); rule->ridentifier = nvlist_get_number(nvl, "ridentifier"); strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); strlcpy(rule->pqname, nvlist_get_string(nvl, "pqname"), PF_QNAME_SIZE); strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"), PF_TABLE_NAME_SIZE); pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool); rule->evaluations = nvlist_get_number(nvl, "evaluations"); pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL); pf_nvuint_64_array(nvl, "bytes", 2, rule->bytes, NULL); if (nvlist_exists_number(nvl, "timestamp")) { rule->last_active_timestamp = nvlist_get_number(nvl, "timestamp"); } rule->os_fingerprint = nvlist_get_number(nvl, "os_fingerprint"); rule->rtableid = nvlist_get_number(nvl, "rtableid"); pf_nvuint_32_array(nvl, "timeout", PFTM_MAX, rule->timeout, NULL); rule->max_states = nvlist_get_number(nvl, "max_states"); rule->max_src_nodes = nvlist_get_number(nvl, "max_src_nodes"); rule->max_src_states = nvlist_get_number(nvl, "max_src_states"); rule->max_src_conn = nvlist_get_number(nvl, "max_src_conn"); rule->max_src_conn_rate.limit = nvlist_get_number(nvl, "max_src_conn_rate.limit"); rule->max_src_conn_rate.seconds = nvlist_get_number(nvl, "max_src_conn_rate.seconds"); rule->qid = nvlist_get_number(nvl, "qid"); rule->pqid = nvlist_get_number(nvl, "pqid"); rule->dnpipe = nvlist_get_number(nvl, "dnpipe"); rule->dnrpipe = nvlist_get_number(nvl, "dnrpipe"); rule->free_flags = nvlist_get_number(nvl, "dnflags"); rule->prob = nvlist_get_number(nvl, "prob"); rule->cuid = nvlist_get_number(nvl, "cuid"); rule->cpid = nvlist_get_number(nvl, "cpid"); rule->return_icmp = nvlist_get_number(nvl, "return_icmp"); rule->return_icmp6 = nvlist_get_number(nvl, "return_icmp6"); rule->max_mss = nvlist_get_number(nvl, "max_mss"); rule->scrub_flags = nvlist_get_number(nvl, "scrub_flags"); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "uid"), &rule->uid); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "gid"), (struct pf_rule_uid *)&rule->gid); rule->rule_flag = nvlist_get_number(nvl, "rule_flag"); rule->action = nvlist_get_number(nvl, "action"); rule->direction = nvlist_get_number(nvl, "direction"); rule->log = nvlist_get_number(nvl, "log"); rule->logif = nvlist_get_number(nvl, "logif"); rule->quick = nvlist_get_number(nvl, "quick"); rule->ifnot = nvlist_get_number(nvl, "ifnot"); rule->match_tag_not = nvlist_get_number(nvl, "match_tag_not"); rule->natpass = nvlist_get_number(nvl, "natpass"); rule->keep_state = nvlist_get_number(nvl, "keep_state"); rule->af = nvlist_get_number(nvl, "af"); rule->proto = nvlist_get_number(nvl, "proto"); rule->type = nvlist_get_number(nvl, "type"); rule->code = nvlist_get_number(nvl, "code"); rule->flags = nvlist_get_number(nvl, "flags"); rule->flagset = nvlist_get_number(nvl, "flagset"); rule->min_ttl = nvlist_get_number(nvl, "min_ttl"); rule->allow_opts = nvlist_get_number(nvl, "allow_opts"); rule->rt = nvlist_get_number(nvl, "rt"); rule->return_ttl = nvlist_get_number(nvl, "return_ttl"); rule->tos = nvlist_get_number(nvl, "tos"); rule->set_tos = nvlist_get_number(nvl, "set_tos"); rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); rule->flush = nvlist_get_number(nvl, "flush"); rule->prio = nvlist_get_number(nvl, "prio"); pf_nvuint_8_array(nvl, "set_prio", 2, rule->set_prio, NULL); pf_nvdivert_to_divert(nvlist_get_nvlist(nvl, "divert"), rule); rule->states_cur = nvlist_get_number(nvl, "states_cur"); rule->states_tot = nvlist_get_number(nvl, "states_tot"); rule->src_nodes = nvlist_get_number(nvl, "src_nodes"); } static void pfctl_nveth_addr_to_eth_addr(const nvlist_t *nvl, struct pfctl_eth_addr *addr) { static const u_int8_t EMPTY_MAC[ETHER_ADDR_LEN] = { 0 }; size_t len; const void *data; data = nvlist_get_binary(nvl, "addr", &len); assert(len == sizeof(addr->addr)); memcpy(addr->addr, data, sizeof(addr->addr)); data = nvlist_get_binary(nvl, "mask", &len); assert(len == sizeof(addr->mask)); memcpy(addr->mask, data, sizeof(addr->mask)); addr->neg = nvlist_get_bool(nvl, "neg"); /* To make checks for 'is this address set?' easier. */ addr->isset = memcmp(addr->addr, EMPTY_MAC, ETHER_ADDR_LEN) != 0; } static nvlist_t * pfctl_eth_addr_to_nveth_addr(const struct pfctl_eth_addr *addr) { nvlist_t *nvl; nvl = nvlist_create(0); if (nvl == NULL) return (NULL); nvlist_add_bool(nvl, "neg", addr->neg); nvlist_add_binary(nvl, "addr", &addr->addr, ETHER_ADDR_LEN); nvlist_add_binary(nvl, "mask", &addr->mask, ETHER_ADDR_LEN); return (nvl); } static void pfctl_nveth_rule_to_eth_rule(const nvlist_t *nvl, struct pfctl_eth_rule *rule) { const char *const *labels; size_t labelcount, i; rule->nr = nvlist_get_number(nvl, "nr"); rule->quick = nvlist_get_bool(nvl, "quick"); strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); rule->ifnot = nvlist_get_bool(nvl, "ifnot"); rule->direction = nvlist_get_number(nvl, "direction"); rule->proto = nvlist_get_number(nvl, "proto"); strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"), PF_TAG_NAME_SIZE); rule->match_tag = nvlist_get_number(nvl, "match_tag"); rule->match_tag_not = nvlist_get_bool(nvl, "match_tag_not"); labels = nvlist_get_string_array(nvl, "labels", &labelcount); assert(labelcount <= PF_RULE_MAX_LABEL_COUNT); for (i = 0; i < labelcount; i++) strlcpy(rule->label[i], labels[i], PF_RULE_LABEL_SIZE); rule->ridentifier = nvlist_get_number(nvl, "ridentifier"); pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); pfctl_nveth_addr_to_eth_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipsrc"), &rule->ipsrc); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "ipdst"), &rule->ipdst); rule->evaluations = nvlist_get_number(nvl, "evaluations"); rule->packets[0] = nvlist_get_number(nvl, "packets-in"); rule->packets[1] = nvlist_get_number(nvl, "packets-out"); rule->bytes[0] = nvlist_get_number(nvl, "bytes-in"); rule->bytes[1] = nvlist_get_number(nvl, "bytes-out"); if (nvlist_exists_number(nvl, "timestamp")) { rule->last_active_timestamp = nvlist_get_number(nvl, "timestamp"); } strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), PF_TAG_NAME_SIZE); rule->dnpipe = nvlist_get_number(nvl, "dnpipe"); rule->dnflags = nvlist_get_number(nvl, "dnflags"); rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); strlcpy(rule->bridge_to, nvlist_get_string(nvl, "bridge_to"), IFNAMSIZ); rule->action = nvlist_get_number(nvl, "action"); } int pfctl_get_eth_rulesets_info(int dev, struct pfctl_eth_rulesets_info *ri, const char *path) { nvlist_t *nvl; int ret; bzero(ri, sizeof(*ri)); nvl = nvlist_create(0); nvlist_add_string(nvl, "path", path); if ((ret = pfctl_do_ioctl(dev, DIOCGETETHRULESETS, 256, &nvl)) != 0) return (ret); ri->nr = nvlist_get_number(nvl, "nr"); nvlist_destroy(nvl); return (0); } int pfctl_get_eth_ruleset(int dev, const char *path, int nr, struct pfctl_eth_ruleset_info *ri) { 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) return (ret); ri->nr = nvlist_get_number(nvl, "nr"); strlcpy(ri->path, nvlist_get_string(nvl, "path"), MAXPATHLEN); strlcpy(ri->name, nvlist_get_string(nvl, "name"), PF_ANCHOR_NAME_SIZE); return (0); } int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, const char *path) { 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) return (ret); rules->nr = nvlist_get_number(nvl, "nr"); rules->ticket = nvlist_get_number(nvl, "ticket"); nvlist_destroy(nvl); return (0); } 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) return (ret); pfctl_nveth_rule_to_eth_rule(nvl, rule); if (anchor_call) strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), MAXPATHLEN); nvlist_destroy(nvl); return (0); } int pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, 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 (r->label[labelcount][0] != 0 && labelcount < PF_RULE_MAX_LABEL_COUNT) { 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); } 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) { struct pfioc_nv nv; uint64_t timeouts[PFTM_MAX]; uint64_t set_prio[2]; nvlist_t *nvl, *nvlr; size_t labelcount; int ret; nvl = nvlist_create(0); nvlr = nvlist_create(0); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_number(nvl, "pool_ticket", pool_ticket); nvlist_add_string(nvl, "anchor", anchor); nvlist_add_string(nvl, "anchor_call", anchor_call); nvlist_add_number(nvlr, "nr", r->nr); pfctl_nv_add_rule_addr(nvlr, "src", &r->src); pfctl_nv_add_rule_addr(nvlr, "dst", &r->dst); labelcount = 0; while (r->label[labelcount][0] != 0 && labelcount < PF_RULE_MAX_LABEL_COUNT) { nvlist_append_string_array(nvlr, "labels", r->label[labelcount]); labelcount++; } nvlist_add_number(nvlr, "ridentifier", r->ridentifier); nvlist_add_string(nvlr, "ifname", r->ifname); nvlist_add_string(nvlr, "qname", r->qname); nvlist_add_string(nvlr, "pqname", r->pqname); nvlist_add_string(nvlr, "tagname", r->tagname); nvlist_add_string(nvlr, "match_tagname", r->match_tagname); nvlist_add_string(nvlr, "overload_tblname", r->overload_tblname); pfctl_nv_add_pool(nvlr, "rpool", &r->rpool); nvlist_add_number(nvlr, "os_fingerprint", r->os_fingerprint); nvlist_add_number(nvlr, "rtableid", r->rtableid); for (int i = 0; i < PFTM_MAX; i++) timeouts[i] = r->timeout[i]; nvlist_add_number_array(nvlr, "timeout", timeouts, PFTM_MAX); nvlist_add_number(nvlr, "max_states", r->max_states); nvlist_add_number(nvlr, "max_src_nodes", r->max_src_nodes); nvlist_add_number(nvlr, "max_src_states", r->max_src_states); nvlist_add_number(nvlr, "max_src_conn", r->max_src_conn); nvlist_add_number(nvlr, "max_src_conn_rate.limit", r->max_src_conn_rate.limit); nvlist_add_number(nvlr, "max_src_conn_rate.seconds", r->max_src_conn_rate.seconds); nvlist_add_number(nvlr, "dnpipe", r->dnpipe); nvlist_add_number(nvlr, "dnrpipe", r->dnrpipe); nvlist_add_number(nvlr, "dnflags", r->free_flags); nvlist_add_number(nvlr, "prob", r->prob); nvlist_add_number(nvlr, "cuid", r->cuid); nvlist_add_number(nvlr, "cpid", r->cpid); nvlist_add_number(nvlr, "return_icmp", r->return_icmp); nvlist_add_number(nvlr, "return_icmp6", r->return_icmp6); nvlist_add_number(nvlr, "max_mss", r->max_mss); nvlist_add_number(nvlr, "scrub_flags", r->scrub_flags); pfctl_nv_add_uid(nvlr, "uid", &r->uid); pfctl_nv_add_uid(nvlr, "gid", (const struct pf_rule_uid *)&r->gid); nvlist_add_number(nvlr, "rule_flag", r->rule_flag); nvlist_add_number(nvlr, "action", r->action); nvlist_add_number(nvlr, "direction", r->direction); nvlist_add_number(nvlr, "log", r->log); nvlist_add_number(nvlr, "logif", r->logif); nvlist_add_number(nvlr, "quick", r->quick); nvlist_add_number(nvlr, "ifnot", r->ifnot); nvlist_add_number(nvlr, "match_tag_not", r->match_tag_not); nvlist_add_number(nvlr, "natpass", r->natpass); nvlist_add_number(nvlr, "keep_state", r->keep_state); nvlist_add_number(nvlr, "af", r->af); nvlist_add_number(nvlr, "proto", r->proto); nvlist_add_number(nvlr, "type", r->type); nvlist_add_number(nvlr, "code", r->code); nvlist_add_number(nvlr, "flags", r->flags); nvlist_add_number(nvlr, "flagset", r->flagset); nvlist_add_number(nvlr, "min_ttl", r->min_ttl); nvlist_add_number(nvlr, "allow_opts", r->allow_opts); nvlist_add_number(nvlr, "rt", r->rt); nvlist_add_number(nvlr, "return_ttl", r->return_ttl); nvlist_add_number(nvlr, "tos", r->tos); nvlist_add_number(nvlr, "set_tos", r->set_tos); nvlist_add_number(nvlr, "anchor_relative", r->anchor_relative); nvlist_add_number(nvlr, "anchor_wildcard", r->anchor_wildcard); nvlist_add_number(nvlr, "flush", r->flush); nvlist_add_number(nvlr, "prio", r->prio); set_prio[0] = r->set_prio[0]; set_prio[1] = r->set_prio[1]; nvlist_add_number_array(nvlr, "set_prio", set_prio, 2); pfctl_nv_add_divert(nvlr, "divert", r); nvlist_add_nvlist(nvl, "rule", nvlr); nvlist_destroy(nvlr); /* Now do the call. */ nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; ret = ioctl(dev, DIOCADDRULENV, &nv); if (ret == -1) ret = errno; free(nv.data); nvlist_destroy(nvl); return (ret); } int pfctl_get_rules_info(int dev, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path) { struct pfioc_rule pr; int ret; bzero(&pr, sizeof(pr)); if (strlcpy(pr.anchor, path, sizeof(pr.anchor)) >= sizeof(pr.anchor)) return (E2BIG); pr.rule.action = ruleset; ret = ioctl(dev, DIOCGETRULES, &pr); if (ret != 0) return (ret); rules->nr = pr.nr; rules->ticket = pr.ticket; return (0); } 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)); } 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) return (ret); pf_nvrule_to_rule(nvlist_get_nvlist(nvl, "rule"), rule); if (anchor_call) strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), MAXPATHLEN); nvlist_destroy(nvl); return (0); } int pfctl_set_keepcounters(int dev, bool keep) { struct pfioc_nv nv; nvlist_t *nvl; int ret; nvl = nvlist_create(0); nvlist_add_bool(nvl, "keep_counters", keep); nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; nvlist_destroy(nvl); ret = ioctl(dev, DIOCKEEPCOUNTERS, &nv); free(nv.data); return (ret); } +struct pfctl_creator { + uint32_t id; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pfctl_creator, _field) +static struct snl_attr_parser ap_creators[] = { + { .type = PF_ST_CREATORID, .off = _OUT(id), .cb = snl_attr_get_uint32 }, +}; +static struct snl_field_parser fp_creators[] = { +}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(creator_parser, struct genlmsghdr, fp_creators, ap_creators); + +static int +pfctl_get_creators_nl(struct snl_state *ss, uint32_t *creators, size_t *len) +{ + + int family_id = snl_get_genl_family(ss, PFNL_FAMILY_NAME); + size_t i = 0; + + struct nlmsghdr *hdr; + struct snl_writer nw; + + snl_init_writer(ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETCREATORS); + hdr->nlmsg_flags |= NLM_F_DUMP; + snl_finalize_msg(&nw); + 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(uint32_t *creators, size_t *len) +{ + struct snl_state ss = {}; + int error; + + snl_init(&ss, NETLINK_GENERIC); + error = pfctl_get_creators_nl(&ss, creators, len); + snl_free(&ss); + + return (error); + +} + static void pfctl_nv_add_state_cmp(nvlist_t *nvl, const char *name, const struct pfctl_state_cmp *cmp) { nvlist_t *nv; nv = nvlist_create(0); nvlist_add_number(nv, "id", cmp->id); nvlist_add_number(nv, "creatorid", htonl(cmp->creatorid)); nvlist_add_number(nv, "direction", cmp->direction); nvlist_add_nvlist(nvl, name, nv); nvlist_destroy(nv); } static inline bool snl_attr_get_pfaddr(struct snl_state *ss __unused, struct nlattr *nla, const void *arg __unused, void *target) { memcpy(target, NLA_DATA(nla), NLA_DATA_LEN(nla)); return (true); } static inline bool snl_attr_store_ifname(struct snl_state *ss __unused, struct nlattr *nla, const void *arg __unused, void *target) { size_t maxlen = NLA_DATA_LEN(nla); if (strnlen((char *)NLA_DATA(nla), maxlen) < maxlen) { strlcpy(target, (char *)NLA_DATA(nla), maxlen); return (true); } return (false); } #define _OUT(_field) offsetof(struct pfctl_state_peer, _field) static const struct snl_attr_parser nla_p_speer[] = { { .type = PF_STP_SEQLO, .off = _OUT(seqlo), .cb = snl_attr_get_uint32 }, { .type = PF_STP_SEQHI, .off = _OUT(seqhi), .cb = snl_attr_get_uint32 }, { .type = PF_STP_SEQDIFF, .off = _OUT(seqdiff), .cb = snl_attr_get_uint32 }, { .type = PF_STP_STATE, .off = _OUT(state), .cb = snl_attr_get_uint8 }, { .type = PF_STP_WSCALE, .off = _OUT(wscale), .cb = snl_attr_get_uint8 }, }; SNL_DECLARE_ATTR_PARSER(speer_parser, nla_p_speer); #undef _OUT #define _OUT(_field) offsetof(struct pf_state_key_export, _field) static const struct snl_attr_parser nla_p_skey[] = { { .type = PF_STK_ADDR0, .off = _OUT(addr[0]), .cb = snl_attr_get_pfaddr }, { .type = PF_STK_ADDR1, .off = _OUT(addr[1]), .cb = snl_attr_get_pfaddr }, { .type = PF_STK_PORT0, .off = _OUT(port[0]), .cb = snl_attr_get_uint16 }, { .type = PF_STK_PORT1, .off = _OUT(port[1]), .cb = snl_attr_get_uint16 }, }; SNL_DECLARE_ATTR_PARSER(skey_parser, nla_p_skey); #undef _OUT #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct pfctl_state, _field) static struct snl_attr_parser ap_state[] = { { .type = PF_ST_ID, .off = _OUT(id), .cb = snl_attr_get_uint64 }, { .type = PF_ST_CREATORID, .off = _OUT(creatorid), .cb = snl_attr_get_uint32 }, { .type = PF_ST_IFNAME, .off = _OUT(ifname), .cb = snl_attr_store_ifname }, { .type = PF_ST_ORIG_IFNAME, .off = _OUT(orig_ifname), .cb = snl_attr_store_ifname }, { .type = PF_ST_KEY_WIRE, .off = _OUT(key[0]), .arg = &skey_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_KEY_STACK, .off = _OUT(key[1]), .arg = &skey_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_PEER_SRC, .off = _OUT(src), .arg = &speer_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_PEER_DST, .off = _OUT(dst), .arg = &speer_parser, .cb = snl_attr_get_nested }, { .type = PF_ST_RT_ADDR, .off = _OUT(rt_addr), .cb = snl_attr_get_pfaddr }, { .type = PF_ST_RULE, .off = _OUT(rule), .cb = snl_attr_get_uint32 }, { .type = PF_ST_ANCHOR, .off = _OUT(anchor), .cb = snl_attr_get_uint32 }, { .type = PF_ST_NAT_RULE, .off = _OUT(nat_rule), .cb = snl_attr_get_uint32 }, { .type = PF_ST_CREATION, .off = _OUT(creation), .cb = snl_attr_get_uint32 }, { .type = PF_ST_EXPIRE, .off = _OUT(expire), .cb = snl_attr_get_uint32 }, { .type = PF_ST_PACKETS0, .off = _OUT(packets[0]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_PACKETS1, .off = _OUT(packets[1]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_BYTES0, .off = _OUT(bytes[0]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_BYTES1, .off = _OUT(bytes[1]), .cb = snl_attr_get_uint64 }, { .type = PF_ST_AF, .off = _OUT(key[0].af), .cb = snl_attr_get_uint8 }, { .type = PF_ST_PROTO, .off = _OUT(key[0].proto), .cb = snl_attr_get_uint8 }, { .type = PF_ST_DIRECTION, .off = _OUT(direction), .cb = snl_attr_get_uint8 }, { .type = PF_ST_LOG, .off = _OUT(log), .cb = snl_attr_get_uint8 }, { .type = PF_ST_STATE_FLAGS, .off = _OUT(state_flags), .cb = snl_attr_get_uint16 }, { .type = PF_ST_SYNC_FLAGS, .off = _OUT(sync_flags), .cb = snl_attr_get_uint8 }, }; static struct snl_field_parser fp_state[] = { }; #undef _IN #undef _OUT SNL_DECLARE_PARSER(state_parser, struct genlmsghdr, fp_state, ap_state); static const struct snl_hdr_parser *all_parsers[] = { - &state_parser, &skey_parser, &speer_parser + &state_parser, &skey_parser, &speer_parser, + &creator_parser, }; static int pfctl_get_states_nl(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; snl_init_writer(ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETSTATES); hdr->nlmsg_flags |= NLM_F_DUMP; snl_finalize_msg(&nw); uint32_t seq_id = hdr->nlmsg_seq; snl_send_message(ss, hdr); struct snl_errmsg_data e = {}; while ((hdr = snl_read_reply_multi(ss, seq_id, &e)) != NULL) { struct pfctl_state s; bzero(&s, sizeof(s)); if (!snl_parse_nlmsg(ss, hdr, &state_parser, &s)) continue; s.key[1].af = s.key[0].af; s.key[1].proto = s.key[0].proto; ret = f(&s, arg); if (ret != 0) return (ret); } return (0); } int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg) { struct snl_state ss = {}; int error; snl_init(&ss, NETLINK_GENERIC); error = pfctl_get_states_nl(&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, s, 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)); } static int _pfctl_clear_states(int dev, const struct pfctl_kill *kill, unsigned int *killed, uint64_t ioctlval) { nvlist_t *nvl; int ret; nvl = nvlist_create(0); pfctl_nv_add_state_cmp(nvl, "cmp", &kill->cmp); nvlist_add_number(nvl, "af", kill->af); nvlist_add_number(nvl, "proto", kill->proto); pfctl_nv_add_rule_addr(nvl, "src", &kill->src); pfctl_nv_add_rule_addr(nvl, "dst", &kill->dst); pfctl_nv_add_rule_addr(nvl, "rt_addr", &kill->rt_addr); nvlist_add_string(nvl, "ifname", kill->ifname); nvlist_add_string(nvl, "label", kill->label); nvlist_add_bool(nvl, "kill_match", kill->kill_match); if ((ret = pfctl_do_ioctl(dev, ioctlval, 1024, &nvl)) != 0) return (ret); if (killed) *killed = nvlist_get_number(nvl, "killed"); nvlist_destroy(nvl); return (ret); } int pfctl_clear_states(int dev, const struct pfctl_kill *kill, unsigned int *killed) { return (_pfctl_clear_states(dev, kill, killed, DIOCCLRSTATESNV)); } int pfctl_kill_states(int dev, const struct pfctl_kill *kill, unsigned int *killed) { return (_pfctl_clear_states(dev, kill, killed, DIOCKILLSTATESNV)); } int pfctl_clear_rules(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe[2]; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe[0].rs_num = PF_RULESET_SCRUB; if (strlcpy(transe[0].anchor, anchorname, sizeof(transe[0].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[1].rs_num = PF_RULESET_FILTER; if (strlcpy(transe[1].anchor, anchorname, sizeof(transe[1].anchor)) >= sizeof(transe[1].anchor)) return (E2BIG); trans.size = 2; trans.esize = sizeof(transe[0]); trans.array = transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } int pfctl_clear_nat(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe[3]; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe[0].rs_num = PF_RULESET_NAT; if (strlcpy(transe[0].anchor, anchorname, sizeof(transe[0].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[1].rs_num = PF_RULESET_BINAT; if (strlcpy(transe[1].anchor, anchorname, sizeof(transe[1].anchor)) >= sizeof(transe[0].anchor)) return (E2BIG); transe[2].rs_num = PF_RULESET_RDR; if (strlcpy(transe[2].anchor, anchorname, sizeof(transe[2].anchor)) >= sizeof(transe[2].anchor)) return (E2BIG); trans.size = 3; trans.esize = sizeof(transe[0]); trans.array = transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } int pfctl_clear_eth_rules(int dev, const char *anchorname) { struct pfioc_trans trans; struct pfioc_trans_e transe; int ret; bzero(&trans, sizeof(trans)); bzero(&transe, sizeof(transe)); transe.rs_num = PF_RULESET_ETH; if (strlcpy(transe.anchor, anchorname, sizeof(transe.anchor)) >= sizeof(transe.anchor)) return (E2BIG); trans.size = 1; trans.esize = sizeof(transe); trans.array = &transe; ret = ioctl(dev, DIOCXBEGIN, &trans); if (ret != 0) return (ret); return ioctl(dev, DIOCXCOMMIT, &trans); } static int pfctl_get_limit(int dev, const int index, uint *limit) { struct pfioc_limit pl; bzero(&pl, sizeof(pl)); pl.index = index; if (ioctl(dev, DIOCGETLIMIT, &pl) == -1) return (errno); *limit = pl.limit; return (0); } int pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s) { struct pfioc_nv nv; nvlist_t *nvl; int ret; uint state_limit; uint64_t lim, hi, lo; ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); lim = state_limit; hi = lim * s->highwater / 100; lo = lim * s->lowwater / 100; if (lo == hi) hi++; nvl = nvlist_create(0); nvlist_add_bool(nvl, "enabled", s->mode != PFCTL_SYNCOOKIES_NEVER); nvlist_add_bool(nvl, "adaptive", s->mode == PFCTL_SYNCOOKIES_ADAPTIVE); nvlist_add_number(nvl, "highwater", hi); nvlist_add_number(nvl, "lowwater", lo); nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; nvlist_destroy(nvl); nvl = NULL; ret = ioctl(dev, DIOCSETSYNCOOKIES, &nv); free(nv.data); return (ret); } int pfctl_get_syncookies(int dev, struct pfctl_syncookies *s) { nvlist_t *nvl; int ret; uint state_limit; bool enabled, adaptive; ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); bzero(s, sizeof(*s)); nvl = nvlist_create(0); if ((ret = pfctl_do_ioctl(dev, DIOCGETSYNCOOKIES, 256, &nvl)) != 0) return (errno); 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; nvlist_destroy(nvl); return (0); } int pfctl_table_add_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; if (ioctl(dev, DIOCRADDADDRS, &io)) return (errno); if (nadd != NULL) *nadd = io.pfrio_nadd; return (0); } int pfctl_table_del_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; if (ioctl(dev, DIOCRDELADDRS, &io)) return (errno); if (ndel != NULL) *ndel = io.pfrio_ndel; return (0); } int pfctl_table_set_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *size2, int *nadd, int *ndel, int *nchange, int flags) { struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = size; io.pfrio_size2 = (size2 != NULL) ? *size2 : 0; if (ioctl(dev, DIOCRSETADDRS, &io)) return (-1); if (nadd != NULL) *nadd = io.pfrio_nadd; if (ndel != NULL) *ndel = io.pfrio_ndel; if (nchange != NULL) *nchange = io.pfrio_nchange; if (size2 != NULL) *size2 = io.pfrio_size2; return (0); } int pfctl_table_get_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags) { struct pfioc_table io; if (tbl == NULL || size == NULL || *size < 0 || (*size && addr == NULL)) { return (EINVAL); } bzero(&io, sizeof io); io.pfrio_flags = flags; io.pfrio_table = *tbl; io.pfrio_buffer = addr; io.pfrio_esize = sizeof(*addr); io.pfrio_size = *size; if (ioctl(dev, DIOCRGETADDRS, &io)) return (-1); *size = io.pfrio_size; return (0); } diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 4906ec3ccfce..e75f93d8775e 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -1,438 +1,439 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _PFCTL_IOCTL_H_ #define _PFCTL_IOCTL_H_ #include struct pfctl_anchor; struct pfctl_eth_anchor; struct pfctl_status_counter { uint64_t id; uint64_t counter; char *name; TAILQ_ENTRY(pfctl_status_counter) entry; }; TAILQ_HEAD(pfctl_status_counters, pfctl_status_counter); struct pfctl_status { bool running; uint32_t since; uint32_t debug; uint32_t hostid; uint64_t states; uint64_t src_nodes; char ifname[IFNAMSIZ]; uint8_t pf_chksum[PF_MD5_DIGEST_LENGTH]; bool syncookies_active; uint32_t reass; struct pfctl_status_counters counters; struct pfctl_status_counters lcounters; struct pfctl_status_counters fcounters; struct pfctl_status_counters scounters; uint64_t pcounters[2][2][3]; uint64_t bcounters[2][2]; }; struct pfctl_eth_rulesets_info { uint32_t nr; }; struct pfctl_eth_rules_info { uint32_t nr; uint32_t ticket; }; struct pfctl_eth_addr { uint8_t addr[ETHER_ADDR_LEN]; uint8_t mask[ETHER_ADDR_LEN]; bool neg; bool isset; }; struct pfctl_eth_rule { uint32_t nr; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; bool quick; /* Filter */ char ifname[IFNAMSIZ]; uint8_t ifnot; uint8_t direction; uint16_t proto; struct pfctl_eth_addr src, dst; struct pf_rule_addr ipsrc, ipdst; char match_tagname[PF_TAG_NAME_SIZE]; uint16_t match_tag; bool match_tag_not; /* Stats */ uint64_t evaluations; uint64_t packets[2]; uint64_t bytes[2]; time_t last_active_timestamp; /* Action */ char qname[PF_QNAME_SIZE]; char tagname[PF_TAG_NAME_SIZE]; uint16_t dnpipe; uint32_t dnflags; char bridge_to[IFNAMSIZ]; uint8_t action; struct pfctl_eth_anchor *anchor; uint8_t anchor_relative; uint8_t anchor_wildcard; TAILQ_ENTRY(pfctl_eth_rule) entries; }; TAILQ_HEAD(pfctl_eth_rules, pfctl_eth_rule); struct pfctl_eth_ruleset_info { uint32_t nr; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; }; struct pfctl_eth_ruleset { struct pfctl_eth_rules rules; struct pfctl_eth_anchor *anchor; }; struct pfctl_eth_anchor { struct pfctl_eth_anchor *parent; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pfctl_eth_ruleset ruleset; int refcnt; /* anchor rules */ int match; /* XXX: used for pfctl black magic */ }; struct pfctl_pool { struct pf_palist list; struct pf_pooladdr *cur; struct pf_poolhashkey key; struct pf_addr counter; struct pf_mape_portset mape; int tblidx; uint16_t proxy_port[2]; uint8_t opts; }; struct pfctl_rules_info { uint32_t nr; uint32_t ticket; }; struct pfctl_rule { struct pf_rule_addr src; struct pf_rule_addr dst; union pf_rule_ptr skip[PF_SKIP_COUNT]; char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE]; uint32_t ridentifier; char ifname[IFNAMSIZ]; char qname[PF_QNAME_SIZE]; char pqname[PF_QNAME_SIZE]; char tagname[PF_TAG_NAME_SIZE]; char match_tagname[PF_TAG_NAME_SIZE]; char overload_tblname[PF_TABLE_NAME_SIZE]; TAILQ_ENTRY(pfctl_rule) entries; struct pfctl_pool rpool; uint64_t evaluations; uint64_t packets[2]; uint64_t bytes[2]; time_t last_active_timestamp; struct pfi_kif *kif; struct pfctl_anchor *anchor; struct pfr_ktable *overload_tbl; pf_osfp_t os_fingerprint; int rtableid; uint32_t timeout[PFTM_MAX]; uint32_t max_states; uint32_t max_src_nodes; uint32_t max_src_states; uint32_t max_src_conn; struct { uint32_t limit; uint32_t seconds; } max_src_conn_rate; uint32_t qid; uint32_t pqid; uint16_t dnpipe; uint16_t dnrpipe; uint32_t free_flags; uint32_t nr; uint32_t prob; uid_t cuid; pid_t cpid; uint64_t states_cur; uint64_t states_tot; uint64_t src_nodes; uint16_t return_icmp; uint16_t return_icmp6; uint16_t max_mss; uint16_t tag; uint16_t match_tag; uint16_t scrub_flags; struct pf_rule_uid uid; struct pf_rule_gid gid; uint32_t rule_flag; uint8_t action; uint8_t direction; uint8_t log; uint8_t logif; uint8_t quick; uint8_t ifnot; uint8_t match_tag_not; uint8_t natpass; uint8_t keep_state; sa_family_t af; uint8_t proto; uint8_t type; uint8_t code; uint8_t flags; uint8_t flagset; uint8_t min_ttl; uint8_t allow_opts; uint8_t rt; uint8_t return_ttl; uint8_t tos; uint8_t set_tos; uint8_t anchor_relative; uint8_t anchor_wildcard; uint8_t flush; uint8_t prio; uint8_t set_prio[2]; struct { struct pf_addr addr; uint16_t port; } divert; }; TAILQ_HEAD(pfctl_rulequeue, pfctl_rule); struct pfctl_ruleset { struct { struct pfctl_rulequeue queues[2]; struct { struct pfctl_rulequeue *ptr; struct pfctl_rule **ptr_array; uint32_t rcount; uint32_t ticket; int open; } active, inactive; } rules[PF_RULESET_MAX]; struct pfctl_anchor *anchor; uint32_t tticket; int tables; int topen; }; RB_HEAD(pfctl_anchor_global, pfctl_anchor); RB_HEAD(pfctl_anchor_node, pfctl_anchor); struct pfctl_anchor { RB_ENTRY(pfctl_anchor) entry_global; RB_ENTRY(pfctl_anchor) entry_node; struct pfctl_anchor *parent; struct pfctl_anchor_node children; char name[PF_ANCHOR_NAME_SIZE]; char path[MAXPATHLEN]; struct pfctl_ruleset ruleset; int refcnt; /* anchor rules */ int match; /* XXX: used for pfctl black magic */ }; RB_PROTOTYPE(pfctl_anchor_global, pfctl_anchor, entry_global, pf_anchor_compare); RB_PROTOTYPE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare); struct pfctl_state_cmp { uint64_t id; uint32_t creatorid; uint8_t direction; }; struct pfctl_kill { struct pfctl_state_cmp cmp; sa_family_t af; int proto; struct pf_rule_addr src; struct pf_rule_addr dst; struct pf_rule_addr rt_addr; char ifname[IFNAMSIZ]; char label[PF_RULE_LABEL_SIZE]; bool kill_match; }; struct pfctl_state_peer { uint32_t seqlo; uint32_t seqhi; uint32_t seqdiff; uint8_t state; uint8_t wscale; }; struct pfctl_state_key { struct pf_addr addr[2]; uint16_t port[2]; sa_family_t af; uint8_t proto; }; struct pfctl_state { TAILQ_ENTRY(pfctl_state) entry; uint64_t id; uint32_t creatorid; uint8_t direction; struct pfctl_state_peer src; struct pfctl_state_peer dst; uint32_t rule; uint32_t anchor; uint32_t nat_rule; struct pf_addr rt_addr; struct pfctl_state_key key[2]; /* addresses stack and wire */ char ifname[IFNAMSIZ]; char orig_ifname[IFNAMSIZ]; uint64_t packets[2]; uint64_t bytes[2]; uint32_t creation; uint32_t expire; uint32_t pfsync_time; uint16_t state_flags; uint32_t sync_flags; uint16_t qid; uint16_t pqid; uint16_t dnpipe; uint16_t dnrpipe; uint8_t log; int32_t rtableid; uint8_t min_ttl; uint8_t set_tos; uint16_t max_mss; uint8_t set_prio[2]; uint8_t rt; char rt_ifname[IFNAMSIZ]; }; TAILQ_HEAD(pfctl_statelist, pfctl_state); struct pfctl_states { struct pfctl_statelist states; size_t count; }; 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 */ }; struct pfctl_status* pfctl_get_status(int dev); uint64_t pfctl_status_counter(struct pfctl_status *status, int id); uint64_t pfctl_status_fcounter(struct pfctl_status *status, int id); uint64_t pfctl_status_scounter(struct pfctl_status *status, int id); void pfctl_free_status(struct pfctl_status *status); int pfctl_get_eth_rulesets_info(int dev, struct pfctl_eth_rulesets_info *ri, const char *path); int pfctl_get_eth_ruleset(int dev, const char *path, int nr, struct pfctl_eth_ruleset_info *ri); int pfctl_get_eth_rules_info(int dev, struct pfctl_eth_rules_info *rules, const char *path); int pfctl_get_eth_rule(int dev, uint32_t nr, uint32_t ticket, const char *path, struct pfctl_eth_rule *rule, bool clear, char *anchor_call); int pfctl_add_eth_rule(int dev, const struct pfctl_eth_rule *r, const char *anchor, const char *anchor_call, uint32_t ticket); int pfctl_get_rules_info(int dev, struct pfctl_rules_info *rules, uint32_t ruleset, const char *path); int pfctl_get_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call); int pfctl_get_clear_rule(int dev, uint32_t nr, uint32_t ticket, const char *anchor, uint32_t ruleset, struct pfctl_rule *rule, char *anchor_call, bool clear); int pfctl_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_set_keepcounters(int dev, bool keep); +int pfctl_get_creatorids(uint32_t *creators, size_t *len); 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_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_rules(int dev, const char *anchorname); int pfctl_clear_nat(int dev, const char *anchorname); int pfctl_clear_eth_rules(int dev, const char *anchorname); int pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s); int pfctl_get_syncookies(int dev, struct pfctl_syncookies *s); int pfctl_table_add_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *nadd, int flags); int pfctl_table_del_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *ndel, int flags); int pfctl_table_set_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int size, int *size2, int *nadd, int *ndel, int *nchange, int flags); int pfctl_table_get_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags); #endif diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 9a75eb7d00b5..759b36d9cebe 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1,3261 +1,3280 @@ /* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #define PFIOC_USE_LATEST #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); int pfctl_clear_stats(int, int); int pfctl_get_skip_ifaces(void); int pfctl_check_skip_ifaces(char *); int pfctl_adjust_skip_ifaces(struct pfctl *); int pfctl_clear_interface_flags(int, int); int pfctl_flush_eth_rules(int, int, char *); int pfctl_flush_rules(int, int, char *); int pfctl_flush_nat(int, int, char *); int pfctl_clear_altq(int, int); int pfctl_clear_src_nodes(int, int); int pfctl_clear_iface_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); int pfctl_kill_src_nodes(int, const char *, int); int pfctl_net_kill_states(int, const char *, int); int pfctl_gateway_kill_states(int, const char *, int); int pfctl_label_kill_states(int, const char *, int); int pfctl_id_kill_states(int, const char *, int); void pfctl_init_options(struct pfctl *); int pfctl_load_options(struct pfctl *); int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); int pfctl_load_debug(struct pfctl *, unsigned int); int pfctl_load_logif(struct pfctl *, char *); int pfctl_load_hostid(struct pfctl *, u_int32_t); int pfctl_load_reassembly(struct pfctl *, u_int32_t); int pfctl_load_syncookies(struct pfctl *, u_int8_t); int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int, char *); void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int); void pfctl_print_rule_counters(struct pfctl_rule *, int); int pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int); int pfctl_show_nat(int, char *, int, char *, int); int pfctl_show_src_nodes(int, int); int pfctl_show_states(int, const char *, int); int pfctl_show_status(int, int); int pfctl_show_running(int); int pfctl_show_timeouts(int, int); int pfctl_show_limits(int, int); void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); int pfctl_show_eth_anchors(int, int, char *); int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool); int pfctl_eth_ruleset_trans(struct pfctl *, char *, struct pfctl_eth_anchor *); int pfctl_load_eth_ruleset(struct pfctl *, char *, struct pfctl_eth_ruleset *, int); int pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *, int); int pfctl_load_ruleset(struct pfctl *, char *, struct pfctl_ruleset *, int, int); int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); const char *pfctl_lookup_option(char *, const char * const *); static struct pfctl_anchor_global pf_anchors; struct pfctl_anchor pf_main_anchor; struct pfctl_eth_anchor pf_eth_main_anchor; static struct pfr_buffer skip_b; static const char *clearopt; static char *rulesopt; static const char *showopt; static const char *debugopt; static char *anchoropt; static const char *optiopt = NULL; static const char *pf_device = "/dev/pf"; static char *ifaceopt; static char *tableopt; static const char *tblcmdopt; static int src_node_killers; static char *src_node_kill[2]; static int state_killers; static char *state_kill[2]; int loadopt; int altqsupport; int dev = -1; static int first_title = 1; static int labels = 0; #define INDENT(d, o) do { \ if (o) { \ int i; \ for (i=0; i < d; i++) \ printf(" "); \ } \ } while (0); \ static const struct { const char *name; int index; } pf_limits[] = { { "states", PF_LIMIT_STATES }, { "src-nodes", PF_LIMIT_SRC_NODES }, { "frags", PF_LIMIT_FRAGS }, { "table-entries", PF_LIMIT_TABLE_ENTRIES }, { NULL, 0 } }; struct pf_hint { const char *name; int timeout; }; static const struct pf_hint pf_hint_normal[] = { { "tcp.first", 2 * 60 }, { "tcp.opening", 30 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 }, { "tcp.finwait", 45 }, { "tcp.closed", 90 }, { "tcp.tsdiff", 30 }, { NULL, 0 } }; static const struct pf_hint pf_hint_satellite[] = { { "tcp.first", 3 * 60 }, { "tcp.opening", 30 + 5 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 + 5 }, { "tcp.finwait", 45 + 5 }, { "tcp.closed", 90 + 5 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_conservative[] = { { "tcp.first", 60 * 60 }, { "tcp.opening", 15 * 60 }, { "tcp.established", 5 * 24 * 60 * 60 }, { "tcp.closing", 60 * 60 }, { "tcp.finwait", 10 * 60 }, { "tcp.closed", 3 * 60 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_aggressive[] = { { "tcp.first", 30 }, { "tcp.opening", 5 }, { "tcp.established", 5 * 60 * 60 }, { "tcp.closing", 60 }, { "tcp.finwait", 30 }, { "tcp.closed", 30 }, { "tcp.tsdiff", 10 }, { NULL, 0 } }; static const struct { const char *name; const struct pf_hint *hint; } pf_hints[] = { { "normal", pf_hint_normal }, { "satellite", pf_hint_satellite }, { "high-latency", pf_hint_satellite }, { "conservative", pf_hint_conservative }, { "aggressive", pf_hint_aggressive }, { NULL, NULL } }; static const char * const clearopt_list[] = { "nat", "queue", "rules", "Sources", "states", "info", "Tables", "osfp", "all", "ethernet", NULL }; static const char * const showopt_list[] = { "ether", "nat", "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", - "osfp", "Running", "all", NULL + "osfp", "Running", "all", "creatorids", NULL }; static const char * const tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", "expire", NULL }; static const char * const debugopt_list[] = { "none", "urgent", "misc", "loud", NULL }; static const char * const optiopt_list[] = { "none", "basic", "profile", NULL }; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-AdeghMmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "\t[-f file] [-i interface] [-K host | network]\n" "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", __progname); exit(1); } /* * Cache protocol number to name translations. * * Translation is performed a lot e.g., when dumping states and * getprotobynumber is incredibly expensive. * * Note from the getprotobynumber(3) manpage: * * These functions use a thread-specific data space; if the data is needed * for future use, it should be copied before any subsequent calls overwrite * it. Only the Internet protocols are currently understood. * * * Consequently we only cache the name and strdup it for safety. * * At the time of writing this comment the last entry in /etc/protocols is: * divert 258 DIVERT # Divert pseudo-protocol [non IANA] */ const char * pfctl_proto2name(int proto) { static const char *pfctl_proto_cache[259]; struct protoent *p; if (proto >= nitems(pfctl_proto_cache)) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } return (p->p_name); } if (pfctl_proto_cache[proto] == NULL) { p = getprotobynumber(proto); if (p == NULL) { return (NULL); } pfctl_proto_cache[proto] = strdup(p->p_name); } return (pfctl_proto_cache[proto]); } int pfctl_enable(int dev, int opts) { if (ioctl(dev, DIOCSTART)) { if (errno == EEXIST) errx(1, "pf already enabled"); else if (errno == ESRCH) errx(1, "pfil registeration failed"); else err(1, "DIOCSTART"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf enabled\n"); if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) if (errno != EEXIST) err(1, "DIOCSTARTALTQ"); return (0); } int pfctl_disable(int dev, int opts) { if (ioctl(dev, DIOCSTOP)) { if (errno == ENOENT) errx(1, "pf not enabled"); else err(1, "DIOCSTOP"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf disabled\n"); if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) if (errno != ENOENT) err(1, "DIOCSTOPALTQ"); return (0); } int pfctl_clear_stats(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATUS)) err(1, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); return (0); } int pfctl_get_skip_ifaces(void) { bzero(&skip_b, sizeof(skip_b)); skip_b.pfrb_type = PFRB_IFACES; for (;;) { pfr_buf_grow(&skip_b, skip_b.pfrb_size); skip_b.pfrb_size = skip_b.pfrb_msize; if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size)) err(1, "pfi_get_ifaces"); if (skip_b.pfrb_size <= skip_b.pfrb_msize) break; } return (0); } int pfctl_check_skip_ifaces(char *ifname) { struct pfi_kif *p; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (!strcmp(ifname, p->pfik_name) && (p->pfik_flags & PFI_IFLAG_SKIP)) p->pfik_flags &= ~PFI_IFLAG_SKIP; if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) { if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) { if (p->pfik_ifp == NULL) continue; if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) continue; p->pfik_flags &= ~PFI_IFLAG_SKIP; } } } return (0); } int pfctl_adjust_skip_ifaces(struct pfctl *pf) { struct pfi_kif *p, *pp; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) PFRB_FOREACH(pp, &skip_b) { if (pp->pfik_ifp == NULL) continue; if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ)) continue; if (!(pp->pfik_flags & PFI_IFLAG_SKIP)) pfctl_set_interface_flags(pf, pp->pfik_name, PFI_IFLAG_SKIP, 1); if (pp->pfik_flags & PFI_IFLAG_SKIP) pp->pfik_flags &= ~PFI_IFLAG_SKIP; } } PFRB_FOREACH(p, &skip_b) { if (p->pfik_ifp == NULL || ! (p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); } return (0); } int pfctl_clear_interface_flags(int dev, int opts) { struct pfioc_iface pi; if ((opts & PF_OPT_NOACTION) == 0) { bzero(&pi, sizeof(pi)); pi.pfiio_flags = PFI_IFLAG_SKIP; if (ioctl(dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: interface flags reset\n"); } return (0); } int pfctl_flush_eth_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_eth_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_eth_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "Ethernet rules cleared\n"); return (ret); } int pfctl_flush_rules(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_rules(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } int pfctl_flush_nat(int dev, int opts, char *anchorname) { int ret; ret = pfctl_clear_nat(dev, anchorname); if (ret != 0) err(1, "pfctl_clear_nat"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } int pfctl_clear_altq(int dev, int opts) { struct pfr_buffer t; if (!altqsupport) return (-1); memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_altq"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "altq cleared\n"); return (0); } int pfctl_clear_src_nodes(int dev, int opts) { if (ioctl(dev, DIOCCLRSRCNODES)) err(1, "DIOCCLRSRCNODES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "source tracking entries cleared\n"); return (0); } int pfctl_clear_iface_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if (pfctl_clear_states(dev, &kill, &killed)) err(1, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "%d states cleared\n", killed); return (0); } void pfctl_addrprefix(char *addr, struct pf_addr *mask) { char *p; const char *errstr; int prefix, ret_ga, q, r; struct addrinfo hints, *res; if ((p = strchr(addr, '/')) == NULL) return; *p++ = '\0'; prefix = strtonum(p, 0, 128, &errstr); if (errstr) errx(1, "prefix is %s: %s", errstr, p); bzero(&hints, sizeof(hints)); /* prefix only with numeric addresses */ hints.ai_flags |= AI_NUMERICHOST; if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } if (res->ai_family == AF_INET && prefix > 32) errx(1, "prefix too long for AF_INET"); else if (res->ai_family == AF_INET6 && prefix > 128) errx(1, "prefix too long for AF_INET6"); q = prefix >> 3; r = prefix & 7; switch (res->ai_family) { case AF_INET: bzero(&mask->v4, sizeof(mask->v4)); mask->v4.s_addr = htonl((u_int32_t) (0xffffffffffULL << (32 - prefix))); break; case AF_INET6: bzero(&mask->v6, sizeof(mask->v6)); if (q > 0) memset((void *)&mask->v6, 0xff, q); if (r > 0) *((u_char *)&mask->v6 + q) = (0xff00 >> r) & 0xff; break; } freeaddrinfo(res); } int pfctl_kill_src_nodes(int dev, const char *iface, int opts) { struct pfioc_src_node_kill psnk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psnk, 0, sizeof(psnk)); memset(&psnk.psnk_src.addr.v.a.mask, 0xff, sizeof(psnk.psnk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psnk.psnk_af = resp[0]->ai_family; sources++; if (psnk.psnk_af == AF_INET) psnk.psnk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (src_node_killers > 1) { dests = 0; memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, sizeof(psnk.psnk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[1], &psnk.psnk_dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psnk.psnk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psnk.psnk_af == AF_INET) psnk.psnk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d src nodes from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_net_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; unsigned int newkilled; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&kill, 0, sizeof(kill)); memset(&kill.src.addr.v.a.mask, 0xff, sizeof(kill.src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); pfctl_addrprefix(state_kill[0], &kill.src.addr.v.a.mask); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; kill.af = resp[0]->ai_family; sources++; if (kill.af == AF_INET) kill.src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (state_killers > 1) { dests = 0; memset(&kill.dst.addr.v.a.mask, 0xff, sizeof(kill.dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(state_kill[1], &kill.dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (kill.af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (kill.af == AF_INET) kill.dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (pfctl_kill_states(dev, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res[1]); } else { if (pfctl_kill_states(dev, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_gateway_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; struct addrinfo *res, *resp; struct sockaddr last_src; unsigned int newkilled; int killed = 0; int ret_ga; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no gateway specified"); usage(); } memset(&kill, 0, sizeof(kill)); memset(&kill.rt_addr.addr.v.a.mask, 0xff, sizeof(kill.rt_addr.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp = res; resp; resp = resp->ai_next) { if (resp->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp->ai_addr; kill.af = resp->ai_family; if (kill.af == AF_INET) kill.rt_addr.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp->ai_addr)->sin_addr; else if (kill.af == AF_INET6) kill.rt_addr.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", kill.af); if (pfctl_kill_states(dev, &kill, &newkilled)) err(1, "DIOCKILLSTATES"); killed += newkilled; } freeaddrinfo(res); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_label_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no label specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (iface != NULL && strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >= sizeof(kill.ifname)) errx(1, "invalid interface: %s", iface); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >= sizeof(kill.label)) errx(1, "label too long: %s", state_kill[1]); if (pfctl_kill_states(dev, &kill, &killed)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_id_kill_states(int dev, const char *iface, int opts) { struct pfctl_kill kill; unsigned int killed; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no id specified"); usage(); } memset(&kill, 0, sizeof(kill)); if (opts & PF_OPT_KILLMATCH) kill.kill_match = true; if ((sscanf(state_kill[1], "%jx/%x", &kill.cmp.id, &kill.cmp.creatorid)) == 2) { } else if ((sscanf(state_kill[1], "%jx", &kill.cmp.id)) == 1) { kill.cmp.creatorid = 0; } else { warnx("wrong id format specified"); usage(); } if (kill.cmp.id == 0) { warnx("cannot kill id 0"); usage(); } if (pfctl_kill_states(dev, &kill, &killed)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", killed); return (0); } int pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, char *anchorname) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; memset(&pp, 0, sizeof(pp)); memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); pp.r_action = r_action; pp.r_num = nr; pp.ticket = ticket; if (ioctl(dev, DIOCGETADDRS, &pp)) { warn("DIOCGETADDRS"); return (-1); } mpnr = pp.nr; TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { pp.nr = pnr; if (ioctl(dev, DIOCGETADDR, &pp)) { warn("DIOCGETADDR"); return (-1); } pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "calloc"); bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); TAILQ_INSERT_TAIL(&pool->list, pa, entries); } return (0); } void pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&src->list)) != NULL) { TAILQ_REMOVE(&src->list, pa, entries); TAILQ_INSERT_TAIL(&dst->list, pa, entries); } } void pfctl_clear_pool(struct pfctl_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts) { if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1])); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "sp", "da", "dp" }; int i; printf(" [ Skip steps: "); for (i = 0; i < PF_SKIP_COUNT; ++i) { if (rule->skip[i].nr == rule->nr + 1) continue; printf("%s=", t[i]); if (rule->skip[i].nr == -1) printf("end "); else printf("%u ", rule->skip[i].nr); } printf("]\n"); printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); } if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu States: %-6ju]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1]), (uintmax_t)rule->states_cur); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", (unsigned)rule->cuid, (unsigned)rule->cpid, (uintmax_t)rule->states_tot); } if (opts & PF_OPT_VERBOSE2) { char timestr[30]; if (rule->last_active_timestamp != 0) { bcopy(ctime(&rule->last_active_timestamp), timestr, sizeof(timestr)); *strchr(timestr, '\n') = '\0'; } else { snprintf(timestr, sizeof(timestr), "N/A"); } printf(" [ Last Active Time: %s ]\n", timestr); } } void pfctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } int pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { char anchor_call[MAXPATHLEN]; struct pfctl_eth_rules_info info; struct pfctl_eth_rule rule; int brace; int dotitle = opts & PF_OPT_SHOWALL; int len = strlen(path); char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl_rules: calloc"); snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfctl_eth_rulesets_info ri; u_int32_t mnr, nr; if (pfctl_get_eth_rulesets_info(dev, &ri, npath)) { if (errno == EINVAL) { fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); } else { warn("DIOCGETETHRULESETS"); return (-1); } } mnr = ri.nr; pfctl_print_eth_rule_counters(&rule, opts); for (nr = 0; nr < mnr; ++nr) { struct pfctl_eth_ruleset_info rs; if (pfctl_get_eth_ruleset(dev, npath, nr, &rs)) err(1, "DIOCGETETHRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", rs.name); pfctl_show_eth_rules(dev, npath, opts, format, rs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if (pfctl_get_eth_rules_info(dev, &info, path)) { warn("DIOCGETETHRULES"); return (-1); } for (int nr = 0; nr < info.nr; nr++) { brace = 0; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if (pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule, opts & PF_OPT_CLRRULECTRS, anchor_call) != 0) { warn("DIOCGETETHRULE"); return (-1); } if (anchor_call[0] && ((((p = strrchr(anchor_call, '_')) != NULL) && (p == anchor_call || *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { brace++; int aclen = strlen(anchor_call); if (anchor_call[aclen - 1] == '*') anchor_call[aclen - 2] = '\0'; } p = &anchor_call[0]; if (dotitle) { pfctl_print_title("ETH RULES:"); dotitle = 0; } print_eth_rule(&rule, anchor_call, opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); if (brace) printf(" {\n"); else printf("\n"); pfctl_print_eth_rule_counters(&rule, opts); if (brace) { pfctl_show_eth_rules(dev, path, opts, format, p, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } } path[len] = '\0'; return (0); } int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth, int wildcard) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr, header = 0; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path), ret = 0; char *npath, *p; /* * Truncate a trailing / and * on an anchorname before searching for * the ruleset, this is syntactic sugar that doesn't actually make it * to the kernel. */ if ((p = strrchr(anchorname, '/')) != NULL && p[1] == '*' && p[2] == '\0') { p[0] = '\0'; } if (anchorname[0] == '/') { if ((npath = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl_rules: calloc"); snprintf(npath, MAXPATHLEN, "%s", anchorname); } else { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); npath = path; } /* * If this anchor was called with a wildcard path, go through * the rulesets in the anchor rather than the rules. */ if (wildcard && (opts & PF_OPT_RECURSE)) { struct pfioc_ruleset prs; u_int32_t mnr, nr; memset(&prs, 0, sizeof(prs)); memcpy(prs.path, npath, sizeof(prs.path)); if (ioctl(dev, DIOCGETRULESETS, &prs)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' " "not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); } mnr = prs.nr; pfctl_print_rule_counters(&rule, opts); for (nr = 0; nr < mnr; ++nr) { prs.nr = nr; if (ioctl(dev, DIOCGETRULESET, &prs)) err(1, "DIOCGETRULESET"); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("anchor \"%s\" all {\n", prs.name); pfctl_show_rules(dev, npath, opts, format, prs.name, depth + 1, 0); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); } if (opts & PF_OPT_SHOWALL) { ret = pfctl_get_rules_info(dev, &ri, PF_PASS, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } header++; } ret = pfctl_get_rules_info(dev, &ri, PF_SCRUB, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } if (opts & PF_OPT_SHOWALL) { if (format == PFCTL_SHOW_RULES && (ri.nr > 0 || header)) pfctl_print_title("FILTER RULES:"); else if (format == PFCTL_SHOW_LABELS && labels) pfctl_print_title("LABEL COUNTERS:"); } for (nr = 0; nr < ri.nr; ++nr) { if (pfctl_get_clear_rule(dev, nr, ri.ticket, path, PF_SCRUB, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) { warn("DIOCGETRULENV"); goto error; } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, PF_SCRUB, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: break; case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; print_rule(&rule, anchor_call, rule_numbers, numeric); printf("\n"); pfctl_print_rule_counters(&rule, opts); break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rpool); } ret = pfctl_get_rules_info(dev, &ri, PF_PASS, path); if (ret != 0) { warn("DIOCGETRULES"); goto error; } for (nr = 0; nr < ri.nr; ++nr) { if (pfctl_get_clear_rule(dev, nr, ri.ticket, path, PF_PASS, &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) { warn("DIOCGETRULE"); goto error; } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, PF_PASS, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: { bool show = false; int i = 0; while (rule.label[i][0]) { printf("%s ", rule.label[i++]); show = true; } if (show) { printf("%llu %llu %llu %llu" " %llu %llu %llu %ju\n", (unsigned long long)rule.evaluations, (unsigned long long)(rule.packets[0] + rule.packets[1]), (unsigned long long)(rule.bytes[0] + rule.bytes[1]), (unsigned long long)rule.packets[0], (unsigned long long)rule.bytes[0], (unsigned long long)rule.packets[1], (unsigned long long)rule.bytes[1], (uintmax_t)rule.states_tot); } break; } case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); print_rule(&rule, anchor_call, rule_numbers, numeric); /* * If this is a 'unnamed' brace notation * anchor, OR the user has explicitly requested * recursion, print it recursively. */ if (anchor_call[0] && (((p = strrchr(anchor_call, '/')) ? p[1] == '_' : anchor_call[0] == '_') || opts & PF_OPT_RECURSE)) { printf(" {\n"); pfctl_print_rule_counters(&rule, opts); pfctl_show_rules(dev, npath, opts, format, anchor_call, depth + 1, rule.anchor_wildcard); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } else { printf("\n"); pfctl_print_rule_counters(&rule, opts); } break; case PFCTL_SHOW_NOTHING: break; } pfctl_clear_pool(&rule.rpool); } error: path[len] = '\0'; return (ret); } int pfctl_show_nat(int dev, char *path, int opts, char *anchorname, int depth) { struct pfctl_rules_info ri; struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i, dotitle = opts & PF_OPT_SHOWALL; int brace, ret; int len = strlen(path); char *p; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); for (i = 0; i < 3; i++) { ret = pfctl_get_rules_info(dev, &ri, nattype[i], path); if (ret != 0) { warn("DIOCGETRULES"); return (-1); } for (nr = 0; nr < ri.nr; ++nr) { brace = 0; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if (pfctl_get_rule(dev, nr, ri.ticket, path, nattype[i], &rule, anchor_call)) { warn("DIOCGETRULE"); return (-1); } if (pfctl_get_pool(dev, &rule.rpool, nr, ri.ticket, nattype[i], path) != 0) return (-1); if (anchor_call[0] && ((((p = strrchr(anchor_call, '_')) != NULL) && (p == anchor_call || *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { brace++; if ((p = strrchr(anchor_call, '/')) != NULL) p++; else p = &anchor_call[0]; } else p = &anchor_call[0]; if (dotitle) { pfctl_print_title("TRANSLATION RULES:"); dotitle = 0; } print_rule(&rule, anchor_call, opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); if (brace) printf(" {\n"); else printf("\n"); pfctl_print_rule_counters(&rule, opts); pfctl_clear_pool(&rule.rpool); if (brace) { pfctl_show_nat(dev, path, opts, p, depth + 1); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } } } return (0); } int pfctl_show_src_nodes(int dev, int opts) { struct pfioc_src_nodes psn; struct pf_src_node *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i; memset(&psn, 0, sizeof(psn)); for (;;) { psn.psn_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); psn.psn_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) { warn("DIOCGETSRCNODES"); free(inbuf); return (-1); } if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len) break; if (len == 0 && psn.psn_len == 0) goto done; if (len == 0 && psn.psn_len != 0) len = psn.psn_len; if (psn.psn_len == 0) goto done; /* no src_nodes */ len *= 2; } p = psn.psn_src_nodes; if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL)) pfctl_print_title("SOURCE TRACKING NODES:"); for (i = 0; i < psn.psn_len; i += sizeof(*p)) { print_src_node(p, opts); p++; } done: free(inbuf); return (0); } struct pfctl_show_state_arg { int opts; int dotitle; const char *iface; }; static int pfctl_show_state(struct pfctl_state *s, void *arg) { struct pfctl_show_state_arg *a = (struct pfctl_show_state_arg *)arg; if (a->iface != NULL && strcmp(s->ifname, a->iface)) return (0); if (a->dotitle) { pfctl_print_title("STATES:"); a->dotitle = 0; } print_state(s, a->opts); return (0); } int pfctl_show_states(int dev, const char *iface, int opts) { struct pfctl_show_state_arg arg; arg.opts = opts; arg.dotitle = opts & PF_OPT_SHOWALL; arg.iface = iface; if (pfctl_get_states_iter(pfctl_show_state, &arg)) return (-1); return (0); } int pfctl_show_status(int dev, int opts) { struct pfctl_status *status; struct pfctl_syncookies cookies; if ((status = pfctl_get_status(dev)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } if (pfctl_get_syncookies(dev, &cookies)) { pfctl_free_status(status); warn("DIOCGETSYNCOOKIES"); return (-1); } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INFO:"); print_status(status, &cookies, opts); pfctl_free_status(status); return (0); } int pfctl_show_running(int dev) { struct pfctl_status *status; int running; if ((status = pfctl_get_status(dev)) == NULL) { warn("DIOCGETSTATUS"); return (-1); } running = status->running; print_running(status); pfctl_free_status(status); return (!running); } int pfctl_show_timeouts(int dev, int opts) { struct pfioc_tm pt; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("TIMEOUTS:"); memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { pt.timeout = pf_timeouts[i].timeout; if (ioctl(dev, DIOCGETTIMEOUT, &pt)) err(1, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START && pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END) printf(" states"); else printf("s"); printf("\n"); } return (0); } int pfctl_show_limits(int dev, int opts) { struct pfioc_limit pl; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { pl.index = pf_limits[i].index; if (ioctl(dev, DIOCGETLIMIT, &pl)) err(1, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); if (pl.limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %8u\n", pl.limit); } return (0); } +void +pfctl_show_creators(int opts) +{ + int ret; + uint32_t creators[16]; + size_t count = nitems(creators); + + ret = pfctl_get_creatorids(creators, &count); + if (ret != 0) + errx(ret, "Failed to retrieve creators"); + + printf("Creator IDs:\n"); + for (size_t i = 0; i < count; i++) + printf("%08x\n", creators[i]); +} + /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) err(1, "DIOCBEGINADDRS"); } pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) err(1, "DIOCADDADDR"); } } return (0); } int pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, const char *anchor_call) { u_int8_t rs_num; struct pfctl_rule *rule; struct pfctl_ruleset *rs; char *p; rs_num = pf_get_ruleset_number(r->action); if (rs_num == PF_RULESET_MAX) errx(1, "Invalid rule type %d", r->action); rs = &pf->anchor->ruleset; if (anchor_call[0] && r->anchor == NULL) { /* * Don't make non-brace anchors part of the main anchor pool. */ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) err(1, "pfctl_append_rule: calloc"); pf_init_ruleset(&r->anchor->ruleset); r->anchor->ruleset.anchor = r->anchor; if (strlcpy(r->anchor->path, anchor_call, sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) errx(1, "pfctl_append_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_append_rule: bad anchor name %s", anchor_call); } else p = (char *)anchor_call; if (strlcpy(r->anchor->name, p, sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) errx(1, "pfctl_append_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INIT(&rule->rpool.list); pfctl_move_pool(&r->rpool, &rule->rpool); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); return (0); } int pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r, const char *anchor_call) { struct pfctl_eth_rule *rule; struct pfctl_eth_ruleset *rs; char *p; rs = &pf->eanchor->ruleset; if (anchor_call[0] && r->anchor == NULL) { /* * Don't make non-brace anchors part of the main anchor pool. */ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) err(1, "pfctl_append_rule: calloc"); pf_init_eth_ruleset(&r->anchor->ruleset); r->anchor->ruleset.anchor = r->anchor; if (strlcpy(r->anchor->path, anchor_call, sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) errx(1, "pfctl_append_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_append_eth_rule: bad anchor name %s", anchor_call); } else p = (char *)anchor_call; if (strlcpy(r->anchor->name, p, sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) errx(1, "pfctl_append_eth_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INSERT_TAIL(&rs->rules, rule, entries); return (0); } int pfctl_eth_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_eth_anchor *a) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) return (1); } if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) { if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path)) return (1); } if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_RDR, path)) return (1); } if (a == pf->astack[0] && ((altqsupport && (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) { if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path)) return (2); } if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) || pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path)) return (3); } if (pf->loadopt & PFCTL_FLAG_TABLE) if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path)) return (4); if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int pfctl_load_eth_ruleset(struct pfctl *pf, char *path, struct pfctl_eth_ruleset *rs, int depth) { struct pfctl_eth_rule *r; int error, len = strlen(path); int brace = 0; pf->eanchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name); if (depth) { if (TAILQ_FIRST(&rs->rules) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_eth_ruleset_trans(pf, path, rs->anchor))) { printf("pfctl_load_eth_rulesets: " "pfctl_eth_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } while ((r = TAILQ_FIRST(&rs->rules)) != NULL) { TAILQ_REMOVE(&rs->rules, r, entries); error = pfctl_load_eth_rule(pf, path, r, depth); if (error) return (error); if (r->anchor) { if ((error = pfctl_load_eth_ruleset(pf, path, &r->anchor->ruleset, depth + 1))) return (error); } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); } if (brace && pf->opts & PF_OPT_VERBOSE) { INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (error); } int pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r, int depth) { char *name; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_eth_rule: strlcpy"); if (r->anchor) { if (r->anchor->match) { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", r->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", r->anchor->name); name = r->anchor->name; } else name = r->anchor->path; } else name = ""; if ((pf->opts & PF_OPT_NOACTION) == 0) if (pfctl_add_eth_rule(pf->dev, r, anchor, name, pf->eth_ticket)) err(1, "DIOCADDETHRULENV"); if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_eth_rule(r, r->anchor ? r->anchor->name : "", pf->opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG)); } path[len] = '\0'; return (0); } int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) { struct pfctl_rule *r; int error, len = strlen(path); int brace = 0; pf->anchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name); if (depth) { if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_ruleset_trans(pf, path, rs->anchor, false))) { printf("pfctl_load_rulesets: " "pfctl_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } if (pf->optimize && rs_num == PF_RULESET_FILTER) pfctl_optimize_ruleset(pf, rs); while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries); for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) expand_label(r->label[i], PF_RULE_LABEL_SIZE, r); expand_label(r->tagname, PF_TAG_NAME_SIZE, r); expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r); if ((error = pfctl_load_rule(pf, path, r, depth))) goto error; if (r->anchor) { if ((error = pfctl_load_ruleset(pf, path, &r->anchor->ruleset, rs_num, depth + 1))) goto error; } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); } if (brace && pf->opts & PF_OPT_VERBOSE) { INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (error); } int pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth) { u_int8_t rs_num = pf_get_ruleset_number(r->action); char *name; u_int32_t ticket; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); int error; bool was_present; /* set up anchor before adding to path for anchor_call */ if ((pf->opts & PF_OPT_NOACTION) == 0) ticket = pfctl_get_ticket(pf->trans, rs_num, path); if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_rule: strlcpy"); if (r->anchor) { if (r->anchor->match) { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", r->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", r->anchor->name); name = r->anchor->name; } else name = r->anchor->path; } else name = ""; was_present = false; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); error = pfctl_add_rule(pf->dev, r, anchor, name, ticket, pf->paddr.ticket); switch (error) { case 0: /* things worked, do nothing */ break; case EEXIST: /* an identical rule is already present */ was_present = true; break; default: err(1, "DIOCADDRULENV"); } } if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_rule(r, name, pf->opts & PF_OPT_VERBOSE2, pf->opts & PF_OPT_NUMERIC); if (was_present) printf(" -- rule was already present"); } path[len] = '\0'; pfctl_clear_pool(&r->rpool); return (0); } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { if (altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0) { memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { if (errno == ENXIO) errx(1, "qtype not configured"); else if (errno == ENODEV) errx(1, "%s: driver does not support " "altq", a->ifname); else err(1, "DIOCADDALTQ"); } } pfaltq_store(&pf->paltq->altq); } return (0); } int pfctl_rules(int dev, char *filename, int opts, int optimize, char *anchorname, struct pfr_buffer *trans) { #define ERR(x) do { warn(x); goto _error; } while(0) #define ERRX(x) do { warnx(x); goto _error; } while(0) struct pfr_buffer *t, buf; struct pfioc_altq pa; struct pfctl pf; struct pfctl_ruleset *rs; struct pfctl_eth_ruleset *ethrs; struct pfr_table trs; char *path; int osize; RB_INIT(&pf_anchors); memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); pf_main_anchor.ruleset.anchor = &pf_main_anchor; memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor)); pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset); pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor; if (trans == NULL) { bzero(&buf, sizeof(buf)); buf.pfrb_type = PFRB_TRANS; t = &buf; osize = 0; } else { t = trans; osize = t->pfrb_size; } memset(&pa, 0, sizeof(pa)); pa.version = PFIOC_ALTQ_VERSION; memset(&pf, 0, sizeof(pf)); memset(&trs, 0, sizeof(trs)); if ((path = calloc(1, MAXPATHLEN)) == NULL) ERRX("pfctl_rules: calloc"); if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) ERRX("pfctl_rules: strlcpy"); pf.dev = dev; pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) ERRX("pfctl_rules: calloc"); rs = &pf.anchor->ruleset; pf_init_ruleset(rs); rs->anchor = pf.anchor; if (strlcpy(pf.anchor->path, anchorname, sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) errx(1, "pfctl_rules: strlcpy"); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) errx(1, "pfctl_rules: strlcpy"); pf.astack[0] = pf.anchor; pf.asd = 0; if (anchorname[0]) pf.loadopt &= ~PFCTL_FLAG_ALTQ; pf.paltq = &pa; pf.trans = t; pfctl_init_options(&pf); /* Set up ethernet anchor */ if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL) ERRX("pfctl_rules: calloc"); if (strlcpy(pf.eanchor->path, anchorname, sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path)) errx(1, "pfctl_rules: strlcpy"); if (strlcpy(pf.eanchor->name, anchorname, sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name)) errx(1, "pfctl_rules: strlcpy"); ethrs = &pf.eanchor->ruleset; pf_init_eth_ruleset(ethrs); ethrs->anchor = pf.eanchor; pf.eastack[0] = pf.eanchor; if ((opts & PF_OPT_NOACTION) == 0) { /* * XXX For the time being we need to open transactions for * the main ruleset before parsing, because tables are still * loaded at parse time. */ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true)) ERRX("pfctl_rules"); if (pf.loadopt & PFCTL_FLAG_ETH) pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname); if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); if (pf.loadopt & PFCTL_FLAG_TABLE) pf.astack[0]->ruleset.tticket = pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname); } if (parse_config(filename, &pf) < 0) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Syntax error in config file: " "pf rules not loaded"); else goto _error; } if (loadopt & PFCTL_FLAG_OPTION) pfctl_adjust_skip_ifaces(&pf); if ((pf.loadopt & PFCTL_FLAG_FILTER && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || (pf.loadopt & PFCTL_FLAG_ETH && (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) || (pf.loadopt & PFCTL_FLAG_NAT && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) || (pf.loadopt & PFCTL_FLAG_FILTER && pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Unable to load rules into kernel"); else goto _error; } if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) if (check_commit_altq(dev, opts) != 0) ERRX("errors in altq config"); /* process "load anchor" directives */ if (!anchorname[0]) if (pfctl_load_anchors(dev, &pf, t) == -1) ERRX("load anchors"); if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) { if (!anchorname[0]) if (pfctl_load_options(&pf)) goto _error; if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) ERR("DIOCXCOMMIT"); } free(path); return (0); _error: if (trans == NULL) { /* main ruleset */ if ((opts & PF_OPT_NOACTION) == 0) if (pfctl_trans(dev, t, DIOCXROLLBACK, osize)) err(1, "DIOCXROLLBACK"); exit(1); } else { /* sub ruleset */ free(path); return (-1); } #undef ERR #undef ERRX } FILE * pfctl_fopen(const char *name, const char *mode) { struct stat st; FILE *fp; fp = fopen(name, mode); if (fp == NULL) return (NULL); if (fstat(fileno(fp), &st)) { fclose(fp); return (NULL); } if (S_ISDIR(st.st_mode)) { fclose(fp); errno = EISDIR; return (NULL); } return (fp); } void pfctl_init_options(struct pfctl *pf) { pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL; pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT; pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT; pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT; pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT; pf->debug = PF_DEBUG_URGENT; pf->reassemble = 0; pf->syncookies = false; pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT; pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT; } int pfctl_load_options(struct pfctl *pf) { int i, error = 0; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); /* load limits */ for (i = 0; i < PF_LIMIT_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i]) continue; if (pfctl_load_limit(pf, i, pf->limit[i])) error = 1; } /* * If we've set the limit, but haven't explicitly set adaptive * timeouts, do it now with a start of 60% and end of 120%. */ if (pf->limit_set[PF_LIMIT_STATES] && !pf->timeout_set[PFTM_ADAPTIVE_START] && !pf->timeout_set[PFTM_ADAPTIVE_END]) { pf->timeout[PFTM_ADAPTIVE_START] = (pf->limit[PF_LIMIT_STATES] / 10) * 6; pf->timeout_set[PFTM_ADAPTIVE_START] = 1; pf->timeout[PFTM_ADAPTIVE_END] = (pf->limit[PF_LIMIT_STATES] / 10) * 12; pf->timeout_set[PFTM_ADAPTIVE_END] = 1; } /* load timeouts */ for (i = 0; i < PFTM_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i]) continue; if (pfctl_load_timeout(pf, i, pf->timeout[i])) error = 1; } /* load debug */ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set) if (pfctl_load_debug(pf, pf->debug)) error = 1; /* load logif */ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set) if (pfctl_load_logif(pf, pf->ifname)) error = 1; /* load hostid */ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set) if (pfctl_load_hostid(pf, pf->hostid)) error = 1; /* load reassembly settings */ if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set) if (pfctl_load_reassembly(pf, pf->reassemble)) error = 1; /* load keepcounters */ if (pfctl_set_keepcounters(pf->dev, pf->keep_counters)) error = 1; /* load syncookies settings */ if (pfctl_load_syncookies(pf, pf->syncookies)) error = 1; return (error); } int pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) { int i; for (i = 0; pf_limits[i].name; i++) { if (strcasecmp(opt, pf_limits[i].name) == 0) { pf->limit[pf_limits[i].index] = limit; pf->limit_set[pf_limits[i].index] = 1; break; } } if (pf_limits[i].name == NULL) { warnx("Bad pool name."); return (1); } if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); return (0); } int pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) { struct pfioc_limit pl; memset(&pl, 0, sizeof(pl)); pl.index = index; pl.limit = limit; if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { if (errno == EBUSY) warnx("Current pool size exceeds requested hard limit"); else warnx("DIOCSETLIMIT"); return (1); } return (0); } int pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_timeouts[i].name; i++) { if (strcasecmp(opt, pf_timeouts[i].name) == 0) { pf->timeout[pf_timeouts[i].timeout] = seconds; pf->timeout_set[pf_timeouts[i].timeout] = 1; break; } } if (pf_timeouts[i].name == NULL) { warnx("Bad timeout name."); return (1); } if (pf->opts & PF_OPT_VERBOSE && ! quiet) printf("set timeout %s %d\n", opt, seconds); return (0); } int pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds) { struct pfioc_tm pt; memset(&pt, 0, sizeof(pt)); pt.timeout = timeout; pt.seconds = seconds; if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) { warnx("DIOCSETTIMEOUT"); return (1); } return (0); } int pfctl_set_reassembly(struct pfctl *pf, int on, int nodf) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); pf->reass_set = 1; if (on) { pf->reassemble = PF_REASS_ENABLED; if (nodf) pf->reassemble |= PF_REASS_NODF; } else { pf->reassemble = 0; } if (pf->opts & PF_OPT_VERBOSE) printf("set reassemble %s %s\n", on ? "yes" : "no", nodf ? "no-df" : ""); return (0); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { const struct pf_hint *hint; int i, r; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_hints[i].name; i++) if (strcasecmp(opt, pf_hints[i].name) == 0) break; hint = pf_hints[i].hint; if (hint == NULL) { warnx("invalid state timeouts optimization"); return (1); } for (i = 0; hint[i].name; i++) if ((r = pfctl_set_timeout(pf, hint[i].name, hint[i].timeout, 1))) return (r); if (pf->opts & PF_OPT_VERBOSE) printf("set optimization %s\n", opt); return (0); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(ifname, "none")) { free(pf->ifname); pf->ifname = NULL; } else { pf->ifname = strdup(ifname); if (!pf->ifname) errx(1, "pfctl_set_logif: strdup"); } pf->ifname_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set loginterface %s\n", ifname); return (0); } int pfctl_load_logif(struct pfctl *pf, char *ifname) { struct pfioc_if pi; memset(&pi, 0, sizeof(pi)); if (ifname && strlcpy(pi.ifname, ifname, sizeof(pi.ifname)) >= sizeof(pi.ifname)) { warnx("pfctl_load_logif: strlcpy"); return (1); } if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) { warnx("DIOCSETSTATUSIF"); return (1); } return (0); } int pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); HTONL(hostid); pf->hostid = hostid; pf->hostid_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set hostid 0x%08x\n", ntohl(hostid)); return (0); } int pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) { if (ioctl(dev, DIOCSETHOSTID, &hostid)) { warnx("DIOCSETHOSTID"); return (1); } return (0); } int pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly) { if (ioctl(dev, DIOCSETREASS, &reassembly)) { warnx("DIOCSETREASS"); return (1); } return (0); } int pfctl_load_syncookies(struct pfctl *pf, u_int8_t val) { struct pfctl_syncookies cookies; bzero(&cookies, sizeof(cookies)); cookies.mode = val; cookies.lowwater = pf->syncookieswat[0]; cookies.highwater = pf->syncookieswat[1]; if (pfctl_set_syncookies(dev, &cookies)) { warnx("DIOCSETSYNCOOKIES"); return (1); } return (0); } int pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w) { if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) { warnx("syncookies start/end only apply to adaptive"); return (1); } if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) { if (!w->hi) w->hi = PF_SYNCOOKIES_HIWATPCT; if (!w->lo) w->lo = w->hi / 2; if (w->lo >= w->hi) { warnx("start must be higher than end"); return (1); } pf->syncookieswat[0] = w->lo; pf->syncookieswat[1] = w->hi; pf->syncookieswat_set = 1; } if (pf->opts & PF_OPT_VERBOSE) { if (val == PF_SYNCOOKIES_NEVER) printf("set syncookies never\n"); else if (val == PF_SYNCOOKIES_ALWAYS) printf("set syncookies always\n"); else if (val == PF_SYNCOOKIES_ADAPTIVE) { if (pf->syncookieswat_set) printf("set syncookies adaptive (start %u%%, " "end %u%%)\n", pf->syncookieswat[1], pf->syncookieswat[0]); else printf("set syncookies adaptive\n"); } else { /* cannot happen */ warnx("king bula ate all syncookies"); return (1); } } pf->syncookies = val; return (0); } int pfctl_set_debug(struct pfctl *pf, char *d) { u_int32_t level; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(d, "none")) pf->debug = PF_DEBUG_NONE; else if (!strcmp(d, "urgent")) pf->debug = PF_DEBUG_URGENT; else if (!strcmp(d, "misc")) pf->debug = PF_DEBUG_MISC; else if (!strcmp(d, "loud")) pf->debug = PF_DEBUG_NOISY; else { warnx("unknown debug level \"%s\"", d); return (-1); } pf->debug_set = 1; level = pf->debug; if ((pf->opts & PF_OPT_NOACTION) == 0) if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if (pf->opts & PF_OPT_VERBOSE) printf("set debug %s\n", d); return (0); } int pfctl_load_debug(struct pfctl *pf, unsigned int level) { if (ioctl(pf->dev, DIOCSETDEBUG, &level)) { warnx("DIOCSETDEBUG"); return (1); } return (0); } int pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) { struct pfioc_iface pi; struct node_host *h = NULL, *n = NULL; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); bzero(&pi, sizeof(pi)); pi.pfiio_flags = flags; /* Make sure our cache matches the kernel. If we set or clear the flag * for a group this applies to all members. */ h = ifa_grouplookup(ifname, 0); for (n = h; n != NULL; n = n->next) pfctl_set_interface_flags(pf, n->ifname, flags, how); if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) errx(1, "pfctl_set_interface_flags: strlcpy"); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (how == 0) { if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); } else { if (ioctl(pf->dev, DIOCSETIFFLAG, &pi)) err(1, "DIOCSETIFFLAG"); pfctl_check_skip_ifaces(ifname); } } return (0); } void pfctl_debug(int dev, u_int32_t level, int opts) { if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if ((opts & PF_OPT_QUIET) == 0) { fprintf(stderr, "debug level set to '"); switch (level) { case PF_DEBUG_NONE: fprintf(stderr, "none"); break; case PF_DEBUG_URGENT: fprintf(stderr, "urgent"); break; case PF_DEBUG_MISC: fprintf(stderr, "misc"); break; case PF_DEBUG_NOISY: fprintf(stderr, "loud"); break; default: fprintf(stderr, ""); break; } fprintf(stderr, "'\n"); } } int pfctl_test_altqsupport(int dev, int opts) { struct pfioc_altq pa; pa.version = PFIOC_ALTQ_VERSION; if (ioctl(dev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { if (opts & PF_OPT_VERBOSE) fprintf(stderr, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else err(1, "DIOCGETALTQS"); } return (1); } int pfctl_show_anchors(int dev, int opts, char *anchorname) { struct pfioc_ruleset pr; u_int32_t mnr, nr; memset(&pr, 0, sizeof(pr)); memcpy(pr.path, anchorname, sizeof(pr.path)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { char sub[MAXPATHLEN]; pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (pr.path[0]) { strlcat(sub, pr.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, pr.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub)) return (-1); } return (0); } int pfctl_show_eth_anchors(int dev, int opts, char *anchorname) { struct pfctl_eth_rulesets_info ri; struct pfctl_eth_ruleset_info rs; int ret; if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) { if (ret == ENOENT) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else err(1, "DIOCGETETHRULESETS"); return (-1); } for (int nr = 0; nr < ri.nr; nr++) { char sub[MAXPATHLEN]; if (pfctl_get_eth_ruleset(dev, anchorname, nr, &rs) != 0) err(1, "DIOCGETETHRULESET"); if (!strcmp(rs.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (rs.path[0]) { strlcat(sub, rs.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, rs.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub)) return (-1); } return (0); } const char * pfctl_lookup_option(char *cmd, const char * const *list) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int mode = O_RDONLY; int opts = 0; int optimize = PF_OPTIMIZE_BASIC; char anchorname[MAXPATHLEN]; char *path; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:t:T:vx:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; break; case 'd': opts |= PF_OPT_DISABLE; mode = O_RDWR; break; case 'D': if (pfctl_cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'e': opts |= PF_OPT_ENABLE; mode = O_RDWR; break; case 'q': opts |= PF_OPT_QUIET; break; case 'F': clearopt = pfctl_lookup_option(optarg, clearopt_list); if (clearopt == NULL) { warnx("Unknown flush modifier '%s'", optarg); usage(); } mode = O_RDWR; break; case 'i': ifaceopt = optarg; break; case 'k': if (state_killers >= 2) { warnx("can only specify -k twice"); usage(); /* NOTREACHED */ } state_kill[state_killers++] = optarg; mode = O_RDWR; break; case 'K': if (src_node_killers >= 2) { warnx("can only specify -K twice"); usage(); /* NOTREACHED */ } src_node_kill[src_node_killers++] = optarg; mode = O_RDWR; break; case 'm': opts |= PF_OPT_MERGE; break; case 'M': opts |= PF_OPT_KILLMATCH; break; case 'n': opts |= PF_OPT_NOACTION; break; case 'N': loadopt |= PFCTL_FLAG_NAT; break; case 'r': opts |= PF_OPT_USEDNS; break; case 'f': rulesopt = optarg; mode = O_RDWR; break; case 'g': opts |= PF_OPT_DEBUG; break; case 'A': loadopt |= PFCTL_FLAG_ALTQ; break; case 'R': loadopt |= PFCTL_FLAG_FILTER; break; case 'o': optiopt = pfctl_lookup_option(optarg, optiopt_list); if (optiopt == NULL) { warnx("Unknown optimization '%s'", optarg); usage(); } opts |= PF_OPT_OPTIMIZE; break; case 'O': loadopt |= PFCTL_FLAG_OPTION; break; case 'p': pf_device = optarg; break; case 'P': opts |= PF_OPT_NUMERIC; break; case 's': showopt = pfctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); } break; case 't': tableopt = optarg; break; case 'T': tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); if (tblcmdopt == NULL) { warnx("Unknown table command '%s'", optarg); usage(); } break; case 'v': if (opts & PF_OPT_VERBOSE) opts |= PF_OPT_VERBOSE2; opts |= PF_OPT_VERBOSE; break; case 'x': debugopt = pfctl_lookup_option(optarg, debugopt_list); if (debugopt == NULL) { warnx("Unknown debug level '%s'", optarg); usage(); } mode = O_RDWR; break; case 'z': opts |= PF_OPT_CLRRULECTRS; mode = O_RDWR; break; case 'h': /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } } if (tblcmdopt != NULL) { argc -= optind; argv += optind; ch = *tblcmdopt; if (ch == 'l') { loadopt |= PFCTL_FLAG_TABLE; tblcmdopt = NULL; } else mode = strchr("acdefkrz", ch) ? O_RDWR : O_RDONLY; } else if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (loadopt == 0) loadopt = ~0; if ((path = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl: calloc"); memset(anchorname, 0, sizeof(anchorname)); if (anchoropt != NULL) { int len = strlen(anchoropt); if (len >= 1 && anchoropt[len - 1] == '*') { if (len >= 2 && anchoropt[len - 2] == '/') anchoropt[len - 2] = '\0'; else anchoropt[len - 1] = '\0'; opts |= PF_OPT_RECURSE; } if (strlcpy(anchorname, anchoropt, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH; } if ((opts & PF_OPT_NOACTION) == 0) { dev = open(pf_device, mode); if (dev == -1) err(1, "%s", pf_device); altqsupport = pfctl_test_altqsupport(dev, opts); } else { dev = open(pf_device, O_RDONLY); if (dev >= 0) opts |= PF_OPT_DUMMYACTION; /* turn off options */ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); clearopt = showopt = debugopt = NULL; #if !defined(ENABLE_ALTQ) altqsupport = 0; #else altqsupport = 1; #endif } if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); if (opts & PF_OPT_VERBOSE2) printf("Ethernet:\n"); pfctl_show_eth_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, anchorname, 0, 0); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, anchorname, 0, 0); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, path, opts, anchorname, 0); break; case 'q': pfctl_show_altq(dev, ifaceopt, opts, opts & PF_OPT_VERBOSE2); break; case 's': pfctl_show_states(dev, ifaceopt, opts); break; case 'S': pfctl_show_src_nodes(dev, opts); break; case 'i': pfctl_show_status(dev, opts); break; case 'R': error = pfctl_show_running(dev); break; case 't': pfctl_show_timeouts(dev, opts); break; case 'm': pfctl_show_limits(dev, opts); break; case 'e': pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_nat(dev, path, opts, anchorname, 0); pfctl_show_rules(dev, path, opts, 0, anchorname, 0, 0); pfctl_show_altq(dev, ifaceopt, opts, 0); pfctl_show_states(dev, ifaceopt, opts); pfctl_show_src_nodes(dev, opts); pfctl_show_status(dev, opts); pfctl_show_rules(dev, path, opts, 1, anchorname, 0, 0); pfctl_show_timeouts(dev, opts); pfctl_show_limits(dev, opts); pfctl_show_tables(anchorname, opts); pfctl_show_fingerprints(opts); break; case 'T': pfctl_show_tables(anchorname, opts); break; case 'o': pfctl_load_fingerprints(dev, opts); pfctl_show_fingerprints(opts); break; case 'I': pfctl_show_ifaces(ifaceopt, opts); break; + case 'c': + pfctl_show_creators(opts); + break; } } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) { pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0, 0); } if (clearopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); switch (*clearopt) { case 'e': pfctl_flush_eth_rules(dev, opts, anchorname); break; case 'r': pfctl_flush_rules(dev, opts, anchorname); break; case 'n': pfctl_flush_nat(dev, opts, anchorname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_iface_states(dev, ifaceopt, opts); break; case 'S': pfctl_clear_src_nodes(dev, opts); break; case 'i': pfctl_clear_stats(dev, opts); break; case 'a': pfctl_flush_eth_rules(dev, opts, anchorname); pfctl_flush_rules(dev, opts, anchorname); pfctl_flush_nat(dev, opts, anchorname); pfctl_clear_tables(anchorname, opts); if (!*anchorname) { pfctl_clear_altq(dev, opts); pfctl_clear_iface_states(dev, ifaceopt, opts); pfctl_clear_src_nodes(dev, opts); pfctl_clear_stats(dev, opts); pfctl_clear_fingerprints(dev, opts); pfctl_clear_interface_flags(dev, opts); } break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_clear_tables(anchorname, opts); break; } } if (state_killers) { if (!strcmp(state_kill[0], "label")) pfctl_label_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "id")) pfctl_id_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "gateway")) pfctl_gateway_kill_states(dev, ifaceopt, opts); else pfctl_net_kill_states(dev, ifaceopt, opts); } if (src_node_killers) pfctl_kill_src_nodes(dev, ifaceopt, opts); if (tblcmdopt != NULL) { error = pfctl_command_tables(argc, argv, tableopt, tblcmdopt, rulesopt, anchorname, opts); rulesopt = NULL; } if (optiopt != NULL) { switch (*optiopt) { case 'n': optimize = 0; break; case 'b': optimize |= PF_OPTIMIZE_BASIC; break; case 'o': case 'p': optimize |= PF_OPTIMIZE_PROFILE; break; } } if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) && !anchorname[0] && !(opts & PF_OPT_NOACTION)) if (pfctl_get_skip_ifaces()) error = 1; if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) && !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION)) if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) error = 1; if (rulesopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); if (pfctl_rules(dev, rulesopt, opts, optimize, anchorname, NULL)) error = 1; else if (!(opts & PF_OPT_NOACTION) && (loadopt & PFCTL_FLAG_TABLE)) warn_namespace_collision(NULL); } if (opts & PF_OPT_ENABLE) if (pfctl_enable(dev, opts)) error = 1; if (debugopt != NULL) { switch (*debugopt) { case 'n': pfctl_debug(dev, PF_DEBUG_NONE, opts); break; case 'u': pfctl_debug(dev, PF_DEBUG_URGENT, opts); break; case 'm': pfctl_debug(dev, PF_DEBUG_MISC, opts); break; case 'l': pfctl_debug(dev, PF_DEBUG_NOISY, opts); break; } } exit(error); } diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index b9da5e96a90e..cf198d557299 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -1,152 +1,153 @@ /* $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 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))) int pfr_get_fd(void); 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_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_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 *, 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)); char *pfr_strerror(int); int pfi_get_ifaces(const char *, struct pfi_kif *, int *); int pfi_clr_istats(const char *, int *, int); void pfctl_print_title(char *); int pfctl_clear_tables(const char *, int); int pfctl_show_tables(const char *, int); int pfctl_command_tables(int, char *[], char *, const char *, char *, const char *, int); int pfctl_show_altq(int, const char *, int, int); void warn_namespace_collision(const char *); int 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_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 unmask(struct pf_addr *, sa_family_t); 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); #endif /* _PFCTL_H_ */ diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c index cbea76e7386f..bb50b3b2b321 100644 --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -1,292 +1,372 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023 Alexander V. Chernikov * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include "opt_netlink.h" #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_MOD_NAME nl_pf #define DEBUG_MAX_LEVEL LOG_DEBUG3 #include _DECLARE_DEBUG(LOG_DEBUG); struct nl_parsed_state { uint8_t version; uint32_t id; uint32_t creatorid; }; #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 }, }; static const struct nlfield_parser nlf_p_generic[] = { { .off_in = _IN(version), .off_out = _OUT(version), .cb = nlf_get_u8 }, }; #undef _IN #undef _OUT NL_DECLARE_PARSER(state_parser, struct genlmsghdr, nlf_p_generic, nla_p_state); static void dump_addr(struct nl_writer *nw, int attr, const struct pf_addr *addr, int af) { switch (af) { case AF_INET: nlattr_add(nw, attr, 4, &addr->v4); break; case AF_INET6: nlattr_add(nw, attr, 16, &addr->v6); break; }; } static bool dump_state_peer(struct nl_writer *nw, int attr, const struct pf_state_peer *peer) { int off = nlattr_add_nested(nw, attr); if (off == 0) return (false); nlattr_add_u32(nw, PF_STP_SEQLO, peer->seqlo); nlattr_add_u32(nw, PF_STP_SEQHI, peer->seqhi); nlattr_add_u32(nw, PF_STP_SEQDIFF, peer->seqdiff); nlattr_add_u16(nw, PF_STP_MAX_WIN, peer->max_win); nlattr_add_u16(nw, PF_STP_MSS, peer->mss); nlattr_add_u8(nw, PF_STP_STATE, peer->state); nlattr_add_u8(nw, PF_STP_WSCALE, peer->wscale); if (peer->scrub != NULL) { struct pf_state_scrub *sc = peer->scrub; uint16_t pfss_flags = sc->pfss_flags & PFSS_TIMESTAMP; nlattr_add_u16(nw, PF_STP_PFSS_FLAGS, pfss_flags); nlattr_add_u32(nw, PF_STP_PFSS_TS_MOD, sc->pfss_ts_mod); nlattr_add_u8(nw, PF_STP_PFSS_TTL, sc->pfss_ttl); nlattr_add_u8(nw, PF_STP_SCRUB_FLAG, PFSYNC_SCRUB_FLAG_VALID); } nlattr_set_len(nw, off); return (true); } static bool dump_state_key(struct nl_writer *nw, int attr, const struct pf_state_key *key) { int off = nlattr_add_nested(nw, attr); if (off == 0) return (false); dump_addr(nw, PF_STK_ADDR0, &key->addr[0], key->af); dump_addr(nw, PF_STK_ADDR1, &key->addr[1], key->af); nlattr_add_u16(nw, PF_STK_PORT0, key->port[0]); nlattr_add_u16(nw, PF_STK_PORT1, key->port[1]); nlattr_set_len(nw, off); return (true); } static int dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, struct pf_kstate *s, struct nl_pstate *npt) { struct nl_writer *nw = npt->nw; int error = 0; int af; struct pf_state_key *key; if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) goto enomem; struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); ghdr_new->cmd = PFNL_CMD_GETSTATES; ghdr_new->version = 0; ghdr_new->reserved = 0; nlattr_add_u64(nw, PF_ST_VERSION, PF_STATE_VERSION); key = s->key[PF_SK_WIRE]; if (!dump_state_key(nw, PF_ST_KEY_WIRE, key)) goto enomem; key = s->key[PF_SK_STACK]; if (!dump_state_key(nw, PF_ST_KEY_STACK, key)) goto enomem; af = s->key[PF_SK_WIRE]->af; nlattr_add_u8(nw, PF_ST_PROTO, s->key[PF_SK_WIRE]->proto); nlattr_add_u8(nw, PF_ST_AF, af); nlattr_add_string(nw, PF_ST_IFNAME, s->kif->pfik_name); nlattr_add_string(nw, PF_ST_ORIG_IFNAME, s->orig_kif->pfik_name); dump_addr(nw, PF_ST_RT_ADDR, &s->rt_addr, af); nlattr_add_u32(nw, PF_ST_CREATION, time_uptime - s->creation); uint32_t expire = pf_state_expires(s); if (expire > time_uptime) expire = expire - time_uptime; nlattr_add_u32(nw, PF_ST_EXPIRE, expire); nlattr_add_u8(nw, PF_ST_DIRECTION, s->direction); nlattr_add_u8(nw, PF_ST_LOG, s->act.log); nlattr_add_u8(nw, PF_ST_TIMEOUT, s->timeout); nlattr_add_u16(nw, PF_ST_STATE_FLAGS, s->state_flags); uint8_t sync_flags = 0; if (s->src_node) sync_flags |= PFSYNC_FLAG_SRCNODE; if (s->nat_src_node) sync_flags |= PFSYNC_FLAG_NATSRCNODE; nlattr_add_u8(nw, PF_ST_SYNC_FLAGS, sync_flags); nlattr_add_u64(nw, PF_ST_ID, s->id); nlattr_add_u32(nw, PF_ST_CREATORID, htonl(s->creatorid)); nlattr_add_u32(nw, PF_ST_RULE, s->rule.ptr ? s->rule.ptr->nr : -1); nlattr_add_u32(nw, PF_ST_ANCHOR, s->anchor.ptr ? s->anchor.ptr->nr : -1); nlattr_add_u32(nw, PF_ST_NAT_RULE, s->nat_rule.ptr ? s->nat_rule.ptr->nr : -1); nlattr_add_u64(nw, PF_ST_PACKETS0, s->packets[0]); nlattr_add_u64(nw, PF_ST_PACKETS1, s->packets[1]); nlattr_add_u64(nw, PF_ST_BYTES0, s->bytes[0]); nlattr_add_u64(nw, PF_ST_BYTES1, s->bytes[1]); if (!dump_state_peer(nw, PF_ST_PEER_SRC, &s->src)) goto enomem; if (!dump_state_peer(nw, PF_ST_PEER_DST, &s->dst)) goto enomem; if (nlmsg_end(nw)) return (0); enomem: error = ENOMEM; nlmsg_abort(nw); return (error); } static int handle_dumpstates(struct nlpcb *nlp, struct nl_parsed_state *attrs, struct nlmsghdr *hdr, struct nl_pstate *npt) { int error = 0; hdr->nlmsg_flags |= NLM_F_MULTI; for (int i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; struct pf_kstate *s; if (LIST_EMPTY(&ih->states)) continue; PF_HASHROW_LOCK(ih); LIST_FOREACH(s, &ih->states, entry) { if (s->timeout != PFTM_UNLINKED) { error = dump_state(nlp, hdr, s, npt); if (error != 0) break; } } PF_HASHROW_UNLOCK(ih); } if (!nlmsg_end_dump(npt->nw, error, hdr)) { NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); return (ENOMEM); } return (error); } static int handle_getstate(struct nlpcb *nlp, struct nl_parsed_state *attrs, struct nlmsghdr *hdr, struct nl_pstate *npt) { struct pf_kstate *s = pf_find_state_byid(attrs->id, attrs->creatorid); if (s == NULL) return (ENOENT); return (dump_state(nlp, hdr, s, npt)); } +static int +dump_creatorid(struct nlpcb *nlp, const struct nlmsghdr *hdr, uint32_t creator, + struct nl_pstate *npt) +{ + struct nl_writer *nw = npt->nw; + + if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) + goto enomem; + + struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + ghdr_new->cmd = PFNL_CMD_GETCREATORS; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + nlattr_add_u32(nw, PF_ST_CREATORID, htonl(creator)); + + if (nlmsg_end(nw)) + return (0); + +enomem: + nlmsg_abort(nw); + return (ENOMEM); +} + static int pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt) { int error; struct nl_parsed_state attrs = {}; error = nl_parse_nlmsg(hdr, &state_parser, npt, &attrs); if (error != 0) return (error); if (attrs.id != 0) error = handle_getstate(npt->nlp, &attrs, hdr, npt); else error = handle_dumpstates(npt->nlp, &attrs, hdr, npt); return (error); } +static int +pf_handle_getcreators(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + uint32_t creators[16]; + int error = 0; + + bzero(creators, sizeof(creators)); + + for (int i = 0; i < pf_hashmask; i++) { + struct pf_idhash *ih = &V_pf_idhash[i]; + struct pf_kstate *s; + + if (LIST_EMPTY(&ih->states)) + continue; + + PF_HASHROW_LOCK(ih); + LIST_FOREACH(s, &ih->states, entry) { + int j; + if (s->timeout == PFTM_UNLINKED) + continue; + + for (j = 0; j < nitems(creators); j++) { + if (creators[j] == s->creatorid) + break; + if (creators[j] == 0) { + creators[j] = s->creatorid; + break; + } + } + if (j == nitems(creators)) + printf("Warning: too many creators!\n"); + } + PF_HASHROW_UNLOCK(ih); + } + + hdr->nlmsg_flags |= NLM_F_MULTI; + for (int i = 0; i < nitems(creators); i++) { + if (creators[i] == 0) + break; + error = dump_creatorid(npt->nlp, hdr, creators[i], npt); + } + + if (!nlmsg_end_dump(npt->nw, error, hdr)) { + NL_LOG(LOG_DEBUG, "Unable to finalize the dump"); + return (ENOMEM); + } + + return (error); +} + static const struct nlhdr_parser *all_parsers[] = { &state_parser }; static int family_id; static const struct genl_cmd pf_cmds[] = { { .cmd_num = PFNL_CMD_GETSTATES, .cmd_name = "GETSTATES", .cmd_cb = pf_handle_getstates, .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, }, + { + .cmd_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, + }, }; void pf_nl_register(void) { NL_VERIFY_PARSERS(all_parsers); family_id = genl_register_family(PFNL_FAMILY_NAME, 0, 2, PFNL_CMD_MAX); genl_register_cmds(PFNL_FAMILY_NAME, pf_cmds, NL_ARRAY_LEN(pf_cmds)); } void pf_nl_unregister(void) { genl_unregister_family(PFNL_FAMILY_NAME); } diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h index 5ef757eead21..98525641b43d 100644 --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -1,105 +1,106 @@ /*- * 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_MAX, }; #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1) enum pfstate_key_type_t { PF_STK_UNSPEC, PF_STK_ADDR0 = 1, /* ip */ PF_STK_ADDR1 = 2, /* ip */ PF_STK_PORT0 = 3, /* u16 */ PF_STK_PORT1 = 4, /* u16 */ }; enum pfstate_peer_type_t { PF_STP_UNSPEC, PF_STP_PFSS_FLAGS = 1, /* u16 */ PF_STP_PFSS_TTL = 2, /* u8 */ PF_STP_SCRUB_FLAG = 3, /* u8 */ PF_STP_PFSS_TS_MOD = 4, /* u32 */ PF_STP_SEQLO = 5, /* u32 */ PF_STP_SEQHI = 6, /* u32 */ PF_STP_SEQDIFF = 7, /* u32 */ PF_STP_MAX_WIN = 8, /* u16 */ PF_STP_MSS = 9, /* u16 */ PF_STP_STATE = 10, /* u8 */ PF_STP_WSCALE = 11, /* u8 */ }; enum pfstate_type_t { PF_ST_UNSPEC, PF_ST_ID = 1, /* u32, state id */ PF_ST_CREATORID = 2, /* u32, */ PF_ST_IFNAME = 3, /* string */ PF_ST_ORIG_IFNAME = 4, /* string */ PF_ST_KEY_WIRE = 5, /* nested, pfstate_key_type_t */ PF_ST_KEY_STACK = 6, /* nested, pfstate_key_type_t */ PF_ST_PEER_SRC = 7, /* nested, pfstate_peer_type_t*/ PF_ST_PEER_DST = 8, /* nested, pfstate_peer_type_t */ PF_ST_RT_ADDR = 9, /* ip */ PF_ST_RULE = 10, /* u32 */ PF_ST_ANCHOR = 11, /* u32 */ PF_ST_NAT_RULE = 12, /* u32 */ PF_ST_CREATION = 13, /* u32 */ PF_ST_EXPIRE = 14, /* u32 */ PF_ST_PACKETS0 = 15, /* u64 */ PF_ST_PACKETS1 = 16, /* u64 */ PF_ST_BYTES0 = 17, /* u64 */ PF_ST_BYTES1 = 18, /* u64 */ PF_ST_AF = 19, /* u8 */ PF_ST_PROTO = 21, /* u8 */ PF_ST_DIRECTION = 22, /* u8 */ PF_ST_LOG = 23, /* u8 */ PF_ST_TIMEOUT = 24, /* u8 */ PF_ST_STATE_FLAGS = 25, /* u8 */ PF_ST_SYNC_FLAGS = 26, /* u8 */ PF_ST_UPDATES = 27, /* u8 */ PF_ST_VERSION = 28, /* u64 */ }; #ifdef _KERNEL void pf_nl_register(void); void pf_nl_unregister(void); #endif #endif diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh index 5d30f5b44888..87dfcf748d3c 100644 --- a/tests/sys/netpfil/pf/pfsync.sh +++ b/tests/sys/netpfil/pf/pfsync.sh @@ -1,937 +1,945 @@ # # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2018 Orange Business Services # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. . $(atf_get_srcdir)/utils.subr common_dir=$(atf_get_srcdir)/../common atf_test_case "basic" "cleanup" basic_head() { atf_set descr 'Basic pfsync test' atf_set require.user root } basic_body() { common_body } common_body() { defer=$1 pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up jexec one ifconfig ${epair_one}a 198.51.100.1/24 up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ $defer \ up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ maxupd 1 \ $defer \ up # Enable pf! jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass out keep state" jexec two pfctl -e pft_set_rules two \ "set skip on ${epair_sync}b" \ "pass out keep state" + hostid_one=$(jexec one pfctl -si -v | awk '/Hostid:/ { gsub(/0x/, "", $2); printf($2); }') + ifconfig ${epair_one}b 198.51.100.254/24 up ping -c 1 -S 198.51.100.254 198.51.100.1 # Give pfsync time to do its thing sleep 2 if ! jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.254 ; then atf_fail "state not found on synced host" fi + + if ! jexec two pfctl -sc | grep ""${hostid_one}""; + then + jexec two pfctl -sc + atf_fail "HostID for host one not found on two" + fi } basic_cleanup() { pfsynct_cleanup } atf_test_case "basic_defer" "cleanup" basic_defer_head() { atf_set descr 'Basic defer mode pfsync test' atf_set require.user root } basic_defer_body() { common_body defer } basic_defer_cleanup() { pfsynct_cleanup } atf_test_case "defer" "cleanup" defer_head() { atf_set descr 'Defer mode pfsync test' atf_set require.user root atf_set require.progs scapy } defer_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_in=$(vnet_mkepair) epair_out=$(vnet_mkepair) vnet_mkjail alcatraz ${epair_sync}a ${epair_in}a ${epair_out}a jexec alcatraz ifconfig ${epair_sync}a 192.0.2.1/24 up jexec alcatraz ifconfig ${epair_out}a 198.51.100.1/24 up jexec alcatraz ifconfig ${epair_in}a 203.0.113.1/24 up jexec alcatraz arp -s 203.0.113.2 00:01:02:03:04:05 jexec alcatraz sysctl net.inet.ip.forwarding=1 # Set a long defer delay jexec alcatraz sysctl net.pfsync.defer_delay=2500 jexec alcatraz ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ defer \ up ifconfig ${epair_sync}b 192.0.2.2/24 up ifconfig ${epair_out}b 198.51.100.2/24 up ifconfig ${epair_in}b up route add -net 203.0.113.0/24 198.51.100.1 # Enable pf jexec alcatraz sysctl net.pf.filter_local=0 jexec alcatraz pfctl -e pft_set_rules alcatraz \ "set skip on ${epair_sync}a" \ "pass keep state" atf_check -s exit:0 env PYTHONPATH=${common_dir} \ $(atf_get_srcdir)/pfsync_defer.py \ --syncdev ${epair_sync}b \ --indev ${epair_in}b \ --outdev ${epair_out}b # Now disable defer mode and expect failure. jexec alcatraz ifconfig pfsync0 -defer # Flush state pft_set_rules alcatraz \ "set skip on ${epair_sync}a" \ "pass keep state" atf_check -s exit:3 env PYTHONPATH=${common_dir} \ $(atf_get_srcdir)/pfsync_defer.py \ --syncdev ${epair_sync}b \ --indev ${epair_in}b \ --outdev ${epair_out}b } defer_cleanup() { pfsynct_cleanup } atf_test_case "bulk" "cleanup" bulk_head() { atf_set descr 'Test bulk updates' atf_set require.user root } bulk_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up jexec one ifconfig ${epair_one}a 198.51.100.1/24 up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1\ up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up # Enable pf jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass keep state" jexec two pfctl -e pft_set_rules two \ "set skip on ${epair_sync}b" \ "pass keep state" ifconfig ${epair_one}b 198.51.100.254/24 up # Create state prior to setting up pfsync ping -c 1 -S 198.51.100.254 198.51.100.1 # Wait before setting up pfsync on two, so we don't accidentally catch # the update anyway. sleep 1 # Now set up pfsync in jail two jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ up # Give pfsync time to do its thing sleep 2 jexec two pfctl -s states if ! jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.2 ; then atf_fail "state not found on synced host" fi } bulk_cleanup() { pfsynct_cleanup } atf_test_case "pbr" "cleanup" pbr_head() { atf_set descr 'route_to and reply_to directives test' atf_set require.user root atf_set timeout '600' } pbr_body() { pbr_common_body } pbr_cleanup() { pbr_common_cleanup } atf_test_case "pfsync_pbr" "cleanup" pfsync_pbr_head() { atf_set descr 'route_to and reply_to directives pfsync test' atf_set require.user root atf_set timeout '600' } pfsync_pbr_body() { pbr_common_body backup_promotion } pfsync_pbr_cleanup() { pbr_common_cleanup } pbr_common_body() { # + builds bellow topology and initiate a single ping session # from client to server. # + gw* forward traffic through pbr not fib lookups. # + if backup_promotion arg is given, a carp failover event occurs # during the ping session on both gateways. # ┌──────┐ # │client│ # └───┬──┘ # │ # ┌───┴───┐ # │bridge0│ # └┬─────┬┘ # │ │ # ┌────────────────┴─┐ ┌─┴────────────────┐ # │gw_route_to_master├─┤gw_route_to_backup│ # └────────────────┬─┘ └─┬────────────────┘ # │ │ # ┌┴─────┴┐ # │bridge1│ # └┬─────┬┘ # │ │ # ┌────────────────┴─┐ ┌─┴────────────────┐ # │gw_reply_to_master├─┤gw_reply_to_backup│ # └────────────────┬─┘ └─┬────────────────┘ # │ │ # ┌┴─────┴┐ # │bridge2│ # └───┬───┘ # │ # ┌───┴──┐ # │server│ # └──────┘ if ! kldstat -q -m carp then atf_skip "This test requires carp" fi pfsynct_init bridge0=$(vnet_mkbridge) bridge1=$(vnet_mkbridge) bridge2=$(vnet_mkbridge) epair_sync_gw_route_to=$(vnet_mkepair) epair_sync_gw_reply_to=$(vnet_mkepair) epair_client_bridge0=$(vnet_mkepair) epair_gw_route_to_master_bridge0=$(vnet_mkepair) epair_gw_route_to_backup_bridge0=$(vnet_mkepair) epair_gw_route_to_master_bridge1=$(vnet_mkepair) epair_gw_route_to_backup_bridge1=$(vnet_mkepair) epair_gw_reply_to_master_bridge1=$(vnet_mkepair) epair_gw_reply_to_backup_bridge1=$(vnet_mkepair) epair_gw_reply_to_master_bridge2=$(vnet_mkepair) epair_gw_reply_to_backup_bridge2=$(vnet_mkepair) epair_server_bridge2=$(vnet_mkepair) ifconfig ${bridge0} up ifconfig ${epair_client_bridge0}b up ifconfig ${epair_gw_route_to_master_bridge0}b up ifconfig ${epair_gw_route_to_backup_bridge0}b up ifconfig ${bridge0} \ addm ${epair_client_bridge0}b \ addm ${epair_gw_route_to_master_bridge0}b \ addm ${epair_gw_route_to_backup_bridge0}b ifconfig ${bridge1} up ifconfig ${epair_gw_route_to_master_bridge1}b up ifconfig ${epair_gw_route_to_backup_bridge1}b up ifconfig ${epair_gw_reply_to_master_bridge1}b up ifconfig ${epair_gw_reply_to_backup_bridge1}b up ifconfig ${bridge1} \ addm ${epair_gw_route_to_master_bridge1}b \ addm ${epair_gw_route_to_backup_bridge1}b \ addm ${epair_gw_reply_to_master_bridge1}b \ addm ${epair_gw_reply_to_backup_bridge1}b ifconfig ${bridge2} up ifconfig ${epair_gw_reply_to_master_bridge2}b up ifconfig ${epair_gw_reply_to_backup_bridge2}b up ifconfig ${epair_server_bridge2}b up ifconfig ${bridge2} \ addm ${epair_gw_reply_to_master_bridge2}b \ addm ${epair_gw_reply_to_backup_bridge2}b \ addm ${epair_server_bridge2}b vnet_mkjail client ${epair_client_bridge0}a jexec client hostname client vnet_mkjail gw_route_to_master \ ${epair_gw_route_to_master_bridge0}a \ ${epair_gw_route_to_master_bridge1}a \ ${epair_sync_gw_route_to}a jexec gw_route_to_master hostname gw_route_to_master vnet_mkjail gw_route_to_backup \ ${epair_gw_route_to_backup_bridge0}a \ ${epair_gw_route_to_backup_bridge1}a \ ${epair_sync_gw_route_to}b jexec gw_route_to_backup hostname gw_route_to_backup vnet_mkjail gw_reply_to_master \ ${epair_gw_reply_to_master_bridge1}a \ ${epair_gw_reply_to_master_bridge2}a \ ${epair_sync_gw_reply_to}a jexec gw_reply_to_master hostname gw_reply_to_master vnet_mkjail gw_reply_to_backup \ ${epair_gw_reply_to_backup_bridge1}a \ ${epair_gw_reply_to_backup_bridge2}a \ ${epair_sync_gw_reply_to}b jexec gw_reply_to_backup hostname gw_reply_to_backup vnet_mkjail server ${epair_server_bridge2}a jexec server hostname server jexec client ifconfig ${epair_client_bridge0}a inet 198.18.0.1/24 up jexec client route add 198.18.2.0/24 198.18.0.10 jexec gw_route_to_master ifconfig ${epair_sync_gw_route_to}a \ inet 198.19.10.1/24 up jexec gw_route_to_master ifconfig ${epair_gw_route_to_master_bridge0}a \ inet 198.18.0.8/24 up jexec gw_route_to_master ifconfig ${epair_gw_route_to_master_bridge0}a \ alias 198.18.0.10/32 vhid 10 pass 3WjvVVw7 advskew 50 jexec gw_route_to_master ifconfig ${epair_gw_route_to_master_bridge1}a \ inet 198.18.1.8/24 up jexec gw_route_to_master ifconfig ${epair_gw_route_to_master_bridge1}a \ alias 198.18.1.10/32 vhid 11 pass 3WjvVVw7 advskew 50 jexec gw_route_to_master sysctl net.inet.ip.forwarding=1 jexec gw_route_to_master sysctl net.inet.carp.preempt=1 vnet_ifrename_jail gw_route_to_master ${epair_sync_gw_route_to}a if_pfsync vnet_ifrename_jail gw_route_to_master ${epair_gw_route_to_master_bridge0}a if_br0 vnet_ifrename_jail gw_route_to_master ${epair_gw_route_to_master_bridge1}a if_br1 jexec gw_route_to_master ifconfig pfsync0 \ syncpeer 198.19.10.2 \ syncdev if_pfsync \ maxupd 1 \ up pft_set_rules gw_route_to_master \ "keep_state = 'tag auth_packet keep state'" \ "set timeout { icmp.first 120, icmp.error 60 }" \ "block log all" \ "pass quick on if_pfsync proto pfsync keep state (no-sync)" \ "pass quick on { if_br0 if_br1 } proto carp keep state (no-sync)" \ "block drop in quick to 224.0.0.18/32" \ "pass out quick tagged auth_packet keep state" \ "pass in quick log on if_br0 route-to (if_br1 198.18.1.20) proto { icmp udp tcp } from 198.18.0.0/24 to 198.18.2.0/24 \$keep_state" jexec gw_route_to_master pfctl -e jexec gw_route_to_backup ifconfig ${epair_sync_gw_route_to}b \ inet 198.19.10.2/24 up jexec gw_route_to_backup ifconfig ${epair_gw_route_to_backup_bridge0}a \ inet 198.18.0.9/24 up jexec gw_route_to_backup ifconfig ${epair_gw_route_to_backup_bridge0}a \ alias 198.18.0.10/32 vhid 10 pass 3WjvVVw7 advskew 100 jexec gw_route_to_backup ifconfig ${epair_gw_route_to_backup_bridge1}a \ inet 198.18.1.9/24 up jexec gw_route_to_backup ifconfig ${epair_gw_route_to_backup_bridge1}a \ alias 198.18.1.10/32 vhid 11 pass 3WjvVVw7 advskew 100 jexec gw_route_to_backup sysctl net.inet.ip.forwarding=1 jexec gw_route_to_backup sysctl net.inet.carp.preempt=1 vnet_ifrename_jail gw_route_to_backup ${epair_sync_gw_route_to}b if_pfsync vnet_ifrename_jail gw_route_to_backup ${epair_gw_route_to_backup_bridge0}a if_br0 vnet_ifrename_jail gw_route_to_backup ${epair_gw_route_to_backup_bridge1}a if_br1 jexec gw_route_to_backup ifconfig pfsync0 \ syncpeer 198.19.10.1 \ syncdev if_pfsync \ up pft_set_rules gw_route_to_backup \ "keep_state = 'tag auth_packet keep state'" \ "set timeout { icmp.first 120, icmp.error 60 }" \ "block log all" \ "pass quick on if_pfsync proto pfsync keep state (no-sync)" \ "pass quick on { if_br0 if_br1 } proto carp keep state (no-sync)" \ "block drop in quick to 224.0.0.18/32" \ "pass out quick tagged auth_packet keep state" \ "pass in quick log on if_br0 route-to (if_br1 198.18.1.20) proto { icmp udp tcp } from 198.18.0.0/24 to 198.18.2.0/24 \$keep_state" jexec gw_route_to_backup pfctl -e jexec gw_reply_to_master ifconfig ${epair_sync_gw_reply_to}a \ inet 198.19.20.1/24 up jexec gw_reply_to_master ifconfig ${epair_gw_reply_to_master_bridge1}a \ inet 198.18.1.18/24 up jexec gw_reply_to_master ifconfig ${epair_gw_reply_to_master_bridge1}a \ alias 198.18.1.20/32 vhid 21 pass 3WjvVVw7 advskew 50 jexec gw_reply_to_master ifconfig ${epair_gw_reply_to_master_bridge2}a \ inet 198.18.2.18/24 up jexec gw_reply_to_master ifconfig ${epair_gw_reply_to_master_bridge2}a \ alias 198.18.2.20/32 vhid 22 pass 3WjvVVw7 advskew 50 jexec gw_reply_to_master sysctl net.inet.ip.forwarding=1 jexec gw_reply_to_master sysctl net.inet.carp.preempt=1 vnet_ifrename_jail gw_reply_to_master ${epair_sync_gw_reply_to}a if_pfsync vnet_ifrename_jail gw_reply_to_master ${epair_gw_reply_to_master_bridge1}a if_br1 vnet_ifrename_jail gw_reply_to_master ${epair_gw_reply_to_master_bridge2}a if_br2 jexec gw_reply_to_master ifconfig pfsync0 \ syncpeer 198.19.20.2 \ syncdev if_pfsync \ maxupd 1 \ up pft_set_rules gw_reply_to_master \ "set timeout { icmp.first 120, icmp.error 60 }" \ "block log all" \ "pass quick on if_pfsync proto pfsync keep state (no-sync)" \ "pass quick on { if_br1 if_br2 } proto carp keep state (no-sync)" \ "block drop in quick to 224.0.0.18/32" \ "pass out quick on if_br2 reply-to (if_br1 198.18.1.10) tagged auth_packet_reply_to keep state" \ "pass in quick log on if_br1 proto { icmp udp tcp } from 198.18.0.0/24 to 198.18.2.0/24 tag auth_packet_reply_to keep state" jexec gw_reply_to_master pfctl -e jexec gw_reply_to_backup ifconfig ${epair_sync_gw_reply_to}b \ inet 198.19.20.2/24 up jexec gw_reply_to_backup ifconfig ${epair_gw_reply_to_backup_bridge1}a \ inet 198.18.1.19/24 up jexec gw_reply_to_backup ifconfig ${epair_gw_reply_to_backup_bridge1}a \ alias 198.18.1.20/32 vhid 21 pass 3WjvVVw7 advskew 100 jexec gw_reply_to_backup ifconfig ${epair_gw_reply_to_backup_bridge2}a \ inet 198.18.2.19/24 up jexec gw_reply_to_backup ifconfig ${epair_gw_reply_to_backup_bridge2}a \ alias 198.18.2.20/32 vhid 22 pass 3WjvVVw7 advskew 100 jexec gw_reply_to_backup sysctl net.inet.ip.forwarding=1 jexec gw_reply_to_backup sysctl net.inet.carp.preempt=1 vnet_ifrename_jail gw_reply_to_backup ${epair_sync_gw_reply_to}b if_pfsync vnet_ifrename_jail gw_reply_to_backup ${epair_gw_reply_to_backup_bridge1}a if_br1 vnet_ifrename_jail gw_reply_to_backup ${epair_gw_reply_to_backup_bridge2}a if_br2 jexec gw_reply_to_backup ifconfig pfsync0 \ syncpeer 198.19.20.1 \ syncdev if_pfsync \ up pft_set_rules gw_reply_to_backup \ "set timeout { icmp.first 120, icmp.error 60 }" \ "block log all" \ "pass quick on if_pfsync proto pfsync keep state (no-sync)" \ "pass quick on { if_br1 if_br2 } proto carp keep state (no-sync)" \ "block drop in quick to 224.0.0.18/32" \ "pass out quick on if_br2 reply-to (if_br1 198.18.1.10) tagged auth_packet_reply_to keep state" \ "pass in quick log on if_br1 proto { icmp udp tcp } from 198.18.0.0/24 to 198.18.2.0/24 tag auth_packet_reply_to keep state" jexec gw_reply_to_backup pfctl -e jexec server ifconfig ${epair_server_bridge2}a inet 198.18.2.1/24 up jexec server route add 198.18.0.0/24 198.18.2.20 # Waiting for platform to settle while ! jexec gw_route_to_backup ifconfig | grep 'carp: BACKUP' do sleep 1 done while ! jexec gw_reply_to_backup ifconfig | grep 'carp: BACKUP' do sleep 1 done while ! jexec client ping -c 10 198.18.2.1 | grep ', 0.0% packet loss' do sleep 1 done # Checking cluster members pf.conf checksums match gw_route_to_master_checksum=$(jexec gw_route_to_master pfctl -si -v | grep 'Checksum:' | cut -d ' ' -f 2) gw_route_to_backup_checksum=$(jexec gw_route_to_backup pfctl -si -v | grep 'Checksum:' | cut -d ' ' -f 2) gw_reply_to_master_checksum=$(jexec gw_reply_to_master pfctl -si -v | grep 'Checksum:' | cut -d ' ' -f 2) gw_reply_to_backup_checksum=$(jexec gw_reply_to_backup pfctl -si -v | grep 'Checksum:' | cut -d ' ' -f 2) if [ "$gw_route_to_master_checksum" != "$gw_route_to_backup_checksum" ] then atf_fail "gw_route_to cluster members pf.conf do not match each others" fi if [ "$gw_reply_to_master_checksum" != "$gw_reply_to_backup_checksum" ] then atf_fail "gw_reply_to cluster members pf.conf do not match each others" fi # Creating state entries (jexec client ping -c 10 198.18.2.1 >ping.stdout) & if [ "$1" = "backup_promotion" ] then sleep 1 jexec gw_route_to_backup ifconfig if_br0 vhid 10 advskew 0 jexec gw_route_to_backup ifconfig if_br1 vhid 11 advskew 0 jexec gw_reply_to_backup ifconfig if_br1 vhid 21 advskew 0 jexec gw_reply_to_backup ifconfig if_br2 vhid 22 advskew 0 fi while ! grep -q -e 'packet loss' ping.stdout do sleep 1 done atf_check -s exit:0 -e ignore -o ignore grep ', 0.0% packet loss' ping.stdout } pbr_common_cleanup() { pft_cleanup } atf_test_case "ipsec" "cleanup" ipsec_head() { atf_set descr 'Transport pfsync over IPSec' atf_set require.user root } ipsec_body() { if ! sysctl -q kern.features.ipsec >/dev/null ; then atf_skip "This test requires ipsec" fi # Run the common test, to set up pfsync common_body # But we want unicast pfsync jexec one ifconfig pfsync0 syncpeer 192.0.2.2 jexec two ifconfig pfsync0 syncpeer 192.0.2.1 # Flush existing states jexec one pfctl -Fs jexec two pfctl -Fs # Now define an ipsec policy to run over the epair_sync interfaces echo "flush; spdflush; spdadd 192.0.2.1/32 192.0.2.2/32 any -P out ipsec esp/transport//require; spdadd 192.0.2.2/32 192.0.2.1/32 any -P in ipsec esp/transport//require; add 192.0.2.1 192.0.2.2 esp 0x1000 -E aes-gcm-16 \"12345678901234567890\"; add 192.0.2.2 192.0.2.1 esp 0x1001 -E aes-gcm-16 \"12345678901234567890\";" \ | jexec one setkey -c echo "flush; spdflush; spdadd 192.0.2.2/32 192.0.2.1/32 any -P out ipsec esp/transport//require; spdadd 192.0.2.1/32 192.0.2.2/32 any -P in ipsec esp/transport//require; add 192.0.2.1 192.0.2.2 esp 0x1000 -E aes-gcm-16 \"12345678901234567891\"; add 192.0.2.2 192.0.2.1 esp 0x1001 -E aes-gcm-16 \"12345678901234567891\";" \ | jexec two setkey -c # We've set incompatible keys, so pfsync will be broken. ping -c 1 -S 198.51.100.254 198.51.100.1 # Give pfsync time to do its thing sleep 2 if jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.2 ; then atf_fail "state synced although IPSec should have prevented it" fi # Flush existing states jexec one pfctl -Fs jexec two pfctl -Fs # Fix the IPSec key to match echo "flush; spdflush; spdadd 192.0.2.2/32 192.0.2.1/32 any -P out ipsec esp/transport//require; spdadd 192.0.2.1/32 192.0.2.2/32 any -P in ipsec esp/transport//require; add 192.0.2.1 192.0.2.2 esp 0x1000 -E aes-gcm-16 \"12345678901234567890\"; add 192.0.2.2 192.0.2.1 esp 0x1001 -E aes-gcm-16 \"12345678901234567890\";" \ | jexec two setkey -c ping -c 1 -S 198.51.100.254 198.51.100.1 # Give pfsync time to do its thing sleep 2 if ! jexec two pfctl -s states | grep icmp | grep 198.51.100.1 | \ grep 198.51.100.2 ; then atf_fail "state not found on synced host" fi } ipsec_cleanup() { pft_cleanup } atf_test_case "timeout" "cleanup" timeout_head() { atf_set descr 'Trigger pfsync_timeout()' atf_set require.user root } timeout_body() { pft_init vnet_mkjail one jexec one ifconfig lo0 127.0.0.1/8 up jexec one ifconfig lo0 inet6 ::1/128 up pft_set_rules one \ "pass all" jexec one pfctl -e jexec one ifconfig pfsync0 defer up jexec one ping -c 1 ::1 jexec one ping -c 1 127.0.0.1 # Give pfsync_timeout() time to fire (a callout on a 1 second delay) sleep 2 } timeout_cleanup() { pft_cleanup } atf_test_case "basic_ipv6_unicast" "cleanup" basic_ipv6_unicast_head() { atf_set descr 'Basic pfsync test (IPv6)' atf_set require.user root } basic_ipv6_unicast_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a inet6 fd2c::1/64 no_dad up jexec one ifconfig ${epair_one}a inet6 fd2b::1/64 no_dad up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ syncpeer fd2c::2 \ maxupd 1 \ up jexec two ifconfig ${epair_two}a inet6 fd2b::2/64 no_dad up jexec two ifconfig ${epair_sync}b inet6 fd2c::2/64 no_dad up jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ syncpeer fd2c::1 \ maxupd 1 \ up # Enable pf! jexec one pfctl -e pft_set_rules one \ "block on ${epair_sync}a inet" \ "pass out keep state" jexec two pfctl -e pft_set_rules two \ "block on ${epair_sync}b inet" \ "pass out keep state" ifconfig ${epair_one}b inet6 fd2b::f0/64 no_dad up ping6 -c 1 -S fd2b::f0 fd2b::1 # Give pfsync time to do its thing sleep 2 if ! jexec two pfctl -s states | grep icmp | grep fd2b::1 | \ grep fd2b::f0 ; then atf_fail "state not found on synced host" fi } basic_ipv6_unicast_cleanup() { pfsynct_cleanup } atf_test_case "basic_ipv6" "cleanup" basic_ipv6_head() { atf_set descr 'Basic pfsync test (IPv6)' atf_set require.user root } basic_ipv6_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a vnet_mkjail two ${epair_two}a ${epair_sync}b # pfsync interface jexec one ifconfig ${epair_sync}a inet6 fd2c::1/64 no_dad up jexec one ifconfig ${epair_one}a inet6 fd2b::1/64 no_dad up jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ syncpeer ff12::f0 \ maxupd 1 \ up jexec two ifconfig ${epair_two}a inet6 fd2b::2/64 no_dad up jexec two ifconfig ${epair_sync}b inet6 fd2c::2/64 no_dad up jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ syncpeer ff12::f0 \ maxupd 1 \ up # Enable pf! jexec one pfctl -e pft_set_rules one \ "block on ${epair_sync}a inet" \ "pass out keep state" jexec two pfctl -e pft_set_rules two \ "block on ${epair_sync}b inet" \ "pass out keep state" ifconfig ${epair_one}b inet6 fd2b::f0/64 no_dad up ping6 -c 1 -S fd2b::f0 fd2b::1 # Give pfsync time to do its thing sleep 2 if ! jexec two pfctl -s states | grep icmp | grep fd2b::1 | \ grep fd2b::f0 ; then atf_fail "state not found on synced host" fi } basic_ipv6_cleanup() { pfsynct_cleanup } atf_test_case "route_to" "cleanup" route_to_head() { atf_set descr 'Test route-to with default rule' atf_set require.user root atf_set require.progs scapy } route_to_body() { pfsynct_init epair_sync=$(vnet_mkepair) epair_one=$(vnet_mkepair) epair_two=$(vnet_mkepair) epair_out_one=$(vnet_mkepair) epair_out_two=$(vnet_mkepair) vnet_mkjail one ${epair_one}a ${epair_sync}a ${epair_out_one}a vnet_mkjail two ${epair_two}a ${epair_sync}b ${epair_out_two}a # pfsync interface jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up jexec one ifconfig ${epair_one}a 198.51.100.1/24 up jexec one ifconfig ${epair_out_one}a 203.0.113.1/24 up jexec one ifconfig ${epair_out_one}a name outif jexec one sysctl net.inet.ip.forwarding=1 jexec one arp -s 203.0.113.254 00:01:02:03:04:05 jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up #jexec two ifconfig ${epair_out_two}a name outif jexec two sysctl net.inet.ip.forwarding=1 jexec two arp -s 203.0.113.254 00:01:02:03:04:05 jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ maxupd 1 \ up # Enable pf! jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass out route-to (outif 203.0.113.254)" jexec two pfctl -e # Make sure we have different rulesets so the synced state is associated with # V_pf_default_rule pft_set_rules two \ "set skip on ${epair_sync}b" \ "pass out route-to (outif 203.0.113.254)" \ "pass out proto tcp" ifconfig ${epair_one}b 198.51.100.254/24 up ifconfig ${epair_two}b 198.51.100.253/24 up route add -net 203.0.113.0/24 198.51.100.1 ifconfig ${epair_two}b up ifconfig ${epair_out_one}b up ifconfig ${epair_out_two}b up atf_check -s exit:0 env PYTHONPATH=${common_dir} \ ${common_dir}/pft_ping.py \ --sendif ${epair_one}b \ --fromaddr 198.51.100.254 \ --to 203.0.113.254 \ --recvif ${epair_out_one}b # Allow time for sync ifconfig ${epair_one}b inet 198.51.100.254 -alias route del -net 203.0.113.0/24 198.51.100.1 route add -net 203.0.113.0/24 198.51.100.2 sleep 2 # Now try to trigger the state on the other pfsync member env PYTHONPATH=${common_dir} \ ${common_dir}/pft_ping.py \ --sendif ${epair_two}b \ --fromaddr 198.51.100.254 \ --to 203.0.113.254 \ --recvif ${epair_out_two}b true } route_to_cleanup() { pfsynct_cleanup } atf_init_test_cases() { atf_add_test_case "basic" atf_add_test_case "basic_defer" atf_add_test_case "defer" atf_add_test_case "bulk" atf_add_test_case "pbr" atf_add_test_case "pfsync_pbr" atf_add_test_case "ipsec" atf_add_test_case "timeout" atf_add_test_case "basic_ipv6_unicast" atf_add_test_case "basic_ipv6" atf_add_test_case "route_to" }