[patch] ipfw interface tracking and opcode rewriting
Luigi Rizzo
rizzo at iet.unipi.it
Wed Apr 24 16:22:05 UTC 2013
On Wed, Apr 24, 2013 at 08:01:23PM +0400, Alexander V. Chernikov wrote:
> Hello list!
>
> Currently ipfw uses strncmp() function to do interface matching which is
> quite slow.
> Additionally, ipfw_insn_if opcode is quite big and given that struct
> ip_fw occupy 48 bytes
> (without first instruction) which gives us good chance that part of
> interface name will be on the second cache line on amd64.
>
> Pure synthetic testing (ipfw with 1 and 2 'ipfw count ip from any to any
> recv ifaceX') shows about 3.8% performance loss (190kpps out of 5.1
> mpps) for each rule,
> while indexed version shows about 2.0% and 1.2% for first and second rule.
>
> Additionally, our production (8.3-based firewalls with old strncmp)
> shows about 40% kernel time spent in strncmp on 1-2mpps (each packet
> traverses 5-6 such rules).
>
> Here is the patch which does the following:
> 1) adds interface tracking for ipfw. Every interface is tracked
> regardless of its usage in the ruleset. This simplifies locking and
> makes easier to port such functionality to userland.
> 2) adds general opcode rewriting system permitting kernel to
> algorithmically (stateless) or statefully (involving extrernal data)
> rewrite user-supplied opcodes with possible size change.
> This can be used to deprecate opcodes which are now superseded by newer
> ones while keeping ABI (and we currently have such opcodes).
> 3) Store (and track) inderface index for non-wildcard interface inside
> opcode.
>
> If there are no objections I would like to commit (possibly updated
> vesrion) in the middle of the next week.
hmmm.... this is quite a large change, and from the description it
is a bit unclear to me how the "opcode rewriting" thing relates to
the use of strings vs index for name matching.
Additionally, i wonder if there isn't a better way to replace strncmp
with some two 64-bit comparisons (the name is 16 bytes) by making
sure that the fields are zero-padded and suitably aligned.
At this point, on the machines you care about (able to sustain
1+ Mpps) the two comparison should have the same cost as
the index comparison, without the need to track update in the names.
note, my comment is only about the strncmp() part, i guess the
opcode rewriting is orthogonal and may have other applications,
but i am not completely sure about it.
cheers
luigi
understand if
While in principle
i do not think the strncmp is a major performance killer. even if it is,
i believe it
>
> Index: sys/netinet/ip_fw.h
> ===================================================================
> --- sys/netinet/ip_fw.h (revision 248704)
> +++ sys/netinet/ip_fw.h (working copy)
> @@ -341,6 +341,7 @@ typedef struct _ipfw_insn_if {
> union {
> struct in_addr ip;
> int glob;
> + unsigned int if_idx; /* Interface index (kernel) */
> } p;
> char name[IFNAMSIZ];
> } ipfw_insn_if;
> @@ -495,6 +496,8 @@ typedef struct _ipfw_insn_icmp6 {
> * queue(3) macros for portability and readability.
> */
>
> +#define IP_FW_RULE_REWRITTEN 0x01 /* Rule is modified by rewriter */
> +
> struct ip_fw {
> struct ip_fw *x_next; /* linked list of rules */
> struct ip_fw *next_rule; /* ptr to next [skipto] rule */
> @@ -505,7 +508,7 @@ struct ip_fw {
> uint16_t rulenum; /* rule number */
> uint8_t set; /* rule set (0..31) */
> #define RESVD_SET 31 /* set for default and persistent rules */
> - uint8_t _pad; /* padding */
> + uint8_t flags; /* padding */
> uint32_t id; /* rule id */
>
> /* These fields are present in all rules. */
> Index: sys/modules/ipfw/Makefile
> ===================================================================
> --- sys/modules/ipfw/Makefile (revision 248704)
> +++ sys/modules/ipfw/Makefile (working copy)
> @@ -8,6 +8,7 @@ KMOD= ipfw
> SRCS= ip_fw2.c ip_fw_pfil.c
> SRCS+= ip_fw_dynamic.c ip_fw_log.c
> SRCS+= ip_fw_sockopt.c ip_fw_table.c
> +SRCS+= ip_fw_iface.c ip_fw_rewrite.c
> SRCS+= opt_inet.h opt_inet6.h opt_ipdivert.h opt_ipfw.h opt_ipsec.h
>
> CFLAGS+= -DIPFIREWALL
> Index: sys/netpfil/ipfw/ip_fw2.c
> ===================================================================
> --- sys/netpfil/ipfw/ip_fw2.c (revision 248704)
> +++ sys/netpfil/ipfw/ip_fw2.c (working copy)
> @@ -353,17 +353,17 @@ iface_match(struct ifnet *ifp, ipfw_insn_if *cmd,
> if (ifp == NULL) /* no iface with this packet, match fails */
> return 0;
> /* Check by name or by IP address */
> - if (cmd->name[0] != '\0') { /* match by name */
> - if (cmd->name[0] == '\1') /* use tablearg to match */
> + if (cmd->o.arg1 != 0) { /* match by name */
> + if (cmd->o.arg1 == 1) /* use tablearg to match */
> return ipfw_lookup_table_extended(chain, cmd->p.glob,
> ifp->if_xname, tablearg, IPFW_TABLE_INTERFACE);
> /* Check name */
> - if (cmd->p.glob) {
> + if (cmd->p.if_idx) {
> + if (ifp->if_index == cmd->p.if_idx)
> + return (1);
> + } else {
> if (fnmatch(cmd->name, ifp->if_xname, 0) == 0)
> return(1);
> - } else {
> - if (strncmp(ifp->if_xname, cmd->name, IFNAMSIZ) == 0)
> - return(1);
> }
> } else {
> #ifdef __FreeBSD__ /* and OSX too ? */
> @@ -2667,6 +2667,8 @@ vnet_ipfw_init(const void *unused)
>
> IPFW_LOCK_INIT(chain);
> ipfw_dyn_init(chain);
> + ipfw_ifhash_init(chain);
> + ipfw_rewrite_init(chain);
>
> /* First set up some values that are compile time options */
> V_ipfw_vnet_ready = 1; /* Open for business */
> @@ -2708,6 +2710,7 @@ vnet_ipfw_uninit(const void *unused)
> (void)ipfw_attach_hooks(0 /* detach */);
> V_ip_fw_ctl_ptr = NULL;
> IPFW_UH_WLOCK(chain);
> + ipfw_ifhash_detach(chain); /* detach eventhandlers */
> IPFW_UH_WUNLOCK(chain);
> IPFW_UH_WLOCK(chain);
>
> @@ -2722,9 +2725,14 @@ vnet_ipfw_uninit(const void *unused)
> rule = chain->map[i];
> rule->x_next = reap;
> reap = rule;
> + /* Clear rewrites if any */
> + if (rule->flags & IP_FW_RULE_REWRITTEN)
> + ipfw_relocate_rewrite(chain, rule->cmd, NULL);
> }
> if (chain->map)
> free(chain->map, M_IPFW);
> + ipfw_rewrite_free(chain);
> + ipfw_ifhash_free(chain);
> IPFW_WUNLOCK(chain);
> IPFW_UH_WUNLOCK(chain);
> if (reap != NULL)
> Index: sys/netpfil/ipfw/ip_fw_private.h
> ===================================================================
> --- sys/netpfil/ipfw/ip_fw_private.h (revision 248704)
> +++ sys/netpfil/ipfw/ip_fw_private.h (working copy)
> @@ -212,6 +212,13 @@ VNET_DECLARE(int, autoinc_step);
> VNET_DECLARE(unsigned int, fw_tables_max);
> #define V_fw_tables_max VNET(fw_tables_max)
>
> +
> +#define CMDSIZE(rule) (((struct ip_fw *)(rule))->cmd_len * sizeof(uint32_t))
> +
> +
> +struct ip_fw_if_data;
> +struct ip_fw_rw_data;
> +
> struct ip_fw_chain {
> struct ip_fw *rules; /* list of rules */
> struct ip_fw *reap; /* list of rules to reap */
> @@ -232,8 +239,42 @@ struct ip_fw_chain {
> #endif
> uint32_t id; /* ruleset id */
> uint32_t gencnt; /* generation count */
> + struct ip_fw_if_data *if_data; /* Interface tracking data */
> + struct ip_fw_rw_data *rewrite_data; /* Rule rewrite data */
> };
>
> +/* ip_fw_rewrite.c */
> +struct ip_fw_rw_info {
> + void *sptr; /* State created by ipfw_prepare_rewrite() */
> + int count; /* Number of opcodes requesting rewrite */
> + int states; /* Number of opcodes with stateful rewrite */
> + int lendiff; /* Difference with oridinal rule len (insns) */
> +};
> +
> +void ipfw_rewrite_init(struct ip_fw_chain *chain);
> +void ipfw_rewrite_free(struct ip_fw_chain *chain);
> +int ipfw_rewrite_len(struct ip_fw_chain *chain);
> +void *ipfw_prepare_rewrite(struct ip_fw_chain *chain, ipfw_insn *cmd,
> + int cmd_len, struct ip_fw_rw_info *rwi);
> +void ipfw_perform_rewrite(struct ip_fw_chain *chain, ipfw_insn *kcmd,
> + void *state);
> +void ipfw_relocate_rewrite(struct ip_fw_chain *chain, ipfw_insn *old,
> + ipfw_insn *new);
> +int ipfw_export_rewrite(struct ip_fw_chain *chain, ipfw_insn *kcmd,
> + ipfw_insn *target);
> +
> +void ipfw_check_rewrite(struct ip_fw_chain *chain, ipfw_insn *insn,
> + struct ip_fw_rw_info *rwi);
> +void ipfw_update_rewrite(struct ip_fw_chain *chain, ipfw_insn *insn,
> + void *state, uintptr_t val);
> +
> +
> +/* ip_fw_iface.c */
> +void ipfw_ifhash_init(struct ip_fw_chain *chain);
> +void ipfw_ifhash_free(struct ip_fw_chain *chain);
> +void ipfw_ifhash_detach(struct ip_fw_chain *chain);
> +
> +
> struct sockopt; /* used by tcp_var.h */
>
> /* Macro for working with various counters */
> @@ -295,7 +336,8 @@ struct sockopt; /* used by tcp_var.h */
>
> /* In ip_fw_sockopt.c */
> int ipfw_find_rule(struct ip_fw_chain *chain, uint32_t key, uint32_t id);
> -int ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule);
> +int ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule,
> + struct ip_fw_rw_info *rwi);
> int ipfw_ctl(struct sockopt *sopt);
> int ipfw_chk(struct ip_fw_args *args);
> void ipfw_reap_rules(struct ip_fw *head);
> Index: sys/netpfil/ipfw/ip_fw_sockopt.c
> ===================================================================
> --- sys/netpfil/ipfw/ip_fw_sockopt.c (revision 248971)
> +++ sys/netpfil/ipfw/ip_fw_sockopt.c (working copy)
> @@ -73,6 +73,8 @@ MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct
> * static variables followed by global ones (none in this file)
> */
>
> +static void ipfw_export_header(struct ip_fw *krule, struct ip_fw *dst);
> +
> /*
> * Find the smallest rule >= key, id.
> * We could use bsearch but it is so simple that we code it directly
> @@ -153,7 +155,8 @@ swap_map(struct ip_fw_chain *chain, struct ip_fw *
> * Must be called without IPFW_UH held
> */
> int
> -ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule)
> +ipfw_add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule,
> + struct ip_fw_rw_info *rwi)
> {
> struct ip_fw *rule;
> int i, l, insert_before;
> @@ -163,7 +166,8 @@ int
> return (EINVAL);
>
> l = RULESIZE(input_rule);
> - rule = malloc(l, M_IPFW, M_WAITOK | M_ZERO);
> + rule = malloc(l + rwi->lendiff * sizeof(uint32_t), M_IPFW,
> + M_WAITOK | M_ZERO);
> /* get_map returns with IPFW_UH_WLOCK if successful */
> map = get_map(chain, 1, 0 /* not locked */);
> if (map == NULL) {
> @@ -171,7 +175,15 @@ int
> return ENOSPC;
> }
>
> - bcopy(input_rule, rule, l);
> + if (rwi->sptr == NULL)
> + bcopy(input_rule, rule, l);
> + else {
> + /* Copy header and first instuction */
> + bcopy(input_rule, rule, sizeof(struct ip_fw));
> + rule->flags |= IP_FW_RULE_REWRITTEN;
> + ipfw_perform_rewrite(chain, rule->cmd, rwi->sptr);
> + }
> +
> /* clear fields not settable from userland */
> rule->x_next = NULL;
> rule->next_rule = NULL;
> @@ -366,6 +378,14 @@ del_entry(struct ip_fw_chain *chain, uint32_t arg)
> rule = chain->map[i];
> if (keep_rule(rule, cmd, new_set, num))
> map[ofs++] = rule;
> + else {
> + /* Clear rewrites if any */
> + if (rule->flags & IP_FW_RULE_REWRITTEN) {
> + printf("Moving rule %p to clear list\n", rule);
> + ipfw_relocate_rewrite(chain,
> + rule->cmd, NULL);
> + }
> + }
> }
> /* 3. copy the final part of the map */
> bcopy(chain->map + end, map + ofs,
> @@ -384,6 +404,7 @@ del_entry(struct ip_fw_chain *chain, uint32_t arg)
> ipfw_expire_dyn_rules(chain, rule, RESVD_SET);
> rule->x_next = chain->reap;
> chain->reap = rule;
> + printf("Adding rule %p to reap list\n", rule);
> }
> break;
>
> @@ -517,7 +538,8 @@ zero_entry(struct ip_fw_chain *chain, u_int32_t ar
> * Rules are simple, so this mostly need to check rule sizes.
> */
> static int
> -check_ipfw_struct(struct ip_fw *rule, int size)
> +check_ipfw_struct(struct ip_fw_chain *chain, struct ip_fw *rule, int size,
> + struct ip_fw_rw_info *rwi)
> {
> int l, cmdlen = 0;
> int have_action=0;
> @@ -696,6 +718,7 @@ static int
> case O_VIA:
> if (cmdlen != F_INSN_SIZE(ipfw_insn_if))
> goto bad_size;
> + ipfw_check_rewrite(chain, cmd, rwi);
> break;
>
> case O_ALTQ:
> @@ -868,6 +891,13 @@ int convert_rule_to_8(struct ip_fw *rule);
> #endif
>
>
> +static void
> +ipfw_export_header(struct ip_fw *krule, struct ip_fw *dst)
> +{
> +
> + memcpy(dst, krule, sizeof(struct ip_fw) - sizeof(ipfw_insn));
> +}
> +
> /*
> * Copy the static and dynamic rules to the supplied buffer
> * and return the amount of space actually used.
> @@ -887,11 +917,28 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf
> rule = chain->map[i];
>
> if (is7) {
> - /* Convert rule to FreeBSd 7.2 format */
> - l = RULESIZE7(rule);
> + /* Convert rule to FreeBSD 7.2 format */
> + if (rule->flags & IP_FW_RULE_REWRITTEN)
> + l = ipfw_export_rewrite(chain, rule->cmd, NULL);
> + else
> + l = CMDSIZE(rule);
> +
> + /*
> + * Add header length.
> + * v.8 rule header is 4 bytes bigger.
> + */
> + l += sizeof(struct ip_fw7) - sizeof(ipfw_insn);
> +
> if (bp + l + sizeof(uint32_t) <= ep) {
> int error;
> bcopy(rule, bp, l + sizeof(uint32_t));
> +
> + if (rule->flags & IP_FW_RULE_REWRITTEN) {
> + ipfw_export_rewrite(chain, rule->cmd, dst->cmd);
> + ipfw_export_header(rule, dst);
> + } else
> + bcopy(rule, bp, l + sizeof(uint32_t));
> +
> error = convert_rule_to_7((struct ip_fw *) bp);
> if (error)
> return 0; /*XXX correct? */
> @@ -910,14 +957,23 @@ ipfw_getrules(struct ip_fw_chain *chain, void *buf
> continue; /* go to next rule */
> }
>
> - /* normal mode, don't touch rules */
> - l = RULESIZE(rule);
> + if (rule->flags & IP_FW_RULE_REWRITTEN)
> + l = ipfw_export_rewrite(chain, rule->cmd, NULL);
> + else
> + l = CMDSIZE(rule);
> + /* Add header length */
> + l += sizeof(struct ip_fw) - sizeof(ipfw_insn);
> +
> if (bp + l > ep) { /* should not happen */
> printf("overflow dumping static rules\n");
> break;
> }
> dst = (struct ip_fw *)bp;
> - bcopy(rule, dst, l);
> + if (rule->flags & IP_FW_RULE_REWRITTEN) {
> + ipfw_export_rewrite(chain, rule->cmd, dst->cmd);
> + ipfw_export_header(rule, dst);
> + } else
> + bcopy(rule, dst, l);
> /*
> * XXX HACK. Store the disable mask in the "next"
> * pointer in a wild attempt to keep the ABI the same.
> @@ -949,6 +1005,7 @@ ipfw_ctl(struct sockopt *sopt)
> uint32_t opt;
> char xbuf[128];
> ip_fw3_opheader *op3 = NULL;
> + struct ip_fw_rw_info rwi;
>
> error = priv_check(sopt->sopt_td, PRIV_NETINET_IPFW);
> if (error)
> @@ -998,7 +1055,7 @@ ipfw_ctl(struct sockopt *sopt)
> for (;;) {
> int len = 0, want;
>
> - size = chain->static_len;
> + size = chain->static_len + ipfw_rewrite_len(chain);
> size += ipfw_dyn_len();
> if (size >= sopt->sopt_valsize)
> break;
> @@ -1027,6 +1084,8 @@ ipfw_ctl(struct sockopt *sopt)
> error = sooptcopyin(sopt, rule, RULE_MAXSIZE,
> sizeof(struct ip_fw7) );
>
> + memset(&rwi, 0, sizeof(rwi));
> +
> /*
> * If the size of commands equals RULESIZE7 then we assume
> * a FreeBSD7.2 binary is talking to us (set is7=1).
> @@ -1042,15 +1101,21 @@ ipfw_ctl(struct sockopt *sopt)
> if (error)
> return error;
> if (error == 0)
> - error = check_ipfw_struct(rule, RULESIZE(rule));
> + error = check_ipfw_struct(chain, rule, RULESIZE(rule),
> + &rwi);
> } else {
> is7 = 0;
> if (error == 0)
> - error = check_ipfw_struct(rule, sopt->sopt_valsize);
> + error = check_ipfw_struct(chain, rule,
> + sopt->sopt_valsize, &rwi);
> }
> if (error == 0) {
> + /* Prepare rewrite, if needed */
> + if (rwi.count > 0)
> + rwi.sptr = ipfw_prepare_rewrite(chain,
> + rule->cmd, rule->cmd_len, &rwi);
> /* locking is done within ipfw_add_rule() */
> - error = ipfw_add_rule(chain, rule);
> + error = ipfw_add_rule(chain, rule, &rwi);
> size = RULESIZE(rule);
> if (!error && sopt->sopt_dir == SOPT_GET) {
> if (is7) {
> @@ -1350,7 +1415,7 @@ convert_rule_to_7(struct ip_fw *rule)
> bcopy(rule, tmp, RULE_MAXSIZE);
>
> /* Copy fields */
> - rule7->_pad = tmp->_pad;
> + rule7->_pad = 0;
> rule7->set = tmp->set;
> rule7->rulenum = tmp->rulenum;
> rule7->cmd_len = tmp->cmd_len;
> @@ -1423,7 +1488,7 @@ convert_rule_to_8(struct ip_fw *rule)
> }
> }
>
> - rule->_pad = tmp->_pad;
> + rule->flags = 0;
> rule->set = tmp->set;
> rule->rulenum = tmp->rulenum;
> rule->cmd_len = tmp->cmd_len;
> --- /dev/null 2013-04-24 17:20:19.000000000 +0400
> +++ sys/netpfil/ipfw/ip_fw_rewrite.c 2013-04-24 17:19:15.278097243 +0400
> @@ -0,0 +1,835 @@
> +/*-
> + * Copyright (c) 2013 Yandex LLC.
> + * Author: Alexander V. Chernikov <melifaro at yandex-team.ru>
> + *
> + * 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$");
> +
> +/*
> + * Rule opcode rewriting system for ipfw.
> + * System permits automatic algoritmic (stateless) or statefull (
> + * requiring access/monidifcation to external data) of opcodes.
> + * Modification is done by calling special per-opcode dependent
> + * callbacks. Saving unmodified user-supplied rules, size recalculation,
> + * rule export and relocation is handled by subsystem.
> + * Writing opcode modificator requires adding it to rewrites[] array
> + * and filling appropriate callbacks (at least 'convert' one.
> + */
> +
> +#include "opt_ipfw.h"
> +
> +#include <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/malloc.h>
> +#include <sys/kernel.h>
> +#include <sys/lock.h>
> +#include <sys/rwlock.h>
> +#include <sys/fnv_hash.h>
> +#include <sys/socket.h>
> +#include <net/if.h>
> +
> +#include <netinet/in.h>
> +#include <netinet/ip_var.h> /* hooks */
> +#include <netinet/ip_fw.h>
> +
> +#include <netpfil/ipfw/ip_fw_private.h>
> +#include <netpfil/ipfw/ip_fw_iface.h>
> +
> +#define NO_REWRITE 0
> +#define STATELESS_REWRITE 1
> +#define STATEFUL_REWRITE 2
> +
> +struct ip_fw_rewrite {
> + uint32_t opcode;
> +
> + /*
> + * Checks if given opcode needs to be changed. Called (indirectly)
> + * from check_ipfw_struct() without holding any locks. Fuction should
> + * quickly check if given opcode needs to be rewritten and set @len to
> + * size difference (in bytes) between new (altered) opcode size and
> + * old one. Note that &len hould be aligned to u32.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @insn - given ipfw_instn
> + * @len - pointer to length diff (in insns)
> + *
> + * Returns:
> + * NO_REWRITE - no need to convert
> + * STATELESS_REWRITE - (algoritmic) conversion required.
> + * STATEFUL_REWRITE - stateful conversion required.
> + *
> + * Callback is OPTIONAL, defaults to STATELESS_REWRITE if not set.
> + */
> + int (*check)(struct ip_fw_chain *, ipfw_insn *, int *);
> +
> + /*
> + * Prepares state for given opcode if needed. Called without
> + * holding any locks permitting to allocate any amount of memory.
> + * Note that result (and actual state usage) has to be consistent
> + * with *check (and other) callbacks.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @insn - given ipfw_instn
> + * @pstate - pointer to pointer to state
> + *
> + * Returns:
> + * NO_REWRITE - no need to convert
> + * STATELESS_REWRITE - (algoritmic) conversion can be done.
> + * STATEFUL_REWRITE - stateful conversion required, state is saved to
> + * given pointer.
> + *
> + * Callback is OPTIONAL, defaults to STATELESS_REWRITE if not set.
> + */
> + int (*prepare)(struct ip_fw_chain *, ipfw_insn *insn, void **);
> +
> + /*
> + * Performs opcode conversion. Called with chain WLOCK held.
> + * Note that opcode copy is handled automatically if
> + * NO_REWRITE is returned. @len has to be filled otherwise.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @_old - userland ipfw_instn
> + * @_new - kernel ipfw_insn
> + * @state - pointer to state saved
> + * @len - pointer to opcode length (in instructions)
> + *
> + * Returns:
> + * NO_REWRITE - no need to convert
> + * STATELESS_REWRITE - (algoritmic) conversion is done.
> + * STATEFUL_REWRITE - stateful conversion is done, state is consumed.
> + *
> + * Callback is MANDATORY.
> + */
> + int (*convert)(struct ip_fw_chain *, ipfw_insn *, ipfw_insn *, void *, int *);
> +
> + /*
> + * Performs state cleanup (rule deletion). Called with chain WLOCK held.
> + * State hint can be provided.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @insn - kernel ipfw_insn
> + * @state - pointer to state hint
> + *
> + * Callback is OPTIONAL.
> + */
> + void (*clear)(struct ip_fw_chain *, ipfw_insn *, void *);
> +
> + /*
> + * Performs opcode-dependent update.
> + * Flag/argument can be provided.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @insn - kernel ipfw_insn
> + * @state - pointer to opcode-dependent data
> + * @val - opcode-dependet value
> + *
> + * Callback is OPTIONAL.
> + */
> + void (*update)(struct ip_fw_chain *, ipfw_insn *, void *, uintptr_t);
> +
> + /*
> + * Dispatches memory relocation of given opcode, Called with WLOCK held.
> + * Actual copy is already done at the moment of call.
> + *
> + * Params:
> + * @chain - pointer to current ifpw chain
> + * @_old - kernel ipfw_insn
> + * @_old - new kernel ipfw_insn
> + *
> + * Callback is OPTIONAL.
> + */
> + void (*move)(struct ip_fw_chain *, ipfw_insn *, ipfw_insn *);
> +};
> +
> +/* Opcode callbacks */
> +static int
> +convertable_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn);
> +
> +static void move_insn_if(struct ip_fw_chain *chain, ipfw_insn *_old, ipfw_insn *_new);
> +static void update_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn,
> + void *_iface_mask, uintptr_t new_id);
> +static void clear_insn_if(struct ip_fw_chain *chain, ipfw_insn *_src, void *data);
> +static int convert_insn_if(struct ip_fw_chain *chain, ipfw_insn *_old, ipfw_insn *_new,
> + void *state, int *len);
> +static int prepare_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn, void **pstate);
> +static int check_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn, int *len);
> +
> +
> +/* Note opcodes MUST be in asceding order */
> +struct ip_fw_rewrite rewrites[] = {
> + {
> + O_RECV,
> + check_insn_if,
> + prepare_insn_if,
> + convert_insn_if,
> + clear_insn_if,
> + update_insn_if,
> + move_insn_if,
> + },
> + {
> + O_XMIT,
> + check_insn_if,
> + prepare_insn_if,
> + convert_insn_if,
> + clear_insn_if,
> + update_insn_if,
> + move_insn_if,
> + },
> + {
> + O_VIA,
> + check_insn_if,
> + prepare_insn_if,
> + convert_insn_if,
> + clear_insn_if,
> + update_insn_if,
> + move_insn_if,
> + },
> +};
> +
> +struct rewrite_rule_ptr {
> + TAILQ_ENTRY(rewrite_rule_ptr) next;
> + int cmd_klen; /* Kernel opcodes len (insns) */
> + int cmd_len; /* Original opcodes len (insns) */
> + ipfw_insn *kcmd; /* Kernel rule version */
> + void **states; /* opcode states */
> + int states_count; /* number of states */
> + ipfw_insn cmd[1]; /* Original opcodes */
> +};
> +TAILQ_HEAD(rewrite_rule_head, rewrite_rule_ptr);
> +
> +struct ip_fw_rw_data {
> + struct rewrite_rule_head *hash;
> + size_t hsize;
> + int lendiff; /* sizeof(kern) - sizeof(user) */
> +};
> +
> +#define DEFAULT_HASH_SIZE 32
> +#define PTR_HASH_PRIME 31
> +
> +static struct ip_fw_rewrite *ipfw_find_rewrite(uint32_t opcode);
> +
> +void
> +ipfw_rewrite_init(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_rw_data *rwd;
> + struct rewrite_rule_head *rh;
> + int i;
> +
> + rwd = malloc(sizeof(struct ip_fw_rw_data), M_IPFW, M_WAITOK | M_ZERO);
> +
> + rwd->hsize = DEFAULT_HASH_SIZE;
> + rwd->hash = malloc(sizeof(struct rewrite_rule_head) * rwd->hsize,
> + M_IPFW, M_WAITOK | M_ZERO);
> +
> + for (i = 0, rh = rwd->hash; i < rwd->hsize; i++, rh++)
> + TAILQ_INIT(rh);
> +
> + chain->rewrite_data = rwd;
> +}
> +
> +void
> +ipfw_rewrite_free(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_rw_data *rwd;
> +
> + rwd = chain->rewrite_data;
> + chain->rewrite_data = NULL;
> +
> + /* Assume every rule to be already removed */
> + free(rwd->hash, M_IPFW);
> + free(rwd, M_IPFW);
> +}
> +
> +int
> +ipfw_rewrite_len(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_rw_data *rwd;
> +
> + rwd = chain->rewrite_data;
> +
> + return (rwd->lendiff);
> +}
> +
> +/*
> + * Prepares given rule for modification:
> + * allocates memory for rule and number of states reported
> + * by 'check' callbacks. Calls 'prepare' callback for
> + * every opcode in rule.
> + *
> + * Returns state to be passed to ipfw_store_rule.
> + */
> +void *
> +ipfw_prepare_rewrite(struct ip_fw_chain *chain, ipfw_insn *ucmd,
> + int cmd_len, struct ip_fw_rw_info *rwi)
> +{
> + int i, l, cmdlen, size, states_count;
> + struct rewrite_rule_ptr *rptr;
> + ipfw_insn *cmd;
> + struct ip_fw_rewrite *rewrite;
> + void **pstate;
> +
> + /*
> + * Allocate memory for rule header, opcodes and state array.
> + */
> + size = sizeof(struct rewrite_rule_ptr) +
> + (cmd_len - 1) * sizeof(uint32_t);
> +
> + size = roundup(size, sizeof(void *));
> +
> + rptr = malloc(size + rwi->states * sizeof(void *), M_IPFW,
> + M_WAITOK | M_ZERO);
> +
> + /* Save original opcodes */
> + memcpy(rptr->cmd, ucmd, cmd_len * sizeof(uint32_t));
> + rptr->cmd_len = cmd_len;
> + rptr->cmd_klen = rptr->cmd_len + rwi->lendiff;
> +
> + rptr->states = (void **)((char *)rptr + size);
> + rptr->states_count = rwi->states;
> + pstate = rptr->states;
> + states_count = rptr->states_count;
> +
> + CTR4(KTR_NET, "Prepare rule rewrite: cmd %p len %d klen %d rptr %p",
> + ucmd, rptr->cmd_len, rptr->cmd_klen, rptr);
> +
> + for (l = cmd_len, cmd = ucmd ;
> + l > 0 ; l -= cmdlen, cmd += cmdlen) {
> + cmdlen = F_LEN(cmd);
> +
> + if ((rewrite = ipfw_find_rewrite(cmd->opcode)) == NULL)
> + continue;
> +
> + if (rewrite->prepare == NULL)
> + continue;
> +
> + i = rewrite->prepare(chain, cmd, pstate);
> +
> + if (i == STATEFUL_REWRITE) {
> + CTR3(KTR_NET, "New stateful rewrite %p val %p count %d",
> + pstate, *pstate, states_count);
> + pstate++;
> + states_count--;
> +
> + KASSERT(states_count >= 0,
> + ("prepare_rewrite state overflow"));
> + }
> + }
> +
> + return ((void *)rptr);
> +}
> +
> +static int
> +hash_ptr(struct ip_fw_rw_data *rwd, ipfw_insn *cmd)
> +{
> + return (uintptr_t)cmd % PTR_HASH_PRIME;
> +}
> +
> +/*
> + * Fills in kernel rule with modified opcodes. Updates old rule state
> + * with new kernel pointer. Actual rewriting and header copy is done
> + * in ipfw_run_rewrite().
> + */
> +void
> +ipfw_perform_rewrite(struct ip_fw_chain *chain, ipfw_insn *kcmd, void *state)
> +{
> + struct rewrite_rule_ptr *rptr;
> + struct rewrite_rule_head *rh;
> + struct ip_fw_rw_data *rwd;
> + struct ip_fw_rewrite *rewrite;
> + ipfw_insn *ucmd;
> + void **pstate;
> + int i, l, ucmdlen, kcmdlen, states_count;
> +
> + rwd = chain->rewrite_data;
> +
> + rptr = (struct rewrite_rule_ptr *)state;
> + rptr->kcmd = kcmd;
> + pstate = rptr->states;
> + states_count = rptr->states_count;
> +
> + CTR3(KTR_NET, "Linking kcmd %p to orig %p idx %d",
> + kcmd, rptr, hash_ptr(rwd, kcmd));
> +
> + rh = &rwd->hash[hash_ptr(rwd, kcmd)];
> + TAILQ_INSERT_TAIL(rh, rptr, next);
> +
> + ucmd = rptr->cmd;
> +
> + for (l = rptr->cmd_len; l > 0 ;
> + l -= ucmdlen, ucmd += ucmdlen, kcmd += kcmdlen) {
> + ucmdlen = F_LEN(ucmd);
> +
> + if ((rewrite = ipfw_find_rewrite(ucmd->opcode)) == NULL) {
> + /* No conversion required, copy as is */
> + kcmdlen = ucmdlen;
> + memcpy(kcmd, ucmd, ucmdlen * sizeof(ipfw_insn));
> + continue;
> + }
> +
> + i = rewrite->convert(chain, ucmd, kcmd, *pstate, &kcmdlen);
> + CTR2("RW for %d st %p returned %d", ucmd->cmd, *pstate, i);
> +
> + if (i == NO_REWRITE) {
> + kcmdlen = ucmdlen;
> + memcpy(kcmd, ucmd, ucmdlen * sizeof(ipfw_insn));
> + } else if (i == STATEFUL_REWRITE) {
> + pstate++;
> + states_count--;
> +
> + KASSERT(states_count >= 0, ("rewrite state overflow"));
> + }
> + }
> +
> + /* Save size difference */
> + rwd->lendiff += rptr->cmd_klen - rptr->cmd_len;
> + CTR2(KTR_NET, "old len: %d, new: %d", rptr->cmd_len, rptr->cmd_klen);
> +}
> +
> +/*
> + * Handle rule moving to new place (or deletion).
> + * Updates kernel rule pointer and run opcode callbacks via
> + * ipfw_move_rewrite() or clears state via ipfw_clear_rewrite()
> + * int latter case.
> + */
> +void
> +ipfw_relocate_rewrite(struct ip_fw_chain *chain, ipfw_insn *old, ipfw_insn *new)
> +{
> + struct rewrite_rule_ptr *rptr;
> + struct rewrite_rule_head *rh;
> + struct ip_fw_rw_data *rwd;
> + struct ip_fw_rewrite *rewrite;
> + int l, cmdlen;
> +
> + rwd = chain->rewrite_data;
> +
> + rh = &rwd->hash[hash_ptr(rwd, old)];
> +
> + TAILQ_FOREACH(rptr, rh, next) {
> + if (rptr->kcmd == old)
> + break;
> + }
> +
> + CTR3(KTR_NET, "Moving %p idx %p to %p", rptr, hash_ptr(rwd, old), new);
> +
> + KASSERT(rptr != NULL, ("ipfw_relocate_rewrite: old rule not found"));
> +
> + TAILQ_REMOVE(rh, rptr, next);
> +
> + if (new == NULL) {
> + /* Clear states (if any) and delete original rule */
> + for (l = rptr->cmd_klen; l > 0; l -= cmdlen, old += cmdlen) {
> + cmdlen = F_LEN(old);
> +
> + if ((rewrite = ipfw_find_rewrite(old->opcode)) == NULL)
> + continue;
> +
> + if (rewrite->clear == NULL)
> + continue;
> +
> + CTR1(KTR_NET, "clear-state for opcode %u", old->opcode);
> + rewrite->clear(chain, old, NULL);
> + }
> +
> + /* Update size difference */
> + rwd->lendiff -= rptr->cmd_klen - rptr->cmd_len;
> + free(rptr, M_IPFW);
> + } else {
> + /* Put to new slot */
> + rptr->kcmd = new;
> + rh = &rwd->hash[hash_ptr(rwd, new)];
> + TAILQ_INSERT_TAIL(rh, rptr, next);
> +
> + /* Update instructions pointers */
> + for (l = rptr->cmd_klen; l > 0 ;
> + l -= cmdlen, old += cmdlen, new += cmdlen) {
> + cmdlen = F_LEN(old);
> +
> + if ((rewrite = ipfw_find_rewrite(old->opcode)) == NULL)
> + continue;
> +
> + if (rewrite->move == NULL)
> + continue;
> +
> + rewrite->move(chain, old, new);
> + }
> + }
> +}
> +
> +/*
> + * Exports modified rule to userland. Returns userland rule length
> + * (used in initial size-checking calculations). Copies userland rule version
> + * with updated counters to supplied buffer.
> + */
> +int
> +ipfw_export_rewrite(struct ip_fw_chain *chain, ipfw_insn *kcmd, ipfw_insn *target)
> +{
> + struct rewrite_rule_ptr *rptr;
> + struct rewrite_rule_head *rh;
> + struct ip_fw_rw_data *rwd;
> + ipfw_insn *ucmd;
> +
> + rwd = chain->rewrite_data;
> +
> + KASSERT(rw != NULL, ("ipfw_export_rewrite: rewrite not initialized"));
> +
> + rh = &rwd->hash[hash_ptr(rwd, kcmd)];
> +
> + TAILQ_FOREACH(rptr, rh, next) {
> + if (rptr->kcmd == kcmd)
> + break;
> + }
> +
> + KASSERT(rptr != NULL, ("ipfw_export_rewrite: kcmd not found"));
> + ucmd = rptr->cmd;
> +
> + if (target != NULL)
> + memcpy(target, rptr->cmd, rptr->cmd_len * sizeof(uint32_t));
> +
> + return (rptr->cmd_len * sizeof(uint32_t));
> +}
> +
> +/*
> + * bsearch() helper function.
> + */
> +static int
> +rewrite_comp(const void *_key, const void *_member)
> +{
> + uint32_t opcode;
> + struct ip_fw_rewrite *rewrite;
> +
> + opcode = *((uint32_t *)_key);
> + rewrite = (struct ip_fw_rewrite *)_member;
> +
> + if (opcode < rewrite->opcode)
> + return (-1);
> + else if (opcode == rewrite->opcode)
> + return (0);
> + else
> + return (1);
> +}
> +
> +
> +static struct ip_fw_rewrite *
> +ipfw_find_rewrite(uint32_t opcode)
> +{
> + size_t count;
> + struct ip_fw_rewrite *rewrite;
> +
> + count = sizeof(rewrites) / sizeof(struct ip_fw_rewrite);
> +
> + rewrite = (struct ip_fw_rewrite *)bsearch(&opcode, rewrites,
> + count, sizeof(struct ip_fw_rewrite), rewrite_comp);
> +
> + return (rewrite);
> +}
> +
> +
> +/*
> + * Checks if given opcode needs to be changed.
> + * Updates @rwi appropriate fields if instruction needs to be
> + * stateless/stafully rewritten possibly with changed size.
> + */
> +void
> +ipfw_check_rewrite(struct ip_fw_chain *chain, ipfw_insn *insn,
> + struct ip_fw_rw_info *rwi)
> +{
> + struct ip_fw_rewrite *rewrite;
> + int i = 0, len = 0;
> +
> + if ((rewrite = ipfw_find_rewrite(insn->opcode)) == NULL)
> + i = NO_REWRITE;
> + else if (rewrite->check == NULL)
> + i = STATELESS_REWRITE;
> + else
> + i = rewrite->check(chain, insn, &len);
> +
> + if (len != 0)
> + rwi->lendiff += len;
> +
> + if (i == STATELESS_REWRITE)
> + rwi->count++;
> +
> + if (i == STATEFUL_REWRITE) {
> + rwi->count++;
> + rwi->states++;
> + }
> +
> + if (i != NO_REWRITE)
> + CTR4(KTR_NET, "opcode %d: count=%d states=%d len=%d",
> + insn->opcode, rwi->count, rwi->states, rwi->lendiff);
> +}
> +
> +/*
> + * Call opcode-dependent 'update' callback.
> + */
> +void
> +ipfw_update_rewrite(struct ip_fw_chain *chain, ipfw_insn *insn,
> + void *state, uintptr_t val)
> +{
> + struct ip_fw_rewrite *rewrite;
> +
> + if ((rewrite = ipfw_find_rewrite(insn->opcode)) == NULL)
> + return;
> +
> + if (rewrite->update == NULL)
> + return;
> +
> + rewrite->update(chain, insn, state, val);
> +}
> +
> +/*******************************************************************
> + * *
> + * O_RECV | O_VIA | O_XMIT rewrite handling. *
> + * *
> + *******************************************************************/
> +/*
> + * Converts insns_if to more compact form. Currently instruction
> + * is used to specify
> + * 1) interface name ( ->name[0] != ('\0' | '\1') AND p.glob == 0)
> + * 2) interface pattern ( ->name[0] != ('\0' | '\1') AND p.glob != 0)
> + * 3) eXtended table number ( ->name[0] == '\1')
> + * 4) interface address ( ->name[0] == '\0')
> + *
> + * We want to save iface index in case 1 (and to eliminate interface name at all).
> + * Given that, we do the following:
> + *
> + * p.glob is now p.if_idx (u_int) (glob if zero, iface index otherwise)
> + * o.arg1 works like ->name[0], so:
> + *
> + * 1) interface name (o.arg1 == 2, p.if_idx contains index)
> + * 2) interface pattern (o.arg1 == 2, p.if_idx == 0)
> + * 3) eXtended table number (o.arg1 == 1)
> + * 4) interface address (o.arg1 == 0)
> + */
> +
> +static int
> +convertable_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn)
> +{
> + ipfw_insn_if *cmd = (ipfw_insn_if *)insn;
> +
> + /* Either IPv4 address or extended table (3) and (4) */
> + if (cmd->name[0] == '\0' || cmd->name[0] == '\1')
> + return (0);
> +
> + return (1);
> +}
> +
> +static int
> +check_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn, int *len_diff)
> +{
> + ipfw_insn_if *cmd = (ipfw_insn_if *)insn;
> +
> + *len_diff = 0;
> +
> + if (convertable_insn_if(chain, insn) == 0)
> + return (STATELESS_REWRITE);
> +
> + /* Either interface name (1) or glob pattern (2). */
> +
> + if (cmd->p.glob != 0)
> + return (STATELESS_REWRITE);
> + else
> + return (STATEFUL_REWRITE);
> +}
> +
> +static int
> +prepare_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn, void **pstate)
> +{
> + struct iface_mask *ifm;
> + struct ipfw_insn_ptr *insn_ptr;
> + ipfw_insn_if *cmd = (ipfw_insn_if *)insn;
> +
> + if (convertable_insn_if(chain, insn) == 0)
> + return (STATELESS_REWRITE);
> +
> + if (cmd->p.glob != 0) {
> + /* Glob pattern (2), no state needed, */
> + return (STATELESS_REWRITE);
> + }
> +
> + /* Allocate data used by convert callback */
> + insn_ptr = malloc(sizeof(struct ipfw_insn_ptr), M_IPFW,
> + M_WAITOK | M_ZERO);
> + ifm = malloc(sizeof(struct iface_mask), M_IPFW, M_WAITOK | M_ZERO);
> +
> + TAILQ_INIT(&ifm->instructions);
> + TAILQ_INSERT_TAIL(&ifm->instructions, insn_ptr, next);
> +
> + CTR3(KTR_NET, "pstate %p, val %p insns %p", pstate, ifm, insn);
> +
> + *pstate = ifm;
> + return (STATEFUL_REWRITE);
> +}
> +
> +static int
> +convert_insn_if(struct ip_fw_chain *chain, ipfw_insn *_old, ipfw_insn *_new,
> + void *state, int *len)
> +{
> + struct iface_mask *ifm, *ifm2;
> + struct ipfw_insn_ptr *insn_ptr;
> + ipfw_insn_if *cmd_old = (ipfw_insn_if *)_old;
> + ipfw_insn_if *cmd_new = (ipfw_insn_if *)_new;
> +
> + /* Set length anyway */
> + *len = F_INSN_SIZE(ipfw_insn_if);
> + memcpy(cmd_new, cmd_old, sizeof(ipfw_insn_if));
> +
> + if (convertable_insn_if(chain, _old) == 0) {
> + /*
> + * case (3, eX table): o.arg1 = 1
> + * case (4, ifaddr): o.arg1 = 0
> + */
> +
> + cmd_new->o.arg1 = (cmd_old->name[0] == '\1') ? 1 : 0;
> +
> + return (STATELESS_REWRITE);
> + }
> +
> + /*
> + * Prepare instruction for altering.
> + * case (1, ifname): o.arg1 = 2; p_if_idx == interface index
> + * case (2, glob): o.arg1 = 2' p.if_idx = 0
> + */
> + memcpy(cmd_new, cmd_old, sizeof(ipfw_insn_if));
> + cmd_new->o.arg1 = 2;
> +
> + if (cmd_old->p.glob) {
> + /* Interface mask (2). Copy as is and set index */
> + cmd_new->p.if_idx = 0;
> + return (STATELESS_REWRITE);
> + }
> +
> + /* Interface name. */
> + ifm = (struct iface_mask *)state;
> + insn_ptr = TAILQ_FIRST(&ifm->instructions);
> +
> + insn_ptr->insn = _new;
> +
> + if ((ifm2 = ipfw_search_ifname(chain, cmd_old->name)) != NULL) {
> + /* Interface found, link entry here */
> + TAILQ_INSERT_TAIL(&ifm2->instructions, insn_ptr, next);
> + ifm2->refcount++;
> + cmd_new->p.if_idx = ifm2->idx;
> + if (ifm2->flags & IPFW_IFLAG_FAKE)
> + cmd_new->p.if_idx |= IPFW_FAKE_IDX;
> +
> + free(ifm, M_IPFW);
> + return (STATEFUL_REWRITE);
> + }
> +
> + /* Interface not found, add and mark as unexistent */
> + strlcpy(ifm->name, cmd_old->name, IFNAMSIZ);
> + ifm->flags |= IPFW_IFLAG_FAKE;
> + ifm->refcount++;
> + ipfw_add_ifname(chain, ifm);
> + cmd_new->p.if_idx = ifm->idx | IPFW_FAKE_IDX;
> + /* Add instruction back (add_ifname reinits list) */
> + TAILQ_INSERT_TAIL(&ifm->instructions, insn_ptr, next);
> +
> + return (STATEFUL_REWRITE);
> +}
> +
> +static void
> +clear_insn_if(struct ip_fw_chain *chain, ipfw_insn *_src, void *data)
> +{
> + struct iface_mask *ifm;
> + ipfw_insn_if *cmd;
> + struct ipfw_insn_ptr *insn_ptr = (struct ipfw_insn_ptr *)data;
> +
> + cmd = (ipfw_insn_if *)_src;
> +
> + /* State is used for interface names, skip other cases */
> + if (cmd->o.arg1 != 2)
> + return;
> +
> + ifm = ipfw_search_ifindex(chain, cmd->p.if_idx);
> + KASSERT(ifm != NULL, ("no ifp found for index %u", cmd->p.if_idx));
> +
> + if (insn_ptr == NULL) {
> + TAILQ_FOREACH(insn_ptr, &ifm->instructions, next) {
> + if (insn_ptr->insn == _src)
> + break;
> + }
> +
> + KASSERT(insn_ptr != NULL, ("no insns found"));
> + }
> +
> + /* Remove instruction from interface */
> + TAILQ_REMOVE(&ifm->instructions, insn_ptr, next);
> + ifm->refcount--;
> +
> + free(insn_ptr, M_IPFW);
> +}
> +
> +static void
> +update_insn_if(struct ip_fw_chain *chain, ipfw_insn *insn, void *_iface_mask,
> + uintptr_t new_id)
> +{
> + struct ip_fw_if_data *ifd;
> + ipfw_insn_if *cmd;
> +
> + IPFW_WLOCK_ASSERT(chain);
> +
> + ifd = chain->if_data;
> + cmd = (ipfw_insn_if *)insn;
> +
> + CTR2(KTR_NET, "updating insn: ifi %u -> %u",
> + cmd->p.if_idx, (uint32_t)new_id);
> +
> + cmd->p.if_idx = (uint32_t)new_id;
> +}
> +
> +static void
> +move_insn_if(struct ip_fw_chain *chain, ipfw_insn *_old, ipfw_insn *_new)
> +{
> + struct iface_mask *ifm;
> + ipfw_insn_if *cmd;
> + struct ipfw_insn_ptr *insn_ptr;
> +
> + cmd = (ipfw_insn_if *)_old;
> +
> + /* State is used for interface names, skip other cases */
> + if (cmd->o.arg1 != 2)
> + return;
> +
> + ifm = ipfw_search_ifindex(chain, cmd->p.if_idx);
> + KASSERT(ifm != NULL, ("no ifp found for index %u", cmd->p.if_idx));
> +
> + TAILQ_FOREACH(insn_ptr, &ifm->instructions, next) {
> + if (insn_ptr->insn == _old)
> + break;
> + }
> +
> + KASSERT(insn_ptr != NULL, ("no insns found"));
> +
> + insn_ptr->insn = _new;
> +}
> +
> +
> --- /dev/null 2013-04-24 17:20:19.000000000 +0400
> +++ sys/netpfil/ipfw/ip_fw_iface.c 2013-04-24 17:18:35.546357594 +0400
> @@ -0,0 +1,467 @@
> +/*-
> + * Copyright (c) 2013 Yandex LLC.
> + * Author: Alexander V. Chernikov <melifaro at yandex-team.ru>
> + *
> + * 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$");
> +
> +/*
> + * Interface tracking for ipfw.
> + */
> +
> +#include "opt_ipfw.h"
> +
> +#include <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/malloc.h>
> +#include <sys/kernel.h>
> +#include <sys/lock.h>
> +#include <sys/rwlock.h>
> +#include <sys/fnv_hash.h>
> +#include <sys/socket.h>
> +#include <net/if.h>
> +#include <net/vnet.h>
> +
> +#include <netinet/in.h>
> +#include <netinet/ip_var.h> /* hooks */
> +#include <netinet/ip_fw.h>
> +
> +#include <netpfil/ipfw/ip_fw_private.h>
> +#include <netpfil/ipfw/ip_fw_iface.h>
> +
> +#define IPFW_IFHASH_IDX(idx, hsize) ((idx) % (hsize))
> +#define IPFW_IFHASH_NAME(name, hsize) (fnv_32_str(name, FNV1_32_INIT) % (hsize))
> +
> +TAILQ_HEAD(iface_mask_head, iface_mask);
> +
> +struct ip_fw_if_data {
> + struct iface_mask_head *masks; /* Interface name hash */
> + size_t masks_count, masks_hsize;
> + struct iface_mask_head *real_ifaces; /* 'Real' interface index hash */
> + size_t real_count, real_hsize;
> + struct iface_mask_head *fake_ifaces; /* Nonexistent interface index hash */
> + size_t fake_count, fake_hsize;
> + eventhandler_tag arrival, departure;
> + u_short fake_idx;
> +};
> +
> +static void ipfw_ifhash_init_int(struct iface_mask_head **phash, size_t hsize);
> +static void ipfw_ifnet_init(struct ip_fw_chain *chain, struct iface_mask *ifm);
> +
> +/*
> + * Mappings:
> + * 'iface_mask' -> idx
> + * if_index -> iface_mask
> + * fake_index -> iface_mask
> + *
> + * List of masks
> + *
> + */
> +static void ifnet_arrival(void *arg, struct ifnet *ifp);
> +static void ifnet_departure(void *arg, struct ifnet *ifp);
> +
> +/*
> + * Find interface structure by name.
> + * Called with either UH or chain readlock held.
> + */
> +struct iface_mask *
> +ipfw_search_ifname(struct ip_fw_chain *chain, char *name)
> +{
> + struct iface_mask *ifm;
> + struct ip_fw_if_data *ifd;
> + struct iface_mask_head *ifh;
> + int i;
> +
> + ifd = chain->if_data;
> +
> + i = IPFW_IFHASH_NAME(name, ifd->masks_hsize);
> +
> + ifh = &ifd->masks[i];
> + TAILQ_FOREACH(ifm, ifh, name_next) {
> + if (strcmp(name, ifm->name) == 0)
> + return (ifm);
> + }
> +
> + return (NULL);
> +}
> +
> +/*
> + * Find interface structure by real or fake ifindex.
> + * Called with either UH or chain readlock held.
> + */
> +struct iface_mask *
> +ipfw_search_ifindex(struct ip_fw_chain *chain, uint32_t idx)
> +{
> + struct iface_mask *ifm;
> + struct ip_fw_if_data *ifd;
> + struct iface_mask_head *ifh;
> + int i;
> +
> + ifd = chain->if_data;
> +
> + if (idx & IPFW_FAKE_IDX) {
> + idx &= ~IPFW_FAKE_IDX;
> + i = IPFW_IFHASH_IDX(idx, ifd->fake_hsize);
> + ifh = &ifd->fake_ifaces[i];
> + } else {
> + i = IPFW_IFHASH_IDX(idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> + }
> +
> + TAILQ_FOREACH(ifm, ifh, idx_next) {
> + if (ifm->idx == idx)
> + return (ifm);
> + }
> +
> + return (NULL);
> +}
> +
> +void
> +ipfw_add_ifname(struct ip_fw_chain *chain, struct iface_mask *ifm)
> +{
> + struct ip_fw_if_data *ifd;
> + struct iface_mask_head *ifh;
> + struct iface_mask *iftemp;
> + int i;
> +
> + ifd = chain->if_data;
> +
> + ipfw_ifnet_init(chain, ifm);
> +
> + /* Add to named hash */
> + i = IPFW_IFHASH_NAME(ifm->name, ifd->masks_hsize);
> + ifh = &ifd->masks[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, name_next);
> +
> + if (ifm->flags & IPFW_IFLAG_FAKE) {
> + /* Add to fake interfaces hash */
> + ifm->idx = ++ifd->fake_idx;
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->fake_hsize);
> + ifh = &ifd->fake_ifaces[i];
> + } else {
> + /* Add to real interfaces hash */
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> +
> + /* Check index for consistency */
> + TAILQ_FOREACH(iftemp, ifh, idx_next) {
> + KASSERT(iftemp->idx != ifm->idx,
> + ("Non-fake if %s w idx %d found (%s)!",
> + iftemp->name, ifm->idx, ifm->name));
> + }
> + }
> +
> + TAILQ_INSERT_TAIL(ifh, ifm, idx_next);
> +}
> +
> +static void
> +ifnet_arrival(void *arg, struct ifnet *ifp)
> +{
> + struct ip_fw_chain *chain = (struct ip_fw_chain *)arg;
> + struct ip_fw_if_data *ifd;
> + struct iface_mask *iftemp, *ifm;
> + struct iface_mask_head *ifh;
> + struct ipfw_insn_ptr *insn_ptr;
> + int i;
> +
> + iftemp = malloc(sizeof(struct iface_mask), M_IPFW, M_WAITOK | M_ZERO);
> +
> + iftemp->ifp = ifp;
> + iftemp->idx = ifp->if_index;
> + strlcpy(iftemp->name, ifp->if_xname, IFNAMSIZ);
> +
> + IPFW_UH_WLOCK(chain);
> + IPFW_WLOCK(chain);
> +
> + ifd = chain->if_data;
> +
> + if (ifd == NULL || ifd->arrival == NULL) {
> + /* We're shutting down */
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> + free(iftemp, M_IPFW);
> + return;
> + }
> +
> + ifm = ipfw_search_ifname(chain, iftemp->name);
> +
> + if (ifm != NULL) {
> + /* Found. Let's update index */
> + KASSERT(ifm->flags & IPFW_IFLAG_FAKE,
> + ("Non-fake interface found for %s", ifm->name));
> +
> + ifm->flags &= ~IPFW_IFLAG_FAKE;
> + /* Relink to real index */
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->fake_hsize);
> + ifh = &ifd->fake_ifaces[i];
> + TAILQ_REMOVE(ifh, ifm, idx_next);
> +
> + i = IPFW_IFHASH_IDX(iftemp->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, idx_next);
> +
> + CTR2(KTR_NET, "ifnet upgrade: fake %u -> %u", ifm->idx,
> + iftemp->idx);
> + /* Notify consumers */
> + TAILQ_FOREACH(insn_ptr, &ifm->instructions, next)
> + ipfw_update_rewrite(chain, insn_ptr->insn, ifm,
> + (uintptr_t)iftemp->idx);
> +
> + ifm->idx = iftemp->idx;
> + } else {
> + /* Not found. Add to list */
> + ifm = iftemp;
> + iftemp = NULL;
> +
> + ipfw_ifnet_init(chain, ifm);
> +
> + CTR2(KTR_NET, "ifmp=%p uc=%u", ifm, ifm->refcount);
> +
> + /* Add to named hash */
> + i = IPFW_IFHASH_NAME(ifm->name, ifd->masks_hsize);
> + ifh = &ifd->masks[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, name_next);
> +
> + /* Add to real interfaces hash */
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> +
> + /* Check index for consistency */
> + TAILQ_FOREACH(iftemp, ifh, idx_next) {
> + KASSERT(iftemp->idx != ifm->idx,
> + ("Non-fake if %s w idx %d found (%s)!",
> + iftemp->name, ifm->idx, ifm->name));
> + }
> +
> + TAILQ_INSERT_TAIL(ifh, ifm, idx_next);
> +
> + CTR3(KTR_NET, "new iface %p, idx %u uc=%u", ifm->name,
> + ifm->idx, ifm->refcount);
> + }
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> +
> + if (iftemp != NULL)
> + free(iftemp, M_IPFW);
> +}
> +
> +static void
> +ifnet_departure(void *arg, struct ifnet *ifp)
> +{
> + struct ip_fw_chain *chain = (struct ip_fw_chain *)arg;
> + struct ip_fw_if_data *ifd;
> + struct iface_mask *ifm;
> + struct iface_mask_head *ifh;
> + struct ipfw_insn_ptr *insn_ptr;
> + int i;
> +
> + IPFW_UH_WLOCK(chain);
> + IPFW_WLOCK(chain);
> +
> + if ((ifd = chain->if_data) == NULL) {
> + /* We're shutting down */
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> + return;
> + }
> +
> + ifm = ipfw_search_ifname(chain, ifp->if_xname);
> +
> + if (ifm == NULL) {
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> + printf("ipfw: unknown iface %s departure\n", ifp->if_xname);
> + return;
> + }
> +
> + KASSERT((ifm->flags & IPFW_IFLAG_FAKE) == 0,
> + ("Fake interface found for %s", ifm->name));
> +
> + /* Check if we need to save given interface. */
> + if (ifm->refcount == 0) {
> + CTR1(KTR_NET, "Deleting interface %p", ifm);
> + /* Delete from name hash */
> + i = IPFW_IFHASH_NAME(ifm->name, ifd->masks_hsize);
> + ifh = &ifd->masks[i];
> + TAILQ_REMOVE(ifh, ifm, name_next);
> +
> + /* Delete from real iface hash */
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> + TAILQ_REMOVE(ifh, ifm, idx_next);
> +
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> +
> + free(ifm, M_IPFW);
> + return;
> + }
> +
> + CTR1(KTR_NET, "Interface uc=%u", ifm->refcount);
> +
> + /* Interface is used. Move to fake hash */
> + ifm->flags |= IPFW_IFLAG_FAKE;
> + /* Relink to fake index */
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> + TAILQ_REMOVE(ifh, ifm, idx_next);
> +
> + /* Alloc fake index */
> + ifd->fake_idx++;
> + i = IPFW_IFHASH_IDX(ifd->fake_idx, ifd->fake_hsize);
> + ifh = &ifd->fake_ifaces[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, idx_next);
> +
> + CTR2(KTR_NET, "Interface %p departure, fake index %u",
> + ifm, ifd->fake_idx);
> +
> + /* Notify consumers */
> + TAILQ_FOREACH(insn_ptr, &ifm->instructions, next)
> + ipfw_update_rewrite(chain, insn_ptr->insn, ifm,
> + (uintptr_t)(ifd->fake_idx | IPFW_FAKE_IDX));
> +
> + ifm->idx = ifd->fake_idx;
> +
> + IPFW_WUNLOCK(chain);
> + IPFW_UH_WUNLOCK(chain);
> +}
> +
> +static void
> +ipfw_ifnet_init(struct ip_fw_chain *chain, struct iface_mask *ifm)
> +{
> +
> + TAILQ_INIT(&ifm->instructions);
> +}
> +
> +
> +static void
> +ipfw_ifhash_init_int(struct iface_mask_head **phash, size_t hsize)
> +{
> + struct iface_mask_head *ifh;
> + int i;
> +
> + ifh = malloc(sizeof(struct iface_mask_head) * hsize, M_IPFW,
> + M_WAITOK | M_ZERO);
> +
> + *phash = ifh;
> +
> + for (i = 0; i < hsize; i++, ifh++)
> + TAILQ_INIT(ifh);
> +}
> +
> +void
> +ipfw_ifhash_init(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_if_data *ifd;
> + struct iface_mask_head *ifh;
> + struct iface_mask *ifm;
> + struct ifnet *ifp;
> + int i;
> +
> + ifd = malloc(sizeof(struct ip_fw_if_data), M_IPFW, M_WAITOK | M_ZERO);
> + chain->if_data = ifd;
> +
> + ifd->masks_hsize = ifd->real_hsize = ifd->fake_hsize = 32;
> +
> + ipfw_ifhash_init_int(&ifd->masks, ifd->masks_hsize);
> + ipfw_ifhash_init_int(&ifd->real_ifaces, ifd->fake_hsize);
> + ipfw_ifhash_init_int(&ifd->fake_ifaces, ifd->real_hsize);
> +
> + IFNET_RLOCK();
> + TAILQ_FOREACH(ifp, &V_ifnet, if_link) {
> + ifm = malloc(sizeof(struct iface_mask), M_IPFW, M_WAITOK | M_ZERO);
> + strlcpy(ifm->name, ifp->if_xname, IFNAMSIZ);
> + ifm->ifp = ifp;
> + ifm->idx = ifp->if_index;
> +
> + ipfw_ifnet_init(chain, ifm);
> +
> + i = IPFW_IFHASH_IDX(ifm->idx, ifd->real_hsize);
> + ifh = &ifd->real_ifaces[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, idx_next);
> +
> + i = IPFW_IFHASH_NAME(ifm->name, ifd->masks_hsize);
> + ifh = &ifd->masks[i];
> + TAILQ_INSERT_TAIL(ifh, ifm, name_next);
> +
> + CTR2(KTR_NET, "init iface %p idx %u", ifm, ifm->idx);
> +
> + }
> + IFNET_RUNLOCK();
> +
> + /* XXX: there is a gap between RUNLOCK and interface registration */
> +
> + ifd->arrival = EVENTHANDLER_REGISTER(ifnet_arrival_event,
> + ifnet_arrival, chain, EVENTHANDLER_PRI_ANY);
> +
> + ifd->departure = EVENTHANDLER_REGISTER(ifnet_departure_event,
> + ifnet_departure, chain, EVENTHANDLER_PRI_ANY);
> +}
> +
> +void
> +ipfw_ifhash_detach(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_if_data *ifd;
> +
> + ifd = chain->if_data;
> +
> + EVENTHANDLER_DEREGISTER(ifnet_arrival_event, ifd->arrival);
> + EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifd->departure);
> +
> + ifd->arrival = NULL;
> + ifd->departure = NULL;
> +}
> +
> +
> +void
> +ipfw_ifhash_free(struct ip_fw_chain *chain)
> +{
> + struct ip_fw_if_data *ifd;
> + struct iface_mask_head *ifh;
> + struct iface_mask *ifm, *ifm_next;
> + int i;
> +
> + ifd = chain->if_data;
> + chain->if_data = NULL;
> +
> + ifh = ifd->masks;
> +
> + for (i = 0; i < ifd->masks_hsize; i++, ifh++) {
> + TAILQ_FOREACH_SAFE(ifm, ifh, name_next, ifm_next) {
> + /*
> + * Assume every consumer to free its
> + * iface-specific data beforehand.
> + */
> + free(ifm, M_IPFW);
> + }
> + }
> +
> + free(ifd->masks, M_IPFW);
> + free(ifd->real_ifaces, M_IPFW);
> + free(ifd->fake_ifaces, M_IPFW);
> +
> + free(ifd, M_IPFW);
> +}
> +
> --- /dev/null 2013-04-24 17:22:00.000000000 +0400
> +++ sys/netpfil/ipfw/ip_fw_iface.h 2013-04-22 19:09:56.624996491 +0400
> @@ -0,0 +1,55 @@
> +/*-
> + * Copyright (c) 2013 Yandex LLC.
> + *
> + * 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.
> + *
> + * $FreeBSD$
> + */
> +
> +#ifndef _IP_FW_IFACE_H_
> +#define _IP_FW_IFACE_H_
> +
> +struct ipfw_insn_ptr {
> + TAILQ_ENTRY(ipfw_insn_ptr) next;
> + ipfw_insn *insn;
> +};
> +
> +struct iface_mask {
> + struct ifnet *ifp;
> + uint32_t idx; /* Saved interface index */
> + uint32_t flags; /* Pad */
> + uint32_t refcount; /* Usage count */
> + char name[IFNAMSIZ]; /* Interface/mask */
> + TAILQ_ENTRY(iface_mask) idx_next;
> + TAILQ_ENTRY(iface_mask) name_next;
> + TAILQ_HEAD(rule_list, ipfw_insn_ptr) instructions; /* instructions using given mask */
> +};
> +#define IPFW_IFLAG_FAKE 0x01
> +
> +#define IPFW_FAKE_IDX (1 << 31)
> +
> +struct iface_mask *ipfw_search_ifname(struct ip_fw_chain *chain, char *name);
> +struct iface_mask *ipfw_search_ifindex(struct ip_fw_chain *chain, uint32_t idx);
> +void ipfw_add_ifname(struct ip_fw_chain *chain, struct iface_mask *ifm);
> +
> +#endif
> +
>
More information about the freebsd-ipfw
mailing list