#include <sys/cdefs.h>
#include "opt_ipfw.h"
#include "opt_inet.h"
#ifndef INET
#error IPFIREWALL requires INET.
#endif
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/rwlock.h>
#include <sys/rmlock.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/fnv_hash.h>
#include <net/if.h>
#include <net/route.h>
#include <net/vnet.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <netinet/in.h>
#include <netinet/ip_var.h>
#include <netinet/ip_fw.h>
#include <netpfil/ipfw/ip_fw_private.h>
#include <netpfil/ipfw/ip_fw_table.h>
#ifdef MAC
#include <security/mac/mac_framework.h>
#endif
static enum ipfw_opcheck_result
check_opcode_compat_nop(ipfw_insn **pcmd, int *plen,
struct rule_check_info *ci)
{
return (FAILED);
}
static ipfw_check_opcode_t check_opcode_f = check_opcode_compat_nop;
static int check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len,
struct rule_check_info *ci);
static int rewrite_rule_uidx(struct ip_fw_chain *chain,
struct rule_check_info *ci);
struct namedobj_instance {
struct namedobjects_head *names;
struct namedobjects_head *values;
uint32_t nn_size;
uint32_t nv_size;
u_long *idx_mask;
uint32_t max_blocks;
uint32_t count;
uint16_t free_off[IPFW_MAX_SETS];
objhash_hash_f *hash_f;
objhash_cmp_f *cmp_f;
};
#define BLOCK_ITEMS (8 * sizeof(u_long))
static uint32_t objhash_hash_name(struct namedobj_instance *ni,
const void *key, uint32_t kopt);
static uint32_t objhash_hash_idx(struct namedobj_instance *ni, uint32_t val);
static int objhash_cmp_name(struct named_object *no, const void *name,
uint32_t set);
MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");
static struct mtx ctl3_lock;
#define CTL3_LOCK_INIT() mtx_init(&ctl3_lock, "ctl3_lock", NULL, MTX_DEF)
#define CTL3_LOCK_DESTROY() mtx_destroy(&ctl3_lock)
#define CTL3_LOCK() mtx_lock(&ctl3_lock)
#define CTL3_UNLOCK() mtx_unlock(&ctl3_lock)
static struct ipfw_sopt_handler *ctl3_handlers;
static size_t ctl3_hsize;
static uint64_t ctl3_refct, ctl3_gencnt;
#define CTL3_SMALLBUF 4096
#define CTL3_LARGEBUF (16 * 1024 * 1024)
static int ipfw_flush_sopt_data(struct sockopt_data *sd);
static sopt_handler_f dump_config, add_rules, del_rules, clear_rules,
move_rules, manage_sets, dump_soptcodes, dump_srvobjects,
manage_skiptocache;
static struct ipfw_sopt_handler scodes[] = {
{ IP_FW_XGET, IP_FW3_OPVER, HDIR_GET, dump_config },
{ IP_FW_XADD, IP_FW3_OPVER, HDIR_BOTH, add_rules },
{ IP_FW_XDEL, IP_FW3_OPVER, HDIR_BOTH, del_rules },
{ IP_FW_XZERO, IP_FW3_OPVER, HDIR_SET, clear_rules },
{ IP_FW_XRESETLOG, IP_FW3_OPVER, HDIR_SET, clear_rules },
{ IP_FW_XMOVE, IP_FW3_OPVER, HDIR_SET, move_rules },
{ IP_FW_SET_SWAP, IP_FW3_OPVER, HDIR_SET, manage_sets },
{ IP_FW_SET_MOVE, IP_FW3_OPVER, HDIR_SET, manage_sets },
{ IP_FW_SET_ENABLE, IP_FW3_OPVER, HDIR_SET, manage_sets },
{ IP_FW_DUMP_SOPTCODES, IP_FW3_OPVER, HDIR_GET, dump_soptcodes },
{ IP_FW_DUMP_SRVOBJECTS, IP_FW3_OPVER, HDIR_GET, dump_srvobjects },
{ IP_FW_SKIPTO_CACHE, IP_FW3_OPVER, HDIR_BOTH, manage_skiptocache },
};
static struct opcode_obj_rewrite *find_op_rw(ipfw_insn *cmd,
uint32_t *puidx, uint8_t *ptype);
static int ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule,
struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti);
static int ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd,
struct tid_info *ti, struct obj_idx *pidx, int *unresolved);
static void unref_rule_objects(struct ip_fw_chain *chain, struct ip_fw *rule);
static void unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd,
struct obj_idx *oib, struct obj_idx *end);
static int export_objhash_ntlv(struct namedobj_instance *ni, uint32_t kidx,
struct sockopt_data *sd);
struct opcode_obj_rewrite *ctl3_rewriters;
static size_t ctl3_rsize;
VNET_DEFINE_STATIC(uma_zone_t, ipfw_cntr_zone);
#define V_ipfw_cntr_zone VNET(ipfw_cntr_zone)
void
ipfw_init_counters(void)
{
V_ipfw_cntr_zone = uma_zcreate("IPFW counters",
IPFW_RULE_CNTR_SIZE, NULL, NULL, NULL, NULL,
UMA_ALIGN_PTR, UMA_ZONE_PCPU);
}
void
ipfw_destroy_counters(void)
{
uma_zdestroy(V_ipfw_cntr_zone);
}
struct ip_fw *
ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize)
{
struct ip_fw *rule;
rule = malloc(rulesize, M_IPFW, M_WAITOK | M_ZERO);
rule->cntr = uma_zalloc_pcpu(V_ipfw_cntr_zone, M_WAITOK | M_ZERO);
rule->refcnt = 1;
return (rule);
}
void
ipfw_free_rule(struct ip_fw *rule)
{
if (rule->refcnt > 1)
return;
uma_zfree_pcpu(V_ipfw_cntr_zone, rule->cntr);
free(rule, M_IPFW);
}
int
ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id)
{
int i, lo, hi;
struct ip_fw *r;
for (lo = 0, hi = chain->n_rules - 1; lo < hi;) {
i = (lo + hi) / 2;
r = chain->map[i];
if (r->rulenum < key)
lo = i + 1;
else if (r->rulenum > key)
hi = i;
else if (r->id < id)
lo = i + 1;
else
hi = i;
}
return hi;
}
static void
update_skipto_cache(struct ip_fw_chain *chain, struct ip_fw **map)
{
uint32_t *smap, rulenum;
int i, mi;
IPFW_UH_WLOCK_ASSERT(chain);
mi = 0;
rulenum = map[mi]->rulenum;
smap = chain->idxmap_back;
if (smap == NULL)
return;
for (i = 0; i <= IPFW_DEFAULT_RULE; i++) {
smap[i] = mi;
if (i != rulenum || i == IPFW_DEFAULT_RULE)
continue;
rulenum = map[++mi]->rulenum;
while (rulenum == i)
rulenum = map[++mi]->rulenum;
}
}
static void
swap_skipto_cache(struct ip_fw_chain *chain)
{
uint32_t *map;
IPFW_UH_WLOCK_ASSERT(chain);
IPFW_WLOCK_ASSERT(chain);
map = chain->idxmap;
chain->idxmap = chain->idxmap_back;
chain->idxmap_back = map;
}
void
ipfw_init_skipto_cache(struct ip_fw_chain *chain)
{
uint32_t *idxmap, *idxmap_back;
idxmap = malloc((IPFW_DEFAULT_RULE + 1) * sizeof(uint32_t),
M_IPFW, M_WAITOK | M_ZERO);
idxmap_back = malloc((IPFW_DEFAULT_RULE + 1) * sizeof(uint32_t),
M_IPFW, M_WAITOK | M_ZERO);
IPFW_UH_WLOCK(chain);
if (chain->idxmap != NULL) {
IPFW_UH_WUNLOCK(chain);
free(idxmap, M_IPFW);
free(idxmap_back, M_IPFW);
return;
}
chain->idxmap_back = idxmap_back;
if (V_skipto_cache != 0)
update_skipto_cache(chain, chain->map);
IPFW_WLOCK(chain);
chain->idxmap = idxmap;
swap_skipto_cache(chain);
IPFW_WUNLOCK(chain);
IPFW_UH_WUNLOCK(chain);
}
void
ipfw_destroy_skipto_cache(struct ip_fw_chain *chain)
{
free(chain->idxmap, M_IPFW);
free(chain->idxmap_back, M_IPFW);
}
static struct ip_fw **
get_map(struct ip_fw_chain *chain, int extra, int locked)
{
for (;;) {
struct ip_fw **map;
u_int i, mflags;
mflags = M_ZERO | ((locked != 0) ? M_NOWAIT : M_WAITOK);
i = chain->n_rules + extra;
map = malloc(i * sizeof(struct ip_fw *), M_IPFW, mflags);
if (map == NULL) {
printf("%s: cannot allocate map\n", __FUNCTION__);
return NULL;
}
if (!locked)
IPFW_UH_WLOCK(chain);
if (i >= chain->n_rules + extra)
return map;
if (!locked)
IPFW_UH_WUNLOCK(chain);
free(map, M_IPFW);
}
}
static struct ip_fw **
swap_map(struct ip_fw_chain *chain, struct ip_fw **new_map, int new_len)
{
struct ip_fw **old_map;
IPFW_WLOCK(chain);
chain->id++;
chain->n_rules = new_len;
old_map = chain->map;
chain->map = new_map;
swap_skipto_cache(chain);
IPFW_WUNLOCK(chain);
return old_map;
}
static void
export_cntr1_base(struct ip_fw *krule, struct ip_fw_bcounter *cntr)
{
struct timeval boottime;
cntr->size = sizeof(*cntr);
if (krule->cntr != NULL) {
cntr->pcnt = counter_u64_fetch(krule->cntr);
cntr->bcnt = counter_u64_fetch(krule->cntr + 1);
cntr->timestamp = krule->timestamp;
}
if (cntr->timestamp > 0) {
getboottime(&boottime);
cntr->timestamp += boottime.tv_sec;
}
}
static void
export_rule1(struct ip_fw *krule, caddr_t data, int len, int rcntrs)
{
struct ip_fw_bcounter *cntr;
struct ip_fw_rule *urule;
ipfw_obj_tlv *tlv;
tlv = (ipfw_obj_tlv *)data;
tlv->type = IPFW_TLV_RULE_ENT;
tlv->length = len;
if (rcntrs != 0) {
cntr = (struct ip_fw_bcounter *)(tlv + 1);
urule = (struct ip_fw_rule *)(cntr + 1);
export_cntr1_base(krule, cntr);
} else
urule = (struct ip_fw_rule *)(tlv + 1);
urule->act_ofs = krule->act_ofs;
urule->cmd_len = krule->cmd_len;
urule->rulenum = krule->rulenum;
urule->set = krule->set;
urule->flags = krule->flags;
urule->id = krule->id;
memcpy(urule->cmd, krule->cmd, krule->cmd_len * sizeof(uint32_t));
}
int
ipfw_commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci,
int count)
{
int error, i, insert_before, tcount, rule_idx, last_rule_idx;
uint32_t rulenum;
struct rule_check_info *ci;
struct ip_fw *krule;
struct ip_fw **map;
tcount = 0;
for (ci = rci, i = 0; i < count; ci++, i++) {
if (ci->object_opcodes == 0)
continue;
error = rewrite_rule_uidx(chain, ci);
if (error != 0) {
if (tcount > 0) {
IPFW_UH_WLOCK(chain);
while (ci != rci) {
ci--;
if (ci->object_opcodes == 0)
continue;
unref_rule_objects(chain,ci->krule);
}
IPFW_UH_WUNLOCK(chain);
}
return (error);
}
tcount++;
}
map = get_map(chain, count, 0 );
if (map == NULL) {
if (tcount > 0) {
IPFW_UH_WLOCK(chain);
for (ci = rci, i = 0; i < count; ci++, i++) {
if (ci->object_opcodes == 0)
continue;
unref_rule_objects(chain, ci->krule);
}
IPFW_UH_WUNLOCK(chain);
}
return (ENOSPC);
}
if (V_autoinc_step < 1)
V_autoinc_step = 1;
else if (V_autoinc_step > 1000)
V_autoinc_step = 1000;
last_rule_idx = 0;
for (ci = rci, i = 0; i < count; ci++, i++) {
krule = ci->krule;
rulenum = krule->rulenum;
krule->id = chain->id + 1;
insert_before = rulenum ? rulenum + 1 : IPFW_DEFAULT_RULE;
rule_idx = ipfw_find_rule(chain, insert_before, 0);
if (last_rule_idx < rule_idx)
bcopy(chain->map + last_rule_idx, map + last_rule_idx + i,
(rule_idx - last_rule_idx) * sizeof(struct ip_fw *));
last_rule_idx = rule_idx;
map[rule_idx + i] = krule;
if (rulenum == 0) {
rulenum = rule_idx + i > 0 ? map[rule_idx + i - 1]->rulenum : 0;
if (rulenum < IPFW_DEFAULT_RULE - V_autoinc_step)
rulenum += V_autoinc_step;
krule->rulenum = rulenum;
memcpy((char *)ci->urule + ci->urule_numoff, &rulenum,
sizeof(rulenum));
}
}
bcopy(chain->map + last_rule_idx, map + last_rule_idx + count,
(chain->n_rules - last_rule_idx) * sizeof(struct ip_fw *));
if (V_skipto_cache != 0)
update_skipto_cache(chain, map);
map = swap_map(chain, map, chain->n_rules + count);
IPFW_UH_WUNLOCK(chain);
if (map)
free(map, M_IPFW);
return (0);
}
int
ipfw_add_protected_rule(struct ip_fw_chain *chain, struct ip_fw *rule,
int locked)
{
struct ip_fw **map;
map = get_map(chain, 1, locked);
if (map == NULL)
return (ENOMEM);
if (chain->n_rules > 0)
bcopy(chain->map, map,
chain->n_rules * sizeof(struct ip_fw *));
map[chain->n_rules] = rule;
rule->rulenum = IPFW_DEFAULT_RULE;
rule->set = RESVD_SET;
rule->id = chain->id + 1;
map = swap_map(chain, map, chain->n_rules + 1);
IPFW_UH_WUNLOCK(chain);
free(map, M_IPFW);
return (0);
}
void
ipfw_reap_add(struct ip_fw_chain *chain, struct ip_fw **head,
struct ip_fw *rule)
{
IPFW_UH_WLOCK_ASSERT(chain);
unref_rule_objects(chain, rule);
rule->next = *head;
*head = rule;
}
void
ipfw_reap_rules(struct ip_fw *head)
{
struct ip_fw *rule;
while ((rule = head) != NULL) {
head = head->next;
ipfw_free_rule(rule);
}
}
int
ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt)
{
if (rule->rulenum == IPFW_DEFAULT_RULE &&
(rt->flags & IPFW_RCFLAG_DEFAULT) == 0)
return (0);
if ((rt->flags & IPFW_RCFLAG_ALL) != 0 && rule->set == RESVD_SET)
return (0);
if ((rt->flags & IPFW_RCFLAG_SET) != 0 && rule->set != rt->set)
return (0);
if ((rt->flags & IPFW_RCFLAG_RANGE) != 0 &&
(rule->rulenum < rt->start_rule || rule->rulenum > rt->end_rule))
return (0);
return (1);
}
struct manage_sets_args {
uint32_t set;
uint8_t new_set;
};
static int
swap_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set == (uint8_t)args->set)
no->set = args->new_set;
else if (no->set == args->new_set)
no->set = (uint8_t)args->set;
return (0);
}
static int
move_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set == (uint8_t)args->set)
no->set = args->new_set;
return (0);
}
static int
test_sets_cb(struct namedobj_instance *ni, struct named_object *no,
void *arg)
{
struct manage_sets_args *args;
args = (struct manage_sets_args *)arg;
if (no->set != (uint8_t)args->set)
return (0);
if (ipfw_objhash_lookup_name_type(ni, args->new_set,
no->etlv, no->name) != NULL)
return (EEXIST);
return (0);
}
int
ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type,
uint32_t set, uint8_t new_set, enum ipfw_sets_cmd cmd)
{
struct manage_sets_args args;
struct named_object *no;
args.set = set;
args.new_set = new_set;
switch (cmd) {
case SWAP_ALL:
return (ipfw_objhash_foreach_type(ni, swap_sets_cb,
&args, type));
case TEST_ALL:
return (ipfw_objhash_foreach_type(ni, test_sets_cb,
&args, type));
case MOVE_ALL:
return (ipfw_objhash_foreach_type(ni, move_sets_cb,
&args, type));
case COUNT_ONE:
no = ipfw_objhash_lookup_kidx(ni, set);
if (new_set != 0)
no->ocnt++;
else
no->ocnt = 0;
return (0);
case TEST_ONE:
no = ipfw_objhash_lookup_kidx(ni, set);
if (no->ocnt != no->refcnt)
return (EBUSY);
if (ipfw_objhash_lookup_name_type(ni, new_set, type,
no->name) != NULL)
return (EEXIST);
return (0);
case MOVE_ONE:
no = ipfw_objhash_lookup_kidx(ni, set);
no->set = new_set;
return (0);
}
return (EINVAL);
}
int
delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel)
{
struct ip_fw *reap, *rule, **map;
uint32_t end, start;
int i, n, ndyn, ofs;
reap = NULL;
IPFW_UH_WLOCK(chain);
start = 0;
end = chain->n_rules - 1;
if ((rt->flags & IPFW_RCFLAG_RANGE) != 0) {
start = ipfw_find_rule(chain, rt->start_rule, 0);
if (rt->end_rule >= IPFW_DEFAULT_RULE)
rt->end_rule = IPFW_DEFAULT_RULE - 1;
end = ipfw_find_rule(chain, rt->end_rule, UINT32_MAX);
}
if (rt->flags & IPFW_RCFLAG_DYNAMIC) {
*ndel = 0;
ipfw_expire_dyn_states(chain, rt);
IPFW_UH_WUNLOCK(chain);
return (0);
}
map = get_map(chain, 0, 1 );
if (map == NULL) {
IPFW_UH_WUNLOCK(chain);
return (ENOMEM);
}
n = 0;
ndyn = 0;
ofs = start;
if (start > 0)
bcopy(chain->map, map, start * sizeof(struct ip_fw *));
for (i = start; i < end; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0) {
map[ofs++] = rule;
continue;
}
n++;
if (ipfw_is_dyn_rule(rule) != 0)
ndyn++;
}
bcopy(chain->map + end, map + ofs,
(chain->n_rules - end) * sizeof(struct ip_fw *));
update_skipto_cache(chain, map);
map = swap_map(chain, map, chain->n_rules - n);
if (ndyn > 0)
ipfw_expire_dyn_states(chain, rt);
for (i = start; i < end; i++) {
rule = map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
ipfw_reap_add(chain, &reap, rule);
}
IPFW_UH_WUNLOCK(chain);
ipfw_reap_rules(reap);
if (map != NULL)
free(map, M_IPFW);
*ndel = n;
return (0);
}
static int
move_objects(struct ip_fw_chain *ch, ipfw_range_tlv *rt)
{
struct opcode_obj_rewrite *rw;
struct ip_fw *rule;
ipfw_insn *cmd;
uint32_t kidx;
int cmdlen, i, l, c;
IPFW_UH_WLOCK_ASSERT(ch);
for (c = 0, i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set)
continue;
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
if (rw->manage_sets(ch, kidx, 1, COUNT_ONE) != 0)
continue;
c++;
}
}
if (c == 0)
return (0);
for (c = 0, i = 0; (i < ch->n_rules - 1) && c == 0; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set)
continue;
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0 && c == 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
c = rw->manage_sets(ch, kidx,
(uint8_t)rt->new_set, TEST_ONE);
}
}
for (i = 0; i < ch->n_rules - 1; i++) {
rule = ch->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
if (rule->set == rt->new_set)
continue;
for (l = rule->cmd_len, cmdlen = 0, cmd = rule->cmd;
l > 0; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, NULL);
if (rw == NULL || rw->manage_sets == NULL)
continue;
rw->manage_sets(ch, kidx,
0 , COUNT_ONE);
if (c != 0)
continue;
rw->manage_sets(ch, kidx,
(uint8_t)rt->new_set, MOVE_ONE);
}
}
return (c);
}
static int
move_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
{
struct ip_fw *rule;
int i;
IPFW_UH_WLOCK(chain);
if ((i = move_objects(chain, rt)) != 0) {
IPFW_UH_WUNLOCK(chain);
return (i);
}
for (i = 0; i < chain->n_rules; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
rule->set = rt->new_set;
}
IPFW_UH_WUNLOCK(chain);
return (0);
}
ipfw_insn *
ipfw_get_action(struct ip_fw *rule)
{
ipfw_insn *cmd;
int l, cmdlen;
cmd = ACTION_PTR(rule);
l = rule->cmd_len - rule->act_ofs;
while (l > 0) {
switch (cmd->opcode) {
case O_ALTQ:
case O_LOG:
case O_TAG:
break;
default:
return (cmd);
}
cmdlen = F_LEN(cmd);
l -= cmdlen;
cmd += cmdlen;
}
panic("%s: rule (%p) has not action opcode", __func__, rule);
return (NULL);
}
static void
clear_counters(struct ip_fw *rule, int log_only)
{
ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule);
if (log_only == 0)
IPFW_ZERO_RULE_COUNTER(rule);
if (l->o.opcode == O_LOG)
l->log_left = l->max_log;
}
static int
clear_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int log_only)
{
struct ip_fw *rule;
int num;
int i;
num = 0;
rt->flags |= IPFW_RCFLAG_DEFAULT;
IPFW_UH_WLOCK(chain);
for (i = 0; i < chain->n_rules; i++) {
rule = chain->map[i];
if (ipfw_match_range(rule, rt) == 0)
continue;
clear_counters(rule, log_only);
num++;
}
IPFW_UH_WUNLOCK(chain);
return (num);
}
static int
check_range_tlv(ipfw_range_tlv *rt)
{
if (rt->head.length != sizeof(*rt))
return (1);
if (rt->start_rule > rt->end_rule)
return (1);
if (rt->set >= IPFW_MAX_SETS || rt->new_set >= IPFW_MAX_SETS)
return (1);
if ((rt->flags & IPFW_RCFLAG_USER) != rt->flags)
return (1);
return (0);
}
static int
del_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int error, ndel;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
ndel = 0;
if ((error = delete_range(chain, &rh->range, &ndel)) != 0)
return (error);
rh->range.new_set = ndel;
return (0);
}
static int
move_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
return (move_range(chain, &rh->range));
}
static int
clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int log_only, num;
char *msg;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (check_range_tlv(&rh->range) != 0)
return (EINVAL);
log_only = (op3->opcode == IP_FW_XRESETLOG);
num = clear_range(chain, &rh->range, log_only);
if (rh->range.flags & IPFW_RCFLAG_ALL)
msg = log_only ? "All logging counts reset" :
"Accounting cleared";
else
msg = log_only ? "logging count reset" : "cleared";
if (V_fw_verbose) {
int lev = LOG_SECURITY | LOG_NOTICE;
log(lev, "ipfw: %s.\n", msg);
}
rh->range.new_set = num;
return (0);
}
static void
enable_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt)
{
uint32_t v_set;
IPFW_UH_WLOCK_ASSERT(chain);
v_set = (V_set_disable | rt->set) & ~rt->new_set;
v_set &= ~(1 << RESVD_SET);
IPFW_WLOCK(chain);
V_set_disable = v_set;
IPFW_WUNLOCK(chain);
}
static int
swap_sets(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int mv)
{
struct opcode_obj_rewrite *rw;
struct ip_fw *rule;
int i;
IPFW_UH_WLOCK_ASSERT(chain);
if (rt->set == rt->new_set)
return (0);
if (mv != 0) {
for (rw = ctl3_rewriters;
rw < ctl3_rewriters + ctl3_rsize; rw++) {
if (rw->manage_sets == NULL)
continue;
i = rw->manage_sets(chain, (uint8_t)rt->set,
(uint8_t)rt->new_set, TEST_ALL);
if (i != 0)
return (EEXIST);
}
}
for (i = 0; i < chain->n_rules - 1; i++) {
rule = chain->map[i];
if (rule->set == (uint8_t)rt->set)
rule->set = (uint8_t)rt->new_set;
else if (rule->set == (uint8_t)rt->new_set && mv == 0)
rule->set = (uint8_t)rt->set;
}
for (rw = ctl3_rewriters; rw < ctl3_rewriters + ctl3_rsize; rw++) {
if (rw->manage_sets == NULL)
continue;
rw->manage_sets(chain, (uint8_t)rt->set,
(uint8_t)rt->new_set, mv != 0 ? MOVE_ALL: SWAP_ALL);
}
return (0);
}
static int
manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_range_header *rh;
int ret;
if (sd->valsize != sizeof(*rh))
return (EINVAL);
rh = (ipfw_range_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (rh->range.head.length != sizeof(ipfw_range_tlv))
return (1);
if (op3->opcode != IP_FW_SET_ENABLE &&
(rh->range.set >= IPFW_MAX_SETS ||
rh->range.new_set >= IPFW_MAX_SETS))
return (EINVAL);
ret = 0;
IPFW_UH_WLOCK(chain);
switch (op3->opcode) {
case IP_FW_SET_SWAP:
case IP_FW_SET_MOVE:
ret = swap_sets(chain, &rh->range,
op3->opcode == IP_FW_SET_MOVE);
break;
case IP_FW_SET_ENABLE:
enable_sets(chain, &rh->range);
break;
}
IPFW_UH_WUNLOCK(chain);
return (ret);
}
int
ipfw_check_rule(struct ip_fw_rule *rule, size_t size,
struct rule_check_info *ci)
{
int l;
if (size < sizeof(*rule)) {
printf("ipfw: rule too short\n");
return (EINVAL);
}
l = roundup2(RULESIZE(rule), sizeof(uint64_t));
if (l != size) {
printf("ipfw: size mismatch (have %zu want %d)\n", size, l);
return (EINVAL);
}
if (rule->act_ofs >= rule->cmd_len) {
printf("ipfw: bogus action offset (%u > %u)\n",
rule->act_ofs, rule->cmd_len - 1);
return (EINVAL);
}
if (rule->rulenum > IPFW_DEFAULT_RULE - 1)
return (EINVAL);
return (check_ipfw_rule_body(rule->cmd, rule->cmd_len, ci));
}
#define CHECK_TARG(a, c) \
((a) == IP_FW_TARG && ((c)->flags & IPFW_RCIFLAG_HAS_STATE))
enum ipfw_opcheck_result
ipfw_check_opcode(ipfw_insn **pcmd, int *plen, struct rule_check_info *ci)
{
ipfw_insn *cmd;
size_t cmdlen;
cmd = *pcmd;
cmdlen = F_LEN(cmd);
switch (cmd->opcode) {
case O_PROBE_STATE:
case O_KEEP_STATE:
if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx))
return (BAD_SIZE);
ci->object_opcodes++;
ci->flags |= IPFW_RCIFLAG_HAS_STATE;
break;
case O_PROTO:
case O_IP_SRC_ME:
case O_IP_DST_ME:
case O_LAYER2:
case O_IN:
case O_FRAG:
case O_DIVERTED:
case O_IPOPT:
case O_IPTOS:
case O_IPPRECEDENCE:
case O_IPVER:
case O_SOCKARG:
case O_TCPFLAGS:
case O_TCPOPTS:
case O_ESTAB:
case O_VERREVPATH:
case O_VERSRCREACH:
case O_ANTISPOOF:
case O_IPSEC:
#ifdef INET6
case O_IP6_SRC_ME:
case O_IP6_DST_ME:
case O_EXT_HDR:
case O_IP6:
#endif
case O_IP4:
case O_TAG:
case O_SKIP_ACTION:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
break;
case O_EXTERNAL_ACTION:
if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx))
return (BAD_SIZE);
if (insntod(cmd, kidx)->kidx == 0)
return (FAILED);
ci->object_opcodes++;
if (*plen != cmdlen) {
*plen -= cmdlen;
cmd += cmdlen;
*pcmd = cmd;
cmdlen = F_LEN(cmd);
if (cmd->opcode == O_EXTERNAL_DATA)
return (CHECK_ACTION);
if (cmd->opcode != O_EXTERNAL_INSTANCE) {
printf("ipfw: invalid opcode "
"next to external action %u\n",
cmd->opcode);
return (FAILED);
}
if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx))
return (BAD_SIZE);
if (insntod(cmd, kidx)->kidx == 0)
return (FAILED);
ci->object_opcodes++;
}
return (CHECK_ACTION);
case O_FIB:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if (cmd->arg1 >= rt_numfibs) {
printf("ipfw: invalid fib number %d\n",
cmd->arg1);
return (FAILED);
}
break;
case O_SETFIB:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if ((cmd->arg1 != IP_FW_TARG) &&
((cmd->arg1 & 0x7FFF) >= rt_numfibs)) {
printf("ipfw: invalid fib number %d\n",
cmd->arg1 & 0x7FFF);
return (FAILED);
}
if (CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
case O_UID:
case O_GID:
case O_JAIL:
case O_IP_SRC:
case O_IP_DST:
case O_TCPSEQ:
case O_TCPACK:
case O_PROB:
case O_ICMPTYPE:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32))
return (BAD_SIZE);
break;
case O_LIMIT:
if (cmdlen != F_INSN_SIZE(ipfw_insn_limit))
return (BAD_SIZE);
ci->object_opcodes++;
break;
case O_LOG:
if (cmdlen != F_INSN_SIZE(ipfw_insn_log))
return (BAD_SIZE);
insntod(cmd, log)->log_left = insntod(cmd, log)->max_log;
break;
case O_IP_SRC_MASK:
case O_IP_DST_MASK:
if ((cmdlen & 1) == 0)
return (BAD_SIZE);
break;
case O_IP_SRC_SET:
case O_IP_DST_SET:
if (cmd->arg1 == 0 || cmd->arg1 > 256) {
printf("ipfw: invalid set size %d\n",
cmd->arg1);
return (FAILED);
}
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) +
(cmd->arg1+31)/32 )
return (BAD_SIZE);
break;
case O_IP_SRC_LOOKUP:
case O_IP_DST_LOOKUP:
case O_IP_FLOW_LOOKUP:
case O_MAC_SRC_LOOKUP:
case O_MAC_DST_LOOKUP:
if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx) &&
cmdlen != F_INSN_SIZE(ipfw_insn_table))
return (BAD_SIZE);
if (insntod(cmd, kidx)->kidx >= V_fw_tables_max) {
printf("ipfw: invalid table index %u\n",
insntod(cmd, kidx)->kidx);
return (FAILED);
}
ci->object_opcodes++;
break;
case O_MACADDR2:
if (cmdlen != F_INSN_SIZE(ipfw_insn_mac))
return (BAD_SIZE);
break;
case O_NOP:
case O_IPID:
case O_IPTTL:
case O_IPLEN:
case O_TCPDATALEN:
case O_TCPMSS:
case O_TCPWIN:
case O_TAGGED:
if (cmdlen < 1 || cmdlen > 31)
return (BAD_SIZE);
break;
case O_DSCP:
case O_MARK:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) + 1)
return (BAD_SIZE);
break;
case O_MAC_TYPE:
case O_IP_SRCPORT:
case O_IP_DSTPORT:
if (cmdlen < 2 || cmdlen > 31)
return (BAD_SIZE);
break;
case O_RECV:
case O_XMIT:
case O_VIA:
if (cmdlen != F_INSN_SIZE(ipfw_insn_if))
return (BAD_SIZE);
ci->object_opcodes++;
break;
case O_ALTQ:
if (cmdlen != F_INSN_SIZE(ipfw_insn_altq))
return (BAD_SIZE);
break;
case O_PIPE:
case O_QUEUE:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if (CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
case O_FORWARD_IP:
if (cmdlen != F_INSN_SIZE(ipfw_insn_sa))
return (BAD_SIZE);
if (insntoc(cmd, sa)->sa.sin_addr.s_addr == INADDR_ANY &&
(ci->flags & IPFW_RCIFLAG_HAS_STATE))
goto bad_targ;
return (CHECK_ACTION);
#ifdef INET6
case O_FORWARD_IP6:
if (cmdlen != F_INSN_SIZE(ipfw_insn_sa6))
return (BAD_SIZE);
return (CHECK_ACTION);
#endif
case O_DIVERT:
case O_TEE:
if (ip_divert_ptr == NULL)
return (FAILED);
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if (CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
case O_NETGRAPH:
case O_NGTEE:
if (ng_ipfw_input_p == NULL)
return (FAILED);
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if (CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
case O_NAT:
if (!IPFW_NAT_LOADED)
return (FAILED);
if (cmdlen != F_INSN_SIZE(ipfw_insn_nat))
return (BAD_SIZE);
if (CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
case O_SKIPTO:
case O_CALLRETURN:
case O_SETMARK:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32))
return (BAD_SIZE);
if (cmd->opcode != O_CALLRETURN || (cmd->len & F_NOT) == 0) {
if (CHECK_TARG(insntoc(cmd, u32)->d[0], ci))
goto bad_targ;
}
return (CHECK_ACTION);
case O_CHECK_STATE:
if (cmdlen != F_INSN_SIZE(ipfw_insn_kidx))
return (BAD_SIZE);
ci->object_opcodes++;
return (CHECK_ACTION);
case O_FORWARD_MAC:
case O_COUNT:
case O_ACCEPT:
case O_DENY:
case O_REJECT:
case O_SETDSCP:
#ifdef INET6
case O_UNREACH6:
#endif
case O_REASS:
if (cmdlen != F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
if (cmd->opcode == O_SETDSCP && CHECK_TARG(cmd->arg1, ci))
goto bad_targ;
return (CHECK_ACTION);
#ifdef INET6
case O_IP6_SRC:
case O_IP6_DST:
if (cmdlen != F_INSN_SIZE(struct in6_addr) +
F_INSN_SIZE(ipfw_insn))
return (BAD_SIZE);
break;
case O_FLOW6ID:
if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) +
((ipfw_insn_u32 *)cmd)->o.arg1)
return (BAD_SIZE);
break;
case O_IP6_SRC_MASK:
case O_IP6_DST_MASK:
if ( !(cmdlen & 1) || cmdlen > 127)
return (BAD_SIZE);
break;
case O_ICMP6TYPE:
if( cmdlen != F_INSN_SIZE( ipfw_insn_icmp6 ) )
return (BAD_SIZE);
break;
#endif
default:
switch (cmd->opcode) {
#ifndef INET6
case O_IP6_SRC_ME:
case O_IP6_DST_ME:
case O_EXT_HDR:
case O_IP6:
case O_UNREACH6:
case O_IP6_SRC:
case O_IP6_DST:
case O_FLOW6ID:
case O_IP6_SRC_MASK:
case O_IP6_DST_MASK:
case O_ICMP6TYPE:
printf("ipfw: no IPv6 support in kernel\n");
return (FAILED);
#endif
default:
printf("ipfw: opcode %d: unknown opcode\n",
cmd->opcode);
return (FAILED);
}
}
return (SUCCESS);
bad_targ:
printf("ipfw: tablearg is not allowed with dynamic states\n");
return (FAILED);
}
static __noinline int
check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci)
{
int cmdlen, l;
int have_action, ret;
have_action = 0;
for (l = cmd_len; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
if (cmdlen > l) {
printf("ipfw: opcode %d: size truncated\n",
cmd->opcode);
return (EINVAL);
}
if (ci->version != IP_FW3_OPVER)
ret = (*check_opcode_f)(&cmd, &l, ci);
else
ret = ipfw_check_opcode(&cmd, &l, ci);
if (ret == CHECK_ACTION) {
if (have_action != 0) {
printf("ipfw: opcode %d: multiple actions"
" not allowed\n", cmd->opcode);
ret = FAILED;
} else
have_action = 1;
if (l != F_LEN(cmd)) {
printf("ipfw: opcode %d: action must be"
" last opcode\n", cmd->opcode);
ret = FAILED;
}
}
switch (ret) {
case SUCCESS:
continue;
case BAD_SIZE:
printf("ipfw: opcode %d: wrong size %d\n",
cmd->opcode, cmdlen);
case FAILED:
return (EINVAL);
}
}
if (have_action == 0) {
printf("ipfw: missing action\n");
return (EINVAL);
}
return (0);
}
struct dump_args {
uint32_t b;
uint32_t e;
uint32_t rcount;
uint32_t rsize;
uint32_t tcount;
int rcounters;
uint32_t *bmask;
};
void
ipfw_export_obj_ntlv(struct named_object *no, ipfw_obj_ntlv *ntlv)
{
ntlv->head.type = no->etlv;
ntlv->head.length = sizeof(*ntlv);
ntlv->idx = no->kidx;
strlcpy(ntlv->name, no->name, sizeof(ntlv->name));
}
static int
export_objhash_ntlv(struct namedobj_instance *ni, uint32_t kidx,
struct sockopt_data *sd)
{
struct named_object *no;
ipfw_obj_ntlv *ntlv;
no = ipfw_objhash_lookup_kidx(ni, kidx);
KASSERT(no != NULL, ("invalid object kernel index passed"));
ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv));
if (ntlv == NULL)
return (ENOMEM);
ipfw_export_obj_ntlv(no, ntlv);
return (0);
}
static int
export_named_objects(struct namedobj_instance *ni, struct dump_args *da,
struct sockopt_data *sd)
{
uint32_t i;
int error;
for (i = 0; i < IPFW_TABLES_MAX && da->tcount > 0; i++) {
if ((da->bmask[i / 32] & (1 << (i % 32))) == 0)
continue;
if ((error = export_objhash_ntlv(ni, i, sd)) != 0)
return (error);
da->tcount--;
}
return (0);
}
static int
dump_named_objects(struct ip_fw_chain *ch, struct dump_args *da,
struct sockopt_data *sd)
{
ipfw_obj_ctlv *ctlv;
int error;
MPASS(da->tcount > 0);
ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv));
if (ctlv == NULL)
return (ENOMEM);
ctlv->head.type = IPFW_TLV_TBLNAME_LIST;
ctlv->head.length = da->tcount * sizeof(ipfw_obj_ntlv) +
sizeof(*ctlv);
ctlv->count = da->tcount;
ctlv->objsize = sizeof(ipfw_obj_ntlv);
error = export_named_objects(ipfw_get_table_objhash(ch), da, sd);
if (error != 0)
return (error);
da->bmask += IPFW_TABLES_MAX / 32;
return (export_named_objects(CHAIN_TO_SRV(ch), da, sd));
}
static int
dump_static_rules(struct ip_fw_chain *chain, struct dump_args *da,
struct sockopt_data *sd)
{
ipfw_obj_ctlv *ctlv;
struct ip_fw *krule;
caddr_t dst;
int i, l;
ctlv = (ipfw_obj_ctlv *)ipfw_get_sopt_space(sd, sizeof(*ctlv));
if (ctlv == NULL)
return (ENOMEM);
ctlv->head.type = IPFW_TLV_RULE_LIST;
ctlv->head.length = da->rsize + sizeof(*ctlv);
ctlv->count = da->rcount;
for (i = da->b; i < da->e; i++) {
krule = chain->map[i];
l = RULEUSIZE1(krule) + sizeof(ipfw_obj_tlv);
if (da->rcounters != 0)
l += sizeof(struct ip_fw_bcounter);
dst = (caddr_t)ipfw_get_sopt_space(sd, l);
if (dst == NULL)
return (ENOMEM);
export_rule1(krule, dst, l, da->rcounters);
}
return (0);
}
int
ipfw_mark_object_kidx(uint32_t *bmask, uint16_t etlv, uint32_t kidx)
{
uint32_t bidx;
bidx = (etlv == IPFW_TLV_TBL_NAME) ? 0: IPFW_TABLES_MAX / 32;
bidx += kidx / 32;
if ((bmask[bidx] & (1 << (kidx % 32))) != 0)
return (0);
bmask[bidx] |= 1 << (kidx % 32);
return (1);
}
static void
mark_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule,
struct dump_args *da)
{
struct opcode_obj_rewrite *rw;
ipfw_insn *cmd;
uint32_t kidx;
int cmdlen, l;
uint8_t subtype;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, &subtype);
if (rw == NULL)
continue;
if (ipfw_mark_object_kidx(da->bmask, rw->etlv, kidx))
da->tcount++;
}
}
static int
dump_config(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
struct dump_args da;
ipfw_cfg_lheader *hdr;
struct ip_fw *rule;
size_t sz, rnum;
uint32_t hdr_flags, *bmask;
int error, i;
hdr = (ipfw_cfg_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr));
if (hdr == NULL)
return (EINVAL);
error = 0;
bmask = NULL;
memset(&da, 0, sizeof(da));
if (hdr->flags & (IPFW_CFG_GET_STATIC | IPFW_CFG_GET_STATES))
da.bmask = bmask = malloc(
sizeof(uint32_t) * IPFW_TABLES_MAX * 2 / 32, M_TEMP,
M_WAITOK | M_ZERO);
IPFW_UH_RLOCK(chain);
sz = sizeof(ipfw_cfg_lheader);
da.e = chain->n_rules;
if (hdr->end_rule != 0) {
if ((rnum = hdr->start_rule) > IPFW_DEFAULT_RULE)
rnum = IPFW_DEFAULT_RULE;
da.b = ipfw_find_rule(chain, rnum, 0);
rnum = (hdr->end_rule < IPFW_DEFAULT_RULE) ?
hdr->end_rule + 1: IPFW_DEFAULT_RULE;
da.e = ipfw_find_rule(chain, rnum, UINT32_MAX) + 1;
}
if (hdr->flags & IPFW_CFG_GET_STATIC) {
for (i = da.b; i < da.e; i++) {
rule = chain->map[i];
da.rsize += RULEUSIZE1(rule) + sizeof(ipfw_obj_tlv);
da.rcount++;
mark_rule_objects(chain, rule, &da);
}
if (hdr->flags & IPFW_CFG_GET_COUNTERS) {
da.rsize += sizeof(struct ip_fw_bcounter) * da.rcount;
da.rcounters = 1;
}
sz += da.rsize + sizeof(ipfw_obj_ctlv);
}
if (hdr->flags & IPFW_CFG_GET_STATES) {
sz += sizeof(ipfw_obj_ctlv) +
ipfw_dyn_get_count(bmask, &i) * sizeof(ipfw_obj_dyntlv);
da.tcount += i;
}
if (da.tcount > 0)
sz += da.tcount * sizeof(ipfw_obj_ntlv) +
sizeof(ipfw_obj_ctlv);
hdr->size = sz;
hdr->set_mask = ~V_set_disable;
hdr_flags = hdr->flags;
hdr = NULL;
if (sd->valsize < sz) {
error = ENOMEM;
goto cleanup;
}
if (da.tcount > 0) {
error = dump_named_objects(chain, &da, sd);
if (error != 0)
goto cleanup;
}
if (hdr_flags & IPFW_CFG_GET_STATIC) {
error = dump_static_rules(chain, &da, sd);
if (error != 0)
goto cleanup;
}
if (hdr_flags & IPFW_CFG_GET_STATES)
error = ipfw_dump_states(chain, sd);
cleanup:
IPFW_UH_RUNLOCK(chain);
if (bmask != NULL)
free(bmask, M_TEMP);
return (error);
}
int
ipfw_check_object_name_generic(const char *name)
{
int nsize;
nsize = sizeof(((ipfw_obj_ntlv *)0)->name);
if (strnlen(name, nsize) == nsize)
return (EINVAL);
if (name[0] == '\0')
return (EINVAL);
return (0);
}
int
create_objects_compat(struct ip_fw_chain *ch, ipfw_insn *cmd,
struct obj_idx *oib, struct obj_idx *pidx, struct tid_info *ti)
{
struct opcode_obj_rewrite *rw;
struct obj_idx *p;
uint32_t kidx;
int error;
for (p = oib; p < pidx; p++) {
if (p->kidx != 0)
continue;
ti->uidx = p->uidx;
ti->type = p->type;
ti->atype = 0;
rw = find_op_rw(cmd + p->off, NULL, NULL);
KASSERT(rw != NULL, ("Unable to find handler for op %d",
(cmd + p->off)->opcode));
if (rw->create_object == NULL)
error = EOPNOTSUPP;
else
error = rw->create_object(ch, ti, &kidx);
if (error == 0) {
p->kidx = kidx;
continue;
}
IPFW_UH_WLOCK(ch);
unref_oib_objects(ch, cmd, oib, pidx);
IPFW_UH_WUNLOCK(ch);
return (error);
}
return (0);
}
static void
unref_oib_objects(struct ip_fw_chain *ch, ipfw_insn *cmd, struct obj_idx *oib,
struct obj_idx *end)
{
struct opcode_obj_rewrite *rw;
struct named_object *no;
struct obj_idx *p;
IPFW_UH_WLOCK_ASSERT(ch);
for (p = oib; p < end; p++) {
if (p->kidx == 0)
continue;
rw = find_op_rw(cmd + p->off, NULL, NULL);
KASSERT(rw != NULL, ("Unable to find handler for op %d",
(cmd + p->off)->opcode));
no = rw->find_bykidx(ch, p->kidx);
KASSERT(no != NULL, ("Ref'd object %d disappeared", p->kidx));
no->refcnt--;
}
}
static void
unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule)
{
struct opcode_obj_rewrite *rw;
struct named_object *no;
ipfw_insn *cmd;
uint32_t kidx;
int cmdlen, l;
uint8_t subtype;
IPFW_UH_WLOCK_ASSERT(ch);
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
rw = find_op_rw(cmd, &kidx, &subtype);
if (rw == NULL)
continue;
no = rw->find_bykidx(ch, kidx);
KASSERT(no != NULL, ("object id %d not found", kidx));
KASSERT(no->subtype == subtype,
("wrong type %d (%d) for object id %d",
no->subtype, subtype, kidx));
KASSERT(no->refcnt > 0, ("refcount for object %d is %d",
kidx, no->refcnt));
if (no->refcnt == 1 && rw->destroy_object != NULL)
rw->destroy_object(ch, no);
else
no->refcnt--;
}
}
static int
ref_opcode_object(struct ip_fw_chain *ch, ipfw_insn *cmd, struct tid_info *ti,
struct obj_idx *pidx, int *unresolved)
{
struct named_object *no;
struct opcode_obj_rewrite *rw;
int error;
rw = find_op_rw(cmd, &ti->uidx, &ti->type);
if (rw == NULL)
return (0);
pidx->uidx = ti->uidx;
pidx->type = ti->type;
error = rw->find_byname(ch, ti, &no);
if (error != 0)
return (error);
if (no == NULL) {
*unresolved = 1;
return (0);
}
if (ti->type != no->subtype)
return (EINVAL);
no->refcnt++;
rw->update(cmd, no->kidx);
return (0);
}
static int
ref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule,
struct rule_check_info *ci, struct obj_idx *oib, struct tid_info *ti)
{
struct obj_idx *pidx;
ipfw_insn *cmd;
int cmdlen, error, l, unresolved;
pidx = oib;
l = rule->cmd_len;
cmd = rule->cmd;
cmdlen = 0;
error = 0;
IPFW_UH_WLOCK(ch);
for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) {
cmdlen = F_LEN(cmd);
unresolved = 0;
error = ref_opcode_object(ch, cmd, ti, pidx, &unresolved);
if (error != 0)
break;
if (unresolved != 0) {
pidx->off = rule->cmd_len - l;
pidx++;
}
}
if (error != 0) {
unref_oib_objects(ch, rule->cmd, oib, pidx);
IPFW_UH_WUNLOCK(ch);
return (error);
}
IPFW_UH_WUNLOCK(ch);
if (pidx != oib)
error = create_objects_compat(ch, rule->cmd, oib, pidx, ti);
ci->object_opcodes = (uint16_t)(pidx - oib);
return (error);
}
static int
rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci)
{
int error;
ipfw_insn *cmd;
struct obj_idx *p, *pidx_first, *pidx_last;
struct tid_info ti;
if (ci->object_opcodes <= (sizeof(ci->obuf)/sizeof(ci->obuf[0]))) {
pidx_first = ci->obuf;
} else
pidx_first = malloc(
ci->object_opcodes * sizeof(struct obj_idx),
M_IPFW, M_WAITOK | M_ZERO);
error = 0;
memset(&ti, 0, sizeof(ti));
ti.set = ci->krule->set;
if (ci->ctlv != NULL) {
ti.tlvs = (void *)(ci->ctlv + 1);
ti.tlen = ci->ctlv->head.length - sizeof(ipfw_obj_ctlv);
}
error = ref_rule_objects(chain, ci->krule, ci, pidx_first, &ti);
if (error != 0)
goto free;
p = pidx_first;
pidx_last = pidx_first + ci->object_opcodes;
for (p = pidx_first; p < pidx_last; p++) {
cmd = ci->krule->cmd + p->off;
update_opcode_kidx(cmd, p->kidx);
}
free:
if (pidx_first != ci->obuf)
free(pidx_first, M_IPFW);
return (error);
}
static __noinline int
parse_rules_v1(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd, ipfw_obj_ctlv **prtlv,
struct rule_check_info **pci)
{
ipfw_obj_ctlv *ctlv, *rtlv, *tstate;
ipfw_obj_ntlv *ntlv;
struct rule_check_info *ci, *cbuf;
struct ip_fw_rule *r;
size_t count, clen, read, rsize;
uint32_t idx, rulenum;
int error;
op3 = (ip_fw3_opheader *)ipfw_get_sopt_space(sd, sd->valsize);
ctlv = (ipfw_obj_ctlv *)(op3 + 1);
read = sizeof(ip_fw3_opheader);
if (read + sizeof(*ctlv) > sd->valsize)
return (EINVAL);
rtlv = NULL;
tstate = NULL;
cbuf = NULL;
if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) {
clen = ctlv->head.length;
if (read + clen > sd->valsize || clen < sizeof(*ctlv) ||
(clen % sizeof(uint64_t)) != 0)
return (EINVAL);
count = (ctlv->head.length - sizeof(*ctlv)) / sizeof(*ntlv);
if (ctlv->count != count || ctlv->objsize != sizeof(*ntlv))
return (EINVAL);
idx = 0;
ntlv = (ipfw_obj_ntlv *)(ctlv + 1);
while (count > 0) {
if (ntlv->head.length != sizeof(ipfw_obj_ntlv))
return (EINVAL);
error = ipfw_check_object_name_generic(ntlv->name);
if (error != 0)
return (error);
if (ntlv->idx <= idx)
return (EINVAL);
idx = ntlv->idx;
count--;
ntlv++;
}
tstate = ctlv;
read += ctlv->head.length;
ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
if (read + sizeof(*ctlv) > sd->valsize)
return (EINVAL);
}
if (ctlv->head.type == IPFW_TLV_RULE_LIST) {
clen = ctlv->head.length;
if (read + clen > sd->valsize || clen < sizeof(*ctlv) ||
(clen % sizeof(uint64_t)) != 0)
return (EINVAL);
clen -= sizeof(*ctlv);
if (ctlv->count == 0 ||
ctlv->count > clen / sizeof(struct ip_fw_rule))
return (EINVAL);
cbuf = malloc(ctlv->count * sizeof(struct rule_check_info),
M_TEMP, M_WAITOK | M_ZERO);
rulenum = 0;
count = 0;
error = 0;
ci = cbuf;
r = (struct ip_fw_rule *)(ctlv + 1);
while (clen > 0) {
rsize = RULEUSIZE1(r);
if (rsize > clen || count > ctlv->count) {
error = EINVAL;
break;
}
ci->ctlv = tstate;
ci->version = IP_FW3_OPVER;
error = ipfw_check_rule(r, rsize, ci);
if (error != 0)
break;
if (count != 0 && ((rulenum == 0) != (r->rulenum == 0) ||
r->rulenum < rulenum)) {
printf("ipfw: wrong order: rulenum %u"
" vs %u\n", r->rulenum, rulenum);
error = EINVAL;
break;
}
rulenum = r->rulenum;
ci->urule = (caddr_t)r;
clen -= rsize;
r = (struct ip_fw_rule *)((caddr_t)r + rsize);
count++;
ci++;
}
if (ctlv->count != count || error != 0) {
free(cbuf, M_TEMP);
return (EINVAL);
}
rtlv = ctlv;
read += ctlv->head.length;
ctlv = (ipfw_obj_ctlv *)((caddr_t)ctlv + ctlv->head.length);
}
if (read != sd->valsize || rtlv == NULL) {
free(cbuf, M_TEMP);
return (EINVAL);
}
*prtlv = rtlv;
*pci = cbuf;
return (0);
}
static void
import_rule_v1(struct ip_fw_chain *chain, struct rule_check_info *ci)
{
struct ip_fw_rule *urule;
struct ip_fw *krule;
urule = (struct ip_fw_rule *)ci->urule;
krule = ci->krule = ipfw_alloc_rule(chain, RULEKSIZE1(urule));
krule->act_ofs = urule->act_ofs;
krule->cmd_len = urule->cmd_len;
krule->rulenum = urule->rulenum;
krule->set = urule->set;
krule->flags = urule->flags;
ci->urule_numoff = offsetof(struct ip_fw_rule, rulenum);
memcpy(krule->cmd, urule->cmd, krule->cmd_len * sizeof(uint32_t));
}
static int
add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_ctlv *rtlv;
struct rule_check_info *ci, *nci;
int i, ret;
ret = parse_rules_v1(chain, op3, sd, &rtlv, &nci);
if (ret != 0)
return (ret);
for (i = 0, ci = nci; i < rtlv->count; i++, ci++)
import_rule_v1(chain, ci);
if ((ret = ipfw_commit_rules(chain, nci, rtlv->count)) != 0) {
for (i = 0, ci = nci; i < rtlv->count; i++, ci++)
ipfw_free_rule(ci->krule);
}
free(nci, M_TEMP);
return (ret);
}
static int
dump_soptcodes(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
struct _ipfw_obj_lheader *olh;
ipfw_sopt_info *i;
struct ipfw_sopt_handler *sh;
uint32_t count, n, size;
olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,
sizeof(*olh));
if (olh == NULL)
return (EINVAL);
if (sd->valsize < olh->size)
return (EINVAL);
CTL3_LOCK();
count = ctl3_hsize;
size = count * sizeof(ipfw_sopt_info) + sizeof(ipfw_obj_lheader);
olh->count = count;
olh->objsize = sizeof(ipfw_sopt_info);
if (size > olh->size) {
olh->size = size;
CTL3_UNLOCK();
return (ENOMEM);
}
olh->size = size;
for (n = 0; n < count; n++) {
i = (ipfw_sopt_info *)ipfw_get_sopt_space(sd, sizeof(*i));
KASSERT(i != NULL, ("previously checked buffer is not enough"));
sh = &ctl3_handlers[n];
i->opcode = sh->opcode;
i->version = sh->version;
i->refcnt = sh->refcnt;
}
CTL3_UNLOCK();
return (0);
}
static int
compare_opcodes(const void *_a, const void *_b)
{
const struct opcode_obj_rewrite *a, *b;
a = (const struct opcode_obj_rewrite *)_a;
b = (const struct opcode_obj_rewrite *)_b;
if (a->opcode < b->opcode)
return (-1);
else if (a->opcode > b->opcode)
return (1);
return (0);
}
static int
find_op_rw_range(uint16_t op, struct opcode_obj_rewrite **plo,
struct opcode_obj_rewrite **phi)
{
struct opcode_obj_rewrite *ctl3_max, *lo, *hi, h, *rw;
memset(&h, 0, sizeof(h));
h.opcode = op;
rw = (struct opcode_obj_rewrite *)bsearch(&h, ctl3_rewriters,
ctl3_rsize, sizeof(h), compare_opcodes);
if (rw == NULL)
return (1);
lo = rw;
for ( ; lo > ctl3_rewriters && (lo - 1)->opcode == op; lo--)
;
hi = rw;
ctl3_max = ctl3_rewriters + ctl3_rsize;
for ( ; (hi + 1) < ctl3_max && (hi + 1)->opcode == op; hi++)
;
*plo = lo;
*phi = hi;
return (0);
}
static struct opcode_obj_rewrite *
find_op_rw(ipfw_insn *cmd, uint32_t *puidx, uint8_t *ptype)
{
struct opcode_obj_rewrite *rw, *lo, *hi;
uint32_t uidx;
uint8_t subtype;
if (find_op_rw_range(cmd->opcode, &lo, &hi) != 0)
return (NULL);
for (rw = lo; rw <= hi; rw++) {
if (rw->classifier(cmd, &uidx, &subtype) == 0) {
if (puidx != NULL)
*puidx = uidx;
if (ptype != NULL)
*ptype = subtype;
return (rw);
}
}
return (NULL);
}
int
classify_opcode_kidx(ipfw_insn *cmd, uint32_t *puidx)
{
if (find_op_rw(cmd, puidx, NULL) == NULL)
return (1);
return (0);
}
void
update_opcode_kidx(ipfw_insn *cmd, uint32_t idx)
{
struct opcode_obj_rewrite *rw;
rw = find_op_rw(cmd, NULL, NULL);
KASSERT(rw != NULL, ("No handler to update opcode %d", cmd->opcode));
rw->update(cmd, idx);
}
void
ipfw_init_obj_rewriter(void)
{
ctl3_rewriters = NULL;
ctl3_rsize = 0;
}
void
ipfw_destroy_obj_rewriter(void)
{
if (ctl3_rewriters != NULL)
free(ctl3_rewriters, M_IPFW);
ctl3_rewriters = NULL;
ctl3_rsize = 0;
}
void
ipfw_add_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count)
{
size_t sz;
struct opcode_obj_rewrite *tmp;
CTL3_LOCK();
for (;;) {
sz = ctl3_rsize + count;
CTL3_UNLOCK();
tmp = malloc(sizeof(*rw) * sz, M_IPFW, M_WAITOK | M_ZERO);
CTL3_LOCK();
if (ctl3_rsize + count <= sz)
break;
free(tmp, M_IPFW);
}
sz = ctl3_rsize + count;
memcpy(tmp, ctl3_rewriters, ctl3_rsize * sizeof(*rw));
memcpy(&tmp[ctl3_rsize], rw, count * sizeof(*rw));
qsort(tmp, sz, sizeof(*rw), compare_opcodes);
if (ctl3_rewriters != NULL)
free(ctl3_rewriters, M_IPFW);
ctl3_rewriters = tmp;
ctl3_rsize = sz;
CTL3_UNLOCK();
}
int
ipfw_del_obj_rewriter(struct opcode_obj_rewrite *rw, size_t count)
{
size_t sz;
struct opcode_obj_rewrite *ctl3_max, *ktmp, *lo, *hi;
int i;
CTL3_LOCK();
for (i = 0; i < count; i++) {
if (find_op_rw_range(rw[i].opcode, &lo, &hi) != 0)
continue;
for (ktmp = lo; ktmp <= hi; ktmp++) {
if (ktmp->classifier != rw[i].classifier)
continue;
ctl3_max = ctl3_rewriters + ctl3_rsize;
sz = (ctl3_max - (ktmp + 1)) * sizeof(*ktmp);
memmove(ktmp, ktmp + 1, sz);
ctl3_rsize--;
break;
}
}
if (ctl3_rsize == 0) {
if (ctl3_rewriters != NULL)
free(ctl3_rewriters, M_IPFW);
ctl3_rewriters = NULL;
}
CTL3_UNLOCK();
return (0);
}
static int
export_objhash_ntlv_internal(struct namedobj_instance *ni,
struct named_object *no, void *arg)
{
struct sockopt_data *sd;
ipfw_obj_ntlv *ntlv;
sd = (struct sockopt_data *)arg;
ntlv = (ipfw_obj_ntlv *)ipfw_get_sopt_space(sd, sizeof(*ntlv));
if (ntlv == NULL)
return (ENOMEM);
ipfw_export_obj_ntlv(no, ntlv);
return (0);
}
static int
dump_srvobjects(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_obj_lheader *hdr;
int count;
hdr = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr));
if (hdr == NULL)
return (EINVAL);
IPFW_UH_RLOCK(chain);
count = ipfw_objhash_count(CHAIN_TO_SRV(chain));
hdr->size = sizeof(ipfw_obj_lheader) + count * sizeof(ipfw_obj_ntlv);
if (sd->valsize < hdr->size) {
IPFW_UH_RUNLOCK(chain);
return (ENOMEM);
}
hdr->count = count;
hdr->objsize = sizeof(ipfw_obj_ntlv);
if (count > 0)
ipfw_objhash_foreach(CHAIN_TO_SRV(chain),
export_objhash_ntlv_internal, sd);
IPFW_UH_RUNLOCK(chain);
return (0);
}
void
ipfw_enable_skipto_cache(struct ip_fw_chain *chain)
{
IPFW_UH_WLOCK_ASSERT(chain);
update_skipto_cache(chain, chain->map);
IPFW_WLOCK(chain);
swap_skipto_cache(chain);
V_skipto_cache = 1;
IPFW_WUNLOCK(chain);
}
static int
manage_skiptocache(struct ip_fw_chain *chain, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
ipfw_cmd_header *hdr;
if (sd->valsize != sizeof(*hdr))
return (EINVAL);
hdr = (ipfw_cmd_header *)ipfw_get_sopt_space(sd, sd->valsize);
if (hdr->cmd != SKIPTO_CACHE_DISABLE &&
hdr->cmd != SKIPTO_CACHE_ENABLE)
return (EOPNOTSUPP);
IPFW_UH_WLOCK(chain);
if (hdr->cmd != V_skipto_cache) {
if (hdr->cmd == SKIPTO_CACHE_ENABLE)
ipfw_enable_skipto_cache(chain);
V_skipto_cache = hdr->cmd;
}
IPFW_UH_WUNLOCK(chain);
return (0);
}
static int
compare_sh(const void *_a, const void *_b)
{
const struct ipfw_sopt_handler *a, *b;
a = (const struct ipfw_sopt_handler *)_a;
b = (const struct ipfw_sopt_handler *)_b;
if (a->opcode < b->opcode)
return (-1);
else if (a->opcode > b->opcode)
return (1);
if (a->version < b->version)
return (-1);
else if (a->version > b->version)
return (1);
if (a->handler == NULL)
return (0);
if ((uintptr_t)a->handler < (uintptr_t)b->handler)
return (-1);
else if ((uintptr_t)a->handler > (uintptr_t)b->handler)
return (1);
return (0);
}
static struct ipfw_sopt_handler *
find_sh(uint16_t code, uint8_t version, sopt_handler_f *handler)
{
struct ipfw_sopt_handler *sh, h;
memset(&h, 0, sizeof(h));
h.opcode = code;
h.version = version;
h.handler = handler;
sh = (struct ipfw_sopt_handler *)bsearch(&h, ctl3_handlers,
ctl3_hsize, sizeof(h), compare_sh);
return (sh);
}
static int
find_ref_sh(uint16_t opcode, uint8_t version, struct ipfw_sopt_handler *psh)
{
struct ipfw_sopt_handler *sh;
CTL3_LOCK();
if ((sh = find_sh(opcode, version, NULL)) == NULL) {
CTL3_UNLOCK();
printf("ipfw: ipfw_ctl3 invalid option %d""v""%d\n",
opcode, version);
return (EINVAL);
}
sh->refcnt++;
ctl3_refct++;
*psh = *sh;
CTL3_UNLOCK();
return (0);
}
static void
find_unref_sh(struct ipfw_sopt_handler *psh)
{
struct ipfw_sopt_handler *sh;
CTL3_LOCK();
sh = find_sh(psh->opcode, psh->version, NULL);
KASSERT(sh != NULL, ("ctl3 handler disappeared"));
sh->refcnt--;
ctl3_refct--;
CTL3_UNLOCK();
}
void
ipfw_init_sopt_handler(void)
{
CTL3_LOCK_INIT();
IPFW_ADD_SOPT_HANDLER(1, scodes);
}
void
ipfw_destroy_sopt_handler(void)
{
IPFW_DEL_SOPT_HANDLER(1, scodes);
CTL3_LOCK_DESTROY();
}
void
ipfw_register_compat(ipfw_check_opcode_t f)
{
check_opcode_f = f;
}
void
ipfw_unregister_compat(void)
{
check_opcode_f = check_opcode_compat_nop;
}
void
ipfw_add_sopt_handler(struct ipfw_sopt_handler *sh, size_t count)
{
size_t sz;
struct ipfw_sopt_handler *tmp;
CTL3_LOCK();
for (;;) {
sz = ctl3_hsize + count;
CTL3_UNLOCK();
tmp = malloc(sizeof(*sh) * sz, M_IPFW, M_WAITOK | M_ZERO);
CTL3_LOCK();
if (ctl3_hsize + count <= sz)
break;
free(tmp, M_IPFW);
}
sz = ctl3_hsize + count;
memcpy(tmp, ctl3_handlers, ctl3_hsize * sizeof(*sh));
memcpy(&tmp[ctl3_hsize], sh, count * sizeof(*sh));
qsort(tmp, sz, sizeof(*sh), compare_sh);
if (ctl3_handlers != NULL)
free(ctl3_handlers, M_IPFW);
ctl3_handlers = tmp;
ctl3_hsize = sz;
ctl3_gencnt++;
CTL3_UNLOCK();
}
int
ipfw_del_sopt_handler(struct ipfw_sopt_handler *sh, size_t count)
{
size_t sz;
struct ipfw_sopt_handler *tmp, *h;
int i;
CTL3_LOCK();
for (i = 0; i < count; i++) {
tmp = &sh[i];
h = find_sh(tmp->opcode, tmp->version, tmp->handler);
if (h == NULL)
continue;
sz = (ctl3_handlers + ctl3_hsize - (h + 1)) * sizeof(*h);
memmove(h, h + 1, sz);
ctl3_hsize--;
}
if (ctl3_hsize == 0) {
if (ctl3_handlers != NULL)
free(ctl3_handlers, M_IPFW);
ctl3_handlers = NULL;
}
ctl3_gencnt++;
CTL3_UNLOCK();
return (0);
}
static int
ipfw_flush_sopt_data(struct sockopt_data *sd)
{
struct sockopt *sopt;
int error;
size_t sz;
sz = sd->koff;
if (sz == 0)
return (0);
sopt = sd->sopt;
if (sopt->sopt_dir == SOPT_GET) {
error = copyout(sd->kbuf, sopt->sopt_val, sz);
if (error != 0)
return (error);
}
memset(sd->kbuf, 0, sd->ksize);
sd->ktotal += sz;
sd->koff = 0;
if (sd->ktotal + sd->ksize < sd->valsize)
sd->kavail = sd->ksize;
else
sd->kavail = sd->valsize - sd->ktotal;
sopt->sopt_valsize = sd->ktotal;
sopt->sopt_val = sd->sopt_val + sd->ktotal;
return (0);
}
caddr_t
ipfw_get_sopt_space(struct sockopt_data *sd, size_t needed)
{
int error;
caddr_t addr;
if (sd->kavail < needed) {
error = ipfw_flush_sopt_data(sd);
if (sd->kavail < needed || error != 0)
return (NULL);
}
addr = sd->kbuf + sd->koff;
sd->koff += needed;
sd->kavail -= needed;
return (addr);
}
caddr_t
ipfw_get_sopt_header(struct sockopt_data *sd, size_t needed)
{
caddr_t addr;
if ((addr = ipfw_get_sopt_space(sd, needed)) == NULL)
return (NULL);
if (sd->kavail > 0)
memset(sd->kbuf + sd->koff, 0, sd->kavail);
return (addr);
}
int
ipfw_ctl3(struct sockopt *sopt)
{
int error, locked;
size_t size, valsize;
struct ip_fw_chain *chain;
char xbuf[256];
struct sockopt_data sdata;
struct ipfw_sopt_handler h;
ip_fw3_opheader *op3 = NULL;
error = priv_check(sopt->sopt_td, PRIV_NETINET_IPFW);
if (error != 0)
return (error);
if (sopt->sopt_name != IP_FW3)
return (EOPNOTSUPP);
chain = &V_layer3_chain;
error = 0;
valsize = sopt->sopt_valsize;
memset(&sdata, 0, sizeof(sdata));
op3 = (ip_fw3_opheader *)xbuf;
error = sooptcopyin(sopt, op3, sizeof(*op3), sizeof(*op3));
if (error != 0)
return (error);
sopt->sopt_valsize = valsize;
error = find_ref_sh(op3->opcode, op3->version, &h);
if (error != 0)
return (error);
if ((h.dir & HDIR_SET) != 0 && h.opcode != IP_FW_XRESETLOG) {
error = securelevel_ge(sopt->sopt_td->td_ucred, 3);
if (error != 0) {
find_unref_sh(&h);
return (error);
}
}
locked = 0;
if (valsize <= sizeof(xbuf)) {
sdata.kbuf = xbuf;
sdata.ksize = sizeof(xbuf);
sdata.kavail = valsize;
} else {
if ((h.dir & HDIR_SET) != 0) {
if (valsize > CTL3_LARGEBUF) {
find_unref_sh(&h);
return (EFBIG);
}
size = valsize;
} else {
size = (valsize<CTL3_SMALLBUF) ? valsize:CTL3_SMALLBUF;
if (size < valsize) {
error = vslock(sopt->sopt_val, valsize);
if (error != 0)
return (error);
locked = 1;
}
}
sdata.kbuf = malloc(size, M_TEMP, M_WAITOK | M_ZERO);
sdata.ksize = size;
sdata.kavail = size;
}
sdata.sopt = sopt;
sdata.sopt_val = sopt->sopt_val;
sdata.valsize = valsize;
if ((error = sooptcopyin(sopt, sdata.kbuf, sdata.ksize,
sizeof(ip_fw3_opheader))) != 0)
return (error);
op3 = (ip_fw3_opheader *)sdata.kbuf;
error = h.handler(chain, op3, &sdata);
find_unref_sh(&h);
if (error == 0)
error = ipfw_flush_sopt_data(&sdata);
else
ipfw_flush_sopt_data(&sdata);
if (locked != 0)
vsunlock(sdata.sopt_val, valsize);
sopt->sopt_val = sdata.sopt_val;
sopt->sopt_valsize = sdata.ktotal;
if (sdata.kbuf != xbuf)
free(sdata.kbuf, M_TEMP);
return (error);
}
void
ipfw_init_srv(struct ip_fw_chain *ch)
{
ch->srvmap = ipfw_objhash_create(IPFW_OBJECTS_DEFAULT,
DEFAULT_OBJHASH_SIZE);
ch->srvstate = malloc(sizeof(void *) * IPFW_OBJECTS_DEFAULT,
M_IPFW, M_WAITOK | M_ZERO);
}
void
ipfw_destroy_srv(struct ip_fw_chain *ch)
{
free(ch->srvstate, M_IPFW);
ipfw_objhash_destroy(ch->srvmap);
}
void
ipfw_objhash_bitmap_alloc(uint32_t items, void **idx, int *pblocks)
{
size_t size;
int max_blocks;
u_long *idx_mask;
KASSERT((items % BLOCK_ITEMS) == 0,
("bitmask size needs to power of 2 and greater or equal to %zu",
BLOCK_ITEMS));
max_blocks = items / BLOCK_ITEMS;
size = items / 8;
idx_mask = malloc(size * IPFW_MAX_SETS, M_IPFW, M_WAITOK);
memset(idx_mask, 0xFF, size * IPFW_MAX_SETS);
*idx_mask &= ~(u_long)1;
*idx = idx_mask;
*pblocks = max_blocks;
}
void
ipfw_objhash_bitmap_merge(struct namedobj_instance *ni, void **idx, int *blocks)
{
int old_blocks, new_blocks;
u_long *old_idx, *new_idx;
int i;
old_idx = ni->idx_mask;
old_blocks = ni->max_blocks;
new_idx = *idx;
new_blocks = *blocks;
for (i = 0; i < IPFW_MAX_SETS; i++) {
memcpy(&new_idx[new_blocks * i], &old_idx[old_blocks * i],
old_blocks * sizeof(u_long));
}
}
void
ipfw_objhash_bitmap_swap(struct namedobj_instance *ni, void **idx, int *blocks)
{
int old_blocks;
u_long *old_idx;
old_idx = ni->idx_mask;
old_blocks = ni->max_blocks;
ni->idx_mask = *idx;
ni->max_blocks = *blocks;
*idx = old_idx;
*blocks = old_blocks;
}
void
ipfw_objhash_bitmap_free(void *idx, int blocks)
{
free(idx, M_IPFW);
}
struct namedobj_instance *
ipfw_objhash_create(uint32_t items, size_t hash_size)
{
struct namedobj_instance *ni;
int i;
size_t size;
size = sizeof(struct namedobj_instance) +
sizeof(struct namedobjects_head) * hash_size +
sizeof(struct namedobjects_head) * hash_size;
ni = malloc(size, M_IPFW, M_WAITOK | M_ZERO);
ni->nn_size = hash_size;
ni->nv_size = hash_size;
ni->names = (struct namedobjects_head *)(ni +1);
ni->values = &ni->names[ni->nn_size];
for (i = 0; i < ni->nn_size; i++)
TAILQ_INIT(&ni->names[i]);
for (i = 0; i < ni->nv_size; i++)
TAILQ_INIT(&ni->values[i]);
ni->hash_f = objhash_hash_name;
ni->cmp_f = objhash_cmp_name;
ipfw_objhash_bitmap_alloc(items, (void*)&ni->idx_mask, &ni->max_blocks);
return (ni);
}
void
ipfw_objhash_destroy(struct namedobj_instance *ni)
{
free(ni->idx_mask, M_IPFW);
free(ni, M_IPFW);
}
void
ipfw_objhash_set_funcs(struct namedobj_instance *ni, objhash_hash_f *hash_f,
objhash_cmp_f *cmp_f)
{
ni->hash_f = hash_f;
ni->cmp_f = cmp_f;
}
static uint32_t
objhash_hash_name(struct namedobj_instance *ni, const void *name, uint32_t set)
{
return (fnv_32_str((const char *)name, FNV1_32_INIT));
}
static int
objhash_cmp_name(struct named_object *no, const void *name, uint32_t set)
{
if ((strcmp(no->name, (const char *)name) == 0) && (no->set == set))
return (0);
return (1);
}
static uint32_t
objhash_hash_idx(struct namedobj_instance *ni, uint32_t val)
{
uint32_t v;
v = val % (ni->nv_size - 1);
return (v);
}
struct named_object *
ipfw_objhash_lookup_name(struct namedobj_instance *ni, uint32_t set,
const char *name)
{
struct named_object *no;
uint32_t hash;
hash = ni->hash_f(ni, name, set) % ni->nn_size;
TAILQ_FOREACH(no, &ni->names[hash], nn_next) {
if (ni->cmp_f(no, name, set) == 0)
return (no);
}
return (NULL);
}
ipfw_obj_ntlv *
ipfw_find_name_tlv_type(void *tlvs, int len, uint32_t uidx, uint32_t etlv)
{
ipfw_obj_ntlv *ntlv;
uintptr_t pa, pe;
int l;
pa = (uintptr_t)tlvs;
pe = pa + len;
l = 0;
for (; pa < pe; pa += l) {
ntlv = (ipfw_obj_ntlv *)pa;
l = ntlv->head.length;
if (l != sizeof(*ntlv))
return (NULL);
if (ntlv->idx != uidx)
continue;
if (ntlv->head.type != 0 &&
ntlv->head.type != (uint16_t)etlv)
continue;
if (ipfw_check_object_name_generic(ntlv->name) != 0)
return (NULL);
return (ntlv);
}
return (NULL);
}
int
ipfw_objhash_find_type(struct namedobj_instance *ni, struct tid_info *ti,
uint32_t etlv, struct named_object **pno)
{
char *name;
ipfw_obj_ntlv *ntlv;
uint32_t set;
if (ti->tlvs == NULL)
return (EINVAL);
ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, etlv);
if (ntlv == NULL)
return (EINVAL);
name = ntlv->name;
set = ti->set;
*pno = ipfw_objhash_lookup_name(ni, set, name);
if (*pno == NULL)
return (ESRCH);
return (0);
}
struct named_object *
ipfw_objhash_lookup_name_type(struct namedobj_instance *ni, uint32_t set,
uint32_t type, const char *name)
{
struct named_object *no;
uint32_t hash;
hash = ni->hash_f(ni, name, set) % ni->nn_size;
TAILQ_FOREACH(no, &ni->names[hash], nn_next) {
if (ni->cmp_f(no, name, set) == 0 &&
no->etlv == (uint16_t)type)
return (no);
}
return (NULL);
}
struct named_object *
ipfw_objhash_lookup_kidx(struct namedobj_instance *ni, uint32_t kidx)
{
struct named_object *no;
uint32_t hash;
hash = objhash_hash_idx(ni, kidx);
TAILQ_FOREACH(no, &ni->values[hash], nv_next) {
if (no->kidx == kidx)
return (no);
}
return (NULL);
}
int
ipfw_objhash_same_name(struct namedobj_instance *ni, struct named_object *a,
struct named_object *b)
{
if ((strcmp(a->name, b->name) == 0) && a->set == b->set)
return (1);
return (0);
}
void
ipfw_objhash_add(struct namedobj_instance *ni, struct named_object *no)
{
uint32_t hash;
hash = ni->hash_f(ni, no->name, no->set) % ni->nn_size;
TAILQ_INSERT_HEAD(&ni->names[hash], no, nn_next);
hash = objhash_hash_idx(ni, no->kidx);
TAILQ_INSERT_HEAD(&ni->values[hash], no, nv_next);
ni->count++;
}
void
ipfw_objhash_del(struct namedobj_instance *ni, struct named_object *no)
{
uint32_t hash;
hash = ni->hash_f(ni, no->name, no->set) % ni->nn_size;
TAILQ_REMOVE(&ni->names[hash], no, nn_next);
hash = objhash_hash_idx(ni, no->kidx);
TAILQ_REMOVE(&ni->values[hash], no, nv_next);
ni->count--;
}
uint32_t
ipfw_objhash_count(struct namedobj_instance *ni)
{
return (ni->count);
}
uint32_t
ipfw_objhash_count_type(struct namedobj_instance *ni, uint16_t type)
{
struct named_object *no;
uint32_t count;
int i;
count = 0;
for (i = 0; i < ni->nn_size; i++) {
TAILQ_FOREACH(no, &ni->names[i], nn_next) {
if (no->etlv == type)
count++;
}
}
return (count);
}
int
ipfw_objhash_foreach(struct namedobj_instance *ni, objhash_cb_t *f, void *arg)
{
struct named_object *no, *no_tmp;
int i, ret;
for (i = 0; i < ni->nn_size; i++) {
TAILQ_FOREACH_SAFE(no, &ni->names[i], nn_next, no_tmp) {
ret = f(ni, no, arg);
if (ret != 0)
return (ret);
}
}
return (0);
}
int
ipfw_objhash_foreach_type(struct namedobj_instance *ni, objhash_cb_t *f,
void *arg, uint16_t type)
{
struct named_object *no, *no_tmp;
int i, ret;
for (i = 0; i < ni->nn_size; i++) {
TAILQ_FOREACH_SAFE(no, &ni->names[i], nn_next, no_tmp) {
if (no->etlv != type)
continue;
ret = f(ni, no, arg);
if (ret != 0)
return (ret);
}
}
return (0);
}
int
ipfw_objhash_free_idx(struct namedobj_instance *ni, uint32_t idx)
{
u_long *mask;
int i, v;
i = idx / BLOCK_ITEMS;
v = idx % BLOCK_ITEMS;
if (i >= ni->max_blocks)
return (1);
mask = &ni->idx_mask[i];
if ((*mask & ((u_long)1 << v)) != 0)
return (1);
*mask |= (u_long)1 << v;
if (ni->free_off[0] > i)
ni->free_off[0] = i;
return (0);
}
int
ipfw_objhash_alloc_idx(void *n, uint32_t *pidx)
{
struct namedobj_instance *ni;
u_long *mask;
int i, off, v;
ni = (struct namedobj_instance *)n;
off = ni->free_off[0];
mask = &ni->idx_mask[off];
for (i = off; i < ni->max_blocks; i++, mask++) {
if ((v = ffsl(*mask)) == 0)
continue;
*mask &= ~ ((u_long)1 << (v - 1));
ni->free_off[0] = i;
v = BLOCK_ITEMS * i + v - 1;
*pidx = v;
return (0);
}
return (1);
}