summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c')
-rw-r--r--freebsd/sys/netpfil/ipfw/ip_fw_sockopt.c3904
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 */