freebsd-skq/sys/netpfil/ipfw/ip_fw_eaction.c
Andrey V. Elsukov 2acdf79f53 Add External Actions KPI to ipfw(9).
It allows implementing loadable kernel modules with new actions and
without needing to modify kernel headers and ipfw(8). The module
registers its action handler and keyword string, that will be used
as action name. Using generic syntax user can add rules with this
action. Also ipfw(8) can be easily modified to extend basic syntax
for external actions, that become a part base system.
Sample modules will coming soon.

Obtained from:	Yandex LLC
Sponsored by:	Yandex LLC
2016-04-14 22:51:23 +00:00

370 lines
10 KiB
C

/*-
* 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));
}