diff options
Diffstat (limited to 'freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c')
-rw-r--r-- | freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c | 3904 |
1 files changed, 3533 insertions, 371 deletions
diff --git a/freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c b/freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c index 95cd8c81..468e4ad4 100644 --- a/freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -2,6 +2,8 @@ /*- * Copyright (c) 2002-2009 Luigi Rizzo, Universita` di Pisa + * Copyright (c) 2014 Yandex LLC + * Copyright (c) 2014 Alexander V. Chernikov * * Supported by: Valeria Paoli * @@ -31,8 +33,8 @@ __FBSDID("$FreeBSD$"); /* - * Sockopt support for ipfw. The routines here implement - * the upper half of the ipfw code. + * Control socket and rule management routines for ipfw. + * Control is currently implemented via IP_FW3 setsockopt() code. */ #include <rtems/bsd/local/opt_ipfw.h> @@ -51,30 +53,174 @@ __FBSDID("$FreeBSD$"); #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> /* hooks */ #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 int ipfw_ctl(struct sockopt *sopt); +static int check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, + struct rule_check_info *ci); +static int check_ipfw_rule1(struct ip_fw_rule *rule, int size, + struct rule_check_info *ci); +static int check_ipfw_rule0(struct ip_fw_rule0 *rule, int size, + struct rule_check_info *ci); +static int rewrite_rule_uidx(struct ip_fw_chain *chain, + struct rule_check_info *ci); + +#define NAMEDOBJ_HASH_SIZE 32 + +struct namedobj_instance { + struct namedobjects_head *names; + struct namedobjects_head *values; + uint32_t nn_size; /* names hash size */ + uint32_t nv_size; /* number hash size */ + u_long *idx_mask; /* used items bitmask */ + uint32_t max_blocks; /* number of "long" blocks in bitmask */ + uint32_t count; /* number of items */ + uint16_t free_off[IPFW_MAX_SETS]; /* first possible free offset */ + objhash_hash_f *hash_f; + objhash_cmp_f *cmp_f; +}; +#define BLOCK_ITEMS (8 * sizeof(u_long)) /* Number of items for ffsl() */ + +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 int dump_config(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int del_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int move_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int manage_sets(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int dump_soptcodes(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); +static int dump_srvobjects(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd); + +/* ctl3 handler data */ +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 /* small page-size write buffer */ +#define CTL3_LARGEBUF 16 * 1024 * 1024 /* handle large rulesets */ + +static int ipfw_flush_sopt_data(struct sockopt_data *sd); + +static struct ipfw_sopt_handler scodes[] = { + { IP_FW_XGET, 0, HDIR_GET, dump_config }, + { IP_FW_XADD, 0, HDIR_BOTH, add_rules }, + { IP_FW_XDEL, 0, HDIR_BOTH, del_rules }, + { IP_FW_XZERO, 0, HDIR_SET, clear_rules }, + { IP_FW_XRESETLOG, 0, HDIR_SET, clear_rules }, + { IP_FW_XMOVE, 0, HDIR_SET, move_rules }, + { IP_FW_SET_SWAP, 0, HDIR_SET, manage_sets }, + { IP_FW_SET_MOVE, 0, HDIR_SET, manage_sets }, + { IP_FW_SET_ENABLE, 0, HDIR_SET, manage_sets }, + { IP_FW_DUMP_SOPTCODES, 0, HDIR_GET, dump_soptcodes }, + { IP_FW_DUMP_SRVOBJECTS,0, HDIR_GET, dump_srvobjects }, +}; + +static int +set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule); +static struct opcode_obj_rewrite *find_op_rw(ipfw_insn *cmd, + uint16_t *puidx, uint8_t *ptype); +static int mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, + uint32_t *bmask); +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, uint16_t kidx, + struct sockopt_data *sd); + +/* + * Opcode object rewriter variables + */ +struct opcode_obj_rewrite *ctl3_rewriters; +static size_t ctl3_rsize; + /* - * static variables followed by global ones (none in this file) + * static variables followed by global ones */ +static VNET_DEFINE(uma_zone_t, ipfw_cntr_zone); +#define V_ipfw_cntr_zone VNET(ipfw_cntr_zone) + +void +ipfw_init_counters() +{ + + 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() +{ + + 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(V_ipfw_cntr_zone, M_WAITOK | M_ZERO); + + return (rule); +} + +static void +free_rule(struct ip_fw *rule) +{ + + uma_zfree(V_ipfw_cntr_zone, rule->cntr); + free(rule, M_IPFW); +} + + /* * Find the smallest rule >= key, id. * We could use bsearch but it is so simple that we code it directly @@ -96,11 +242,109 @@ ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id) lo = i + 1; /* continue from the next one */ else /* r->id >= id */ hi = i; /* this might be good */ - }; + } return hi; } /* + * Builds skipto cache on rule set @map. + */ +static void +update_skipto_cache(struct ip_fw_chain *chain, struct ip_fw **map) +{ + int *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 < 65536; i++) { + smap[i] = mi; + /* Use the same rule index until i < rulenum */ + if (i != rulenum || i == 65535) + continue; + /* Find next rule with num > i */ + rulenum = map[++mi]->rulenum; + while (rulenum == i) + rulenum = map[++mi]->rulenum; + } +} + +/* + * Swaps prepared (backup) index with current one. + */ +static void +swap_skipto_cache(struct ip_fw_chain *chain) +{ + int *map; + + IPFW_UH_WLOCK_ASSERT(chain); + IPFW_WLOCK_ASSERT(chain); + + map = chain->idxmap; + chain->idxmap = chain->idxmap_back; + chain->idxmap_back = map; +} + +/* + * Allocate and initialize skipto cache. + */ +void +ipfw_init_skipto_cache(struct ip_fw_chain *chain) +{ + int *idxmap, *idxmap_back; + + idxmap = malloc(65536 * sizeof(uint32_t *), M_IPFW, + M_WAITOK | M_ZERO); + idxmap_back = malloc(65536 * sizeof(uint32_t *), M_IPFW, + M_WAITOK | M_ZERO); + + /* + * Note we may be called at any time after initialization, + * for example, on first skipto rule, so we need to + * provide valid chain->idxmap on return + */ + + IPFW_UH_WLOCK(chain); + if (chain->idxmap != NULL) { + IPFW_UH_WUNLOCK(chain); + free(idxmap, M_IPFW); + free(idxmap_back, M_IPFW); + return; + } + + /* Set backup pointer first to permit building cache */ + chain->idxmap_back = idxmap_back; + update_skipto_cache(chain, chain->map); + IPFW_WLOCK(chain); + /* It is now safe to set chain->idxmap ptr */ + chain->idxmap = idxmap; + swap_skipto_cache(chain); + IPFW_WUNLOCK(chain); + IPFW_UH_WUNLOCK(chain); +} + +/* + * Destroys skipto cache. + */ +void +ipfw_destroy_skipto_cache(struct ip_fw_chain *chain) +{ + + if (chain->idxmap != NULL) + free(chain->idxmap, M_IPFW); + if (chain->idxmap != NULL) + free(chain->idxmap_back, M_IPFW); +} + + +/* * allocate a new map, returns the chain locked. extra is the number * of entries to add or delete. */ @@ -110,11 +354,12 @@ get_map(struct ip_fw_chain *chain, int extra, int locked) for (;;) { struct ip_fw **map; - int i; + 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, - locked ? M_NOWAIT : M_WAITOK); + map = malloc(i * sizeof(struct ip_fw *), M_IPFW, mflags); if (map == NULL) { printf("%s: cannot allocate map\n", __FUNCTION__); return NULL; @@ -143,69 +388,403 @@ swap_map(struct ip_fw_chain *chain, struct ip_fw **new_map, int new_len) 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_cntr0_base(struct ip_fw *krule, struct ip_fw_bcounter0 *cntr) +{ + struct timeval boottime; + + 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; + } +} + +/* + * Copies rule @urule from v1 userland format (current). + * to kernel @krule. + * Assume @krule is zeroed. + */ +static void +import_rule1(struct rule_check_info *ci) +{ + struct ip_fw_rule *urule; + struct ip_fw *krule; + + urule = (struct ip_fw_rule *)ci->urule; + krule = (struct ip_fw *)ci->krule; + + /* copy header */ + krule->act_ofs = urule->act_ofs; + krule->cmd_len = urule->cmd_len; + krule->rulenum = urule->rulenum; + krule->set = urule->set; + krule->flags = urule->flags; + + /* Save rulenum offset */ + ci->urule_numoff = offsetof(struct ip_fw_rule, rulenum); + + /* Copy opcodes */ + memcpy(krule->cmd, urule->cmd, krule->cmd_len * sizeof(uint32_t)); +} + +/* + * Export rule into v1 format (Current). + * Layout: + * [ ipfw_obj_tlv(IPFW_TLV_RULE_ENT) + * [ ip_fw_rule ] OR + * [ ip_fw_bcounter ip_fw_rule] (depends on rcntrs). + * ] + * Assume @data is zeroed. + */ +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; + + /* Fill in TLV header */ + tlv = (ipfw_obj_tlv *)data; + tlv->type = IPFW_TLV_RULE_ENT; + tlv->length = len; + + if (rcntrs != 0) { + /* Copy counters */ + 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); + + /* copy header */ + 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; + + /* Copy opcodes */ + memcpy(urule->cmd, krule->cmd, krule->cmd_len * sizeof(uint32_t)); +} + + +/* + * Copies rule @urule from FreeBSD8 userland format (v0) + * to kernel @krule. + * Assume @krule is zeroed. + */ +static void +import_rule0(struct rule_check_info *ci) +{ + struct ip_fw_rule0 *urule; + struct ip_fw *krule; + int cmdlen, l; + ipfw_insn *cmd; + ipfw_insn_limit *lcmd; + ipfw_insn_if *cmdif; + + urule = (struct ip_fw_rule0 *)ci->urule; + krule = (struct ip_fw *)ci->krule; + + /* copy header */ + krule->act_ofs = urule->act_ofs; + krule->cmd_len = urule->cmd_len; + krule->rulenum = urule->rulenum; + krule->set = urule->set; + if ((urule->_pad & 1) != 0) + krule->flags |= IPFW_RULE_NOOPT; + + /* Save rulenum offset */ + ci->urule_numoff = offsetof(struct ip_fw_rule0, rulenum); + + /* Copy opcodes */ + memcpy(krule->cmd, urule->cmd, krule->cmd_len * sizeof(uint32_t)); + + /* + * Alter opcodes: + * 1) convert tablearg value from 65535 to 0 + * 2) Add high bit to O_SETFIB/O_SETDSCP values (to make room + * for targ). + * 3) convert table number in iface opcodes to u16 + * 4) convert old `nat global` into new 65535 + */ + l = krule->cmd_len; + cmd = krule->cmd; + cmdlen = 0; + + for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { + cmdlen = F_LEN(cmd); + + switch (cmd->opcode) { + /* Opcodes supporting tablearg */ + case O_TAG: + case O_TAGGED: + case O_PIPE: + case O_QUEUE: + case O_DIVERT: + case O_TEE: + case O_SKIPTO: + case O_CALLRETURN: + case O_NETGRAPH: + case O_NGTEE: + case O_NAT: + if (cmd->arg1 == IP_FW_TABLEARG) + cmd->arg1 = IP_FW_TARG; + else if (cmd->arg1 == 0) + cmd->arg1 = IP_FW_NAT44_GLOBAL; + break; + case O_SETFIB: + case O_SETDSCP: + if (cmd->arg1 == IP_FW_TABLEARG) + cmd->arg1 = IP_FW_TARG; + else + cmd->arg1 |= 0x8000; + break; + case O_LIMIT: + lcmd = (ipfw_insn_limit *)cmd; + if (lcmd->conn_limit == IP_FW_TABLEARG) + lcmd->conn_limit = IP_FW_TARG; + break; + /* Interface tables */ + case O_XMIT: + case O_RECV: + case O_VIA: + /* Interface table, possibly */ + cmdif = (ipfw_insn_if *)cmd; + if (cmdif->name[0] != '\1') + break; + + cmdif->p.kidx = (uint16_t)cmdif->p.glob; + break; + } + } +} + +/* + * Copies rule @krule from kernel to FreeBSD8 userland format (v0) + */ +static void +export_rule0(struct ip_fw *krule, struct ip_fw_rule0 *urule, int len) +{ + int cmdlen, l; + ipfw_insn *cmd; + ipfw_insn_limit *lcmd; + ipfw_insn_if *cmdif; + + /* copy header */ + memset(urule, 0, len); + urule->act_ofs = krule->act_ofs; + urule->cmd_len = krule->cmd_len; + urule->rulenum = krule->rulenum; + urule->set = krule->set; + if ((krule->flags & IPFW_RULE_NOOPT) != 0) + urule->_pad |= 1; + + /* Copy opcodes */ + memcpy(urule->cmd, krule->cmd, krule->cmd_len * sizeof(uint32_t)); + + /* Export counters */ + export_cntr0_base(krule, (struct ip_fw_bcounter0 *)&urule->pcnt); + + /* + * Alter opcodes: + * 1) convert tablearg value from 0 to 65535 + * 2) Remove highest bit from O_SETFIB/O_SETDSCP values. + * 3) convert table number in iface opcodes to int + */ + l = urule->cmd_len; + cmd = urule->cmd; + cmdlen = 0; + + for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { + cmdlen = F_LEN(cmd); + + switch (cmd->opcode) { + /* Opcodes supporting tablearg */ + case O_TAG: + case O_TAGGED: + case O_PIPE: + case O_QUEUE: + case O_DIVERT: + case O_TEE: + case O_SKIPTO: + case O_CALLRETURN: + case O_NETGRAPH: + case O_NGTEE: + case O_NAT: + if (cmd->arg1 == IP_FW_TARG) + cmd->arg1 = IP_FW_TABLEARG; + else if (cmd->arg1 == IP_FW_NAT44_GLOBAL) + cmd->arg1 = 0; + break; + case O_SETFIB: + case O_SETDSCP: + if (cmd->arg1 == IP_FW_TARG) + cmd->arg1 = IP_FW_TABLEARG; + else + cmd->arg1 &= ~0x8000; + break; + case O_LIMIT: + lcmd = (ipfw_insn_limit *)cmd; + if (lcmd->conn_limit == IP_FW_TARG) + lcmd->conn_limit = IP_FW_TABLEARG; + break; + /* Interface tables */ + case O_XMIT: + case O_RECV: + case O_VIA: + /* Interface table, possibly */ + cmdif = (ipfw_insn_if *)cmd; + if (cmdif->name[0] != '\1') + break; + + cmdif->p.glob = cmdif->p.kidx; + break; + } + } +} + /* - * Add a new rule to the list. Copy the rule into a malloc'ed area, then - * possibly create a rule number and add the rule to the list. + * Add new rule(s) to the list possibly creating rule number for each. * Update the rule_number in the input struct so the caller knows it as well. - * XXX DO NOT USE FOR THE DEFAULT RULE. * Must be called without IPFW_UH held */ -int -ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule) +static int +commit_rules(struct ip_fw_chain *chain, struct rule_check_info *rci, int count) { - struct ip_fw *rule; - int i, l, insert_before; + int error, i, insert_before, tcount; + uint16_t rulenum, *pnum; + struct rule_check_info *ci; + struct ip_fw *krule; struct ip_fw **map; /* the new array of pointers */ - if (chain->map == NULL || input_rule->rulenum > IPFW_DEFAULT_RULE - 1) - return (EINVAL); + /* Check if we need to do table/obj index remap */ + tcount = 0; + for (ci = rci, i = 0; i < count; ci++, i++) { + if (ci->object_opcodes == 0) + continue; + + /* + * Rule has some object opcodes. + * We need to find (and create non-existing) + * kernel objects, and reference existing ones. + */ + error = rewrite_rule_uidx(chain, ci); + if (error != 0) { + + /* + * rewrite failed, state for current rule + * has been reverted. Check if we need to + * revert more. + */ + if (tcount > 0) { + + /* + * We have some more table rules + * we need to rollback. + */ + + 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++; + } - l = RULESIZE(input_rule); - rule = malloc(l, M_IPFW, M_WAITOK | M_ZERO); - if (rule == NULL) - return (ENOSPC); /* get_map returns with IPFW_UH_WLOCK if successful */ - map = get_map(chain, 1, 0 /* not locked */); + map = get_map(chain, count, 0 /* not locked */); if (map == NULL) { - free(rule, M_IPFW); - return ENOSPC; - } + if (tcount > 0) { + /* Unbind tables */ + 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); + } - bcopy(input_rule, rule, l); - /* clear fields not settable from userland */ - rule->x_next = NULL; - rule->next_rule = NULL; - IPFW_ZERO_RULE_COUNTER(rule); + return (ENOSPC); + } if (V_autoinc_step < 1) V_autoinc_step = 1; else if (V_autoinc_step > 1000) V_autoinc_step = 1000; + + /* FIXME: Handle count > 1 */ + ci = rci; + krule = ci->krule; + rulenum = krule->rulenum; + /* find the insertion point, we will insert before */ - insert_before = rule->rulenum ? rule->rulenum + 1 : IPFW_DEFAULT_RULE; + insert_before = rulenum ? rulenum + 1 : IPFW_DEFAULT_RULE; i = ipfw_find_rule(chain, insert_before, 0); /* duplicate first part */ if (i > 0) bcopy(chain->map, map, i * sizeof(struct ip_fw *)); - map[i] = rule; + map[i] = krule; /* duplicate remaining part, we always have the default rule */ bcopy(chain->map + i, map + i + 1, sizeof(struct ip_fw *) *(chain->n_rules - i)); - if (rule->rulenum == 0) { - /* write back the number */ - rule->rulenum = i > 0 ? map[i-1]->rulenum : 0; - if (rule->rulenum < IPFW_DEFAULT_RULE - V_autoinc_step) - rule->rulenum += V_autoinc_step; - input_rule->rulenum = rule->rulenum; + if (rulenum == 0) { + /* Compute rule number and write it back */ + rulenum = i > 0 ? map[i-1]->rulenum : 0; + if (rulenum < IPFW_DEFAULT_RULE - V_autoinc_step) + rulenum += V_autoinc_step; + krule->rulenum = rulenum; + /* Save number to userland rule */ + pnum = (uint16_t *)((caddr_t)ci->urule + ci->urule_numoff); + *pnum = rulenum; } - rule->id = chain->id + 1; + krule->id = chain->id + 1; + update_skipto_cache(chain, map); map = swap_map(chain, map, chain->n_rules + 1); - chain->static_len += l; + chain->static_len += RULEUSIZE0(krule); IPFW_UH_WUNLOCK(chain); if (map) free(map, M_IPFW); @@ -213,6 +792,23 @@ ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule) } /* + * Adds @rule to the list of rules to reap + */ +void +ipfw_reap_add(struct ip_fw_chain *chain, struct ip_fw **head, + struct ip_fw *rule) +{ + + IPFW_UH_WLOCK_ASSERT(chain); + + /* Unlink rule from everywhere */ + unref_rule_objects(chain, rule); + + *((struct ip_fw **)rule) = *head; + *head = rule; +} + +/* * Reclaim storage associated with a list of rules. This is * typically the list created using remove_rule. * A NULL pointer on input is handled correctly. @@ -223,22 +819,12 @@ ipfw_reap_rules(struct ip_fw *head) struct ip_fw *rule; while ((rule = head) != NULL) { - head = head->x_next; - free(rule, M_IPFW); + head = *((struct ip_fw **)head); + free_rule(rule); } } /* - * Used by del_entry() to check if a rule should be kept. - * Returns 1 if the rule must be kept, 0 otherwise. - * - * Called with cmd = {0,1,5}. - * cmd == 0 matches on rule numbers, excludes rules in RESVD_SET if n == 0 ; - * cmd == 1 matches on set numbers only, rule numbers are ignored; - * cmd == 5 matches on rule and set numbers. - * - * n == 0 is a wildcard for rule numbers, there is no wildcard for sets. - * * Rules to keep are * (default || reserved || !match_set || !match_number) * where @@ -255,14 +841,608 @@ ipfw_reap_rules(struct ip_fw *head) * // number is ignored for cmd == 1 or n == 0 * */ +int +ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt) +{ + + /* Don't match default rule for modification queries */ + if (rule->rulenum == IPFW_DEFAULT_RULE && + (rt->flags & IPFW_RCFLAG_DEFAULT) == 0) + return (0); + + /* Don't match rules in reserved set for flush requests */ + if ((rt->flags & IPFW_RCFLAG_ALL) != 0 && rule->set == RESVD_SET) + return (0); + + /* If we're filtering by set, don't match other sets */ + 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 { + uint16_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); +} + +/* + * Generic function to handler moving and swapping sets. + */ +int +ipfw_obj_manage_sets(struct namedobj_instance *ni, uint16_t type, + uint16_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: + /* + * @set used to pass kidx. + * When @new_set is zero - reset object counter, + * otherwise increment it. + */ + no = ipfw_objhash_lookup_kidx(ni, set); + if (new_set != 0) + no->ocnt++; + else + no->ocnt = 0; + return (0); + case TEST_ONE: + /* @set used to pass kidx */ + no = ipfw_objhash_lookup_kidx(ni, set); + /* + * First check number of references: + * when it differs, this mean other rules are holding + * reference to given object, so it is not possible to + * change its set. Note that refcnt may account references + * to some going-to-be-added rules. Since we don't know + * their numbers (and even if they will be added) it is + * perfectly OK to return error here. + */ + 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: + /* @set used to pass kidx */ + no = ipfw_objhash_lookup_kidx(ni, set); + no->set = new_set; + return (0); + } + return (EINVAL); +} + +/* + * Delete rules matching range @rt. + * Saves number of deleted rules in @ndel. + * + * Returns 0 on success. + */ +static int +delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel) +{ + struct ip_fw *reap, *rule, **map; + int end, start; + int i, n, ndyn, ofs; + + reap = NULL; + IPFW_UH_WLOCK(chain); /* arbitrate writers */ + + /* + * Stage 1: Determine range to inspect. + * Range is half-inclusive, e.g [start, end). + */ + start = 0; + end = chain->n_rules - 1; + + if ((rt->flags & IPFW_RCFLAG_RANGE) != 0) { + start = ipfw_find_rule(chain, rt->start_rule, 0); + + end = ipfw_find_rule(chain, rt->end_rule, 0); + if (rt->end_rule != IPFW_DEFAULT_RULE) + while (chain->map[end]->rulenum == rt->end_rule) + end++; + } + + /* Allocate new map of the same size */ + map = get_map(chain, 0, 1 /* locked */); + if (map == NULL) { + IPFW_UH_WUNLOCK(chain); + return (ENOMEM); + } + + n = 0; + ndyn = 0; + ofs = start; + /* 1. bcopy the initial part of the map */ + if (start > 0) + bcopy(chain->map, map, start * sizeof(struct ip_fw *)); + /* 2. copy active rules between start and end */ + 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++; + } + /* 3. copy the final part of the map */ + bcopy(chain->map + end, map + ofs, + (chain->n_rules - end) * sizeof(struct ip_fw *)); + /* 4. recalculate skipto cache */ + update_skipto_cache(chain, map); + /* 5. swap the maps (under UH_WLOCK + WHLOCK) */ + map = swap_map(chain, map, chain->n_rules - n); + /* 6. Remove all dynamic states originated by deleted rules */ + if (ndyn > 0) + ipfw_expire_dyn_rules(chain, rt); + /* 7. now remove the rules deleted from the old map */ + for (i = start; i < end; i++) { + rule = map[i]; + if (ipfw_match_range(rule, rt) == 0) + continue; + chain->static_len -= RULEUSIZE0(rule); + 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; + int cmdlen, i, l, c; + uint16_t kidx; + + IPFW_UH_WLOCK_ASSERT(ch); + + /* Stage 1: count number of references by given rules */ + 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) /* nothing to do */ + continue; + /* Search opcodes with named objects */ + 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; + /* + * When manage_sets() returns non-zero value to + * COUNT_ONE command, consider this as an object + * doesn't support sets (e.g. disabled with sysctl). + * So, skip checks for this object. + */ + if (rw->manage_sets(ch, kidx, 1, COUNT_ONE) != 0) + continue; + c++; + } + } + if (c == 0) /* No objects found */ + return (0); + /* Stage 2: verify "ownership" */ + 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) /* nothing to do */ + continue; + /* Search opcodes with named objects */ + 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; + /* Test for ownership and conflicting names */ + c = rw->manage_sets(ch, kidx, + (uint8_t)rt->new_set, TEST_ONE); + } + } + /* Stage 3: change set and cleanup */ + 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) /* nothing to do */ + continue; + /* Search opcodes with named objects */ + 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; + /* cleanup object counter */ + rw->manage_sets(ch, kidx, + 0 /* reset counter */, COUNT_ONE); + if (c != 0) + continue; + /* change set */ + rw->manage_sets(ch, kidx, + (uint8_t)rt->new_set, MOVE_ONE); + } + } + return (c); +}/* + * Changes set of given rule rannge @rt + * with each other. + * + * Returns 0 on success. + */ +static int +move_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt) +{ + struct ip_fw *rule; + int i; + + IPFW_UH_WLOCK(chain); + + /* + * Move rules with matching paramenerts to a new set. + * This one is much more complex. We have to ensure + * that all referenced tables (if any) are referenced + * by given rule subset only. Otherwise, we can't move + * them to new set and have to return error. + */ + if ((i = move_objects(chain, rt)) != 0) { + IPFW_UH_WUNLOCK(chain); + return (i); + } + + /* XXX: We have to do swap holding WLOCK */ + 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); +} + +/* + * Clear counters for a specific rule. + * Normally run under IPFW_UH_RLOCK, but these are idempotent ops + * so we only care that rules do not disappear. + */ +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; +} + +/* + * Flushes rules counters and/or log values on matching range. + * + * Returns number of items cleared. + */ +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); /* arbitrate writers */ + 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); +} + +/* + * Delete rules matching specified parameters + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ipfw_range_tlv ] + * Reply: [ ipfw_obj_header ipfw_range_tlv ] + * + * Saves number of deleted rules in ipfw_range_tlv->new_set. + * + * Returns 0 on success. + */ +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); + + /* Save number of rules deleted */ + rh->range.new_set = ndel; + return (0); +} + +/* + * Move rules/sets matching specified parameters + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ipfw_range_tlv ] + * + * Returns 0 on success. + */ +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)); +} + +/* + * Clear rule accounting data matching specified parameters + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ipfw_range_tlv ] + * Reply: [ ipfw_obj_header ipfw_range_tlv ] + * + * Saves number of cleared rules in ipfw_range_tlv->new_set. + * + * Returns 0 on success. + */ static int -keep_rule(struct ip_fw *rule, uint8_t cmd, uint8_t set, uint32_t n) +clear_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd) { - return - (rule->rulenum == IPFW_DEFAULT_RULE) || - (cmd == 0 && n == 0 && rule->set == RESVD_SET) || - !(cmd == 0 || rule->set == set) || - !(cmd == 1 || n == 0 || n == rule->rulenum); + 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); + } + + /* Save number of rules cleared */ + 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); + + /* Change enabled/disabled sets mask */ + v_set = (V_set_disable | rt->set) & ~rt->new_set; + v_set &= ~(1 << RESVD_SET); /* set RESVD_SET always enabled */ + 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) /* nothing to do */ + return (0); + + if (mv != 0) { + /* + * Berfore moving the rules we need to check that + * there aren't any conflicting named objects. + */ + 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); + } + } + /* Swap or move two sets */ + 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); +} + +/* + * Swaps or moves set + * Data layout (v0)(current): + * Request: [ ipfw_obj_header ipfw_range_tlv ] + * + * Returns 0 on success. + */ +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); + /* enable_sets() expects bitmasks. */ + 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); } /** @@ -282,12 +1462,11 @@ keep_rule(struct ip_fw *rule, uint8_t cmd, uint8_t set, uint32_t n) static int del_entry(struct ip_fw_chain *chain, uint32_t arg) { - struct ip_fw *rule; uint32_t num; /* rule number or old_set */ uint8_t cmd, new_set; - int start, end, i, ofs, n; - struct ip_fw **map = NULL; + int do_del, ndel; int error = 0; + ipfw_range_tlv rt; num = arg & 0xffff; cmd = (arg >> 24) & 0xff; @@ -303,149 +1482,60 @@ del_entry(struct ip_fw_chain *chain, uint32_t arg) return EINVAL; } - IPFW_UH_WLOCK(chain); /* arbitrate writers */ - chain->reap = NULL; /* prepare for deletions */ + /* Convert old requests into new representation */ + memset(&rt, 0, sizeof(rt)); + rt.start_rule = num; + rt.end_rule = num; + rt.set = num; + rt.new_set = new_set; + do_del = 0; switch (cmd) { - case 0: /* delete rules "num" (num == 0 matches all) */ - case 1: /* delete all rules in set N */ - case 5: /* delete rules with number N and set "new_set". */ - - /* - * Locate first rule to delete (start), the rule after - * the last one to delete (end), and count how many - * rules to delete (n). Always use keep_rule() to - * determine which rules to keep. - */ - n = 0; - if (cmd == 1) { - /* look for a specific set including RESVD_SET. - * Must scan the entire range, ignore num. - */ - new_set = num; - for (start = -1, end = i = 0; i < chain->n_rules; i++) { - if (keep_rule(chain->map[i], cmd, new_set, 0)) - continue; - if (start < 0) - start = i; - end = i; - n++; - } - end++; /* first non-matching */ - } else { - /* Optimized search on rule numbers */ - start = ipfw_find_rule(chain, num, 0); - for (end = start; end < chain->n_rules; end++) { - rule = chain->map[end]; - if (num > 0 && rule->rulenum != num) - break; - if (!keep_rule(rule, cmd, new_set, num)) - n++; - } - } - - if (n == 0) { - /* A flush request (arg == 0 or cmd == 1) on empty - * ruleset returns with no error. On the contrary, - * if there is no match on a specific request, - * we return EINVAL. - */ - if (arg != 0 && cmd != 1) - error = EINVAL; - break; - } - - /* We have something to delete. Allocate the new map */ - map = get_map(chain, -n, 1 /* locked */); - if (map == NULL) { - error = EINVAL; - break; - } - - /* 1. bcopy the initial part of the map */ - if (start > 0) - bcopy(chain->map, map, start * sizeof(struct ip_fw *)); - /* 2. copy active rules between start and end */ - for (i = ofs = start; i < end; i++) { - rule = chain->map[i]; - if (keep_rule(rule, cmd, new_set, num)) - map[ofs++] = rule; - } - /* 3. copy the final part of the map */ - bcopy(chain->map + end, map + ofs, - (chain->n_rules - end) * sizeof(struct ip_fw *)); - /* 4. swap the maps (under BH_LOCK) */ - map = swap_map(chain, map, chain->n_rules - n); - /* 5. now remove the rules deleted from the old map */ - if (cmd == 1) - ipfw_expire_dyn_rules(chain, NULL, new_set); - for (i = start; i < end; i++) { - rule = map[i]; - if (keep_rule(rule, cmd, new_set, num)) - continue; - chain->static_len -= RULESIZE(rule); - if (cmd != 1) - ipfw_expire_dyn_rules(chain, rule, RESVD_SET); - rule->x_next = chain->reap; - chain->reap = rule; - } + case 0: /* delete rules numbered "rulenum" */ + if (num == 0) + rt.flags |= IPFW_RCFLAG_ALL; + else + rt.flags |= IPFW_RCFLAG_RANGE; + do_del = 1; break; - - /* - * In the next 3 cases the loop stops at (n_rules - 1) - * because the default rule is never eligible.. - */ - - case 2: /* move rules with given RULE number to new set */ - for (i = 0; i < chain->n_rules - 1; i++) { - rule = chain->map[i]; - if (rule->rulenum == num) - rule->set = new_set; - } + case 1: /* delete rules in set "rulenum" */ + rt.flags |= IPFW_RCFLAG_SET; + do_del = 1; break; - - case 3: /* move rules with given SET number to new set */ - for (i = 0; i < chain->n_rules - 1; i++) { - rule = chain->map[i]; - if (rule->set == num) - rule->set = new_set; - } + case 5: /* delete rules "rulenum" and set "new_set" */ + rt.flags |= IPFW_RCFLAG_RANGE | IPFW_RCFLAG_SET; + rt.set = new_set; + rt.new_set = 0; + do_del = 1; break; - - case 4: /* swap two sets */ - for (i = 0; i < chain->n_rules - 1; i++) { - rule = chain->map[i]; - if (rule->set == num) - rule->set = new_set; - else if (rule->set == new_set) - rule->set = num; - } + case 2: /* move rules "rulenum" to set "new_set" */ + rt.flags |= IPFW_RCFLAG_RANGE; break; + case 3: /* move rules from set "rulenum" to set "new_set" */ + IPFW_UH_WLOCK(chain); + error = swap_sets(chain, &rt, 1); + IPFW_UH_WUNLOCK(chain); + return (error); + case 4: /* swap sets "rulenum" and "new_set" */ + IPFW_UH_WLOCK(chain); + error = swap_sets(chain, &rt, 0); + IPFW_UH_WUNLOCK(chain); + return (error); + default: + return (ENOTSUP); } - rule = chain->reap; - chain->reap = NULL; - IPFW_UH_WUNLOCK(chain); - ipfw_reap_rules(rule); - if (map) - free(map, M_IPFW); - return error; -} + if (do_del != 0) { + if ((error = delete_range(chain, &rt, &ndel)) != 0) + return (error); -/* - * Clear counters for a specific rule. - * Normally run under IPFW_UH_RLOCK, but these are idempotent ops - * so we only care that rules do not disappear. - */ -static void -clear_counters(struct ip_fw *rule, int log_only) -{ - ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule); + if (ndel == 0 && (cmd != 1 && num != 0)) + return (EINVAL); - if (log_only == 0) - IPFW_ZERO_RULE_COUNTER(rule); - if (l->o.opcode == O_LOG) - l->log_left = l->max_log; + return (0); + } + + return (move_range(chain, &rt)); } /** @@ -516,23 +1606,57 @@ zero_entry(struct ip_fw_chain *chain, u_int32_t arg, int log_only) return (0); } + /* - * Check validity of the structure before insert. - * Rules are simple, so this mostly need to check rule sizes. + * Check rule head in FreeBSD11 format + * */ static int -check_ipfw_struct(struct ip_fw *rule, int size) +check_ipfw_rule1(struct ip_fw_rule *rule, int size, + struct rule_check_info *ci) { - int l, cmdlen = 0; - int have_action=0; - ipfw_insn *cmd; + int l; + + if (size < sizeof(*rule)) { + printf("ipfw: rule too short\n"); + return (EINVAL); + } + + /* Check for valid cmd_len */ + l = roundup2(RULESIZE(rule), sizeof(uint64_t)); + if (l != size) { + printf("ipfw: size mismatch (have %d 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)); +} + +/* + * Check rule head in FreeBSD8 format + * + */ +static int +check_ipfw_rule0(struct ip_fw_rule0 *rule, int size, + struct rule_check_info *ci) +{ + int l; if (size < sizeof(*rule)) { printf("ipfw: rule too short\n"); return (EINVAL); } - /* first, check for valid size */ - l = RULESIZE(rule); + + /* Check for valid cmd_len */ + l = sizeof(*rule) + rule->cmd_len * 4 - 4; if (l != size) { printf("ipfw: size mismatch (have %d want %d)\n", size, l); return (EINVAL); @@ -542,12 +1666,26 @@ check_ipfw_struct(struct ip_fw *rule, int size) 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)); +} + +static int +check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) +{ + int cmdlen, l; + int have_action; + + have_action = 0; + /* * Now go for the individual checks. Very simple ones, basically only * instruction sizes. */ - for (l = rule->cmd_len, cmd = rule->cmd ; - l > 0 ; l -= cmdlen, cmd += cmdlen) { + for (l = cmd_len; l > 0 ; l -= cmdlen, cmd += cmdlen) { cmdlen = F_LEN(cmd); if (cmdlen > l) { printf("ipfw: opcode %d size truncated\n", @@ -557,6 +1695,10 @@ check_ipfw_struct(struct ip_fw *rule, int size) switch (cmd->opcode) { case O_PROBE_STATE: case O_KEEP_STATE: + if (cmdlen != F_INSN_SIZE(ipfw_insn)) + goto bad_size; + ci->object_opcodes++; + break; case O_PROTO: case O_IP_SRC_ME: case O_IP_DST_ME: @@ -588,6 +1730,35 @@ check_ipfw_struct(struct ip_fw *rule, int size) goto bad_size; break; + case O_EXTERNAL_ACTION: + if (cmd->arg1 == 0 || + cmdlen != F_INSN_SIZE(ipfw_insn)) { + printf("ipfw: invalid external " + "action opcode\n"); + return (EINVAL); + } + ci->object_opcodes++; + /* Do we have O_EXTERNAL_INSTANCE opcode? */ + if (l != cmdlen) { + l -= cmdlen; + cmd += cmdlen; + cmdlen = F_LEN(cmd); + if (cmd->opcode != O_EXTERNAL_INSTANCE) { + printf("ipfw: invalid opcode " + "next to external action %u\n", + cmd->opcode); + return (EINVAL); + } + if (cmd->arg1 == 0 || + cmdlen != F_INSN_SIZE(ipfw_insn)) { + printf("ipfw: invalid external " + "action instance opcode\n"); + return (EINVAL); + } + ci->object_opcodes++; + } + goto check_action; + case O_FIB: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; @@ -601,10 +1772,10 @@ check_ipfw_struct(struct ip_fw *rule, int size) case O_SETFIB: if (cmdlen != F_INSN_SIZE(ipfw_insn)) goto bad_size; - if ((cmd->arg1 != IP_FW_TABLEARG) && - (cmd->arg1 >= rt_numfibs)) { + if ((cmd->arg1 != IP_FW_TARG) && + ((cmd->arg1 & 0x7FFF) >= rt_numfibs)) { printf("ipfw: invalid fib number %d\n", - cmd->arg1); + cmd->arg1 & 0x7FFF); return EINVAL; } goto check_action; @@ -625,6 +1796,7 @@ check_ipfw_struct(struct ip_fw *rule, int size) case O_LIMIT: if (cmdlen != F_INSN_SIZE(ipfw_insn_limit)) goto bad_size; + ci->object_opcodes++; break; case O_LOG: @@ -639,7 +1811,7 @@ check_ipfw_struct(struct ip_fw *rule, int size) case O_IP_SRC_MASK: case O_IP_DST_MASK: /* only odd command lengths */ - if ( !(cmdlen & 1) || cmdlen > 31) + if ((cmdlen & 1) == 0) goto bad_size; break; @@ -666,6 +1838,18 @@ check_ipfw_struct(struct ip_fw *rule, int size) cmdlen != F_INSN_SIZE(ipfw_insn_u32) + 1 && cmdlen != F_INSN_SIZE(ipfw_insn_u32)) goto bad_size; + ci->object_opcodes++; + break; + case O_IP_FLOW_LOOKUP: + if (cmd->arg1 >= V_fw_tables_max) { + printf("ipfw: invalid table number %d\n", + cmd->arg1); + return (EINVAL); + } + if (cmdlen != F_INSN_SIZE(ipfw_insn) && + cmdlen != F_INSN_SIZE(ipfw_insn_u32)) + goto bad_size; + ci->object_opcodes++; break; case O_MACADDR2: if (cmdlen != F_INSN_SIZE(ipfw_insn_mac)) @@ -700,6 +1884,7 @@ check_ipfw_struct(struct ip_fw *rule, int size) case O_VIA: if (cmdlen != F_INSN_SIZE(ipfw_insn_if)) goto bad_size; + ci->object_opcodes++; break; case O_ALTQ: @@ -742,8 +1927,10 @@ check_ipfw_struct(struct ip_fw *rule, int size) if (cmdlen != F_INSN_SIZE(ipfw_insn_nat)) goto bad_size; goto check_action; - case O_FORWARD_MAC: /* XXX not implemented yet */ case O_CHECK_STATE: + ci->object_opcodes++; + /* FALLTHROUGH */ + case O_FORWARD_MAC: /* XXX not implemented yet */ case O_COUNT: case O_ACCEPT: case O_DENY: @@ -763,14 +1950,14 @@ check_action: printf("ipfw: opcode %d, multiple actions" " not allowed\n", cmd->opcode); - return EINVAL; + return (EINVAL); } have_action = 1; if (l != cmdlen) { printf("ipfw: opcode %d, action must be" " last opcode\n", cmd->opcode); - return EINVAL; + return (EINVAL); } break; #ifdef INET6 @@ -813,25 +2000,25 @@ check_action: case O_IP6_DST_MASK: case O_ICMP6TYPE: printf("ipfw: no IPv6 support in kernel\n"); - return EPROTONOSUPPORT; + return (EPROTONOSUPPORT); #endif default: printf("ipfw: opcode %d, unknown opcode\n", cmd->opcode); - return EINVAL; + return (EINVAL); } } } if (have_action == 0) { printf("ipfw: missing action\n"); - return EINVAL; + return (EINVAL); } return 0; bad_size: printf("ipfw: opcode %d size %d wrong\n", cmd->opcode, cmdlen); - return EINVAL; + return (EINVAL); } @@ -863,8 +2050,8 @@ struct ip_fw7 { ipfw_insn cmd[1]; /* storage for commands */ }; - int convert_rule_to_7(struct ip_fw *rule); -int convert_rule_to_8(struct ip_fw *rule); +static int convert_rule_to_7(struct ip_fw_rule0 *rule); +static int convert_rule_to_8(struct ip_fw_rule0 *rule); #ifndef RULESIZE7 #define RULESIZE7(rule) (sizeof(struct ip_fw7) + \ @@ -882,10 +2069,15 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space) { char *bp = buf; char *ep = bp + space; - struct ip_fw *rule, *dst; - int l, i; + struct ip_fw *rule; + struct ip_fw_rule0 *dst; + struct timeval boottime; + int error, i, l, warnflag; time_t boot_seconds; + warnflag = 0; + + getboottime(&boottime); boot_seconds = boottime.tv_sec; for (i = 0; i < chain->n_rules; i++) { rule = chain->map[i]; @@ -894,9 +2086,12 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space) /* Convert rule to FreeBSd 7.2 format */ l = RULESIZE7(rule); if (bp + l + sizeof(uint32_t) <= ep) { - int error; bcopy(rule, bp, l + sizeof(uint32_t)); - error = convert_rule_to_7((struct ip_fw *) bp); + error = set_legacy_obj_kidx(chain, + (struct ip_fw_rule0 *)bp); + if (error != 0) + return (0); + error = convert_rule_to_7((struct ip_fw_rule0 *) bp); if (error) return 0; /*XXX correct? */ /* @@ -914,76 +2109,1631 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space) continue; /* go to next rule */ } - /* normal mode, don't touch rules */ - l = RULESIZE(rule); + l = RULEUSIZE0(rule); if (bp + l > ep) { /* should not happen */ printf("overflow dumping static rules\n"); break; } - dst = (struct ip_fw *)bp; - bcopy(rule, dst, l); + dst = (struct ip_fw_rule0 *)bp; + export_rule0(rule, dst, l); + error = set_legacy_obj_kidx(chain, dst); + /* * XXX HACK. Store the disable mask in the "next" * pointer in a wild attempt to keep the ABI the same. * Why do we do this on EVERY rule? + * + * XXX: "ipfw set show" (ab)uses IP_FW_GET to read disabled mask + * so we need to fail _after_ saving at least one mask. */ bcopy(&V_set_disable, &dst->next_rule, sizeof(V_set_disable)); if (dst->timestamp) dst->timestamp += boot_seconds; bp += l; + + if (error != 0) { + if (error == 2) { + /* Non-fatal table rewrite error. */ + warnflag = 1; + continue; + } + printf("Stop on rule %d. Fail to convert table\n", + rule->rulenum); + break; + } } + if (warnflag != 0) + printf("ipfw: process %s is using legacy interfaces," + " consider rebuilding\n", ""); ipfw_get_dynamic(chain, &bp, ep); /* protected by the dynamic lock */ return (bp - (char *)buf); } -#define IP_FW3_OPLENGTH(x) ((x)->sopt_valsize - sizeof(ip_fw3_opheader)) -/** - * {set|get}sockopt parser. +struct dump_args { + uint32_t b; /* start rule */ + uint32_t e; /* end rule */ + uint32_t rcount; /* number of rules */ + uint32_t rsize; /* rules size */ + uint32_t tcount; /* number of tables */ + int rcounters; /* counters */ +}; + +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)); +} + +/* + * Export named object info in instance @ni, identified by @kidx + * to ipfw_obj_ntlv. TLV is allocated from @sd space. + * + * Returns 0 on success. + */ +static int +export_objhash_ntlv(struct namedobj_instance *ni, uint16_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); +} + +/* + * Dumps static rules with table TLVs in buffer @sd. + * + * Returns 0 on success. + */ +static int +dump_static_rules(struct ip_fw_chain *chain, struct dump_args *da, + uint32_t *bmask, struct sockopt_data *sd) +{ + int error; + int i, l; + uint32_t tcount; + ipfw_obj_ctlv *ctlv; + struct ip_fw *krule; + struct namedobj_instance *ni; + caddr_t dst; + + /* Dump table names first (if any) */ + if (da->tcount > 0) { + /* Header first */ + 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); + } + + i = 0; + tcount = da->tcount; + ni = ipfw_get_table_objhash(chain); + while (tcount > 0) { + if ((bmask[i / 32] & (1 << (i % 32))) == 0) { + i++; + continue; + } + + /* Jump to shared named object bitmask */ + if (i >= IPFW_TABLES_MAX) { + ni = CHAIN_TO_SRV(chain); + i -= IPFW_TABLES_MAX; + bmask += IPFW_TABLES_MAX / 32; + } + + if ((error = export_objhash_ntlv(ni, i, sd)) != 0) + return (error); + + i++; + tcount--; + } + + /* Dump rules */ + 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); +} + +/* + * Marks every object index used in @rule with bit in @bmask. + * Used to generate bitmask of referenced tables/objects for given ruleset + * or its part. + * + * Returns number of newly-referenced objects. + */ +static int +mark_object_kidx(struct ip_fw_chain *ch, struct ip_fw *rule, + uint32_t *bmask) +{ + struct opcode_obj_rewrite *rw; + ipfw_insn *cmd; + int bidx, cmdlen, l, count; + uint16_t kidx; + uint8_t subtype; + + l = rule->cmd_len; + cmd = rule->cmd; + cmdlen = 0; + count = 0; + for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { + cmdlen = F_LEN(cmd); + + rw = find_op_rw(cmd, &kidx, &subtype); + if (rw == NULL) + continue; + + bidx = kidx / 32; + /* + * Maintain separate bitmasks for table and + * non-table objects. + */ + if (rw->etlv != IPFW_TLV_TBL_NAME) + bidx += IPFW_TABLES_MAX / 32; + + if ((bmask[bidx] & (1 << (kidx % 32))) == 0) + count++; + + bmask[bidx] |= 1 << (kidx % 32); + } + + return (count); +} + +/* + * Dumps requested objects data + * Data layout (version 0)(current): + * Request: [ ipfw_cfg_lheader ] + IPFW_CFG_GET_* flags + * size = ipfw_cfg_lheader.size + * Reply: [ ipfw_cfg_lheader + * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional) + * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) + * ipfw_obj_tlv(IPFW_TLV_RULE_ENT) [ ip_fw_bcounter (optional) ip_fw_rule ] + * ] (optional) + * [ ipfw_obj_ctlv(IPFW_TLV_STATE_LIST) ipfw_obj_dyntlv x N ] (optional) + * ] + * * NOTE IPFW_TLV_STATE_LIST has the single valid field: objsize. + * The rest (size, count) are set to zero and needs to be ignored. + * + * Returns 0 on success. */ +static int +dump_config(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_cfg_lheader *hdr; + struct ip_fw *rule; + size_t sz, rnum; + uint32_t hdr_flags; + int error, i; + struct dump_args da; + uint32_t *bmask; + + hdr = (ipfw_cfg_lheader *)ipfw_get_sopt_header(sd, sizeof(*hdr)); + if (hdr == NULL) + return (EINVAL); + + error = 0; + bmask = NULL; + /* Allocate needed state. Note we allocate 2xspace mask, for table&srv */ + if (hdr->flags & IPFW_CFG_GET_STATIC) + bmask = malloc(IPFW_TABLES_MAX / 4, M_TEMP, M_WAITOK | M_ZERO); + + IPFW_UH_RLOCK(chain); + + /* + * STAGE 1: Determine size/count for objects in range. + * Prepare used tables bitmask. + */ + sz = sizeof(ipfw_cfg_lheader); + memset(&da, 0, sizeof(da)); + + da.b = 0; + da.e = chain->n_rules; + + if (hdr->end_rule != 0) { + /* Handle custom range */ + if ((rnum = hdr->start_rule) > IPFW_DEFAULT_RULE) + rnum = IPFW_DEFAULT_RULE; + da.b = ipfw_find_rule(chain, rnum, 0); + rnum = hdr->end_rule; + rnum = (rnum < IPFW_DEFAULT_RULE) ? rnum+1 : IPFW_DEFAULT_RULE; + da.e = ipfw_find_rule(chain, rnum, 0) + 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++; + /* Update bitmask of used objects for given range */ + da.tcount += mark_object_kidx(chain, rule, bmask); + } + /* Add counters if requested */ + if (hdr->flags & IPFW_CFG_GET_COUNTERS) { + da.rsize += sizeof(struct ip_fw_bcounter) * da.rcount; + da.rcounters = 1; + } + + if (da.tcount > 0) + sz += da.tcount * sizeof(ipfw_obj_ntlv) + + sizeof(ipfw_obj_ctlv); + sz += da.rsize + sizeof(ipfw_obj_ctlv); + } + + if (hdr->flags & IPFW_CFG_GET_STATES) + sz += ipfw_dyn_get_count() * sizeof(ipfw_obj_dyntlv) + + sizeof(ipfw_obj_ctlv); + + + /* + * Fill header anyway. + * Note we have to save header fields to stable storage + * buffer inside @sd can be flushed after dumping rules + */ + hdr->size = sz; + hdr->set_mask = ~V_set_disable; + hdr_flags = hdr->flags; + hdr = NULL; + + if (sd->valsize < sz) { + error = ENOMEM; + goto cleanup; + } + + /* STAGE2: Store actual data */ + if (hdr_flags & IPFW_CFG_GET_STATIC) { + error = dump_static_rules(chain, &da, bmask, 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_ctl(struct sockopt *sopt) +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); +} + +/* + * Creates non-existent objects referenced by rule. + * + * Return 0 on success. + */ +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; + uint16_t kidx; + int error; + + /* + * Compatibility stuff: do actual creation for non-existing, + * but referenced objects. + */ + 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; + } + + /* + * Error happened. We have to rollback everything. + * Drop all already acquired references. + */ + IPFW_UH_WLOCK(ch); + unref_oib_objects(ch, cmd, oib, pidx); + IPFW_UH_WUNLOCK(ch); + + return (error); + } + + return (0); +} + +/* + * Compatibility function for old ipfw(8) binaries. + * Rewrites table/nat kernel indices with userland ones. + * Convert tables matching '/^\d+$/' to their atoi() value. + * Use number 65535 for other tables. + * + * Returns 0 on success. + */ +static int +set_legacy_obj_kidx(struct ip_fw_chain *ch, struct ip_fw_rule0 *rule) +{ + struct opcode_obj_rewrite *rw; + struct named_object *no; + ipfw_insn *cmd; + char *end; + long val; + int cmdlen, error, l; + uint16_t kidx, uidx; + uint8_t subtype; + + error = 0; + + l = rule->cmd_len; + cmd = rule->cmd; + cmdlen = 0; + for ( ; l > 0 ; l -= cmdlen, cmd += cmdlen) { + cmdlen = F_LEN(cmd); + + /* Check if is index in given opcode */ + rw = find_op_rw(cmd, &kidx, &subtype); + if (rw == NULL) + continue; + + /* Try to find referenced kernel object */ + no = rw->find_bykidx(ch, kidx); + if (no == NULL) + continue; + + val = strtol(no->name, &end, 10); + if (*end == '\0' && val < 65535) { + uidx = val; + } else { + + /* + * We are called via legacy opcode. + * Save error and show table as fake number + * not to make ipfw(8) hang. + */ + uidx = 65535; + error = 2; + } + + rw->update(cmd, uidx); + } + + return (error); +} + + +/* + * Unreferences all already-referenced objects in given @cmd rule, + * using information in @oib. + * + * Used to rollback partially converted rule on error. + */ +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)); + + /* Find & unref by existing idx */ + no = rw->find_bykidx(ch, p->kidx); + KASSERT(no != NULL, ("Ref'd object %d disappeared", p->kidx)); + no->refcnt--; + } +} + +/* + * Remove references from every object used in @rule. + * Used at rule removal code. + */ +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; + int cmdlen, l; + uint16_t kidx; + 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, ("table id %d not found", kidx)); + KASSERT(no->subtype == subtype, + ("wrong type %d (%d) for table id %d", + no->subtype, subtype, kidx)); + KASSERT(no->refcnt > 0, ("refcount for table %d is %d", + kidx, no->refcnt)); + + if (no->refcnt == 1 && rw->destroy_object != NULL) + rw->destroy_object(ch, no); + else + no->refcnt--; + } +} + + +/* + * Find and reference object (if any) stored in instruction @cmd. + * + * Saves object info in @pidx, sets + * - @unresolved to 1 if object should exists but not found + * + * Returns non-zero value in case of error. + */ +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; + + /* Check if this opcode is candidate for rewrite */ + rw = find_op_rw(cmd, &ti->uidx, &ti->type); + if (rw == NULL) + return (0); + + /* Need to rewrite. Save necessary fields */ + pidx->uidx = ti->uidx; + pidx->type = ti->type; + + /* Try to find referenced kernel object */ + error = rw->find_byname(ch, ti, &no); + if (error != 0) + return (error); + if (no == NULL) { + /* + * Report about unresolved object for automaic + * creation. + */ + *unresolved = 1; + return (0); + } + + /* Found. Bump refcount and update kidx. */ + no->refcnt++; + rw->update(cmd, no->kidx); + return (0); +} + +/* + * Finds and bumps refcount for objects referenced by given @rule. + * Auto-creates non-existing tables. + * Fills in @oib array with userland/kernel indexes. + * + * Returns 0 on success. + */ +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); + + /* Increase refcount on each existing referenced table. */ + 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; + /* + * Compatibility stuff for old clients: + * prepare to automaitcally create non-existing objects. + */ + if (unresolved != 0) { + pidx->off = rule->cmd_len - l; + pidx++; + } + } + + if (error != 0) { + /* Unref everything we have already done */ + unref_oib_objects(ch, rule->cmd, oib, pidx); + IPFW_UH_WUNLOCK(ch); + return (error); + } + IPFW_UH_WUNLOCK(ch); + + /* Perform auto-creation for non-existing objects */ + if (pidx != oib) + error = create_objects_compat(ch, rule->cmd, oib, pidx, ti); + + /* Calculate real number of dynamic objects */ + ci->object_opcodes = (uint16_t)(pidx - oib); + + return (error); +} + +/* + * Checks is opcode is referencing table of appropriate type. + * Adds reference count for found table if true. + * Rewrites user-supplied opcode values with kernel ones. + * + * Returns 0 on success and appropriate error code otherwise. + */ +static int +rewrite_rule_uidx(struct ip_fw_chain *chain, struct rule_check_info *ci) +{ + int error; + ipfw_insn *cmd; + uint8_t type; + struct obj_idx *p, *pidx_first, *pidx_last; + struct tid_info ti; + + /* + * Prepare an array for storing opcode indices. + * Use stack allocation by default. + */ + if (ci->object_opcodes <= (sizeof(ci->obuf)/sizeof(ci->obuf[0]))) { + /* Stack */ + pidx_first = ci->obuf; + } else + pidx_first = malloc( + ci->object_opcodes * sizeof(struct obj_idx), + M_IPFW, M_WAITOK | M_ZERO); + + error = 0; + type = 0; + memset(&ti, 0, sizeof(ti)); + + /* Use set rule is assigned to. */ + 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); + } + + /* Reference all used tables and other objects */ + error = ref_rule_objects(chain, ci->krule, ci, pidx_first, &ti); + if (error != 0) + goto free; + /* + * Note that ref_rule_objects() might have updated ci->object_opcodes + * to reflect actual number of object opcodes. + */ + + /* Perform rewrite of remaining opcodes */ + 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); +} + +/* + * Adds one or more rules to ipfw @chain. + * Data layout (version 0)(current): + * Request: + * [ + * ip_fw3_opheader + * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional *1) + * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] (*2) (*3) + * ] + * Reply: + * [ + * ip_fw3_opheader + * [ ipfw_obj_ctlv(IPFW_TLV_TBL_LIST) ipfw_obj_ntlv x N ] (optional) + * [ ipfw_obj_ctlv(IPFW_TLV_RULE_LIST) ip_fw x N ] + * ] + * + * Rules in reply are modified to store their actual ruleset number. + * + * (*1) TLVs inside IPFW_TLV_TBL_LIST needs to be sorted ascending + * according to their idx field and there has to be no duplicates. + * (*2) Numbered rules inside IPFW_TLV_RULE_LIST needs to be sorted ascending. + * (*3) Each ip_fw structure needs to be aligned to u64 boundary. + * + * Returns 0 on success. + */ +static int +add_rules(struct ip_fw_chain *chain, ip_fw3_opheader *op3, + struct sockopt_data *sd) +{ + ipfw_obj_ctlv *ctlv, *rtlv, *tstate; + ipfw_obj_ntlv *ntlv; + int clen, error, idx; + uint32_t count, read; + struct ip_fw_rule *r; + struct rule_check_info rci, *ci, *cbuf; + int i, rsize; + + op3 = (ip_fw3_opheader *)ipfw_get_sopt_space(sd, sd->valsize); + ctlv = (ipfw_obj_ctlv *)(op3 + 1); + + read = sizeof(ip_fw3_opheader); + rtlv = NULL; + tstate = NULL; + cbuf = NULL; + memset(&rci, 0, sizeof(struct rule_check_info)); + + if (read + sizeof(*ctlv) > sd->valsize) + return (EINVAL); + + if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) { + clen = ctlv->head.length; + /* Check size and alignment */ + if (clen > sd->valsize || clen < sizeof(*ctlv)) + return (EINVAL); + if ((clen % sizeof(uint64_t)) != 0) + return (EINVAL); + + /* + * Some table names or other named objects. + * Check for validness. + */ + count = (ctlv->head.length - sizeof(*ctlv)) / sizeof(*ntlv); + if (ctlv->count != count || ctlv->objsize != sizeof(*ntlv)) + return (EINVAL); + + /* + * Check each TLV. + * Ensure TLVs are sorted ascending and + * there are no duplicates. + */ + idx = -1; + 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 (clen + read > sd->valsize || clen < sizeof(*ctlv)) + return (EINVAL); + if ((clen % sizeof(uint64_t)) != 0) + return (EINVAL); + + /* + * TODO: Permit adding multiple rules at once + */ + if (ctlv->count != 1) + return (ENOTSUP); + + clen -= sizeof(*ctlv); + + if (ctlv->count > clen / sizeof(struct ip_fw_rule)) + return (EINVAL); + + /* Allocate state for each rule or use stack */ + if (ctlv->count == 1) { + memset(&rci, 0, sizeof(struct rule_check_info)); + cbuf = &rci; + } else + cbuf = malloc(ctlv->count * sizeof(*ci), M_TEMP, + M_WAITOK | M_ZERO); + ci = cbuf; + + /* + * Check each rule for validness. + * Ensure numbered rules are sorted ascending + * and properly aligned + */ + idx = 0; + r = (struct ip_fw_rule *)(ctlv + 1); + count = 0; + error = 0; + while (clen > 0) { + rsize = roundup2(RULESIZE(r), sizeof(uint64_t)); + if (rsize > clen || ctlv->count <= count) { + error = EINVAL; + break; + } + + ci->ctlv = tstate; + error = check_ipfw_rule1(r, rsize, ci); + if (error != 0) + break; + + /* Check sorting */ + if (r->rulenum != 0 && r->rulenum < idx) { + printf("rulenum %d idx %d\n", r->rulenum, idx); + error = EINVAL; + break; + } + idx = r->rulenum; + + ci->urule = (caddr_t)r; + + rsize = roundup2(rsize, sizeof(uint64_t)); + clen -= rsize; + r = (struct ip_fw_rule *)((caddr_t)r + rsize); + count++; + ci++; + } + + if (ctlv->count != count || error != 0) { + if (cbuf != &rci) + 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 || rtlv->count == 0) { + if (cbuf != NULL && cbuf != &rci) + free(cbuf, M_TEMP); + return (EINVAL); + } + + /* + * Passed rules seems to be valid. + * Allocate storage and try to add them to chain. + */ + for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) { + clen = RULEKSIZE1((struct ip_fw_rule *)ci->urule); + ci->krule = ipfw_alloc_rule(chain, clen); + import_rule1(ci); + } + + if ((error = commit_rules(chain, cbuf, rtlv->count)) != 0) { + /* Free allocate krules */ + for (i = 0, ci = cbuf; i < rtlv->count; i++, ci++) + free_rule(ci->krule); + } + + if (cbuf != NULL && cbuf != &rci) + free(cbuf, M_TEMP); + + return (error); +} + +/* + * Lists all sopts currently registered. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size + * Reply: [ ipfw_obj_lheader ipfw_sopt_info x N ] + * + * Returns 0 on success + */ +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); + + /* Fill in header regadless of buffer size */ + 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 = 1; 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); +} + +/* + * Compares two opcodes. + * Used both in qsort() and bsearch(). + * + * Returns 0 if match is found. + */ +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); +} + +/* + * XXX: Rewrite bsearch() + */ +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); + + /* Find the first element matching the same opcode */ + lo = rw; + for ( ; lo > ctl3_rewriters && (lo - 1)->opcode == op; lo--) + ; + + /* Find the last element matching the same opcode */ + hi = rw; + ctl3_max = ctl3_rewriters + ctl3_rsize; + for ( ; (hi + 1) < ctl3_max && (hi + 1)->opcode == op; hi++) + ; + + *plo = lo; + *phi = hi; + + return (0); +} + +/* + * Finds opcode object rewriter based on @code. + * + * Returns pointer to handler or NULL. + */ +static struct opcode_obj_rewrite * +find_op_rw(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + struct opcode_obj_rewrite *rw, *lo, *hi; + uint16_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, uint16_t *puidx) +{ + + if (find_op_rw(cmd, puidx, NULL) == 0) + return (1); + return (0); +} + +void +update_opcode_kidx(ipfw_insn *cmd, uint16_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() +{ + + ctl3_rewriters = NULL; + ctl3_rsize = 0; +} + +void +ipfw_destroy_obj_rewriter() +{ + + if (ctl3_rewriters != NULL) + free(ctl3_rewriters, M_IPFW); + ctl3_rewriters = NULL; + ctl3_rsize = 0; +} + +/* + * Adds one or more opcode object rewrite handlers to the global array. + * Function may sleep. + */ +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; + + /* Retry */ + free(tmp, M_IPFW); + } + + /* Merge old & new arrays */ + 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); + /* Switch new and free old */ + if (ctl3_rewriters != NULL) + free(ctl3_rewriters, M_IPFW); + ctl3_rewriters = tmp; + ctl3_rsize = sz; + + CTL3_UNLOCK(); +} + +/* + * Removes one or more object rewrite handlers from the global array. + */ +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); +} + +/* + * Lists all service objects. + * Data layout (v0)(current): + * Request: [ ipfw_obj_lheader ] size = ipfw_obj_lheader.size + * Reply: [ ipfw_obj_lheader [ ipfw_obj_ntlv x N ] (optional) ] + * Returns 0 on success + */ +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); +} + +/* + * Compares two sopt handlers (code, version and handler ptr). + * Used both as qsort() and bsearch(). + * Does not compare handler for latter case. + * + * Returns 0 if match is found. + */ +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); + + /* bsearch helper */ + 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); +} + +/* + * Finds sopt handler based on @code and @version. + * + * Returns pointer to handler or NULL. + */ +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++; + /* Copy handler data to requested buffer */ + *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() +{ + + CTL3_LOCK_INIT(); + IPFW_ADD_SOPT_HANDLER(1, scodes); +} + +void +ipfw_destroy_sopt_handler() +{ + + IPFW_DEL_SOPT_HANDLER(1, scodes); + CTL3_LOCK_DESTROY(); +} + +/* + * Adds one or more sockopt handlers to the global array. + * Function may sleep. + */ +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; + + /* Retry */ + free(tmp, M_IPFW); + } + + /* Merge old & new arrays */ + 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); + /* Switch new and free old */ + if (ctl3_handlers != NULL) + free(ctl3_handlers, M_IPFW); + ctl3_handlers = tmp; + ctl3_hsize = sz; + ctl3_gencnt++; + + CTL3_UNLOCK(); +} + +/* + * Removes one or more sockopt handlers from the global array. + */ +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); +} + +/* + * Writes data accumulated in @sd to sockopt buffer. + * Zeroes internal @sd buffer. + */ +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; + + /* Update sopt buffer data */ + sopt->sopt_valsize = sd->ktotal; + sopt->sopt_val = sd->sopt_val + sd->ktotal; + + return (0); +} + +/* + * Ensures that @sd buffer has contiguous @neeeded number of + * bytes. + * + * Returns pointer to requested space or NULL. + */ +caddr_t +ipfw_get_sopt_space(struct sockopt_data *sd, size_t needed) { -#define RULE_MAXSIZE (256*sizeof(u_int32_t)) int error; - size_t size, len, valsize; - struct ip_fw *buf, *rule; + caddr_t addr; + + if (sd->kavail < needed) { + /* + * Flush data and try another time. + */ + 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); +} + +/* + * Requests @needed contiguous bytes from @sd buffer. + * Function is used to notify subsystem that we are + * interesed in first @needed bytes (request header) + * and the rest buffer can be safely zeroed. + * + * Returns pointer to requested space or NULL. + */ +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); +} + +/* + * New sockopt handler. + */ +int +ipfw_ctl3(struct sockopt *sopt) +{ + int error, locked; + size_t size, valsize; struct ip_fw_chain *chain; - u_int32_t rulenum[2]; - uint32_t opt; - char xbuf[128]; + 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) + if (error != 0) + return (error); + + if (sopt->sopt_name != IP_FW3) + return (ipfw_ctl(sopt)); + + chain = &V_layer3_chain; + error = 0; + + /* Save original valsize before it is altered via sooptcopyin() */ + valsize = sopt->sopt_valsize; + memset(&sdata, 0, sizeof(sdata)); + /* Read op3 header first to determine actual operation */ + op3 = (ip_fw3_opheader *)xbuf; + error = sooptcopyin(sopt, op3, sizeof(*op3), sizeof(*op3)); + if (error != 0) + return (error); + sopt->sopt_valsize = valsize; + + /* + * Find and reference command. + */ + error = find_ref_sh(op3->opcode, op3->version, &h); + if (error != 0) return (error); /* * Disallow modifications in really-really secure mode, but still allow * the logging counters to be reset. */ - if (sopt->sopt_name == IP_FW_ADD || - (sopt->sopt_dir == SOPT_SET && sopt->sopt_name != IP_FW_RESETLOG)) { + if ((h.dir & HDIR_SET) != 0 && h.opcode != IP_FW_XRESETLOG) { error = securelevel_ge(sopt->sopt_td->td_ucred, 3); - if (error) + if (error != 0) { + find_unref_sh(&h); return (error); + } } + /* + * Fill in sockopt_data structure that may be useful for + * IP_FW3 get requests. + */ + locked = 0; + if (valsize <= sizeof(xbuf)) { + /* use on-stack buffer */ + sdata.kbuf = xbuf; + sdata.ksize = sizeof(xbuf); + sdata.kavail = valsize; + } else { + + /* + * Determine opcode type/buffer size: + * allocate sliding-window buf for data export or + * contiguous buffer for special ops. + */ + if ((h.dir & HDIR_SET) != 0) { + /* Set request. Allocate contigous buffer. */ + if (valsize > CTL3_LARGEBUF) { + find_unref_sh(&h); + return (EFBIG); + } + + size = valsize; + } else { + /* Get request. Allocate sliding window buffer */ + size = (valsize<CTL3_SMALLBUF) ? valsize:CTL3_SMALLBUF; + + if (size < valsize) { + /* We have to wire user buffer */ + 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; + + /* + * Copy either all request (if valsize < bsize_max) + * or first bsize_max bytes to guarantee most consumers + * that all necessary data has been copied). + * Anyway, copy not less than sizeof(ip_fw3_opheader). + */ + if ((error = sooptcopyin(sopt, sdata.kbuf, sdata.ksize, + sizeof(ip_fw3_opheader))) != 0) + return (error); + op3 = (ip_fw3_opheader *)sdata.kbuf; + + /* Finally, run handler */ + error = h.handler(chain, op3, &sdata); + find_unref_sh(&h); + + /* Flush state and free buffers */ + if (error == 0) + error = ipfw_flush_sopt_data(&sdata); + else + ipfw_flush_sopt_data(&sdata); + + if (locked != 0) + vsunlock(sdata.sopt_val, valsize); + + /* Restore original pointer and set number of bytes written */ + sopt->sopt_val = sdata.sopt_val; + sopt->sopt_valsize = sdata.ktotal; + if (sdata.kbuf != xbuf) + free(sdata.kbuf, M_TEMP); + + return (error); +} + +/** + * {set|get}sockopt parser. + */ +int +ipfw_ctl(struct sockopt *sopt) +{ +#define RULE_MAXSIZE (512*sizeof(u_int32_t)) + int error; + size_t size, valsize; + struct ip_fw *buf; + struct ip_fw_rule0 *rule; + struct ip_fw_chain *chain; + u_int32_t rulenum[2]; + uint32_t opt; + struct rule_check_info ci; + IPFW_RLOCK_TRACKER; + chain = &V_layer3_chain; error = 0; /* Save original valsize before it is altered via sooptcopyin() */ valsize = sopt->sopt_valsize; - if ((opt = sopt->sopt_name) == IP_FW3) { - /* - * Copy not less than sizeof(ip_fw3_opheader). - * We hope any IP_FW3 command will fit into 128-byte buffer. - */ - if ((error = sooptcopyin(sopt, xbuf, sizeof(xbuf), - sizeof(ip_fw3_opheader))) != 0) + opt = sopt->sopt_name; + + /* + * Disallow modifications in really-really secure mode, but still allow + * the logging counters to be reset. + */ + if (opt == IP_FW_ADD || + (sopt->sopt_dir == SOPT_SET && opt != IP_FW_RESETLOG)) { + error = securelevel_ge(sopt->sopt_td->td_ucred, 3); + if (error != 0) return (error); - op3 = (ip_fw3_opheader *)xbuf; - opt = op3->opcode; } switch (opt) { @@ -1006,9 +3756,7 @@ ipfw_ctl(struct sockopt *sopt) size += ipfw_dyn_len(); if (size >= sopt->sopt_valsize) break; - buf = malloc(size, M_TEMP, M_WAITOK); - if (buf == NULL) - break; + buf = malloc(size, M_TEMP, M_WAITOK | M_ZERO); IPFW_UH_RLOCK(chain); /* check again how much space we need */ want = chain->static_len + ipfw_dyn_len(); @@ -1033,6 +3781,8 @@ ipfw_ctl(struct sockopt *sopt) error = sooptcopyin(sopt, rule, RULE_MAXSIZE, sizeof(struct ip_fw7) ); + memset(&ci, 0, sizeof(struct rule_check_info)); + /* * If the size of commands equals RULESIZE7 then we assume * a FreeBSD7.2 binary is talking to us (set is7=1). @@ -1042,25 +3792,30 @@ ipfw_ctl(struct sockopt *sopt) * the first ipfw command is 'ipfw [pipe] list') * the ipfw binary may crash or loop infinitly... */ - if (sopt->sopt_valsize == RULESIZE7(rule)) { + size = sopt->sopt_valsize; + if (size == RULESIZE7(rule)) { is7 = 1; error = convert_rule_to_8(rule); if (error) { free(rule, M_TEMP); return error; } - if (error == 0) - error = check_ipfw_struct(rule, RULESIZE(rule)); - } else { + size = RULESIZE(rule); + } else is7 = 0; if (error == 0) - error = check_ipfw_struct(rule, sopt->sopt_valsize); - } + error = check_ipfw_rule0(rule, size, &ci); if (error == 0) { - /* locking is done within ipfw_add_rule() */ - error = ipfw_add_rule(chain, rule); - size = RULESIZE(rule); - if (!error && sopt->sopt_dir == SOPT_GET) { + /* locking is done within add_rule() */ + struct ip_fw *krule; + krule = ipfw_alloc_rule(chain, RULEKSIZE0(rule)); + ci.urule = (caddr_t)rule; + ci.krule = krule; + import_rule0(&ci); + error = commit_rules(chain, &ci, 1); + if (error != 0) + free_rule(ci.krule); + else if (sopt->sopt_dir == SOPT_GET) { if (is7) { error = convert_rule_to_7(rule); size = RULESIZE7(rule); @@ -1119,82 +3874,64 @@ ipfw_ctl(struct sockopt *sopt) sopt->sopt_name == IP_FW_RESETLOG); break; - /*--- TABLE manipulations are protected by the IPFW_LOCK ---*/ + /*--- TABLE opcodes ---*/ case IP_FW_TABLE_ADD: - { - ipfw_table_entry ent; - - error = sooptcopyin(sopt, &ent, - sizeof(ent), sizeof(ent)); - if (error) - break; - error = ipfw_add_table_entry(chain, ent.tbl, - &ent.addr, sizeof(ent.addr), ent.masklen, - IPFW_TABLE_CIDR, ent.value); - } - break; - case IP_FW_TABLE_DEL: { ipfw_table_entry ent; + struct tentry_info tei; + struct tid_info ti; + struct table_value v; error = sooptcopyin(sopt, &ent, sizeof(ent), sizeof(ent)); if (error) break; - error = ipfw_del_table_entry(chain, ent.tbl, - &ent.addr, sizeof(ent.addr), ent.masklen, IPFW_TABLE_CIDR); - } - break; - - case IP_FW_TABLE_XADD: /* IP_FW3 */ - case IP_FW_TABLE_XDEL: /* IP_FW3 */ - { - ipfw_table_xentry *xent = (ipfw_table_xentry *)(op3 + 1); - - /* Check minimum header size */ - if (IP_FW3_OPLENGTH(sopt) < offsetof(ipfw_table_xentry, k)) { - error = EINVAL; - break; - } - /* Check if len field is valid */ - if (xent->len > sizeof(ipfw_table_xentry)) { - error = EINVAL; - break; - } - - len = xent->len - offsetof(ipfw_table_xentry, k); - - error = (opt == IP_FW_TABLE_XADD) ? - ipfw_add_table_entry(chain, xent->tbl, &xent->k, - len, xent->masklen, xent->type, xent->value) : - ipfw_del_table_entry(chain, xent->tbl, &xent->k, - len, xent->masklen, xent->type); + memset(&tei, 0, sizeof(tei)); + tei.paddr = &ent.addr; + tei.subtype = AF_INET; + tei.masklen = ent.masklen; + ipfw_import_table_value_legacy(ent.value, &v); + tei.pvalue = &v; + memset(&ti, 0, sizeof(ti)); + ti.uidx = ent.tbl; + ti.type = IPFW_TABLE_CIDR; + + error = (opt == IP_FW_TABLE_ADD) ? + add_table_entry(chain, &ti, &tei, 0, 1) : + del_table_entry(chain, &ti, &tei, 0, 1); } break; + case IP_FW_TABLE_FLUSH: { u_int16_t tbl; + struct tid_info ti; error = sooptcopyin(sopt, &tbl, sizeof(tbl), sizeof(tbl)); if (error) break; - error = ipfw_flush_table(chain, tbl); + memset(&ti, 0, sizeof(ti)); + ti.uidx = tbl; + error = flush_table(chain, &ti); } break; case IP_FW_TABLE_GETSIZE: { u_int32_t tbl, cnt; + struct tid_info ti; if ((error = sooptcopyin(sopt, &tbl, sizeof(tbl), sizeof(tbl)))) break; + memset(&ti, 0, sizeof(ti)); + ti.uidx = tbl; IPFW_RLOCK(chain); - error = ipfw_count_table(chain, tbl, &cnt); + error = ipfw_count_table(chain, &ti, &cnt); IPFW_RUNLOCK(chain); if (error) break; @@ -1205,6 +3942,7 @@ ipfw_ctl(struct sockopt *sopt) case IP_FW_TABLE_LIST: { ipfw_table *tbl; + struct tid_info ti; if (sopt->sopt_valsize < sizeof(*tbl)) { error = EINVAL; @@ -1219,8 +3957,10 @@ ipfw_ctl(struct sockopt *sopt) } tbl->size = (size - sizeof(*tbl)) / sizeof(ipfw_table_entry); + memset(&ti, 0, sizeof(ti)); + ti.uidx = tbl->tbl; IPFW_RLOCK(chain); - error = ipfw_dump_table(chain, tbl); + error = ipfw_dump_table_legacy(chain, &ti, tbl); IPFW_RUNLOCK(chain); if (error) { free(tbl, M_TEMP); @@ -1231,62 +3971,6 @@ ipfw_ctl(struct sockopt *sopt) } break; - case IP_FW_TABLE_XGETSIZE: /* IP_FW3 */ - { - uint32_t *tbl; - - if (IP_FW3_OPLENGTH(sopt) < sizeof(uint32_t)) { - error = EINVAL; - break; - } - - tbl = (uint32_t *)(op3 + 1); - - IPFW_RLOCK(chain); - error = ipfw_count_xtable(chain, *tbl, tbl); - IPFW_RUNLOCK(chain); - if (error) - break; - error = sooptcopyout(sopt, op3, sopt->sopt_valsize); - } - break; - - case IP_FW_TABLE_XLIST: /* IP_FW3 */ - { - ipfw_xtable *tbl; - - if ((size = valsize) < sizeof(ipfw_xtable)) { - error = EINVAL; - break; - } - - tbl = malloc(size, M_TEMP, M_ZERO | M_WAITOK); - memcpy(tbl, op3, sizeof(ipfw_xtable)); - - /* Get maximum number of entries we can store */ - tbl->size = (size - sizeof(ipfw_xtable)) / - sizeof(ipfw_table_xentry); - IPFW_RLOCK(chain); - error = ipfw_dump_xtable(chain, tbl); - IPFW_RUNLOCK(chain); - if (error) { - free(tbl, M_TEMP); - break; - } - - /* Revert size field back to bytes */ - tbl->size = tbl->size * sizeof(ipfw_table_xentry) + - sizeof(ipfw_table); - /* - * Since we call sooptcopyin() with small buffer, sopt_valsize is - * decreased to reflect supplied buffer size. Set it back to original value - */ - sopt->sopt_valsize = valsize; - error = sooptcopyout(sopt, tbl, size); - free(tbl, M_TEMP); - } - break; - /*--- NAT operations are protected by the IPFW_LOCK ---*/ case IP_FW_NAT_CFG: if (IPFW_NAT_LOADED) @@ -1336,18 +4020,16 @@ ipfw_ctl(struct sockopt *sopt) return (error); #undef RULE_MAXSIZE } - - #define RULE_MAXSIZE (256*sizeof(u_int32_t)) /* Functions to convert rules 7.2 <==> 8.0 */ -int -convert_rule_to_7(struct ip_fw *rule) +static int +convert_rule_to_7(struct ip_fw_rule0 *rule) { /* Used to modify original rule */ struct ip_fw7 *rule7 = (struct ip_fw7 *)rule; /* copy of original rule, version 8 */ - struct ip_fw *tmp; + struct ip_fw_rule0 *tmp; /* Used to copy commands */ ipfw_insn *ccmd, *dst; @@ -1360,13 +4042,12 @@ convert_rule_to_7(struct ip_fw *rule) bcopy(rule, tmp, RULE_MAXSIZE); /* Copy fields */ - rule7->_pad = tmp->_pad; + //rule7->_pad = tmp->_pad; rule7->set = tmp->set; rule7->rulenum = tmp->rulenum; rule7->cmd_len = tmp->cmd_len; rule7->act_ofs = tmp->act_ofs; rule7->next_rule = (struct ip_fw7 *)tmp->next_rule; - rule7->next = (struct ip_fw7 *)tmp->x_next; rule7->cmd_len = tmp->cmd_len; rule7->pcnt = tmp->pcnt; rule7->bcnt = tmp->bcnt; @@ -1396,8 +4077,8 @@ convert_rule_to_7(struct ip_fw *rule) return 0; } -int -convert_rule_to_8(struct ip_fw *rule) +static int +convert_rule_to_8(struct ip_fw_rule0 *rule) { /* Used to modify original rule */ struct ip_fw7 *rule7 = (struct ip_fw7 *) rule; @@ -1439,7 +4120,6 @@ convert_rule_to_8(struct ip_fw *rule) rule->cmd_len = tmp->cmd_len; rule->act_ofs = tmp->act_ofs; rule->next_rule = (struct ip_fw *)tmp->next_rule; - rule->x_next = (struct ip_fw *)tmp->next; rule->cmd_len = tmp->cmd_len; rule->id = 0; /* XXX see if is ok = 0 */ rule->pcnt = tmp->pcnt; @@ -1450,4 +4130,486 @@ convert_rule_to_8(struct ip_fw *rule) return 0; } +/* + * Named object api + * + */ + +void +ipfw_init_srv(struct ip_fw_chain *ch) +{ + + ch->srvmap = ipfw_objhash_create(IPFW_OBJECTS_DEFAULT); + 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); +} + +/* + * Allocate new bitmask which can be used to enlarge/shrink + * named instance index. + */ +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); + /* Mark all as free */ + memset(idx_mask, 0xFF, size * IPFW_MAX_SETS); + *idx_mask &= ~(u_long)1; /* Skip index 0 */ + + *idx = idx_mask; + *pblocks = max_blocks; +} + +/* + * Copy current bitmask index to new one. + */ +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)); + } +} + +/* + * Swaps current @ni index with new one. + */ +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; + + /* Save old values */ + *idx = old_idx; + *blocks = old_blocks; +} + +void +ipfw_objhash_bitmap_free(void *idx, int blocks) +{ + + free(idx, M_IPFW); +} + +/* + * Creates named hash instance. + * Must be called without holding any locks. + * Return pointer to new instance. + */ +struct namedobj_instance * +ipfw_objhash_create(uint32_t items) +{ + struct namedobj_instance *ni; + int i; + size_t size; + + size = sizeof(struct namedobj_instance) + + sizeof(struct namedobjects_head) * NAMEDOBJ_HASH_SIZE + + sizeof(struct namedobjects_head) * NAMEDOBJ_HASH_SIZE; + + ni = malloc(size, M_IPFW, M_WAITOK | M_ZERO); + ni->nn_size = NAMEDOBJ_HASH_SIZE; + ni->nv_size = NAMEDOBJ_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]); + + /* Set default hashing/comparison functions */ + ni->hash_f = objhash_hash_name; + ni->cmp_f = objhash_cmp_name; + + /* Allocate bitmask separately due to possible resize */ + 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, 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); +} + +/* + * Find named object by @uid. + * Check @tlvs for valid data inside. + * + * Returns pointer to found TLV or NULL. + */ +ipfw_obj_ntlv * +ipfw_find_name_tlv_type(void *tlvs, int len, uint16_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; + /* + * When userland has specified zero TLV type, do + * not compare it with eltv. In some cases userland + * doesn't know what type should it have. Use only + * uidx and name for search named_object. + */ + 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); +} + +/* + * Finds object config based on either legacy index + * or name in ntlv. + * Note @ti structure contains unchecked data from userland. + * + * Returns 0 in success and fills in @pno with found config + */ +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; + + /* + * Use set provided by @ti instead of @ntlv one. + * This is needed due to different sets behavior + * controlled by V_fw_tables_sets. + */ + set = ti->set; + *pno = ipfw_objhash_lookup_name(ni, set, name); + if (*pno == NULL) + return (ESRCH); + return (0); +} + +/* + * Find named object by name, considering also its TLV type. + */ +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, uint16_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); +} + +/* + * Runs @func for each found named object. + * It is safe to delete objects from callback + */ +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); +} + +/* + * Runs @f for each found named object with type @type. + * It is safe to delete objects from callback + */ +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); +} + +/* + * Removes index from given set. + * Returns 0 on success. + */ +int +ipfw_objhash_free_idx(struct namedobj_instance *ni, uint16_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); + + /* Mark as free */ + *mask |= (u_long)1 << v; + + /* Update free offset */ + if (ni->free_off[0] > i) + ni->free_off[0] = i; + + return (0); +} + +/* + * Allocate new index in given instance and stores in in @pidx. + * Returns 0 on success. + */ +int +ipfw_objhash_alloc_idx(void *n, uint16_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; + + /* Mark as busy */ + *mask &= ~ ((u_long)1 << (v - 1)); + + ni->free_off[0] = i; + + v = BLOCK_ITEMS * i + v - 1; + + *pidx = v; + return (0); + } + + return (1); +} + /* end of file */ |