/*- * Copyright (c) 2010-2020 The NetBSD Foundation, Inc. * All rights reserved. * * This material is based upon work partially supported by The * NetBSD Foundation under a contract with Mindaugas Rasiukevicius. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.46.2.3 2020/06/20 15:46:48 martin Exp $"); #include #include #include #if !defined(_NPF_STANDALONE) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define _NPF_PRIVATE #include "npf.h" struct nl_rule { nvlist_t * rule_dict; }; struct nl_rproc { nvlist_t * rproc_dict; }; struct nl_table { nvlist_t * table_dict; }; struct nl_alg { nvlist_t * alg_dict; }; struct nl_ext { nvlist_t * ext_dict; }; struct nl_config { nvlist_t * ncf_dict; /* Temporary rule list. */ nvlist_t ** ncf_rule_list; unsigned ncf_rule_count; /* Iterators. */ unsigned ncf_reduce[16]; unsigned ncf_nlevel; nl_rule_t ncf_cur_rule; nl_table_t ncf_cur_table; nl_rproc_t ncf_cur_rproc; }; /* * Various helper routines. */ static bool _npf_add_addr(nvlist_t *nvl, const char *name, int af, const npf_addr_t *addr) { size_t sz; if (af == AF_INET) { sz = sizeof(struct in_addr); } else if (af == AF_INET6) { sz = sizeof(struct in6_addr); } else { return false; } nvlist_add_binary(nvl, name, addr, sz); return nvlist_error(nvl) == 0; } static unsigned _npf_get_addr(const nvlist_t *nvl, const char *name, npf_addr_t *addr) { const void *d; size_t sz = 0; d = nvlist_get_binary(nvl, name, &sz); switch (sz) { case sizeof(struct in_addr): case sizeof(struct in6_addr): memcpy(addr, d, sz); return (unsigned)sz; } return 0; } static bool _npf_dataset_lookup(const nvlist_t *dict, const char *dataset, const char *key, const char *name) { const nvlist_t * const *items; size_t nitems; if (!nvlist_exists_nvlist_array(dict, dataset)) { return false; } items = nvlist_get_nvlist_array(dict, dataset, &nitems); for (unsigned i = 0; i < nitems; i++) { const char *item_name; item_name = dnvlist_get_string(items[i], key, NULL); if (item_name && strcmp(item_name, name) == 0) { return true; } } return false; } static const nvlist_t * _npf_dataset_getelement(nvlist_t *dict, const char *dataset, unsigned i) { const nvlist_t * const *items; size_t nitems; if (!nvlist_exists_nvlist_array(dict, dataset)) { return NULL; } items = nvlist_get_nvlist_array(dict, dataset, &nitems); if (i < nitems) { return items[i]; } return NULL; } /* * _npf_rules_process: transform the ruleset representing nested rules * with sublists into a single array with skip-to marks. */ static void _npf_rules_process(nl_config_t *ncf, nvlist_t *dict, const char *key) { nvlist_t **items; size_t nitems; if (!nvlist_exists_nvlist_array(dict, key)) { return; } items = nvlist_take_nvlist_array(dict, key, &nitems); for (unsigned i = 0; i < nitems; i++) { nvlist_t *rule_dict = items[i]; size_t len = (ncf->ncf_rule_count + 1) * sizeof(nvlist_t *); void *p = realloc(ncf->ncf_rule_list, len); /* * - Add rule to the transformed array. * - Process subrules recursively. * - Add the skip-to position. */ ncf->ncf_rule_list = p; ncf->ncf_rule_list[ncf->ncf_rule_count] = rule_dict; ncf->ncf_rule_count++; if (nvlist_exists_nvlist_array(rule_dict, "subrules")) { unsigned idx; _npf_rules_process(ncf, rule_dict, "subrules"); idx = ncf->ncf_rule_count; // post-recursion index nvlist_add_number(rule_dict, "skip-to", idx); } assert(nvlist_error(rule_dict) == 0); } free(items); } /* * _npf_extract_error: check the error number field and extract the * error details into the npf_error_t structure. */ static int _npf_extract_error(nvlist_t *resp, npf_error_t *errinfo) { int error; error = dnvlist_get_number(resp, "errno", 0); if (error && errinfo) { memset(errinfo, 0, sizeof(npf_error_t)); errinfo->id = dnvlist_get_number(resp, "id", 0); errinfo->error_msg = dnvlist_take_string(resp, "error-msg", NULL); errinfo->source_file = dnvlist_take_string(resp, "source-file", NULL); errinfo->source_line = dnvlist_take_number(resp, "source-line", 0); } return error; } /* * npf_xfer_fd: transfer the given request and receive a response. * * => Sets the 'operation' key on the 'req' dictionary. * => On success: returns 0 and valid nvlist in 'resp'. * => On failure: returns an error number. */ static int _npf_xfer_fd(int fd, unsigned long cmd, nvlist_t *req, nvlist_t **resp) { struct stat st; int kernver; /* * Set the NPF version and operation. */ if (!nvlist_exists(req, "version")) { nvlist_add_number(req, "version", NPF_VERSION); } nvlist_add_number(req, "operation", cmd); /* * Determine the type of file descriptor: * - If socket, then perform nvlist_send()/nvlist_recv(). * - If a character device, then use ioctl. */ if (fstat(fd, &st) == -1) { goto err; } switch (st.st_mode & S_IFMT) { #if !defined(__NetBSD__) case S_IFSOCK: if (nvlist_send(fd, req) == -1) { goto err; } if (resp && (*resp = nvlist_recv(fd, 0)) == NULL) { goto err; } break; #endif #if !defined(_NPF_STANDALONE) case S_IFBLK: case S_IFCHR: if (ioctl(fd, IOC_NPF_VERSION, &kernver) == -1) { goto err; } if (kernver != NPF_VERSION) { errno = EPROGMISMATCH; goto err; } if (nvlist_xfer_ioctl(fd, cmd, req, resp) == -1) { goto err; } break; #else (void)kernver; #endif default: errno = ENOTSUP; goto err; } return 0; err: return errno ? errno : EIO; } /* * npf_xfer_fd_errno: same as npf_xfer_fd(), but: * * => After successful retrieval of the response, inspects it, extracts * the 'errno' value (if any) and returns it. * => Destroys the response. */ static int _npf_xfer_fd_errno(int fd, unsigned long cmd, nvlist_t *req) { nvlist_t *resp; int error; error = _npf_xfer_fd(fd, cmd, req, &resp); if (error) { return error; } error = _npf_extract_error(resp, NULL); nvlist_destroy(resp); return error; } /* * CONFIGURATION INTERFACE. */ nl_config_t * npf_config_create(void) { nl_config_t *ncf; ncf = calloc(1, sizeof(nl_config_t)); if (!ncf) { return NULL; } ncf->ncf_dict = nvlist_create(0); nvlist_add_number(ncf->ncf_dict, "version", NPF_VERSION); return ncf; } int npf_config_submit(nl_config_t *ncf, int fd, npf_error_t *errinfo) { nvlist_t *resp = NULL; int error; /* Ensure the config is built. */ (void)npf_config_build(ncf); error = _npf_xfer_fd(fd, IOC_NPF_LOAD, ncf->ncf_dict, &resp); if (error) { return error; } error = _npf_extract_error(resp, errinfo); nvlist_destroy(resp); return error; } nl_config_t * npf_config_retrieve(int fd) { nl_config_t *ncf; nvlist_t *req, *resp = NULL; int error; ncf = calloc(1, sizeof(nl_config_t)); if (!ncf) { return NULL; } req = nvlist_create(0); error = _npf_xfer_fd(fd, IOC_NPF_SAVE, req, &resp); nvlist_destroy(req); if (error || _npf_extract_error(resp, NULL) != 0) { nvlist_destroy(resp); free(ncf); return NULL; } ncf->ncf_dict = resp; return ncf; } void * npf_config_export(nl_config_t *ncf, size_t *length) { /* Ensure the config is built. */ (void)npf_config_build(ncf); return nvlist_pack(ncf->ncf_dict, length); } nl_config_t * npf_config_import(const void *blob, size_t len) { nl_config_t *ncf; ncf = calloc(1, sizeof(nl_config_t)); if (!ncf) { return NULL; } ncf->ncf_dict = nvlist_unpack(blob, len, 0); if (!ncf->ncf_dict) { free(ncf); return NULL; } return ncf; } int npf_config_flush(int fd) { nl_config_t *ncf; npf_error_t errinfo; int error; ncf = npf_config_create(); if (!ncf) { return ENOMEM; } nvlist_add_bool(ncf->ncf_dict, "flush", true); error = npf_config_submit(ncf, fd, &errinfo); npf_config_destroy(ncf); return error; } bool npf_config_active_p(nl_config_t *ncf) { return dnvlist_get_bool(ncf->ncf_dict, "active", false); } bool npf_config_loaded_p(nl_config_t *ncf) { return nvlist_exists_nvlist_array(ncf->ncf_dict, "rules"); } const void * npf_config_build(nl_config_t *ncf) { _npf_rules_process(ncf, ncf->ncf_dict, "__rules"); if (ncf->ncf_rule_list) { /* Set the transformed ruleset. */ nvlist_move_nvlist_array(ncf->ncf_dict, "rules", ncf->ncf_rule_list, ncf->ncf_rule_count); /* Clear the temporary list. */ ncf->ncf_rule_list = NULL; ncf->ncf_rule_count = 0; } assert(nvlist_error(ncf->ncf_dict) == 0); return (void *)ncf->ncf_dict; } void npf_config_destroy(nl_config_t *ncf) { nvlist_destroy(ncf->ncf_dict); free(ncf); } /* * PARAMETERS. */ int npf_param_get(nl_config_t *ncf, const char *name, int *valp) { const nvlist_t *params; params = dnvlist_get_nvlist(ncf->ncf_dict, "params", NULL); if (params == NULL || !nvlist_exists(params, name)) { return ENOENT; } *valp = (int)dnvlist_get_number(params, name, 0); return 0; } int npf_param_set(nl_config_t *ncf, const char *name, int val) { nvlist_t *params; /* Ensure params dictionary. */ if (nvlist_exists(ncf->ncf_dict, "params")) { params = nvlist_take_nvlist(ncf->ncf_dict, "params"); } else { params = nvlist_create(0); } /* * If the parameter is already set, then free it first. * Set the parameter. Note: values can be negative. */ if (nvlist_exists(params, name)) { nvlist_free_number(params, name); } nvlist_add_number(params, name, (uint64_t)val); nvlist_add_nvlist(ncf->ncf_dict, "params", params); return 0; } const char * npf_param_iterate(nl_config_t *ncf, nl_iter_t *iter, int *val, int *defval) { void *cookie = (void *)(intptr_t)*iter; const nvlist_t *params, *dparams; const char *name; int type; assert(sizeof(nl_iter_t) >= sizeof(void *)); params = dnvlist_get_nvlist(ncf->ncf_dict, "params", NULL); if (params == NULL) { return NULL; } skip: if ((name = nvlist_next(params, &type, &cookie)) == NULL) { *iter = NPF_ITER_BEGIN; return NULL; } if (type != NV_TYPE_NUMBER) { goto skip; // should never happen, though } if (defval) { dparams = dnvlist_get_nvlist(ncf->ncf_dict, "params-defaults", NULL); if (dparams == NULL) { errno = EINVAL; return NULL; } *defval = (int)nvlist_get_number(dparams, name); } *val = (int)nvlist_get_number(params, name); *iter = (intptr_t)cookie; return name; } /* * DYNAMIC RULESET INTERFACE. */ static inline bool _npf_nat_ruleset_p(const char *name) { return strncmp(name, NPF_RULESET_MAP_PREF, sizeof(NPF_RULESET_MAP_PREF) - 1) == 0; } int npf_ruleset_add(int fd, const char *rname, nl_rule_t *rl, uint64_t *id) { const bool natset = _npf_nat_ruleset_p(rname); nvlist_t *rule_nvl = rl->rule_dict, *resp; int error; nvlist_add_number(rule_nvl, "attr", NPF_RULE_DYNAMIC | nvlist_take_number(rule_nvl, "attr")); if (natset && !dnvlist_get_bool(rule_nvl, "nat-rule", false)) { errno = EINVAL; return errno; } nvlist_add_string(rule_nvl, "ruleset-name", rname); nvlist_add_bool(rule_nvl, "nat-ruleset", natset); nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_ADD); error = _npf_xfer_fd(fd, IOC_NPF_RULE, rule_nvl, &resp); if (error) { return error; } *id = nvlist_get_number(resp, "id"); nvlist_destroy(resp); return 0; } int npf_ruleset_remove(int fd, const char *rname, uint64_t id) { const bool natset = _npf_nat_ruleset_p(rname); nvlist_t *rule_nvl = nvlist_create(0); int error; nvlist_add_string(rule_nvl, "ruleset-name", rname); nvlist_add_bool(rule_nvl, "nat-ruleset", natset); nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_REMOVE); nvlist_add_number(rule_nvl, "id", id); error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl); nvlist_destroy(rule_nvl); return error; } int npf_ruleset_remkey(int fd, const char *rname, const void *key, size_t len) { const bool natset = _npf_nat_ruleset_p(rname); nvlist_t *rule_nvl = nvlist_create(0); int error; nvlist_add_string(rule_nvl, "ruleset-name", rname); nvlist_add_bool(rule_nvl, "nat-ruleset", natset); nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_REMKEY); nvlist_add_binary(rule_nvl, "key", key, len); error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl); nvlist_destroy(rule_nvl); return error; } int npf_ruleset_flush(int fd, const char *rname) { const bool natset = _npf_nat_ruleset_p(rname); nvlist_t *rule_nvl = nvlist_create(0); int error; nvlist_add_string(rule_nvl, "ruleset-name", rname); nvlist_add_bool(rule_nvl, "nat-ruleset", natset); nvlist_add_number(rule_nvl, "command", NPF_CMD_RULE_FLUSH); error = _npf_xfer_fd_errno(fd, IOC_NPF_RULE, rule_nvl); nvlist_destroy(rule_nvl); return error; } /* * NPF EXTENSION INTERFACE. */ nl_ext_t * npf_ext_construct(const char *name) { nl_ext_t *ext; ext = malloc(sizeof(*ext)); if (!ext) { return NULL; } ext->ext_dict = nvlist_create(0); nvlist_add_string(ext->ext_dict, "name", name); return ext; } void npf_ext_param_u32(nl_ext_t *ext, const char *key, uint32_t val) { nvlist_add_number(ext->ext_dict, key, val); } void npf_ext_param_bool(nl_ext_t *ext, const char *key, bool val) { nvlist_add_bool(ext->ext_dict, key, val); } void npf_ext_param_string(nl_ext_t *ext, const char *key, const char *val) { nvlist_add_string(ext->ext_dict, key, val); } /* * RULE INTERFACE. */ nl_rule_t * npf_rule_create(const char *name, uint32_t attr, const char *ifname) { nl_rule_t *rl; rl = malloc(sizeof(nl_rule_t)); if (!rl) { return NULL; } rl->rule_dict = nvlist_create(0); nvlist_add_number(rl->rule_dict, "attr", attr); if (name) { nvlist_add_string(rl->rule_dict, "name", name); } if (ifname) { nvlist_add_string(rl->rule_dict, "ifname", ifname); } return rl; } int npf_rule_setcode(nl_rule_t *rl, int type, const void *code, size_t len) { if (type != NPF_CODE_BPF) { return ENOTSUP; } nvlist_add_number(rl->rule_dict, "code-type", (unsigned)type); nvlist_add_binary(rl->rule_dict, "code", code, len); return nvlist_error(rl->rule_dict); } int npf_rule_setkey(nl_rule_t *rl, const void *key, size_t len) { nvlist_add_binary(rl->rule_dict, "key", key, len); return nvlist_error(rl->rule_dict); } int npf_rule_setinfo(nl_rule_t *rl, const void *info, size_t len) { nvlist_add_binary(rl->rule_dict, "info", info, len); return nvlist_error(rl->rule_dict); } int npf_rule_setprio(nl_rule_t *rl, int pri) { nvlist_add_number(rl->rule_dict, "prio", (uint64_t)pri); return nvlist_error(rl->rule_dict); } int npf_rule_setproc(nl_rule_t *rl, const char *name) { nvlist_add_string(rl->rule_dict, "rproc", name); return nvlist_error(rl->rule_dict); } void * npf_rule_export(nl_rule_t *rl, size_t *length) { return nvlist_pack(rl->rule_dict, length); } bool npf_rule_exists_p(nl_config_t *ncf, const char *name) { const char *key = nvlist_exists_nvlist_array(ncf->ncf_dict, "rules") ? "rules" : "__rules"; // config may not be built yet return _npf_dataset_lookup(ncf->ncf_dict, key, "name", name); } int npf_rule_insert(nl_config_t *ncf, nl_rule_t *parent, nl_rule_t *rl) { nvlist_t *rule_dict = rl->rule_dict; nvlist_t *target; const char *key; if (parent) { /* Subrule of the parent. */ target = parent->rule_dict; key = "subrules"; } else { /* Global ruleset. */ target = ncf->ncf_dict; key = "__rules"; } nvlist_append_nvlist_array(target, key, rule_dict); nvlist_destroy(rule_dict); free(rl); return 0; } static nl_rule_t * _npf_rule_iterate1(nl_config_t *ncf, const char *key, nl_iter_t *iter, unsigned *level) { unsigned i = *iter; const nvlist_t *rule_dict; uint32_t skipto; if (i == 0) { /* Initialise the iterator. */ ncf->ncf_nlevel = 0; ncf->ncf_reduce[0] = 0; } rule_dict = _npf_dataset_getelement(ncf->ncf_dict, key, i); if (!rule_dict) { *iter = NPF_ITER_BEGIN; return NULL; } *iter = i + 1; // next *level = ncf->ncf_nlevel; skipto = dnvlist_get_number(rule_dict, "skip-to", 0); if (skipto) { ncf->ncf_nlevel++; ncf->ncf_reduce[ncf->ncf_nlevel] = skipto; } if (ncf->ncf_reduce[ncf->ncf_nlevel] == (i + 1)) { assert(ncf->ncf_nlevel > 0); ncf->ncf_nlevel--; } ncf->ncf_cur_rule.rule_dict = __UNCONST(rule_dict); // XXX return &ncf->ncf_cur_rule; } nl_rule_t * npf_rule_iterate(nl_config_t *ncf, nl_iter_t *iter, unsigned *level) { return _npf_rule_iterate1(ncf, "rules", iter, level); } const char * npf_rule_getname(nl_rule_t *rl) { return dnvlist_get_string(rl->rule_dict, "name", NULL); } uint32_t npf_rule_getattr(nl_rule_t *rl) { return dnvlist_get_number(rl->rule_dict, "attr", 0); } const char * npf_rule_getinterface(nl_rule_t *rl) { return dnvlist_get_string(rl->rule_dict, "ifname", NULL); } const void * npf_rule_getinfo(nl_rule_t *rl, size_t *len) { return dnvlist_get_binary(rl->rule_dict, "info", len, NULL, 0); } const char * npf_rule_getproc(nl_rule_t *rl) { return dnvlist_get_string(rl->rule_dict, "rproc", NULL); } uint64_t npf_rule_getid(nl_rule_t *rl) { return dnvlist_get_number(rl->rule_dict, "id", 0); } const void * npf_rule_getcode(nl_rule_t *rl, int *type, size_t *len) { *type = (int)dnvlist_get_number(rl->rule_dict, "code-type", 0); return dnvlist_get_binary(rl->rule_dict, "code", len, NULL, 0); } int _npf_ruleset_list(int fd, const char *rname, nl_config_t *ncf) { const bool natset = _npf_nat_ruleset_p(rname); nvlist_t *req, *resp; int error; req = nvlist_create(0); nvlist_add_string(req, "ruleset-name", rname); nvlist_add_bool(req, "nat-ruleset", natset); nvlist_add_number(req, "command", NPF_CMD_RULE_LIST); error = _npf_xfer_fd(fd, IOC_NPF_RULE, req, &resp); nvlist_destroy(req); if (error) { return error; } if (nvlist_exists_nvlist_array(resp, "rules")) { nvlist_t **rules; size_t n; rules = nvlist_take_nvlist_array(resp, "rules", &n); nvlist_move_nvlist_array(ncf->ncf_dict, "rules", rules, n); } nvlist_destroy(resp); return 0; } void npf_rule_destroy(nl_rule_t *rl) { nvlist_destroy(rl->rule_dict); free(rl); } /* * RULE PROCEDURE INTERFACE. */ nl_rproc_t * npf_rproc_create(const char *name) { nl_rproc_t *rp; rp = malloc(sizeof(nl_rproc_t)); if (!rp) { return NULL; } rp->rproc_dict = nvlist_create(0); nvlist_add_string(rp->rproc_dict, "name", name); return rp; } int npf_rproc_extcall(nl_rproc_t *rp, nl_ext_t *ext) { nvlist_t *rproc_dict = rp->rproc_dict; const char *name = dnvlist_get_string(ext->ext_dict, "name", NULL); if (_npf_dataset_lookup(rproc_dict, "extcalls", "name", name)) { return EEXIST; } nvlist_append_nvlist_array(rproc_dict, "extcalls", ext->ext_dict); nvlist_destroy(ext->ext_dict); free(ext); return 0; } bool npf_rproc_exists_p(nl_config_t *ncf, const char *name) { return _npf_dataset_lookup(ncf->ncf_dict, "rprocs", "name", name); } int npf_rproc_insert(nl_config_t *ncf, nl_rproc_t *rp) { const char *name; name = dnvlist_get_string(rp->rproc_dict, "name", NULL); if (!name) { return EINVAL; } if (npf_rproc_exists_p(ncf, name)) { return EEXIST; } nvlist_append_nvlist_array(ncf->ncf_dict, "rprocs", rp->rproc_dict); nvlist_destroy(rp->rproc_dict); free(rp); return 0; } nl_rproc_t * npf_rproc_iterate(nl_config_t *ncf, nl_iter_t *iter) { const nvlist_t *rproc_dict; unsigned i = *iter; rproc_dict = _npf_dataset_getelement(ncf->ncf_dict, "rprocs", i); if (!rproc_dict) { *iter = NPF_ITER_BEGIN; return NULL; } *iter = i + 1; // next ncf->ncf_cur_rproc.rproc_dict = __UNCONST(rproc_dict); // XXX return &ncf->ncf_cur_rproc; } const char * npf_rproc_getname(nl_rproc_t *rp) { return dnvlist_get_string(rp->rproc_dict, "name", NULL); } /* * NAT INTERFACE. */ nl_nat_t * npf_nat_create(int type, unsigned flags, const char *ifname) { nl_rule_t *rl; nvlist_t *rule_dict; uint32_t attr; attr = NPF_RULE_PASS | NPF_RULE_FINAL | (type == NPF_NATOUT ? NPF_RULE_OUT : NPF_RULE_IN); /* Create a rule for NAT policy. Next, will add NAT data. */ rl = npf_rule_create(NULL, attr, ifname); if (!rl) { return NULL; } rule_dict = rl->rule_dict; /* Translation type and flags. */ nvlist_add_number(rule_dict, "type", type); nvlist_add_number(rule_dict, "flags", flags); nvlist_add_bool(rule_dict, "nat-rule", true); return (nl_nat_t *)rl; } int npf_nat_insert(nl_config_t *ncf, nl_nat_t *nt) { nvlist_append_nvlist_array(ncf->ncf_dict, "nat", nt->rule_dict); nvlist_destroy(nt->rule_dict); free(nt); return 0; } nl_nat_t * npf_nat_iterate(nl_config_t *ncf, nl_iter_t *iter) { unsigned level; return _npf_rule_iterate1(ncf, "nat", iter, &level); } int npf_nat_setaddr(nl_nat_t *nt, int af, npf_addr_t *addr, npf_netmask_t mask) { /* Translation IP and mask. */ if (!_npf_add_addr(nt->rule_dict, "nat-addr", af, addr)) { return nvlist_error(nt->rule_dict); } nvlist_add_number(nt->rule_dict, "nat-mask", (uint32_t)mask); return nvlist_error(nt->rule_dict); } int npf_nat_setport(nl_nat_t *nt, in_port_t port) { /* Translation port (for redirect case). */ nvlist_add_number(nt->rule_dict, "nat-port", port); return nvlist_error(nt->rule_dict); } int npf_nat_settable(nl_nat_t *nt, unsigned tid) { /* * Translation table ID; the address/mask will then serve as a filter. */ nvlist_add_number(nt->rule_dict, "nat-table-id", tid); return nvlist_error(nt->rule_dict); } int npf_nat_setalgo(nl_nat_t *nt, unsigned algo) { nvlist_add_number(nt->rule_dict, "nat-algo", algo); return nvlist_error(nt->rule_dict); } int npf_nat_setnpt66(nl_nat_t *nt, uint16_t adj) { int error; if ((error = npf_nat_setalgo(nt, NPF_ALGO_NPT66)) != 0) { return error; } nvlist_add_number(nt->rule_dict, "npt66-adj", adj); return nvlist_error(nt->rule_dict); } int npf_nat_gettype(nl_nat_t *nt) { return dnvlist_get_number(nt->rule_dict, "type", 0); } unsigned npf_nat_getflags(nl_nat_t *nt) { return dnvlist_get_number(nt->rule_dict, "flags", 0); } unsigned npf_nat_getalgo(nl_nat_t *nt) { return dnvlist_get_number(nt->rule_dict, "nat-algo", 0); } const npf_addr_t * npf_nat_getaddr(nl_nat_t *nt, size_t *alen, npf_netmask_t *mask) { const void *data; if (nvlist_exists(nt->rule_dict, "nat-addr")) { data = nvlist_get_binary(nt->rule_dict, "nat-addr", alen); *mask = nvlist_get_number(nt->rule_dict, "nat-mask"); } else { data = NULL; *alen = 0; *mask = NPF_NO_NETMASK; } return data; } in_port_t npf_nat_getport(nl_nat_t *nt) { return (uint16_t)dnvlist_get_number(nt->rule_dict, "nat-port", 0); } unsigned npf_nat_gettable(nl_nat_t *nt) { return dnvlist_get_number(nt->rule_dict, "nat-table-id", 0); } /* * TABLE INTERFACE. */ nl_table_t * npf_table_create(const char *name, unsigned id, int type) { nl_table_t *tl; tl = malloc(sizeof(*tl)); if (!tl) { return NULL; } tl->table_dict = nvlist_create(0); nvlist_add_string(tl->table_dict, "name", name); nvlist_add_number(tl->table_dict, "id", id); nvlist_add_number(tl->table_dict, "type", type); return tl; } int npf_table_add_entry(nl_table_t *tl, int af, const npf_addr_t *addr, const npf_netmask_t mask) { nvlist_t *entry; entry = nvlist_create(0); if (!entry) { return ENOMEM; } if (!_npf_add_addr(entry, "addr", af, addr)) { nvlist_destroy(entry); return EINVAL; } nvlist_add_number(entry, "mask", mask); nvlist_append_nvlist_array(tl->table_dict, "entries", entry); nvlist_destroy(entry); return 0; } static inline int _npf_table_build_const(nl_table_t *tl) { struct cdbw *cdbw; const nvlist_t * const *entries; int error = 0, fd = -1; size_t nitems, len; void *cdb, *buf; struct stat sb; char sfn[32]; if (dnvlist_get_number(tl->table_dict, "type", 0) != NPF_TABLE_CONST) { return 0; } if (!nvlist_exists_nvlist_array(tl->table_dict, "entries")) { return 0; } /* * Create a constant database and put all the entries. */ if ((cdbw = cdbw_open()) == NULL) { return errno; } entries = nvlist_get_nvlist_array(tl->table_dict, "entries", &nitems); for (unsigned i = 0; i < nitems; i++) { const nvlist_t *entry = entries[i]; const npf_addr_t *addr; size_t alen; addr = dnvlist_get_binary(entry, "addr", &alen, NULL, 0); if (addr == NULL || alen == 0 || alen > sizeof(npf_addr_t)) { error = EINVAL; goto out; } if (cdbw_put(cdbw, addr, alen, addr, alen) == -1) { error = errno; goto out; } } /* * Write the constant database into a temporary file. */ strncpy(sfn, "/tmp/npfcdb.XXXXXX", sizeof(sfn)); sfn[sizeof(sfn) - 1] = '\0'; if ((fd = mkstemp(sfn)) == -1) { error = errno; goto out; } unlink(sfn); if (cdbw_output(cdbw, fd, "npf-table-cdb", NULL) == -1) { error = errno; goto out; } if (fstat(fd, &sb) == -1) { error = errno; goto out; } len = sb.st_size; /* * Memory-map the database and copy it into a buffer. */ buf = malloc(len); if (!buf) { error = ENOMEM; goto out; } cdb = mmap(NULL, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); if (cdb == MAP_FAILED) { error = errno; free(buf); goto out; } munmap(cdb, len); /* * Move the data buffer to the nvlist. */ nvlist_move_binary(tl->table_dict, "data", buf, len); error = nvlist_error(tl->table_dict); out: if (fd != -1) { close(fd); } cdbw_close(cdbw); return error; } int npf_table_insert(nl_config_t *ncf, nl_table_t *tl) { const char *name; int error; name = dnvlist_get_string(tl->table_dict, "name", NULL); if (!name) { return EINVAL; } if (_npf_dataset_lookup(ncf->ncf_dict, "tables", "name", name)) { return EEXIST; } if ((error = _npf_table_build_const(tl)) != 0) { return error; } nvlist_append_nvlist_array(ncf->ncf_dict, "tables", tl->table_dict); nvlist_destroy(tl->table_dict); free(tl); return 0; } int npf_table_replace(int fd, nl_table_t *tl, npf_error_t *errinfo) { nvlist_t *resp = NULL; int error; /* Ensure const tables are built. */ if ((error = _npf_table_build_const(tl)) != 0) { return error; } error = _npf_xfer_fd(fd, IOC_NPF_TABLE_REPLACE, tl->table_dict, &resp); if (error) { assert(resp == NULL); return errno; } error = _npf_extract_error(resp, errinfo); nvlist_destroy(resp); return error; } nl_table_t * npf_table_iterate(nl_config_t *ncf, nl_iter_t *iter) { const nvlist_t *table_dict; unsigned i = *iter; table_dict = _npf_dataset_getelement(ncf->ncf_dict, "tables", i); if (!table_dict) { *iter = NPF_ITER_BEGIN; return NULL; } *iter = i + 1; // next ncf->ncf_cur_table.table_dict = __UNCONST(table_dict); // XXX return &ncf->ncf_cur_table; } unsigned npf_table_getid(nl_table_t *tl) { return dnvlist_get_number(tl->table_dict, "id", (unsigned)-1); } const char * npf_table_getname(nl_table_t *tl) { return dnvlist_get_string(tl->table_dict, "name", NULL); } int npf_table_gettype(nl_table_t *tl) { return dnvlist_get_number(tl->table_dict, "type", 0); } void npf_table_destroy(nl_table_t *tl) { nvlist_destroy(tl->table_dict); free(tl); } /* * ALG INTERFACE. */ int npf_alg_load(nl_config_t *ncf, const char *name) { nvlist_t *alg_dict; if (_npf_dataset_lookup(ncf->ncf_dict, "algs", "name", name)) { return EEXIST; } alg_dict = nvlist_create(0); nvlist_add_string(alg_dict, "name", name); nvlist_append_nvlist_array(ncf->ncf_dict, "algs", alg_dict); nvlist_destroy(alg_dict); return 0; } /* * CONNECTION / NAT ENTRY INTERFACE. */ typedef struct { unsigned alen; unsigned proto; npf_addr_t addr[3]; in_port_t port[3]; } npf_connpoint_t; static int _npf_conn_lookup(int fd, const int af, npf_addr_t *addr[2], in_port_t port[2], unsigned proto, const char *ifname, unsigned di) { nvlist_t *req = NULL, *resp = NULL, *key_nv; const nvlist_t *nat; int error = EINVAL; /* * Setup the connection lookup key. */ if ((key_nv = nvlist_create(0)) == NULL) { return ENOMEM; } if (!_npf_add_addr(key_nv, "saddr", af, addr[0])) { nvlist_destroy(key_nv); goto out; } if (!_npf_add_addr(key_nv, "daddr", af, addr[1])) { nvlist_destroy(key_nv); goto out; } nvlist_add_number(key_nv, "sport", htons(port[0])); nvlist_add_number(key_nv, "dport", htons(port[1])); nvlist_add_number(key_nv, "proto", proto); if (ifname) { nvlist_add_string(key_nv, "ifname", ifname); } if (di) { nvlist_add_number(key_nv, "di", di); } /* * Setup the request. */ if ((req = nvlist_create(0)) == NULL) { error = ENOMEM; goto out; } nvlist_move_nvlist(req, "key", key_nv); /* Lookup: retrieve the connection entry. */ error = _npf_xfer_fd(fd, IOC_NPF_CONN_LOOKUP, req, &resp); if (error) { goto out; } /* * Get the NAT entry and extract the translated pair. */ if ((nat = dnvlist_get_nvlist(resp, "nat", NULL)) == NULL) { error = ENOENT; goto out; } if (_npf_get_addr(nat, "oaddr", addr[0]) == 0 || _npf_get_addr(nat, "taddr", addr[1]) == 0) { error = EINVAL; goto out; } port[0] = ntohs(nvlist_get_number(nat, "oport")); port[1] = ntohs(nvlist_get_number(nat, "tport")); out: if (resp) { nvlist_destroy(resp); } if (req) { nvlist_destroy(req); } return error; } int npf_nat_lookup(int fd, int af, npf_addr_t *addr[2], in_port_t port[2], int proto, int di __unused) { int error; port[0] = ntohs(port[0]); port[1] = ntohs(port[1]); error = _npf_conn_lookup(fd, af, addr, port, proto, NULL, 0); port[0] = htons(port[0]); port[1] = htons(port[1]); return error; } static bool npf_connkey_handle(const nvlist_t *key_nv, npf_connpoint_t *ep) { unsigned alen1, alen2; alen1 = _npf_get_addr(key_nv, "saddr", &ep->addr[0]); alen2 = _npf_get_addr(key_nv, "daddr", &ep->addr[1]); if (alen1 == 0 || alen1 != alen2) { return false; } ep->alen = alen1; ep->port[0] = ntohs(nvlist_get_number(key_nv, "sport")); ep->port[1] = ntohs(nvlist_get_number(key_nv, "dport")); ep->proto = nvlist_get_number(key_nv, "proto"); return true; } static void npf_conn_handle(const nvlist_t *conn, npf_conn_func_t func, void *arg) { const nvlist_t *key_nv, *nat_nv; const char *ifname; npf_connpoint_t ep; memset(&ep, 0, sizeof(npf_connpoint_t)); ifname = dnvlist_get_string(conn, "ifname", NULL); key_nv = dnvlist_get_nvlist(conn, "forw-key", NULL); if (!npf_connkey_handle(key_nv, &ep)) { goto err; } if ((nat_nv = dnvlist_get_nvlist(conn, "nat", NULL)) != NULL) { if (_npf_get_addr(nat_nv, "taddr", &ep.addr[2]) != ep.alen) { goto err; } ep.port[2] = ntohs(nvlist_get_number(nat_nv, "tport")); } /* * XXX: add 'proto' and 'flow'; perhaps expand and pass the * whole to npf_connpoint_t? */ (*func)((unsigned)ep.alen, ep.addr, ep.port, ifname, arg); err: return; } int npf_conn_list(int fd, npf_conn_func_t func, void *arg) { nl_config_t *ncf; const nvlist_t * const *conns; size_t nitems; ncf = npf_config_retrieve(fd); if (!ncf) { return errno; } if (!nvlist_exists_nvlist_array(ncf->ncf_dict, "conn-list")) { return 0; } conns = nvlist_get_nvlist_array(ncf->ncf_dict, "conn-list", &nitems); for (unsigned i = 0; i < nitems; i++) { const nvlist_t *conn = conns[i]; npf_conn_handle(conn, func, arg); } npf_config_destroy(ncf); return 0; } /* * MISC. */ void _npf_debug_addif(nl_config_t *ncf, const char *ifname) { nvlist_t *debug; /* * Initialise the debug dictionary on the first call. */ debug = dnvlist_take_nvlist(ncf->ncf_dict, "debug", NULL); if (debug == NULL) { debug = nvlist_create(0); } if (!_npf_dataset_lookup(debug, "interfaces", "name", ifname)) { nvlist_t *ifdict = nvlist_create(0); nvlist_add_string(ifdict, "name", ifname); nvlist_add_number(ifdict, "index", if_nametoindex(ifname)); nvlist_append_nvlist_array(debug, "interfaces", ifdict); nvlist_destroy(ifdict); } nvlist_move_nvlist(ncf->ncf_dict, "debug", debug); } void _npf_config_dump(nl_config_t *ncf, int fd) { (void)npf_config_build(ncf); nvlist_dump(ncf->ncf_dict, fd); }