#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/if.h>
#include <net/pfil.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include "opt_ipfw.h"
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), insntod((cmd), kidx)->kidx))
#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;
return (IP_FW_DENY);
}
static int
eaction_classify(ipfw_insn *cmd0, uint32_t *puidx, uint8_t *ptype)
{
ipfw_insn_kidx *cmd;
if (F_LEN(cmd0) <= 1)
return (EINVAL);
cmd = insntod(cmd0, kidx);
EACTION_DEBUG("opcode %u, kidx %u", cmd0->opcode, cmd->kidx);
*puidx = cmd->kidx;
*ptype = 0;
return (0);
}
static void
eaction_update(ipfw_insn *cmd0, uint32_t idx)
{
ipfw_insn_kidx *cmd;
cmd = insntod(cmd0, kidx);
cmd->kidx = idx;
EACTION_DEBUG("opcode %u, kidx -> %u", cmd0->opcode, cmd->kidx);
}
static int
eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti,
struct named_object **pno)
{
ipfw_obj_ntlv *ntlv;
if (ti->tlvs == NULL)
return (EINVAL);
ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx,
IPFW_TLV_EACTION);
if (ntlv == NULL)
return (EINVAL);
EACTION_DEBUG("name %s, uidx %u, type %u", ntlv->name,
ti->uidx, ti->type);
*pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch),
0, IPFW_TLV_EACTION, ntlv->name);
if (*pno == NULL)
return (ESRCH);
return (0);
}
static struct named_object *
eaction_findbykidx(struct ip_fw_chain *ch, uint32_t idx)
{
EACTION_DEBUG("kidx %u", idx);
return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx));
}
static struct opcode_obj_rewrite eaction_opcodes[] = {
{
.opcode = O_EXTERNAL_ACTION,
.etlv = IPFW_TLV_EACTION,
.classifier = eaction_classify,
.update = eaction_update,
.find_byname = eaction_findbyname,
.find_bykidx = eaction_findbykidx,
},
};
static int
create_eaction_obj(struct ip_fw_chain *ch, ipfw_eaction_t handler,
const char *name, uint32_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) {
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);
}
static void
reset_eaction_rules(struct ip_fw_chain *ch, uint32_t eaction_id,
uint32_t instance_id, bool reset_rules)
{
struct named_object *no;
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("Going to replace id %u with %u", eaction_id, no->kidx);
IPFW_WLOCK(ch);
if (reset_rules) {
for (i = 0; i < ch->n_rules; i++) {
if (ipfw_reset_eaction(ch, ch->map[i], eaction_id,
no->kidx, instance_id) != 0)
no->refcnt++;
}
}
ipfw_dyn_reset_eaction(ch, eaction_id, no->kidx, instance_id);
IPFW_WUNLOCK(ch);
}
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");
}
uint32_t
ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler,
const char *name)
{
uint32_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);
}
int
ipfw_del_eaction(struct ip_fw_chain *ch, uint32_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);
}
reset_eaction_rules(ch, eaction_id, 0, (no->refcnt > 1));
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_reset_eaction(struct ip_fw_chain *ch, struct ip_fw *rule,
uint32_t eaction_id, uint32_t default_id, uint32_t instance_id)
{
ipfw_insn *cmd, *icmd;
int l;
IPFW_UH_WLOCK_ASSERT(ch);
IPFW_WLOCK_ASSERT(ch);
cmd = ipfw_get_action(rule);
if (cmd->opcode != O_EXTERNAL_ACTION ||
insntod(cmd, kidx)->kidx != eaction_id)
return (0);
l = rule->cmd + rule->cmd_len - cmd;
if (l > 2) {
MPASS(F_LEN(cmd) == 2);
icmd = cmd + F_LEN(cmd);
if (icmd->opcode == O_EXTERNAL_INSTANCE &&
instance_id != 0 &&
insntod(icmd, kidx)->kidx != instance_id)
return (0);
EACTION_DEBUG("truncate rule %u: len %u -> %u",
rule->rulenum, rule->cmd_len,
rule->cmd_len - F_LEN(icmd));
rule->cmd_len -= F_LEN(icmd);
MPASS(((uint32_t *)icmd -
(uint32_t *)rule->cmd) == rule->cmd_len);
}
insntod(cmd, kidx)->kidx = default_id;
return (1);
}
int
ipfw_reset_eaction_instance(struct ip_fw_chain *ch, uint32_t eaction_id,
uint32_t kidx)
{
struct named_object *no;
IPFW_UH_WLOCK_ASSERT(ch);
no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id);
if (no == NULL || no->etlv != IPFW_TLV_EACTION)
return (EINVAL);
reset_eaction_rules(ch, eaction_id, kidx, 0);
return (0);
}
int
ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args,
ipfw_insn *cmd, int *done)
{
MPASS(F_LEN(cmd) == 2);
return (EACTION_OBJ(ch, cmd)->handler(ch, args, cmd, done));
}