summaryrefslogtreecommitdiff
path: root/freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c
diff options
context:
space:
mode:
Diffstat (limited to 'freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c')
-rw-r--r--freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c4112
1 files changed, 4112 insertions, 0 deletions
diff --git a/freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c b/freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c
new file mode 100644
index 00000000..e4c82131
--- /dev/null
+++ b/freebsd/sys/netpfil/ipfw/ip_fw_table_algo.c
@@ -0,0 +1,4112 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * Copyright (c) 2014 Yandex LLC
+ * Copyright (c) 2014 Alexander V. Chernikov
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Lookup table algorithms.
+ *
+ */
+
+#include <rtems/bsd/local/opt_ipfw.h>
+#include <rtems/bsd/local/opt_inet.h>
+#ifndef INET
+#error IPFIREWALL requires INET.
+#endif /* INET */
+#include <rtems/bsd/local/opt_inet6.h>
+
+#include <rtems/bsd/sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/rwlock.h>
+#include <sys/rmlock.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <net/if.h> /* ip_fw.h requires IFNAMSIZ */
+#include <net/radix.h>
+#include <net/route.h>
+#include <net/route_var.h>
+
+#include <netinet/in.h>
+#include <netinet/in_fib.h>
+#include <netinet/ip_var.h> /* struct ipfw_rule_ref */
+#include <netinet/ip_fw.h>
+#include <netinet6/in6_fib.h>
+
+#include <netpfil/ipfw/ip_fw_private.h>
+#include <netpfil/ipfw/ip_fw_table.h>
+
+
+/*
+ * IPFW table lookup algorithms.
+ *
+ * What is needed to add another table algo?
+ *
+ * Algo init:
+ * * struct table_algo has to be filled with:
+ * name: "type:algoname" format, e.g. "addr:radix". Currently
+ * there are the following types: "addr", "iface", "number" and "flow".
+ * type: one of IPFW_TABLE_* types
+ * flags: one or more TA_FLAGS_*
+ * ta_buf_size: size of structure used to store add/del item state.
+ * Needs to be less than TA_BUF_SZ.
+ * callbacks: see below for description.
+ * * ipfw_add_table_algo / ipfw_del_table_algo has to be called
+ *
+ * Callbacks description:
+ *
+ * -init: request to initialize new table instance.
+ * typedef int (ta_init)(struct ip_fw_chain *ch, void **ta_state,
+ * struct table_info *ti, char *data, uint8_t tflags);
+ * MANDATORY, unlocked. (M_WAITOK). Returns 0 on success.
+ *
+ * Allocate all structures needed for normal operations.
+ * * Caller may want to parse @data for some algo-specific
+ * options provided by userland.
+ * * Caller may want to save configuration state pointer to @ta_state
+ * * Caller needs to save desired runtime structure pointer(s)
+ * inside @ti fields. Note that it is not correct to save
+ * @ti pointer at this moment. Use -change_ti hook for that.
+ * * Caller has to fill in ti->lookup to appropriate function
+ * pointer.
+ *
+ *
+ *
+ * -destroy: request to destroy table instance.
+ * typedef void (ta_destroy)(void *ta_state, struct table_info *ti);
+ * MANDATORY, unlocked. (M_WAITOK).
+ *
+ * Frees all table entries and all tables structures allocated by -init.
+ *
+ *
+ *
+ * -prepare_add: request to allocate state for adding new entry.
+ * typedef int (ta_prepare_add)(struct ip_fw_chain *ch, struct tentry_info *tei,
+ * void *ta_buf);
+ * MANDATORY, unlocked. (M_WAITOK). Returns 0 on success.
+ *
+ * Allocates state and fills it in with all necessary data (EXCEPT value)
+ * from @tei to minimize operations needed to be done under WLOCK.
+ * "value" field has to be copied to new entry in @add callback.
+ * Buffer ta_buf of size ta->ta_buf_sz may be used to store
+ * allocated state.
+ *
+ *
+ *
+ * -prepare_del: request to set state for deleting existing entry.
+ * typedef int (ta_prepare_del)(struct ip_fw_chain *ch, struct tentry_info *tei,
+ * void *ta_buf);
+ * MANDATORY, locked, UH. (M_NOWAIT). Returns 0 on success.
+ *
+ * Buffer ta_buf of size ta->ta_buf_sz may be used to store
+ * allocated state. Caller should use on-stack ta_buf allocation
+ * instead of doing malloc().
+ *
+ *
+ *
+ * -add: request to insert new entry into runtime/config structures.
+ * typedef int (ta_add)(void *ta_state, struct table_info *ti,
+ * struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+ * MANDATORY, UH+WLOCK. (M_NOWAIT). Returns 0 on success.
+ *
+ * Insert new entry using previously-allocated state in @ta_buf.
+ * * @tei may have the following flags:
+ * TEI_FLAGS_UPDATE: request to add or update entry.
+ * TEI_FLAGS_DONTADD: request to update (but not add) entry.
+ * * Caller is required to do the following:
+ * copy real entry value from @tei
+ * entry added: return 0, set 1 to @pnum
+ * entry updated: return 0, store 0 to @pnum, store old value in @tei,
+ * add TEI_FLAGS_UPDATED flag to @tei.
+ * entry exists: return EEXIST
+ * entry not found: return ENOENT
+ * other error: return non-zero error code.
+ *
+ *
+ *
+ * -del: request to delete existing entry from runtime/config structures.
+ * typedef int (ta_del)(void *ta_state, struct table_info *ti,
+ * struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+ * MANDATORY, UH+WLOCK. (M_NOWAIT). Returns 0 on success.
+ *
+ * Delete entry using previously set up in @ta_buf.
+ * * Caller is required to do the following:
+ * entry deleted: return 0, set 1 to @pnum, store old value in @tei.
+ * entry not found: return ENOENT
+ * other error: return non-zero error code.
+ *
+ *
+ *
+ * -flush_entry: flush entry state created by -prepare_add / -del / others
+ * typedef void (ta_flush_entry)(struct ip_fw_chain *ch,
+ * struct tentry_info *tei, void *ta_buf);
+ * MANDATORY, may be locked. (M_NOWAIT).
+ *
+ * Delete state allocated by:
+ * -prepare_add (-add returned EEXIST|UPDATED)
+ * -prepare_del (if any)
+ * -del
+ * * Caller is required to handle empty @ta_buf correctly.
+ *
+ *
+ * -find_tentry: finds entry specified by key @tei
+ * typedef int ta_find_tentry(void *ta_state, struct table_info *ti,
+ * ipfw_obj_tentry *tent);
+ * OPTIONAL, locked (UH). (M_NOWAIT). Returns 0 on success.
+ *
+ * Finds entry specified by given key.
+ * * Caller is required to do the following:
+ * entry found: returns 0, export entry to @tent
+ * entry not found: returns ENOENT
+ *
+ *
+ * -need_modify: checks if @ti has enough space to hold another @count items.
+ * typedef int (ta_need_modify)(void *ta_state, struct table_info *ti,
+ * uint32_t count, uint64_t *pflags);
+ * OPTIONAL, locked (UH). (M_NOWAIT). Returns 0 if has.
+ *
+ * Checks if given table has enough space to add @count items without
+ * resize. Caller may use @pflags to store desired modification data.
+ *
+ *
+ *
+ * -prepare_mod: allocate structures for table modification.
+ * typedef int (ta_prepare_mod)(void *ta_buf, uint64_t *pflags);
+ * OPTIONAL(need_modify), unlocked. (M_WAITOK). Returns 0 on success.
+ *
+ * Allocate all needed state for table modification. Caller
+ * should use `struct mod_item` to store new state in @ta_buf.
+ * Up to TA_BUF_SZ (128 bytes) can be stored in @ta_buf.
+ *
+ *
+ *
+ * -fill_mod: copy some data to new state/
+ * typedef int (ta_fill_mod)(void *ta_state, struct table_info *ti,
+ * void *ta_buf, uint64_t *pflags);
+ * OPTIONAL(need_modify), locked (UH). (M_NOWAIT). Returns 0 on success.
+ *
+ * Copy as much data as we can to minimize changes under WLOCK.
+ * For example, array can be merged inside this callback.
+ *
+ *
+ *
+ * -modify: perform final modification.
+ * typedef void (ta_modify)(void *ta_state, struct table_info *ti,
+ * void *ta_buf, uint64_t pflags);
+ * OPTIONAL(need_modify), locked (UH+WLOCK). (M_NOWAIT).
+ *
+ * Performs all changes necessary to switch to new structures.
+ * * Caller should save old pointers to @ta_buf storage.
+ *
+ *
+ *
+ * -flush_mod: flush table modification state.
+ * typedef void (ta_flush_mod)(void *ta_buf);
+ * OPTIONAL(need_modify), unlocked. (M_WAITOK).
+ *
+ * Performs flush for the following:
+ * - prepare_mod (modification was not necessary)
+ * - modify (for the old state)
+ *
+ *
+ *
+ * -change_gi: monitor table info pointer changes
+ * typedef void (ta_change_ti)(void *ta_state, struct table_info *ti);
+ * OPTIONAL, locked (UH). (M_NOWAIT).
+ *
+ * Called on @ti pointer changed. Called immediately after -init
+ * to set initial state.
+ *
+ *
+ *
+ * -foreach: calls @f for each table entry
+ * typedef void ta_foreach(void *ta_state, struct table_info *ti,
+ * ta_foreach_f *f, void *arg);
+ * MANDATORY, locked(UH). (M_NOWAIT).
+ *
+ * Runs callback with specified argument for each table entry,
+ * Typically used for dumping table entries.
+ *
+ *
+ *
+ * -dump_tentry: dump table entry in current @tentry format.
+ * typedef int ta_dump_tentry(void *ta_state, struct table_info *ti, void *e,
+ * ipfw_obj_tentry *tent);
+ * MANDATORY, locked(UH). (M_NOWAIT). Returns 0 on success.
+ *
+ * Dumps entry @e to @tent.
+ *
+ *
+ * -print_config: prints custom algorithm options into buffer.
+ * typedef void (ta_print_config)(void *ta_state, struct table_info *ti,
+ * char *buf, size_t bufsize);
+ * OPTIONAL. locked(UH). (M_NOWAIT).
+ *
+ * Prints custom algorithm options in the format suitable to pass
+ * back to -init callback.
+ *
+ *
+ *
+ * -dump_tinfo: dumps algo-specific info.
+ * typedef void ta_dump_tinfo(void *ta_state, struct table_info *ti,
+ * ipfw_ta_tinfo *tinfo);
+ * OPTIONAL. locked(UH). (M_NOWAIT).
+ *
+ * Dumps options like items size/hash size, etc.
+ */
+
+MALLOC_DEFINE(M_IPFW_TBL, "ipfw_tbl", "IpFw tables");
+
+/*
+ * Utility structures/functions common to more than one algo
+ */
+
+struct mod_item {
+ void *main_ptr;
+ size_t size;
+ void *main_ptr6;
+ size_t size6;
+};
+
+static int badd(const void *key, void *item, void *base, size_t nmemb,
+ size_t size, int (*compar) (const void *, const void *));
+static int bdel(const void *key, void *base, size_t nmemb, size_t size,
+ int (*compar) (const void *, const void *));
+
+
+/*
+ * ADDR implementation using radix
+ *
+ */
+
+/*
+ * The radix code expects addr and mask to be array of bytes,
+ * with the first byte being the length of the array. rn_inithead
+ * is called with the offset in bits of the lookup key within the
+ * array. If we use a sockaddr_in as the underlying type,
+ * sin_len is conveniently located at offset 0, sin_addr is at
+ * offset 4 and normally aligned.
+ * But for portability, let's avoid assumption and make the code explicit
+ */
+#define KEY_LEN(v) *((uint8_t *)&(v))
+/*
+ * Do not require radix to compare more than actual IPv4/IPv6 address
+ */
+#define KEY_LEN_INET (offsetof(struct sockaddr_in, sin_addr) + sizeof(in_addr_t))
+#define KEY_LEN_INET6 (offsetof(struct sa_in6, sin6_addr) + sizeof(struct in6_addr))
+
+#define OFF_LEN_INET (8 * offsetof(struct sockaddr_in, sin_addr))
+#define OFF_LEN_INET6 (8 * offsetof(struct sa_in6, sin6_addr))
+
+struct radix_addr_entry {
+ struct radix_node rn[2];
+ struct sockaddr_in addr;
+ uint32_t value;
+ uint8_t masklen;
+};
+
+struct sa_in6 {
+ uint8_t sin6_len;
+ uint8_t sin6_family;
+ uint8_t pad[2];
+ struct in6_addr sin6_addr;
+};
+
+struct radix_addr_xentry {
+ struct radix_node rn[2];
+ struct sa_in6 addr6;
+ uint32_t value;
+ uint8_t masklen;
+};
+
+struct radix_cfg {
+ struct radix_node_head *head4;
+ struct radix_node_head *head6;
+ size_t count4;
+ size_t count6;
+};
+
+struct ta_buf_radix
+{
+ void *ent_ptr;
+ struct sockaddr *addr_ptr;
+ struct sockaddr *mask_ptr;
+ union {
+ struct {
+ struct sockaddr_in sa;
+ struct sockaddr_in ma;
+ } a4;
+ struct {
+ struct sa_in6 sa;
+ struct sa_in6 ma;
+ } a6;
+ } addr;
+};
+
+static int ta_lookup_radix(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int ta_init_radix(struct ip_fw_chain *ch, void **ta_state,
+ struct table_info *ti, char *data, uint8_t tflags);
+static int flush_radix_entry(struct radix_node *rn, void *arg);
+static void ta_destroy_radix(void *ta_state, struct table_info *ti);
+static void ta_dump_radix_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int ta_dump_radix_tentry(void *ta_state, struct table_info *ti,
+ void *e, ipfw_obj_tentry *tent);
+static int ta_find_radix_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static void ta_foreach_radix(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+static void tei_to_sockaddr_ent(struct tentry_info *tei, struct sockaddr *sa,
+ struct sockaddr *ma, int *set_mask);
+static int ta_prepare_add_radix(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_add_radix(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static int ta_prepare_del_radix(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_del_radix(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static void ta_flush_radix_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_need_modify_radix(void *ta_state, struct table_info *ti,
+ uint32_t count, uint64_t *pflags);
+
+static int
+ta_lookup_radix(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct radix_node_head *rnh;
+
+ if (keylen == sizeof(in_addr_t)) {
+ struct radix_addr_entry *ent;
+ struct sockaddr_in sa;
+ KEY_LEN(sa) = KEY_LEN_INET;
+ sa.sin_addr.s_addr = *((in_addr_t *)key);
+ rnh = (struct radix_node_head *)ti->state;
+ ent = (struct radix_addr_entry *)(rnh->rnh_matchaddr(&sa, &rnh->rh));
+ if (ent != NULL) {
+ *val = ent->value;
+ return (1);
+ }
+ } else {
+ struct radix_addr_xentry *xent;
+ struct sa_in6 sa6;
+ KEY_LEN(sa6) = KEY_LEN_INET6;
+ memcpy(&sa6.sin6_addr, key, sizeof(struct in6_addr));
+ rnh = (struct radix_node_head *)ti->xstate;
+ xent = (struct radix_addr_xentry *)(rnh->rnh_matchaddr(&sa6, &rnh->rh));
+ if (xent != NULL) {
+ *val = xent->value;
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * New table
+ */
+static int
+ta_init_radix(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ struct radix_cfg *cfg;
+
+ if (!rn_inithead(&ti->state, OFF_LEN_INET))
+ return (ENOMEM);
+ if (!rn_inithead(&ti->xstate, OFF_LEN_INET6)) {
+ rn_detachhead(&ti->state);
+ return (ENOMEM);
+ }
+
+ cfg = malloc(sizeof(struct radix_cfg), M_IPFW, M_WAITOK | M_ZERO);
+
+ *ta_state = cfg;
+ ti->lookup = ta_lookup_radix;
+
+ return (0);
+}
+
+static int
+flush_radix_entry(struct radix_node *rn, void *arg)
+{
+ struct radix_node_head * const rnh = arg;
+ struct radix_addr_entry *ent;
+
+ ent = (struct radix_addr_entry *)
+ rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, &rnh->rh);
+ if (ent != NULL)
+ free(ent, M_IPFW_TBL);
+ return (0);
+}
+
+static void
+ta_destroy_radix(void *ta_state, struct table_info *ti)
+{
+ struct radix_cfg *cfg;
+ struct radix_node_head *rnh;
+
+ cfg = (struct radix_cfg *)ta_state;
+
+ rnh = (struct radix_node_head *)(ti->state);
+ rnh->rnh_walktree(&rnh->rh, flush_radix_entry, rnh);
+ rn_detachhead(&ti->state);
+
+ rnh = (struct radix_node_head *)(ti->xstate);
+ rnh->rnh_walktree(&rnh->rh, flush_radix_entry, rnh);
+ rn_detachhead(&ti->xstate);
+
+ free(cfg, M_IPFW);
+}
+
+/*
+ * Provide algo-specific table info
+ */
+static void
+ta_dump_radix_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+ struct radix_cfg *cfg;
+
+ cfg = (struct radix_cfg *)ta_state;
+
+ tinfo->flags = IPFW_TATFLAGS_AFDATA | IPFW_TATFLAGS_AFITEM;
+ tinfo->taclass4 = IPFW_TACLASS_RADIX;
+ tinfo->count4 = cfg->count4;
+ tinfo->itemsize4 = sizeof(struct radix_addr_entry);
+ tinfo->taclass6 = IPFW_TACLASS_RADIX;
+ tinfo->count6 = cfg->count6;
+ tinfo->itemsize6 = sizeof(struct radix_addr_xentry);
+}
+
+static int
+ta_dump_radix_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct radix_addr_entry *n;
+#ifdef INET6
+ struct radix_addr_xentry *xn;
+#endif
+
+ n = (struct radix_addr_entry *)e;
+
+ /* Guess IPv4/IPv6 radix by sockaddr family */
+ if (n->addr.sin_family == AF_INET) {
+ tent->k.addr.s_addr = n->addr.sin_addr.s_addr;
+ tent->masklen = n->masklen;
+ tent->subtype = AF_INET;
+ tent->v.kidx = n->value;
+#ifdef INET6
+ } else {
+ xn = (struct radix_addr_xentry *)e;
+ memcpy(&tent->k, &xn->addr6.sin6_addr, sizeof(struct in6_addr));
+ tent->masklen = xn->masklen;
+ tent->subtype = AF_INET6;
+ tent->v.kidx = xn->value;
+#endif
+ }
+
+ return (0);
+}
+
+static int
+ta_find_radix_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct radix_node_head *rnh;
+ void *e;
+
+ e = NULL;
+ if (tent->subtype == AF_INET) {
+ struct sockaddr_in sa;
+ KEY_LEN(sa) = KEY_LEN_INET;
+ sa.sin_addr.s_addr = tent->k.addr.s_addr;
+ rnh = (struct radix_node_head *)ti->state;
+ e = rnh->rnh_matchaddr(&sa, &rnh->rh);
+ } else {
+ struct sa_in6 sa6;
+ KEY_LEN(sa6) = KEY_LEN_INET6;
+ memcpy(&sa6.sin6_addr, &tent->k.addr6, sizeof(struct in6_addr));
+ rnh = (struct radix_node_head *)ti->xstate;
+ e = rnh->rnh_matchaddr(&sa6, &rnh->rh);
+ }
+
+ if (e != NULL) {
+ ta_dump_radix_tentry(ta_state, ti, e, tent);
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_foreach_radix(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct radix_node_head *rnh;
+
+ rnh = (struct radix_node_head *)(ti->state);
+ rnh->rnh_walktree(&rnh->rh, (walktree_f_t *)f, arg);
+
+ rnh = (struct radix_node_head *)(ti->xstate);
+ rnh->rnh_walktree(&rnh->rh, (walktree_f_t *)f, arg);
+}
+
+
+#ifdef INET6
+static inline void ipv6_writemask(struct in6_addr *addr6, uint8_t mask);
+
+static inline void
+ipv6_writemask(struct in6_addr *addr6, uint8_t mask)
+{
+ uint32_t *cp;
+
+ for (cp = (uint32_t *)addr6; mask >= 32; mask -= 32)
+ *cp++ = 0xFFFFFFFF;
+ if (mask > 0)
+ *cp = htonl(mask ? ~((1 << (32 - mask)) - 1) : 0);
+}
+#endif
+
+static void
+tei_to_sockaddr_ent(struct tentry_info *tei, struct sockaddr *sa,
+ struct sockaddr *ma, int *set_mask)
+{
+ int mlen;
+#ifdef INET
+ struct sockaddr_in *addr, *mask;
+#endif
+#ifdef INET6
+ struct sa_in6 *addr6, *mask6;
+#endif
+ in_addr_t a4;
+
+ mlen = tei->masklen;
+
+ if (tei->subtype == AF_INET) {
+#ifdef INET
+ addr = (struct sockaddr_in *)sa;
+ mask = (struct sockaddr_in *)ma;
+ /* Set 'total' structure length */
+ KEY_LEN(*addr) = KEY_LEN_INET;
+ KEY_LEN(*mask) = KEY_LEN_INET;
+ addr->sin_family = AF_INET;
+ mask->sin_addr.s_addr =
+ htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
+ a4 = *((in_addr_t *)tei->paddr);
+ addr->sin_addr.s_addr = a4 & mask->sin_addr.s_addr;
+ if (mlen != 32)
+ *set_mask = 1;
+ else
+ *set_mask = 0;
+#endif
+#ifdef INET6
+ } else if (tei->subtype == AF_INET6) {
+ /* IPv6 case */
+ addr6 = (struct sa_in6 *)sa;
+ mask6 = (struct sa_in6 *)ma;
+ /* Set 'total' structure length */
+ KEY_LEN(*addr6) = KEY_LEN_INET6;
+ KEY_LEN(*mask6) = KEY_LEN_INET6;
+ addr6->sin6_family = AF_INET6;
+ ipv6_writemask(&mask6->sin6_addr, mlen);
+ memcpy(&addr6->sin6_addr, tei->paddr, sizeof(struct in6_addr));
+ APPLY_MASK(&addr6->sin6_addr, &mask6->sin6_addr);
+ if (mlen != 128)
+ *set_mask = 1;
+ else
+ *set_mask = 0;
+#endif
+ }
+}
+
+static int
+ta_prepare_add_radix(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_radix *tb;
+ struct radix_addr_entry *ent;
+#ifdef INET6
+ struct radix_addr_xentry *xent;
+#endif
+ struct sockaddr *addr, *mask;
+ int mlen, set_mask;
+
+ tb = (struct ta_buf_radix *)ta_buf;
+
+ mlen = tei->masklen;
+ set_mask = 0;
+
+ if (tei->subtype == AF_INET) {
+#ifdef INET
+ if (mlen > 32)
+ return (EINVAL);
+ ent = malloc(sizeof(*ent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ ent->masklen = mlen;
+
+ addr = (struct sockaddr *)&ent->addr;
+ mask = (struct sockaddr *)&tb->addr.a4.ma;
+ tb->ent_ptr = ent;
+#endif
+#ifdef INET6
+ } else if (tei->subtype == AF_INET6) {
+ /* IPv6 case */
+ if (mlen > 128)
+ return (EINVAL);
+ xent = malloc(sizeof(*xent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ xent->masklen = mlen;
+
+ addr = (struct sockaddr *)&xent->addr6;
+ mask = (struct sockaddr *)&tb->addr.a6.ma;
+ tb->ent_ptr = xent;
+#endif
+ } else {
+ /* Unknown CIDR type */
+ return (EINVAL);
+ }
+
+ tei_to_sockaddr_ent(tei, addr, mask, &set_mask);
+ /* Set pointers */
+ tb->addr_ptr = addr;
+ if (set_mask != 0)
+ tb->mask_ptr = mask;
+
+ return (0);
+}
+
+static int
+ta_add_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct radix_cfg *cfg;
+ struct radix_node_head *rnh;
+ struct radix_node *rn;
+ struct ta_buf_radix *tb;
+ uint32_t *old_value, value;
+
+ cfg = (struct radix_cfg *)ta_state;
+ tb = (struct ta_buf_radix *)ta_buf;
+
+ /* Save current entry value from @tei */
+ if (tei->subtype == AF_INET) {
+ rnh = ti->state;
+ ((struct radix_addr_entry *)tb->ent_ptr)->value = tei->value;
+ } else {
+ rnh = ti->xstate;
+ ((struct radix_addr_xentry *)tb->ent_ptr)->value = tei->value;
+ }
+
+ /* Search for an entry first */
+ rn = rnh->rnh_lookup(tb->addr_ptr, tb->mask_ptr, &rnh->rh);
+ if (rn != NULL) {
+ if ((tei->flags & TEI_FLAGS_UPDATE) == 0)
+ return (EEXIST);
+ /* Record already exists. Update value if we're asked to */
+ if (tei->subtype == AF_INET)
+ old_value = &((struct radix_addr_entry *)rn)->value;
+ else
+ old_value = &((struct radix_addr_xentry *)rn)->value;
+
+ value = *old_value;
+ *old_value = tei->value;
+ tei->value = value;
+
+ /* Indicate that update has happened instead of addition */
+ tei->flags |= TEI_FLAGS_UPDATED;
+ *pnum = 0;
+
+ return (0);
+ }
+
+ if ((tei->flags & TEI_FLAGS_DONTADD) != 0)
+ return (EFBIG);
+
+ rn = rnh->rnh_addaddr(tb->addr_ptr, tb->mask_ptr, &rnh->rh,tb->ent_ptr);
+ if (rn == NULL) {
+ /* Unknown error */
+ return (EINVAL);
+ }
+
+ if (tei->subtype == AF_INET)
+ cfg->count4++;
+ else
+ cfg->count6++;
+ tb->ent_ptr = NULL;
+ *pnum = 1;
+
+ return (0);
+}
+
+static int
+ta_prepare_del_radix(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_radix *tb;
+ struct sockaddr *addr, *mask;
+ int mlen, set_mask;
+
+ tb = (struct ta_buf_radix *)ta_buf;
+
+ mlen = tei->masklen;
+ set_mask = 0;
+
+ if (tei->subtype == AF_INET) {
+ if (mlen > 32)
+ return (EINVAL);
+
+ addr = (struct sockaddr *)&tb->addr.a4.sa;
+ mask = (struct sockaddr *)&tb->addr.a4.ma;
+#ifdef INET6
+ } else if (tei->subtype == AF_INET6) {
+ if (mlen > 128)
+ return (EINVAL);
+
+ addr = (struct sockaddr *)&tb->addr.a6.sa;
+ mask = (struct sockaddr *)&tb->addr.a6.ma;
+#endif
+ } else
+ return (EINVAL);
+
+ tei_to_sockaddr_ent(tei, addr, mask, &set_mask);
+ tb->addr_ptr = addr;
+ if (set_mask != 0)
+ tb->mask_ptr = mask;
+
+ return (0);
+}
+
+static int
+ta_del_radix(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct radix_cfg *cfg;
+ struct radix_node_head *rnh;
+ struct radix_node *rn;
+ struct ta_buf_radix *tb;
+
+ cfg = (struct radix_cfg *)ta_state;
+ tb = (struct ta_buf_radix *)ta_buf;
+
+ if (tei->subtype == AF_INET)
+ rnh = ti->state;
+ else
+ rnh = ti->xstate;
+
+ rn = rnh->rnh_deladdr(tb->addr_ptr, tb->mask_ptr, &rnh->rh);
+
+ if (rn == NULL)
+ return (ENOENT);
+
+ /* Save entry value to @tei */
+ if (tei->subtype == AF_INET)
+ tei->value = ((struct radix_addr_entry *)rn)->value;
+ else
+ tei->value = ((struct radix_addr_xentry *)rn)->value;
+
+ tb->ent_ptr = rn;
+
+ if (tei->subtype == AF_INET)
+ cfg->count4--;
+ else
+ cfg->count6--;
+ *pnum = 1;
+
+ return (0);
+}
+
+static void
+ta_flush_radix_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_radix *tb;
+
+ tb = (struct ta_buf_radix *)ta_buf;
+
+ if (tb->ent_ptr != NULL)
+ free(tb->ent_ptr, M_IPFW_TBL);
+}
+
+static int
+ta_need_modify_radix(void *ta_state, struct table_info *ti, uint32_t count,
+ uint64_t *pflags)
+{
+
+ /*
+ * radix does not require additional memory allocations
+ * other than nodes itself. Adding new masks to the tree do
+ * but we don't have any API to call (and we don't known which
+ * sizes do we need).
+ */
+ return (0);
+}
+
+struct table_algo addr_radix = {
+ .name = "addr:radix",
+ .type = IPFW_TABLE_ADDR,
+ .flags = TA_FLAG_DEFAULT,
+ .ta_buf_size = sizeof(struct ta_buf_radix),
+ .init = ta_init_radix,
+ .destroy = ta_destroy_radix,
+ .prepare_add = ta_prepare_add_radix,
+ .prepare_del = ta_prepare_del_radix,
+ .add = ta_add_radix,
+ .del = ta_del_radix,
+ .flush_entry = ta_flush_radix_entry,
+ .foreach = ta_foreach_radix,
+ .dump_tentry = ta_dump_radix_tentry,
+ .find_tentry = ta_find_radix_tentry,
+ .dump_tinfo = ta_dump_radix_tinfo,
+ .need_modify = ta_need_modify_radix,
+};
+
+
+/*
+ * addr:hash cmds
+ *
+ *
+ * ti->data:
+ * [inv.mask4][inv.mask6][log2hsize4][log2hsize6]
+ * [ 8][ 8[ 8][ 8]
+ *
+ * inv.mask4: 32 - mask
+ * inv.mask6:
+ * 1) _slow lookup: mask
+ * 2) _aligned: (128 - mask) / 8
+ * 3) _64: 8
+ *
+ *
+ * pflags:
+ * [v4=1/v6=0][hsize]
+ * [ 32][ 32]
+ */
+
+struct chashentry;
+
+SLIST_HEAD(chashbhead, chashentry);
+
+struct chash_cfg {
+ struct chashbhead *head4;
+ struct chashbhead *head6;
+ size_t size4;
+ size_t size6;
+ size_t items4;
+ size_t items6;
+ uint8_t mask4;
+ uint8_t mask6;
+};
+
+struct chashentry {
+ SLIST_ENTRY(chashentry) next;
+ uint32_t value;
+ uint32_t type;
+ union {
+ uint32_t a4; /* Host format */
+ struct in6_addr a6; /* Network format */
+ } a;
+};
+
+struct ta_buf_chash
+{
+ void *ent_ptr;
+ struct chashentry ent;
+};
+
+#ifdef INET
+static __inline uint32_t hash_ip(uint32_t addr, int hsize);
+#endif
+#ifdef INET6
+static __inline uint32_t hash_ip6(struct in6_addr *addr6, int hsize);
+static __inline uint16_t hash_ip64(struct in6_addr *addr6, int hsize);
+static __inline uint32_t hash_ip6_slow(struct in6_addr *addr6, void *key,
+ int mask, int hsize);
+static __inline uint32_t hash_ip6_al(struct in6_addr *addr6, void *key, int mask,
+ int hsize);
+#endif
+static int ta_lookup_chash_slow(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int ta_lookup_chash_aligned(struct table_info *ti, void *key,
+ uint32_t keylen, uint32_t *val);
+static int ta_lookup_chash_64(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int chash_parse_opts(struct chash_cfg *cfg, char *data);
+static void ta_print_chash_config(void *ta_state, struct table_info *ti,
+ char *buf, size_t bufsize);
+static int ta_log2(uint32_t v);
+static int ta_init_chash(struct ip_fw_chain *ch, void **ta_state,
+ struct table_info *ti, char *data, uint8_t tflags);
+static void ta_destroy_chash(void *ta_state, struct table_info *ti);
+static void ta_dump_chash_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int ta_dump_chash_tentry(void *ta_state, struct table_info *ti,
+ void *e, ipfw_obj_tentry *tent);
+static uint32_t hash_ent(struct chashentry *ent, int af, int mlen,
+ uint32_t size);
+static int tei_to_chash_ent(struct tentry_info *tei, struct chashentry *ent);
+static int ta_find_chash_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static void ta_foreach_chash(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+static int ta_prepare_add_chash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_add_chash(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static int ta_prepare_del_chash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_del_chash(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static void ta_flush_chash_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_need_modify_chash(void *ta_state, struct table_info *ti,
+ uint32_t count, uint64_t *pflags);
+static int ta_prepare_mod_chash(void *ta_buf, uint64_t *pflags);
+static int ta_fill_mod_chash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t *pflags);
+static void ta_modify_chash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags);
+static void ta_flush_mod_chash(void *ta_buf);
+
+
+#ifdef INET
+static __inline uint32_t
+hash_ip(uint32_t addr, int hsize)
+{
+
+ return (addr % (hsize - 1));
+}
+#endif
+
+#ifdef INET6
+static __inline uint32_t
+hash_ip6(struct in6_addr *addr6, int hsize)
+{
+ uint32_t i;
+
+ i = addr6->s6_addr32[0] ^ addr6->s6_addr32[1] ^
+ addr6->s6_addr32[2] ^ addr6->s6_addr32[3];
+
+ return (i % (hsize - 1));
+}
+
+
+static __inline uint16_t
+hash_ip64(struct in6_addr *addr6, int hsize)
+{
+ uint32_t i;
+
+ i = addr6->s6_addr32[0] ^ addr6->s6_addr32[1];
+
+ return (i % (hsize - 1));
+}
+
+
+static __inline uint32_t
+hash_ip6_slow(struct in6_addr *addr6, void *key, int mask, int hsize)
+{
+ struct in6_addr mask6;
+
+ ipv6_writemask(&mask6, mask);
+ memcpy(addr6, key, sizeof(struct in6_addr));
+ APPLY_MASK(addr6, &mask6);
+ return (hash_ip6(addr6, hsize));
+}
+
+static __inline uint32_t
+hash_ip6_al(struct in6_addr *addr6, void *key, int mask, int hsize)
+{
+ uint64_t *paddr;
+
+ paddr = (uint64_t *)addr6;
+ *paddr = 0;
+ *(paddr + 1) = 0;
+ memcpy(addr6, key, mask);
+ return (hash_ip6(addr6, hsize));
+}
+#endif
+
+static int
+ta_lookup_chash_slow(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct chashbhead *head;
+ struct chashentry *ent;
+ uint16_t hash, hsize;
+ uint8_t imask;
+
+ if (keylen == sizeof(in_addr_t)) {
+#ifdef INET
+ head = (struct chashbhead *)ti->state;
+ imask = ti->data >> 24;
+ hsize = 1 << ((ti->data & 0xFFFF) >> 8);
+ uint32_t a;
+ a = ntohl(*((in_addr_t *)key));
+ a = a >> imask;
+ hash = hash_ip(a, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (ent->a.a4 == a) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ } else {
+#ifdef INET6
+ /* IPv6: worst scenario: non-round mask */
+ struct in6_addr addr6;
+ head = (struct chashbhead *)ti->xstate;
+ imask = (ti->data & 0xFF0000) >> 16;
+ hsize = 1 << (ti->data & 0xFF);
+ hash = hash_ip6_slow(&addr6, key, imask, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (memcmp(&ent->a.a6, &addr6, 16) == 0) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ }
+
+ return (0);
+}
+
+static int
+ta_lookup_chash_aligned(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct chashbhead *head;
+ struct chashentry *ent;
+ uint16_t hash, hsize;
+ uint8_t imask;
+
+ if (keylen == sizeof(in_addr_t)) {
+#ifdef INET
+ head = (struct chashbhead *)ti->state;
+ imask = ti->data >> 24;
+ hsize = 1 << ((ti->data & 0xFFFF) >> 8);
+ uint32_t a;
+ a = ntohl(*((in_addr_t *)key));
+ a = a >> imask;
+ hash = hash_ip(a, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (ent->a.a4 == a) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ } else {
+#ifdef INET6
+ /* IPv6: aligned to 8bit mask */
+ struct in6_addr addr6;
+ uint64_t *paddr, *ptmp;
+ head = (struct chashbhead *)ti->xstate;
+ imask = (ti->data & 0xFF0000) >> 16;
+ hsize = 1 << (ti->data & 0xFF);
+
+ hash = hash_ip6_al(&addr6, key, imask, hsize);
+ paddr = (uint64_t *)&addr6;
+ SLIST_FOREACH(ent, &head[hash], next) {
+ ptmp = (uint64_t *)&ent->a.a6;
+ if (paddr[0] == ptmp[0] && paddr[1] == ptmp[1]) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ }
+
+ return (0);
+}
+
+static int
+ta_lookup_chash_64(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct chashbhead *head;
+ struct chashentry *ent;
+ uint16_t hash, hsize;
+ uint8_t imask;
+
+ if (keylen == sizeof(in_addr_t)) {
+#ifdef INET
+ head = (struct chashbhead *)ti->state;
+ imask = ti->data >> 24;
+ hsize = 1 << ((ti->data & 0xFFFF) >> 8);
+ uint32_t a;
+ a = ntohl(*((in_addr_t *)key));
+ a = a >> imask;
+ hash = hash_ip(a, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (ent->a.a4 == a) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ } else {
+#ifdef INET6
+ /* IPv6: /64 */
+ uint64_t a6, *paddr;
+ head = (struct chashbhead *)ti->xstate;
+ paddr = (uint64_t *)key;
+ hsize = 1 << (ti->data & 0xFF);
+ a6 = *paddr;
+ hash = hash_ip64((struct in6_addr *)key, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ paddr = (uint64_t *)&ent->a.a6;
+ if (a6 == *paddr) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+#endif
+ }
+
+ return (0);
+}
+
+static int
+chash_parse_opts(struct chash_cfg *cfg, char *data)
+{
+ char *pdel, *pend, *s;
+ int mask4, mask6;
+
+ mask4 = cfg->mask4;
+ mask6 = cfg->mask6;
+
+ if (data == NULL)
+ return (0);
+ if ((pdel = strchr(data, ' ')) == NULL)
+ return (0);
+ while (*pdel == ' ')
+ pdel++;
+ if (strncmp(pdel, "masks=", 6) != 0)
+ return (EINVAL);
+ if ((s = strchr(pdel, ' ')) != NULL)
+ *s++ = '\0';
+
+ pdel += 6;
+ /* Need /XX[,/YY] */
+ if (*pdel++ != '/')
+ return (EINVAL);
+ mask4 = strtol(pdel, &pend, 10);
+ if (*pend == ',') {
+ /* ,/YY */
+ pdel = pend + 1;
+ if (*pdel++ != '/')
+ return (EINVAL);
+ mask6 = strtol(pdel, &pend, 10);
+ if (*pend != '\0')
+ return (EINVAL);
+ } else if (*pend != '\0')
+ return (EINVAL);
+
+ if (mask4 < 0 || mask4 > 32 || mask6 < 0 || mask6 > 128)
+ return (EINVAL);
+
+ cfg->mask4 = mask4;
+ cfg->mask6 = mask6;
+
+ return (0);
+}
+
+static void
+ta_print_chash_config(void *ta_state, struct table_info *ti, char *buf,
+ size_t bufsize)
+{
+ struct chash_cfg *cfg;
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ if (cfg->mask4 != 32 || cfg->mask6 != 128)
+ snprintf(buf, bufsize, "%s masks=/%d,/%d", "addr:hash",
+ cfg->mask4, cfg->mask6);
+ else
+ snprintf(buf, bufsize, "%s", "addr:hash");
+}
+
+static int
+ta_log2(uint32_t v)
+{
+ uint32_t r;
+
+ r = 0;
+ while (v >>= 1)
+ r++;
+
+ return (r);
+}
+
+/*
+ * New table.
+ * We assume 'data' to be either NULL or the following format:
+ * 'addr:hash [masks=/32[,/128]]'
+ */
+static int
+ta_init_chash(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ int error, i;
+ uint32_t hsize;
+ struct chash_cfg *cfg;
+
+ cfg = malloc(sizeof(struct chash_cfg), M_IPFW, M_WAITOK | M_ZERO);
+
+ cfg->mask4 = 32;
+ cfg->mask6 = 128;
+
+ if ((error = chash_parse_opts(cfg, data)) != 0) {
+ free(cfg, M_IPFW);
+ return (error);
+ }
+
+ cfg->size4 = 128;
+ cfg->size6 = 128;
+
+ cfg->head4 = malloc(sizeof(struct chashbhead) * cfg->size4, M_IPFW,
+ M_WAITOK | M_ZERO);
+ cfg->head6 = malloc(sizeof(struct chashbhead) * cfg->size6, M_IPFW,
+ M_WAITOK | M_ZERO);
+ for (i = 0; i < cfg->size4; i++)
+ SLIST_INIT(&cfg->head4[i]);
+ for (i = 0; i < cfg->size6; i++)
+ SLIST_INIT(&cfg->head6[i]);
+
+
+ *ta_state = cfg;
+ ti->state = cfg->head4;
+ ti->xstate = cfg->head6;
+
+ /* Store data depending on v6 mask length */
+ hsize = ta_log2(cfg->size4) << 8 | ta_log2(cfg->size6);
+ if (cfg->mask6 == 64) {
+ ti->data = (32 - cfg->mask4) << 24 | (128 - cfg->mask6) << 16|
+ hsize;
+ ti->lookup = ta_lookup_chash_64;
+ } else if ((cfg->mask6 % 8) == 0) {
+ ti->data = (32 - cfg->mask4) << 24 |
+ cfg->mask6 << 13 | hsize;
+ ti->lookup = ta_lookup_chash_aligned;
+ } else {
+ /* don't do that! */
+ ti->data = (32 - cfg->mask4) << 24 |
+ cfg->mask6 << 16 | hsize;
+ ti->lookup = ta_lookup_chash_slow;
+ }
+
+ return (0);
+}
+
+static void
+ta_destroy_chash(void *ta_state, struct table_info *ti)
+{
+ struct chash_cfg *cfg;
+ struct chashentry *ent, *ent_next;
+ int i;
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ for (i = 0; i < cfg->size4; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head4[i], next, ent_next)
+ free(ent, M_IPFW_TBL);
+
+ for (i = 0; i < cfg->size6; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head6[i], next, ent_next)
+ free(ent, M_IPFW_TBL);
+
+ free(cfg->head4, M_IPFW);
+ free(cfg->head6, M_IPFW);
+
+ free(cfg, M_IPFW);
+}
+
+static void
+ta_dump_chash_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+ struct chash_cfg *cfg;
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ tinfo->flags = IPFW_TATFLAGS_AFDATA | IPFW_TATFLAGS_AFITEM;
+ tinfo->taclass4 = IPFW_TACLASS_HASH;
+ tinfo->size4 = cfg->size4;
+ tinfo->count4 = cfg->items4;
+ tinfo->itemsize4 = sizeof(struct chashentry);
+ tinfo->taclass6 = IPFW_TACLASS_HASH;
+ tinfo->size6 = cfg->size6;
+ tinfo->count6 = cfg->items6;
+ tinfo->itemsize6 = sizeof(struct chashentry);
+}
+
+static int
+ta_dump_chash_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct chash_cfg *cfg;
+ struct chashentry *ent;
+
+ cfg = (struct chash_cfg *)ta_state;
+ ent = (struct chashentry *)e;
+
+ if (ent->type == AF_INET) {
+ tent->k.addr.s_addr = htonl(ent->a.a4 << (32 - cfg->mask4));
+ tent->masklen = cfg->mask4;
+ tent->subtype = AF_INET;
+ tent->v.kidx = ent->value;
+#ifdef INET6
+ } else {
+ memcpy(&tent->k, &ent->a.a6, sizeof(struct in6_addr));
+ tent->masklen = cfg->mask6;
+ tent->subtype = AF_INET6;
+ tent->v.kidx = ent->value;
+#endif
+ }
+
+ return (0);
+}
+
+static uint32_t
+hash_ent(struct chashentry *ent, int af, int mlen, uint32_t size)
+{
+ uint32_t hash;
+
+ hash = 0;
+
+ if (af == AF_INET) {
+#ifdef INET
+ hash = hash_ip(ent->a.a4, size);
+#endif
+ } else {
+#ifdef INET6
+ if (mlen == 64)
+ hash = hash_ip64(&ent->a.a6, size);
+ else
+ hash = hash_ip6(&ent->a.a6, size);
+#endif
+ }
+
+ return (hash);
+}
+
+static int
+tei_to_chash_ent(struct tentry_info *tei, struct chashentry *ent)
+{
+ int mlen;
+#ifdef INET6
+ struct in6_addr mask6;
+#endif
+
+
+ mlen = tei->masklen;
+
+ if (tei->subtype == AF_INET) {
+#ifdef INET
+ if (mlen > 32)
+ return (EINVAL);
+ ent->type = AF_INET;
+
+ /* Calculate masked address */
+ ent->a.a4 = ntohl(*((in_addr_t *)tei->paddr)) >> (32 - mlen);
+#endif
+#ifdef INET6
+ } else if (tei->subtype == AF_INET6) {
+ /* IPv6 case */
+ if (mlen > 128)
+ return (EINVAL);
+ ent->type = AF_INET6;
+
+ ipv6_writemask(&mask6, mlen);
+ memcpy(&ent->a.a6, tei->paddr, sizeof(struct in6_addr));
+ APPLY_MASK(&ent->a.a6, &mask6);
+#endif
+ } else {
+ /* Unknown CIDR type */
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+static int
+ta_find_chash_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct chash_cfg *cfg;
+ struct chashbhead *head;
+ struct chashentry ent, *tmp;
+ struct tentry_info tei;
+ int error;
+ uint32_t hash;
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ memset(&ent, 0, sizeof(ent));
+ memset(&tei, 0, sizeof(tei));
+
+ if (tent->subtype == AF_INET) {
+ tei.paddr = &tent->k.addr;
+ tei.masklen = cfg->mask4;
+ tei.subtype = AF_INET;
+
+ if ((error = tei_to_chash_ent(&tei, &ent)) != 0)
+ return (error);
+
+ head = cfg->head4;
+ hash = hash_ent(&ent, AF_INET, cfg->mask4, cfg->size4);
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (tmp->a.a4 != ent.a.a4)
+ continue;
+
+ ta_dump_chash_tentry(ta_state, ti, tmp, tent);
+ return (0);
+ }
+ } else {
+ tei.paddr = &tent->k.addr6;
+ tei.masklen = cfg->mask6;
+ tei.subtype = AF_INET6;
+
+ if ((error = tei_to_chash_ent(&tei, &ent)) != 0)
+ return (error);
+
+ head = cfg->head6;
+ hash = hash_ent(&ent, AF_INET6, cfg->mask6, cfg->size6);
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (memcmp(&tmp->a.a6, &ent.a.a6, 16) != 0)
+ continue;
+ ta_dump_chash_tentry(ta_state, ti, tmp, tent);
+ return (0);
+ }
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_foreach_chash(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct chash_cfg *cfg;
+ struct chashentry *ent, *ent_next;
+ int i;
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ for (i = 0; i < cfg->size4; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head4[i], next, ent_next)
+ f(ent, arg);
+
+ for (i = 0; i < cfg->size6; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head6[i], next, ent_next)
+ f(ent, arg);
+}
+
+static int
+ta_prepare_add_chash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_chash *tb;
+ struct chashentry *ent;
+ int error;
+
+ tb = (struct ta_buf_chash *)ta_buf;
+
+ ent = malloc(sizeof(*ent), M_IPFW_TBL, M_WAITOK | M_ZERO);
+
+ error = tei_to_chash_ent(tei, ent);
+ if (error != 0) {
+ free(ent, M_IPFW_TBL);
+ return (error);
+ }
+ tb->ent_ptr = ent;
+
+ return (0);
+}
+
+static int
+ta_add_chash(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct chash_cfg *cfg;
+ struct chashbhead *head;
+ struct chashentry *ent, *tmp;
+ struct ta_buf_chash *tb;
+ int exists;
+ uint32_t hash, value;
+
+ cfg = (struct chash_cfg *)ta_state;
+ tb = (struct ta_buf_chash *)ta_buf;
+ ent = (struct chashentry *)tb->ent_ptr;
+ hash = 0;
+ exists = 0;
+
+ /* Read current value from @tei */
+ ent->value = tei->value;
+
+ /* Read cuurrent value */
+ if (tei->subtype == AF_INET) {
+ if (tei->masklen != cfg->mask4)
+ return (EINVAL);
+ head = cfg->head4;
+ hash = hash_ent(ent, AF_INET, cfg->mask4, cfg->size4);
+
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (tmp->a.a4 == ent->a.a4) {
+ exists = 1;
+ break;
+ }
+ }
+ } else {
+ if (tei->masklen != cfg->mask6)
+ return (EINVAL);
+ head = cfg->head6;
+ hash = hash_ent(ent, AF_INET6, cfg->mask6, cfg->size6);
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (memcmp(&tmp->a.a6, &ent->a.a6, 16) == 0) {
+ exists = 1;
+ break;
+ }
+ }
+ }
+
+ if (exists == 1) {
+ if ((tei->flags & TEI_FLAGS_UPDATE) == 0)
+ return (EEXIST);
+ /* Record already exists. Update value if we're asked to */
+ value = tmp->value;
+ tmp->value = tei->value;
+ tei->value = value;
+ /* Indicate that update has happened instead of addition */
+ tei->flags |= TEI_FLAGS_UPDATED;
+ *pnum = 0;
+ } else {
+ if ((tei->flags & TEI_FLAGS_DONTADD) != 0)
+ return (EFBIG);
+ SLIST_INSERT_HEAD(&head[hash], ent, next);
+ tb->ent_ptr = NULL;
+ *pnum = 1;
+
+ /* Update counters */
+ if (tei->subtype == AF_INET)
+ cfg->items4++;
+ else
+ cfg->items6++;
+ }
+
+ return (0);
+}
+
+static int
+ta_prepare_del_chash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_chash *tb;
+
+ tb = (struct ta_buf_chash *)ta_buf;
+
+ return (tei_to_chash_ent(tei, &tb->ent));
+}
+
+static int
+ta_del_chash(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct chash_cfg *cfg;
+ struct chashbhead *head;
+ struct chashentry *tmp, *tmp_next, *ent;
+ struct ta_buf_chash *tb;
+ uint32_t hash;
+
+ cfg = (struct chash_cfg *)ta_state;
+ tb = (struct ta_buf_chash *)ta_buf;
+ ent = &tb->ent;
+
+ if (tei->subtype == AF_INET) {
+ if (tei->masklen != cfg->mask4)
+ return (EINVAL);
+ head = cfg->head4;
+ hash = hash_ent(ent, AF_INET, cfg->mask4, cfg->size4);
+
+ SLIST_FOREACH_SAFE(tmp, &head[hash], next, tmp_next) {
+ if (tmp->a.a4 != ent->a.a4)
+ continue;
+
+ SLIST_REMOVE(&head[hash], tmp, chashentry, next);
+ cfg->items4--;
+ tb->ent_ptr = tmp;
+ tei->value = tmp->value;
+ *pnum = 1;
+ return (0);
+ }
+ } else {
+ if (tei->masklen != cfg->mask6)
+ return (EINVAL);
+ head = cfg->head6;
+ hash = hash_ent(ent, AF_INET6, cfg->mask6, cfg->size6);
+ SLIST_FOREACH_SAFE(tmp, &head[hash], next, tmp_next) {
+ if (memcmp(&tmp->a.a6, &ent->a.a6, 16) != 0)
+ continue;
+
+ SLIST_REMOVE(&head[hash], tmp, chashentry, next);
+ cfg->items6--;
+ tb->ent_ptr = tmp;
+ tei->value = tmp->value;
+ *pnum = 1;
+ return (0);
+ }
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_flush_chash_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_chash *tb;
+
+ tb = (struct ta_buf_chash *)ta_buf;
+
+ if (tb->ent_ptr != NULL)
+ free(tb->ent_ptr, M_IPFW_TBL);
+}
+
+/*
+ * Hash growing callbacks.
+ */
+
+static int
+ta_need_modify_chash(void *ta_state, struct table_info *ti, uint32_t count,
+ uint64_t *pflags)
+{
+ struct chash_cfg *cfg;
+ uint64_t data;
+
+ /*
+ * Since we don't know exact number of IPv4/IPv6 records in @count,
+ * ignore non-zero @count value at all. Check current hash sizes
+ * and return appropriate data.
+ */
+
+ cfg = (struct chash_cfg *)ta_state;
+
+ data = 0;
+ if (cfg->items4 > cfg->size4 && cfg->size4 < 65536)
+ data |= (cfg->size4 * 2) << 16;
+ if (cfg->items6 > cfg->size6 && cfg->size6 < 65536)
+ data |= cfg->size6 * 2;
+
+ if (data != 0) {
+ *pflags = data;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Allocate new, larger chash.
+ */
+static int
+ta_prepare_mod_chash(void *ta_buf, uint64_t *pflags)
+{
+ struct mod_item *mi;
+ struct chashbhead *head;
+ int i;
+
+ mi = (struct mod_item *)ta_buf;
+
+ memset(mi, 0, sizeof(struct mod_item));
+ mi->size = (*pflags >> 16) & 0xFFFF;
+ mi->size6 = *pflags & 0xFFFF;
+ if (mi->size > 0) {
+ head = malloc(sizeof(struct chashbhead) * mi->size,
+ M_IPFW, M_WAITOK | M_ZERO);
+ for (i = 0; i < mi->size; i++)
+ SLIST_INIT(&head[i]);
+ mi->main_ptr = head;
+ }
+
+ if (mi->size6 > 0) {
+ head = malloc(sizeof(struct chashbhead) * mi->size6,
+ M_IPFW, M_WAITOK | M_ZERO);
+ for (i = 0; i < mi->size6; i++)
+ SLIST_INIT(&head[i]);
+ mi->main_ptr6 = head;
+ }
+
+ return (0);
+}
+
+/*
+ * Copy data from old runtime array to new one.
+ */
+static int
+ta_fill_mod_chash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t *pflags)
+{
+
+ /* In is not possible to do rehash if we're not holidng WLOCK. */
+ return (0);
+}
+
+/*
+ * Switch old & new arrays.
+ */
+static void
+ta_modify_chash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags)
+{
+ struct mod_item *mi;
+ struct chash_cfg *cfg;
+ struct chashbhead *old_head, *new_head;
+ struct chashentry *ent, *ent_next;
+ int af, i, mlen;
+ uint32_t nhash;
+ size_t old_size, new_size;
+
+ mi = (struct mod_item *)ta_buf;
+ cfg = (struct chash_cfg *)ta_state;
+
+ /* Check which hash we need to grow and do we still need that */
+ if (mi->size > 0 && cfg->size4 < mi->size) {
+ new_head = (struct chashbhead *)mi->main_ptr;
+ new_size = mi->size;
+ old_size = cfg->size4;
+ old_head = ti->state;
+ mlen = cfg->mask4;
+ af = AF_INET;
+
+ for (i = 0; i < old_size; i++) {
+ SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) {
+ nhash = hash_ent(ent, af, mlen, new_size);
+ SLIST_INSERT_HEAD(&new_head[nhash], ent, next);
+ }
+ }
+
+ ti->state = new_head;
+ cfg->head4 = new_head;
+ cfg->size4 = mi->size;
+ mi->main_ptr = old_head;
+ }
+
+ if (mi->size6 > 0 && cfg->size6 < mi->size6) {
+ new_head = (struct chashbhead *)mi->main_ptr6;
+ new_size = mi->size6;
+ old_size = cfg->size6;
+ old_head = ti->xstate;
+ mlen = cfg->mask6;
+ af = AF_INET6;
+
+ for (i = 0; i < old_size; i++) {
+ SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) {
+ nhash = hash_ent(ent, af, mlen, new_size);
+ SLIST_INSERT_HEAD(&new_head[nhash], ent, next);
+ }
+ }
+
+ ti->xstate = new_head;
+ cfg->head6 = new_head;
+ cfg->size6 = mi->size6;
+ mi->main_ptr6 = old_head;
+ }
+
+ /* Update lower 32 bits with new values */
+ ti->data &= 0xFFFFFFFF00000000;
+ ti->data |= ta_log2(cfg->size4) << 8 | ta_log2(cfg->size6);
+}
+
+/*
+ * Free unneded array.
+ */
+static void
+ta_flush_mod_chash(void *ta_buf)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+ if (mi->main_ptr != NULL)
+ free(mi->main_ptr, M_IPFW);
+ if (mi->main_ptr6 != NULL)
+ free(mi->main_ptr6, M_IPFW);
+}
+
+struct table_algo addr_hash = {
+ .name = "addr:hash",
+ .type = IPFW_TABLE_ADDR,
+ .ta_buf_size = sizeof(struct ta_buf_chash),
+ .init = ta_init_chash,
+ .destroy = ta_destroy_chash,
+ .prepare_add = ta_prepare_add_chash,
+ .prepare_del = ta_prepare_del_chash,
+ .add = ta_add_chash,
+ .del = ta_del_chash,
+ .flush_entry = ta_flush_chash_entry,
+ .foreach = ta_foreach_chash,
+ .dump_tentry = ta_dump_chash_tentry,
+ .find_tentry = ta_find_chash_tentry,
+ .print_config = ta_print_chash_config,
+ .dump_tinfo = ta_dump_chash_tinfo,
+ .need_modify = ta_need_modify_chash,
+ .prepare_mod = ta_prepare_mod_chash,
+ .fill_mod = ta_fill_mod_chash,
+ .modify = ta_modify_chash,
+ .flush_mod = ta_flush_mod_chash,
+};
+
+
+/*
+ * Iface table cmds.
+ *
+ * Implementation:
+ *
+ * Runtime part:
+ * - sorted array of "struct ifidx" pointed by ti->state.
+ * Array is allocated with rounding up to IFIDX_CHUNK. Only existing
+ * interfaces are stored in array, however its allocated size is
+ * sufficient to hold all table records if needed.
+ * - current array size is stored in ti->data
+ *
+ * Table data:
+ * - "struct iftable_cfg" is allocated to store table state (ta_state).
+ * - All table records are stored inside namedobj instance.
+ *
+ */
+
+struct ifidx {
+ uint16_t kidx;
+ uint16_t spare;
+ uint32_t value;
+};
+#define DEFAULT_IFIDX_SIZE 64
+
+struct iftable_cfg;
+
+struct ifentry {
+ struct named_object no;
+ struct ipfw_ifc ic;
+ struct iftable_cfg *icfg;
+ uint32_t value;
+ int linked;
+};
+
+struct iftable_cfg {
+ struct namedobj_instance *ii;
+ struct ip_fw_chain *ch;
+ struct table_info *ti;
+ void *main_ptr;
+ size_t size; /* Number of items allocated in array */
+ size_t count; /* Number of all items */
+ size_t used; /* Number of items _active_ now */
+};
+
+struct ta_buf_ifidx
+{
+ struct ifentry *ife;
+ uint32_t value;
+};
+
+int compare_ifidx(const void *k, const void *v);
+static struct ifidx * ifidx_find(struct table_info *ti, void *key);
+static int ta_lookup_ifidx(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int ta_init_ifidx(struct ip_fw_chain *ch, void **ta_state,
+ struct table_info *ti, char *data, uint8_t tflags);
+static void ta_change_ti_ifidx(void *ta_state, struct table_info *ti);
+static int destroy_ifidx_locked(struct namedobj_instance *ii,
+ struct named_object *no, void *arg);
+static void ta_destroy_ifidx(void *ta_state, struct table_info *ti);
+static void ta_dump_ifidx_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int ta_prepare_add_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_add_ifidx(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static int ta_prepare_del_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_del_ifidx(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static void ta_flush_ifidx_entry(struct ip_fw_chain *ch,
+ struct tentry_info *tei, void *ta_buf);
+static void if_notifier(struct ip_fw_chain *ch, void *cbdata, uint16_t ifindex);
+static int ta_need_modify_ifidx(void *ta_state, struct table_info *ti,
+ uint32_t count, uint64_t *pflags);
+static int ta_prepare_mod_ifidx(void *ta_buf, uint64_t *pflags);
+static int ta_fill_mod_ifidx(void *ta_state, struct table_info *ti,
+ void *ta_buf, uint64_t *pflags);
+static void ta_modify_ifidx(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags);
+static void ta_flush_mod_ifidx(void *ta_buf);
+static int ta_dump_ifidx_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent);
+static int ta_find_ifidx_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static int foreach_ifidx(struct namedobj_instance *ii, struct named_object *no,
+ void *arg);
+static void ta_foreach_ifidx(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+
+int
+compare_ifidx(const void *k, const void *v)
+{
+ const struct ifidx *ifidx;
+ uint16_t key;
+
+ key = *((const uint16_t *)k);
+ ifidx = (const struct ifidx *)v;
+
+ if (key < ifidx->kidx)
+ return (-1);
+ else if (key > ifidx->kidx)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Adds item @item with key @key into ascending-sorted array @base.
+ * Assumes @base has enough additional storage.
+ *
+ * Returns 1 on success, 0 on duplicate key.
+ */
+static int
+badd(const void *key, void *item, void *base, size_t nmemb,
+ size_t size, int (*compar) (const void *, const void *))
+{
+ int min, max, mid, shift, res;
+ caddr_t paddr;
+
+ if (nmemb == 0) {
+ memcpy(base, item, size);
+ return (1);
+ }
+
+ /* Binary search */
+ min = 0;
+ max = nmemb - 1;
+ mid = 0;
+ while (min <= max) {
+ mid = (min + max) / 2;
+ res = compar(key, (const void *)((caddr_t)base + mid * size));
+ if (res == 0)
+ return (0);
+
+ if (res > 0)
+ min = mid + 1;
+ else
+ max = mid - 1;
+ }
+
+ /* Item not found. */
+ res = compar(key, (const void *)((caddr_t)base + mid * size));
+ if (res > 0)
+ shift = mid + 1;
+ else
+ shift = mid;
+
+ paddr = (caddr_t)base + shift * size;
+ if (nmemb > shift)
+ memmove(paddr + size, paddr, (nmemb - shift) * size);
+
+ memcpy(paddr, item, size);
+
+ return (1);
+}
+
+/*
+ * Deletes item with key @key from ascending-sorted array @base.
+ *
+ * Returns 1 on success, 0 for non-existent key.
+ */
+static int
+bdel(const void *key, void *base, size_t nmemb, size_t size,
+ int (*compar) (const void *, const void *))
+{
+ caddr_t item;
+ size_t sz;
+
+ item = (caddr_t)bsearch(key, base, nmemb, size, compar);
+
+ if (item == NULL)
+ return (0);
+
+ sz = (caddr_t)base + nmemb * size - item;
+
+ if (sz > 0)
+ memmove(item, item + size, sz);
+
+ return (1);
+}
+
+static struct ifidx *
+ifidx_find(struct table_info *ti, void *key)
+{
+ struct ifidx *ifi;
+
+ ifi = bsearch(key, ti->state, ti->data, sizeof(struct ifidx),
+ compare_ifidx);
+
+ return (ifi);
+}
+
+static int
+ta_lookup_ifidx(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct ifidx *ifi;
+
+ ifi = ifidx_find(ti, key);
+
+ if (ifi != NULL) {
+ *val = ifi->value;
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+ta_init_ifidx(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ struct iftable_cfg *icfg;
+
+ icfg = malloc(sizeof(struct iftable_cfg), M_IPFW, M_WAITOK | M_ZERO);
+
+ icfg->ii = ipfw_objhash_create(DEFAULT_IFIDX_SIZE);
+ icfg->size = DEFAULT_IFIDX_SIZE;
+ icfg->main_ptr = malloc(sizeof(struct ifidx) * icfg->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+ icfg->ch = ch;
+
+ *ta_state = icfg;
+ ti->state = icfg->main_ptr;
+ ti->lookup = ta_lookup_ifidx;
+
+ return (0);
+}
+
+/*
+ * Handle tableinfo @ti pointer change (on table array resize).
+ */
+static void
+ta_change_ti_ifidx(void *ta_state, struct table_info *ti)
+{
+ struct iftable_cfg *icfg;
+
+ icfg = (struct iftable_cfg *)ta_state;
+ icfg->ti = ti;
+}
+
+static int
+destroy_ifidx_locked(struct namedobj_instance *ii, struct named_object *no,
+ void *arg)
+{
+ struct ifentry *ife;
+ struct ip_fw_chain *ch;
+
+ ch = (struct ip_fw_chain *)arg;
+ ife = (struct ifentry *)no;
+
+ ipfw_iface_del_notify(ch, &ife->ic);
+ ipfw_iface_unref(ch, &ife->ic);
+ free(ife, M_IPFW_TBL);
+ return (0);
+}
+
+
+/*
+ * Destroys table @ti
+ */
+static void
+ta_destroy_ifidx(void *ta_state, struct table_info *ti)
+{
+ struct iftable_cfg *icfg;
+ struct ip_fw_chain *ch;
+
+ icfg = (struct iftable_cfg *)ta_state;
+ ch = icfg->ch;
+
+ if (icfg->main_ptr != NULL)
+ free(icfg->main_ptr, M_IPFW);
+
+ IPFW_UH_WLOCK(ch);
+ ipfw_objhash_foreach(icfg->ii, destroy_ifidx_locked, ch);
+ IPFW_UH_WUNLOCK(ch);
+
+ ipfw_objhash_destroy(icfg->ii);
+
+ free(icfg, M_IPFW);
+}
+
+/*
+ * Provide algo-specific table info
+ */
+static void
+ta_dump_ifidx_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+ struct iftable_cfg *cfg;
+
+ cfg = (struct iftable_cfg *)ta_state;
+
+ tinfo->taclass4 = IPFW_TACLASS_ARRAY;
+ tinfo->size4 = cfg->size;
+ tinfo->count4 = cfg->used;
+ tinfo->itemsize4 = sizeof(struct ifidx);
+}
+
+/*
+ * Prepare state to add to the table:
+ * allocate ifentry and reference needed interface.
+ */
+static int
+ta_prepare_add_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_ifidx *tb;
+ char *ifname;
+ struct ifentry *ife;
+
+ tb = (struct ta_buf_ifidx *)ta_buf;
+
+ /* Check if string is terminated */
+ ifname = (char *)tei->paddr;
+ if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE)
+ return (EINVAL);
+
+ ife = malloc(sizeof(struct ifentry), M_IPFW_TBL, M_WAITOK | M_ZERO);
+ ife->ic.cb = if_notifier;
+ ife->ic.cbdata = ife;
+
+ if (ipfw_iface_ref(ch, ifname, &ife->ic) != 0) {
+ free(ife, M_IPFW_TBL);
+ return (EINVAL);
+ }
+
+ /* Use ipfw_iface 'ifname' field as stable storage */
+ ife->no.name = ife->ic.iface->ifname;
+
+ tb->ife = ife;
+
+ return (0);
+}
+
+static int
+ta_add_ifidx(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct iftable_cfg *icfg;
+ struct ifentry *ife, *tmp;
+ struct ta_buf_ifidx *tb;
+ struct ipfw_iface *iif;
+ struct ifidx *ifi;
+ char *ifname;
+ uint32_t value;
+
+ tb = (struct ta_buf_ifidx *)ta_buf;
+ ifname = (char *)tei->paddr;
+ icfg = (struct iftable_cfg *)ta_state;
+ ife = tb->ife;
+
+ ife->icfg = icfg;
+ ife->value = tei->value;
+
+ tmp = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname);
+
+ if (tmp != NULL) {
+ if ((tei->flags & TEI_FLAGS_UPDATE) == 0)
+ return (EEXIST);
+
+ /* Exchange values in @tmp and @tei */
+ value = tmp->value;
+ tmp->value = tei->value;
+ tei->value = value;
+
+ iif = tmp->ic.iface;
+ if (iif->resolved != 0) {
+ /* We have to update runtime value, too */
+ ifi = ifidx_find(ti, &iif->ifindex);
+ ifi->value = ife->value;
+ }
+
+ /* Indicate that update has happened instead of addition */
+ tei->flags |= TEI_FLAGS_UPDATED;
+ *pnum = 0;
+ return (0);
+ }
+
+ if ((tei->flags & TEI_FLAGS_DONTADD) != 0)
+ return (EFBIG);
+
+ /* Link to internal list */
+ ipfw_objhash_add(icfg->ii, &ife->no);
+
+ /* Link notifier (possible running its callback) */
+ ipfw_iface_add_notify(icfg->ch, &ife->ic);
+ icfg->count++;
+
+ tb->ife = NULL;
+ *pnum = 1;
+
+ return (0);
+}
+
+/*
+ * Prepare to delete key from table.
+ * Do basic interface name checks.
+ */
+static int
+ta_prepare_del_ifidx(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_ifidx *tb;
+ char *ifname;
+
+ tb = (struct ta_buf_ifidx *)ta_buf;
+
+ /* Check if string is terminated */
+ ifname = (char *)tei->paddr;
+ if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE)
+ return (EINVAL);
+
+ return (0);
+}
+
+/*
+ * Remove key from both configuration list and
+ * runtime array. Removed interface notification.
+ */
+static int
+ta_del_ifidx(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct iftable_cfg *icfg;
+ struct ifentry *ife;
+ struct ta_buf_ifidx *tb;
+ char *ifname;
+ uint16_t ifindex;
+ int res;
+
+ tb = (struct ta_buf_ifidx *)ta_buf;
+ ifname = (char *)tei->paddr;
+ icfg = (struct iftable_cfg *)ta_state;
+ ife = tb->ife;
+
+ ife = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname);
+
+ if (ife == NULL)
+ return (ENOENT);
+
+ if (ife->linked != 0) {
+ /* We have to remove item from runtime */
+ ifindex = ife->ic.iface->ifindex;
+
+ res = bdel(&ifindex, icfg->main_ptr, icfg->used,
+ sizeof(struct ifidx), compare_ifidx);
+
+ KASSERT(res == 1, ("index %d does not exist", ifindex));
+ icfg->used--;
+ ti->data = icfg->used;
+ ife->linked = 0;
+ }
+
+ /* Unlink from local list */
+ ipfw_objhash_del(icfg->ii, &ife->no);
+ /* Unlink notifier and deref */
+ ipfw_iface_del_notify(icfg->ch, &ife->ic);
+ ipfw_iface_unref(icfg->ch, &ife->ic);
+
+ icfg->count--;
+ tei->value = ife->value;
+
+ tb->ife = ife;
+ *pnum = 1;
+
+ return (0);
+}
+
+/*
+ * Flush deleted entry.
+ * Drops interface reference and frees entry.
+ */
+static void
+ta_flush_ifidx_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_ifidx *tb;
+
+ tb = (struct ta_buf_ifidx *)ta_buf;
+
+ if (tb->ife != NULL)
+ free(tb->ife, M_IPFW_TBL);
+}
+
+
+/*
+ * Handle interface announce/withdrawal for particular table.
+ * Every real runtime array modification happens here.
+ */
+static void
+if_notifier(struct ip_fw_chain *ch, void *cbdata, uint16_t ifindex)
+{
+ struct ifentry *ife;
+ struct ifidx ifi;
+ struct iftable_cfg *icfg;
+ struct table_info *ti;
+ int res;
+
+ ife = (struct ifentry *)cbdata;
+ icfg = ife->icfg;
+ ti = icfg->ti;
+
+ KASSERT(ti != NULL, ("ti=NULL, check change_ti handler"));
+
+ if (ife->linked == 0 && ifindex != 0) {
+ /* Interface announce */
+ ifi.kidx = ifindex;
+ ifi.spare = 0;
+ ifi.value = ife->value;
+ res = badd(&ifindex, &ifi, icfg->main_ptr, icfg->used,
+ sizeof(struct ifidx), compare_ifidx);
+ KASSERT(res == 1, ("index %d already exists", ifindex));
+ icfg->used++;
+ ti->data = icfg->used;
+ ife->linked = 1;
+ } else if (ife->linked != 0 && ifindex == 0) {
+ /* Interface withdrawal */
+ ifindex = ife->ic.iface->ifindex;
+
+ res = bdel(&ifindex, icfg->main_ptr, icfg->used,
+ sizeof(struct ifidx), compare_ifidx);
+
+ KASSERT(res == 1, ("index %d does not exist", ifindex));
+ icfg->used--;
+ ti->data = icfg->used;
+ ife->linked = 0;
+ }
+}
+
+
+/*
+ * Table growing callbacks.
+ */
+
+static int
+ta_need_modify_ifidx(void *ta_state, struct table_info *ti, uint32_t count,
+ uint64_t *pflags)
+{
+ struct iftable_cfg *cfg;
+ uint32_t size;
+
+ cfg = (struct iftable_cfg *)ta_state;
+
+ size = cfg->size;
+ while (size < cfg->count + count)
+ size *= 2;
+
+ if (size != cfg->size) {
+ *pflags = size;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Allocate ned, larger runtime ifidx array.
+ */
+static int
+ta_prepare_mod_ifidx(void *ta_buf, uint64_t *pflags)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+
+ memset(mi, 0, sizeof(struct mod_item));
+ mi->size = *pflags;
+ mi->main_ptr = malloc(sizeof(struct ifidx) * mi->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+
+ return (0);
+}
+
+/*
+ * Copy data from old runtime array to new one.
+ */
+static int
+ta_fill_mod_ifidx(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t *pflags)
+{
+ struct mod_item *mi;
+ struct iftable_cfg *icfg;
+
+ mi = (struct mod_item *)ta_buf;
+ icfg = (struct iftable_cfg *)ta_state;
+
+ /* Check if we still need to grow array */
+ if (icfg->size >= mi->size) {
+ *pflags = 0;
+ return (0);
+ }
+
+ memcpy(mi->main_ptr, icfg->main_ptr, icfg->used * sizeof(struct ifidx));
+
+ return (0);
+}
+
+/*
+ * Switch old & new arrays.
+ */
+static void
+ta_modify_ifidx(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags)
+{
+ struct mod_item *mi;
+ struct iftable_cfg *icfg;
+ void *old_ptr;
+
+ mi = (struct mod_item *)ta_buf;
+ icfg = (struct iftable_cfg *)ta_state;
+
+ old_ptr = icfg->main_ptr;
+ icfg->main_ptr = mi->main_ptr;
+ icfg->size = mi->size;
+ ti->state = icfg->main_ptr;
+
+ mi->main_ptr = old_ptr;
+}
+
+/*
+ * Free unneded array.
+ */
+static void
+ta_flush_mod_ifidx(void *ta_buf)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+ if (mi->main_ptr != NULL)
+ free(mi->main_ptr, M_IPFW);
+}
+
+static int
+ta_dump_ifidx_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct ifentry *ife;
+
+ ife = (struct ifentry *)e;
+
+ tent->masklen = 8 * IF_NAMESIZE;
+ memcpy(&tent->k, ife->no.name, IF_NAMESIZE);
+ tent->v.kidx = ife->value;
+
+ return (0);
+}
+
+static int
+ta_find_ifidx_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct iftable_cfg *icfg;
+ struct ifentry *ife;
+ char *ifname;
+
+ icfg = (struct iftable_cfg *)ta_state;
+ ifname = tent->k.iface;
+
+ if (strnlen(ifname, IF_NAMESIZE) == IF_NAMESIZE)
+ return (EINVAL);
+
+ ife = (struct ifentry *)ipfw_objhash_lookup_name(icfg->ii, 0, ifname);
+
+ if (ife != NULL) {
+ ta_dump_ifidx_tentry(ta_state, ti, ife, tent);
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+struct wa_ifidx {
+ ta_foreach_f *f;
+ void *arg;
+};
+
+static int
+foreach_ifidx(struct namedobj_instance *ii, struct named_object *no,
+ void *arg)
+{
+ struct ifentry *ife;
+ struct wa_ifidx *wa;
+
+ ife = (struct ifentry *)no;
+ wa = (struct wa_ifidx *)arg;
+
+ wa->f(ife, wa->arg);
+ return (0);
+}
+
+static void
+ta_foreach_ifidx(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct iftable_cfg *icfg;
+ struct wa_ifidx wa;
+
+ icfg = (struct iftable_cfg *)ta_state;
+
+ wa.f = f;
+ wa.arg = arg;
+
+ ipfw_objhash_foreach(icfg->ii, foreach_ifidx, &wa);
+}
+
+struct table_algo iface_idx = {
+ .name = "iface:array",
+ .type = IPFW_TABLE_INTERFACE,
+ .flags = TA_FLAG_DEFAULT,
+ .ta_buf_size = sizeof(struct ta_buf_ifidx),
+ .init = ta_init_ifidx,
+ .destroy = ta_destroy_ifidx,
+ .prepare_add = ta_prepare_add_ifidx,
+ .prepare_del = ta_prepare_del_ifidx,
+ .add = ta_add_ifidx,
+ .del = ta_del_ifidx,
+ .flush_entry = ta_flush_ifidx_entry,
+ .foreach = ta_foreach_ifidx,
+ .dump_tentry = ta_dump_ifidx_tentry,
+ .find_tentry = ta_find_ifidx_tentry,
+ .dump_tinfo = ta_dump_ifidx_tinfo,
+ .need_modify = ta_need_modify_ifidx,
+ .prepare_mod = ta_prepare_mod_ifidx,
+ .fill_mod = ta_fill_mod_ifidx,
+ .modify = ta_modify_ifidx,
+ .flush_mod = ta_flush_mod_ifidx,
+ .change_ti = ta_change_ti_ifidx,
+};
+
+/*
+ * Number array cmds.
+ *
+ * Implementation:
+ *
+ * Runtime part:
+ * - sorted array of "struct numarray" pointed by ti->state.
+ * Array is allocated with rounding up to NUMARRAY_CHUNK.
+ * - current array size is stored in ti->data
+ *
+ */
+
+struct numarray {
+ uint32_t number;
+ uint32_t value;
+};
+
+struct numarray_cfg {
+ void *main_ptr;
+ size_t size; /* Number of items allocated in array */
+ size_t used; /* Number of items _active_ now */
+};
+
+struct ta_buf_numarray
+{
+ struct numarray na;
+};
+
+int compare_numarray(const void *k, const void *v);
+static struct numarray *numarray_find(struct table_info *ti, void *key);
+static int ta_lookup_numarray(struct table_info *ti, void *key,
+ uint32_t keylen, uint32_t *val);
+static int ta_init_numarray(struct ip_fw_chain *ch, void **ta_state,
+ struct table_info *ti, char *data, uint8_t tflags);
+static void ta_destroy_numarray(void *ta_state, struct table_info *ti);
+static void ta_dump_numarray_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int ta_prepare_add_numarray(struct ip_fw_chain *ch,
+ struct tentry_info *tei, void *ta_buf);
+static int ta_add_numarray(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static int ta_del_numarray(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static void ta_flush_numarray_entry(struct ip_fw_chain *ch,
+ struct tentry_info *tei, void *ta_buf);
+static int ta_need_modify_numarray(void *ta_state, struct table_info *ti,
+ uint32_t count, uint64_t *pflags);
+static int ta_prepare_mod_numarray(void *ta_buf, uint64_t *pflags);
+static int ta_fill_mod_numarray(void *ta_state, struct table_info *ti,
+ void *ta_buf, uint64_t *pflags);
+static void ta_modify_numarray(void *ta_state, struct table_info *ti,
+ void *ta_buf, uint64_t pflags);
+static void ta_flush_mod_numarray(void *ta_buf);
+static int ta_dump_numarray_tentry(void *ta_state, struct table_info *ti,
+ void *e, ipfw_obj_tentry *tent);
+static int ta_find_numarray_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static void ta_foreach_numarray(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+
+int
+compare_numarray(const void *k, const void *v)
+{
+ const struct numarray *na;
+ uint32_t key;
+
+ key = *((const uint32_t *)k);
+ na = (const struct numarray *)v;
+
+ if (key < na->number)
+ return (-1);
+ else if (key > na->number)
+ return (1);
+
+ return (0);
+}
+
+static struct numarray *
+numarray_find(struct table_info *ti, void *key)
+{
+ struct numarray *ri;
+
+ ri = bsearch(key, ti->state, ti->data, sizeof(struct numarray),
+ compare_ifidx);
+
+ return (ri);
+}
+
+static int
+ta_lookup_numarray(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct numarray *ri;
+
+ ri = numarray_find(ti, key);
+
+ if (ri != NULL) {
+ *val = ri->value;
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+ta_init_numarray(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ struct numarray_cfg *cfg;
+
+ cfg = malloc(sizeof(*cfg), M_IPFW, M_WAITOK | M_ZERO);
+
+ cfg->size = 16;
+ cfg->main_ptr = malloc(sizeof(struct numarray) * cfg->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+
+ *ta_state = cfg;
+ ti->state = cfg->main_ptr;
+ ti->lookup = ta_lookup_numarray;
+
+ return (0);
+}
+
+/*
+ * Destroys table @ti
+ */
+static void
+ta_destroy_numarray(void *ta_state, struct table_info *ti)
+{
+ struct numarray_cfg *cfg;
+
+ cfg = (struct numarray_cfg *)ta_state;
+
+ if (cfg->main_ptr != NULL)
+ free(cfg->main_ptr, M_IPFW);
+
+ free(cfg, M_IPFW);
+}
+
+/*
+ * Provide algo-specific table info
+ */
+static void
+ta_dump_numarray_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+ struct numarray_cfg *cfg;
+
+ cfg = (struct numarray_cfg *)ta_state;
+
+ tinfo->taclass4 = IPFW_TACLASS_ARRAY;
+ tinfo->size4 = cfg->size;
+ tinfo->count4 = cfg->used;
+ tinfo->itemsize4 = sizeof(struct numarray);
+}
+
+/*
+ * Prepare for addition/deletion to an array.
+ */
+static int
+ta_prepare_add_numarray(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_numarray *tb;
+
+ tb = (struct ta_buf_numarray *)ta_buf;
+
+ tb->na.number = *((uint32_t *)tei->paddr);
+
+ return (0);
+}
+
+static int
+ta_add_numarray(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct numarray_cfg *cfg;
+ struct ta_buf_numarray *tb;
+ struct numarray *ri;
+ int res;
+ uint32_t value;
+
+ tb = (struct ta_buf_numarray *)ta_buf;
+ cfg = (struct numarray_cfg *)ta_state;
+
+ /* Read current value from @tei */
+ tb->na.value = tei->value;
+
+ ri = numarray_find(ti, &tb->na.number);
+
+ if (ri != NULL) {
+ if ((tei->flags & TEI_FLAGS_UPDATE) == 0)
+ return (EEXIST);
+
+ /* Exchange values between ri and @tei */
+ value = ri->value;
+ ri->value = tei->value;
+ tei->value = value;
+ /* Indicate that update has happened instead of addition */
+ tei->flags |= TEI_FLAGS_UPDATED;
+ *pnum = 0;
+ return (0);
+ }
+
+ if ((tei->flags & TEI_FLAGS_DONTADD) != 0)
+ return (EFBIG);
+
+ res = badd(&tb->na.number, &tb->na, cfg->main_ptr, cfg->used,
+ sizeof(struct numarray), compare_numarray);
+
+ KASSERT(res == 1, ("number %d already exists", tb->na.number));
+ cfg->used++;
+ ti->data = cfg->used;
+ *pnum = 1;
+
+ return (0);
+}
+
+/*
+ * Remove key from both configuration list and
+ * runtime array. Removed interface notification.
+ */
+static int
+ta_del_numarray(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct numarray_cfg *cfg;
+ struct ta_buf_numarray *tb;
+ struct numarray *ri;
+ int res;
+
+ tb = (struct ta_buf_numarray *)ta_buf;
+ cfg = (struct numarray_cfg *)ta_state;
+
+ ri = numarray_find(ti, &tb->na.number);
+ if (ri == NULL)
+ return (ENOENT);
+
+ tei->value = ri->value;
+
+ res = bdel(&tb->na.number, cfg->main_ptr, cfg->used,
+ sizeof(struct numarray), compare_numarray);
+
+ KASSERT(res == 1, ("number %u does not exist", tb->na.number));
+ cfg->used--;
+ ti->data = cfg->used;
+ *pnum = 1;
+
+ return (0);
+}
+
+static void
+ta_flush_numarray_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+
+ /* We don't have any state, do nothing */
+}
+
+
+/*
+ * Table growing callbacks.
+ */
+
+static int
+ta_need_modify_numarray(void *ta_state, struct table_info *ti, uint32_t count,
+ uint64_t *pflags)
+{
+ struct numarray_cfg *cfg;
+ size_t size;
+
+ cfg = (struct numarray_cfg *)ta_state;
+
+ size = cfg->size;
+ while (size < cfg->used + count)
+ size *= 2;
+
+ if (size != cfg->size) {
+ *pflags = size;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Allocate new, larger runtime array.
+ */
+static int
+ta_prepare_mod_numarray(void *ta_buf, uint64_t *pflags)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+
+ memset(mi, 0, sizeof(struct mod_item));
+ mi->size = *pflags;
+ mi->main_ptr = malloc(sizeof(struct numarray) * mi->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+
+ return (0);
+}
+
+/*
+ * Copy data from old runtime array to new one.
+ */
+static int
+ta_fill_mod_numarray(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t *pflags)
+{
+ struct mod_item *mi;
+ struct numarray_cfg *cfg;
+
+ mi = (struct mod_item *)ta_buf;
+ cfg = (struct numarray_cfg *)ta_state;
+
+ /* Check if we still need to grow array */
+ if (cfg->size >= mi->size) {
+ *pflags = 0;
+ return (0);
+ }
+
+ memcpy(mi->main_ptr, cfg->main_ptr, cfg->used * sizeof(struct numarray));
+
+ return (0);
+}
+
+/*
+ * Switch old & new arrays.
+ */
+static void
+ta_modify_numarray(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags)
+{
+ struct mod_item *mi;
+ struct numarray_cfg *cfg;
+ void *old_ptr;
+
+ mi = (struct mod_item *)ta_buf;
+ cfg = (struct numarray_cfg *)ta_state;
+
+ old_ptr = cfg->main_ptr;
+ cfg->main_ptr = mi->main_ptr;
+ cfg->size = mi->size;
+ ti->state = cfg->main_ptr;
+
+ mi->main_ptr = old_ptr;
+}
+
+/*
+ * Free unneded array.
+ */
+static void
+ta_flush_mod_numarray(void *ta_buf)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+ if (mi->main_ptr != NULL)
+ free(mi->main_ptr, M_IPFW);
+}
+
+static int
+ta_dump_numarray_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct numarray *na;
+
+ na = (struct numarray *)e;
+
+ tent->k.key = na->number;
+ tent->v.kidx = na->value;
+
+ return (0);
+}
+
+static int
+ta_find_numarray_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct numarray_cfg *cfg;
+ struct numarray *ri;
+
+ cfg = (struct numarray_cfg *)ta_state;
+
+ ri = numarray_find(ti, &tent->k.key);
+
+ if (ri != NULL) {
+ ta_dump_numarray_tentry(ta_state, ti, ri, tent);
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_foreach_numarray(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct numarray_cfg *cfg;
+ struct numarray *array;
+ int i;
+
+ cfg = (struct numarray_cfg *)ta_state;
+ array = cfg->main_ptr;
+
+ for (i = 0; i < cfg->used; i++)
+ f(&array[i], arg);
+}
+
+struct table_algo number_array = {
+ .name = "number:array",
+ .type = IPFW_TABLE_NUMBER,
+ .ta_buf_size = sizeof(struct ta_buf_numarray),
+ .init = ta_init_numarray,
+ .destroy = ta_destroy_numarray,
+ .prepare_add = ta_prepare_add_numarray,
+ .prepare_del = ta_prepare_add_numarray,
+ .add = ta_add_numarray,
+ .del = ta_del_numarray,
+ .flush_entry = ta_flush_numarray_entry,
+ .foreach = ta_foreach_numarray,
+ .dump_tentry = ta_dump_numarray_tentry,
+ .find_tentry = ta_find_numarray_tentry,
+ .dump_tinfo = ta_dump_numarray_tinfo,
+ .need_modify = ta_need_modify_numarray,
+ .prepare_mod = ta_prepare_mod_numarray,
+ .fill_mod = ta_fill_mod_numarray,
+ .modify = ta_modify_numarray,
+ .flush_mod = ta_flush_mod_numarray,
+};
+
+/*
+ * flow:hash cmds
+ *
+ *
+ * ti->data:
+ * [inv.mask4][inv.mask6][log2hsize4][log2hsize6]
+ * [ 8][ 8[ 8][ 8]
+ *
+ * inv.mask4: 32 - mask
+ * inv.mask6:
+ * 1) _slow lookup: mask
+ * 2) _aligned: (128 - mask) / 8
+ * 3) _64: 8
+ *
+ *
+ * pflags:
+ * [hsize4][hsize6]
+ * [ 16][ 16]
+ */
+
+struct fhashentry;
+
+SLIST_HEAD(fhashbhead, fhashentry);
+
+struct fhashentry {
+ SLIST_ENTRY(fhashentry) next;
+ uint8_t af;
+ uint8_t proto;
+ uint16_t spare0;
+ uint16_t dport;
+ uint16_t sport;
+ uint32_t value;
+ uint32_t spare1;
+};
+
+struct fhashentry4 {
+ struct fhashentry e;
+ struct in_addr dip;
+ struct in_addr sip;
+};
+
+struct fhashentry6 {
+ struct fhashentry e;
+ struct in6_addr dip6;
+ struct in6_addr sip6;
+};
+
+struct fhash_cfg {
+ struct fhashbhead *head;
+ size_t size;
+ size_t items;
+ struct fhashentry4 fe4;
+ struct fhashentry6 fe6;
+};
+
+struct ta_buf_fhash {
+ void *ent_ptr;
+ struct fhashentry6 fe6;
+};
+
+static __inline int cmp_flow_ent(struct fhashentry *a,
+ struct fhashentry *b, size_t sz);
+static __inline uint32_t hash_flow4(struct fhashentry4 *f, int hsize);
+static __inline uint32_t hash_flow6(struct fhashentry6 *f, int hsize);
+static uint32_t hash_flow_ent(struct fhashentry *ent, uint32_t size);
+static int ta_lookup_fhash(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int ta_init_fhash(struct ip_fw_chain *ch, void **ta_state,
+struct table_info *ti, char *data, uint8_t tflags);
+static void ta_destroy_fhash(void *ta_state, struct table_info *ti);
+static void ta_dump_fhash_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int ta_dump_fhash_tentry(void *ta_state, struct table_info *ti,
+ void *e, ipfw_obj_tentry *tent);
+static int tei_to_fhash_ent(struct tentry_info *tei, struct fhashentry *ent);
+static int ta_find_fhash_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static void ta_foreach_fhash(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+static int ta_prepare_add_fhash(struct ip_fw_chain *ch,
+ struct tentry_info *tei, void *ta_buf);
+static int ta_add_fhash(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static int ta_prepare_del_fhash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_del_fhash(void *ta_state, struct table_info *ti,
+ struct tentry_info *tei, void *ta_buf, uint32_t *pnum);
+static void ta_flush_fhash_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf);
+static int ta_need_modify_fhash(void *ta_state, struct table_info *ti,
+ uint32_t count, uint64_t *pflags);
+static int ta_prepare_mod_fhash(void *ta_buf, uint64_t *pflags);
+static int ta_fill_mod_fhash(void *ta_state, struct table_info *ti,
+ void *ta_buf, uint64_t *pflags);
+static void ta_modify_fhash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags);
+static void ta_flush_mod_fhash(void *ta_buf);
+
+static __inline int
+cmp_flow_ent(struct fhashentry *a, struct fhashentry *b, size_t sz)
+{
+ uint64_t *ka, *kb;
+
+ ka = (uint64_t *)(&a->next + 1);
+ kb = (uint64_t *)(&b->next + 1);
+
+ if (*ka == *kb && (memcmp(a + 1, b + 1, sz) == 0))
+ return (1);
+
+ return (0);
+}
+
+static __inline uint32_t
+hash_flow4(struct fhashentry4 *f, int hsize)
+{
+ uint32_t i;
+
+ i = (f->dip.s_addr) ^ (f->sip.s_addr) ^ (f->e.dport) ^ (f->e.sport);
+
+ return (i % (hsize - 1));
+}
+
+static __inline uint32_t
+hash_flow6(struct fhashentry6 *f, int hsize)
+{
+ uint32_t i;
+
+ i = (f->dip6.__u6_addr.__u6_addr32[2]) ^
+ (f->dip6.__u6_addr.__u6_addr32[3]) ^
+ (f->sip6.__u6_addr.__u6_addr32[2]) ^
+ (f->sip6.__u6_addr.__u6_addr32[3]) ^
+ (f->e.dport) ^ (f->e.sport);
+
+ return (i % (hsize - 1));
+}
+
+static uint32_t
+hash_flow_ent(struct fhashentry *ent, uint32_t size)
+{
+ uint32_t hash;
+
+ if (ent->af == AF_INET) {
+ hash = hash_flow4((struct fhashentry4 *)ent, size);
+ } else {
+ hash = hash_flow6((struct fhashentry6 *)ent, size);
+ }
+
+ return (hash);
+}
+
+static int
+ta_lookup_fhash(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+ struct fhashbhead *head;
+ struct fhashentry *ent;
+ struct fhashentry4 *m4;
+ struct ipfw_flow_id *id;
+ uint16_t hash, hsize;
+
+ id = (struct ipfw_flow_id *)key;
+ head = (struct fhashbhead *)ti->state;
+ hsize = ti->data;
+ m4 = (struct fhashentry4 *)ti->xstate;
+
+ if (id->addr_type == 4) {
+ struct fhashentry4 f;
+
+ /* Copy hash mask */
+ f = *m4;
+
+ f.dip.s_addr &= id->dst_ip;
+ f.sip.s_addr &= id->src_ip;
+ f.e.dport &= id->dst_port;
+ f.e.sport &= id->src_port;
+ f.e.proto &= id->proto;
+ hash = hash_flow4(&f, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (cmp_flow_ent(ent, &f.e, 2 * 4) != 0) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+ } else if (id->addr_type == 6) {
+ struct fhashentry6 f;
+ uint64_t *fp, *idp;
+
+ /* Copy hash mask */
+ f = *((struct fhashentry6 *)(m4 + 1));
+
+ /* Handle lack of __u6_addr.__u6_addr64 */
+ fp = (uint64_t *)&f.dip6;
+ idp = (uint64_t *)&id->dst_ip6;
+ /* src IPv6 is stored after dst IPv6 */
+ *fp++ &= *idp++;
+ *fp++ &= *idp++;
+ *fp++ &= *idp++;
+ *fp &= *idp;
+ f.e.dport &= id->dst_port;
+ f.e.sport &= id->src_port;
+ f.e.proto &= id->proto;
+ hash = hash_flow6(&f, hsize);
+ SLIST_FOREACH(ent, &head[hash], next) {
+ if (cmp_flow_ent(ent, &f.e, 2 * 16) != 0) {
+ *val = ent->value;
+ return (1);
+ }
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * New table.
+ */
+static int
+ta_init_fhash(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ int i;
+ struct fhash_cfg *cfg;
+ struct fhashentry4 *fe4;
+ struct fhashentry6 *fe6;
+
+ cfg = malloc(sizeof(struct fhash_cfg), M_IPFW, M_WAITOK | M_ZERO);
+
+ cfg->size = 512;
+
+ cfg->head = malloc(sizeof(struct fhashbhead) * cfg->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+ for (i = 0; i < cfg->size; i++)
+ SLIST_INIT(&cfg->head[i]);
+
+ /* Fill in fe masks based on @tflags */
+ fe4 = &cfg->fe4;
+ fe6 = &cfg->fe6;
+ if (tflags & IPFW_TFFLAG_SRCIP) {
+ memset(&fe4->sip, 0xFF, sizeof(fe4->sip));
+ memset(&fe6->sip6, 0xFF, sizeof(fe6->sip6));
+ }
+ if (tflags & IPFW_TFFLAG_DSTIP) {
+ memset(&fe4->dip, 0xFF, sizeof(fe4->dip));
+ memset(&fe6->dip6, 0xFF, sizeof(fe6->dip6));
+ }
+ if (tflags & IPFW_TFFLAG_SRCPORT) {
+ memset(&fe4->e.sport, 0xFF, sizeof(fe4->e.sport));
+ memset(&fe6->e.sport, 0xFF, sizeof(fe6->e.sport));
+ }
+ if (tflags & IPFW_TFFLAG_DSTPORT) {
+ memset(&fe4->e.dport, 0xFF, sizeof(fe4->e.dport));
+ memset(&fe6->e.dport, 0xFF, sizeof(fe6->e.dport));
+ }
+ if (tflags & IPFW_TFFLAG_PROTO) {
+ memset(&fe4->e.proto, 0xFF, sizeof(fe4->e.proto));
+ memset(&fe6->e.proto, 0xFF, sizeof(fe6->e.proto));
+ }
+
+ fe4->e.af = AF_INET;
+ fe6->e.af = AF_INET6;
+
+ *ta_state = cfg;
+ ti->state = cfg->head;
+ ti->xstate = &cfg->fe4;
+ ti->data = cfg->size;
+ ti->lookup = ta_lookup_fhash;
+
+ return (0);
+}
+
+static void
+ta_destroy_fhash(void *ta_state, struct table_info *ti)
+{
+ struct fhash_cfg *cfg;
+ struct fhashentry *ent, *ent_next;
+ int i;
+
+ cfg = (struct fhash_cfg *)ta_state;
+
+ for (i = 0; i < cfg->size; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head[i], next, ent_next)
+ free(ent, M_IPFW_TBL);
+
+ free(cfg->head, M_IPFW);
+ free(cfg, M_IPFW);
+}
+
+/*
+ * Provide algo-specific table info
+ */
+static void
+ta_dump_fhash_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+ struct fhash_cfg *cfg;
+
+ cfg = (struct fhash_cfg *)ta_state;
+
+ tinfo->flags = IPFW_TATFLAGS_AFITEM;
+ tinfo->taclass4 = IPFW_TACLASS_HASH;
+ tinfo->size4 = cfg->size;
+ tinfo->count4 = cfg->items;
+ tinfo->itemsize4 = sizeof(struct fhashentry4);
+ tinfo->itemsize6 = sizeof(struct fhashentry6);
+}
+
+static int
+ta_dump_fhash_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct fhash_cfg *cfg;
+ struct fhashentry *ent;
+ struct fhashentry4 *fe4;
+#ifdef INET6
+ struct fhashentry6 *fe6;
+#endif
+ struct tflow_entry *tfe;
+
+ cfg = (struct fhash_cfg *)ta_state;
+ ent = (struct fhashentry *)e;
+ tfe = &tent->k.flow;
+
+ tfe->af = ent->af;
+ tfe->proto = ent->proto;
+ tfe->dport = htons(ent->dport);
+ tfe->sport = htons(ent->sport);
+ tent->v.kidx = ent->value;
+ tent->subtype = ent->af;
+
+ if (ent->af == AF_INET) {
+ fe4 = (struct fhashentry4 *)ent;
+ tfe->a.a4.sip.s_addr = htonl(fe4->sip.s_addr);
+ tfe->a.a4.dip.s_addr = htonl(fe4->dip.s_addr);
+ tent->masklen = 32;
+#ifdef INET6
+ } else {
+ fe6 = (struct fhashentry6 *)ent;
+ tfe->a.a6.sip6 = fe6->sip6;
+ tfe->a.a6.dip6 = fe6->dip6;
+ tent->masklen = 128;
+#endif
+ }
+
+ return (0);
+}
+
+static int
+tei_to_fhash_ent(struct tentry_info *tei, struct fhashentry *ent)
+{
+#ifdef INET
+ struct fhashentry4 *fe4;
+#endif
+#ifdef INET6
+ struct fhashentry6 *fe6;
+#endif
+ struct tflow_entry *tfe;
+
+ tfe = (struct tflow_entry *)tei->paddr;
+
+ ent->af = tei->subtype;
+ ent->proto = tfe->proto;
+ ent->dport = ntohs(tfe->dport);
+ ent->sport = ntohs(tfe->sport);
+
+ if (tei->subtype == AF_INET) {
+#ifdef INET
+ fe4 = (struct fhashentry4 *)ent;
+ fe4->sip.s_addr = ntohl(tfe->a.a4.sip.s_addr);
+ fe4->dip.s_addr = ntohl(tfe->a.a4.dip.s_addr);
+#endif
+#ifdef INET6
+ } else if (tei->subtype == AF_INET6) {
+ fe6 = (struct fhashentry6 *)ent;
+ fe6->sip6 = tfe->a.a6.sip6;
+ fe6->dip6 = tfe->a.a6.dip6;
+#endif
+ } else {
+ /* Unknown CIDR type */
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+
+static int
+ta_find_fhash_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct fhash_cfg *cfg;
+ struct fhashbhead *head;
+ struct fhashentry *ent, *tmp;
+ struct fhashentry6 fe6;
+ struct tentry_info tei;
+ int error;
+ uint32_t hash;
+ size_t sz;
+
+ cfg = (struct fhash_cfg *)ta_state;
+
+ ent = &fe6.e;
+
+ memset(&fe6, 0, sizeof(fe6));
+ memset(&tei, 0, sizeof(tei));
+
+ tei.paddr = &tent->k.flow;
+ tei.subtype = tent->subtype;
+
+ if ((error = tei_to_fhash_ent(&tei, ent)) != 0)
+ return (error);
+
+ head = cfg->head;
+ hash = hash_flow_ent(ent, cfg->size);
+
+ if (tei.subtype == AF_INET)
+ sz = 2 * sizeof(struct in_addr);
+ else
+ sz = 2 * sizeof(struct in6_addr);
+
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (cmp_flow_ent(tmp, ent, sz) != 0) {
+ ta_dump_fhash_tentry(ta_state, ti, tmp, tent);
+ return (0);
+ }
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_foreach_fhash(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct fhash_cfg *cfg;
+ struct fhashentry *ent, *ent_next;
+ int i;
+
+ cfg = (struct fhash_cfg *)ta_state;
+
+ for (i = 0; i < cfg->size; i++)
+ SLIST_FOREACH_SAFE(ent, &cfg->head[i], next, ent_next)
+ f(ent, arg);
+}
+
+static int
+ta_prepare_add_fhash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_fhash *tb;
+ struct fhashentry *ent;
+ size_t sz;
+ int error;
+
+ tb = (struct ta_buf_fhash *)ta_buf;
+
+ if (tei->subtype == AF_INET)
+ sz = sizeof(struct fhashentry4);
+ else if (tei->subtype == AF_INET6)
+ sz = sizeof(struct fhashentry6);
+ else
+ return (EINVAL);
+
+ ent = malloc(sz, M_IPFW_TBL, M_WAITOK | M_ZERO);
+
+ error = tei_to_fhash_ent(tei, ent);
+ if (error != 0) {
+ free(ent, M_IPFW_TBL);
+ return (error);
+ }
+ tb->ent_ptr = ent;
+
+ return (0);
+}
+
+static int
+ta_add_fhash(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct fhash_cfg *cfg;
+ struct fhashbhead *head;
+ struct fhashentry *ent, *tmp;
+ struct ta_buf_fhash *tb;
+ int exists;
+ uint32_t hash, value;
+ size_t sz;
+
+ cfg = (struct fhash_cfg *)ta_state;
+ tb = (struct ta_buf_fhash *)ta_buf;
+ ent = (struct fhashentry *)tb->ent_ptr;
+ exists = 0;
+
+ /* Read current value from @tei */
+ ent->value = tei->value;
+
+ head = cfg->head;
+ hash = hash_flow_ent(ent, cfg->size);
+
+ if (tei->subtype == AF_INET)
+ sz = 2 * sizeof(struct in_addr);
+ else
+ sz = 2 * sizeof(struct in6_addr);
+
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (cmp_flow_ent(tmp, ent, sz) != 0) {
+ exists = 1;
+ break;
+ }
+ }
+
+ if (exists == 1) {
+ if ((tei->flags & TEI_FLAGS_UPDATE) == 0)
+ return (EEXIST);
+ /* Record already exists. Update value if we're asked to */
+ /* Exchange values between tmp and @tei */
+ value = tmp->value;
+ tmp->value = tei->value;
+ tei->value = value;
+ /* Indicate that update has happened instead of addition */
+ tei->flags |= TEI_FLAGS_UPDATED;
+ *pnum = 0;
+ } else {
+ if ((tei->flags & TEI_FLAGS_DONTADD) != 0)
+ return (EFBIG);
+
+ SLIST_INSERT_HEAD(&head[hash], ent, next);
+ tb->ent_ptr = NULL;
+ *pnum = 1;
+
+ /* Update counters and check if we need to grow hash */
+ cfg->items++;
+ }
+
+ return (0);
+}
+
+static int
+ta_prepare_del_fhash(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_fhash *tb;
+
+ tb = (struct ta_buf_fhash *)ta_buf;
+
+ return (tei_to_fhash_ent(tei, &tb->fe6.e));
+}
+
+static int
+ta_del_fhash(void *ta_state, struct table_info *ti, struct tentry_info *tei,
+ void *ta_buf, uint32_t *pnum)
+{
+ struct fhash_cfg *cfg;
+ struct fhashbhead *head;
+ struct fhashentry *ent, *tmp;
+ struct ta_buf_fhash *tb;
+ uint32_t hash;
+ size_t sz;
+
+ cfg = (struct fhash_cfg *)ta_state;
+ tb = (struct ta_buf_fhash *)ta_buf;
+ ent = &tb->fe6.e;
+
+ head = cfg->head;
+ hash = hash_flow_ent(ent, cfg->size);
+
+ if (tei->subtype == AF_INET)
+ sz = 2 * sizeof(struct in_addr);
+ else
+ sz = 2 * sizeof(struct in6_addr);
+
+ /* Check for existence */
+ SLIST_FOREACH(tmp, &head[hash], next) {
+ if (cmp_flow_ent(tmp, ent, sz) == 0)
+ continue;
+
+ SLIST_REMOVE(&head[hash], tmp, fhashentry, next);
+ tei->value = tmp->value;
+ *pnum = 1;
+ cfg->items--;
+ tb->ent_ptr = tmp;
+ return (0);
+ }
+
+ return (ENOENT);
+}
+
+static void
+ta_flush_fhash_entry(struct ip_fw_chain *ch, struct tentry_info *tei,
+ void *ta_buf)
+{
+ struct ta_buf_fhash *tb;
+
+ tb = (struct ta_buf_fhash *)ta_buf;
+
+ if (tb->ent_ptr != NULL)
+ free(tb->ent_ptr, M_IPFW_TBL);
+}
+
+/*
+ * Hash growing callbacks.
+ */
+
+static int
+ta_need_modify_fhash(void *ta_state, struct table_info *ti, uint32_t count,
+ uint64_t *pflags)
+{
+ struct fhash_cfg *cfg;
+
+ cfg = (struct fhash_cfg *)ta_state;
+
+ if (cfg->items > cfg->size && cfg->size < 65536) {
+ *pflags = cfg->size * 2;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Allocate new, larger fhash.
+ */
+static int
+ta_prepare_mod_fhash(void *ta_buf, uint64_t *pflags)
+{
+ struct mod_item *mi;
+ struct fhashbhead *head;
+ int i;
+
+ mi = (struct mod_item *)ta_buf;
+
+ memset(mi, 0, sizeof(struct mod_item));
+ mi->size = *pflags;
+ head = malloc(sizeof(struct fhashbhead) * mi->size, M_IPFW,
+ M_WAITOK | M_ZERO);
+ for (i = 0; i < mi->size; i++)
+ SLIST_INIT(&head[i]);
+
+ mi->main_ptr = head;
+
+ return (0);
+}
+
+/*
+ * Copy data from old runtime array to new one.
+ */
+static int
+ta_fill_mod_fhash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t *pflags)
+{
+
+ /* In is not possible to do rehash if we're not holidng WLOCK. */
+ return (0);
+}
+
+/*
+ * Switch old & new arrays.
+ */
+static void
+ta_modify_fhash(void *ta_state, struct table_info *ti, void *ta_buf,
+ uint64_t pflags)
+{
+ struct mod_item *mi;
+ struct fhash_cfg *cfg;
+ struct fhashbhead *old_head, *new_head;
+ struct fhashentry *ent, *ent_next;
+ int i;
+ uint32_t nhash;
+ size_t old_size;
+
+ mi = (struct mod_item *)ta_buf;
+ cfg = (struct fhash_cfg *)ta_state;
+
+ old_size = cfg->size;
+ old_head = ti->state;
+
+ new_head = (struct fhashbhead *)mi->main_ptr;
+ for (i = 0; i < old_size; i++) {
+ SLIST_FOREACH_SAFE(ent, &old_head[i], next, ent_next) {
+ nhash = hash_flow_ent(ent, mi->size);
+ SLIST_INSERT_HEAD(&new_head[nhash], ent, next);
+ }
+ }
+
+ ti->state = new_head;
+ ti->data = mi->size;
+ cfg->head = new_head;
+ cfg->size = mi->size;
+
+ mi->main_ptr = old_head;
+}
+
+/*
+ * Free unneded array.
+ */
+static void
+ta_flush_mod_fhash(void *ta_buf)
+{
+ struct mod_item *mi;
+
+ mi = (struct mod_item *)ta_buf;
+ if (mi->main_ptr != NULL)
+ free(mi->main_ptr, M_IPFW);
+}
+
+struct table_algo flow_hash = {
+ .name = "flow:hash",
+ .type = IPFW_TABLE_FLOW,
+ .flags = TA_FLAG_DEFAULT,
+ .ta_buf_size = sizeof(struct ta_buf_fhash),
+ .init = ta_init_fhash,
+ .destroy = ta_destroy_fhash,
+ .prepare_add = ta_prepare_add_fhash,
+ .prepare_del = ta_prepare_del_fhash,
+ .add = ta_add_fhash,
+ .del = ta_del_fhash,
+ .flush_entry = ta_flush_fhash_entry,
+ .foreach = ta_foreach_fhash,
+ .dump_tentry = ta_dump_fhash_tentry,
+ .find_tentry = ta_find_fhash_tentry,
+ .dump_tinfo = ta_dump_fhash_tinfo,
+ .need_modify = ta_need_modify_fhash,
+ .prepare_mod = ta_prepare_mod_fhash,
+ .fill_mod = ta_fill_mod_fhash,
+ .modify = ta_modify_fhash,
+ .flush_mod = ta_flush_mod_fhash,
+};
+
+/*
+ * Kernel fibs bindings.
+ *
+ * Implementation:
+ *
+ * Runtime part:
+ * - fully relies on route API
+ * - fib number is stored in ti->data
+ *
+ */
+
+static int ta_lookup_kfib(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val);
+static int kfib_parse_opts(int *pfib, char *data);
+static void ta_print_kfib_config(void *ta_state, struct table_info *ti,
+ char *buf, size_t bufsize);
+static int ta_init_kfib(struct ip_fw_chain *ch, void **ta_state,
+ struct table_info *ti, char *data, uint8_t tflags);
+static void ta_destroy_kfib(void *ta_state, struct table_info *ti);
+static void ta_dump_kfib_tinfo(void *ta_state, struct table_info *ti,
+ ipfw_ta_tinfo *tinfo);
+static int contigmask(uint8_t *p, int len);
+static int ta_dump_kfib_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent);
+static int ta_dump_kfib_tentry_int(struct sockaddr *paddr,
+ struct sockaddr *pmask, ipfw_obj_tentry *tent);
+static int ta_find_kfib_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent);
+static void ta_foreach_kfib(void *ta_state, struct table_info *ti,
+ ta_foreach_f *f, void *arg);
+
+
+static int
+ta_lookup_kfib(struct table_info *ti, void *key, uint32_t keylen,
+ uint32_t *val)
+{
+#ifdef INET
+ struct nhop4_basic nh4;
+ struct in_addr in;
+#endif
+#ifdef INET6
+ struct nhop6_basic nh6;
+#endif
+ int error;
+
+ error = ENOENT;
+#ifdef INET
+ if (keylen == 4) {
+ in.s_addr = *(in_addr_t *)key;
+ error = fib4_lookup_nh_basic(ti->data,
+ in, 0, 0, &nh4);
+ }
+#endif
+#ifdef INET6
+ if (keylen == 6)
+ error = fib6_lookup_nh_basic(ti->data,
+ (struct in6_addr *)key, 0, 0, 0, &nh6);
+#endif
+
+ if (error != 0)
+ return (0);
+
+ *val = 0;
+
+ return (1);
+}
+
+/* Parse 'fib=%d' */
+static int
+kfib_parse_opts(int *pfib, char *data)
+{
+ char *pdel, *pend, *s;
+ int fibnum;
+
+ if (data == NULL)
+ return (0);
+ if ((pdel = strchr(data, ' ')) == NULL)
+ return (0);
+ while (*pdel == ' ')
+ pdel++;
+ if (strncmp(pdel, "fib=", 4) != 0)
+ return (EINVAL);
+ if ((s = strchr(pdel, ' ')) != NULL)
+ *s++ = '\0';
+
+ pdel += 4;
+ /* Need \d+ */
+ fibnum = strtol(pdel, &pend, 10);
+ if (*pend != '\0')
+ return (EINVAL);
+
+ *pfib = fibnum;
+
+ return (0);
+}
+
+static void
+ta_print_kfib_config(void *ta_state, struct table_info *ti, char *buf,
+ size_t bufsize)
+{
+
+ if (ti->data != 0)
+ snprintf(buf, bufsize, "%s fib=%lu", "addr:kfib", ti->data);
+ else
+ snprintf(buf, bufsize, "%s", "addr:kfib");
+}
+
+static int
+ta_init_kfib(struct ip_fw_chain *ch, void **ta_state, struct table_info *ti,
+ char *data, uint8_t tflags)
+{
+ int error, fibnum;
+
+ fibnum = 0;
+ if ((error = kfib_parse_opts(&fibnum, data)) != 0)
+ return (error);
+
+ if (fibnum >= rt_numfibs)
+ return (E2BIG);
+
+ ti->data = fibnum;
+ ti->lookup = ta_lookup_kfib;
+
+ return (0);
+}
+
+/*
+ * Destroys table @ti
+ */
+static void
+ta_destroy_kfib(void *ta_state, struct table_info *ti)
+{
+
+}
+
+/*
+ * Provide algo-specific table info
+ */
+static void
+ta_dump_kfib_tinfo(void *ta_state, struct table_info *ti, ipfw_ta_tinfo *tinfo)
+{
+
+ tinfo->flags = IPFW_TATFLAGS_AFDATA;
+ tinfo->taclass4 = IPFW_TACLASS_RADIX;
+ tinfo->count4 = 0;
+ tinfo->itemsize4 = sizeof(struct rtentry);
+ tinfo->taclass6 = IPFW_TACLASS_RADIX;
+ tinfo->count6 = 0;
+ tinfo->itemsize6 = sizeof(struct rtentry);
+}
+
+static int
+contigmask(uint8_t *p, int len)
+{
+ int i, n;
+
+ for (i = 0; i < len ; i++)
+ if ( (p[i/8] & (1 << (7 - (i%8)))) == 0) /* first bit unset */
+ break;
+ for (n= i + 1; n < len; n++)
+ if ( (p[n/8] & (1 << (7 - (n % 8)))) != 0)
+ return (-1); /* mask not contiguous */
+ return (i);
+}
+
+
+static int
+ta_dump_kfib_tentry(void *ta_state, struct table_info *ti, void *e,
+ ipfw_obj_tentry *tent)
+{
+ struct rtentry *rte;
+
+ rte = (struct rtentry *)e;
+
+ return ta_dump_kfib_tentry_int(rt_key(rte), rt_mask(rte), tent);
+}
+
+static int
+ta_dump_kfib_tentry_int(struct sockaddr *paddr, struct sockaddr *pmask,
+ ipfw_obj_tentry *tent)
+{
+#ifdef INET
+ struct sockaddr_in *addr, *mask;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *addr6, *mask6;
+#endif
+ int len;
+
+ len = 0;
+
+ /* Guess IPv4/IPv6 radix by sockaddr family */
+#ifdef INET
+ if (paddr->sa_family == AF_INET) {
+ addr = (struct sockaddr_in *)paddr;
+ mask = (struct sockaddr_in *)pmask;
+ tent->k.addr.s_addr = addr->sin_addr.s_addr;
+ len = 32;
+ if (mask != NULL)
+ len = contigmask((uint8_t *)&mask->sin_addr, 32);
+ if (len == -1)
+ len = 0;
+ tent->masklen = len;
+ tent->subtype = AF_INET;
+ tent->v.kidx = 0; /* Do we need to put GW here? */
+ }
+#endif
+#ifdef INET6
+ if (paddr->sa_family == AF_INET6) {
+ addr6 = (struct sockaddr_in6 *)paddr;
+ mask6 = (struct sockaddr_in6 *)pmask;
+ memcpy(&tent->k, &addr6->sin6_addr, sizeof(struct in6_addr));
+ len = 128;
+ if (mask6 != NULL)
+ len = contigmask((uint8_t *)&mask6->sin6_addr, 128);
+ if (len == -1)
+ len = 0;
+ tent->masklen = len;
+ tent->subtype = AF_INET6;
+ tent->v.kidx = 0;
+ }
+#endif
+
+ return (0);
+}
+
+static int
+ta_find_kfib_tentry(void *ta_state, struct table_info *ti,
+ ipfw_obj_tentry *tent)
+{
+ struct rt_addrinfo info;
+ struct sockaddr_in6 key6, dst6, mask6;
+ struct sockaddr *dst, *key, *mask;
+
+ /* Prepare sockaddr for prefix/mask and info */
+ bzero(&dst6, sizeof(dst6));
+ dst6.sin6_len = sizeof(dst6);
+ dst = (struct sockaddr *)&dst6;
+ bzero(&mask6, sizeof(mask6));
+ mask6.sin6_len = sizeof(mask6);
+ mask = (struct sockaddr *)&mask6;
+
+ bzero(&info, sizeof(info));
+ info.rti_info[RTAX_DST] = dst;
+ info.rti_info[RTAX_NETMASK] = mask;
+
+ /* Prepare the lookup key */
+ bzero(&key6, sizeof(key6));
+ key6.sin6_family = tent->subtype;
+ key = (struct sockaddr *)&key6;
+
+ if (tent->subtype == AF_INET) {
+ ((struct sockaddr_in *)&key6)->sin_addr = tent->k.addr;
+ key6.sin6_len = sizeof(struct sockaddr_in);
+ } else {
+ key6.sin6_addr = tent->k.addr6;
+ key6.sin6_len = sizeof(struct sockaddr_in6);
+ }
+
+ if (rib_lookup_info(ti->data, key, 0, 0, &info) != 0)
+ return (ENOENT);
+ if ((info.rti_addrs & RTA_NETMASK) == 0)
+ mask = NULL;
+
+ ta_dump_kfib_tentry_int(dst, mask, tent);
+
+ return (0);
+}
+
+static void
+ta_foreach_kfib(void *ta_state, struct table_info *ti, ta_foreach_f *f,
+ void *arg)
+{
+ struct rib_head *rh;
+ int error;
+
+ rh = rt_tables_get_rnh(ti->data, AF_INET);
+ if (rh != NULL) {
+ RIB_RLOCK(rh);
+ error = rh->rnh_walktree(&rh->head, (walktree_f_t *)f, arg);
+ RIB_RUNLOCK(rh);
+ }
+
+ rh = rt_tables_get_rnh(ti->data, AF_INET6);
+ if (rh != NULL) {
+ RIB_RLOCK(rh);
+ error = rh->rnh_walktree(&rh->head, (walktree_f_t *)f, arg);
+ RIB_RUNLOCK(rh);
+ }
+}
+
+struct table_algo addr_kfib = {
+ .name = "addr:kfib",
+ .type = IPFW_TABLE_ADDR,
+ .flags = TA_FLAG_READONLY,
+ .ta_buf_size = 0,
+ .init = ta_init_kfib,
+ .destroy = ta_destroy_kfib,
+ .foreach = ta_foreach_kfib,
+ .dump_tentry = ta_dump_kfib_tentry,
+ .find_tentry = ta_find_kfib_tentry,
+ .dump_tinfo = ta_dump_kfib_tinfo,
+ .print_config = ta_print_kfib_config,
+};
+
+void
+ipfw_table_algo_init(struct ip_fw_chain *ch)
+{
+ size_t sz;
+
+ /*
+ * Register all algorithms presented here.
+ */
+ sz = sizeof(struct table_algo);
+ ipfw_add_table_algo(ch, &addr_radix, sz, &addr_radix.idx);
+ ipfw_add_table_algo(ch, &addr_hash, sz, &addr_hash.idx);
+ ipfw_add_table_algo(ch, &iface_idx, sz, &iface_idx.idx);
+ ipfw_add_table_algo(ch, &number_array, sz, &number_array.idx);
+ ipfw_add_table_algo(ch, &flow_hash, sz, &flow_hash.idx);
+ ipfw_add_table_algo(ch, &addr_kfib, sz, &addr_kfib.idx);
+}
+
+void
+ipfw_table_algo_destroy(struct ip_fw_chain *ch)
+{
+
+ ipfw_del_table_algo(ch, addr_radix.idx);
+ ipfw_del_table_algo(ch, addr_hash.idx);
+ ipfw_del_table_algo(ch, iface_idx.idx);
+ ipfw_del_table_algo(ch, number_array.idx);
+ ipfw_del_table_algo(ch, flow_hash.idx);
+ ipfw_del_table_algo(ch, addr_kfib.idx);
+}
+
+