diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index 51276d8bb343..571fabae4359 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1,1713 +1,1713 @@ /*- * 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; } int pfctl_startstop(int start) { struct snl_state ss = {}; struct snl_errmsg_data e = {}; struct snl_writer nw; struct nlmsghdr *hdr; uint32_t seq_id; int family_id; snl_init(&ss, NETLINK_GENERIC); family_id = snl_get_genl_family(&ss, PFNL_FAMILY_NAME); snl_init_writer(&ss, &nw); hdr = snl_create_genl_msg_request(&nw, family_id, start ? PFNL_CMD_START : PFNL_CMD_STOP); - snl_finalize_msg(&nw); + hdr = snl_finalize_msg(&nw); seq_id = hdr->nlmsg_seq; snl_send_message(&ss, hdr); while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { } return (e.error); } static void _pfctl_get_status_counters(const nvlist_t *nvl, struct pfctl_status_counters *counters) { const uint64_t *ids, *counts; const char *const *names; size_t id_len, counter_len, names_len; ids = nvlist_get_number_array(nvl, "ids", &id_len); counts = nvlist_get_number_array(nvl, "counters", &counter_len); names = nvlist_get_string_array(nvl, "names", &names_len); assert(id_len == counter_len); assert(counter_len == names_len); TAILQ_INIT(counters); for (size_t i = 0; i < id_len; i++) { struct pfctl_status_counter *c; c = malloc(sizeof(*c)); 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); + hdr = 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, &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); + hdr = 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/sbin/ifconfig/af_inet.c b/sbin/ifconfig/af_inet.c index d9499d64ed13..5e3084165b33 100644 --- a/sbin/ifconfig/af_inet.c +++ b/sbin/ifconfig/af_inet.c @@ -1,577 +1,577 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 "ifconfig.h" #include "ifconfig_netlink.h" #ifdef WITHOUT_NETLINK static struct in_aliasreq in_addreq; static struct ifreq in_ridreq; #else struct in_px { struct in_addr addr; int plen; bool addrset; bool maskset; }; struct in_pdata { struct in_px addr; struct in_px dst_addr; struct in_px brd_addr; uint32_t flags; uint32_t vhid; }; static struct in_pdata in_add, in_del; #endif static char addr_buf[NI_MAXHOST]; /*for getnameinfo()*/ extern char *f_inet, *f_addr; static void print_addr(struct sockaddr_in *sin) { int error, n_flags; if (f_addr != NULL && strcmp(f_addr, "fqdn") == 0) n_flags = 0; else if (f_addr != NULL && strcmp(f_addr, "host") == 0) n_flags = NI_NOFQDN; else n_flags = NI_NUMERICHOST; error = getnameinfo((struct sockaddr *)sin, sin->sin_len, addr_buf, sizeof(addr_buf), NULL, 0, n_flags); if (error) inet_ntop(AF_INET, &sin->sin_addr, addr_buf, sizeof(addr_buf)); printf("\tinet %s", addr_buf); } #ifdef WITHOUT_NETLINK static void in_status(if_ctx *ctx __unused, const struct ifaddrs *ifa) { struct sockaddr_in *sin, null_sin = {}; sin = satosin(ifa->ifa_addr); if (sin == NULL) return; print_addr(sin); if (ifa->ifa_flags & IFF_POINTOPOINT) { sin = satosin(ifa->ifa_dstaddr); if (sin == NULL) sin = &null_sin; printf(" --> %s", inet_ntoa(sin->sin_addr)); } sin = satosin(ifa->ifa_netmask); if (sin == NULL) sin = &null_sin; if (f_inet != NULL && strcmp(f_inet, "cidr") == 0) { int cidr = 32; unsigned long smask; smask = ntohl(sin->sin_addr.s_addr); while ((smask & 1) == 0) { smask = smask >> 1; cidr--; if (cidr == 0) break; } printf("/%d", cidr); } else if (f_inet != NULL && strcmp(f_inet, "dotted") == 0) printf(" netmask %s", inet_ntoa(sin->sin_addr)); else printf(" netmask 0x%lx", (unsigned long)ntohl(sin->sin_addr.s_addr)); if (ifa->ifa_flags & IFF_BROADCAST) { sin = satosin(ifa->ifa_broadaddr); if (sin != NULL && sin->sin_addr.s_addr != 0) printf(" broadcast %s", inet_ntoa(sin->sin_addr)); } print_vhid(ifa); putchar('\n'); } #else static struct in_addr get_mask(int plen) { struct in_addr a; a.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0); return (a); } static void in_status_nl(if_ctx *ctx __unused, if_link_t *link, if_addr_t *ifa) { struct sockaddr_in *sin = satosin(ifa->ifa_local); int plen = ifa->ifa_prefixlen; print_addr(sin); if (link->ifi_flags & IFF_POINTOPOINT) { struct sockaddr_in *dst = satosin(ifa->ifa_address); printf(" --> %s", inet_ntoa(dst->sin_addr)); } if (f_inet != NULL && strcmp(f_inet, "cidr") == 0) { printf("/%d", plen); } else if (f_inet != NULL && strcmp(f_inet, "dotted") == 0) printf(" netmask %s", inet_ntoa(get_mask(plen))); else printf(" netmask 0x%lx", (unsigned long)ntohl(get_mask(plen).s_addr)); if ((link->ifi_flags & IFF_BROADCAST) && plen != 0) { struct sockaddr_in *brd = satosin(ifa->ifa_broadcast); if (brd != NULL) printf(" broadcast %s", inet_ntoa(brd->sin_addr)); } if (ifa->ifaf_vhid != 0) printf(" vhid %d", ifa->ifaf_vhid); putchar('\n'); } #endif #ifdef WITHOUT_NETLINK #define SIN(x) ((struct sockaddr_in *) &(x)) static struct sockaddr_in *sintab[] = { SIN(in_ridreq.ifr_addr), SIN(in_addreq.ifra_addr), SIN(in_addreq.ifra_mask), SIN(in_addreq.ifra_broadaddr) }; static void in_copyaddr(if_ctx *ctx __unused, int to, int from) { memcpy(sintab[to], sintab[from], sizeof(struct sockaddr_in)); } static void in_getaddr(const char *s, int which) { struct sockaddr_in *sin = sintab[which]; struct hostent *hp; struct netent *np; sin->sin_len = sizeof(*sin); sin->sin_family = AF_INET; if (which == ADDR) { char *p = NULL; if((p = strrchr(s, '/')) != NULL) { const char *errstr; /* address is `name/masklen' */ int masklen = 0; struct sockaddr_in *min = sintab[MASK]; *p = '\0'; if (!isdigit(*(p + 1))) errstr = "invalid"; else masklen = (int)strtonum(p + 1, 0, 32, &errstr); if (errstr != NULL) { *p = '/'; errx(1, "%s: bad value (width %s)", s, errstr); } min->sin_family = AF_INET; min->sin_len = sizeof(*min); min->sin_addr.s_addr = htonl(~((1LL << (32 - masklen)) - 1) & 0xffffffff); } } if (inet_aton(s, &sin->sin_addr)) return; if ((hp = gethostbyname(s)) != NULL) bcopy(hp->h_addr, (char *)&sin->sin_addr, MIN((size_t)hp->h_length, sizeof(sin->sin_addr))); else if ((np = getnetbyname(s)) != NULL) sin->sin_addr = inet_makeaddr(np->n_net, INADDR_ANY); else errx(1, "%s: bad value", s); } #else static struct in_px *sintab_nl[] = { &in_del.addr, /* RIDADDR */ &in_add.addr, /* ADDR */ NULL, /* MASK */ &in_add.dst_addr, /* DSTADDR*/ &in_add.brd_addr, /* BRDADDR*/ }; static void in_copyaddr(if_ctx *ctx __unused, int to, int from) { sintab_nl[to]->addr = sintab_nl[from]->addr; sintab_nl[to]->addrset = sintab_nl[from]->addrset; } static void in_getip(const char *addr_str, struct in_addr *ip) { struct hostent *hp; struct netent *np; if (inet_aton(addr_str, ip)) return; if ((hp = gethostbyname(addr_str)) != NULL) bcopy(hp->h_addr, (char *)ip, MIN((size_t)hp->h_length, sizeof(ip))); else if ((np = getnetbyname(addr_str)) != NULL) *ip = inet_makeaddr(np->n_net, INADDR_ANY); else errx(1, "%s: bad value", addr_str); } static void in_getaddr(const char *s, int which) { struct in_px *px = sintab_nl[which]; if (which == MASK) { struct in_px *px_addr = sintab_nl[ADDR]; struct in_addr mask = {}; in_getip(s, &mask); px_addr->plen = __bitcount32(mask.s_addr); px_addr->maskset = true; return; } if (which == ADDR) { char *p = NULL; if((p = strrchr(s, '/')) != NULL) { const char *errstr; /* address is `name/masklen' */ int masklen; *p = '\0'; if (!isdigit(*(p + 1))) errstr = "invalid"; else masklen = (int)strtonum(p + 1, 0, 32, &errstr); if (errstr != NULL) { *p = '/'; errx(1, "%s: bad value (width %s)", s, errstr); } px->plen = masklen; px->maskset = true; } } in_getip(s, &px->addr); px->addrset = true; } /* * Deletes the first found IPv4 interface address for the interface. * * This function provides SIOCDIFADDR semantics missing in Netlink. * When no valid IPv4 address is specified (sin_family or sin_len is wrong) to * the SIOCDIFADDR call, it deletes the first found IPv4 address on the interface. * 'ifconfig IFNAME inet addr/prefix' relies on that behavior, as it * executes empty SIOCDIFADDR before adding a new address. */ static int in_delete_first_nl(if_ctx *ctx) { struct nlmsghdr *hdr; struct ifaddrmsg *ifahdr; uint32_t nlmsg_seq; struct in_addr addr; struct snl_writer nw = {}; struct snl_errmsg_data e = {}; struct snl_state *ss = ctx->io_ss; bool found = false; uint32_t ifindex = if_nametoindex_nl(ss, ctx->ifname); if (ifindex == 0) { /* No interface with the desired name, nothing to delete */ return (EADDRNOTAVAIL); } snl_init_writer(ss, &nw); hdr = snl_create_msg_request(&nw, NL_RTM_GETADDR); hdr->nlmsg_flags |= NLM_F_DUMP; ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); ifahdr->ifa_family = AF_INET; ifahdr->ifa_index = ifindex; - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (EINVAL); nlmsg_seq = hdr->nlmsg_seq; while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) { struct snl_parsed_addr attrs = {}; if (snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &attrs)) { addr = satosin(attrs.ifa_local)->sin_addr; ifindex = attrs.ifa_index; found = true; break; } else return (EINVAL); } if (e.error != 0) { if (e.error_str != NULL) warnx("%s(): %s", __func__, e.error_str); return (e.error); } if (!found) return (0); /* Try to delete the found address */ snl_init_writer(ss, &nw); hdr = snl_create_msg_request(&nw, NL_RTM_DELADDR); ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); ifahdr->ifa_family = AF_INET; ifahdr->ifa_index = ifindex; snl_add_msg_attr_ip4(&nw, IFA_LOCAL, &addr); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (EINVAL); memset(&e, 0, sizeof(e)); snl_read_reply_code(ss, hdr->nlmsg_seq, &e); if (e.error_str != NULL) warnx("%s(): %s", __func__, e.error_str); return (e.error); } static int in_exec_nl(if_ctx *ctx, unsigned long action, void *data) { struct in_pdata *pdata = (struct in_pdata *)data; struct snl_writer nw = {}; if (action == NL_RTM_DELADDR && !pdata->addr.addrset) return (in_delete_first_nl(ctx)); snl_init_writer(ctx->io_ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, action); struct ifaddrmsg *ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); ifahdr->ifa_family = AF_INET; ifahdr->ifa_prefixlen = pdata->addr.plen; ifahdr->ifa_index = if_nametoindex_nl(ctx->io_ss, ctx->ifname); snl_add_msg_attr_ip4(&nw, IFA_LOCAL, &pdata->addr.addr); if (action == NL_RTM_NEWADDR && pdata->dst_addr.addrset) snl_add_msg_attr_ip4(&nw, IFA_ADDRESS, &pdata->dst_addr.addr); if (action == NL_RTM_NEWADDR && pdata->brd_addr.addrset) snl_add_msg_attr_ip4(&nw, IFA_BROADCAST, &pdata->brd_addr.addr); int off = snl_add_msg_attr_nested(&nw, IFA_FREEBSD); snl_add_msg_attr_u32(&nw, IFAF_FLAGS, pdata->flags); if (pdata->vhid != 0) snl_add_msg_attr_u32(&nw, IFAF_VHID, pdata->vhid); snl_end_attr_nested(&nw, off); - if (!snl_finalize_msg(&nw) || !snl_send_message(ctx->io_ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ctx->io_ss, hdr)) return (0); struct snl_errmsg_data e = {}; snl_read_reply_code(ctx->io_ss, hdr->nlmsg_seq, &e); if (e.error_str != NULL) warnx("%s(): %s", __func__, e.error_str); return (e.error); } static void in_setdefaultmask_nl(void) { struct in_px *px = sintab_nl[ADDR]; in_addr_t i = ntohl(px->addr.s_addr); /* * If netmask isn't supplied, use historical default. * This is deprecated for interfaces other than loopback * or point-to-point; warn in other cases. In the future * we should return an error rather than warning. */ if (IN_CLASSA(i)) px->plen = IN_CLASSA_NSHIFT; else if (IN_CLASSB(i)) px->plen = IN_CLASSB_NSHIFT; else px->plen = IN_CLASSC_NSHIFT; px->maskset = true; } #endif static void warn_nomask(int ifflags) { if ((ifflags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) { warnx("WARNING: setting interface address without mask " "is deprecated,\ndefault mask may not be correct."); } } static void in_postproc(if_ctx *ctx __unused, int newaddr, int ifflags) { #ifdef WITHOUT_NETLINK if (sintab[ADDR]->sin_len != 0 && sintab[MASK]->sin_len == 0 && newaddr) { warn_nomask(ifflags); } #else if (sintab_nl[ADDR]->addrset && !sintab_nl[ADDR]->maskset && newaddr) { warn_nomask(ifflags); in_setdefaultmask_nl(); } #endif } static void in_status_tunnel(if_ctx *ctx) { char src[NI_MAXHOST]; char dst[NI_MAXHOST]; struct ifreq ifr; const struct sockaddr *sa = (const struct sockaddr *) &ifr.ifr_addr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ctx->ifname, IFNAMSIZ); if (ioctl_ctx(ctx, SIOCGIFPSRCADDR, (caddr_t)&ifr) < 0) return; if (sa->sa_family != AF_INET) return; if (getnameinfo(sa, sa->sa_len, src, sizeof(src), 0, 0, NI_NUMERICHOST) != 0) src[0] = '\0'; if (ioctl_ctx(ctx, SIOCGIFPDSTADDR, (caddr_t)&ifr) < 0) return; if (sa->sa_family != AF_INET) return; if (getnameinfo(sa, sa->sa_len, dst, sizeof(dst), 0, 0, NI_NUMERICHOST) != 0) dst[0] = '\0'; printf("\ttunnel inet %s --> %s\n", src, dst); } static void in_set_tunnel(if_ctx *ctx, struct addrinfo *srcres, struct addrinfo *dstres) { struct in_aliasreq addreq; memset(&addreq, 0, sizeof(addreq)); strlcpy(addreq.ifra_name, ctx->ifname, IFNAMSIZ); memcpy(&addreq.ifra_addr, srcres->ai_addr, srcres->ai_addr->sa_len); memcpy(&addreq.ifra_dstaddr, dstres->ai_addr, dstres->ai_addr->sa_len); if (ioctl_ctx(ctx, SIOCSIFPHYADDR, &addreq) < 0) warn("SIOCSIFPHYADDR"); } static void in_set_vhid(int vhid) { #ifdef WITHOUT_NETLINK in_addreq.ifra_vhid = vhid; #else in_add.vhid = (uint32_t)vhid; #endif } static struct afswtch af_inet = { .af_name = "inet", .af_af = AF_INET, #ifdef WITHOUT_NETLINK .af_status = in_status, #else .af_status = in_status_nl, #endif .af_getaddr = in_getaddr, .af_copyaddr = in_copyaddr, .af_postproc = in_postproc, .af_status_tunnel = in_status_tunnel, .af_settunnel = in_set_tunnel, .af_setvhid = in_set_vhid, #ifdef WITHOUT_NETLINK .af_difaddr = SIOCDIFADDR, .af_aifaddr = SIOCAIFADDR, .af_ridreq = &in_ridreq, .af_addreq = &in_addreq, .af_exec = af_exec_ioctl, #else .af_difaddr = NL_RTM_DELADDR, .af_aifaddr = NL_RTM_NEWADDR, .af_ridreq = &in_del, .af_addreq = &in_add, .af_exec = in_exec_nl, #endif }; static __constructor void inet_ctor(void) { #ifndef RESCUE if (!feature_present("inet")) return; #endif af_register(&af_inet); } diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c index bc4f77f6848d..fcd04139a8c1 100644 --- a/sbin/ifconfig/af_inet6.c +++ b/sbin/ifconfig/af_inet6.c @@ -1,788 +1,788 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 /* Define ND6_INFINITE_LIFETIME */ #include "ifconfig.h" #include "ifconfig_netlink.h" #ifndef WITHOUT_NETLINK struct in6_px { struct in6_addr addr; int plen; bool set; }; struct in6_pdata { struct in6_px addr; struct in6_px dst_addr; struct in6_addrlifetime lifetime; uint32_t flags; uint32_t vhid; }; static struct in6_pdata in6_del; static struct in6_pdata in6_add = { .lifetime = { 0, 0, ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME }, }; #else static struct in6_ifreq in6_ridreq; static struct in6_aliasreq in6_addreq = { .ifra_flags = 0, .ifra_lifetime = { 0, 0, ND6_INFINITE_LIFETIME, ND6_INFINITE_LIFETIME } }; #endif static int ip6lifetime; #ifdef WITHOUT_NETLINK static int prefix(void *, int); #endif static char *sec2str(time_t); static int explicit_prefix = 0; extern char *f_inet6, *f_addr; extern void setnd6flags(if_ctx *, const char *, int); extern void setnd6defif(if_ctx *,const char *, int); extern void nd6_status(if_ctx *); static char addr_buf[NI_MAXHOST]; /*for getnameinfo()*/ static void setifprefixlen(if_ctx *ctx __netlink_unused, const char *addr, int dummy __unused) { #ifdef WITHOUT_NETLINK const struct afswtch *afp = ctx->afp; if (afp->af_getprefix != NULL) afp->af_getprefix(addr, MASK); #else int plen = strtol(addr, NULL, 10); if ((plen < 0) || (plen > 128)) errx(1, "%s: bad value", addr); in6_add.addr.plen = plen; #endif explicit_prefix = 1; } static void setip6flags(if_ctx *ctx, const char *dummyaddr __unused, int flag) { const struct afswtch *afp = ctx->afp; if (afp->af_af != AF_INET6) err(1, "address flags can be set only for inet6 addresses"); #ifdef WITHOUT_NETLINK if (flag < 0) in6_addreq.ifra_flags &= ~(-flag); else in6_addreq.ifra_flags |= flag; #else if (flag < 0) in6_add.flags &= ~(-flag); else in6_add.flags |= flag; #endif } static void setip6lifetime(if_ctx *ctx, const char *cmd, const char *val) { const struct afswtch *afp = ctx->afp; struct timespec now; time_t newval; char *ep; #ifdef WITHOUT_NETLINK struct in6_addrlifetime *lifetime = &in6_addreq.ifra_lifetime; #else struct in6_addrlifetime *lifetime = &in6_add.lifetime; #endif clock_gettime(CLOCK_MONOTONIC_FAST, &now); newval = (time_t)strtoul(val, &ep, 0); if (val == ep) errx(1, "invalid %s", cmd); if (afp->af_af != AF_INET6) errx(1, "%s not allowed for the AF", cmd); if (strcmp(cmd, "vltime") == 0) { lifetime->ia6t_expire = now.tv_sec + newval; lifetime->ia6t_vltime = newval; } else if (strcmp(cmd, "pltime") == 0) { lifetime->ia6t_preferred = now.tv_sec + newval; lifetime->ia6t_pltime = newval; } } static void setip6pltime(if_ctx *ctx, const char *seconds, int dummy __unused) { setip6lifetime(ctx, "pltime", seconds); } static void setip6vltime(if_ctx *ctx, const char *seconds, int dummy __unused) { setip6lifetime(ctx, "vltime", seconds); } static void setip6eui64(if_ctx *ctx, const char *cmd, int dummy __unused) { const struct afswtch *afp = ctx->afp; struct ifaddrs *ifap, *ifa; const struct sockaddr_in6 *sin6 = NULL; const struct in6_addr *lladdr = NULL; struct in6_addr *in6; if (afp->af_af != AF_INET6) errx(EXIT_FAILURE, "%s not allowed for the AF", cmd); #ifdef WITHOUT_NETLINK in6 = (struct in6_addr *)&in6_addreq.ifra_addr.sin6_addr; #else in6 = &in6_add.addr.addr; #endif if (memcmp(&in6addr_any.s6_addr[8], &in6->s6_addr[8], 8) != 0) errx(EXIT_FAILURE, "interface index is already filled"); if (getifaddrs(&ifap) != 0) err(EXIT_FAILURE, "getifaddrs"); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family == AF_INET6 && strcmp(ifa->ifa_name, ctx->ifname) == 0) { sin6 = (const struct sockaddr_in6 *)satosin6(ifa->ifa_addr); if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { lladdr = &sin6->sin6_addr; break; } } } if (!lladdr) errx(EXIT_FAILURE, "could not determine link local address"); memcpy(&in6->s6_addr[8], &lladdr->s6_addr[8], 8); freeifaddrs(ifap); } static void print_addr(struct sockaddr_in6 *sin) { int error, n_flags; if (f_addr != NULL && strcmp(f_addr, "fqdn") == 0) n_flags = 0; else if (f_addr != NULL && strcmp(f_addr, "host") == 0) n_flags = NI_NOFQDN; else n_flags = NI_NUMERICHOST; error = getnameinfo((struct sockaddr *)sin, sin->sin6_len, addr_buf, sizeof(addr_buf), NULL, 0, n_flags); if (error != 0) inet_ntop(AF_INET6, &sin->sin6_addr, addr_buf, sizeof(addr_buf)); printf("\tinet6 %s", addr_buf); } static void print_p2p(struct sockaddr_in6 *sin) { int error; error = getnameinfo((struct sockaddr *)sin, sin->sin6_len, addr_buf, sizeof(addr_buf), NULL, 0, NI_NUMERICHOST); if (error != 0) inet_ntop(AF_INET6, &sin->sin6_addr, addr_buf, sizeof(addr_buf)); printf(" --> %s", addr_buf); } static void print_mask(int plen) { if (f_inet6 != NULL && strcmp(f_inet6, "cidr") == 0) printf("/%d", plen); else printf(" prefixlen %d", plen); } static void print_flags(int flags6) { if ((flags6 & IN6_IFF_ANYCAST) != 0) printf(" anycast"); if ((flags6 & IN6_IFF_TENTATIVE) != 0) printf(" tentative"); if ((flags6 & IN6_IFF_DUPLICATED) != 0) printf(" duplicated"); if ((flags6 & IN6_IFF_DETACHED) != 0) printf(" detached"); if ((flags6 & IN6_IFF_DEPRECATED) != 0) printf(" deprecated"); if ((flags6 & IN6_IFF_AUTOCONF) != 0) printf(" autoconf"); if ((flags6 & IN6_IFF_TEMPORARY) != 0) printf(" temporary"); if ((flags6 & IN6_IFF_PREFER_SOURCE) != 0) printf(" prefer_source"); } static void print_lifetime(const char *prepend, time_t px_time, struct timespec *now) { printf(" %s", prepend); if (px_time == 0) printf(" infty"); printf(" %s", px_time < now->tv_sec ? "0" : sec2str(px_time - now->tv_sec)); } #ifdef WITHOUT_NETLINK static void in6_status(if_ctx *ctx, const struct ifaddrs *ifa) { struct sockaddr_in6 *sin, null_sin = {}; struct in6_ifreq ifr6; int s6; u_int32_t flags6; struct in6_addrlifetime lifetime; sin = satosin6(ifa->ifa_addr); if (sin == NULL) return; strlcpy(ifr6.ifr_name, ctx->ifname, sizeof(ifr6.ifr_name)); if ((s6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { warn("socket(AF_INET6,SOCK_DGRAM)"); return; } ifr6.ifr_addr = *sin; if (ioctl(s6, SIOCGIFAFLAG_IN6, &ifr6) < 0) { warn("ioctl(SIOCGIFAFLAG_IN6)"); close(s6); return; } flags6 = ifr6.ifr_ifru.ifru_flags6; memset(&lifetime, 0, sizeof(lifetime)); ifr6.ifr_addr = *sin; if (ioctl(s6, SIOCGIFALIFETIME_IN6, &ifr6) < 0) { warn("ioctl(SIOCGIFALIFETIME_IN6)"); close(s6); return; } lifetime = ifr6.ifr_ifru.ifru_lifetime; close(s6); print_addr(sin); if (ifa->ifa_flags & IFF_POINTOPOINT) { sin = satosin6(ifa->ifa_dstaddr); /* * some of the interfaces do not have valid destination * address. */ if (sin != NULL && sin->sin6_family == AF_INET6) print_p2p(sin); } sin = satosin6(ifa->ifa_netmask); if (sin == NULL) sin = &null_sin; print_mask(prefix(&sin->sin6_addr, sizeof(struct in6_addr))); print_flags(flags6); if ((satosin6(ifa->ifa_addr))->sin6_scope_id) printf(" scopeid 0x%x", (satosin6(ifa->ifa_addr))->sin6_scope_id); if (ip6lifetime && (lifetime.ia6t_preferred || lifetime.ia6t_expire)) { struct timespec now; clock_gettime(CLOCK_MONOTONIC_FAST, &now); print_lifetime("pltime", lifetime.ia6t_preferred, &now); print_lifetime("vltime", lifetime.ia6t_expire, &now); } print_vhid(ifa); putchar('\n'); } #else static void show_lifetime(struct ifa_cacheinfo *ci) { struct timespec now; uint32_t pl, vl; if (ci == NULL) return; int count = ci->ifa_prefered != ND6_INFINITE_LIFETIME; count += ci->ifa_valid != ND6_INFINITE_LIFETIME; if (count == 0) return; pl = (ci->ifa_prefered == ND6_INFINITE_LIFETIME) ? 0 : ci->ifa_prefered; vl = (ci->ifa_valid == ND6_INFINITE_LIFETIME) ? 0 : ci->ifa_valid; clock_gettime(CLOCK_MONOTONIC_FAST, &now); print_lifetime("pltime", pl + now.tv_sec, &now); print_lifetime("vltime", vl + now.tv_sec, &now); } static void in6_status_nl(if_ctx *ctx __unused, if_link_t *link __unused, if_addr_t *ifa) { int plen = ifa->ifa_prefixlen; uint32_t scopeid; if (ifa->ifa_local == NULL) { /* Non-P2P address */ scopeid = satosin6(ifa->ifa_address)->sin6_scope_id; print_addr(satosin6(ifa->ifa_address)); } else { scopeid = satosin6(ifa->ifa_local)->sin6_scope_id; print_addr(satosin6(ifa->ifa_local)); print_p2p(satosin6(ifa->ifa_address)); } print_mask(plen); print_flags(ifa->ifaf_flags); if (scopeid != 0) printf(" scopeid 0x%x", scopeid); show_lifetime(ifa->ifa_cacheinfo); if (ifa->ifaf_vhid != 0) printf(" vhid %d", ifa->ifaf_vhid); putchar('\n'); } static struct in6_px *sin6tab_nl[] = { &in6_del.addr, /* RIDADDR */ &in6_add.addr, /* ADDR */ NULL, /* MASK */ &in6_add.dst_addr, /* DSTADDR*/ }; static void in6_copyaddr(if_ctx *ctx __unused, int to, int from) { sin6tab_nl[to]->addr = sin6tab_nl[from]->addr; sin6tab_nl[to]->set = sin6tab_nl[from]->set; } static void in6_getaddr(const char *addr_str, int which) { struct in6_px *px = sin6tab_nl[which]; px->set = true; px->plen = 128; if (which == ADDR) { char *p = NULL; if((p = strrchr(addr_str, '/')) != NULL) { *p = '\0'; int plen = strtol(p + 1, NULL, 10); if (plen < 0 || plen > 128) errx(1, "%s: bad value", p + 1); px->plen = plen; explicit_prefix = 1; } } struct addrinfo hints = { .ai_family = AF_INET6 }; struct addrinfo *res; int error = getaddrinfo(addr_str, NULL, &hints, &res); if (error != 0) { if (inet_pton(AF_INET6, addr_str, &px->addr) != 1) errx(1, "%s: bad value", addr_str); } else { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)(void *)res->ai_addr; px->addr = sin6->sin6_addr; freeaddrinfo(res); } } static int in6_exec_nl(if_ctx *ctx, unsigned long action, void *data) { struct in6_pdata *pdata = (struct in6_pdata *)data; struct snl_writer nw = {}; snl_init_writer(ctx->io_ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, action); struct ifaddrmsg *ifahdr = snl_reserve_msg_object(&nw, struct ifaddrmsg); ifahdr->ifa_family = AF_INET6; ifahdr->ifa_prefixlen = pdata->addr.plen; ifahdr->ifa_index = if_nametoindex_nl(ctx->io_ss, ctx->ifname); snl_add_msg_attr_ip6(&nw, IFA_LOCAL, &pdata->addr.addr); if (action == NL_RTM_NEWADDR && pdata->dst_addr.set) snl_add_msg_attr_ip6(&nw, IFA_ADDRESS, &pdata->dst_addr.addr); struct ifa_cacheinfo ci = { .ifa_prefered = pdata->lifetime.ia6t_pltime, .ifa_valid = pdata->lifetime.ia6t_vltime, }; snl_add_msg_attr(&nw, IFA_CACHEINFO, sizeof(ci), &ci); int off = snl_add_msg_attr_nested(&nw, IFA_FREEBSD); snl_add_msg_attr_u32(&nw, IFAF_FLAGS, pdata->flags); if (pdata->vhid != 0) snl_add_msg_attr_u32(&nw, IFAF_VHID, pdata->vhid); snl_end_attr_nested(&nw, off); - if (!snl_finalize_msg(&nw) || !snl_send_message(ctx->io_ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ctx->io_ss, hdr)) return (0); struct snl_errmsg_data e = {}; snl_read_reply_code(ctx->io_ss, hdr->nlmsg_seq, &e); return (e.error); } #endif #ifdef WITHOUT_NETLINK static struct sockaddr_in6 *sin6tab[] = { &in6_ridreq.ifr_addr, &in6_addreq.ifra_addr, &in6_addreq.ifra_prefixmask, &in6_addreq.ifra_dstaddr }; static void in6_copyaddr(if_ctx *ctx __unused, int to, int from) { memcpy(sin6tab[to], sin6tab[from], sizeof(struct sockaddr_in6)); } static void in6_getprefix(const char *plen, int which) { struct sockaddr_in6 *sin = sin6tab[which]; u_char *cp; int len = atoi(plen); if ((len < 0) || (len > 128)) errx(1, "%s: bad value", plen); sin->sin6_len = sizeof(*sin); if (which != MASK) sin->sin6_family = AF_INET6; if ((len == 0) || (len == 128)) { memset(&sin->sin6_addr, 0xff, sizeof(struct in6_addr)); return; } memset((void *)&sin->sin6_addr, 0x00, sizeof(sin->sin6_addr)); for (cp = (u_char *)&sin->sin6_addr; len > 7; len -= 8) *cp++ = 0xff; *cp = 0xff << (8 - len); } static void in6_getaddr(const char *s, int which) { struct sockaddr_in6 *sin = sin6tab[which]; struct addrinfo hints, *res; int error = -1; sin->sin6_len = sizeof(*sin); if (which != MASK) sin->sin6_family = AF_INET6; if (which == ADDR) { char *p = NULL; if((p = strrchr(s, '/')) != NULL) { *p = '\0'; in6_getprefix(p + 1, MASK); explicit_prefix = 1; } } if (sin->sin6_family == AF_INET6) { bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; error = getaddrinfo(s, NULL, &hints, &res); if (error != 0) { if (inet_pton(AF_INET6, s, &sin->sin6_addr) != 1) errx(1, "%s: bad value", s); } else { bcopy(res->ai_addr, sin, res->ai_addrlen); freeaddrinfo(res); } } } static int prefix(void *val, int size) { u_char *name = (u_char *)val; int byte, bit, plen = 0; for (byte = 0; byte < size; byte++, plen += 8) if (name[byte] != 0xff) break; if (byte == size) return (plen); for (bit = 7; bit != 0; bit--, plen++) if (!(name[byte] & (1 << bit))) break; for (; bit != 0; bit--) if (name[byte] & (1 << bit)) return(0); byte++; for (; byte < size; byte++) if (name[byte]) return(0); return (plen); } #endif static char * sec2str(time_t total) { static char result[256]; int days, hours, mins, secs; int first = 1; char *p = result; if (0) { days = total / 3600 / 24; hours = (total / 3600) % 24; mins = (total / 60) % 60; secs = total % 60; if (days) { first = 0; p += sprintf(p, "%dd", days); } if (!first || hours) { first = 0; p += sprintf(p, "%dh", hours); } if (!first || mins) { first = 0; p += sprintf(p, "%dm", mins); } sprintf(p, "%ds", secs); } else sprintf(result, "%lu", (unsigned long)total); return(result); } static void in6_postproc(if_ctx *ctx, int newaddr __unused, int ifflags __unused) { if (explicit_prefix == 0) { /* Aggregatable address architecture defines all prefixes are 64. So, it is convenient to set prefixlen to 64 if it is not specified. */ setifprefixlen(ctx, "64", 0); /* in6_getprefix("64", MASK) if MASK is available here... */ } } static void in6_status_tunnel(if_ctx *ctx) { char src[NI_MAXHOST]; char dst[NI_MAXHOST]; struct in6_ifreq in6_ifr; const struct sockaddr *sa = (const struct sockaddr *) &in6_ifr.ifr_addr; memset(&in6_ifr, 0, sizeof(in6_ifr)); strlcpy(in6_ifr.ifr_name, ctx->ifname, sizeof(in6_ifr.ifr_name)); if (ioctl_ctx(ctx, SIOCGIFPSRCADDR_IN6, (caddr_t)&in6_ifr) < 0) return; if (sa->sa_family != AF_INET6) return; if (getnameinfo(sa, sa->sa_len, src, sizeof(src), 0, 0, NI_NUMERICHOST) != 0) src[0] = '\0'; if (ioctl_ctx(ctx, SIOCGIFPDSTADDR_IN6, (caddr_t)&in6_ifr) < 0) return; if (sa->sa_family != AF_INET6) return; if (getnameinfo(sa, sa->sa_len, dst, sizeof(dst), 0, 0, NI_NUMERICHOST) != 0) dst[0] = '\0'; printf("\ttunnel inet6 %s --> %s\n", src, dst); } static void in6_set_tunnel(if_ctx *ctx, struct addrinfo *srcres, struct addrinfo *dstres) { struct in6_aliasreq in6_req = {}; strlcpy(in6_req.ifra_name, ctx->ifname, sizeof(in6_req.ifra_name)); memcpy(&in6_req.ifra_addr, srcres->ai_addr, srcres->ai_addr->sa_len); memcpy(&in6_req.ifra_dstaddr, dstres->ai_addr, dstres->ai_addr->sa_len); if (ioctl_ctx(ctx, SIOCSIFPHYADDR_IN6, &in6_req) < 0) warn("SIOCSIFPHYADDR_IN6"); } static void in6_set_vhid(int vhid) { #ifdef WITHOUT_NETLINK in6_addreq.ifra_vhid = vhid; #else in6_add.vhid = (uint32_t)vhid; #endif } static struct cmd inet6_cmds[] = { DEF_CMD_ARG("prefixlen", setifprefixlen), DEF_CMD("anycast", IN6_IFF_ANYCAST, setip6flags), DEF_CMD("tentative", IN6_IFF_TENTATIVE, setip6flags), DEF_CMD("-tentative", -IN6_IFF_TENTATIVE, setip6flags), DEF_CMD("deprecated", IN6_IFF_DEPRECATED, setip6flags), DEF_CMD("-deprecated", -IN6_IFF_DEPRECATED, setip6flags), DEF_CMD("autoconf", IN6_IFF_AUTOCONF, setip6flags), DEF_CMD("-autoconf", -IN6_IFF_AUTOCONF, setip6flags), DEF_CMD("prefer_source",IN6_IFF_PREFER_SOURCE, setip6flags), DEF_CMD("-prefer_source",-IN6_IFF_PREFER_SOURCE,setip6flags), DEF_CMD("accept_rtadv", ND6_IFF_ACCEPT_RTADV, setnd6flags), DEF_CMD("-accept_rtadv",-ND6_IFF_ACCEPT_RTADV, setnd6flags), DEF_CMD("no_radr", ND6_IFF_NO_RADR, setnd6flags), DEF_CMD("-no_radr", -ND6_IFF_NO_RADR, setnd6flags), DEF_CMD("defaultif", 1, setnd6defif), DEF_CMD("-defaultif", -1, setnd6defif), DEF_CMD("ifdisabled", ND6_IFF_IFDISABLED, setnd6flags), DEF_CMD("-ifdisabled", -ND6_IFF_IFDISABLED, setnd6flags), DEF_CMD("nud", ND6_IFF_PERFORMNUD, setnd6flags), DEF_CMD("-nud", -ND6_IFF_PERFORMNUD, setnd6flags), DEF_CMD("auto_linklocal",ND6_IFF_AUTO_LINKLOCAL,setnd6flags), DEF_CMD("-auto_linklocal",-ND6_IFF_AUTO_LINKLOCAL,setnd6flags), DEF_CMD("no_prefer_iface",ND6_IFF_NO_PREFER_IFACE,setnd6flags), DEF_CMD("-no_prefer_iface",-ND6_IFF_NO_PREFER_IFACE,setnd6flags), DEF_CMD("no_dad", ND6_IFF_NO_DAD, setnd6flags), DEF_CMD("-no_dad", -ND6_IFF_NO_DAD, setnd6flags), DEF_CMD_ARG("pltime", setip6pltime), DEF_CMD_ARG("vltime", setip6vltime), DEF_CMD("eui64", 0, setip6eui64), #ifdef EXPERIMENTAL DEF_CMD("ipv6_only", ND6_IFF_IPV6_ONLY_MANUAL,setnd6flags), DEF_CMD("-ipv6_only", -ND6_IFF_IPV6_ONLY_MANUAL,setnd6flags), #endif }; static struct afswtch af_inet6 = { .af_name = "inet6", .af_af = AF_INET6, #ifdef WITHOUT_NETLINK .af_status = in6_status, #else .af_status = in6_status_nl, #endif .af_getaddr = in6_getaddr, .af_copyaddr = in6_copyaddr, #ifdef WITHOUT_NETLINK .af_getprefix = in6_getprefix, #endif .af_other_status = nd6_status, .af_postproc = in6_postproc, .af_status_tunnel = in6_status_tunnel, .af_settunnel = in6_set_tunnel, .af_setvhid = in6_set_vhid, #ifdef WITHOUT_NETLINK .af_difaddr = SIOCDIFADDR_IN6, .af_aifaddr = SIOCAIFADDR_IN6, .af_ridreq = &in6_addreq, .af_addreq = &in6_addreq, .af_exec = af_exec_ioctl, #else .af_difaddr = NL_RTM_DELADDR, .af_aifaddr = NL_RTM_NEWADDR, .af_ridreq = &in6_add, .af_addreq = &in6_add, .af_exec = in6_exec_nl, #endif }; static void in6_Lopt_cb(const char *arg __unused) { ip6lifetime++; /* print IPv6 address lifetime */ } static struct option in6_Lopt = { .opt = "L", .opt_usage = "[-L]", .cb = in6_Lopt_cb }; static __constructor void inet6_ctor(void) { size_t i; #ifndef RESCUE if (!feature_present("inet6")) return; #endif for (i = 0; i < nitems(inet6_cmds); i++) cmd_register(&inet6_cmds[i]); af_register(&af_inet6); opt_register(&in6_Lopt); } diff --git a/sbin/ifconfig/ifconfig_netlink.c b/sbin/ifconfig/ifconfig_netlink.c index a6f52ea2a7f0..826d199d3ccb 100644 --- a/sbin/ifconfig/ifconfig_netlink.c +++ b/sbin/ifconfig/ifconfig_netlink.c @@ -1,497 +1,497 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2022 Alexander V. Chernikov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ifconfig.h" #include "ifconfig_netlink.h" static const char *IFFBITS[] = { "UP", /* 00:0x1 IFF_UP*/ "BROADCAST", /* 01:0x2 IFF_BROADCAST*/ "DEBUG", /* 02:0x4 IFF_DEBUG*/ "LOOPBACK", /* 03:0x8 IFF_LOOPBACK*/ "POINTOPOINT", /* 04:0x10 IFF_POINTOPOINT*/ "NEEDSEPOCH", /* 05:0x20 IFF_NEEDSEPOCH*/ "RUNNING", /* 06:0x40 IFF_DRV_RUNNING*/ "NOARP", /* 07:0x80 IFF_NOARP*/ "PROMISC", /* 08:0x100 IFF_PROMISC*/ "ALLMULTI", /* 09:0x200 IFF_ALLMULTI*/ "DRV_OACTIVE", /* 10:0x400 IFF_DRV_OACTIVE*/ "SIMPLEX", /* 11:0x800 IFF_SIMPLEX*/ "LINK0", /* 12:0x1000 IFF_LINK0*/ "LINK1", /* 13:0x2000 IFF_LINK1*/ "LINK2", /* 14:0x4000 IFF_LINK2*/ "MULTICAST", /* 15:0x8000 IFF_MULTICAST*/ "CANTCONFIG", /* 16:0x10000 IFF_CANTCONFIG*/ "PPROMISC", /* 17:0x20000 IFF_PPROMISC*/ "MONITOR", /* 18:0x40000 IFF_MONITOR*/ "STATICARP", /* 19:0x80000 IFF_STATICARP*/ "STICKYARP", /* 20:0x100000 IFF_STICKYARP*/ "DYING", /* 21:0x200000 IFF_DYING*/ "RENAMING", /* 22:0x400000 IFF_RENAMING*/ "NOGROUP", /* 23:0x800000 IFF_NOGROUP*/ "LOWER_UP", /* 24:0x1000000 IFF_NETLINK_1*/ }; static void print_bits(const char *btype, uint32_t *v, const int v_count, const char **names, const int n_count) { int num = 0; for (int i = 0; i < v_count * 32; i++) { bool is_set = v[i / 32] & (1U << (i % 32)); if (is_set) { if (num++ == 0) printf("<"); if (num != 1) printf(","); if (i < n_count) printf("%s", names[i]); else printf("%s_%d", btype, i); } } if (num > 0) printf(">"); } static void nl_init_socket(struct snl_state *ss) { if (snl_init(ss, NETLINK_ROUTE)) return; if (modfind("netlink") == -1 && errno == ENOENT) { /* Try to load */ if (kldload("netlink") == -1) err(1, "netlink is not loaded and load attempt failed"); if (snl_init(ss, NETLINK_ROUTE)) return; } err(1, "unable to open netlink socket"); } int ifconfig_nl(if_ctx *ctx, int iscreate, const struct afswtch *uafp) { struct snl_state ss = {}; nl_init_socket(&ss); ctx->io_ss = &ss; int error = ifconfig_ioctl(ctx, iscreate, uafp); snl_free(&ss); ctx->io_ss = NULL; return (error); } struct ifa { struct ifa *next; uint32_t idx; struct snl_parsed_addr addr; }; struct iface { struct snl_parsed_link link; struct ifa *ifa; uint32_t ifa_count; uint32_t idx; }; struct ifmap { uint32_t size; uint32_t count; struct iface **ifaces; }; /* * Returns ifmap ifindex->snl_parsed_link. * Memory is allocated using snl temporary buffers */ static struct ifmap * prepare_ifmap(struct snl_state *ss) { struct snl_writer nw = {}; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); hdr->nlmsg_flags |= NLM_F_DUMP; snl_reserve_msg_object(&nw, struct ifinfomsg); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (NULL); uint32_t nlmsg_seq = hdr->nlmsg_seq; struct ifmap *ifmap = snl_allocz(ss, sizeof(*ifmap)); struct snl_errmsg_data e = {}; while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) { struct iface *iface = snl_allocz(ss, sizeof(*iface)); if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser, &iface->link)) continue; if (iface->link.ifi_index >= ifmap->size) { size_t new_size = MAX(ifmap->size, 32); while (new_size <= iface->link.ifi_index + 1) new_size *= 2; struct iface **ifaces= snl_allocz(ss, new_size * sizeof(void *)); memcpy(ifaces, ifmap->ifaces, ifmap->size * sizeof(void *)); ifmap->ifaces = ifaces; ifmap->size = new_size; } ifmap->ifaces[iface->link.ifi_index] = iface; ifmap->count++; iface->idx = ifmap->count; } return (ifmap); } uint32_t if_nametoindex_nl(struct snl_state *ss, const char *ifname) { struct snl_writer nw = {}; struct snl_parsed_link_simple link = {}; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); snl_reserve_msg_object(&nw, struct ifinfomsg); snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (0); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr->nlmsg_type != NL_RTM_NEWLINK) return (0); if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, &link)) return (0); return (link.ifi_index); } ifType convert_iftype(ifType iftype) { switch (iftype) { case IFT_IEEE8023ADLAG: return (IFT_ETHER); case IFT_INFINIBANDLAG: return (IFT_INFINIBAND); default: return (iftype); } } static void prepare_ifaddrs(struct snl_state *ss, struct ifmap *ifmap) { struct snl_writer nw = {}; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETADDR); hdr->nlmsg_flags |= NLM_F_DUMP; snl_reserve_msg_object(&nw, struct ifaddrmsg); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return; uint32_t nlmsg_seq = hdr->nlmsg_seq; struct snl_errmsg_data e = {}; uint32_t count = 0; while ((hdr = snl_read_reply_multi(ss, nlmsg_seq, &e)) != NULL) { struct ifa *ifa = snl_allocz(ss, sizeof(*ifa)); if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &ifa->addr)) continue; const uint32_t ifindex = ifa->addr.ifa_index; if (ifindex >= ifmap->size || ifmap->ifaces[ifindex] == NULL) continue; struct iface *iface = ifmap->ifaces[ifindex]; ifa->next = iface->ifa; ifa->idx = ++count; iface->ifa = ifa; iface->ifa_count++; } } static bool match_iface(struct ifconfig_args *args, struct iface *iface) { if_link_t *link = &iface->link; if (args->ifname != NULL && strcmp(args->ifname, link->ifla_ifname)) return (false); if (!match_if_flags(args, link->ifi_flags)) return (false); if (!group_member(link->ifla_ifname, args->matchgroup, args->nogroup)) return (false); if (args->afp == NULL) return (true); if (!strcmp(args->afp->af_name, "ether")) { if (link->ifla_address == NULL) return (false); struct sockaddr_dl sdl = { .sdl_len = sizeof(struct sockaddr_dl), .sdl_family = AF_LINK, .sdl_type = convert_iftype(link->ifi_type), .sdl_alen = NLA_DATA_LEN(link->ifla_address), }; return (match_ether(&sdl)); } else if (args->afp->af_af == AF_LINK) /* * The rtnetlink(4) RTM_GETADDR does not list link level * addresses, so latter cycle won't match anything. Short * circuit on RTM_GETLINK has provided us an address. */ return (link->ifla_address != NULL); for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) { if (args->afp->af_af == ifa->addr.ifa_family) return (true); } return (false); } /* Sort according to the kernel-provided order */ static int cmp_iface(const void *_a, const void *_b) { const struct iface *a = *((const void * const *)_a); const struct iface *b = *((const void * const *)_b); return ((a->idx > b->idx) * 2 - 1); } static int cmp_ifaddr(const void *_a, const void *_b) { const struct ifa *a = *((const void * const *)_a); const struct ifa *b = *((const void * const *)_b); if (a->addr.ifa_family != b->addr.ifa_family) return ((a->addr.ifa_family > b->addr.ifa_family) * 2 - 1); return ((a->idx > b->idx) * 2 - 1); } static void sort_iface_ifaddrs(struct snl_state *ss, struct iface *iface) { if (iface->ifa_count == 0) return; struct ifa **sorted_ifaddrs = snl_allocz(ss, iface->ifa_count * sizeof(void *)); struct ifa *ifa = iface->ifa; for (uint32_t i = 0; i < iface->ifa_count; i++) { struct ifa *ifa_next = ifa->next; sorted_ifaddrs[i] = ifa; ifa->next = NULL; ifa = ifa_next; } qsort(sorted_ifaddrs, iface->ifa_count, sizeof(void *), cmp_ifaddr); ifa = sorted_ifaddrs[0]; iface->ifa = ifa; for (uint32_t i = 1; i < iface->ifa_count; i++) { ifa->next = sorted_ifaddrs[i]; ifa = sorted_ifaddrs[i]; } } static void print_ifcaps(if_ctx *ctx, if_link_t *link) { uint32_t sz_u32 = roundup2(link->iflaf_caps.nla_bitset_size, 32) / 32; if (sz_u32 > 0) { uint32_t *caps = link->iflaf_caps.nla_bitset_value; printf("\toptions=%x", caps[0]); print_bits("IFCAPS", caps, sz_u32, ifcap_bit_names, nitems(ifcap_bit_names)); putchar('\n'); } if (ctx->args->supmedia && sz_u32 > 0) { uint32_t *caps = link->iflaf_caps.nla_bitset_mask; printf("\tcapabilities=%x", caps[0]); print_bits("IFCAPS", caps, sz_u32, ifcap_bit_names, nitems(ifcap_bit_names)); putchar('\n'); } } static void status_nl(if_ctx *ctx, struct iface *iface) { if_link_t *link = &iface->link; struct ifconfig_args *args = ctx->args; printf("%s: ", link->ifla_ifname); printf("flags=%x", link->ifi_flags); print_bits("IFF", &link->ifi_flags, 1, IFFBITS, nitems(IFFBITS)); print_metric(ctx); printf(" mtu %d\n", link->ifla_mtu); if (link->ifla_ifalias != NULL) printf("\tdescription: %s\n", link->ifla_ifalias); print_ifcaps(ctx, link); tunnel_status(ctx); if (args->allfamilies | (args->afp != NULL && args->afp->af_af == AF_LINK)) { /* Start with link-level */ const struct afswtch *p = af_getbyfamily(AF_LINK); if (p != NULL && link->ifla_address != NULL) p->af_status(ctx, link, NULL); } sort_iface_ifaddrs(ctx->io_ss, iface); for (struct ifa *ifa = iface->ifa; ifa != NULL; ifa = ifa->next) { if (args->allfamilies) { const struct afswtch *p = af_getbyfamily(ifa->addr.ifa_family); if (p != NULL) p->af_status(ctx, link, &ifa->addr); } else if (args->afp->af_af == ifa->addr.ifa_family) { const struct afswtch *p = args->afp; p->af_status(ctx, link, &ifa->addr); } } /* TODO: convert to netlink */ if (args->allfamilies) af_other_status(ctx); else if (args->afp->af_other_status != NULL) args->afp->af_other_status(ctx); print_ifstatus(ctx); if (args->verbose > 0) sfp_status(ctx); } static int get_local_socket(void) { int s = socket(AF_LOCAL, SOCK_DGRAM, 0); if (s < 0) err(1, "socket(family %u,SOCK_DGRAM)", AF_LOCAL); return (s); } void list_interfaces_nl(struct ifconfig_args *args) { struct snl_state ss = {}; struct ifconfig_context _ctx = { .args = args, .io_s = get_local_socket(), .io_ss = &ss, }; struct ifconfig_context *ctx = &_ctx; nl_init_socket(&ss); struct ifmap *ifmap = prepare_ifmap(&ss); struct iface **sorted_ifaces = snl_allocz(&ss, ifmap->count * sizeof(void *)); for (uint32_t i = 0, num = 0; i < ifmap->size; i++) { if (ifmap->ifaces[i] != NULL) { sorted_ifaces[num++] = ifmap->ifaces[i]; if (num == ifmap->count) break; } } qsort(sorted_ifaces, ifmap->count, sizeof(void *), cmp_iface); prepare_ifaddrs(&ss, ifmap); for (uint32_t i = 0, num = 0; i < ifmap->count; i++) { struct iface *iface = sorted_ifaces[i]; if (!match_iface(args, iface)) continue; ctx->ifname = iface->link.ifla_ifname; if (args->namesonly) { if (num++ != 0) printf(" "); fputs(iface->link.ifla_ifname, stdout); } else if (args->argc == 0) status_nl(ctx, iface); else ifconfig_ioctl(ctx, 0, args->afp); } if (args->namesonly) printf("\n"); close(ctx->io_s); snl_free(&ss); } diff --git a/sbin/route/route_netlink.c b/sbin/route/route_netlink.c index cad1c8030535..040b97568f20 100644 --- a/sbin/route/route_netlink.c +++ b/sbin/route/route_netlink.c @@ -1,913 +1,913 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char *routename(struct sockaddr *); const char *netname(struct sockaddr *); void printb(int, const char *); extern const char routeflags[]; extern int verbose, debugonly; int rtmsg_nl(int cmd, int rtm_flags, int fib, int rtm_addrs, struct sockaddr_storage *so, struct rt_metrics *rt_metrics); int flushroutes_fib_nl(int fib, int af); void monitor_nl(int fib); struct nl_helper; struct snl_msg_info; static void print_getmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct sockaddr *dst); static void print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo); #define s6_addr32 __u6_addr.__u6_addr32 #define bitcount32(x) __bitcount32((uint32_t)(x)) static int inet6_get_plen(const struct in6_addr *addr) { return (bitcount32(addr->s6_addr32[0]) + bitcount32(addr->s6_addr32[1]) + bitcount32(addr->s6_addr32[2]) + bitcount32(addr->s6_addr32[3])); } static void ip6_writemask(struct in6_addr *addr6, uint8_t mask) { uint32_t *cp; for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32) *cp++ = 0xFFFFFFFF; if (mask > 0) *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0); } static struct sockaddr * get_netmask(struct snl_state *ss, int family, int plen) { if (family == AF_INET) { if (plen == 32) return (NULL); struct sockaddr_in *sin = snl_allocz(ss, sizeof(*sin)); sin->sin_len = sizeof(*sin); sin->sin_family = family; sin->sin_addr.s_addr = htonl(plen ? ~((1 << (32 - plen)) - 1) : 0); return (struct sockaddr *)sin; } else if (family == AF_INET6) { if (plen == 128) return (NULL); struct sockaddr_in6 *sin6 = snl_allocz(ss, sizeof(*sin6)); sin6->sin6_len = sizeof(*sin6); sin6->sin6_family = family; ip6_writemask(&sin6->sin6_addr, plen); return (struct sockaddr *)sin6; } return (NULL); } static void nl_init_socket(struct snl_state *ss) { if (snl_init(ss, NETLINK_ROUTE)) return; if (modfind("netlink") == -1 && errno == ENOENT) { /* Try to load */ if (kldload("netlink") == -1) err(1, "netlink is not loaded and load attempt failed"); if (snl_init(ss, NETLINK_ROUTE)) return; } err(1, "unable to open netlink socket"); } struct nl_helper { struct snl_state ss_cmd; }; static void nl_helper_init(struct nl_helper *h) { nl_init_socket(&h->ss_cmd); } static void nl_helper_free(struct nl_helper *h) { snl_free(&h->ss_cmd); } static struct sockaddr * get_addr(struct sockaddr_storage *so, int rtm_addrs, int addr_type) { struct sockaddr *sa = NULL; if (rtm_addrs & (1 << addr_type)) sa = (struct sockaddr *)&so[addr_type]; return (sa); } static int rtmsg_nl_int(struct nl_helper *h, int cmd, int rtm_flags, int fib, int rtm_addrs, struct sockaddr_storage *so, struct rt_metrics *rt_metrics) { struct snl_state *ss = &h->ss_cmd; struct snl_writer nw; int nl_type = 0, nl_flags = 0; snl_init_writer(ss, &nw); switch (cmd) { case RTSOCK_RTM_ADD: nl_type = RTM_NEWROUTE; nl_flags = NLM_F_CREATE | NLM_F_APPEND; /* Do append by default */ break; case RTSOCK_RTM_CHANGE: nl_type = RTM_NEWROUTE; nl_flags = NLM_F_REPLACE; break; case RTSOCK_RTM_DELETE: nl_type = RTM_DELROUTE; break; case RTSOCK_RTM_GET: nl_type = RTM_GETROUTE; break; default: exit(1); } struct sockaddr *dst = get_addr(so, rtm_addrs, RTAX_DST); struct sockaddr *mask = get_addr(so, rtm_addrs, RTAX_NETMASK); struct sockaddr *gw = get_addr(so, rtm_addrs, RTAX_GATEWAY); if (dst == NULL) return (EINVAL); struct nlmsghdr *hdr = snl_create_msg_request(&nw, nl_type); hdr->nlmsg_flags |= nl_flags; int plen = 0; int rtm_type = RTN_UNICAST; switch (dst->sa_family) { case AF_INET: { struct sockaddr_in *mask4 = (struct sockaddr_in *)mask; if ((rtm_flags & RTF_HOST) == 0 && mask4 != NULL) plen = bitcount32(mask4->sin_addr.s_addr); else plen = 32; break; } case AF_INET6: { struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)mask; if ((rtm_flags & RTF_HOST) == 0 && mask6 != NULL) plen = inet6_get_plen(&mask6->sin6_addr); else plen = 128; break; } default: return (ENOTSUP); } if (rtm_flags & RTF_REJECT) rtm_type = RTN_PROHIBIT; else if (rtm_flags & RTF_BLACKHOLE) rtm_type = RTN_BLACKHOLE; struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); rtm->rtm_family = dst->sa_family; rtm->rtm_protocol = RTPROT_STATIC; rtm->rtm_type = rtm_type; rtm->rtm_dst_len = plen; /* Request exact prefix match if mask is set */ if ((cmd == RTSOCK_RTM_GET) && (mask != NULL)) rtm->rtm_flags = RTM_F_PREFIX; snl_add_msg_attr_ip(&nw, RTA_DST, dst); snl_add_msg_attr_u32(&nw, RTA_TABLE, fib); uint32_t rta_oif = 0; if (gw != NULL) { if (rtm_flags & RTF_GATEWAY) { if (gw->sa_family == dst->sa_family) snl_add_msg_attr_ip(&nw, RTA_GATEWAY, gw); else snl_add_msg_attr_ipvia(&nw, RTA_VIA, gw); if (gw->sa_family == AF_INET6) { struct sockaddr_in6 *gw6 = (struct sockaddr_in6 *)gw; if (IN6_IS_ADDR_LINKLOCAL(&gw6->sin6_addr)) rta_oif = gw6->sin6_scope_id; } } else { /* Should be AF_LINK */ struct sockaddr_dl *sdl = (struct sockaddr_dl *)gw; if (sdl->sdl_index != 0) rta_oif = sdl->sdl_index; } } if (dst->sa_family == AF_INET6 && rta_oif == 0) { struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst; if (IN6_IS_ADDR_LINKLOCAL(&dst6->sin6_addr)) rta_oif = dst6->sin6_scope_id; } if (rta_oif != 0) snl_add_msg_attr_u32(&nw, RTA_OIF, rta_oif); if (rtm_flags != 0) snl_add_msg_attr_u32(&nw, NL_RTA_RTFLAGS, rtm_flags); if (rt_metrics->rmx_mtu > 0) { int off = snl_add_msg_attr_nested(&nw, RTA_METRICS); snl_add_msg_attr_u32(&nw, RTAX_MTU, rt_metrics->rmx_mtu); snl_end_attr_nested(&nw, off); } if (rt_metrics->rmx_weight > 0) snl_add_msg_attr_u32(&nw, NL_RTA_WEIGHT, rt_metrics->rmx_weight); - if (snl_finalize_msg(&nw) && snl_send_message(ss, hdr)) { + if ((hdr = snl_finalize_msg(&nw)) && snl_send_message(ss, hdr)) { struct snl_errmsg_data e = {}; hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (nl_type == NL_RTM_GETROUTE) { if (hdr->nlmsg_type == NL_RTM_NEWROUTE) { print_getmsg(h, hdr, dst); return (0); } } if (snl_parse_errmsg(ss, hdr, &e)) { switch (e.error) { case (ESRCH): warnx("route has not been found"); break; default: if (e.error == 0) break; warnc(e.error, "message indicates error"); } return (e.error); } } return (EINVAL); } int rtmsg_nl(int cmd, int rtm_flags, int fib, int rtm_addrs, struct sockaddr_storage *so, struct rt_metrics *rt_metrics) { struct nl_helper h = {}; nl_helper_init(&h); int error = rtmsg_nl_int(&h, cmd, rtm_flags, fib, rtm_addrs, so, rt_metrics); nl_helper_free(&h); return (error); } static void get_ifdata(struct nl_helper *h, uint32_t ifindex, struct snl_parsed_link_simple *link) { struct snl_state *ss = &h->ss_cmd; struct snl_writer nw; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, NL_RTM_GETLINK); struct ifinfomsg *ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg); if (ifmsg != NULL) ifmsg->ifi_index = ifindex; - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return; hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr != NULL && hdr->nlmsg_type == RTM_NEWLINK) { snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link); } if (link->ifla_ifname == NULL) { char ifname[16]; snprintf(ifname, sizeof(ifname), "if#%u", ifindex); int len = strlen(ifname); char *buf = snl_allocz(ss, len + 1); strlcpy(buf, ifname, len + 1); link->ifla_ifname = buf; } } static void print_getmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct sockaddr *dst) { struct snl_state *ss = &h->ss_cmd; struct timespec ts; struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT }; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) return; struct snl_parsed_link_simple link = {}; get_ifdata(h, r.rta_oif, &link); if (r.rtax_mtu == 0) r.rtax_mtu = link.ifla_mtu; r.rta_rtflags |= (RTF_UP | RTF_DONE); (void)printf(" route to: %s\n", routename(dst)); if (r.rta_dst) (void)printf("destination: %s\n", routename(r.rta_dst)); struct sockaddr *mask = get_netmask(ss, r.rtm_family, r.rtm_dst_len); if (mask) (void)printf(" mask: %s\n", routename(mask)); if (r.rta_gw && (r.rta_rtflags & RTF_GATEWAY)) (void)printf(" gateway: %s\n", routename(r.rta_gw)); (void)printf(" fib: %u\n", (unsigned int)r.rta_table); if (link.ifla_ifname) (void)printf(" interface: %s\n", link.ifla_ifname); (void)printf(" flags: "); printb(r.rta_rtflags, routeflags); struct rt_metrics rmx = { .rmx_mtu = r.rtax_mtu, .rmx_weight = r.rtax_weight, .rmx_expire = r.rta_expire, }; printf("\n%9s %9s %9s %9s %9s %10s %9s\n", "recvpipe", "sendpipe", "ssthresh", "rtt,msec", "mtu ", "weight", "expire"); printf("%8lu ", rmx.rmx_recvpipe); printf("%8lu ", rmx.rmx_sendpipe); printf("%8lu ", rmx.rmx_ssthresh); printf("%8lu ", 0UL); printf("%8lu ", rmx.rmx_mtu); printf("%8lu ", rmx.rmx_weight); if (rmx.rmx_expire > 0) clock_gettime(CLOCK_REALTIME_FAST, &ts); else ts.tv_sec = 0; printf("%8ld \n", (long)(rmx.rmx_expire - ts.tv_sec)); } static void print_prefix(struct nl_helper *h, char *buf, int bufsize, struct sockaddr *sa, int plen) { int sz = 0; if (sa == NULL) { snprintf(buf, bufsize, ""); return; } switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *)sa; char abuf[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf)); sz = snprintf(buf, bufsize, "%s", abuf); break; } case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; char abuf[INET6_ADDRSTRLEN]; char *ifname = NULL; inet_ntop(AF_INET6, &sin6->sin6_addr, abuf, sizeof(abuf)); if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { struct snl_parsed_link_simple link = {}; if (sin6->sin6_scope_id != 0) { get_ifdata(h, sin6->sin6_scope_id, &link); ifname = link.ifla_ifname; } } if (ifname == NULL) sz = snprintf(buf, bufsize, "%s", abuf); else sz = snprintf(buf, bufsize, "%s%%%s", abuf, ifname); break; } default: snprintf(buf, bufsize, "unknown_af#%d", sa->sa_family); plen = -1; } if (plen >= 0) snprintf(buf + sz, bufsize - sz, "/%d", plen); } static int print_line_prefix(struct nlmsghdr *hdr, struct snl_msg_info *cinfo, const char *cmd, const char *name) { struct timespec tp; struct tm tm; char buf[32]; clock_gettime(CLOCK_REALTIME, &tp); localtime_r(&tp.tv_sec, &tm); strftime(buf, sizeof(buf), "%T", &tm); int len = printf("%s.%03ld PID %4u %s %s ", buf, tp.tv_nsec / 1000000, cinfo->process_id, cmd, name); return (len); } static const char * get_action_name(struct nlmsghdr *hdr, int new_cmd) { if (hdr->nlmsg_type == new_cmd) { //return ((hdr->nlmsg_flags & NLM_F_REPLACE) ? "replace" : "add"); return ("add/repl"); } else return ("delete"); } static void print_nlmsg_route_nhop(struct nl_helper *h, struct snl_parsed_route *r, struct rta_mpath_nh *nh, bool first) { // gw 10.0.0.1 ifp vtnet0 mtu 1500 table inet.0 if (nh->gw != NULL) { char gwbuf[128]; print_prefix(h, gwbuf, sizeof(gwbuf), nh->gw, -1); printf("gw %s ", gwbuf); } if (nh->ifindex != 0) { struct snl_parsed_link_simple link = {}; get_ifdata(h, nh->ifindex, &link); if (nh->rtax_mtu == 0) nh->rtax_mtu = link.ifla_mtu; printf("iface %s ", link.ifla_ifname); if (nh->rtax_mtu != 0) printf("mtu %d ", nh->rtax_mtu); } if (first) { switch (r->rtm_family) { case AF_INET: printf("table inet.%d", r->rta_table); break; case AF_INET6: printf("table inet6.%d", r->rta_table); break; } } printf("\n"); } static void print_nlmsg_route(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT }; struct snl_state *ss = &h->ss_cmd; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) return; // 20:19:41.333 add route 10.0.0.0/24 gw 10.0.0.1 ifp vtnet0 mtu 1500 table inet.0 const char *cmd = get_action_name(hdr, RTM_NEWROUTE); int len = print_line_prefix(hdr, cinfo, cmd, "route"); char buf[128]; print_prefix(h, buf, sizeof(buf), r.rta_dst, r.rtm_dst_len); len += strlen(buf) + 1; printf("%s ", buf); switch (r.rtm_type) { case RTN_BLACKHOLE: printf("blackhole\n"); return; case RTN_UNREACHABLE: printf("unreach(reject)\n"); return; case RTN_PROHIBIT: printf("prohibit(reject)\n"); return; } if (r.rta_multipath.num_nhops != 0) { bool first = true; memset(buf, ' ', sizeof(buf)); buf[len] = '\0'; for (uint32_t i = 0; i < r.rta_multipath.num_nhops; i++) { struct rta_mpath_nh *nh = r.rta_multipath.nhops[i]; if (!first) printf("%s", buf); print_nlmsg_route_nhop(h, &r, nh, first); first = false; } } else { struct rta_mpath_nh nh = { .gw = r.rta_gw, .ifindex = r.rta_oif, .rtax_mtu = r.rtax_mtu, }; print_nlmsg_route_nhop(h, &r, &nh, true); } } static const char *operstate[] = { "UNKNOWN", /* 0, IF_OPER_UNKNOWN */ "NOTPRESENT", /* 1, IF_OPER_NOTPRESENT */ "DOWN", /* 2, IF_OPER_DOWN */ "LLDOWN", /* 3, IF_OPER_LOWERLAYERDOWN */ "TESTING", /* 4, IF_OPER_TESTING */ "DORMANT", /* 5, IF_OPER_DORMANT */ "UP", /* 6, IF_OPER_UP */ }; static void print_nlmsg_link(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { struct snl_parsed_link l = {}; struct snl_state *ss = &h->ss_cmd; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser, &l)) return; // 20:19:41.333 add iface#3 vtnet0 admin UP oper UP mtu 1500 table inet.0 const char *cmd = get_action_name(hdr, RTM_NEWLINK); print_line_prefix(hdr, cinfo, cmd, "iface"); printf("iface#%u %s ", l.ifi_index, l.ifla_ifname); printf("admin %s ", (l.ifi_flags & IFF_UP) ? "UP" : "DOWN"); if (l.ifla_operstate < NL_ARRAY_LEN(operstate)) printf("oper %s ", operstate[l.ifla_operstate]); if (l.ifla_mtu > 0) printf("mtu %u ", l.ifla_mtu); printf("\n"); } static void print_nlmsg_addr(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { struct snl_parsed_addr attrs = {}; struct snl_state *ss = &h->ss_cmd; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_addr_parser, &attrs)) return; // add addr 192.168.1.1/24 iface vtnet0 const char *cmd = get_action_name(hdr, RTM_NEWADDR); print_line_prefix(hdr, cinfo, cmd, "addr"); char buf[128]; struct sockaddr *addr = attrs.ifa_local ? attrs.ifa_local : attrs.ifa_address; print_prefix(h, buf, sizeof(buf), addr, attrs.ifa_prefixlen); printf("%s ", buf); struct snl_parsed_link_simple link = {}; get_ifdata(h, attrs.ifa_index, &link); if (link.ifi_flags & IFF_POINTOPOINT) { char buf[64]; print_prefix(h, buf, sizeof(buf), attrs.ifa_address, -1); printf("-> %s ", buf); } printf("iface %s ", link.ifla_ifname); printf("\n"); } static const char *nudstate[] = { "INCOMPLETE", /* 0x01(0) */ "REACHABLE", /* 0x02(1) */ "STALE", /* 0x04(2) */ "DELAY", /* 0x08(3) */ "PROBE", /* 0x10(4) */ "FAILED", /* 0x20(5) */ }; #define NUD_INCOMPLETE 0x01 /* No lladdr, address resolution in progress */ #define NUD_REACHABLE 0x02 /* reachable & recently resolved */ #define NUD_STALE 0x04 /* has lladdr but it's stale */ #define NUD_DELAY 0x08 /* has lladdr, is stale, probes delayed */ #define NUD_PROBE 0x10 /* has lladdr, is stale, probes sent */ #define NUD_FAILED 0x20 /* unused */ static void print_nlmsg_neigh(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { struct snl_parsed_neigh attrs = {}; struct snl_state *ss = &h->ss_cmd; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_neigh_parser, &attrs)) return; // add addr 192.168.1.1 state %s lladdr %s iface vtnet0 const char *cmd = get_action_name(hdr, RTM_NEWNEIGH); print_line_prefix(hdr, cinfo, cmd, "neigh"); char buf[128]; print_prefix(h, buf, sizeof(buf), attrs.nda_dst, -1); printf("%s ", buf); struct snl_parsed_link_simple link = {}; get_ifdata(h, attrs.nda_ifindex, &link); for (unsigned int i = 0; i < NL_ARRAY_LEN(nudstate); i++) { if ((1 << i) & attrs.ndm_state) { printf("state %s ", nudstate[i]); break; } } if (attrs.nda_lladdr != NULL) { int if_type = link.ifi_type; if ((if_type == IFT_ETHER || if_type == IFT_L2VLAN || if_type == IFT_BRIDGE) && NLA_DATA_LEN(attrs.nda_lladdr) == ETHER_ADDR_LEN) { struct ether_addr *ll; ll = (struct ether_addr *)NLA_DATA(attrs.nda_lladdr); printf("lladdr %s ", ether_ntoa(ll)); } else { struct sockaddr_dl sdl = { .sdl_len = sizeof(sdl), .sdl_family = AF_LINK, .sdl_index = attrs.nda_ifindex, .sdl_type = if_type, .sdl_alen = NLA_DATA_LEN(attrs.nda_lladdr), }; if (sdl.sdl_alen < sizeof(sdl.sdl_data)) { void *ll = NLA_DATA(attrs.nda_lladdr); memcpy(sdl.sdl_data, ll, sdl.sdl_alen); printf("lladdr %s ", link_ntoa(&sdl)); } } } if (link.ifla_ifname != NULL) printf("iface %s ", link.ifla_ifname); printf("\n"); } static void print_nlmsg_generic(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { const char *cmd = get_action_name(hdr, 0); print_line_prefix(hdr, cinfo, cmd, "unknown message"); printf(" type %u\n", hdr->nlmsg_type); } static void print_nlmsg(struct nl_helper *h, struct nlmsghdr *hdr, struct snl_msg_info *cinfo) { switch (hdr->nlmsg_type) { case RTM_NEWLINK: case RTM_DELLINK: print_nlmsg_link(h, hdr, cinfo); break; case RTM_NEWADDR: case RTM_DELADDR: print_nlmsg_addr(h, hdr, cinfo); break; case RTM_NEWROUTE: case RTM_DELROUTE: print_nlmsg_route(h, hdr, cinfo); break; case RTM_NEWNEIGH: case RTM_DELNEIGH: print_nlmsg_neigh(h, hdr, cinfo); break; default: print_nlmsg_generic(h, hdr, cinfo); } snl_clear_lb(&h->ss_cmd); } void monitor_nl(int fib) { struct snl_state ss_event = {}; struct nl_helper h; nl_init_socket(&ss_event); nl_helper_init(&h); int groups[] = { RTNLGRP_LINK, RTNLGRP_NEIGH, RTNLGRP_NEXTHOP, #ifdef INET RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE, #endif #ifdef INET6 RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE, #endif }; int optval = 1; socklen_t optlen = sizeof(optval); setsockopt(ss_event.fd, SOL_NETLINK, NETLINK_MSG_INFO, &optval, optlen); for (unsigned int i = 0; i < NL_ARRAY_LEN(groups); i++) { int error; int optval = groups[i]; socklen_t optlen = sizeof(optval); error = setsockopt(ss_event.fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &optval, optlen); if (error != 0) warn("Unable to subscribe to group %d", optval); } struct snl_msg_info attrs = {}; struct nlmsghdr *hdr; while ((hdr = snl_read_message_dbg(&ss_event, &attrs)) != NULL) { print_nlmsg(&h, hdr, &attrs); snl_clear_lb(&h.ss_cmd); snl_clear_lb(&ss_event); } snl_free(&ss_event); nl_helper_free(&h); exit(0); } static void print_flushed_route(struct snl_parsed_route *r, struct sockaddr *gw) { struct sockaddr *sa = r->rta_dst; printf("%-20.20s ", r->rta_rtflags & RTF_HOST ? routename(sa) : netname(sa)); sa = gw; printf("%-20.20s ", routename(sa)); printf("-fib %-3d ", r->rta_table); printf("done\n"); } static int flushroute_one(struct nl_helper *h, struct snl_parsed_route *r) { struct snl_state *ss = &h->ss_cmd; struct snl_errmsg_data e = {}; struct snl_writer nw; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, NL_RTM_DELROUTE); struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); rtm->rtm_family = r->rtm_family; rtm->rtm_dst_len = r->rtm_dst_len; snl_add_msg_attr_u32(&nw, RTA_TABLE, r->rta_table); snl_add_msg_attr_ip(&nw, RTA_DST, r->rta_dst); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (ENOMEM); if (!snl_read_reply_code(ss, hdr->nlmsg_seq, &e)) { return (e.error); if (e.error == EPERM) errc(1, e.error, "RTM_DELROUTE failed:"); else warnc(e.error, "RTM_DELROUTE failed:"); return (true); }; if (verbose) { struct snl_msg_info attrs = {}; print_nlmsg(h, hdr, &attrs); } else { if (r->rta_multipath.num_nhops != 0) { for (uint32_t i = 0; i < r->rta_multipath.num_nhops; i++) { struct rta_mpath_nh *nh = r->rta_multipath.nhops[i]; print_flushed_route(r, nh->gw); } } else print_flushed_route(r, r->rta_gw); } return (0); } int flushroutes_fib_nl(int fib, int af) { struct snl_state ss = {}; struct snl_writer nw; struct nl_helper h = {}; nl_init_socket(&ss); snl_init_writer(&ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, NL_RTM_GETROUTE); hdr->nlmsg_flags |= NLM_F_DUMP; struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); rtm->rtm_family = af; snl_add_msg_attr_u32(&nw, RTA_TABLE, fib); - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { snl_free(&ss); return (EINVAL); } struct snl_errmsg_data e = {}; uint32_t nlm_seq = hdr->nlmsg_seq; nl_helper_init(&h); while ((hdr = snl_read_reply_multi(&ss, nlm_seq, &e)) != NULL) { struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT }; int error; if (!snl_parse_nlmsg(&ss, hdr, &snl_rtm_route_parser, &r)) continue; if (verbose) { struct snl_msg_info attrs = {}; print_nlmsg(&h, hdr, &attrs); } if (r.rta_table != (uint32_t)fib || r.rtm_family != af) continue; if ((r.rta_rtflags & RTF_GATEWAY) == 0) continue; if (debugonly) continue; if ((error = flushroute_one(&h, &r)) != 0) { if (error == EPERM) errc(1, error, "RTM_DELROUTE failed:"); else warnc(error, "RTM_DELROUTE failed:"); } snl_clear_lb(&h.ss_cmd); } snl_free(&ss); nl_helper_free(&h); return (e.error); } diff --git a/sys/netlink/netlink_snl_generic.h b/sys/netlink/netlink_snl_generic.h index 1324cf3da17a..0a2913c9155e 100644 --- a/sys/netlink/netlink_snl_generic.h +++ b/sys/netlink/netlink_snl_generic.h @@ -1,134 +1,134 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 Alexander V. Chernikov * * 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 _NETLINK_NETLINK_SNL_GENERIC_H_ #define _NETLINK_NETLINK_SNL_GENERIC_H_ #include #include #include /* Genetlink helpers */ static inline struct nlmsghdr * snl_create_genl_msg_request(struct snl_writer *nw, int genl_family, uint8_t genl_cmd) { assert(nw->hdr == NULL); struct nlmsghdr *hdr = snl_reserve_msg_object(nw, struct nlmsghdr); hdr->nlmsg_type = genl_family; hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nw->hdr = hdr; struct genlmsghdr *ghdr = snl_reserve_msg_object(nw, struct genlmsghdr); ghdr->cmd = genl_cmd; return (hdr); } static struct snl_field_parser snl_fp_genl[] = {}; #define SNL_DECLARE_GENL_PARSER(_name, _np) SNL_DECLARE_PARSER(_name,\ struct genlmsghdr, snl_fp_genl, _np) struct snl_genl_ctrl_mcast_group { uint32_t mcast_grp_id; char *mcast_grp_name; }; struct snl_genl_ctrl_mcast_groups { uint32_t num_groups; struct snl_genl_ctrl_mcast_group **groups; }; #define _OUT(_field) offsetof(struct snl_genl_ctrl_mcast_group, _field) static struct snl_attr_parser _nla_p_getmc[] = { { .type = CTRL_ATTR_MCAST_GRP_NAME, .off = _OUT(mcast_grp_name), .cb = snl_attr_get_string }, { .type = CTRL_ATTR_MCAST_GRP_ID, .off = _OUT(mcast_grp_id), .cb = snl_attr_get_uint32 }, }; #undef _OUT SNL_DECLARE_ATTR_PARSER_EXT(_genl_ctrl_mc_parser, sizeof(struct snl_genl_ctrl_mcast_group), _nla_p_getmc, NULL); struct _getfamily_attrs { uint16_t family_id; char *family_name; struct snl_genl_ctrl_mcast_groups mcast_groups; }; #define _IN(_field) offsetof(struct genlmsghdr, _field) #define _OUT(_field) offsetof(struct _getfamily_attrs, _field) static struct snl_attr_parser _nla_p_getfam[] = { { .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(family_id), .cb = snl_attr_get_uint16 }, { .type = CTRL_ATTR_FAMILY_NAME, .off = _OUT(family_name), .cb = snl_attr_get_string }, { .type = CTRL_ATTR_MCAST_GROUPS, .off = _OUT(mcast_groups), .cb = snl_attr_get_parray, .arg = &_genl_ctrl_mc_parser, }, }; #undef _IN #undef _OUT SNL_DECLARE_GENL_PARSER(_genl_ctrl_getfam_parser, _nla_p_getfam); static bool snl_get_genl_family_info(struct snl_state *ss, const char *family_name, struct _getfamily_attrs *attrs) { struct snl_writer nw; struct nlmsghdr *hdr; memset(attrs, 0, sizeof(*attrs)); snl_init_writer(ss, &nw); hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, family_name); - if (snl_finalize_msg(&nw) == NULL || !snl_send_message(ss, hdr)) + if ((hdr = snl_finalize_msg(&nw)) == NULL || !snl_send_message(ss, hdr)) return (false); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) { if (snl_parse_nlmsg(ss, hdr, &_genl_ctrl_getfam_parser, attrs)) return (true); } return (false); } static inline uint16_t snl_get_genl_family(struct snl_state *ss, const char *family_name) { struct _getfamily_attrs attrs = {}; snl_get_genl_family_info(ss, family_name, &attrs); return (attrs.family_id); } static const struct snl_hdr_parser *snl_all_genl_parsers[] = { &_genl_ctrl_getfam_parser, &_genl_ctrl_mc_parser, }; #endif diff --git a/tests/sys/netlink/test_snl_generic.c b/tests/sys/netlink/test_snl_generic.c index f3c11daf19e1..d84d3f88f487 100644 --- a/tests/sys/netlink/test_snl_generic.c +++ b/tests/sys/netlink/test_snl_generic.c @@ -1,116 +1,116 @@ #include #include #include #include #include #include #include "netlink/netlink_snl.h" #include "netlink/netlink_snl_generic.h" #include static void require_netlink(void) { if (modfind("netlink") == -1) atf_tc_skip("netlink module not loaded"); } ATF_TC(snl_verify_genl_parsers); ATF_TC_HEAD(snl_verify_genl_parsers, tc) { atf_tc_set_md_var(tc, "descr", "Tests snl(3) generic parsers are correct"); } ATF_TC_BODY(snl_verify_genl_parsers, tc) { SNL_VERIFY_PARSERS(snl_all_genl_parsers); } ATF_TC(test_snl_get_genl_family_success); ATF_TC_HEAD(test_snl_get_genl_family_success, tc) { atf_tc_set_md_var(tc, "descr", "Tests successfull resolution of the 'nlctrl' family"); } ATF_TC_BODY(test_snl_get_genl_family_success, tc) { struct snl_state ss; require_netlink(); if (!snl_init(&ss, NETLINK_GENERIC)) atf_tc_fail("snl_init() failed"); ATF_CHECK_EQ(snl_get_genl_family(&ss, "nlctrl"), GENL_ID_CTRL); } ATF_TC(test_snl_get_genl_family_failure); ATF_TC_HEAD(test_snl_get_genl_family_failure, tc) { atf_tc_set_md_var(tc, "descr", "Tests unsuccessfull resolution of 'no-such-family' family"); } ATF_TC_BODY(test_snl_get_genl_family_failure, tc) { struct snl_state ss; require_netlink(); if (!snl_init(&ss, NETLINK_GENERIC)) atf_tc_fail("snl_init() failed"); ATF_CHECK_EQ(snl_get_genl_family(&ss, "no-such-family"), 0); } ATF_TC(test_snl_get_genl_family_groups); ATF_TC_HEAD(test_snl_get_genl_family_groups, tc) { atf_tc_set_md_var(tc, "descr", "Tests getting 'nlctrl' groups"); } ATF_TC_BODY(test_snl_get_genl_family_groups, tc) { struct snl_state ss; struct snl_writer nw; struct nlmsghdr *hdr; require_netlink(); if (!snl_init(&ss, NETLINK_GENERIC)) atf_tc_fail("snl_init() failed"); snl_init_writer(&ss, &nw); hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, "nlctrl"); - snl_finalize_msg(&nw); + hdr = snl_finalize_msg(&nw); snl_send_message(&ss, hdr); hdr = snl_read_reply(&ss, hdr->nlmsg_seq); ATF_CHECK(hdr != NULL); ATF_CHECK(hdr->nlmsg_type != NLMSG_ERROR); struct _getfamily_attrs attrs = {}; ATF_CHECK(snl_parse_nlmsg(&ss, hdr, &_genl_ctrl_getfam_parser, &attrs)); ATF_CHECK_EQ(attrs.mcast_groups.num_groups, 1); struct snl_genl_ctrl_mcast_group *group = attrs.mcast_groups.groups[0]; ATF_CHECK(group->mcast_grp_id > 0); ATF_CHECK(!strcmp(group->mcast_grp_name, "notify")); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, snl_verify_genl_parsers); ATF_TP_ADD_TC(tp, test_snl_get_genl_family_success); ATF_TP_ADD_TC(tp, test_snl_get_genl_family_failure); ATF_TP_ADD_TC(tp, test_snl_get_genl_family_groups); return (atf_no_error()); } diff --git a/usr.sbin/arp/arp_netlink.c b/usr.sbin/arp/arp_netlink.c index 40b5367f330d..d78f380af04b 100644 --- a/usr.sbin/arp/arp_netlink.c +++ b/usr.sbin/arp/arp_netlink.c @@ -1,442 +1,442 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arp.h" #define RTF_ANNOUNCE RTF_PROTO2 static void nl_init_socket(struct snl_state *ss) { if (snl_init(ss, NETLINK_ROUTE)) return; if (modfind("netlink") == -1 && errno == ENOENT) { /* Try to load */ if (kldload("netlink") == -1) err(1, "netlink is not loaded and load attempt failed"); if (snl_init(ss, NETLINK_ROUTE)) return; } err(1, "unable to open netlink socket"); } static bool get_link_info(struct snl_state *ss, uint32_t ifindex, struct snl_parsed_link_simple *link) { struct snl_writer nw; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); struct ifinfomsg *ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg); if (ifmsg != NULL) ifmsg->ifi_index = ifindex; - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (false); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr == NULL || hdr->nlmsg_type != RTM_NEWLINK) return (false); if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link)) return (false); return (true); } static bool has_l2(struct snl_state *ss, uint32_t ifindex) { struct snl_parsed_link_simple link = {}; if (!get_link_info(ss, ifindex, &link)) return (false); return (valid_type(link.ifi_type) != 0); } static uint32_t get_myfib(void) { uint32_t fibnum = 0; size_t len = sizeof(fibnum); sysctlbyname("net.my_fibnum", (void *)&fibnum, &len, NULL, 0); return (fibnum); } static int guess_ifindex(struct snl_state *ss, uint32_t fibnum, struct in_addr addr) { struct snl_writer nw; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETROUTE); struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); rtm->rtm_family = AF_INET; struct sockaddr_in dst = { .sin_family = AF_INET, .sin_addr = addr }; snl_add_msg_attr_ip(&nw, RTA_DST, (struct sockaddr *)&dst); snl_add_msg_attr_u32(&nw, RTA_TABLE, fibnum); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (0); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr->nlmsg_type != NL_RTM_NEWROUTE) { /* No route found, unable to guess ifindex */ return (0); } struct snl_parsed_route r = {}; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) return (0); if (r.rta_multipath.num_nhops > 0 || (r.rta_rtflags & RTF_GATEWAY)) return (0); /* Check if the interface is of supported type */ if (has_l2(ss, r.rta_oif)) return (r.rta_oif); /* Check the case when we matched the loopback route for P2P */ snl_init_writer(ss, &nw); hdr = snl_create_msg_request(&nw, RTM_GETNEXTHOP); snl_reserve_msg_object(&nw, struct nhmsg); int off = snl_add_msg_attr_nested(&nw, NHA_FREEBSD); snl_add_msg_attr_u32(&nw, NHAF_KID, r.rta_knh_id); snl_add_msg_attr_u8(&nw, NHAF_FAMILY, AF_INET); snl_add_msg_attr_u32(&nw, NHAF_TABLE, fibnum); snl_end_attr_nested(&nw, off); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (0); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr->nlmsg_type != NL_RTM_NEWNEXTHOP) { /* No nexthop found, unable to guess ifindex */ return (0); } struct snl_parsed_nhop nh = {}; if (!snl_parse_nlmsg(ss, hdr, &snl_nhmsg_parser, &nh)) return (0); return (nh.nhaf_aif); } static uint32_t fix_ifindex(struct snl_state *ss, uint32_t ifindex, struct in_addr addr) { if (ifindex == 0) ifindex = guess_ifindex(ss, get_myfib(), addr); return (ifindex); } static void print_entry(struct snl_parsed_neigh *neigh, struct snl_parsed_link_simple *link) { const char *host; struct hostent *hp; struct sockaddr_in *addr = (struct sockaddr_in *)neigh->nda_dst; xo_open_instance("arp-cache"); if (!opts.nflag) hp = gethostbyaddr((caddr_t)&(addr->sin_addr), sizeof(addr->sin_addr), AF_INET); else hp = 0; if (hp) host = hp->h_name; else { host = "?"; if (h_errno == TRY_AGAIN) opts.nflag = true; } xo_emit("{:hostname/%s} ({:ip-address/%s}) at ", host, inet_ntoa(addr->sin_addr)); if (neigh->nda_lladdr != NULL) { struct sockaddr_dl sdl = { .sdl_family = AF_LINK, .sdl_type = link->ifi_type, .sdl_len = sizeof(struct sockaddr_dl), .sdl_alen = NLA_DATA_LEN(neigh->nda_lladdr), }; memcpy(sdl.sdl_data, NLA_DATA(neigh->nda_lladdr), sdl.sdl_alen); if ((sdl.sdl_type == IFT_ETHER || sdl.sdl_type == IFT_L2VLAN || sdl.sdl_type == IFT_BRIDGE) && sdl.sdl_alen == ETHER_ADDR_LEN) xo_emit("{:mac-address/%s}", ether_ntoa((struct ether_addr *)LLADDR(&sdl))); else { xo_emit("{:mac-address/%s}", link_ntoa(&sdl)); } } else xo_emit("{d:/(incomplete)}{en:incomplete/true}"); xo_emit(" on {:interface/%s}", link->ifla_ifname); if (neigh->ndaf_next_ts == 0) xo_emit("{d:/ permanent}{en:permanent/true}"); else { time_t expire_time; struct timeval now; gettimeofday(&now, 0); if ((expire_time = neigh->ndaf_next_ts - now.tv_sec) > 0) xo_emit(" expires in {:expires/%d} seconds", (int)expire_time); else xo_emit("{d:/ expired}{en:expired/true}"); } if (neigh->ndm_flags & NTF_PROXY) xo_emit("{d:/ published}{en:published/true}"); switch(link->ifi_type) { case IFT_ETHER: xo_emit(" [{:type/ethernet}]"); break; case IFT_FDDI: xo_emit(" [{:type/fddi}]"); break; case IFT_ATM: xo_emit(" [{:type/atm}]"); break; case IFT_L2VLAN: xo_emit(" [{:type/vlan}]"); break; case IFT_IEEE1394: xo_emit(" [{:type/firewire}]"); break; case IFT_BRIDGE: xo_emit(" [{:type/bridge}]"); break; case IFT_INFINIBAND: xo_emit(" [{:type/infiniband}]"); break; default: break; } xo_emit("\n"); xo_close_instance("arp-cache"); } int print_entries_nl(uint32_t ifindex, struct in_addr addr) { struct snl_state ss_req = {}, ss_cmd = {}; struct snl_parsed_link_simple link = {}; struct snl_writer nw; nl_init_socket(&ss_req); snl_init_writer(&ss_req, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETNEIGH); struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { ndmsg->ndm_family = AF_INET; /* let kernel filter results by interface if provided */ ndmsg->ndm_ifindex = ifindex; } - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss_req, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss_req, hdr)) { snl_free(&ss_req); return (0); } uint32_t nlmsg_seq = hdr->nlmsg_seq; struct snl_errmsg_data e = {}; int count = 0; nl_init_socket(&ss_cmd); while ((hdr = snl_read_reply_multi(&ss_req, nlmsg_seq, &e)) != NULL) { struct snl_parsed_neigh neigh = {}; struct sockaddr_in *neighaddr; if (!snl_parse_nlmsg(&ss_req, hdr, &snl_rtm_neigh_parser, &neigh)) continue; if (neigh.nda_ifindex != link.ifi_index) { snl_clear_lb(&ss_cmd); memset(&link, 0, sizeof(link)); if (!get_link_info(&ss_cmd, neigh.nda_ifindex, &link)) continue; } /* filter results based on host if provided */ neighaddr = (struct sockaddr_in *)neigh.nda_dst; if (addr.s_addr && (addr.s_addr != neighaddr->sin_addr.s_addr)) continue; print_entry(&neigh, &link); count++; snl_clear_lb(&ss_req); } snl_free(&ss_req); snl_free(&ss_cmd); return (count); } int delete_nl(uint32_t ifindex, char *host) { struct snl_state ss = {}; struct snl_writer nw; struct sockaddr_in *dst; dst = getaddr(host); if (dst == NULL) return (1); nl_init_socket(&ss); ifindex = fix_ifindex(&ss, ifindex, dst->sin_addr); if (ifindex == 0) { xo_warnx("delete: cannot locate %s", host); snl_free(&ss); return (0); } snl_init_writer(&ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_DELNEIGH); struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { ndmsg->ndm_family = AF_INET; ndmsg->ndm_ifindex = ifindex; } snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)dst); - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { snl_free(&ss); return (1); } struct snl_errmsg_data e = {}; snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); if (e.error != 0) { if (e.error_str != NULL) xo_warnx("delete %s: %s (%s)", host, strerror(e.error), e.error_str); else xo_warnx("delete %s: %s", host, strerror(e.error)); } else printf("%s (%s) deleted\n", host, inet_ntoa(dst->sin_addr)); snl_free(&ss); return (e.error != 0); } int set_nl(uint32_t ifindex, struct sockaddr_in *dst, struct sockaddr_dl *sdl, char *host) { struct snl_state ss = {}; struct snl_writer nw; nl_init_socket(&ss); ifindex = fix_ifindex(&ss, ifindex, dst->sin_addr); if (ifindex == 0) { xo_warnx("delete: cannot locate %s", host); snl_free(&ss); return (0); } if (opts.expire_time != 0) opts.flags &= ~RTF_STATIC; snl_init_writer(&ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_NEWNEIGH); hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { uint8_t nl_flags = 0; ndmsg->ndm_family = AF_INET; ndmsg->ndm_ifindex = ifindex; ndmsg->ndm_state = (opts.flags & RTF_STATIC) ? NUD_PERMANENT : NUD_NONE; if (opts.flags & RTF_ANNOUNCE) nl_flags |= NTF_PROXY; if (opts.flags & RTF_STATIC) nl_flags |= NTF_STICKY; ndmsg->ndm_flags = nl_flags; } snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)dst); snl_add_msg_attr(&nw, NDA_LLADDR, sdl->sdl_alen, LLADDR(sdl)); if (opts.expire_time != 0) { struct timeval now; gettimeofday(&now, 0); int off = snl_add_msg_attr_nested(&nw, NDA_FREEBSD); snl_add_msg_attr_u32(&nw, NDAF_NEXT_STATE_TS, now.tv_sec + opts.expire_time); snl_end_attr_nested(&nw, off); } - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { snl_free(&ss); return (1); } struct snl_errmsg_data e = {}; snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); if (e.error != 0) { if (e.error_str != NULL) xo_warnx("set: %s: %s (%s)", host, strerror(e.error), e.error_str); else xo_warnx("set %s: %s", host, strerror(e.error)); } snl_free(&ss); return (e.error != 0); } diff --git a/usr.sbin/ndp/ndp_netlink.c b/usr.sbin/ndp/ndp_netlink.c index 954d16995b5a..79bdec2356d0 100644 --- a/usr.sbin/ndp/ndp_netlink.c +++ b/usr.sbin/ndp/ndp_netlink.c @@ -1,513 +1,513 @@ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ndp.h" #define RTF_ANNOUNCE RTF_PROTO2 static void nl_init_socket(struct snl_state *ss) { if (snl_init(ss, NETLINK_ROUTE)) return; if (modfind("netlink") == -1 && errno == ENOENT) { /* Try to load */ if (kldload("netlink") == -1) err(1, "netlink is not loaded and load attempt failed"); if (snl_init(ss, NETLINK_ROUTE)) return; } err(1, "unable to open netlink socket"); } static bool get_link_info(struct snl_state *ss, uint32_t ifindex, struct snl_parsed_link_simple *link) { struct snl_writer nw; snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK); struct ifinfomsg *ifmsg = snl_reserve_msg_object(&nw, struct ifinfomsg); if (ifmsg != NULL) ifmsg->ifi_index = ifindex; - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (false); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr == NULL || hdr->nlmsg_type != RTM_NEWLINK) return (false); if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_link_parser_simple, link)) return (false); return (true); } static bool has_l2(struct snl_state *ss, uint32_t ifindex) { struct snl_parsed_link_simple link = {}; if (!get_link_info(ss, ifindex, &link)) return (false); return (valid_type(link.ifi_type) != 0); } static uint32_t get_myfib(void) { uint32_t fibnum = 0; size_t len = sizeof(fibnum); sysctlbyname("net.my_fibnum", (void *)&fibnum, &len, NULL, 0); return (fibnum); } static void ip6_writemask(struct in6_addr *addr6, uint8_t mask) { uint32_t *cp; for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32) *cp++ = 0xFFFFFFFF; if (mask > 0) *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0); } #define s6_addr32 __u6_addr.__u6_addr32 #define IN6_MASK_ADDR(a, m) do { \ (a)->s6_addr32[0] &= (m)->s6_addr32[0]; \ (a)->s6_addr32[1] &= (m)->s6_addr32[1]; \ (a)->s6_addr32[2] &= (m)->s6_addr32[2]; \ (a)->s6_addr32[3] &= (m)->s6_addr32[3]; \ } while (0) static int guess_ifindex(struct snl_state *ss, uint32_t fibnum, const struct sockaddr_in6 *dst) { struct snl_writer nw; if (IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr)) return (dst->sin6_scope_id); else if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr)) return (0); snl_init_writer(ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETROUTE); struct rtmsg *rtm = snl_reserve_msg_object(&nw, struct rtmsg); rtm->rtm_family = AF_INET6; snl_add_msg_attr_ip(&nw, RTA_DST, (struct sockaddr *)dst); snl_add_msg_attr_u32(&nw, RTA_TABLE, fibnum); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (0); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr->nlmsg_type != NL_RTM_NEWROUTE) { /* No route found, unable to guess ifindex */ return (0); } struct snl_parsed_route r = {}; if (!snl_parse_nlmsg(ss, hdr, &snl_rtm_route_parser, &r)) return (0); if (r.rta_multipath.num_nhops > 0 || (r.rta_rtflags & RTF_GATEWAY)) return (0); /* Check if the interface is of supported type */ if (has_l2(ss, r.rta_oif)) return (r.rta_oif); /* Check the case when we matched the loopback route for P2P */ snl_init_writer(ss, &nw); hdr = snl_create_msg_request(&nw, RTM_GETNEXTHOP); snl_reserve_msg_object(&nw, struct nhmsg); int off = snl_add_msg_attr_nested(&nw, NHA_FREEBSD); snl_add_msg_attr_u32(&nw, NHAF_KID, r.rta_knh_id); snl_add_msg_attr_u8(&nw, NHAF_FAMILY, AF_INET); snl_add_msg_attr_u32(&nw, NHAF_TABLE, fibnum); snl_end_attr_nested(&nw, off); - if (!snl_finalize_msg(&nw) || !snl_send_message(ss, hdr)) + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr)) return (0); hdr = snl_read_reply(ss, hdr->nlmsg_seq); if (hdr->nlmsg_type != NL_RTM_NEWNEXTHOP) { /* No nexthop found, unable to guess ifindex */ return (0); } struct snl_parsed_nhop nh = {}; if (!snl_parse_nlmsg(ss, hdr, &snl_nhmsg_parser, &nh)) return (0); return (nh.nhaf_aif); } static uint32_t fix_ifindex(struct snl_state *ss, uint32_t ifindex, const struct sockaddr_in6 *sa) { if (ifindex == 0) ifindex = guess_ifindex(ss, get_myfib(), sa); return (ifindex); } static void print_entry(struct snl_parsed_neigh *neigh, struct snl_parsed_link_simple *link) { struct timeval now; char host_buf[NI_MAXHOST]; int addrwidth; int llwidth; int ifwidth; char *ifname; getnameinfo(neigh->nda_dst, sizeof(struct sockaddr_in6), host_buf, sizeof(host_buf), NULL, 0, (opts.nflag ? NI_NUMERICHOST : 0)); gettimeofday(&now, 0); if (opts.tflag) ts_print(&now); struct sockaddr_dl sdl = { .sdl_family = AF_LINK, .sdl_type = link->ifi_type, .sdl_len = sizeof(struct sockaddr_dl), }; if (neigh->nda_lladdr) { sdl.sdl_alen = NLA_DATA_LEN(neigh->nda_lladdr), memcpy(sdl.sdl_data, NLA_DATA(neigh->nda_lladdr), sdl.sdl_alen); } addrwidth = strlen(host_buf); if (addrwidth < W_ADDR) addrwidth = W_ADDR; llwidth = strlen(ether_str(&sdl)); if (W_ADDR + W_LL - addrwidth > llwidth) llwidth = W_ADDR + W_LL - addrwidth; ifname = link->ifla_ifname; ifwidth = strlen(ifname); if (W_ADDR + W_LL + W_IF - addrwidth - llwidth > ifwidth) ifwidth = W_ADDR + W_LL + W_IF - addrwidth - llwidth; xo_open_instance("neighbor-cache"); /* Compose format string for libxo, as it doesn't support *.* */ char xobuf[200]; snprintf(xobuf, sizeof(xobuf), "{:address/%%-%d.%ds/%%s} {:mac-address/%%-%d.%ds/%%s} {:interface/%%%d.%ds/%%s}", addrwidth, addrwidth, llwidth, llwidth, ifwidth, ifwidth); xo_emit(xobuf, host_buf, ether_str(&sdl), ifname); /* Print neighbor discovery specific information */ time_t expire = (time_t)neigh->ndaf_next_ts; int expire_in = expire - now.tv_sec; if (expire > now.tv_sec) xo_emit("{d:/ %-9.9s}{e:expires_sec/%d}", sec2str(expire_in), expire_in); else if (expire == 0) xo_emit("{d:/ %-9.9s}{en:permanent/true}", "permanent"); else xo_emit("{d:/ %-9.9s}{e:expires_sec/%d}", "expired", expire_in); const char *lle_state = ""; switch (neigh->ndm_state) { case NUD_INCOMPLETE: lle_state = "I"; break; case NUD_REACHABLE: lle_state = "R"; break; case NUD_STALE: lle_state = "S"; break; case NUD_DELAY: lle_state = "D"; break; case NUD_PROBE: lle_state = "P"; break; case NUD_FAILED: lle_state = "F"; break; default: lle_state = "N"; break; } xo_emit(" {:neighbor-state/%s}", lle_state); bool isrouter = neigh->ndm_flags & NTF_ROUTER; /* * other flags. R: router, P: proxy, W: ?? */ char flgbuf[8]; snprintf(flgbuf, sizeof(flgbuf), "%s%s", isrouter ? "R" : "", (neigh->ndm_flags & NTF_PROXY) ? "p" : ""); xo_emit(" {:nd-flags/%s}", flgbuf); if (neigh->nda_probes != 0) xo_emit("{u:/ %d}", neigh->nda_probes); xo_emit("\n"); xo_close_instance("neighbor-cache"); } int print_entries_nl(uint32_t ifindex, struct sockaddr_in6 *addr, bool cflag) { struct snl_state ss_req = {}, ss_cmd = {}; struct snl_parsed_link_simple link = {}; struct snl_writer nw; nl_init_socket(&ss_req); snl_init_writer(&ss_req, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETNEIGH); struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { ndmsg->ndm_family = AF_INET6; ndmsg->ndm_ifindex = ifindex; } - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss_req, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss_req, hdr)) { snl_free(&ss_req); return (0); } uint32_t nlmsg_seq = hdr->nlmsg_seq; struct snl_errmsg_data e = {}; int count = 0; nl_init_socket(&ss_cmd); /* Print header */ if (!opts.tflag && !cflag) { char xobuf[200]; snprintf(xobuf, sizeof(xobuf), "{T:/%%-%d.%ds} {T:/%%-%d.%ds} {T:/%%%d.%ds} {T:/%%-9.9s} {T:%%1s} {T:%%5s}\n", W_ADDR, W_ADDR, W_LL, W_LL, W_IF, W_IF); xo_emit(xobuf, "Neighbor", "Linklayer Address", "Netif", "Expire", "S", "Flags"); } xo_open_list("neighbor-cache"); while ((hdr = snl_read_reply_multi(&ss_req, nlmsg_seq, &e)) != NULL) { struct snl_parsed_neigh neigh = {}; if (!snl_parse_nlmsg(&ss_req, hdr, &snl_rtm_neigh_parser, &neigh)) continue; if (neigh.nda_ifindex != link.ifi_index) { snl_clear_lb(&ss_cmd); memset(&link, 0, sizeof(link)); if (!get_link_info(&ss_cmd, neigh.nda_ifindex, &link)) continue; } /* TODO: embed LL in the parser */ struct sockaddr_in6 *dst = (struct sockaddr_in6 *)neigh.nda_dst; if (IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr)) dst->sin6_scope_id = neigh.nda_ifindex; if (addr != NULL) { if (IN6_ARE_ADDR_EQUAL(&addr->sin6_addr, &dst->sin6_addr) == 0 || addr->sin6_scope_id != dst->sin6_scope_id) continue; } print_entry(&neigh, &link); if (cflag) { char dst_str[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &dst->sin6_addr, dst_str, sizeof(dst_str)); delete_nl(neigh.nda_ifindex, dst_str); } count++; snl_clear_lb(&ss_req); } xo_close_list("neighbor-cache"); snl_free(&ss_req); snl_free(&ss_cmd); return (count); } int delete_nl(uint32_t ifindex, char *host) { struct snl_state ss = {}; struct snl_writer nw; struct sockaddr_in6 dst; int gai_error = getaddr(host, &dst); if (gai_error) { xo_warnx("%s: %s", host, gai_strerror(gai_error)); return 1; } nl_init_socket(&ss); ifindex = fix_ifindex(&ss, ifindex, &dst); if (ifindex == 0) { xo_warnx("delete: cannot locate %s", host); snl_free(&ss); return (0); } snl_init_writer(&ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_DELNEIGH); struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { ndmsg->ndm_family = AF_INET6; ndmsg->ndm_ifindex = ifindex; } snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)&dst); - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { snl_free(&ss); return (1); } struct snl_errmsg_data e = {}; snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); if (e.error != 0) { if (e.error_str != NULL) xo_warnx("delete %s: %s (%s)", host, strerror(e.error), e.error_str); else xo_warnx("delete %s: %s", host, strerror(e.error)); } else { char host_buf[NI_MAXHOST]; char ifix_buf[IFNAMSIZ]; getnameinfo((struct sockaddr *)&dst, dst.sin6_len, host_buf, sizeof(host_buf), NULL, 0, (opts.nflag ? NI_NUMERICHOST : 0)); char *ifname = if_indextoname(ifindex, ifix_buf); if (ifname == NULL) { strlcpy(ifix_buf, "?", sizeof(ifix_buf)); ifname = ifix_buf; } char abuf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &dst.sin6_addr, abuf, sizeof(abuf)); xo_open_instance("neighbor-cache"); xo_emit("{:hostname/%s}{d:/ (%s) deleted\n}", host, host_buf); xo_emit("{e:address/%s}{e:interface/%s}", abuf, ifname); xo_close_instance("neighbor-cache"); } snl_free(&ss); return (e.error != 0); } int set_nl(uint32_t ifindex, struct sockaddr_in6 *dst, struct sockaddr_dl *sdl, char *host) { struct snl_state ss = {}; struct snl_writer nw; nl_init_socket(&ss); ifindex = fix_ifindex(&ss, ifindex, dst); if (ifindex == 0) { xo_warnx("delete: cannot locate %s", host); snl_free(&ss); return (0); } snl_init_writer(&ss, &nw); struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_NEWNEIGH); hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; struct ndmsg *ndmsg = snl_reserve_msg_object(&nw, struct ndmsg); if (ndmsg != NULL) { uint8_t nl_flags = NTF_STICKY; ndmsg->ndm_family = AF_INET6; ndmsg->ndm_ifindex = ifindex; ndmsg->ndm_state = NUD_PERMANENT; if (opts.flags & RTF_ANNOUNCE) nl_flags |= NTF_PROXY; ndmsg->ndm_flags = nl_flags; } snl_add_msg_attr_ip(&nw, NDA_DST, (struct sockaddr *)dst); snl_add_msg_attr(&nw, NDA_LLADDR, sdl->sdl_alen, LLADDR(sdl)); - if (!snl_finalize_msg(&nw) || !snl_send_message(&ss, hdr)) { + if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(&ss, hdr)) { snl_free(&ss); return (1); } struct snl_errmsg_data e = {}; snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); if (e.error != 0) { if (e.error_str != NULL) xo_warnx("set: %s: %s (%s)", host, strerror(e.error), e.error_str); else xo_warnx("set %s: %s", host, strerror(e.error)); } snl_free(&ss); return (e.error != 0); }