diff options
Diffstat (limited to 'sys/netpfil/ipfw/ip_fw_eaction.c')
-rw-r--r-- | sys/netpfil/ipfw/ip_fw_eaction.c | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/sys/netpfil/ipfw/ip_fw_eaction.c b/sys/netpfil/ipfw/ip_fw_eaction.c new file mode 100644 index 0000000..144bf49d --- /dev/null +++ b/sys/netpfil/ipfw/ip_fw_eaction.c @@ -0,0 +1,369 @@ +/*- + * Copyright (c) 2016 Yandex LLC + * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org> + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/hash.h> +#include <sys/lock.h> +#include <sys/rwlock.h> +#include <sys/rmlock.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/queue.h> +#include <net/pfil.h> + +#include <net/if.h> /* ip_fw.h requires IFNAMSIZ */ +#include <netinet/in.h> +#include <netinet/ip_var.h> /* struct ipfw_rule_ref */ +#include <netinet/ip_fw.h> + +#include <netpfil/ipfw/ip_fw_private.h> + +#include "opt_ipfw.h" + +/* + * External actions support for ipfw. + * + * This code provides KPI for implementing loadable modules, that + * can provide handlers for external action opcodes in the ipfw's + * rules. + * Module should implement opcode handler with type ipfw_eaction_t. + * This handler will be called by ipfw_chk() function when + * O_EXTERNAL_ACTION opcode will be matched. The handler must return + * value used as return value in ipfw_chk(), i.e. IP_FW_PASS, + * IP_FW_DENY (see ip_fw_private.h). + * Also the last argument must be set by handler. If it is zero, + * the search continues to the next rule. If it has non zero value, + * the search terminates. + * + * The module that implements external action should register its + * handler and name with ipfw_add_eaction() function. + * This function will return eaction_id, that can be used by module. + * + * It is possible to pass some additional information to external + * action handler via the O_EXTERNAL_INSTANCE opcode. This opcode + * will be next after the O_EXTERNAL_ACTION opcode. cmd->arg1 will + * contain index of named object related to instance of external action. + * + * In case when eaction module uses named instances, it should register + * opcode rewriting routines for O_EXTERNAL_INSTANCE opcode. The + * classifier callback can look back into O_EXTERNAL_ACTION opcode (it + * must be in the (ipfw_insn *)(cmd - 1)). By arg1 from O_EXTERNAL_ACTION + * it can deteremine eaction_id and compare it with its own. + * The macro IPFW_TLV_EACTION_NAME(eaction_id) can be used to deteremine + * the type of named_object related to external action instance. + * + * On module unload handler should be deregistered with ipfw_del_eaction() + * function using known eaction_id. + */ + +struct eaction_obj { + struct named_object no; + ipfw_eaction_t *handler; + char name[64]; +}; + +#define EACTION_OBJ(ch, cmd) \ + ((struct eaction_obj *)SRV_OBJECT((ch), (cmd)->arg1)) + +#if 0 +#define EACTION_DEBUG(fmt, ...) do { \ + printf("%s: " fmt "\n", __func__, ## __VA_ARGS__); \ +} while (0) +#else +#define EACTION_DEBUG(fmt, ...) +#endif + +const char *default_eaction_typename = "drop"; +static int +default_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, + ipfw_insn *cmd, int *done) +{ + + *done = 1; /* terminate the search */ + return (IP_FW_DENY); +} + +/* + * Opcode rewriting callbacks. + */ +static int +eaction_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + + EACTION_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1); + *puidx = cmd->arg1; + *ptype = 0; + return (0); +} + +static void +eaction_update(ipfw_insn *cmd, uint16_t idx) +{ + + cmd->arg1 = idx; + EACTION_DEBUG("opcode %d, arg1 -> %d", cmd->opcode, cmd->arg1); +} + +static int +eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, + struct named_object **pno) +{ + + EACTION_DEBUG("uidx %u, type %u", ti->uidx, ti->type); + return (ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, + IPFW_TLV_EACTION, pno)); +} + +static struct named_object * +eaction_findbykidx(struct ip_fw_chain *ch, uint16_t idx) +{ + + EACTION_DEBUG("kidx %u", idx); + return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); +} + +static int +eaction_create_compat(struct ip_fw_chain *ch, struct tid_info *ti, + uint16_t *pkidx) +{ + + return (EOPNOTSUPP); +} + +static struct opcode_obj_rewrite eaction_opcodes[] = { + { + O_EXTERNAL_ACTION, IPFW_TLV_EACTION, + eaction_classify, eaction_update, + eaction_findbyname, eaction_findbykidx, + eaction_create_compat + }, +}; + +static int +create_eaction_obj(struct ip_fw_chain *ch, ipfw_eaction_t handler, + const char *name, uint16_t *eaction_id) +{ + struct namedobj_instance *ni; + struct eaction_obj *obj; + + IPFW_UH_UNLOCK_ASSERT(ch); + + ni = CHAIN_TO_SRV(ch); + obj = malloc(sizeof(*obj), M_IPFW, M_WAITOK | M_ZERO); + obj->no.name = obj->name; + obj->no.etlv = IPFW_TLV_EACTION; + obj->handler = handler; + strlcpy(obj->name, name, sizeof(obj->name)); + + IPFW_UH_WLOCK(ch); + if (ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, + name) != NULL) { + /* + * Object is already created. + * We don't allow eactions with the same name. + */ + IPFW_UH_WUNLOCK(ch); + free(obj, M_IPFW); + EACTION_DEBUG("External action with typename " + "'%s' already exists", name); + return (EEXIST); + } + if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) { + IPFW_UH_WUNLOCK(ch); + free(obj, M_IPFW); + EACTION_DEBUG("alloc_idx failed"); + return (ENOSPC); + } + ipfw_objhash_add(ni, &obj->no); + IPFW_WLOCK(ch); + SRV_OBJECT(ch, obj->no.kidx) = obj; + IPFW_WUNLOCK(ch); + obj->no.refcnt++; + IPFW_UH_WUNLOCK(ch); + + if (eaction_id != NULL) + *eaction_id = obj->no.kidx; + return (0); +} + +static void +destroy_eaction_obj(struct ip_fw_chain *ch, struct named_object *no) +{ + struct namedobj_instance *ni; + struct eaction_obj *obj; + + IPFW_UH_WLOCK_ASSERT(ch); + + ni = CHAIN_TO_SRV(ch); + IPFW_WLOCK(ch); + obj = SRV_OBJECT(ch, no->kidx); + SRV_OBJECT(ch, no->kidx) = NULL; + IPFW_WUNLOCK(ch); + ipfw_objhash_del(ni, no); + ipfw_objhash_free_idx(ni, no->kidx); + free(obj, M_IPFW); +} + +/* + * Resets all eaction opcodes to default handlers. + */ +static void +reset_eaction_obj(struct ip_fw_chain *ch, uint16_t eaction_id) +{ + struct named_object *no; + struct ip_fw *rule; + ipfw_insn *cmd; + int i; + + IPFW_UH_WLOCK_ASSERT(ch); + + no = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0, + IPFW_TLV_EACTION, default_eaction_typename); + if (no == NULL) + panic("Default external action handler is not found"); + if (eaction_id == no->kidx) + panic("Wrong eaction_id"); + EACTION_DEBUG("replace id %u with %u", eaction_id, no->kidx); + IPFW_WLOCK(ch); + for (i = 0; i < ch->n_rules; i++) { + rule = ch->map[i]; + cmd = ACTION_PTR(rule); + if (cmd->opcode != O_EXTERNAL_ACTION) + continue; + if (cmd->arg1 != eaction_id) + continue; + cmd->arg1 = no->kidx; /* Set to default id */ + /* + * XXX: we only bump refcount on default_eaction. + * Refcount on the original object will be just + * ignored on destroy. But on default_eaction it + * will be decremented on rule deletion. + */ + no->refcnt++; + /* + * Since named_object related to this instance will be + * also destroyed, truncate the chain of opcodes to + * remove O_EXTERNAL_INSTANCE opcode. + */ + if (rule->act_ofs < rule->cmd_len - 1) { + EACTION_DEBUG("truncate rule %d", rule->rulenum); + rule->cmd_len--; + } + } + IPFW_WUNLOCK(ch); +} + +/* + * Initialize external actions framework. + * Create object with default eaction handler "drop". + */ +int +ipfw_eaction_init(struct ip_fw_chain *ch, int first) +{ + int error; + + error = create_eaction_obj(ch, default_eaction, + default_eaction_typename, NULL); + if (error != 0) + return (error); + IPFW_ADD_OBJ_REWRITER(first, eaction_opcodes); + EACTION_DEBUG("External actions support initialized"); + return (0); +} + +void +ipfw_eaction_uninit(struct ip_fw_chain *ch, int last) +{ + struct namedobj_instance *ni; + struct named_object *no; + + ni = CHAIN_TO_SRV(ch); + + IPFW_UH_WLOCK(ch); + no = ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION, + default_eaction_typename); + if (no != NULL) + destroy_eaction_obj(ch, no); + IPFW_UH_WUNLOCK(ch); + IPFW_DEL_OBJ_REWRITER(last, eaction_opcodes); + EACTION_DEBUG("External actions support uninitialized"); +} + +/* + * Registers external action handler to the global array. + * On success it returns eaction id, otherwise - zero. + */ +uint16_t +ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler, + const char *name) +{ + uint16_t eaction_id; + + eaction_id = 0; + if (ipfw_check_object_name_generic(name) == 0) { + create_eaction_obj(ch, handler, name, &eaction_id); + EACTION_DEBUG("Registered external action '%s' with id %u", + name, eaction_id); + } + return (eaction_id); +} + +/* + * Deregisters external action handler with id eaction_id. + */ +int +ipfw_del_eaction(struct ip_fw_chain *ch, uint16_t eaction_id) +{ + struct named_object *no; + + IPFW_UH_WLOCK(ch); + no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id); + if (no == NULL || no->etlv != IPFW_TLV_EACTION) { + IPFW_UH_WUNLOCK(ch); + return (EINVAL); + } + if (no->refcnt > 1) + reset_eaction_obj(ch, eaction_id); + EACTION_DEBUG("External action '%s' with id %u unregistered", + no->name, eaction_id); + destroy_eaction_obj(ch, no); + IPFW_UH_WUNLOCK(ch); + return (0); +} + +int +ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args, + ipfw_insn *cmd, int *done) +{ + + return (EACTION_OBJ(ch, cmd)->handler(ch, args, cmd, done)); +} |