root/sys/net/ipfw/ip_fw2.c
/*
 * Copyright (c) 2002 Luigi Rizzo, Universita` di Pisa
 *
 * 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: src/sys/netinet/ip_fw2.c,v 1.6.2.12 2003/04/08 10:42:32 maxim Exp $
 */

/*
 * Implement IP packet firewall (new version)
 */

#include "opt_ipfw.h"
#include "opt_inet.h"
#ifndef INET
#error IPFIREWALL requires INET.
#endif /* INET */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/ucred.h>
#include <sys/in_cksum.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/tree.h>

#include <net/if.h>
#include <net/route.h>
#include <net/pfil.h>
#include <net/dummynet/ip_dummynet.h>

#include <sys/thread2.h>
#include <net/netmsg2.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/in_pcb.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcpip.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/ip_divert.h>
#include <netinet/if_ether.h> /* XXX for ETHERTYPE_IP */

#include <net/ipfw/ip_fw2.h>

#ifdef IPFIREWALL_DEBUG
#define DPRINTF(fmt, ...) \
do { \
        if (fw_debug > 0) \
                kprintf(fmt, __VA_ARGS__); \
} while (0)
#else
#define DPRINTF(fmt, ...)       ((void)0)
#endif

/*
 * Description about per-CPU rule duplication:
 *
 * Module loading/unloading and all ioctl operations are serialized
 * by netisr0, so we don't have any ordering or locking problems.
 *
 * Following graph shows how operation on per-CPU rule list is
 * performed [2 CPU case]:
 *
 *   CPU0                 CPU1
 *
 * netisr0 <------------------------------------+
 *  domsg                                       |
 *    :                                         |
 *    :(delete/add...)                          |
 *    :                                         |
 *    :         netmsg                          | netmsg
 *  forwardmsg---------->netisr1                |
 *                          :                   |
 *                          :(delete/add...)    |
 *                          :                   |
 *                          :                   |
 *                        replymsg--------------+
 *
 *
 *
 * Rule structure [2 CPU case]
 *
 *    CPU0               CPU1
 *
 * layer3_chain       layer3_chain
 *     |                  |
 *     V                  V
 * +-------+ sibling  +-------+ sibling
 * | rule1 |--------->| rule1 |--------->NULL
 * +-------+          +-------+
 *     |                  |
 *     |next              |next
 *     V                  V
 * +-------+ sibling  +-------+ sibling
 * | rule2 |--------->| rule2 |--------->NULL
 * +-------+          +-------+
 *
 * ip_fw.sibling:
 * 1) Ease statistics calculation during IP_FW_GET.  We only need to
 *    iterate layer3_chain in netisr0; the current rule's duplication
 *    to the other CPUs could safely be read-only accessed through
 *    ip_fw.sibling.
 * 2) Accelerate rule insertion and deletion, e.g. rule insertion:
 *    a) In netisr0 rule3 is determined to be inserted between rule1
 *       and rule2.  To make this decision we need to iterate the
 *       layer3_chain in netisr0.  The netmsg, which is used to insert
 *       the rule, will contain rule1 in netisr0 as prev_rule and rule2
 *       in netisr0 as next_rule.
 *    b) After the insertion in netisr0 is done, we will move on to
 *       netisr1.  But instead of relocating the rule3's position in
 *       netisr1 by iterating the layer3_chain in netisr1, we set the
 *       netmsg's prev_rule to rule1->sibling and next_rule to
 *       rule2->sibling before the netmsg is forwarded to netisr1 from
 *       netisr0.
 */

/*
 * Description of states and tracks.
 *
 * Both states and tracks are stored in per-cpu RB trees instead of
 * per-cpu hash tables to avoid the worst case hash degeneration.
 *
 * The lifetimes of states and tracks are regulated by dyn_*_lifetime,
 * measured in seconds and depending on the flags.
 *
 * When a packet is received, its address fields are first masked with
 * the mask defined for the rule, then matched against the entries in
 * the per-cpu state RB tree.  States are generated by 'keep-state'
 * and 'limit' options.
 *
 * The max number of states is ipfw_state_max.  When we reach the
 * maximum number of states we do not create anymore.  This is done to
 * avoid consuming too much memory, but also too much time when
 * searching on each packet.
 *
 * Each state holds a pointer to the parent ipfw rule of the current
 * CPU so we know what action to perform.  States are removed when the
 * parent rule is deleted.  XXX we should make them survive.
 *
 * There are some limitations with states -- we do not obey the
 * 'randomized match', and we do not do multiple passes through the
 * firewall.  XXX check the latter!!!
 *
 * States grow independently on each CPU, e.g. 2 CPU case:
 *
 *        CPU0                     CPU1
 * ...................      ...................
 * :  state RB tree  :      :  state RB tree  :
 * :                 :      :                 :
 * : state1   state2 :      :      state3     :
 * :     |    |      :      :        |        :
 * :.....|....|......:      :........|........:
 *       |    |                      |
 *       |    |                      |st_rule
 *       |    |                      |
 *       V    V                      V
 *     +-------+                 +-------+
 *     | rule1 |                 | rule1 |
 *     +-------+                 +-------+
 *
 * Tracks are used to enforce limits on the number of sessions.  Tracks
 * are generated by 'limit' option.
 *
 * The max number of tracks is ipfw_track_max.  When we reach the
 * maximum number of tracks we do not create anymore.  This is done to
 * avoid consuming too much memory.
 *
 * Tracks are organized into two layers, track counter RB tree is
 * shared between CPUs, track RB tree is per-cpu.  States generated by
 * 'limit' option are linked to the track in addition to the per-cpu
 * state RB tree; mainly to ease expiration.  e.g. 2 CPU case:
 *
 *             ..............................
 *             :    track counter RB tree   :
 *             :                            :
 *             :        +-----------+       :
 *             :        |  trkcnt1  |       :
 *             :        |           |       :
 *             :      +--->counter<----+    :
 *             :      | |           |  |    :
 *             :      | +-----------+  |    :
 *             :......|................|....:
 *                    |                |
 *        CPU0        |                |         CPU1
 * .................  |t_count         |  .................
 * : track RB tree :  |                |  : track RB tree :
 * :               :  |                |  :               :
 * : +-->track1-------+                +--------track2    :
 * : |     A       :                      :               :
 * : |     |       :                      :               :
 * :.|.....|.......:                      :...............:
 *   |     +----------------+
 *   | .................... |
 *   | :   state RB tree  : |st_track
 *   | :                  : |
 *   +---state1    state2---+
 *     :     |       |    :
 *     :.....|.......|....:
 *           |       |
 *           |       |st_rule
 *           V       V
 *         +----------+
 *         |   rule1  |
 *         +----------+
 */

#define IPFW_AUTOINC_STEP_MIN   1
#define IPFW_AUTOINC_STEP_MAX   1000
#define IPFW_AUTOINC_STEP_DEF   100

#define IPFW_TABLE_MAX_DEF      64

#define IPFW_DEFAULT_RULE       65535   /* rulenum for the default rule */
#define IPFW_DEFAULT_SET        31      /* set number for the default rule */

#define MATCH_REVERSE           0
#define MATCH_FORWARD           1
#define MATCH_NONE              2
#define MATCH_UNKNOWN           3

#define TIME_LEQ(a, b)          ((a) - (b) <= 0)

#define IPFW_STATE_TCPFLAGS     (TH_SYN | TH_FIN | TH_RST)
#define IPFW_STATE_TCPSTATES    (IPFW_STATE_TCPFLAGS |  \
                                 (IPFW_STATE_TCPFLAGS << 8))

#define BOTH_SYN                (TH_SYN | (TH_SYN << 8))
#define BOTH_FIN                (TH_FIN | (TH_FIN << 8))
#define BOTH_RST                (TH_RST | (TH_RST << 8))
/* TH_ACK here means FIN was ACKed. */
#define BOTH_FINACK             (TH_ACK | (TH_ACK << 8))

#define IPFW_STATE_TCPCLOSED(s) ((s)->st_proto == IPPROTO_TCP &&        \
                                 (((s)->st_state & BOTH_RST) ||         \
                                  ((s)->st_state & BOTH_FINACK) == BOTH_FINACK))

#define O_ANCHOR                O_NOP

#define IPFW_ISXLAT(type)       ((type) == O_REDIRECT)
#define IPFW_XLAT_INVALID(s)    (IPFW_ISXLAT((s)->st_type) &&   \
                                 ((struct ipfw_xlat *)(s))->xlat_invalid)

#define IPFW_MBUF_XLATINS       FW_MBUF_PRIVATE1
#define IPFW_MBUF_XLATFWD       FW_MBUF_PRIVATE2

#define IPFW_XLATE_INSERT       0x0001
#define IPFW_XLATE_FORWARD      0x0002
#define IPFW_XLATE_OUTPUT       0x0004

struct netmsg_ipfw {
        struct netmsg_base      base;
        const struct ipfw_ioc_rule *ioc_rule;
        struct ip_fw            *next_rule;
        struct ip_fw            *prev_rule;
        struct ip_fw            *sibling;
        uint32_t                rule_flags;
        struct ip_fw            **cross_rules;
};

struct netmsg_del {
        struct netmsg_base      base;
        struct ip_fw            *start_rule;
        struct ip_fw            *prev_rule;
        uint16_t                rulenum;
        uint8_t                 from_set;
        uint8_t                 to_set;
};

struct netmsg_zent {
        struct netmsg_base      base;
        struct ip_fw            *start_rule;
        uint16_t                rulenum;
        uint16_t                log_only;
};

struct netmsg_cpstate {
        struct netmsg_base      base;
        struct ipfw_ioc_state   *ioc_state;
        int                     state_cntmax;
        int                     state_cnt;
};

struct netmsg_tblent {
        struct netmsg_base      base;
        struct sockaddr         *key;
        struct sockaddr         *netmask;
        struct ipfw_tblent      *sibling;
        int                     tableid;
};

struct netmsg_tblflush {
        struct netmsg_base      base;
        int                     tableid;
        int                     destroy;
};

struct netmsg_tblexp {
        struct netmsg_base      base;
        time_t                  expire;
        int                     tableid;
        int                     cnt;
        int                     expcnt;
        struct radix_node_head  *rnh;
};

struct ipfw_table_cp {
        struct ipfw_ioc_tblent  *te;
        int                     te_idx;
        int                     te_cnt;
};

struct ip_fw_local {
        /*
         * offset       The offset of a fragment. offset != 0 means that
         *      we have a fragment at this offset of an IPv4 packet.
         *      offset == 0 means that (if this is an IPv4 packet)
         *      this is the first or only fragment.
         */
        u_short                 offset;

        /*
         * Local copies of addresses. They are only valid if we have
         * an IP packet.
         *
         * proto        The protocol. Set to 0 for non-ip packets,
         *      or to the protocol read from the packet otherwise.
         *      proto != 0 means that we have an IPv4 packet.
         *
         * src_port, dst_port   port numbers, in HOST format. Only
         *      valid for TCP and UDP packets.
         *
         * src_ip, dst_ip       ip addresses, in NETWORK format.
         *      Only valid for IPv4 packets.
         */
        uint8_t                 proto;
        uint16_t                src_port;       /* NOTE: host format    */
        uint16_t                dst_port;       /* NOTE: host format    */
        struct in_addr          src_ip;         /* NOTE: network format */
        struct in_addr          dst_ip;         /* NOTE: network format */
        uint16_t                ip_len;         /* NOTE: host format    */
        struct tcphdr           *tcp;
};

struct ipfw_addrs {
        uint32_t                addr1;  /* host byte order */
        uint32_t                addr2;  /* host byte order */
};

struct ipfw_ports {
        uint16_t                port1;  /* host byte order */
        uint16_t                port2;  /* host byte order */
};

struct ipfw_key {
        union {
                struct ipfw_addrs addrs;
                uint64_t        value;
        } addr_u;
        union {
                struct ipfw_ports ports;
                uint32_t        value;
        } port_u;
        uint8_t                 proto;
        uint8_t                 swap;   /* IPFW_KEY_SWAP_ */
        uint16_t                rsvd2;
};

#define IPFW_KEY_SWAP_ADDRS     0x1
#define IPFW_KEY_SWAP_PORTS     0x2
#define IPFW_KEY_SWAP_ALL       (IPFW_KEY_SWAP_ADDRS | IPFW_KEY_SWAP_PORTS)

struct ipfw_trkcnt {
        RB_ENTRY(ipfw_trkcnt)   tc_rblink;
        struct ipfw_key         tc_key;
        uintptr_t               tc_ruleid;
        int                     tc_refs;
        int                     tc_count;
        time_t                  tc_expire;      /* userland get-only */
        uint16_t                tc_rulenum;     /* userland get-only */
} __cachealign;

#define tc_addrs                tc_key.addr_u.value
#define tc_ports                tc_key.port_u.value
#define tc_proto                tc_key.proto
#define tc_saddr                tc_key.addr_u.addrs.addr1
#define tc_daddr                tc_key.addr_u.addrs.addr2
#define tc_sport                tc_key.port_u.ports.port1
#define tc_dport                tc_key.port_u.ports.port2

RB_HEAD(ipfw_trkcnt_tree, ipfw_trkcnt);

struct ipfw_state;

struct ipfw_track {
        RB_ENTRY(ipfw_track)    t_rblink;
        struct ipfw_key         t_key;
        struct ip_fw            *t_rule;
        time_t                  t_lastexp;
        LIST_HEAD(, ipfw_state) t_state_list;
        time_t                  t_expire;
        volatile int            *t_count;
        struct ipfw_trkcnt      *t_trkcnt;
        TAILQ_ENTRY(ipfw_track) t_link;
};

#define t_addrs                 t_key.addr_u.value
#define t_ports                 t_key.port_u.value
#define t_proto                 t_key.proto
#define t_saddr                 t_key.addr_u.addrs.addr1
#define t_daddr                 t_key.addr_u.addrs.addr2
#define t_sport                 t_key.port_u.ports.port1
#define t_dport                 t_key.port_u.ports.port2

RB_HEAD(ipfw_track_tree, ipfw_track);
TAILQ_HEAD(ipfw_track_list, ipfw_track);

struct ipfw_state {
        RB_ENTRY(ipfw_state)    st_rblink;
        struct ipfw_key         st_key;

        time_t                  st_expire;      /* expire time */
        struct ip_fw            *st_rule;

        uint64_t                st_pcnt;        /* packets */
        uint64_t                st_bcnt;        /* bytes */

        /*
         * st_state:
         * State of this rule, typically a combination of TCP flags.
         *
         * st_ack_fwd/st_ack_rev:
         * Most recent ACKs in forward and reverse direction.  They
         * are used to generate keepalives.
         */
        uint32_t                st_state;
        uint32_t                st_ack_fwd;     /* host byte order */
        uint32_t                st_seq_fwd;     /* host byte order */
        uint32_t                st_ack_rev;     /* host byte order */
        uint32_t                st_seq_rev;     /* host byte order */

        uint16_t                st_flags;       /* IPFW_STATE_F_ */
        uint16_t                st_type;        /* KEEP_STATE/LIMIT/RDR */
        struct ipfw_track       *st_track;

        LIST_ENTRY(ipfw_state)  st_trklink;
        TAILQ_ENTRY(ipfw_state) st_link;
};

#define st_addrs                st_key.addr_u.value
#define st_ports                st_key.port_u.value
#define st_proto                st_key.proto
#define st_swap                 st_key.swap

#define IPFW_STATE_F_ACKFWD     0x0001
#define IPFW_STATE_F_SEQFWD     0x0002
#define IPFW_STATE_F_ACKREV     0x0004
#define IPFW_STATE_F_SEQREV     0x0008
#define IPFW_STATE_F_XLATSRC    0x0010
#define IPFW_STATE_F_XLATSLAVE  0x0020
#define IPFW_STATE_F_LINKED     0x0040

#define IPFW_STATE_SCANSKIP(s)  ((s)->st_type == O_ANCHOR ||    \
                                 ((s)->st_flags & IPFW_STATE_F_XLATSLAVE))

/* Expired or being deleted. */
#define IPFW_STATE_ISDEAD(s)    (TIME_LEQ((s)->st_expire, time_uptime) || \
                                 IPFW_XLAT_INVALID((s)))

TAILQ_HEAD(ipfw_state_list, ipfw_state);
RB_HEAD(ipfw_state_tree, ipfw_state);

struct ipfw_xlat {
        struct ipfw_state       xlat_st;        /* MUST be the first field */
        uint32_t                xlat_addr;      /* network byte order */
        uint16_t                xlat_port;      /* network byte order */
        uint16_t                xlat_dir;       /* MATCH_ */
        struct ifnet            *xlat_ifp;      /* matching ifnet */
        struct ipfw_xlat        *xlat_pair;     /* paired state */
        int                     xlat_pcpu;      /* paired cpu */
        volatile int            xlat_invalid;   /* invalid, but not dtor yet */
        volatile uint64_t       xlat_crefs;     /* cross references */
        struct netmsg_base      xlat_freenm;    /* for remote free */
};

#define xlat_type               xlat_st.st_type
#define xlat_flags              xlat_st.st_flags
#define xlat_rule               xlat_st.st_rule
#define xlat_bcnt               xlat_st.st_bcnt
#define xlat_pcnt               xlat_st.st_pcnt

struct ipfw_tblent {
        struct radix_node       te_nodes[2];
        struct sockaddr_in      te_key;
        u_long                  te_use;
        time_t                  te_lastuse;
        struct ipfw_tblent      *te_sibling;
        volatile int            te_expired;
};

struct ipfw_context {
        struct ip_fw            *ipfw_layer3_chain;     /* rules for layer3 */
        struct ip_fw            *ipfw_default_rule;     /* default rule */
        uint64_t                ipfw_norule_counter;    /* ipfw_log(NULL) stat*/

        /*
         * ipfw_set_disable contains one bit per set value (0..31).
         * If the bit is set, all rules with the corresponding set
         * are disabled.  Set IPDW_DEFAULT_SET is reserved for the
         * default rule and CANNOT be disabled.
         */
        uint32_t                ipfw_set_disable;

        uint8_t                 ipfw_flags;     /* IPFW_FLAG_ */

        struct ip_fw            *ipfw_cont_rule;
        struct ipfw_xlat        *ipfw_cont_xlat;

        struct ipfw_state_tree  ipfw_state_tree;
        struct ipfw_state_list  ipfw_state_list;
        int                     ipfw_state_loosecnt;
        int                     ipfw_state_cnt;

        union {
                struct ipfw_state state;
                struct ipfw_track track;
                struct ipfw_trkcnt trkcnt;
        } ipfw_tmpkey;

        struct ipfw_track_tree  ipfw_track_tree;
        struct ipfw_track_list  ipfw_track_list;
        struct ipfw_trkcnt      *ipfw_trkcnt_spare;

        struct callout          ipfw_stateto_ch;
        time_t                  ipfw_state_lastexp;
        struct netmsg_base      ipfw_stateexp_nm;
        struct netmsg_base      ipfw_stateexp_more;
        struct ipfw_state       ipfw_stateexp_anch;

        struct callout          ipfw_trackto_ch;
        time_t                  ipfw_track_lastexp;
        struct netmsg_base      ipfw_trackexp_nm;
        struct netmsg_base      ipfw_trackexp_more;
        struct ipfw_track       ipfw_trackexp_anch;

        struct callout          ipfw_keepalive_ch;
        struct netmsg_base      ipfw_keepalive_nm;
        struct netmsg_base      ipfw_keepalive_more;
        struct ipfw_state       ipfw_keepalive_anch;

        struct callout          ipfw_xlatreap_ch;
        struct netmsg_base      ipfw_xlatreap_nm;
        struct ipfw_state_list  ipfw_xlatreap;

        /*
         * Statistics
         */
        u_long                  ipfw_sts_reap;
        u_long                  ipfw_sts_reapfailed;
        u_long                  ipfw_sts_overflow;
        u_long                  ipfw_sts_nomem;
        u_long                  ipfw_sts_tcprecycled;

        u_long                  ipfw_tks_nomem;
        u_long                  ipfw_tks_reap;
        u_long                  ipfw_tks_reapfailed;
        u_long                  ipfw_tks_overflow;
        u_long                  ipfw_tks_cntnomem;

        u_long                  ipfw_frags;
        u_long                  ipfw_defraged;
        u_long                  ipfw_defrag_remote;

        u_long                  ipfw_xlated;
        u_long                  ipfw_xlate_split;
        u_long                  ipfw_xlate_conflicts;
        u_long                  ipfw_xlate_cresolved;

        /* Last field */
        struct radix_node_head  *ipfw_tables[];
};

#define IPFW_FLAG_KEEPALIVE     0x01
#define IPFW_FLAG_STATEEXP      0x02
#define IPFW_FLAG_TRACKEXP      0x04
#define IPFW_FLAG_STATEREAP     0x08
#define IPFW_FLAG_TRACKREAP     0x10

#define ipfw_state_tmpkey       ipfw_tmpkey.state
#define ipfw_track_tmpkey       ipfw_tmpkey.track
#define ipfw_trkcnt_tmpkey      ipfw_tmpkey.trkcnt

struct ipfw_global {
        int                     ipfw_state_loosecnt;    /* cache aligned */
        time_t                  ipfw_state_globexp __cachealign;

        struct lwkt_token       ipfw_trkcnt_token __cachealign;
        struct ipfw_trkcnt_tree ipfw_trkcnt_tree;
        int                     ipfw_trkcnt_cnt;
        time_t                  ipfw_track_globexp;

        /* Accessed in netisr0. */
        struct ip_fw            *ipfw_crossref_free __cachealign;
        struct callout          ipfw_crossref_ch;
        struct netmsg_base      ipfw_crossref_nm;

#ifdef KLD_MODULE
        /*
         * Module can not be unloaded, if there are references to
         * certains rules of ipfw(4), e.g. dummynet(4)
         */
        int                     ipfw_refcnt __cachealign;
#endif
} __cachealign;

static struct ipfw_context      *ipfw_ctx[MAXCPU];

MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");

/*
 * Following two global variables are accessed and updated only
 * in netisr0.
 */
static uint32_t static_count;   /* # of static rules */
static uint32_t static_ioc_len; /* bytes of static rules */

/*
 * If 1, then ipfw static rules are being flushed,
 * ipfw_chk() will skip to the default rule.
 */
static int ipfw_flushing;

static int fw_verbose;
static int verbose_limit;

static int fw_debug;
static int autoinc_step = IPFW_AUTOINC_STEP_DEF;

static int      ipfw_table_max = IPFW_TABLE_MAX_DEF;

static int      ipfw_sysctl_enable(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS);

TUNABLE_INT("net.inet.ip.fw.table_max", &ipfw_table_max);

SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall");
SYSCTL_NODE(_net_inet_ip_fw, OID_AUTO, stats, CTLFLAG_RW, 0,
    "Firewall statistics");

SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, enable, CTLTYPE_INT | CTLFLAG_RW,
    &fw_enable, 0, ipfw_sysctl_enable, "I", "Enable ipfw");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, autoinc_step, CTLTYPE_INT | CTLFLAG_RW,
    &autoinc_step, 0, ipfw_sysctl_autoinc_step, "I",
    "Rule number autincrement step");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO,one_pass,CTLFLAG_RW,
    &fw_one_pass, 0,
    "Only do a single pass through ipfw when using dummynet(4)");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, debug, CTLFLAG_RW,
    &fw_debug, 0, "Enable printing of debug ip_fw statements");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_RW,
    &fw_verbose, 0, "Log matches to ipfw rules");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_RW,
    &verbose_limit, 0, "Set upper limit of matches of ipfw rules logged");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, table_max, CTLFLAG_RD,
    &ipfw_table_max, 0, "Max # of tables");

static int      ipfw_sysctl_dyncnt(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_dynmax(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_statecnt(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_statemax(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_scancnt(SYSCTL_HANDLER_ARGS);
static int      ipfw_sysctl_stat(SYSCTL_HANDLER_ARGS);

/*
 * Timeouts for various events in handing states.
 *
 * NOTE:
 * 1 == 0~1 second.
 * 2 == 1~2 second(s).
 *
 * We use 2 seconds for FIN lifetime, so that the states will not be
 * ripped prematurely.
 */
static uint32_t dyn_ack_lifetime = 300;
static uint32_t dyn_syn_lifetime = 20;
static uint32_t dyn_finwait_lifetime = 20;
static uint32_t dyn_fin_lifetime = 2;
static uint32_t dyn_rst_lifetime = 2;
static uint32_t dyn_udp_lifetime = 10;
static uint32_t dyn_short_lifetime = 5; /* used by tracks too */

/*
 * Keepalives are sent if dyn_keepalive is set. They are sent every
 * dyn_keepalive_period seconds, in the last dyn_keepalive_interval
 * seconds of lifetime of a rule.
 */
static uint32_t dyn_keepalive_interval = 20;
static uint32_t dyn_keepalive_period = 5;
static uint32_t dyn_keepalive = 1;      /* do send keepalives */

static struct ipfw_global       ipfw_gd;
static int      ipfw_state_loosecnt_updthr;
static int      ipfw_state_max = 4096;  /* max # of states */
static int      ipfw_track_max = 4096;  /* max # of tracks */

static int      ipfw_state_headroom;    /* setup at module load time */
static int      ipfw_state_reap_min = 8;
static int      ipfw_state_expire_max = 32;
static int      ipfw_state_scan_max = 256;
static int      ipfw_keepalive_max = 8;
static int      ipfw_track_reap_max = 4;
static int      ipfw_track_expire_max = 16;
static int      ipfw_track_scan_max = 128;

static eventhandler_tag ipfw_ifaddr_event;

/* Compat */
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_count,
    CTLTYPE_INT | CTLFLAG_RD, NULL, 0, ipfw_sysctl_dyncnt, "I",
    "Number of states and tracks");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, dyn_max,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, ipfw_sysctl_dynmax, "I",
    "Max number of states and tracks");

SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_cnt,
    CTLTYPE_INT | CTLFLAG_RD, NULL, 0, ipfw_sysctl_statecnt, "I",
    "Number of states");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_max,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, ipfw_sysctl_statemax, "I",
    "Max number of states");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, state_headroom, CTLFLAG_RW,
    &ipfw_state_headroom, 0, "headroom for state reap");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, track_cnt, CTLFLAG_RD,
    &ipfw_gd.ipfw_trkcnt_cnt, 0, "Number of tracks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, track_max, CTLFLAG_RW,
    &ipfw_track_max, 0, "Max number of tracks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, static_count, CTLFLAG_RD,
    &static_count, 0, "Number of static rules");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_ack_lifetime, CTLFLAG_RW,
    &dyn_ack_lifetime, 0, "Lifetime of dyn. rules for acks");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_syn_lifetime, CTLFLAG_RW,
    &dyn_syn_lifetime, 0, "Lifetime of dyn. rules for syn");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_fin_lifetime, CTLFLAG_RW,
    &dyn_fin_lifetime, 0, "Lifetime of dyn. rules for fin");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_finwait_lifetime, CTLFLAG_RW,
    &dyn_finwait_lifetime, 0, "Lifetime of dyn. rules for fin wait");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_rst_lifetime, CTLFLAG_RW,
    &dyn_rst_lifetime, 0, "Lifetime of dyn. rules for rst");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_udp_lifetime, CTLFLAG_RW,
    &dyn_udp_lifetime, 0, "Lifetime of dyn. rules for UDP");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_short_lifetime, CTLFLAG_RW,
    &dyn_short_lifetime, 0, "Lifetime of dyn. rules for other situations");
SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_keepalive, CTLFLAG_RW,
    &dyn_keepalive, 0, "Enable keepalives for dyn. rules");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_scan_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_scan_max, 0, ipfw_sysctl_scancnt,
    "I", "# of states to scan for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_expire_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_expire_max, 0, ipfw_sysctl_scancnt,
    "I", "# of states to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, keepalive_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_keepalive_max, 0, ipfw_sysctl_scancnt,
    "I", "# of states to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, state_reap_min,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_state_reap_min, 0, ipfw_sysctl_scancnt,
    "I", "# of states to reap for state shortage");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_scan_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_scan_max, 0, ipfw_sysctl_scancnt,
    "I", "# of tracks to scan for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_expire_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_expire_max, 0, ipfw_sysctl_scancnt,
    "I", "# of tracks to expire for each expire iteration");
SYSCTL_PROC(_net_inet_ip_fw, OID_AUTO, track_reap_max,
    CTLTYPE_INT | CTLFLAG_RW, &ipfw_track_reap_max, 0, ipfw_sysctl_scancnt,
    "I", "# of tracks to reap for track shortage");

SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_reap,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_sts_reap), ipfw_sysctl_stat,
    "LU", "# of state reaps due to states shortage");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_reapfailed,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_sts_reapfailed), ipfw_sysctl_stat,
    "LU", "# of state reap failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_overflow,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_sts_overflow), ipfw_sysctl_stat,
    "LU", "# of state overflow");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_nomem,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_sts_nomem), ipfw_sysctl_stat,
    "LU", "# of state allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, state_tcprecycled,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_sts_tcprecycled), ipfw_sysctl_stat,
    "LU", "# of state deleted due to fast TCP port recycling");

SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_nomem,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_tks_nomem), ipfw_sysctl_stat,
    "LU", "# of track allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_reap,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_tks_reap), ipfw_sysctl_stat,
    "LU", "# of track reap due to tracks shortage");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_reapfailed,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_tks_reapfailed), ipfw_sysctl_stat,
    "LU", "# of track reap failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_overflow,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_tks_overflow), ipfw_sysctl_stat,
    "LU", "# of track overflow");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, track_cntnomem,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_tks_cntnomem), ipfw_sysctl_stat,
    "LU", "# of track counter allocation failure");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, frags,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_frags), ipfw_sysctl_stat,
    "LU", "# of IP fragements defraged");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, defraged,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_defraged), ipfw_sysctl_stat,
    "LU", "# of IP packets after defrag");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, defrag_remote,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_defrag_remote), ipfw_sysctl_stat,
    "LU", "# of IP packets after defrag dispatched to remote cpus");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlated,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_xlated), ipfw_sysctl_stat,
    "LU", "# address/port translations");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_split,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_xlate_split), ipfw_sysctl_stat,
    "LU", "# address/port translations split between different cpus");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_conflicts,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_xlate_conflicts), ipfw_sysctl_stat,
    "LU", "# address/port translations conflicts on remote cpu");
SYSCTL_PROC(_net_inet_ip_fw_stats, OID_AUTO, xlate_cresolved,
    CTLTYPE_ULONG | CTLFLAG_RW, NULL,
    __offsetof(struct ipfw_context, ipfw_xlate_cresolved), ipfw_sysctl_stat,
    "LU", "# address/port translations conflicts resolved on remote cpu");

static int              ipfw_state_cmp(struct ipfw_state *,
                            struct ipfw_state *);
static int              ipfw_trkcnt_cmp(struct ipfw_trkcnt *,
                            struct ipfw_trkcnt *);
static int              ipfw_track_cmp(struct ipfw_track *,
                            struct ipfw_track *);

RB_PROTOTYPE(ipfw_state_tree, ipfw_state, st_rblink, ipfw_state_cmp);
RB_GENERATE(ipfw_state_tree, ipfw_state, st_rblink, ipfw_state_cmp);

RB_PROTOTYPE(ipfw_trkcnt_tree, ipfw_trkcnt, tc_rblink, ipfw_trkcnt_cmp);
RB_GENERATE(ipfw_trkcnt_tree, ipfw_trkcnt, tc_rblink, ipfw_trkcnt_cmp);

RB_PROTOTYPE(ipfw_track_tree, ipfw_track, t_rblink, ipfw_track_cmp);
RB_GENERATE(ipfw_track_tree, ipfw_track, t_rblink, ipfw_track_cmp);

static int              ipfw_chk(struct ip_fw_args *);
static void             ipfw_track_expire_ipifunc(void *);
static void             ipfw_state_expire_ipifunc(void *);
static void             ipfw_keepalive(void *);
static int              ipfw_state_expire_start(struct ipfw_context *,
                            int, int);
static void             ipfw_crossref_timeo(void *);
static void             ipfw_state_remove(struct ipfw_context *,
                            struct ipfw_state *);
static void             ipfw_xlat_reap_timeo(void *);
static void             ipfw_defrag_redispatch(struct mbuf *, int,
                            struct ip_fw *);

#define IPFW_TRKCNT_TOKGET      lwkt_gettoken(&ipfw_gd.ipfw_trkcnt_token)
#define IPFW_TRKCNT_TOKREL      lwkt_reltoken(&ipfw_gd.ipfw_trkcnt_token)
#define IPFW_TRKCNT_TOKINIT     \
        lwkt_token_init(&ipfw_gd.ipfw_trkcnt_token, "ipfw_trkcnt");

static void
sa_maskedcopy(const struct sockaddr *src, struct sockaddr *dst,
    const struct sockaddr *netmask)
{
        const u_char *cp1 = (const u_char *)src;
        u_char *cp2 = (u_char *)dst;
        const u_char *cp3 = (const u_char *)netmask;
        u_char *cplim = cp2 + *cp3;
        u_char *cplim2 = cp2 + *cp1;

        *cp2++ = *cp1++; *cp2++ = *cp1++; /* copies sa_len & sa_family */
        cp3 += 2;
        if (cplim > cplim2)
                cplim = cplim2;
        while (cp2 < cplim)
                *cp2++ = *cp1++ & *cp3++;
        if (cp2 < cplim2)
                bzero(cp2, cplim2 - cp2);
}

static __inline uint16_t
pfil_cksum_fixup(uint16_t cksum, uint16_t old, uint16_t new, uint8_t udp)
{
        uint32_t l;

        if (udp && !cksum)
                return (0x0000);
        l = cksum + old - new;
        l = (l >> 16) + (l & 65535);
        l = l & 65535;
        if (udp && !l)
                return (0xFFFF);
        return (l);
}

static __inline void
ipfw_key_build(struct ipfw_key *key, in_addr_t saddr, uint16_t sport,
    in_addr_t daddr, uint16_t dport, uint8_t proto)
{

        key->proto = proto;
        key->swap = 0;

        if (saddr < daddr) {
                key->addr_u.addrs.addr1 = daddr;
                key->addr_u.addrs.addr2 = saddr;
                key->swap |= IPFW_KEY_SWAP_ADDRS;
        } else {
                key->addr_u.addrs.addr1 = saddr;
                key->addr_u.addrs.addr2 = daddr;
        }

        if (sport < dport) {
                key->port_u.ports.port1 = dport;
                key->port_u.ports.port2 = sport;
                key->swap |= IPFW_KEY_SWAP_PORTS;
        } else {
                key->port_u.ports.port1 = sport;
                key->port_u.ports.port2 = dport;
        }

        if (sport == dport && (key->swap & IPFW_KEY_SWAP_ADDRS))
                key->swap |= IPFW_KEY_SWAP_PORTS;
        if (saddr == daddr && (key->swap & IPFW_KEY_SWAP_PORTS))
                key->swap |= IPFW_KEY_SWAP_ADDRS;
}

static __inline void
ipfw_key_4tuple(const struct ipfw_key *key, in_addr_t *saddr, uint16_t *sport,
    in_addr_t *daddr, uint16_t *dport)
{

        if (key->swap & IPFW_KEY_SWAP_ADDRS) {
                *saddr = key->addr_u.addrs.addr2;
                *daddr = key->addr_u.addrs.addr1;
        } else {
                *saddr = key->addr_u.addrs.addr1;
                *daddr = key->addr_u.addrs.addr2;
        }

        if (key->swap & IPFW_KEY_SWAP_PORTS) {
                *sport = key->port_u.ports.port2;
                *dport = key->port_u.ports.port1;
        } else {
                *sport = key->port_u.ports.port1;
                *dport = key->port_u.ports.port2;
        }
}

static int
ipfw_state_cmp(struct ipfw_state *s1, struct ipfw_state *s2)
{

        if (s1->st_proto > s2->st_proto)
                return (1);
        if (s1->st_proto < s2->st_proto)
                return (-1);

        if (s1->st_addrs > s2->st_addrs)
                return (1);
        if (s1->st_addrs < s2->st_addrs)
                return (-1);

        if (s1->st_ports > s2->st_ports)
                return (1);
        if (s1->st_ports < s2->st_ports)
                return (-1);

        if (s1->st_swap == s2->st_swap ||
            (s1->st_swap ^ s2->st_swap) == IPFW_KEY_SWAP_ALL)
                return (0);

        if (s1->st_swap > s2->st_swap)
                return (1);
        else
                return (-1);
}

static int
ipfw_trkcnt_cmp(struct ipfw_trkcnt *t1, struct ipfw_trkcnt *t2)
{

        if (t1->tc_proto > t2->tc_proto)
                return (1);
        if (t1->tc_proto < t2->tc_proto)
                return (-1);

        if (t1->tc_addrs > t2->tc_addrs)
                return (1);
        if (t1->tc_addrs < t2->tc_addrs)
                return (-1);

        if (t1->tc_ports > t2->tc_ports)
                return (1);
        if (t1->tc_ports < t2->tc_ports)
                return (-1);

        if (t1->tc_ruleid > t2->tc_ruleid)
                return (1);
        if (t1->tc_ruleid < t2->tc_ruleid)
                return (-1);

        return (0);
}

static int
ipfw_track_cmp(struct ipfw_track *t1, struct ipfw_track *t2)
{

        if (t1->t_proto > t2->t_proto)
                return (1);
        if (t1->t_proto < t2->t_proto)
                return (-1);

        if (t1->t_addrs > t2->t_addrs)
                return (1);
        if (t1->t_addrs < t2->t_addrs)
                return (-1);

        if (t1->t_ports > t2->t_ports)
                return (1);
        if (t1->t_ports < t2->t_ports)
                return (-1);

        if ((uintptr_t)t1->t_rule > (uintptr_t)t2->t_rule)
                return (1);
        if ((uintptr_t)t1->t_rule < (uintptr_t)t2->t_rule)
                return (-1);

        return (0);
}

static __inline struct ipfw_state *
ipfw_state_link(struct ipfw_context *ctx, struct ipfw_state *s)
{
        struct ipfw_state *dup;

        KASSERT((s->st_flags & IPFW_STATE_F_LINKED) == 0,
            ("state %p was linked", s));
        dup = RB_INSERT(ipfw_state_tree, &ctx->ipfw_state_tree, s);
        if (dup == NULL) {
                TAILQ_INSERT_TAIL(&ctx->ipfw_state_list, s, st_link);
                s->st_flags |= IPFW_STATE_F_LINKED;
        }
        return (dup);
}

static __inline void
ipfw_state_unlink(struct ipfw_context *ctx, struct ipfw_state *s)
{

        KASSERT(s->st_flags & IPFW_STATE_F_LINKED,
            ("state %p was not linked", s));
        RB_REMOVE(ipfw_state_tree, &ctx->ipfw_state_tree, s);
        TAILQ_REMOVE(&ctx->ipfw_state_list, s, st_link);
        s->st_flags &= ~IPFW_STATE_F_LINKED;
}

static void
ipfw_state_max_set(int state_max)
{

        ipfw_state_max = state_max;
        /* Allow 5% states over-allocation. */
        ipfw_state_loosecnt_updthr = (state_max / 20) / netisr_ncpus;
}

static __inline int
ipfw_state_cntcoll(void)
{
        int cpu, state_cnt = 0;

        for (cpu = 0; cpu < netisr_ncpus; ++cpu)
                state_cnt += ipfw_ctx[cpu]->ipfw_state_cnt;
        return (state_cnt);
}

static __inline int
ipfw_state_cntsync(void)
{
        int state_cnt;

        state_cnt = ipfw_state_cntcoll();
        ipfw_gd.ipfw_state_loosecnt = state_cnt;
        return (state_cnt);
}

static __inline int
ipfw_free_rule(struct ip_fw *rule)
{
        KASSERT(rule->cpuid == mycpuid, ("rule freed on cpu%d", mycpuid));
        KASSERT(rule->refcnt > 0, ("invalid refcnt %u", rule->refcnt));
        rule->refcnt--;
        if (rule->refcnt == 0) {
                if (rule->cross_rules != NULL)
                        kfree(rule->cross_rules, M_IPFW);
                kfree(rule, M_IPFW);
                return 1;
        }
        return 0;
}

static void
ipfw_unref_rule(void *priv)
{
        ipfw_free_rule(priv);
#ifdef KLD_MODULE
        KASSERT(ipfw_gd.ipfw_refcnt > 0,
            ("invalid ipfw_refcnt %d", ipfw_gd.ipfw_refcnt));
        atomic_subtract_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
}

static __inline void
ipfw_ref_rule(struct ip_fw *rule)
{
        KASSERT(rule->cpuid == mycpuid, ("rule used on cpu%d", mycpuid));
#ifdef KLD_MODULE
        atomic_add_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
        rule->refcnt++;
}

/*
 * This macro maps an ip pointer into a layer3 header pointer of type T
 */
#define L3HDR(T, ip) ((T *)((uint32_t *)(ip) + (ip)->ip_hl))

static __inline int
icmptype_match(struct ip *ip, ipfw_insn_u32 *cmd)
{
        int type = L3HDR(struct icmp,ip)->icmp_type;
        int idx_max = F_LEN(&cmd->o) - F_INSN_SIZE(ipfw_insn);
        int idx = type / 32;

        if (idx >= idx_max)
                return (0);
        return (cmd->d[idx] & (1 << (type % 32)));
}

static __inline int
icmpcode_match(struct ip *ip, ipfw_insn_u32 *cmd)
{
        int code = L3HDR(struct icmp,ip)->icmp_code;
        int idx_max = F_LEN(&cmd->o) - F_INSN_SIZE(ipfw_insn);
        int idx = code / 32;

        if (idx >= idx_max)
                return (0);
        return (cmd->d[idx] & (1 << (code % 32)));
}

#define TT      ((1 << ICMP_ECHO) | \
                 (1 << ICMP_ROUTERSOLICIT) | \
                 (1 << ICMP_TSTAMP) | \
                 (1 << ICMP_IREQ) | \
                 (1 << ICMP_MASKREQ))

static int
is_icmp_query(struct ip *ip)
{
        int type = L3HDR(struct icmp, ip)->icmp_type;

        return (type < 32 && (TT & (1 << type)));
}

#undef TT

/*
 * The following checks use two arrays of 8 or 16 bits to store the
 * bits that we want set or clear, respectively. They are in the
 * low and high half of cmd->arg1 or cmd->d[0].
 *
 * We scan options and store the bits we find set. We succeed if
 *
 *      (want_set & ~bits) == 0 && (want_clear & ~bits) == want_clear
 *
 * The code is sometimes optimized not to store additional variables.
 */
static int
flags_match(ipfw_insn *cmd, uint8_t bits)
{
        u_char want_clear;
        bits = ~bits;

        if (((cmd->arg1 & 0xff) & bits) != 0)
                return 0; /* some bits we want set were clear */

        want_clear = (cmd->arg1 >> 8) & 0xff;
        if ((want_clear & bits) != want_clear)
                return 0; /* some bits we want clear were set */
        return 1;
}

static int
ipopts_match(struct ip *ip, ipfw_insn *cmd)
{
        int optlen, bits = 0;
        u_char *cp = (u_char *)(ip + 1);
        int x = (ip->ip_hl << 2) - sizeof(struct ip);

        for (; x > 0; x -= optlen, cp += optlen) {
                int opt = cp[IPOPT_OPTVAL];

                if (opt == IPOPT_EOL)
                        break;

                if (opt == IPOPT_NOP) {
                        optlen = 1;
                } else {
                        optlen = cp[IPOPT_OLEN];
                        if (optlen <= 0 || optlen > x)
                                return 0; /* invalid or truncated */
                }

                switch (opt) {
                case IPOPT_LSRR:
                        bits |= IP_FW_IPOPT_LSRR;
                        break;

                case IPOPT_SSRR:
                        bits |= IP_FW_IPOPT_SSRR;
                        break;

                case IPOPT_RR:
                        bits |= IP_FW_IPOPT_RR;
                        break;

                case IPOPT_TS:
                        bits |= IP_FW_IPOPT_TS;
                        break;

                default:
                        break;
                }
        }
        return (flags_match(cmd, bits));
}

static int
tcpopts_match(struct ip *ip, ipfw_insn *cmd)
{
        int optlen, bits = 0;
        struct tcphdr *tcp = L3HDR(struct tcphdr,ip);
        u_char *cp = (u_char *)(tcp + 1);
        int x = (tcp->th_off << 2) - sizeof(struct tcphdr);

        for (; x > 0; x -= optlen, cp += optlen) {
                int opt = cp[0];

                if (opt == TCPOPT_EOL)
                        break;

                if (opt == TCPOPT_NOP) {
                        optlen = 1;
                } else {
                        optlen = cp[1];
                        if (optlen <= 0)
                                break;
                }

                switch (opt) {
                case TCPOPT_MAXSEG:
                        bits |= IP_FW_TCPOPT_MSS;
                        break;

                case TCPOPT_WINDOW:
                        bits |= IP_FW_TCPOPT_WINDOW;
                        break;

                case TCPOPT_SACK_PERMITTED:
                case TCPOPT_SACK:
                        bits |= IP_FW_TCPOPT_SACK;
                        break;

                case TCPOPT_TIMESTAMP:
                        bits |= IP_FW_TCPOPT_TS;
                        break;

                case TCPOPT_CC:
                case TCPOPT_CCNEW:
                case TCPOPT_CCECHO:
                        bits |= IP_FW_TCPOPT_CC;
                        break;

                default:
                        break;
                }
        }
        return (flags_match(cmd, bits));
}

static int
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 */
                /* Check name */
                if (cmd->p.glob) {
                        if (kfnmatch(cmd->name, ifp->if_xname, 0) == 0)
                                return(1);
                } else {
                        if (strncmp(ifp->if_xname, cmd->name, IFNAMSIZ) == 0)
                                return(1);
                }
        } else {
                struct ifaddr_container *ifac;

                TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
                        struct ifaddr *ia = ifac->ifa;

                        if (ia->ifa_addr == NULL)
                                continue;
                        if (ia->ifa_addr->sa_family != AF_INET)
                                continue;
                        if (cmd->p.ip.s_addr == ((struct sockaddr_in *)
                            (ia->ifa_addr))->sin_addr.s_addr)
                                return(1);      /* match */
                }
        }
        return(0);      /* no match, fail ... */
}

#define SNPARGS(buf, len) buf + len, sizeof(buf) > len ? sizeof(buf) - len : 0

/*
 * We enter here when we have a rule with O_LOG.
 * XXX this function alone takes about 2Kbytes of code!
 */
static void
ipfw_log(struct ipfw_context *ctx, struct ip_fw *f, u_int hlen,
    struct ether_header *eh, struct mbuf *m, struct ifnet *oif)
{
        char *action;
        int limit_reached = 0;
        char action2[40], proto[48], fragment[28], abuf[INET_ADDRSTRLEN];

        fragment[0] = '\0';
        proto[0] = '\0';

        if (f == NULL) {        /* bogus pkt */
                if (verbose_limit != 0 &&
                    ctx->ipfw_norule_counter >= verbose_limit)
                        return;
                ctx->ipfw_norule_counter++;
                if (ctx->ipfw_norule_counter == verbose_limit)
                        limit_reached = verbose_limit;
                action = "Refuse";
        } else {        /* O_LOG is the first action, find the real one */
                ipfw_insn *cmd = ACTION_PTR(f);
                ipfw_insn_log *l = (ipfw_insn_log *)cmd;

                if (l->max_log != 0 && l->log_left == 0)
                        return;
                l->log_left--;
                if (l->log_left == 0)
                        limit_reached = l->max_log;
                cmd += F_LEN(cmd);      /* point to first action */
                if (cmd->opcode == O_PROB)
                        cmd += F_LEN(cmd);

                action = action2;
                switch (cmd->opcode) {
                case O_DENY:
                        action = "Deny";
                        break;

                case O_REJECT:
                        if (cmd->arg1==ICMP_REJECT_RST) {
                                action = "Reset";
                        } else if (cmd->arg1==ICMP_UNREACH_HOST) {
                                action = "Reject";
                        } else {
                                ksnprintf(SNPARGS(action2, 0), "Unreach %d",
                                          cmd->arg1);
                        }
                        break;

                case O_ACCEPT:
                        action = "Accept";
                        break;

                case O_COUNT:
                        action = "Count";
                        break;

                case O_DIVERT:
                        ksnprintf(SNPARGS(action2, 0), "Divert %d", cmd->arg1);
                        break;

                case O_TEE:
                        ksnprintf(SNPARGS(action2, 0), "Tee %d", cmd->arg1);
                        break;

                case O_SKIPTO:
                        ksnprintf(SNPARGS(action2, 0), "SkipTo %d", cmd->arg1);
                        break;

                case O_PIPE:
                        ksnprintf(SNPARGS(action2, 0), "Pipe %d", cmd->arg1);
                        break;

                case O_QUEUE:
                        ksnprintf(SNPARGS(action2, 0), "Queue %d", cmd->arg1);
                        break;

                case O_FORWARD_IP:
                        {
                                ipfw_insn_sa *sa = (ipfw_insn_sa *)cmd;
                                int len;

                                len = ksnprintf(SNPARGS(action2, 0),
                                    "Forward to %s",
                                    kinet_ntoa(sa->sa.sin_addr, abuf));
                                if (sa->sa.sin_port) {
                                        ksnprintf(SNPARGS(action2, len), ":%d",
                                                  sa->sa.sin_port);
                                }
                        }
                        break;

                default:
                        action = "UNKNOWN";
                        break;
                }
        }

        if (hlen == 0) {        /* non-ip */
                ksnprintf(SNPARGS(proto, 0), "MAC");
        } else {
                struct ip *ip = mtod(m, struct ip *);
                /* these three are all aliases to the same thing */
                struct icmp *const icmp = L3HDR(struct icmp, ip);
                struct tcphdr *const tcp = (struct tcphdr *)icmp;
                struct udphdr *const udp = (struct udphdr *)icmp;

                int ip_off, offset, ip_len;
                int len;

                ip_off = ntohs(ip->ip_off);
                ip_len = ntohs(ip->ip_len);
                offset = ip_off & IP_OFFMASK;

                switch (ip->ip_p) {
                case IPPROTO_TCP:
                        len = ksnprintf(SNPARGS(proto, 0), "TCP %s",
                                        kinet_ntoa(ip->ip_src, abuf));
                        if (offset == 0) {
                                ksnprintf(SNPARGS(proto, len), ":%d %s:%d",
                                          ntohs(tcp->th_sport),
                                          kinet_ntoa(ip->ip_dst, abuf),
                                          ntohs(tcp->th_dport));
                        } else {
                                ksnprintf(SNPARGS(proto, len), " %s",
                                          kinet_ntoa(ip->ip_dst, abuf));
                        }
                        break;

                case IPPROTO_UDP:
                        len = ksnprintf(SNPARGS(proto, 0), "UDP %s",
                                        kinet_ntoa(ip->ip_src, abuf));
                        if (offset == 0) {
                                ksnprintf(SNPARGS(proto, len), ":%d %s:%d",
                                          ntohs(udp->uh_sport),
                                          kinet_ntoa(ip->ip_dst, abuf),
                                          ntohs(udp->uh_dport));
                        } else {
                                ksnprintf(SNPARGS(proto, len), " %s",
                                          kinet_ntoa(ip->ip_dst, abuf));
                        }
                        break;

                case IPPROTO_ICMP:
                        if (offset == 0) {
                                len = ksnprintf(SNPARGS(proto, 0),
                                                "ICMP:%u.%u ",
                                                icmp->icmp_type,
                                                icmp->icmp_code);
                        } else {
                                len = ksnprintf(SNPARGS(proto, 0), "ICMP ");
                        }
                        len += ksnprintf(SNPARGS(proto, len), "%s",
                                         kinet_ntoa(ip->ip_src, abuf));
                        ksnprintf(SNPARGS(proto, len), " %s",
                                  kinet_ntoa(ip->ip_dst, abuf));
                        break;

                default:
                        len = ksnprintf(SNPARGS(proto, 0), "P:%d %s", ip->ip_p,
                                        kinet_ntoa(ip->ip_src, abuf));
                        ksnprintf(SNPARGS(proto, len), " %s",
                                  kinet_ntoa(ip->ip_dst, abuf));
                        break;
                }

                if (ip_off & (IP_MF | IP_OFFMASK)) {
                        ksnprintf(SNPARGS(fragment, 0), " (frag %d:%d@%d%s)",
                                  ntohs(ip->ip_id), ip_len - (ip->ip_hl << 2),
                                  offset << 3, (ip_off & IP_MF) ? "+" : "");
                }
        }

        if (oif || m->m_pkthdr.rcvif) {
                log(LOG_SECURITY | LOG_INFO,
                    "ipfw: %d %s %s %s via %s%s\n",
                    f ? f->rulenum : -1,
                    action, proto, oif ? "out" : "in",
                    oif ? oif->if_xname : m->m_pkthdr.rcvif->if_xname,
                    fragment);
        } else {
                log(LOG_SECURITY | LOG_INFO,
                    "ipfw: %d %s %s [no if info]%s\n",
                    f ? f->rulenum : -1,
                    action, proto, fragment);
        }

        if (limit_reached) {
                log(LOG_SECURITY | LOG_NOTICE,
                    "ipfw: limit %d reached on entry %d\n",
                    limit_reached, f ? f->rulenum : -1);
        }
}

#undef SNPARGS

static void
ipfw_xlat_reap(struct ipfw_xlat *x, struct ipfw_xlat *slave_x)
{
        struct ip_fw *rule = slave_x->xlat_rule;

        KKASSERT(rule->cpuid == mycpuid);

        /* No more cross references; free this pair now. */
        kfree(x, M_IPFW);
        kfree(slave_x, M_IPFW);

        /* See the comment in ipfw_ip_xlate_dispatch(). */
        rule->cross_refs--;
}

static void
ipfw_xlat_reap_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_state *s, *ns;

        ASSERT_NETISR_NCPUS(mycpuid);

        crit_enter();
        /* Reply ASAP. */
        netisr_replymsg(&ctx->ipfw_xlatreap_nm, 0);
        crit_exit();

        /* TODO: limit scanning depth */
        TAILQ_FOREACH_MUTABLE(s, &ctx->ipfw_xlatreap, st_link, ns) {
                struct ipfw_xlat *x = (struct ipfw_xlat *)s;
                struct ipfw_xlat *slave_x = x->xlat_pair;
                uint64_t crefs;

                crefs = slave_x->xlat_crefs + x->xlat_crefs;
                if (crefs == 0) {
                        TAILQ_REMOVE(&ctx->ipfw_xlatreap, &x->xlat_st, st_link);
                        ipfw_xlat_reap(x, slave_x);
                }
        }
        if (!TAILQ_EMPTY(&ctx->ipfw_xlatreap)) {
                callout_reset(&ctx->ipfw_xlatreap_ch, 2, ipfw_xlat_reap_timeo,
                    &ctx->ipfw_xlatreap_nm);
        }
}

static void
ipfw_xlat_reap_timeo(void *xnm)
{
        struct netmsg_base *nm = xnm;

        KKASSERT(mycpuid < netisr_ncpus);

        crit_enter();
        if (nm->lmsg.ms_flags & MSGF_DONE)
                netisr_sendmsg_oncpu(nm);
        crit_exit();
}

static void
ipfw_xlat_free_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_xlat *x = nmsg->lmsg.u.ms_resultp;
        struct ipfw_xlat *slave_x = x->xlat_pair;
        uint64_t crefs;

        ASSERT_NETISR_NCPUS(mycpuid);

        KKASSERT(slave_x != NULL);
        KKASSERT(slave_x->xlat_invalid && x->xlat_invalid);

        KASSERT((x->xlat_flags & IPFW_STATE_F_LINKED) == 0,
            ("master xlat is still linked"));
        if (slave_x->xlat_flags & IPFW_STATE_F_LINKED)
                ipfw_state_unlink(ctx, &slave_x->xlat_st);

        /* See the comment in ipfw_ip_xlate_dispatch(). */
        slave_x->xlat_crefs--;

        crefs = slave_x->xlat_crefs + x->xlat_crefs;
        if (crefs == 0) {
                ipfw_xlat_reap(x, slave_x);
                return;
        }

        if (TAILQ_EMPTY(&ctx->ipfw_xlatreap)) {
                callout_reset(&ctx->ipfw_xlatreap_ch, 2, ipfw_xlat_reap_timeo,
                    &ctx->ipfw_xlatreap_nm);
        }

        /*
         * This pair is still referenced; defer its destruction.
         * YYY reuse st_link.
         */
        TAILQ_INSERT_TAIL(&ctx->ipfw_xlatreap, &x->xlat_st, st_link);
}

static __inline void
ipfw_xlat_invalidate(struct ipfw_xlat *x)
{

        x->xlat_invalid = 1;
        x->xlat_pair->xlat_invalid = 1;
}

static void
ipfw_state_del(struct ipfw_context *ctx, struct ipfw_state *s)
{
        struct ipfw_xlat *x, *slave_x;
        struct netmsg_base *nm;

        KASSERT(s->st_type == O_KEEP_STATE || s->st_type == O_LIMIT ||
            IPFW_ISXLAT(s->st_type), ("invalid state type %u", s->st_type));
        KASSERT((s->st_flags & IPFW_STATE_F_XLATSLAVE) == 0,
            ("delete slave xlat"));

        KASSERT(ctx->ipfw_state_cnt > 0,
            ("invalid state count %d", ctx->ipfw_state_cnt));
        ctx->ipfw_state_cnt--;
        if (ctx->ipfw_state_loosecnt > 0)
                ctx->ipfw_state_loosecnt--;

        /*
         * Unhook this state.
         */
        if (s->st_track != NULL) {
                struct ipfw_track *t = s->st_track;

                KASSERT(!LIST_EMPTY(&t->t_state_list),
                    ("track state list is empty"));
                LIST_REMOVE(s, st_trklink);

                KASSERT(*t->t_count > 0,
                    ("invalid track count %d", *t->t_count));
                atomic_subtract_int(t->t_count, 1);
        }
        ipfw_state_unlink(ctx, s);

        /*
         * Free this state.  Xlat requires special processing,
         * since xlat are paired state and they could be on
         * different cpus.
         */

        if (!IPFW_ISXLAT(s->st_type)) {
                /* Not xlat; free now. */
                kfree(s, M_IPFW);
                /* Done! */
                return;
        }
        x = (struct ipfw_xlat *)s;

        if (x->xlat_pair == NULL) {
                /* Not setup yet; free now. */
                kfree(x, M_IPFW);
                /* Done! */
                return;
        }
        slave_x = x->xlat_pair;
        KKASSERT(slave_x->xlat_flags & IPFW_STATE_F_XLATSLAVE);

        if (x->xlat_pcpu == mycpuid) {
                /*
                 * Paired states are on the same cpu; delete this
                 * pair now.
                 */
                KKASSERT(x->xlat_crefs == 0);
                KKASSERT(slave_x->xlat_crefs == 0);
                if (slave_x->xlat_flags & IPFW_STATE_F_LINKED)
                        ipfw_state_unlink(ctx, &slave_x->xlat_st);
                kfree(x, M_IPFW);
                kfree(slave_x, M_IPFW);
                return;
        }

        /*
         * Free the paired states on the cpu owning the slave xlat.
         */

        /*
         * Mark the state pair invalid; completely deleting them
         * may take some time.
         */
        ipfw_xlat_invalidate(x);

        nm = &x->xlat_freenm;
        netmsg_init(nm, NULL, &netisr_apanic_rport, MSGF_PRIORITY,
            ipfw_xlat_free_dispatch);
        nm->lmsg.u.ms_resultp = x;

        /* See the comment in ipfw_xlate_redispatch(). */
        x->xlat_rule->cross_refs++;
        x->xlat_crefs++;

        netisr_sendmsg(nm, x->xlat_pcpu);
}

static void
ipfw_state_remove(struct ipfw_context *ctx, struct ipfw_state *s)
{

        if (s->st_flags & IPFW_STATE_F_XLATSLAVE) {
                KKASSERT(IPFW_ISXLAT(s->st_type));
                ipfw_xlat_invalidate((struct ipfw_xlat *)s);
                ipfw_state_unlink(ctx, s);
                return;
        }
        ipfw_state_del(ctx, s);
}

static int
ipfw_state_reap(struct ipfw_context *ctx, int reap_max)
{
        struct ipfw_state *s, *anchor;
        int expired;

        if (reap_max < ipfw_state_reap_min)
                reap_max = ipfw_state_reap_min;

        if ((ctx->ipfw_flags & IPFW_FLAG_STATEEXP) == 0) {
                /*
                 * Kick start state expiring.  Ignore scan limit,
                 * we are short of states.
                 */
                ctx->ipfw_flags |= IPFW_FLAG_STATEREAP;
                expired = ipfw_state_expire_start(ctx, INT_MAX, reap_max);
                ctx->ipfw_flags &= ~IPFW_FLAG_STATEREAP;
                return (expired);
        }

        /*
         * States are being expired.
         */

        if (ctx->ipfw_state_cnt == 0)
                return (0);

        expired = 0;
        anchor = &ctx->ipfw_stateexp_anch;
        while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
                /*
                 * Ignore scan limit; we are short of states.
                 */

                TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
                TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);

                if (IPFW_STATE_SCANSKIP(s))
                        continue;

                if (IPFW_STATE_ISDEAD(s) || IPFW_STATE_TCPCLOSED(s)) {
                        ipfw_state_del(ctx, s);
                        if (++expired >= reap_max)
                                break;
                        if ((expired & 0xff) == 0 &&
                            ipfw_state_cntcoll() + ipfw_state_headroom <=
                            ipfw_state_max)
                                break;
                }
        }
        /*
         * NOTE:
         * Leave the anchor on the list, even if the end of the list has
         * been reached.  ipfw_state_expire_more_dispatch() will handle
         * the removal.
         */
        return (expired);
}

static void
ipfw_state_flush(struct ipfw_context *ctx, const struct ip_fw *rule)
{
        struct ipfw_state *s, *sn;

        TAILQ_FOREACH_MUTABLE(s, &ctx->ipfw_state_list, st_link, sn) {
                if (IPFW_STATE_SCANSKIP(s))
                        continue;
                if (rule != NULL && s->st_rule != rule)
                        continue;
                ipfw_state_del(ctx, s);
        }
}

static void
ipfw_state_expire_done(struct ipfw_context *ctx)
{

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
            ("stateexp is not in progress"));
        ctx->ipfw_flags &= ~IPFW_FLAG_STATEEXP;
        callout_reset(&ctx->ipfw_stateto_ch, hz,
            ipfw_state_expire_ipifunc, NULL);
}

static void
ipfw_state_expire_more(struct ipfw_context *ctx)
{
        struct netmsg_base *nm = &ctx->ipfw_stateexp_more;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
            ("stateexp is not in progress"));
        KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
            ("stateexp more did not finish"));
        netisr_sendmsg_oncpu(nm);
}

static int
ipfw_state_expire_loop(struct ipfw_context *ctx, struct ipfw_state *anchor,
    int scan_max, int expire_max)
{
        struct ipfw_state *s;
        int scanned = 0, expired = 0;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
            ("stateexp is not in progress"));

        while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
                if (scanned++ >= scan_max) {
                        ipfw_state_expire_more(ctx);
                        return (expired);
                }

                TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
                TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);

                if (IPFW_STATE_SCANSKIP(s))
                        continue;

                if (IPFW_STATE_ISDEAD(s) ||
                    ((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) &&
                     IPFW_STATE_TCPCLOSED(s))) {
                        ipfw_state_del(ctx, s);
                        if (++expired >= expire_max) {
                                ipfw_state_expire_more(ctx);
                                return (expired);
                        }
                        if ((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) &&
                            (expired & 0xff) == 0 &&
                            ipfw_state_cntcoll() + ipfw_state_headroom <=
                            ipfw_state_max) {
                                ipfw_state_expire_more(ctx);
                                return (expired);
                        }
                }
        }
        TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
        ipfw_state_expire_done(ctx);
        return (expired);
}

static void
ipfw_state_expire_more_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_state *anchor;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(ctx->ipfw_flags & IPFW_FLAG_STATEEXP,
            ("statexp is not in progress"));

        /* Reply ASAP */
        netisr_replymsg(&nm->base, 0);

        anchor = &ctx->ipfw_stateexp_anch;
        if (ctx->ipfw_state_cnt == 0) {
                TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
                ipfw_state_expire_done(ctx);
                return;
        }
        ipfw_state_expire_loop(ctx, anchor,
            ipfw_state_scan_max, ipfw_state_expire_max);
}

static int
ipfw_state_expire_start(struct ipfw_context *ctx, int scan_max, int expire_max)
{
        struct ipfw_state *anchor;

        KASSERT((ctx->ipfw_flags & IPFW_FLAG_STATEEXP) == 0,
            ("stateexp is in progress"));
        ctx->ipfw_flags |= IPFW_FLAG_STATEEXP;

        if (ctx->ipfw_state_cnt == 0) {
                ipfw_state_expire_done(ctx);
                return (0);
        }

        /*
         * Do not expire more than once per second, it is useless.
         */
        if ((ctx->ipfw_flags & IPFW_FLAG_STATEREAP) == 0 &&
            ctx->ipfw_state_lastexp == time_uptime) {
                ipfw_state_expire_done(ctx);
                return (0);
        }
        ctx->ipfw_state_lastexp = time_uptime;

        anchor = &ctx->ipfw_stateexp_anch;
        TAILQ_INSERT_HEAD(&ctx->ipfw_state_list, anchor, st_link);
        return (ipfw_state_expire_loop(ctx, anchor, scan_max, expire_max));
}

static void
ipfw_state_expire_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        /* Reply ASAP */
        crit_enter();
        netisr_replymsg(&nm->base, 0);
        crit_exit();

        if (ctx->ipfw_flags & IPFW_FLAG_STATEEXP) {
                /* Running; done. */
                return;
        }
        ipfw_state_expire_start(ctx,
            ipfw_state_scan_max, ipfw_state_expire_max);
}

static void
ipfw_state_expire_ipifunc(void *dummy __unused)
{
        struct netmsg_base *msg;

        KKASSERT(mycpuid < netisr_ncpus);
        msg = &ipfw_ctx[mycpuid]->ipfw_stateexp_nm;

        crit_enter();
        if (msg->lmsg.ms_flags & MSGF_DONE)
                netisr_sendmsg_oncpu(msg);
        crit_exit();
}

static boolean_t
ipfw_state_update_tcp(struct ipfw_state *s, int dir, const struct tcphdr *tcp)
{
        uint32_t seq = ntohl(tcp->th_seq);
        uint32_t ack = ntohl(tcp->th_ack);

        if (tcp->th_flags & TH_RST)
                return (TRUE);

        if (dir == MATCH_FORWARD) {
                if ((s->st_flags & IPFW_STATE_F_SEQFWD) == 0) {
                        s->st_flags |= IPFW_STATE_F_SEQFWD;
                        s->st_seq_fwd = seq;
                } else if (SEQ_GEQ(seq, s->st_seq_fwd)) {
                        s->st_seq_fwd = seq;
                } else {
                        /* Out-of-sequence; done. */
                        return (FALSE);
                }
                if (tcp->th_flags & TH_ACK) {
                        if ((s->st_flags & IPFW_STATE_F_ACKFWD) == 0) {
                                s->st_flags |= IPFW_STATE_F_ACKFWD;
                                s->st_ack_fwd = ack;
                        } else if (SEQ_GEQ(ack, s->st_ack_fwd)) {
                                s->st_ack_fwd = ack;
                        } else {
                                /* Out-of-sequence; done. */
                                return (FALSE);
                        }

                        if ((s->st_state & ((TH_FIN | TH_ACK) << 8)) ==
                            (TH_FIN << 8) && s->st_ack_fwd == s->st_seq_rev + 1)
                                s->st_state |= (TH_ACK << 8);
                }
        } else {
                if ((s->st_flags & IPFW_STATE_F_SEQREV) == 0) {
                        s->st_flags |= IPFW_STATE_F_SEQREV;
                        s->st_seq_rev = seq;
                } else if (SEQ_GEQ(seq, s->st_seq_rev)) {
                        s->st_seq_rev = seq;
                } else {
                        /* Out-of-sequence; done. */
                        return (FALSE);
                }
                if (tcp->th_flags & TH_ACK) {
                        if ((s->st_flags & IPFW_STATE_F_ACKREV) == 0) {
                                s->st_flags |= IPFW_STATE_F_ACKREV;
                                s->st_ack_rev= ack;
                        } else if (SEQ_GEQ(ack, s->st_ack_rev)) {
                                s->st_ack_rev = ack;
                        } else {
                                /* Out-of-sequence; done. */
                                return (FALSE);
                        }

                        if ((s->st_state & (TH_FIN | TH_ACK)) == TH_FIN &&
                            s->st_ack_rev == s->st_seq_fwd + 1)
                                s->st_state |= TH_ACK;
                }
        }
        return (TRUE);
}

static void
ipfw_state_update(const struct ipfw_flow_id *pkt, int dir,
    const struct tcphdr *tcp, struct ipfw_state *s)
{

        if (pkt->proto == IPPROTO_TCP) { /* update state according to flags */
                u_char flags = pkt->flags & IPFW_STATE_TCPFLAGS;

                if (tcp != NULL && !ipfw_state_update_tcp(s, dir, tcp))
                        return;

                s->st_state |= (dir == MATCH_FORWARD) ? flags : (flags << 8);
                switch (s->st_state & IPFW_STATE_TCPSTATES) {
                case TH_SYN:                            /* opening */
                        s->st_expire = time_uptime + dyn_syn_lifetime;
                        break;

                case BOTH_SYN:                  /* move to established */
                case BOTH_SYN | TH_FIN:         /* one side tries to close */
                case BOTH_SYN | (TH_FIN << 8):
                        s->st_expire = time_uptime + dyn_ack_lifetime;
                        break;

                case BOTH_SYN | BOTH_FIN:       /* both sides closed */
                        if ((s->st_state & BOTH_FINACK) == BOTH_FINACK) {
                                /* And both FINs were ACKed. */
                                s->st_expire = time_uptime + dyn_fin_lifetime;
                        } else {
                                s->st_expire = time_uptime +
                                    dyn_finwait_lifetime;
                        }
                        break;

                default:
#if 0
                        /*
                         * reset or some invalid combination, but can also
                         * occur if we use keep-state the wrong way.
                         */
                        if ((s->st_state & ((TH_RST << 8) | TH_RST)) == 0)
                                kprintf("invalid state: 0x%x\n", s->st_state);
#endif
                        s->st_expire = time_uptime + dyn_rst_lifetime;
                        break;
                }
        } else if (pkt->proto == IPPROTO_UDP) {
                s->st_expire = time_uptime + dyn_udp_lifetime;
        } else {
                /* other protocols */
                s->st_expire = time_uptime + dyn_short_lifetime;
        }
}

/*
 * Lookup a state.
 */
static struct ipfw_state *
ipfw_state_lookup(struct ipfw_context *ctx, const struct ipfw_flow_id *pkt,
    int *match_direction, const struct tcphdr *tcp)
{
        struct ipfw_state *key, *s;
        int dir = MATCH_NONE;

        key = &ctx->ipfw_state_tmpkey;
        ipfw_key_build(&key->st_key, pkt->src_ip, pkt->src_port,
            pkt->dst_ip, pkt->dst_port, pkt->proto);
        s = RB_FIND(ipfw_state_tree, &ctx->ipfw_state_tree, key);
        if (s == NULL)
                goto done; /* not found. */
        if (IPFW_STATE_ISDEAD(s)) {
                ipfw_state_remove(ctx, s);
                s = NULL;
                goto done;
        }
        if ((pkt->flags & TH_SYN) && IPFW_STATE_TCPCLOSED(s)) {
                /* TCP ports recycling is too fast. */
                ctx->ipfw_sts_tcprecycled++;
                ipfw_state_remove(ctx, s);
                s = NULL;
                goto done;
        }

        if (s->st_swap == key->st_swap) {
                dir = MATCH_FORWARD;
        } else {
                KASSERT((s->st_swap & key->st_swap) == 0,
                    ("found mismatch state"));
                dir = MATCH_REVERSE;
        }

        /* Update this state. */
        ipfw_state_update(pkt, dir, tcp, s);

        if (s->st_track != NULL) {
                /* This track has been used. */
                s->st_track->t_expire = time_uptime + dyn_short_lifetime;
        }
done:
        if (match_direction)
                *match_direction = dir;
        return (s);
}

static struct ipfw_state *
ipfw_state_alloc(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
    uint16_t type, struct ip_fw *rule, const struct tcphdr *tcp)
{
        struct ipfw_state *s;
        size_t sz;

        KASSERT(type == O_KEEP_STATE || type == O_LIMIT || IPFW_ISXLAT(type),
            ("invalid state type %u", type));

        sz = sizeof(struct ipfw_state);
        if (IPFW_ISXLAT(type))
                sz = sizeof(struct ipfw_xlat);

        s = kmalloc(sz, M_IPFW, M_INTWAIT | M_NULLOK | M_ZERO);
        if (s == NULL) {
                ctx->ipfw_sts_nomem++;
                return (NULL);
        }

        ipfw_key_build(&s->st_key, id->src_ip, id->src_port,
            id->dst_ip, id->dst_port, id->proto);

        s->st_rule = rule;
        s->st_type = type;
        if (IPFW_ISXLAT(type)) {
                struct ipfw_xlat *x = (struct ipfw_xlat *)s;

                x->xlat_dir = MATCH_NONE;
                x->xlat_pcpu = -1;
        }

        /*
         * Update this state:
         * Set st_expire and st_state.
         */
        ipfw_state_update(id, MATCH_FORWARD, tcp, s);

        return (s);
}

static struct ipfw_state *
ipfw_state_add(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
    uint16_t type, struct ip_fw *rule, struct ipfw_track *t,
    const struct tcphdr *tcp)
{
        struct ipfw_state *s, *dup;

        s = ipfw_state_alloc(ctx, id, type, rule, tcp);
        if (s == NULL)
                return (NULL);

        ctx->ipfw_state_cnt++;
        ctx->ipfw_state_loosecnt++;
        if (ctx->ipfw_state_loosecnt >= ipfw_state_loosecnt_updthr) {
                ipfw_gd.ipfw_state_loosecnt += ctx->ipfw_state_loosecnt;
                ctx->ipfw_state_loosecnt = 0;
        }

        dup = ipfw_state_link(ctx, s);
        if (dup != NULL)
                panic("ipfw: %u state exists %p", type, dup);

        if (t != NULL) {
                /* Keep the track referenced. */
                LIST_INSERT_HEAD(&t->t_state_list, s, st_trklink);
                s->st_track = t;
        }
        return (s);
}

static boolean_t
ipfw_track_free(struct ipfw_context *ctx, struct ipfw_track *t)
{
        struct ipfw_trkcnt *trk;
        boolean_t trk_freed = FALSE;

        KASSERT(t->t_count != NULL, ("track anchor"));
        KASSERT(LIST_EMPTY(&t->t_state_list),
            ("invalid track is still referenced"));

        trk = t->t_trkcnt;
        KASSERT(trk != NULL, ("track has no trkcnt"));

        RB_REMOVE(ipfw_track_tree, &ctx->ipfw_track_tree, t);
        TAILQ_REMOVE(&ctx->ipfw_track_list, t, t_link);
        kfree(t, M_IPFW);

        /*
         * fdrop() style reference counting.
         * See kern/kern_descrip.c fdrop().
         */
        for (;;) {
                int refs = trk->tc_refs;

                cpu_ccfence();
                KASSERT(refs > 0, ("invalid trkcnt refs %d", refs));
                if (refs == 1) {
                        IPFW_TRKCNT_TOKGET;
                        if (atomic_cmpset_int(&trk->tc_refs, refs, 0)) {
                                KASSERT(trk->tc_count == 0,
                                    ("%d states reference this trkcnt",
                                     trk->tc_count));
                                RB_REMOVE(ipfw_trkcnt_tree,
                                    &ipfw_gd.ipfw_trkcnt_tree, trk);

                                KASSERT(ipfw_gd.ipfw_trkcnt_cnt > 0,
                                    ("invalid trkcnt cnt %d",
                                     ipfw_gd.ipfw_trkcnt_cnt));
                                ipfw_gd.ipfw_trkcnt_cnt--;
                                IPFW_TRKCNT_TOKREL;

                                if (ctx->ipfw_trkcnt_spare == NULL)
                                        ctx->ipfw_trkcnt_spare = trk;
                                else
                                        kfree(trk, M_IPFW);
                                trk_freed = TRUE;
                                break; /* done! */
                        }
                        IPFW_TRKCNT_TOKREL;
                        /* retry */
                } else if (atomic_cmpset_int(&trk->tc_refs, refs, refs - 1)) {
                        break; /* done! */
                }
                /* retry */
        }
        return (trk_freed);
}

static void
ipfw_track_flush(struct ipfw_context *ctx, struct ip_fw *rule)
{
        struct ipfw_track *t, *tn;

        TAILQ_FOREACH_MUTABLE(t, &ctx->ipfw_track_list, t_link, tn) {
                if (t->t_count == NULL) /* anchor */
                        continue;
                if (rule != NULL && t->t_rule != rule)
                        continue;
                ipfw_track_free(ctx, t);
        }
}

static boolean_t
ipfw_track_state_expire(struct ipfw_context *ctx, struct ipfw_track *t,
    boolean_t reap)
{
        struct ipfw_state *s, *sn;
        boolean_t ret = FALSE;

        KASSERT(t->t_count != NULL, ("track anchor"));

        if (LIST_EMPTY(&t->t_state_list))
                return (FALSE);

        /*
         * Do not expire more than once per second, it is useless.
         */
        if (t->t_lastexp == time_uptime)
                return (FALSE);
        t->t_lastexp = time_uptime;

        LIST_FOREACH_MUTABLE(s, &t->t_state_list, st_trklink, sn) {
                if (IPFW_STATE_ISDEAD(s) || (reap && IPFW_STATE_TCPCLOSED(s))) {
                        KASSERT(s->st_track == t,
                            ("state track %p does not match %p",
                             s->st_track, t));
                        ipfw_state_del(ctx, s);
                        ret = TRUE;
                }
        }
        return (ret);
}

static __inline struct ipfw_trkcnt *
ipfw_trkcnt_alloc(struct ipfw_context *ctx)
{
        struct ipfw_trkcnt *trk;

        if (ctx->ipfw_trkcnt_spare != NULL) {
                trk = ctx->ipfw_trkcnt_spare;
                ctx->ipfw_trkcnt_spare = NULL;
        } else {
                trk = kmalloc(sizeof(*trk), M_IPFW,
                              M_INTWAIT | M_NULLOK | M_CACHEALIGN);
        }
        return (trk);
}

static void
ipfw_track_expire_done(struct ipfw_context *ctx)
{

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
            ("trackexp is not in progress"));
        ctx->ipfw_flags &= ~IPFW_FLAG_TRACKEXP;
        callout_reset(&ctx->ipfw_trackto_ch, hz,
            ipfw_track_expire_ipifunc, NULL);
}

static void
ipfw_track_expire_more(struct ipfw_context *ctx)
{
        struct netmsg_base *nm = &ctx->ipfw_trackexp_more;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
            ("trackexp is not in progress"));
        KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
            ("trackexp more did not finish"));
        netisr_sendmsg_oncpu(nm);
}

static int
ipfw_track_expire_loop(struct ipfw_context *ctx, struct ipfw_track *anchor,
    int scan_max, int expire_max)
{
        struct ipfw_track *t;
        int scanned = 0, expired = 0;
        boolean_t reap = FALSE;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
            ("trackexp is not in progress"));

        if (ctx->ipfw_flags & IPFW_FLAG_TRACKREAP)
                reap = TRUE;

        while ((t = TAILQ_NEXT(anchor, t_link)) != NULL) {
                if (scanned++ >= scan_max) {
                        ipfw_track_expire_more(ctx);
                        return (expired);
                }

                TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
                TAILQ_INSERT_AFTER(&ctx->ipfw_track_list, t, anchor, t_link);

                if (t->t_count == NULL) /* anchor */
                        continue;

                ipfw_track_state_expire(ctx, t, reap);
                if (!LIST_EMPTY(&t->t_state_list)) {
                        /* There are states referencing this track. */
                        continue;
                }

                if (TIME_LEQ(t->t_expire, time_uptime) || reap) {
                        /* Expired. */
                        if (ipfw_track_free(ctx, t)) {
                                if (++expired >= expire_max) {
                                        ipfw_track_expire_more(ctx);
                                        return (expired);
                                }
                        }
                }
        }
        TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
        ipfw_track_expire_done(ctx);
        return (expired);
}

static int
ipfw_track_expire_start(struct ipfw_context *ctx, int scan_max, int expire_max)
{
        struct ipfw_track *anchor;

        KASSERT((ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) == 0,
            ("trackexp is in progress"));
        ctx->ipfw_flags |= IPFW_FLAG_TRACKEXP;

        if (RB_EMPTY(&ctx->ipfw_track_tree)) {
                ipfw_track_expire_done(ctx);
                return (0);
        }

        /*
         * Do not expire more than once per second, it is useless.
         */
        if ((ctx->ipfw_flags & IPFW_FLAG_TRACKREAP) == 0 &&
            ctx->ipfw_track_lastexp == time_uptime) {
                ipfw_track_expire_done(ctx);
                return (0);
        }
        ctx->ipfw_track_lastexp = time_uptime;

        anchor = &ctx->ipfw_trackexp_anch;
        TAILQ_INSERT_HEAD(&ctx->ipfw_track_list, anchor, t_link);
        return (ipfw_track_expire_loop(ctx, anchor, scan_max, expire_max));
}

static void
ipfw_track_expire_more_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_track *anchor;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(ctx->ipfw_flags & IPFW_FLAG_TRACKEXP,
            ("trackexp is not in progress"));

        /* Reply ASAP */
        netisr_replymsg(&nm->base, 0);

        anchor = &ctx->ipfw_trackexp_anch;
        if (RB_EMPTY(&ctx->ipfw_track_tree)) {
                TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
                ipfw_track_expire_done(ctx);
                return;
        }
        ipfw_track_expire_loop(ctx, anchor,
            ipfw_track_scan_max, ipfw_track_expire_max);
}

static void
ipfw_track_expire_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        /* Reply ASAP */
        crit_enter();
        netisr_replymsg(&nm->base, 0);
        crit_exit();

        if (ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) {
                /* Running; done. */
                return;
        }
        ipfw_track_expire_start(ctx,
            ipfw_track_scan_max, ipfw_track_expire_max);
}

static void
ipfw_track_expire_ipifunc(void *dummy __unused)
{
        struct netmsg_base *msg;

        KKASSERT(mycpuid < netisr_ncpus);
        msg = &ipfw_ctx[mycpuid]->ipfw_trackexp_nm;

        crit_enter();
        if (msg->lmsg.ms_flags & MSGF_DONE)
                netisr_sendmsg_oncpu(msg);
        crit_exit();
}

static int
ipfw_track_reap(struct ipfw_context *ctx)
{
        struct ipfw_track *t, *anchor;
        int expired;

        if ((ctx->ipfw_flags & IPFW_FLAG_TRACKEXP) == 0) {
                /*
                 * Kick start track expiring.  Ignore scan limit,
                 * we are short of tracks.
                 */
                ctx->ipfw_flags |= IPFW_FLAG_TRACKREAP;
                expired = ipfw_track_expire_start(ctx, INT_MAX,
                    ipfw_track_reap_max);
                ctx->ipfw_flags &= ~IPFW_FLAG_TRACKREAP;
                return (expired);
        }

        /*
         * Tracks are being expired.
         */

        if (RB_EMPTY(&ctx->ipfw_track_tree))
                return (0);

        expired = 0;
        anchor = &ctx->ipfw_trackexp_anch;
        while ((t = TAILQ_NEXT(anchor, t_link)) != NULL) {
                /*
                 * Ignore scan limit; we are short of tracks.
                 */

                TAILQ_REMOVE(&ctx->ipfw_track_list, anchor, t_link);
                TAILQ_INSERT_AFTER(&ctx->ipfw_track_list, t, anchor, t_link);

                if (t->t_count == NULL) /* anchor */
                        continue;

                ipfw_track_state_expire(ctx, t, TRUE);
                if (!LIST_EMPTY(&t->t_state_list)) {
                        /* There are states referencing this track. */
                        continue;
                }

                if (ipfw_track_free(ctx, t)) {
                        if (++expired >= ipfw_track_reap_max) {
                                ipfw_track_expire_more(ctx);
                                break;
                        }
                }
        }
        /*
         * NOTE:
         * Leave the anchor on the list, even if the end of the list has
         * been reached.  ipfw_track_expire_more_dispatch() will handle
         * the removal.
         */
        return (expired);
}

static struct ipfw_track *
ipfw_track_alloc(struct ipfw_context *ctx, const struct ipfw_flow_id *id,
    uint16_t limit_mask, struct ip_fw *rule)
{
        struct ipfw_track *key, *t, *dup;
        struct ipfw_trkcnt *trk, *ret;
        boolean_t do_expire = FALSE;

        KASSERT(rule->track_ruleid != 0,
            ("rule %u has no track ruleid", rule->rulenum));

        key = &ctx->ipfw_track_tmpkey;
        key->t_proto = id->proto;
        key->t_addrs = 0;
        key->t_ports = 0;
        key->t_rule = rule;
        if (limit_mask & DYN_SRC_ADDR)
                key->t_saddr = id->src_ip;
        if (limit_mask & DYN_DST_ADDR)
                key->t_daddr = id->dst_ip;
        if (limit_mask & DYN_SRC_PORT)
                key->t_sport = id->src_port;
        if (limit_mask & DYN_DST_PORT)
                key->t_dport = id->dst_port;

        t = RB_FIND(ipfw_track_tree, &ctx->ipfw_track_tree, key);
        if (t != NULL)
                goto done;

        t = kmalloc(sizeof(*t), M_IPFW, M_INTWAIT | M_NULLOK);
        if (t == NULL) {
                ctx->ipfw_tks_nomem++;
                return (NULL);
        }

        t->t_key = key->t_key;
        t->t_rule = rule;
        t->t_lastexp = 0;
        LIST_INIT(&t->t_state_list);

        if (ipfw_gd.ipfw_trkcnt_cnt >= ipfw_track_max) {
                time_t globexp, uptime;

                trk = NULL;
                do_expire = TRUE;

                /*
                 * Do not expire globally more than once per second,
                 * it is useless.
                 */
                uptime = time_uptime;
                globexp = ipfw_gd.ipfw_track_globexp;
                if (globexp != uptime &&
                    atomic_cmpset_long(&ipfw_gd.ipfw_track_globexp,
                    globexp, uptime)) {
                        int cpu;

                        /* Expire tracks on other CPUs. */
                        for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
                                if (cpu == mycpuid)
                                        continue;
                                lwkt_send_ipiq(globaldata_find(cpu),
                                    ipfw_track_expire_ipifunc, NULL);
                        }
                }
        } else {
                trk = ipfw_trkcnt_alloc(ctx);
        }
        if (trk == NULL) {
                struct ipfw_trkcnt *tkey;

                tkey = &ctx->ipfw_trkcnt_tmpkey;
                key = NULL; /* tkey overlaps key */

                tkey->tc_key = t->t_key;
                tkey->tc_ruleid = rule->track_ruleid;

                IPFW_TRKCNT_TOKGET;
                trk = RB_FIND(ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree,
                    tkey);
                if (trk == NULL) {
                        IPFW_TRKCNT_TOKREL;
                        if (do_expire) {
                                ctx->ipfw_tks_reap++;
                                if (ipfw_track_reap(ctx) > 0) {
                                        if (ipfw_gd.ipfw_trkcnt_cnt <
                                            ipfw_track_max) {
                                                trk = ipfw_trkcnt_alloc(ctx);
                                                if (trk != NULL)
                                                        goto install;
                                                ctx->ipfw_tks_cntnomem++;
                                        } else {
                                                ctx->ipfw_tks_overflow++;
                                        }
                                } else {
                                        ctx->ipfw_tks_reapfailed++;
                                        ctx->ipfw_tks_overflow++;
                                }
                        } else {
                                ctx->ipfw_tks_cntnomem++;
                        }
                        kfree(t, M_IPFW);
                        return (NULL);
                }
                KASSERT(trk->tc_refs > 0 && trk->tc_refs < netisr_ncpus,
                    ("invalid trkcnt refs %d", trk->tc_refs));
                atomic_add_int(&trk->tc_refs, 1);
                IPFW_TRKCNT_TOKREL;
        } else {
install:
                trk->tc_key = t->t_key;
                trk->tc_ruleid = rule->track_ruleid;
                trk->tc_refs = 0;
                trk->tc_count = 0;
                trk->tc_expire = 0;
                trk->tc_rulenum = rule->rulenum;

                IPFW_TRKCNT_TOKGET;
                ret = RB_INSERT(ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree,
                    trk);
                if (ret != NULL) {
                        KASSERT(ret->tc_refs > 0 &&
                            ret->tc_refs < netisr_ncpus,
                            ("invalid trkcnt refs %d", ret->tc_refs));
                        KASSERT(ctx->ipfw_trkcnt_spare == NULL,
                            ("trkcnt spare was installed"));
                        ctx->ipfw_trkcnt_spare = trk;
                        trk = ret;
                } else {
                        ipfw_gd.ipfw_trkcnt_cnt++;
                }
                atomic_add_int(&trk->tc_refs, 1);
                IPFW_TRKCNT_TOKREL;
        }
        t->t_count = &trk->tc_count;
        t->t_trkcnt = trk;

        dup = RB_INSERT(ipfw_track_tree, &ctx->ipfw_track_tree, t);
        if (dup != NULL)
                panic("ipfw: track exists");
        TAILQ_INSERT_TAIL(&ctx->ipfw_track_list, t, t_link);
done:
        t->t_expire = time_uptime + dyn_short_lifetime;
        return (t);
}

/*
 * Install state for rule type cmd->o.opcode
 *
 * Returns NULL if state is not installed because of errors or because
 * states limitations are enforced.
 */
static struct ipfw_state *
ipfw_state_install(struct ipfw_context *ctx, struct ip_fw *rule,
    ipfw_insn_limit *cmd, struct ip_fw_args *args, const struct tcphdr *tcp)
{
        struct ipfw_state *s;
        struct ipfw_track *t;
        int count, diff;

        if (ipfw_gd.ipfw_state_loosecnt >= ipfw_state_max &&
            (diff = (ipfw_state_cntsync() - ipfw_state_max)) >= 0) {
                boolean_t overflow = TRUE;

                ctx->ipfw_sts_reap++;
                if (ipfw_state_reap(ctx, diff) == 0)
                        ctx->ipfw_sts_reapfailed++;
                if (ipfw_state_cntsync() < ipfw_state_max)
                        overflow = FALSE;

                if (overflow) {
                        time_t globexp, uptime;
                        int cpu;

                        /*
                         * Do not expire globally more than once per second,
                         * it is useless.
                         */
                        uptime = time_uptime;
                        globexp = ipfw_gd.ipfw_state_globexp;
                        if (globexp == uptime ||
                            !atomic_cmpset_long(&ipfw_gd.ipfw_state_globexp,
                            globexp, uptime)) {
                                ctx->ipfw_sts_overflow++;
                                return (NULL);
                        }

                        /* Expire states on other CPUs. */
                        for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
                                if (cpu == mycpuid)
                                        continue;
                                lwkt_send_ipiq(globaldata_find(cpu),
                                    ipfw_state_expire_ipifunc, NULL);
                        }
                        ctx->ipfw_sts_overflow++;
                        return (NULL);
                }
        }

        switch (cmd->o.opcode) {
        case O_KEEP_STATE: /* bidir rule */
        case O_REDIRECT:
                s = ipfw_state_add(ctx, &args->f_id, cmd->o.opcode, rule, NULL,
                    tcp);
                if (s == NULL)
                        return (NULL);
                break;

        case O_LIMIT: /* limit number of sessions */
                t = ipfw_track_alloc(ctx, &args->f_id, cmd->limit_mask, rule);
                if (t == NULL)
                        return (NULL);

                if (*t->t_count >= cmd->conn_limit) {
                        if (!ipfw_track_state_expire(ctx, t, TRUE))
                                return (NULL);
                }
                for (;;) {
                        count = *t->t_count;
                        if (count >= cmd->conn_limit)
                                return (NULL);
                        if (atomic_cmpset_int(t->t_count, count, count + 1))
                                break;
                }

                s = ipfw_state_add(ctx, &args->f_id, O_LIMIT, rule, t, tcp);
                if (s == NULL) {
                        /* Undo damage. */
                        atomic_subtract_int(t->t_count, 1);
                        return (NULL);
                }
                break;

        default:
                panic("unknown state type %u\n", cmd->o.opcode);
        }

        if (s->st_type == O_REDIRECT) {
                struct ipfw_xlat *x = (struct ipfw_xlat *)s;
                ipfw_insn_rdr *r = (ipfw_insn_rdr *)cmd;

                x->xlat_addr = r->addr.s_addr;
                x->xlat_port = r->port;
                x->xlat_ifp = args->m->m_pkthdr.rcvif;
                x->xlat_dir = MATCH_FORWARD;
                KKASSERT(x->xlat_ifp != NULL);
        }
        return (s);
}

static int
ipfw_table_lookup(struct ipfw_context *ctx, uint16_t tableid,
    const struct in_addr *in)
{
        struct radix_node_head *rnh;
        struct sockaddr_in sin;
        struct ipfw_tblent *te;

        KASSERT(tableid < ipfw_table_max, ("invalid tableid %u", tableid));
        rnh = ctx->ipfw_tables[tableid];
        if (rnh == NULL)
                return (0); /* no match */

        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_len = sizeof(sin);
        sin.sin_addr = *in;

        te = (struct ipfw_tblent *)rnh->rnh_matchaddr(&sin, rnh);
        if (te == NULL)
                return (0); /* no match */

        te->te_use++;
        te->te_lastuse = time_second;
        return (1); /* match */
}

/*
 * Transmit a TCP packet, containing either a RST or a keepalive.
 * When flags & TH_RST, we are sending a RST packet, because of a
 * "reset" action matched the packet.
 * Otherwise we are sending a keepalive, and flags & TH_
 *
 * Only {src,dst}_{ip,port} of "id" are used.
 */
static void
send_pkt(const struct ipfw_flow_id *id, uint32_t seq, uint32_t ack, int flags)
{
        struct mbuf *m;
        struct ip *ip;
        struct tcphdr *tcp;
        struct route sro;       /* fake route */

        MGETHDR(m, M_NOWAIT, MT_HEADER);
        if (m == NULL)
                return;
        m->m_pkthdr.rcvif = NULL;
        m->m_pkthdr.len = m->m_len = sizeof(struct ip) + sizeof(struct tcphdr);
        m->m_data += max_linkhdr;

        ip = mtod(m, struct ip *);
        bzero(ip, m->m_len);
        tcp = (struct tcphdr *)(ip + 1); /* no IP options */
        ip->ip_p = IPPROTO_TCP;
        tcp->th_off = 5;

        /*
         * Assume we are sending a RST (or a keepalive in the reverse
         * direction), swap src and destination addresses and ports.
         */
        ip->ip_src.s_addr = htonl(id->dst_ip);
        ip->ip_dst.s_addr = htonl(id->src_ip);
        tcp->th_sport = htons(id->dst_port);
        tcp->th_dport = htons(id->src_port);
        if (flags & TH_RST) {   /* we are sending a RST */
                if (flags & TH_ACK) {
                        tcp->th_seq = htonl(ack);
                        tcp->th_ack = htonl(0);
                        tcp->th_flags = TH_RST;
                } else {
                        if (flags & TH_SYN)
                                seq++;
                        tcp->th_seq = htonl(0);
                        tcp->th_ack = htonl(seq);
                        tcp->th_flags = TH_RST | TH_ACK;
                }
        } else {
                /*
                 * We are sending a keepalive. flags & TH_SYN determines
                 * the direction, forward if set, reverse if clear.
                 * NOTE: seq and ack are always assumed to be correct
                 * as set by the caller. This may be confusing...
                 */
                if (flags & TH_SYN) {
                        /*
                         * we have to rewrite the correct addresses!
                         */
                        ip->ip_dst.s_addr = htonl(id->dst_ip);
                        ip->ip_src.s_addr = htonl(id->src_ip);
                        tcp->th_dport = htons(id->dst_port);
                        tcp->th_sport = htons(id->src_port);
                }
                tcp->th_seq = htonl(seq);
                tcp->th_ack = htonl(ack);
                tcp->th_flags = TH_ACK;
        }

        /*
         * set ip_len to the payload size so we can compute
         * the tcp checksum on the pseudoheader
         * XXX check this, could save a couple of words ?
         */
        ip->ip_len = htons(sizeof(struct tcphdr));
        tcp->th_sum = in_cksum(m, m->m_pkthdr.len);

        /*
         * now fill fields left out earlier
         */
        ip->ip_ttl = ip_defttl;
        ip->ip_len = htons(m->m_pkthdr.len);

        bzero(&sro, sizeof(sro));
        ip_rtaddr(ip->ip_dst, &sro);

        m->m_pkthdr.fw_flags |= IPFW_MBUF_GENERATED;
        ip_output(m, NULL, &sro, 0, NULL, NULL);
        if (sro.ro_rt)
                RTFREE(sro.ro_rt);
}

/*
 * Send a reject message, consuming the mbuf passed as an argument.
 */
static void
send_reject(struct ip_fw_args *args, int code, int offset, int ip_len)
{
        if (code != ICMP_REJECT_RST) { /* Send an ICMP unreach */
                /* IP header is always left in network order */
                icmp_error(args->m, ICMP_UNREACH, code, 0L, 0);
        } else if (offset == 0 && args->f_id.proto == IPPROTO_TCP) {
                struct tcphdr *const tcp =
                    L3HDR(struct tcphdr, mtod(args->m, struct ip *));

                if ((tcp->th_flags & TH_RST) == 0) {
                        send_pkt(&args->f_id, ntohl(tcp->th_seq),
                                 ntohl(tcp->th_ack), tcp->th_flags | TH_RST);
                }
                m_freem(args->m);
        } else {
                m_freem(args->m);
        }
        args->m = NULL;
}

/*
 * Given an ip_fw *, lookup_next_rule will return a pointer
 * to the next rule, which can be either the jump
 * target (for skipto instructions) or the next one in the list (in
 * all other cases including a missing jump target).
 * The result is also written in the "next_rule" field of the rule.
 * Backward jumps are not allowed, so start looking from the next
 * rule...
 *
 * This never returns NULL -- in case we do not have an exact match,
 * the next rule is returned. When the ruleset is changed,
 * pointers are flushed so we are always correct.
 */
static struct ip_fw *
lookup_next_rule(struct ip_fw *me)
{
        struct ip_fw *rule = NULL;
        ipfw_insn *cmd;

        /* look for action, in case it is a skipto */
        cmd = ACTION_PTR(me);
        if (cmd->opcode == O_LOG)
                cmd += F_LEN(cmd);
        if (cmd->opcode == O_SKIPTO) {
                for (rule = me->next; rule; rule = rule->next) {
                        if (rule->rulenum >= cmd->arg1)
                                break;
                }
        }
        if (rule == NULL)                       /* failure or not a skipto */
                rule = me->next;
        me->next_rule = rule;
        return rule;
}

static int
ipfw_match_uid(const struct ipfw_flow_id *fid, struct ifnet *oif,
                enum ipfw_opcodes opcode, uid_t uid)
{
        struct in_addr src_ip, dst_ip;
        struct inpcbinfo *pi;
        boolean_t wildcard;
        struct inpcb *pcb;

        if (fid->proto == IPPROTO_TCP) {
                wildcard = FALSE;
                pi = &tcbinfo[mycpuid];
        } else if (fid->proto == IPPROTO_UDP) {
                wildcard = TRUE;
                pi = &udbinfo[mycpuid];
        } else {
                return 0;
        }

        /*
         * Values in 'fid' are in host byte order
         */
        dst_ip.s_addr = htonl(fid->dst_ip);
        src_ip.s_addr = htonl(fid->src_ip);
        if (oif) {
                pcb = in_pcblookup_hash(pi,
                        dst_ip, htons(fid->dst_port),
                        src_ip, htons(fid->src_port),
                        wildcard, oif);
        } else {
                pcb = in_pcblookup_hash(pi,
                        src_ip, htons(fid->src_port),
                        dst_ip, htons(fid->dst_port),
                        wildcard, NULL);
        }
        if (pcb == NULL || pcb->inp_socket == NULL)
                return 0;

        if (opcode == O_UID) {
#define socheckuid(a,b) ((a)->so_cred->cr_uid != (b))
                return !socheckuid(pcb->inp_socket, uid);
#undef socheckuid
        } else  {
                return groupmember(uid, pcb->inp_socket->so_cred);
        }
}

static int
ipfw_match_ifip(ipfw_insn_ifip *cmd, const struct in_addr *ip)
{

        if (__predict_false((cmd->o.arg1 & IPFW_IFIP_VALID) == 0)) {
                struct ifaddr_container *ifac;
                struct ifnet *ifp;

                ifp = ifunit_netisr(cmd->ifname);
                if (ifp == NULL)
                        return (0);

                TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) {
                        struct ifaddr *ia = ifac->ifa;

                        if (ia->ifa_addr == NULL)
                                continue;
                        if (ia->ifa_addr->sa_family != AF_INET)
                                continue;

                        cmd->mask.s_addr = INADDR_ANY;
                        if (cmd->o.arg1 & IPFW_IFIP_NET) {
                                cmd->mask = ((struct sockaddr_in *)
                                    ia->ifa_netmask)->sin_addr;
                        }
                        if (cmd->mask.s_addr == INADDR_ANY)
                                cmd->mask.s_addr = INADDR_BROADCAST;

                        cmd->addr =
                            ((struct sockaddr_in *)ia->ifa_addr)->sin_addr;
                        cmd->addr.s_addr &= cmd->mask.s_addr;

                        cmd->o.arg1 |= IPFW_IFIP_VALID;
                        break;
                }
                if ((cmd->o.arg1 & IPFW_IFIP_VALID) == 0)
                        return (0);
        }
        return ((ip->s_addr & cmd->mask.s_addr) == cmd->addr.s_addr);
}

static void
ipfw_xlate(const struct ipfw_xlat *x, struct mbuf *m,
    struct in_addr *old_addr, uint16_t *old_port)
{
        struct ip *ip = mtod(m, struct ip *);
        struct in_addr *addr;
        uint16_t *port, *csum, dlen = 0;
        uint8_t udp = 0;
        boolean_t pseudo = FALSE;

        if (x->xlat_flags & IPFW_STATE_F_XLATSRC) {
                addr = &ip->ip_src;
                switch (ip->ip_p) {
                case IPPROTO_TCP:
                        port = &L3HDR(struct tcphdr, ip)->th_sport;
                        csum = &L3HDR(struct tcphdr, ip)->th_sum;
                        break;
                case IPPROTO_UDP:
                        port = &L3HDR(struct udphdr, ip)->uh_sport;
                        csum = &L3HDR(struct udphdr, ip)->uh_sum;
                        udp = 1;
                        break;
                default:
                        panic("ipfw: unsupported src xlate proto %u", ip->ip_p);
                }
        } else {
                addr = &ip->ip_dst;
                switch (ip->ip_p) {
                case IPPROTO_TCP:
                        port = &L3HDR(struct tcphdr, ip)->th_dport;
                        csum = &L3HDR(struct tcphdr, ip)->th_sum;
                        break;
                case IPPROTO_UDP:
                        port = &L3HDR(struct udphdr, ip)->uh_dport;
                        csum = &L3HDR(struct udphdr, ip)->uh_sum;
                        udp = 1;
                        break;
                default:
                        panic("ipfw: unsupported dst xlate proto %u", ip->ip_p);
                }
        }
        if (old_addr != NULL)
                *old_addr = *addr;
        if (old_port != NULL) {
                if (x->xlat_port != 0)
                        *old_port = *port;
                else
                        *old_port = 0;
        }

        if (m->m_pkthdr.csum_flags & (CSUM_UDP | CSUM_TCP | CSUM_TSO)) {
                if ((m->m_pkthdr.csum_flags & CSUM_TSO) == 0)
                        dlen = ntohs(ip->ip_len) - (ip->ip_hl << 2);
                pseudo = TRUE;
        }

        if (!pseudo) {
                const uint16_t *oaddr, *naddr;

                oaddr = (const uint16_t *)&addr->s_addr;
                naddr = (const uint16_t *)&x->xlat_addr;

                ip->ip_sum = pfil_cksum_fixup(pfil_cksum_fixup(ip->ip_sum,
                    oaddr[0], naddr[0], 0), oaddr[1], naddr[1], 0);
                *csum = pfil_cksum_fixup(pfil_cksum_fixup(*csum,
                    oaddr[0], naddr[0], udp), oaddr[1], naddr[1], udp);
        }
        addr->s_addr = x->xlat_addr;

        if (x->xlat_port != 0) {
                if (!pseudo) {
                        *csum = pfil_cksum_fixup(*csum, *port, x->xlat_port,
                            udp);
                }
                *port = x->xlat_port;
        }

        if (pseudo) {
                *csum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
                    htons(dlen + ip->ip_p));
        }
}

static void
ipfw_ip_xlate_dispatch(netmsg_t nmsg)
{
        struct netmsg_genpkt *nm = (struct netmsg_genpkt *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct mbuf *m = nm->m;
        struct ipfw_xlat *x = nm->arg1;
        struct ip_fw *rule = x->xlat_rule;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(rule->cpuid == mycpuid,
            ("rule does not belong to cpu%d", mycpuid));
        KASSERT(m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE,
            ("mbuf does not have ipfw continue rule"));

        KASSERT(ctx->ipfw_cont_rule == NULL,
            ("pending ipfw continue rule"));
        KASSERT(ctx->ipfw_cont_xlat == NULL,
            ("pending ipfw continue xlat"));
        ctx->ipfw_cont_rule = rule;
        ctx->ipfw_cont_xlat = x;

        if (nm->arg2 == 0)
                ip_input(m);
        else
                ip_output(m, NULL, NULL, IP_FORWARDING, NULL, NULL);

        /* May not be cleared, if ipfw was unload/disabled. */
        ctx->ipfw_cont_rule = NULL;
        ctx->ipfw_cont_xlat = NULL;

        /*
         * This state is no longer used; decrement its xlat_crefs,
         * so this state can be deleted.
         */
        x->xlat_crefs--;
        /*
         * This rule is no longer used; decrement its cross_refs,
         * so this rule can be deleted.
         *
         * NOTE:
         * Decrement cross_refs in the last step of this function,
         * so that the module could be unloaded safely.
         */
        rule->cross_refs--;
}

static void
ipfw_xlate_redispatch(struct mbuf *m, int cpuid, struct ipfw_xlat *x,
    uint32_t flags)
{
        struct netmsg_genpkt *nm;

        KASSERT(x->xlat_pcpu == cpuid, ("xlat paired cpu%d, target cpu%d",
            x->xlat_pcpu, cpuid));

        /*
         * Bump cross_refs to prevent this rule and its siblings
         * from being deleted, while this mbuf is inflight.  The
         * cross_refs of the sibling rule on the target cpu will
         * be decremented, once this mbuf is going to be filtered
         * on the target cpu.
         */
        x->xlat_rule->cross_refs++;
        /*
         * Bump xlat_crefs to prevent this state and its paired
         * state from being deleted, while this mbuf is inflight.
         * The xlat_crefs of the paired state on the target cpu
         * will be decremented, once this mbuf is going to be
         * filtered on the target cpu.
         */
        x->xlat_crefs++;

        m->m_pkthdr.fw_flags |= IPFW_MBUF_CONTINUE;
        if (flags & IPFW_XLATE_INSERT)
                m->m_pkthdr.fw_flags |= IPFW_MBUF_XLATINS;
        if (flags & IPFW_XLATE_FORWARD)
                m->m_pkthdr.fw_flags |= IPFW_MBUF_XLATFWD;

        /*
         * NOTE: We always leave ip_len and ip_off in network
         *       order across all network layers.
         */
        nm = &m->m_hdr.mh_genmsg;
        netmsg_init(&nm->base, NULL, &netisr_apanic_rport, 0,
            ipfw_ip_xlate_dispatch);
        nm->m = m;
        nm->arg1 = x->xlat_pair;
        nm->arg2 = 0;
        if (flags & IPFW_XLATE_OUTPUT)
                nm->arg2 = 1;
        netisr_sendmsg(&nm->base, cpuid);
}

static struct mbuf *
ipfw_setup_local(struct mbuf *m, const int hlen, struct ip_fw_args *args,
    struct ip_fw_local *local, struct ip **ip0)
{
        struct ip *ip = mtod(m, struct ip *);
        struct tcphdr *tcp;
        struct udphdr *udp;

        /*
         * Collect parameters into local variables for faster matching.
         */
        if (hlen == 0) {        /* do not grab addresses for non-ip pkts */
                local->proto = args->f_id.proto = 0;    /* mark f_id invalid */
                goto done;
        }

        local->proto = args->f_id.proto = ip->ip_p;
        local->src_ip = ip->ip_src;
        local->dst_ip = ip->ip_dst;
        local->offset = ntohs(ip->ip_off) & IP_OFFMASK;
        local->ip_len = ntohs(ip->ip_len);

#define PULLUP_TO(len)                                  \
do {                                                    \
        if (m->m_len < (len)) {                         \
                args->m = m = m_pullup(m, (len));       \
                if (m == NULL) {                        \
                        ip = NULL;                      \
                        goto done;                      \
                }                                       \
                ip = mtod(m, struct ip *);              \
        }                                               \
} while (0)

        if (local->offset == 0) {
                switch (local->proto) {
                case IPPROTO_TCP:
                        PULLUP_TO(hlen + sizeof(struct tcphdr));
                        local->tcp = tcp = L3HDR(struct tcphdr, ip);
                        local->dst_port = tcp->th_dport;
                        local->src_port = tcp->th_sport;
                        args->f_id.flags = tcp->th_flags;
                        break;

                case IPPROTO_UDP:
                        PULLUP_TO(hlen + sizeof(struct udphdr));
                        udp = L3HDR(struct udphdr, ip);
                        local->dst_port = udp->uh_dport;
                        local->src_port = udp->uh_sport;
                        break;

                case IPPROTO_ICMP:
                        PULLUP_TO(hlen + 4);    /* type, code and checksum. */
                        args->f_id.flags = L3HDR(struct icmp, ip)->icmp_type;
                        break;

                default:
                        break;
                }
        }

#undef PULLUP_TO

        args->f_id.src_ip = ntohl(local->src_ip.s_addr);
        args->f_id.dst_ip = ntohl(local->dst_ip.s_addr);
        args->f_id.src_port = local->src_port = ntohs(local->src_port);
        args->f_id.dst_port = local->dst_port = ntohs(local->dst_port);
done:
        *ip0 = ip;
        return (m);
}

static struct mbuf *
ipfw_rehashm(struct mbuf *m, const int hlen, struct ip_fw_args *args,
    struct ip_fw_local *local, struct ip **ip0)
{
        m->m_flags &= ~M_HASH;
        ip_hashfn(&m, 0);
        args->m = m;
        if (m == NULL) {
                *ip0 = NULL;
                return (NULL);
        }
        KASSERT(m->m_flags & M_HASH, ("no hash"));

        /* 'm' might be changed by ip_hashfn(). */
        return (ipfw_setup_local(m, hlen, args, local, ip0));
}

/*
 * The main check routine for the firewall.
 *
 * All arguments are in args so we can modify them and return them
 * back to the caller.
 *
 * Parameters:
 *
 *      args->m (in/out) The packet; we set to NULL when/if we nuke it.
 *              Starts with the IP header.
 *      args->eh (in)   Mac header if present, or NULL for layer3 packet.
 *      args->oif       Outgoing interface, or NULL if packet is incoming.
 *              The incoming interface is in the mbuf. (in)
 *
 *      args->rule      Pointer to the last matching rule (in/out)
 *      args->f_id      Addresses grabbed from the packet (out)
 *
 * Return value:
 *
 *      If the packet was denied/rejected and has been dropped, *m is equal
 *      to NULL upon return.
 *
 *      IP_FW_DENY      the packet must be dropped.
 *      IP_FW_PASS      The packet is to be accepted and routed normally.
 *      IP_FW_DIVERT    Divert the packet to port (args->cookie)
 *      IP_FW_TEE       Tee the packet to port (args->cookie)
 *      IP_FW_DUMMYNET  Send the packet to pipe/queue (args->cookie)
 *      IP_FW_CONTINUE  Continue processing on another cpu.
 */
static int
ipfw_chk(struct ip_fw_args *args)
{
        /*
         * Local variables hold state during the processing of a packet.
         *
         * IMPORTANT NOTE: to speed up the processing of rules, there
         * are some assumption on the values of the variables, which
         * are documented here. Should you change them, please check
         * the implementation of the various instructions to make sure
         * that they still work.
         *
         * args->eh     The MAC header. It is non-null for a layer2
         *      packet, it is NULL for a layer-3 packet.
         *
         * m | args->m  Pointer to the mbuf, as received from the caller.
         *      It may change if ipfw_chk() does an m_pullup, or if it
         *      consumes the packet because it calls send_reject().
         *      XXX This has to change, so that ipfw_chk() never modifies
         *      or consumes the buffer.
         * ip   is simply an alias of the value of m, and it is kept
         *      in sync with it (the packet is  supposed to start with
         *      the ip header).
         */
        struct mbuf *m = args->m;
        struct ip *ip = mtod(m, struct ip *);

        /*
         * oif | args->oif      If NULL, ipfw_chk has been called on the
         *      inbound path (ether_input, ip_input).
         *      If non-NULL, ipfw_chk has been called on the outbound path
         *      (ether_output, ip_output).
         */
        struct ifnet *oif = args->oif;

        struct ip_fw *f = NULL;         /* matching rule */
        int retval = IP_FW_PASS;
        struct m_tag *mtag;
        struct divert_info *divinfo;
        struct ipfw_state *s;

        /*
         * hlen The length of the IPv4 header.
         *      hlen >0 means we have an IPv4 packet.
         */
        u_int hlen = 0;         /* hlen >0 means we have an IP pkt */

        struct ip_fw_local lc;

        /*
         * dyn_dir = MATCH_UNKNOWN when rules unchecked,
         *      MATCH_NONE when checked and not matched (dyn_f = NULL),
         *      MATCH_FORWARD or MATCH_REVERSE otherwise (dyn_f != NULL)
         */
        int dyn_dir = MATCH_UNKNOWN;
        struct ip_fw *dyn_f = NULL;
        int cpuid = mycpuid;
        struct ipfw_context *ctx;

        ASSERT_NETISR_NCPUS(cpuid);
        ctx = ipfw_ctx[cpuid];

        if (m->m_pkthdr.fw_flags & IPFW_MBUF_GENERATED)
                return IP_FW_PASS;      /* accept */

        if (args->eh == NULL ||         /* layer 3 packet */
            (m->m_pkthdr.len >= sizeof(struct ip) &&
             ntohs(args->eh->ether_type) == ETHERTYPE_IP))
                hlen = ip->ip_hl << 2;

        memset(&lc, 0, sizeof(lc));

        m = ipfw_setup_local(m, hlen, args, &lc, &ip);
        if (m == NULL)
                goto pullup_failed;

        if (args->rule) {
                /*
                 * Packet has already been tagged. Look for the next rule
                 * to restart processing.
                 *
                 * If fw_one_pass != 0 then just accept it.
                 * XXX should not happen here, but optimized out in
                 * the caller.
                 */
                if (fw_one_pass && (args->flags & IP_FWARG_F_CONT) == 0)
                        return IP_FW_PASS;
                args->flags &= ~IP_FWARG_F_CONT;

                /* This rule is being/has been flushed */
                if (ipfw_flushing)
                        return IP_FW_DENY;

                KASSERT(args->rule->cpuid == cpuid,
                        ("rule used on cpu%d", cpuid));

                /* This rule was deleted */
                if (args->rule->rule_flags & IPFW_RULE_F_INVALID)
                        return IP_FW_DENY;

                if (args->xlat != NULL) {
                        struct ipfw_xlat *x = args->xlat;

                        /* This xlat is being deleted. */
                        if (x->xlat_invalid)
                                return IP_FW_DENY;

                        f = args->rule;

                        dyn_f = f;
                        dyn_dir = (args->flags & IP_FWARG_F_XLATFWD) ?
                            MATCH_FORWARD : MATCH_REVERSE;

                        if (args->flags & IP_FWARG_F_XLATINS) {
                                KASSERT(x->xlat_flags & IPFW_STATE_F_XLATSLAVE,
                                    ("not slave %u state", x->xlat_type));
                                s = ipfw_state_link(ctx, &x->xlat_st);
                                if (s != NULL) {
                                        ctx->ipfw_xlate_conflicts++;
                                        if (IPFW_STATE_ISDEAD(s)) {
                                                ipfw_state_remove(ctx, s);
                                                s = ipfw_state_link(ctx,
                                                    &x->xlat_st);
                                        }
                                        if (s != NULL) {
                                                if (bootverbose) {
                                                        kprintf("ipfw: "
                                                        "slave %u state "
                                                        "conflicts %u state\n",
                                                        x->xlat_type,
                                                        s->st_type);
                                                }
                                                ipfw_xlat_invalidate(x);
                                                return IP_FW_DENY;
                                        }
                                        ctx->ipfw_xlate_cresolved++;
                                }
                        } else {
                                ipfw_state_update(&args->f_id, dyn_dir,
                                    lc.tcp, &x->xlat_st);
                        }
                } else {
                        /* TODO: setup dyn_f, dyn_dir */

                        f = args->rule->next_rule;
                        if (f == NULL)
                                f = lookup_next_rule(args->rule);
                }
        } else {
                /*
                 * Find the starting rule. It can be either the first
                 * one, or the one after divert_rule if asked so.
                 */
                int skipto;

                KKASSERT((args->flags &
                    (IP_FWARG_F_XLATINS | IP_FWARG_F_CONT)) == 0);
                KKASSERT(args->xlat == NULL);

                mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL);
                if (mtag != NULL) {
                        divinfo = m_tag_data(mtag);
                        skipto = divinfo->skipto;
                } else {
                        skipto = 0;
                }

                f = ctx->ipfw_layer3_chain;
                if (args->eh == NULL && skipto != 0) {
                        /* No skipto during rule flushing */
                        if (ipfw_flushing)
                                return IP_FW_DENY;

                        if (skipto >= IPFW_DEFAULT_RULE)
                                return IP_FW_DENY; /* invalid */

                        while (f && f->rulenum <= skipto)
                                f = f->next;
                        if (f == NULL)  /* drop packet */
                                return IP_FW_DENY;
                } else if (ipfw_flushing) {
                        /* Rules are being flushed; skip to default rule */
                        f = ctx->ipfw_default_rule;
                }
        }
        if ((mtag = m_tag_find(m, PACKET_TAG_IPFW_DIVERT, NULL)) != NULL)
                m_tag_delete(m, mtag);

        /*
         * Now scan the rules, and parse microinstructions for each rule.
         */
        for (; f; f = f->next) {
                int l, cmdlen;
                ipfw_insn *cmd;
                int skip_or; /* skip rest of OR block */

again:
                if (ctx->ipfw_set_disable & (1 << f->set)) {
                        args->xlat = NULL;
                        continue;
                }

                if (args->xlat != NULL) {
                        args->xlat = NULL;
                        l = f->cmd_len - f->act_ofs;
                        cmd = ACTION_PTR(f);
                } else {
                        l = f->cmd_len;
                        cmd = f->cmd;
                }

                skip_or = 0;
                for (; l > 0; l -= cmdlen, cmd += cmdlen) {
                        int match;

                        /*
                         * check_body is a jump target used when we find a
                         * CHECK_STATE, and need to jump to the body of
                         * the target rule.
                         */
check_body:
                        cmdlen = F_LEN(cmd);
                        /*
                         * An OR block (insn_1 || .. || insn_n) has the
                         * F_OR bit set in all but the last instruction.
                         * The first match will set "skip_or", and cause
                         * the following instructions to be skipped until
                         * past the one with the F_OR bit clear.
                         */
                        if (skip_or) {          /* skip this instruction */
                                if ((cmd->len & F_OR) == 0)
                                        skip_or = 0;    /* next one is good */
                                continue;
                        }
                        match = 0; /* set to 1 if we succeed */

                        switch (cmd->opcode) {
                        /*
                         * The first set of opcodes compares the packet's
                         * fields with some pattern, setting 'match' if a
                         * match is found. At the end of the loop there is
                         * logic to deal with F_NOT and F_OR flags associated
                         * with the opcode.
                         */
                        case O_NOP:
                                match = 1;
                                break;

                        case O_FORWARD_MAC:
                                kprintf("ipfw: opcode %d unimplemented\n",
                                        cmd->opcode);
                                break;

                        case O_GID:
                        case O_UID:
                                /*
                                 * We only check offset == 0 && proto != 0,
                                 * as this ensures that we have an IPv4
                                 * packet with the ports info.
                                 */
                                if (lc.offset!=0)
                                        break;

                                match = ipfw_match_uid(&args->f_id, oif,
                                        cmd->opcode,
                                        (uid_t)((ipfw_insn_u32 *)cmd)->d[0]);
                                break;

                        case O_RECV:
                                match = iface_match(m->m_pkthdr.rcvif,
                                    (ipfw_insn_if *)cmd);
                                break;

                        case O_XMIT:
                                match = iface_match(oif, (ipfw_insn_if *)cmd);
                                break;

                        case O_VIA:
                                match = iface_match(oif ? oif :
                                    m->m_pkthdr.rcvif, (ipfw_insn_if *)cmd);
                                break;

                        case O_MACADDR2:
                                if (args->eh != NULL) { /* have MAC header */
                                        uint32_t *want = (uint32_t *)
                                                ((ipfw_insn_mac *)cmd)->addr;
                                        uint32_t *mask = (uint32_t *)
                                                ((ipfw_insn_mac *)cmd)->mask;
                                        uint32_t *hdr = (uint32_t *)args->eh;

                                        match =
                                        (want[0] == (hdr[0] & mask[0]) &&
                                         want[1] == (hdr[1] & mask[1]) &&
                                         want[2] == (hdr[2] & mask[2]));
                                }
                                break;

                        case O_MAC_TYPE:
                                if (args->eh != NULL) {
                                        uint16_t t =
                                            ntohs(args->eh->ether_type);
                                        uint16_t *p =
                                            ((ipfw_insn_u16 *)cmd)->ports;
                                        int i;

                                        /* Special vlan handling */
                                        if (m->m_flags & M_VLANTAG)
                                                t = ETHERTYPE_VLAN;

                                        for (i = cmdlen - 1; !match && i > 0;
                                             i--, p += 2) {
                                                match =
                                                (t >= p[0] && t <= p[1]);
                                        }
                                }
                                break;

                        case O_FRAG:
                                match = (hlen > 0 && lc.offset != 0);
                                break;

                        case O_IPFRAG:
                                if (hlen > 0) {
                                        uint16_t off;

                                        off = ntohs(ip->ip_off);
                                        if (off & (IP_MF | IP_OFFMASK))
                                                match = 1;
                                }
                                break;

                        case O_IN:      /* "out" is "not in" */
                                match = (oif == NULL);
                                break;

                        case O_LAYER2:
                                match = (args->eh != NULL);
                                break;

                        case O_PROTO:
                                /*
                                 * We do not allow an arg of 0 so the
                                 * check of "proto" only suffices.
                                 */
                                match = (lc.proto == cmd->arg1);
                                break;

                        case O_IP_SRC:
                                match = (hlen > 0 &&
                                    ((ipfw_insn_ip *)cmd)->addr.s_addr ==
                                    lc.src_ip.s_addr);
                                break;

                        case O_IP_SRC_MASK:
                                match = (hlen > 0 &&
                                    ((ipfw_insn_ip *)cmd)->addr.s_addr ==
                                     (lc.src_ip.s_addr &
                                     ((ipfw_insn_ip *)cmd)->mask.s_addr));
                                break;

                        case O_IP_SRC_ME:
                                if (hlen > 0) {
                                        struct ifnet *tif;

                                        tif = INADDR_TO_IFP(&lc.src_ip);
                                        match = (tif != NULL);
                                }
                                break;

                        case O_IP_SRC_TABLE:
                                match = ipfw_table_lookup(ctx, cmd->arg1,
                                    &lc.src_ip);
                                break;

                        case O_IP_SRC_IFIP:
                                match = ipfw_match_ifip((ipfw_insn_ifip *)cmd,
                                    &lc.src_ip);
                                break;

                        case O_IP_DST_SET:
                        case O_IP_SRC_SET:
                                if (hlen > 0) {
                                        uint32_t *d = (uint32_t *)(cmd + 1);
                                        uint32_t addr =
                                            cmd->opcode == O_IP_DST_SET ?
                                                args->f_id.dst_ip :
                                                args->f_id.src_ip;

                                        if (addr < d[0])
                                                break;
                                        addr -= d[0]; /* subtract base */
                                        match =
                                        (addr < cmd->arg1) &&
                                         (d[1 + (addr >> 5)] &
                                          (1 << (addr & 0x1f)));
                                }
                                break;

                        case O_IP_DST:
                                match = (hlen > 0 &&
                                    ((ipfw_insn_ip *)cmd)->addr.s_addr ==
                                    lc.dst_ip.s_addr);
                                break;

                        case O_IP_DST_MASK:
                                match = (hlen > 0) &&
                                    (((ipfw_insn_ip *)cmd)->addr.s_addr ==
                                     (lc.dst_ip.s_addr &
                                     ((ipfw_insn_ip *)cmd)->mask.s_addr));
                                break;

                        case O_IP_DST_ME:
                                if (hlen > 0) {
                                        struct ifnet *tif;

                                        tif = INADDR_TO_IFP(&lc.dst_ip);
                                        match = (tif != NULL);
                                }
                                break;

                        case O_IP_DST_TABLE:
                                match = ipfw_table_lookup(ctx, cmd->arg1,
                                    &lc.dst_ip);
                                break;

                        case O_IP_DST_IFIP:
                                match = ipfw_match_ifip((ipfw_insn_ifip *)cmd,
                                    &lc.dst_ip);
                                break;

                        case O_IP_SRCPORT:
                        case O_IP_DSTPORT:
                                /*
                                 * offset == 0 && proto != 0 is enough
                                 * to guarantee that we have an IPv4
                                 * packet with port info.
                                 */
                                if ((lc.proto==IPPROTO_UDP ||
                                     lc.proto==IPPROTO_TCP)
                                    && lc.offset == 0) {
                                        uint16_t x =
                                            (cmd->opcode == O_IP_SRCPORT) ?
                                                lc.src_port : lc.dst_port;
                                        uint16_t *p =
                                            ((ipfw_insn_u16 *)cmd)->ports;
                                        int i;

                                        for (i = cmdlen - 1; !match && i > 0;
                                             i--, p += 2) {
                                                match =
                                                (x >= p[0] && x <= p[1]);
                                        }
                                }
                                break;

                        case O_ICMPCODE:
                                match = (lc.offset == 0 &&
                                    lc.proto==IPPROTO_ICMP &&
                                    icmpcode_match(ip, (ipfw_insn_u32 *)cmd));
                                break;

                        case O_ICMPTYPE:
                                match = (lc.offset == 0 &&
                                    lc.proto==IPPROTO_ICMP &&
                                    icmptype_match(ip, (ipfw_insn_u32 *)cmd));
                                break;

                        case O_IPOPT:
                                match = (hlen > 0 && ipopts_match(ip, cmd));
                                break;

                        case O_IPVER:
                                match = (hlen > 0 && cmd->arg1 == ip->ip_v);
                                break;

                        case O_IPTTL:
                                match = (hlen > 0 && cmd->arg1 == ip->ip_ttl);
                                break;

                        case O_IPID:
                                match = (hlen > 0 &&
                                    cmd->arg1 == ntohs(ip->ip_id));
                                break;

                        case O_IPLEN:
                                match = (hlen > 0 && cmd->arg1 == lc.ip_len);
                                break;

                        case O_IPPRECEDENCE:
                                match = (hlen > 0 &&
                                    (cmd->arg1 == (ip->ip_tos & 0xe0)));
                                break;

                        case O_IPTOS:
                                match = (hlen > 0 &&
                                    flags_match(cmd, ip->ip_tos));
                                break;

                        case O_TCPFLAGS:
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 &&
                                    flags_match(cmd,
                                        L3HDR(struct tcphdr,ip)->th_flags));
                                break;

                        case O_TCPOPTS:
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 && tcpopts_match(ip, cmd));
                                break;

                        case O_TCPSEQ:
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 &&
                                    ((ipfw_insn_u32 *)cmd)->d[0] ==
                                        L3HDR(struct tcphdr,ip)->th_seq);
                                break;

                        case O_TCPACK:
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 &&
                                    ((ipfw_insn_u32 *)cmd)->d[0] ==
                                        L3HDR(struct tcphdr,ip)->th_ack);
                                break;

                        case O_TCPWIN:
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 &&
                                    cmd->arg1 ==
                                        L3HDR(struct tcphdr,ip)->th_win);
                                break;

                        case O_ESTAB:
                                /* reject packets which have SYN only */
                                /* XXX should i also check for TH_ACK ? */
                                match = (lc.proto == IPPROTO_TCP &&
                                    lc.offset == 0 &&
                                    (L3HDR(struct tcphdr,ip)->th_flags &
                                     (TH_RST | TH_ACK | TH_SYN)) != TH_SYN);
                                break;

                        case O_LOG:
                                if (fw_verbose) {
                                        ipfw_log(ctx, f, hlen, args->eh, m,
                                            oif);
                                }
                                match = 1;
                                break;

                        case O_PROB:
                                match = (krandom() <
                                        ((ipfw_insn_u32 *)cmd)->d[0]);
                                break;

                        /*
                         * The second set of opcodes represents 'actions',
                         * i.e. the terminal part of a rule once the packet
                         * matches all previous patterns.
                         * Typically there is only one action for each rule,
                         * and the opcode is stored at the end of the rule
                         * (but there are exceptions -- see below).
                         *
                         * In general, here we set retval and terminate the
                         * outer loop (would be a 'break 3' in some language,
                         * but we need to do a 'goto done').
                         *
                         * Exceptions:
                         * O_COUNT and O_SKIPTO actions:
                         *   instead of terminating, we jump to the next rule
                         *   ('goto next_rule', equivalent to a 'break 2'),
                         *   or to the SKIPTO target ('goto again' after
                         *   having set f, cmd and l), respectively.
                         *
                         * O_LIMIT and O_KEEP_STATE, O_REDIRECT: these opcodes
                         *   are not real 'actions', and are stored right
                         *   before the 'action' part of the rule.
                         *   These opcodes try to install an entry in the
                         *   state tables; if successful, we continue with
                         *   the next opcode (match=1; break;), otherwise
                         *   the packet must be dropped ('goto done' after
                         *   setting retval).  If static rules are changed
                         *   during the state installation, the packet will
                         *   be dropped and rule's stats will not beupdated
                         *   ('return IP_FW_DENY').
                         *
                         * O_PROBE_STATE and O_CHECK_STATE: these opcodes
                         *   cause a lookup of the state table, and a jump
                         *   to the 'action' part of the parent rule
                         *   ('goto check_body') if an entry is found, or
                         *   (CHECK_STATE only) a jump to the next rule if
                         *   the entry is not found ('goto next_rule').
                         *   The result of the lookup is cached to make
                         *   further instances of these opcodes are
                         *   effectively NOPs.  If static rules are changed
                         *   during the state looking up, the packet will
                         *   be dropped and rule's stats will not be updated
                         *   ('return IP_FW_DENY').
                         */
                        case O_REDIRECT:
                                if (f->cross_rules == NULL) {
                                        /*
                                         * This rule was not completely setup;
                                         * move on to the next rule.
                                         */
                                        goto next_rule;
                                }
                                /*
                                 * Apply redirect only on input path and
                                 * only to non-fragment TCP segments or
                                 * UDP datagrams.
                                 *
                                 * Does _not_ work with layer2 filtering.
                                 */
                                if (oif != NULL || args->eh != NULL ||
                                    (ip->ip_off & htons(IP_MF | IP_OFFMASK)) ||
                                    (lc.proto != IPPROTO_TCP &&
                                     lc.proto != IPPROTO_UDP))
                                        break;
                                /* FALL THROUGH */
                        case O_LIMIT:
                        case O_KEEP_STATE:
                                if (hlen == 0)
                                        break;
                                s = ipfw_state_install(ctx, f,
                                    (ipfw_insn_limit *)cmd, args, lc.tcp);
                                if (s == NULL) {
                                        retval = IP_FW_DENY;
                                        goto done; /* error/limit violation */
                                }
                                s->st_pcnt++;
                                s->st_bcnt += lc.ip_len;

                                if (s->st_type == O_REDIRECT) {
                                        struct in_addr oaddr;
                                        uint16_t oport;
                                        struct ipfw_xlat *slave_x, *x;
                                        struct ipfw_state *dup;

                                        x = (struct ipfw_xlat *)s;
                                        ipfw_xlate(x, m, &oaddr, &oport);
                                        m = ipfw_rehashm(m, hlen, args, &lc,
                                            &ip);
                                        if (m == NULL) {
                                                ipfw_state_del(ctx, s);
                                                goto pullup_failed;
                                        }

                                        cpuid = netisr_hashcpu(
                                            m->m_pkthdr.hash);

                                        slave_x = (struct ipfw_xlat *)
                                            ipfw_state_alloc(ctx, &args->f_id,
                                            O_REDIRECT, f->cross_rules[cpuid],
                                            lc.tcp);
                                        if (slave_x == NULL) {
                                                ipfw_state_del(ctx, s);
                                                retval = IP_FW_DENY;
                                                goto done;
                                        }
                                        slave_x->xlat_addr = oaddr.s_addr;
                                        slave_x->xlat_port = oport;
                                        slave_x->xlat_dir = MATCH_REVERSE;
                                        slave_x->xlat_flags |=
                                            IPFW_STATE_F_XLATSRC |
                                            IPFW_STATE_F_XLATSLAVE;

                                        slave_x->xlat_pair = x;
                                        slave_x->xlat_pcpu = mycpuid;
                                        x->xlat_pair = slave_x;
                                        x->xlat_pcpu = cpuid;

                                        ctx->ipfw_xlated++;
                                        if (cpuid != mycpuid) {
                                                ctx->ipfw_xlate_split++;
                                                ipfw_xlate_redispatch(
                                                    m, cpuid, x,
                                                    IPFW_XLATE_INSERT |
                                                    IPFW_XLATE_FORWARD);
                                                args->m = NULL;
                                                return (IP_FW_REDISPATCH);
                                        }

                                        dup = ipfw_state_link(ctx,
                                            &slave_x->xlat_st);
                                        if (dup != NULL) {
                                                ctx->ipfw_xlate_conflicts++;
                                                if (IPFW_STATE_ISDEAD(dup)) {
                                                        ipfw_state_remove(ctx,
                                                            dup);
                                                        dup = ipfw_state_link(
                                                        ctx, &slave_x->xlat_st);
                                                }
                                                if (dup != NULL) {
                                                        if (bootverbose) {
                                                            kprintf("ipfw: "
                                                            "slave %u state "
                                                            "conflicts "
                                                            "%u state\n",
                                                            x->xlat_type,
                                                            s->st_type);
                                                        }
                                                        ipfw_state_del(ctx, s);
                                                        return (IP_FW_DENY);
                                                }
                                                ctx->ipfw_xlate_cresolved++;
                                        }
                                }
                                match = 1;
                                break;

                        case O_PROBE_STATE:
                        case O_CHECK_STATE:
                                /*
                                 * States are checked at the first keep-state
                                 * check-state occurrence, with the result
                                 * being stored in dyn_dir.  The compiler
                                 * introduces a PROBE_STATE instruction for
                                 * us when we have a KEEP_STATE/LIMIT/RDR
                                 * (because PROBE_STATE needs to be run first).
                                 */
                                s = NULL;
                                if (dyn_dir == MATCH_UNKNOWN) {
                                        s = ipfw_state_lookup(ctx,
                                            &args->f_id, &dyn_dir, lc.tcp);
                                }
                                if (s == NULL ||
                                    (s->st_type == O_REDIRECT &&
                                     (args->eh != NULL ||
                                      (ip->ip_off & htons(IP_MF | IP_OFFMASK)) ||
                                      (lc.proto != IPPROTO_TCP &&
                                       lc.proto != IPPROTO_UDP)))) {
                                        /*
                                         * State not found. If CHECK_STATE,
                                         * skip to next rule, if PROBE_STATE
                                         * just ignore and continue with next
                                         * opcode.
                                         */
                                        if (cmd->opcode == O_CHECK_STATE)
                                                goto next_rule;
                                        match = 1;
                                        break;
                                }

                                s->st_pcnt++;
                                s->st_bcnt += lc.ip_len;

                                if (s->st_type == O_REDIRECT) {
                                        struct ipfw_xlat *x =
                                            (struct ipfw_xlat *)s;

                                        if (oif != NULL &&
                                            x->xlat_ifp == NULL) {
                                                KASSERT(x->xlat_flags &
                                                    IPFW_STATE_F_XLATSLAVE,
                                                    ("master rdr state "
                                                     "missing ifp"));
                                                x->xlat_ifp = oif;
                                        } else if (
                                            (oif != NULL && x->xlat_ifp!=oif) ||
                                            (oif == NULL &&
                                             x->xlat_ifp!=m->m_pkthdr.rcvif)) {
                                                retval = IP_FW_DENY;
                                                goto done;
                                        }
                                        if (x->xlat_dir != dyn_dir)
                                                goto skip_xlate;

                                        ipfw_xlate(x, m, NULL, NULL);
                                        m = ipfw_rehashm(m, hlen, args, &lc,
                                            &ip);
                                        if (m == NULL)
                                                goto pullup_failed;

                                        cpuid = netisr_hashcpu(
                                            m->m_pkthdr.hash);
                                        if (cpuid != mycpuid) {
                                                uint32_t xlate = 0;

                                                if (oif != NULL) {
                                                        xlate |=
                                                            IPFW_XLATE_OUTPUT;
                                                }
                                                if (dyn_dir == MATCH_FORWARD) {
                                                        xlate |=
                                                            IPFW_XLATE_FORWARD;
                                                }
                                                ipfw_xlate_redispatch(m, cpuid,
                                                    x, xlate);
                                                args->m = NULL;
                                                return (IP_FW_REDISPATCH);
                                        }

                                        KKASSERT(x->xlat_pcpu == mycpuid);
                                        ipfw_state_update(&args->f_id, dyn_dir,
                                            lc.tcp, &x->xlat_pair->xlat_st);
                                }
skip_xlate:
                                /*
                                 * Found a rule from a state; jump to the
                                 * 'action' part of the rule.
                                 */
                                f = s->st_rule;
                                KKASSERT(f->cpuid == mycpuid);

                                cmd = ACTION_PTR(f);
                                l = f->cmd_len - f->act_ofs;
                                dyn_f = f;
                                goto check_body;

                        case O_ACCEPT:
                                retval = IP_FW_PASS;    /* accept */
                                goto done;

                        case O_DEFRAG:
                                if (f->cross_rules == NULL) {
                                        /*
                                         * This rule was not completely setup;
                                         * move on to the next rule.
                                         */
                                        goto next_rule;
                                }

                                /*
                                 * Don't defrag for l2 packets, output packets
                                 * or non-fragments.
                                 */
                                if (oif != NULL || args->eh != NULL ||
                                    (ip->ip_off & htons(IP_MF | IP_OFFMASK)) == 0)
                                        goto next_rule;

                                ctx->ipfw_frags++;
                                m = ip_reass(m);
                                args->m = m;
                                if (m == NULL) {
                                        retval = IP_FW_PASS;
                                        goto done;
                                }
                                ctx->ipfw_defraged++;
                                KASSERT((m->m_flags & M_HASH) == 0,
                                    ("hash not cleared"));

                                /* Update statistics */
                                f->pcnt++;
                                f->bcnt += lc.ip_len;
                                f->timestamp = time_second;

                                ip = mtod(m, struct ip *);
                                hlen = ip->ip_hl << 2;
                                ip->ip_len = htons(ntohs(ip->ip_len) + hlen);

                                ip_hashfn(&m, 0);
                                args->m = m;
                                if (m == NULL)
                                        goto pullup_failed;

                                KASSERT(m->m_flags & M_HASH, ("no hash"));
                                cpuid = netisr_hashcpu(m->m_pkthdr.hash);
                                if (cpuid != mycpuid) {
                                        ctx->ipfw_defrag_remote++;
                                        ipfw_defrag_redispatch(m, cpuid, f);
                                        args->m = NULL;
                                        return (IP_FW_REDISPATCH);
                                }

                                /* 'm' might be changed by ip_hashfn(). */
                                ip = mtod(m, struct ip *);

                                m = ipfw_setup_local(m, hlen, args, &lc, &ip);
                                if (m == NULL)
                                        goto pullup_failed;

                                /* Move on. */
                                goto next_rule;

                        case O_PIPE:
                        case O_QUEUE:
                                args->rule = f; /* report matching rule */
                                args->cookie = cmd->arg1;
                                retval = IP_FW_DUMMYNET;
                                goto done;

                        case O_DIVERT:
                        case O_TEE:
                                if (args->eh) /* not on layer 2 */
                                        break;

                                mtag = m_tag_get(PACKET_TAG_IPFW_DIVERT,
                                    sizeof(*divinfo), M_INTWAIT | M_NULLOK);
                                if (mtag == NULL) {
                                        retval = IP_FW_DENY;
                                        goto done;
                                }
                                divinfo = m_tag_data(mtag);

                                divinfo->skipto = f->rulenum;
                                divinfo->port = cmd->arg1;
                                divinfo->tee = (cmd->opcode == O_TEE);
                                m_tag_prepend(m, mtag);

                                args->cookie = cmd->arg1;
                                retval = (cmd->opcode == O_DIVERT) ?
                                         IP_FW_DIVERT : IP_FW_TEE;
                                goto done;

                        case O_COUNT:
                        case O_SKIPTO:
                                f->pcnt++;      /* update stats */
                                f->bcnt += lc.ip_len;
                                f->timestamp = time_second;
                                if (cmd->opcode == O_COUNT)
                                        goto next_rule;
                                /* handle skipto */
                                if (f->next_rule == NULL)
                                        lookup_next_rule(f);
                                f = f->next_rule;
                                goto again;

                        case O_REJECT:
                                /*
                                 * Drop the packet and send a reject notice
                                 * if the packet is not ICMP (or is an ICMP
                                 * query), and it is not multicast/broadcast.
                                 */
                                if (hlen > 0 &&
                                    (lc.proto != IPPROTO_ICMP ||
                                     is_icmp_query(ip)) &&
                                    !(m->m_flags & (M_BCAST|M_MCAST)) &&
                                    !IN_MULTICAST(ntohl(lc.dst_ip.s_addr))) {
                                        send_reject(args, cmd->arg1,
                                            lc.offset, lc.ip_len);
                                        retval = IP_FW_DENY;
                                        goto done;
                                }
                                /* FALLTHROUGH */
                        case O_DENY:
                                retval = IP_FW_DENY;
                                goto done;

                        case O_FORWARD_IP:
                                if (args->eh)   /* not valid on layer2 pkts */
                                        break;
                                if (!dyn_f || dyn_dir == MATCH_FORWARD) {
                                        struct sockaddr_in *sin;

                                        mtag = m_tag_get(PACKET_TAG_IPFORWARD,
                                            sizeof(*sin), M_INTWAIT | M_NULLOK);
                                        if (mtag == NULL) {
                                                retval = IP_FW_DENY;
                                                goto done;
                                        }
                                        sin = m_tag_data(mtag);

                                        /* Structure copy */
                                        *sin = ((ipfw_insn_sa *)cmd)->sa;

                                        m_tag_prepend(m, mtag);
                                        m->m_pkthdr.fw_flags |=
                                                IPFORWARD_MBUF_TAGGED;
                                        m->m_pkthdr.fw_flags &=
                                                ~BRIDGE_MBUF_TAGGED;
                                }
                                retval = IP_FW_PASS;
                                goto done;

                        default:
                                panic("-- unknown opcode %d", cmd->opcode);
                        } /* end of switch() on opcodes */

                        if (cmd->len & F_NOT)
                                match = !match;

                        if (match) {
                                if (cmd->len & F_OR)
                                        skip_or = 1;
                        } else {
                                if (!(cmd->len & F_OR)) /* not an OR block, */
                                        break;          /* try next rule    */
                        }

                }       /* end of inner for, scan opcodes */

next_rule:;             /* try next rule                */

        }               /* end of outer for, scan rules */
        kprintf("+++ ipfw: ouch!, skip past end of rules, denying packet\n");
        return IP_FW_DENY;

done:
        /* Update statistics */
        f->pcnt++;
        f->bcnt += lc.ip_len;
        f->timestamp = time_second;
        return retval;

pullup_failed:
        if (fw_verbose)
                kprintf("pullup failed\n");
        return IP_FW_DENY;
}

static struct mbuf *
ipfw_dummynet_io(struct mbuf *m, int pipe_nr, int dir, struct ip_fw_args *fwa)
{
        struct m_tag *mtag;
        struct dn_pkt *pkt;
        ipfw_insn *cmd;
        const struct ipfw_flow_id *id;
        struct dn_flow_id *fid;

        M_ASSERTPKTHDR(m);

        mtag = m_tag_get(PACKET_TAG_DUMMYNET, sizeof(*pkt),
            M_INTWAIT | M_NULLOK);
        if (mtag == NULL) {
                m_freem(m);
                return (NULL);
        }
        m_tag_prepend(m, mtag);

        pkt = m_tag_data(mtag);
        bzero(pkt, sizeof(*pkt));

        cmd = fwa->rule->cmd + fwa->rule->act_ofs;
        if (cmd->opcode == O_LOG)
                cmd += F_LEN(cmd);
        KASSERT(cmd->opcode == O_PIPE || cmd->opcode == O_QUEUE,
                ("Rule is not PIPE or QUEUE, opcode %d", cmd->opcode));

        pkt->dn_m = m;
        pkt->dn_flags = (dir & DN_FLAGS_DIR_MASK);
        pkt->ifp = fwa->oif;
        pkt->pipe_nr = pipe_nr;

        pkt->cpuid = mycpuid;
        pkt->msgport = netisr_curport();

        id = &fwa->f_id;
        fid = &pkt->id;
        fid->fid_dst_ip = id->dst_ip;
        fid->fid_src_ip = id->src_ip;
        fid->fid_dst_port = id->dst_port;
        fid->fid_src_port = id->src_port;
        fid->fid_proto = id->proto;
        fid->fid_flags = id->flags;

        ipfw_ref_rule(fwa->rule);
        pkt->dn_priv = fwa->rule;
        pkt->dn_unref_priv = ipfw_unref_rule;

        if (cmd->opcode == O_PIPE)
                pkt->dn_flags |= DN_FLAGS_IS_PIPE;

        m->m_pkthdr.fw_flags |= DUMMYNET_MBUF_TAGGED;
        return (m);
}

/*
 * When a rule is added/deleted, clear the next_rule pointers in all rules.
 * These will be reconstructed on the fly as packets are matched.
 */
static void
ipfw_flush_rule_ptrs(struct ipfw_context *ctx)
{
        struct ip_fw *rule;

        for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
                rule->next_rule = NULL;
}

static void
ipfw_inc_static_count(struct ip_fw *rule)
{
        /* Static rule's counts are updated only on CPU0 */
        KKASSERT(mycpuid == 0);

        static_count++;
        static_ioc_len += IOC_RULESIZE(rule);
}

static void
ipfw_dec_static_count(struct ip_fw *rule)
{
        int l = IOC_RULESIZE(rule);

        /* Static rule's counts are updated only on CPU0 */
        KKASSERT(mycpuid == 0);

        KASSERT(static_count > 0, ("invalid static count %u", static_count));
        static_count--;

        KASSERT(static_ioc_len >= l,
                ("invalid static len %u", static_ioc_len));
        static_ioc_len -= l;
}

static void
ipfw_link_sibling(struct netmsg_ipfw *fwmsg, struct ip_fw *rule)
{
        if (fwmsg->sibling != NULL) {
                KKASSERT(mycpuid > 0 && fwmsg->sibling->cpuid == mycpuid - 1);
                fwmsg->sibling->sibling = rule;
        }
        fwmsg->sibling = rule;
}

static struct ip_fw *
ipfw_create_rule(const struct ipfw_ioc_rule *ioc_rule, uint32_t rule_flags)
{
        struct ip_fw *rule;

        rule = kmalloc(RULESIZE(ioc_rule), M_IPFW, M_WAITOK | M_ZERO);

        rule->act_ofs = ioc_rule->act_ofs;
        rule->cmd_len = ioc_rule->cmd_len;
        rule->rulenum = ioc_rule->rulenum;
        rule->set = ioc_rule->set;
        rule->usr_flags = ioc_rule->usr_flags;

        bcopy(ioc_rule->cmd, rule->cmd, rule->cmd_len * 4 /* XXX */);

        rule->refcnt = 1;
        rule->cpuid = mycpuid;
        rule->rule_flags = rule_flags;

        return rule;
}

static void
ipfw_add_rule_dispatch(netmsg_t nmsg)
{
        struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        rule = ipfw_create_rule(fwmsg->ioc_rule, fwmsg->rule_flags);

        /*
         * Insert rule into the pre-determined position
         */
        if (fwmsg->prev_rule != NULL) {
                struct ip_fw *prev, *next;

                prev = fwmsg->prev_rule;
                KKASSERT(prev->cpuid == mycpuid);

                next = fwmsg->next_rule;
                KKASSERT(next->cpuid == mycpuid);

                rule->next = next;
                prev->next = rule;

                /*
                 * Move to the position on the next CPU
                 * before the msg is forwarded.
                 */
                fwmsg->prev_rule = prev->sibling;
                fwmsg->next_rule = next->sibling;
        } else {
                KKASSERT(fwmsg->next_rule == NULL);
                rule->next = ctx->ipfw_layer3_chain;
                ctx->ipfw_layer3_chain = rule;
        }

        /* Link rule CPU sibling */
        ipfw_link_sibling(fwmsg, rule);

        ipfw_flush_rule_ptrs(ctx);

        if (mycpuid == 0) {
                /* Statistics only need to be updated once */
                ipfw_inc_static_count(rule);

                /* Return the rule on CPU0 */
                nmsg->lmsg.u.ms_resultp = rule;
        }

        if (rule->rule_flags & IPFW_RULE_F_GENTRACK)
                rule->track_ruleid = (uintptr_t)nmsg->lmsg.u.ms_resultp;

        if (fwmsg->cross_rules != NULL) {
                /* Save rules for later use. */
                fwmsg->cross_rules[mycpuid] = rule;
        }

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_crossref_rule_dispatch(netmsg_t nmsg)
{
        struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
        struct ip_fw *rule = fwmsg->sibling;
        int sz = sizeof(struct ip_fw *) * netisr_ncpus;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(rule->rule_flags & IPFW_RULE_F_CROSSREF,
            ("not crossref rule"));

        rule->cross_rules = kmalloc(sz, M_IPFW, M_WAITOK);
        memcpy(rule->cross_rules, fwmsg->cross_rules, sz);

        fwmsg->sibling = rule->sibling;
        netisr_forwardmsg(&fwmsg->base, mycpuid + 1);
}

/*
 * Add a new rule to the list.  Copy the rule into a malloc'ed area,
 * then possibly create a rule number and add the rule to the list.
 * Update the rule_number in the input struct so the caller knows
 * it as well.
 */
static void
ipfw_add_rule(struct ipfw_ioc_rule *ioc_rule, uint32_t rule_flags)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct netmsg_ipfw fwmsg;
        struct ip_fw *f, *prev, *rule;

        ASSERT_NETISR0;

        /*
         * If rulenum is 0, find highest numbered rule before the
         * default rule, and add rule number incremental step.
         */
        if (ioc_rule->rulenum == 0) {
                int step = autoinc_step;

                KKASSERT(step >= IPFW_AUTOINC_STEP_MIN &&
                         step <= IPFW_AUTOINC_STEP_MAX);

                /*
                 * Locate the highest numbered rule before default
                 */
                for (f = ctx->ipfw_layer3_chain; f; f = f->next) {
                        if (f->rulenum == IPFW_DEFAULT_RULE)
                                break;
                        ioc_rule->rulenum = f->rulenum;
                }
                if (ioc_rule->rulenum < IPFW_DEFAULT_RULE - step)
                        ioc_rule->rulenum += step;
        }
        KASSERT(ioc_rule->rulenum != IPFW_DEFAULT_RULE &&
                ioc_rule->rulenum != 0,
                ("invalid rule num %d", ioc_rule->rulenum));

        /*
         * Now find the right place for the new rule in the sorted list.
         */
        for (prev = NULL, f = ctx->ipfw_layer3_chain; f;
             prev = f, f = f->next) {
                if (f->rulenum > ioc_rule->rulenum) {
                        /* Found the location */
                        break;
                }
        }
        KASSERT(f != NULL, ("no default rule?!"));

        /*
         * Duplicate the rule onto each CPU.
         * The rule duplicated on CPU0 will be returned.
         */
        bzero(&fwmsg, sizeof(fwmsg));
        netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_add_rule_dispatch);
        fwmsg.ioc_rule = ioc_rule;
        fwmsg.prev_rule = prev;
        fwmsg.next_rule = prev == NULL ? NULL : f;
        fwmsg.rule_flags = rule_flags;
        if (rule_flags & IPFW_RULE_F_CROSSREF) {
                fwmsg.cross_rules = kmalloc(
                    sizeof(struct ip_fw *) * netisr_ncpus, M_TEMP,
                    M_WAITOK | M_ZERO);
        }

        netisr_domsg_global(&fwmsg.base);
        KKASSERT(fwmsg.prev_rule == NULL && fwmsg.next_rule == NULL);

        rule = fwmsg.base.lmsg.u.ms_resultp;
        KKASSERT(rule != NULL && rule->cpuid == mycpuid);

        if (fwmsg.cross_rules != NULL) {
                netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_crossref_rule_dispatch);
                fwmsg.sibling = rule;
                netisr_domsg_global(&fwmsg.base);
                KKASSERT(fwmsg.sibling == NULL);

                kfree(fwmsg.cross_rules, M_TEMP);

#ifdef KLD_MODULE
                atomic_add_int(&ipfw_gd.ipfw_refcnt, 1);
#endif
        }

        DPRINTF("++ installed rule %d, static count now %d\n",
                rule->rulenum, static_count);
}

/*
 * Free storage associated with a static rule (including derived
 * states/tracks).
 * The caller is in charge of clearing rule pointers to avoid
 * dangling pointers.
 * @return a pointer to the next entry.
 * Arguments are not checked, so they better be correct.
 */
static struct ip_fw *
ipfw_delete_rule(struct ipfw_context *ctx,
                 struct ip_fw *prev, struct ip_fw *rule)
{
        struct ip_fw *n;

        n = rule->next;
        if (prev == NULL)
                ctx->ipfw_layer3_chain = n;
        else
                prev->next = n;

        /* Mark the rule as invalid */
        rule->rule_flags |= IPFW_RULE_F_INVALID;
        rule->next_rule = NULL;
        rule->sibling = NULL;
#ifdef foo
        /* Don't reset cpuid here; keep various assertion working */
        rule->cpuid = -1;
#endif

        /* Statistics only need to be updated once */
        if (mycpuid == 0)
                ipfw_dec_static_count(rule);

        if ((rule->rule_flags & IPFW_RULE_F_CROSSREF) == 0) {
                /* Try to free this rule */
                ipfw_free_rule(rule);
        } else {
                /* TODO: check staging area. */
                if (mycpuid == 0) {
                        rule->next = ipfw_gd.ipfw_crossref_free;
                        ipfw_gd.ipfw_crossref_free = rule;
                }
        }

        /* Return the next rule */
        return n;
}

static void
ipfw_flush_dispatch(netmsg_t nmsg)
{
        int kill_default = nmsg->lmsg.u.ms_result;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        /*
         * Flush states.
         */
        ipfw_state_flush(ctx, NULL);
        KASSERT(ctx->ipfw_state_cnt == 0,
            ("%d pcpu states remain", ctx->ipfw_state_cnt));
        ctx->ipfw_state_loosecnt = 0;
        ctx->ipfw_state_lastexp = 0;

        /*
         * Flush tracks.
         */
        ipfw_track_flush(ctx, NULL);
        ctx->ipfw_track_lastexp = 0;
        if (ctx->ipfw_trkcnt_spare != NULL) {
                kfree(ctx->ipfw_trkcnt_spare, M_IPFW);
                ctx->ipfw_trkcnt_spare = NULL;
        }

        ipfw_flush_rule_ptrs(ctx); /* more efficient to do outside the loop */

        while ((rule = ctx->ipfw_layer3_chain) != NULL &&
               (kill_default || rule->rulenum != IPFW_DEFAULT_RULE))
                ipfw_delete_rule(ctx, NULL, rule);

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

/*
 * Deletes all rules from a chain (including the default rule
 * if the second argument is set).
 */
static void
ipfw_flush(int kill_default)
{
        struct netmsg_base nmsg;
#ifdef INVARIANTS
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        int state_cnt;
#endif

        ASSERT_NETISR0;

        /*
         * If 'kill_default' then caller has done the necessary
         * msgport syncing; unnecessary to do it again.
         */
        if (!kill_default) {
                /*
                 * Let ipfw_chk() know the rules are going to
                 * be flushed, so it could jump directly to
                 * the default rule.
                 */
                ipfw_flushing = 1;
                /* XXX use priority sync */
                netmsg_service_sync();
        }

        /*
         * Press the 'flush' button
         */
        bzero(&nmsg, sizeof(nmsg));
        netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_flush_dispatch);
        nmsg.lmsg.u.ms_result = kill_default;
        netisr_domsg_global(&nmsg);
        ipfw_gd.ipfw_state_loosecnt = 0;
        ipfw_gd.ipfw_state_globexp = 0;
        ipfw_gd.ipfw_track_globexp = 0;

#ifdef INVARIANTS
        state_cnt = ipfw_state_cntcoll();
        KASSERT(state_cnt == 0, ("%d states remain", state_cnt));

        KASSERT(ipfw_gd.ipfw_trkcnt_cnt == 0,
            ("%d trkcnts remain", ipfw_gd.ipfw_trkcnt_cnt));

        if (kill_default) {
                KASSERT(static_count == 0,
                        ("%u static rules remain", static_count));
                KASSERT(static_ioc_len == 0,
                        ("%u bytes of static rules remain", static_ioc_len));
        } else {
                KASSERT(static_count == 1,
                        ("%u static rules remain", static_count));
                KASSERT(static_ioc_len == IOC_RULESIZE(ctx->ipfw_default_rule),
                        ("%u bytes of static rules remain, should be %lu",
                         static_ioc_len,
                         (u_long)IOC_RULESIZE(ctx->ipfw_default_rule)));
        }
#endif

        /* Flush is done */
        ipfw_flushing = 0;
}

static void
ipfw_alt_delete_rule_dispatch(netmsg_t nmsg)
{
        struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule, *prev;

        ASSERT_NETISR_NCPUS(mycpuid);

        rule = dmsg->start_rule;
        KKASSERT(rule->cpuid == mycpuid);
        dmsg->start_rule = rule->sibling;

        prev = dmsg->prev_rule;
        if (prev != NULL) {
                KKASSERT(prev->cpuid == mycpuid);

                /*
                 * Move to the position on the next CPU
                 * before the msg is forwarded.
                 */
                dmsg->prev_rule = prev->sibling;
        }

        /*
         * flush pointers outside the loop, then delete all matching
         * rules.  'prev' remains the same throughout the cycle.
         */
        ipfw_flush_rule_ptrs(ctx);
        while (rule && rule->rulenum == dmsg->rulenum) {
                if (rule->rule_flags & IPFW_RULE_F_GENSTATE) {
                        /* Flush states generated by this rule. */
                        ipfw_state_flush(ctx, rule);
                }
                if (rule->rule_flags & IPFW_RULE_F_GENTRACK) {
                        /* Flush tracks generated by this rule. */
                        ipfw_track_flush(ctx, rule);
                }
                rule = ipfw_delete_rule(ctx, prev, rule);
        }

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_alt_delete_rule(uint16_t rulenum)
{
        struct ip_fw *prev, *rule;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct netmsg_del dmsg;

        ASSERT_NETISR0;

        /*
         * Locate first rule to delete
         */
        for (prev = NULL, rule = ctx->ipfw_layer3_chain;
             rule && rule->rulenum < rulenum;
             prev = rule, rule = rule->next)
                ; /* EMPTY */
        if (rule->rulenum != rulenum)
                return EINVAL;

        /*
         * Get rid of the rule duplications on all CPUs
         */
        bzero(&dmsg, sizeof(dmsg));
        netmsg_init(&dmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_alt_delete_rule_dispatch);
        dmsg.prev_rule = prev;
        dmsg.start_rule = rule;
        dmsg.rulenum = rulenum;

        netisr_domsg_global(&dmsg.base);
        KKASSERT(dmsg.prev_rule == NULL && dmsg.start_rule == NULL);
        return 0;
}

static void
ipfw_alt_delete_ruleset_dispatch(netmsg_t nmsg)
{
        struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *prev, *rule;
#ifdef INVARIANTS
        int del = 0;
#endif

        ASSERT_NETISR_NCPUS(mycpuid);

        ipfw_flush_rule_ptrs(ctx);

        prev = NULL;
        rule = ctx->ipfw_layer3_chain;
        while (rule != NULL) {
                if (rule->set == dmsg->from_set) {
                        if (rule->rule_flags & IPFW_RULE_F_GENSTATE) {
                                /* Flush states generated by this rule. */
                                ipfw_state_flush(ctx, rule);
                        }
                        if (rule->rule_flags & IPFW_RULE_F_GENTRACK) {
                                /* Flush tracks generated by this rule. */
                                ipfw_track_flush(ctx, rule);
                        }
                        rule = ipfw_delete_rule(ctx, prev, rule);
#ifdef INVARIANTS
                        del = 1;
#endif
                } else {
                        prev = rule;
                        rule = rule->next;
                }
        }
        KASSERT(del, ("no match set?!"));

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_alt_delete_ruleset(uint8_t set)
{
        struct netmsg_del dmsg;
        int del;
        struct ip_fw *rule;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR0;

        /*
         * Check whether the 'set' exists.  If it exists,
         * then check whether any rules within the set will
         * try to create states.
         */
        del = 0;
        for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
                if (rule->set == set)
                        del = 1;
        }
        if (!del)
                return 0; /* XXX EINVAL? */

        /*
         * Delete this set
         */
        bzero(&dmsg, sizeof(dmsg));
        netmsg_init(&dmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_alt_delete_ruleset_dispatch);
        dmsg.from_set = set;
        netisr_domsg_global(&dmsg.base);

        return 0;
}

static void
ipfw_alt_move_rule_dispatch(netmsg_t nmsg)
{
        struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        rule = dmsg->start_rule;
        KKASSERT(rule->cpuid == mycpuid);

        /*
         * Move to the position on the next CPU
         * before the msg is forwarded.
         */
        dmsg->start_rule = rule->sibling;

        while (rule && rule->rulenum <= dmsg->rulenum) {
                if (rule->rulenum == dmsg->rulenum)
                        rule->set = dmsg->to_set;
                rule = rule->next;
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_alt_move_rule(uint16_t rulenum, uint8_t set)
{
        struct netmsg_del dmsg;
        struct netmsg_base *nmsg;
        struct ip_fw *rule;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR0;

        /*
         * Locate first rule to move
         */
        for (rule = ctx->ipfw_layer3_chain; rule && rule->rulenum <= rulenum;
             rule = rule->next) {
                if (rule->rulenum == rulenum && rule->set != set)
                        break;
        }
        if (rule == NULL || rule->rulenum > rulenum)
                return 0; /* XXX error? */

        bzero(&dmsg, sizeof(dmsg));
        nmsg = &dmsg.base;
        netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_alt_move_rule_dispatch);
        dmsg.start_rule = rule;
        dmsg.rulenum = rulenum;
        dmsg.to_set = set;

        netisr_domsg_global(nmsg);
        KKASSERT(dmsg.start_rule == NULL);
        return 0;
}

static void
ipfw_alt_move_ruleset_dispatch(netmsg_t nmsg)
{
        struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
                if (rule->set == dmsg->from_set)
                        rule->set = dmsg->to_set;
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_alt_move_ruleset(uint8_t from_set, uint8_t to_set)
{
        struct netmsg_del dmsg;
        struct netmsg_base *nmsg;

        ASSERT_NETISR0;

        bzero(&dmsg, sizeof(dmsg));
        nmsg = &dmsg.base;
        netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_alt_move_ruleset_dispatch);
        dmsg.from_set = from_set;
        dmsg.to_set = to_set;

        netisr_domsg_global(nmsg);
        return 0;
}

static void
ipfw_alt_swap_ruleset_dispatch(netmsg_t nmsg)
{
        struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
                if (rule->set == dmsg->from_set)
                        rule->set = dmsg->to_set;
                else if (rule->set == dmsg->to_set)
                        rule->set = dmsg->from_set;
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_alt_swap_ruleset(uint8_t set1, uint8_t set2)
{
        struct netmsg_del dmsg;
        struct netmsg_base *nmsg;

        ASSERT_NETISR0;

        bzero(&dmsg, sizeof(dmsg));
        nmsg = &dmsg.base;
        netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_alt_swap_ruleset_dispatch);
        dmsg.from_set = set1;
        dmsg.to_set = set2;

        netisr_domsg_global(nmsg);
        return 0;
}

/*
 * Remove all rules with given number, and also do set manipulation.
 *
 * The argument is an uint32_t. The low 16 bit are the rule or set number,
 * the next 8 bits are the new set, the top 8 bits are the command:
 *
 *      0       delete rules with given number
 *      1       delete rules with given set number
 *      2       move rules with given number to new set
 *      3       move rules with given set number to new set
 *      4       swap sets with given numbers
 */
static int
ipfw_ctl_alter(uint32_t arg)
{
        uint16_t rulenum;
        uint8_t cmd, new_set;
        int error = 0;

        ASSERT_NETISR0;

        rulenum = arg & 0xffff;
        cmd = (arg >> 24) & 0xff;
        new_set = (arg >> 16) & 0xff;

        if (cmd > 4)
                return EINVAL;
        if (new_set >= IPFW_DEFAULT_SET)
                return EINVAL;
        if (cmd == 0 || cmd == 2) {
                if (rulenum == IPFW_DEFAULT_RULE)
                        return EINVAL;
        } else {
                if (rulenum >= IPFW_DEFAULT_SET)
                        return EINVAL;
        }

        switch (cmd) {
        case 0: /* delete rules with given number */
                error = ipfw_alt_delete_rule(rulenum);
                break;

        case 1: /* delete all rules with given set number */
                error = ipfw_alt_delete_ruleset(rulenum);
                break;

        case 2: /* move rules with given number to new set */
                error = ipfw_alt_move_rule(rulenum, new_set);
                break;

        case 3: /* move rules with given set number to new set */
                error = ipfw_alt_move_ruleset(rulenum, new_set);
                break;

        case 4: /* swap two sets */
                error = ipfw_alt_swap_ruleset(rulenum, new_set);
                break;
        }
        return error;
}

/*
 * Clear counters for a specific rule.
 */
static void
clear_counters(struct ip_fw *rule, int log_only)
{
        ipfw_insn_log *l = (ipfw_insn_log *)ACTION_PTR(rule);

        if (log_only == 0) {
                rule->bcnt = rule->pcnt = 0;
                rule->timestamp = 0;
        }
        if (l->o.opcode == O_LOG)
                l->log_left = l->max_log;
}

static void
ipfw_zero_entry_dispatch(netmsg_t nmsg)
{
        struct netmsg_zent *zmsg = (struct netmsg_zent *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        if (zmsg->rulenum == 0) {
                KKASSERT(zmsg->start_rule == NULL);

                ctx->ipfw_norule_counter = 0;
                for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
                        clear_counters(rule, zmsg->log_only);
        } else {
                struct ip_fw *start = zmsg->start_rule;

                KKASSERT(start->cpuid == mycpuid);
                KKASSERT(start->rulenum == zmsg->rulenum);

                /*
                 * We can have multiple rules with the same number, so we
                 * need to clear them all.
                 */
                for (rule = start; rule && rule->rulenum == zmsg->rulenum;
                     rule = rule->next)
                        clear_counters(rule, zmsg->log_only);

                /*
                 * Move to the position on the next CPU
                 * before the msg is forwarded.
                 */
                zmsg->start_rule = start->sibling;
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

/*
 * Reset some or all counters on firewall rules.
 * @arg frwl is null to clear all entries, or contains a specific
 * rule number.
 * @arg log_only is 1 if we only want to reset logs, zero otherwise.
 */
static int
ipfw_ctl_zero_entry(int rulenum, int log_only)
{
        struct netmsg_zent zmsg;
        struct netmsg_base *nmsg;
        const char *msg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR0;

        bzero(&zmsg, sizeof(zmsg));
        nmsg = &zmsg.base;
        netmsg_init(nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_zero_entry_dispatch);
        zmsg.log_only = log_only;

        if (rulenum == 0) {
                msg = log_only ? "ipfw: All logging counts reset.\n"
                               : "ipfw: Accounting cleared.\n";
        } else {
                struct ip_fw *rule;

                /*
                 * Locate the first rule with 'rulenum'
                 */
                for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next) {
                        if (rule->rulenum == rulenum)
                                break;
                }
                if (rule == NULL) /* we did not find any matching rules */
                        return (EINVAL);
                zmsg.start_rule = rule;
                zmsg.rulenum = rulenum;

                msg = log_only ? "ipfw: Entry %d logging count reset.\n"
                               : "ipfw: Entry %d cleared.\n";
        }
        netisr_domsg_global(nmsg);
        KKASSERT(zmsg.start_rule == NULL);

        if (fw_verbose)
                log(LOG_SECURITY | LOG_NOTICE, msg, rulenum);
        return (0);
}

/*
 * Check validity of the structure before insert.
 * Fortunately rules are simple, so this mostly need to check rule sizes.
 */
static int
ipfw_check_ioc_rule(struct ipfw_ioc_rule *rule, int size, uint32_t *rule_flags)
{
        int l, cmdlen = 0;
        int have_action = 0;
        ipfw_insn *cmd;

        *rule_flags = 0;

        /* Check for valid size */
        if (size < sizeof(*rule)) {
                kprintf("ipfw: rule too short\n");
                return EINVAL;
        }
        l = IOC_RULESIZE(rule);
        if (l != size) {
                kprintf("ipfw: size mismatch (have %d want %d)\n", size, l);
                return EINVAL;
        }

        /* Check rule number */
        if (rule->rulenum == IPFW_DEFAULT_RULE) {
                kprintf("ipfw: invalid rule number\n");
                return EINVAL;
        }

        /*
         * Now go for the individual checks. Very simple ones, basically only
         * instruction sizes.
         */
        for (l = rule->cmd_len, cmd = rule->cmd; l > 0;
             l -= cmdlen, cmd += cmdlen) {
                cmdlen = F_LEN(cmd);
                if (cmdlen > l) {
                        kprintf("ipfw: opcode %d size truncated\n",
                                cmd->opcode);
                        return EINVAL;
                }

                DPRINTF("ipfw: opcode %d\n", cmd->opcode);

                if (cmd->opcode == O_KEEP_STATE || cmd->opcode == O_LIMIT ||
                    IPFW_ISXLAT(cmd->opcode)) {
                        /* This rule will generate states. */
                        *rule_flags |= IPFW_RULE_F_GENSTATE;
                        if (cmd->opcode == O_LIMIT)
                                *rule_flags |= IPFW_RULE_F_GENTRACK;
                }
                if (cmd->opcode == O_DEFRAG || IPFW_ISXLAT(cmd->opcode))
                        *rule_flags |= IPFW_RULE_F_CROSSREF;
                if (cmd->opcode == O_IP_SRC_IFIP ||
                    cmd->opcode == O_IP_DST_IFIP) {
                        *rule_flags |= IPFW_RULE_F_DYNIFADDR;
                        cmd->arg1 &= IPFW_IFIP_SETTINGS;
                }

                switch (cmd->opcode) {
                case O_NOP:
                case O_PROBE_STATE:
                case O_KEEP_STATE:
                case O_PROTO:
                case O_IP_SRC_ME:
                case O_IP_DST_ME:
                case O_LAYER2:
                case O_IN:
                case O_FRAG:
                case O_IPFRAG:
                case O_IPOPT:
                case O_IPLEN:
                case O_IPID:
                case O_IPTOS:
                case O_IPPRECEDENCE:
                case O_IPTTL:
                case O_IPVER:
                case O_TCPWIN:
                case O_TCPFLAGS:
                case O_TCPOPTS:
                case O_ESTAB:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn))
                                goto bad_size;
                        break;

                case O_IP_SRC_TABLE:
                case O_IP_DST_TABLE:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn))
                                goto bad_size;
                        if (cmd->arg1 >= ipfw_table_max) {
                                kprintf("ipfw: invalid table id %u, max %d\n",
                                    cmd->arg1, ipfw_table_max);
                                return EINVAL;
                        }
                        break;

                case O_IP_SRC_IFIP:
                case O_IP_DST_IFIP:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_ifip))
                                goto bad_size;
                        break;

                case O_ICMPCODE:
                case O_ICMPTYPE:
                        if (cmdlen < F_INSN_SIZE(ipfw_insn_u32))
                                goto bad_size;
                        break;

                case O_UID:
                case O_GID:
                case O_IP_SRC:
                case O_IP_DST:
                case O_TCPSEQ:
                case O_TCPACK:
                case O_PROB:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_u32))
                                goto bad_size;
                        break;

                case O_LIMIT:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_limit))
                                goto bad_size;
                        break;
                case O_REDIRECT:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_rdr))
                                goto bad_size;
                        break;

                case O_LOG:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_log))
                                goto bad_size;

                        ((ipfw_insn_log *)cmd)->log_left =
                            ((ipfw_insn_log *)cmd)->max_log;

                        break;

                case O_IP_SRC_MASK:
                case O_IP_DST_MASK:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_ip))
                                goto bad_size;
                        if (((ipfw_insn_ip *)cmd)->mask.s_addr == 0) {
                                kprintf("ipfw: opcode %d, useless rule\n",
                                        cmd->opcode);
                                return EINVAL;
                        }
                        break;

                case O_IP_SRC_SET:
                case O_IP_DST_SET:
                        if (cmd->arg1 == 0 || cmd->arg1 > 256) {
                                kprintf("ipfw: invalid set size %d\n",
                                        cmd->arg1);
                                return EINVAL;
                        }
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_u32) +
                            (cmd->arg1+31)/32 )
                                goto bad_size;
                        break;

                case O_MACADDR2:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_mac))
                                goto bad_size;
                        break;

                case O_MAC_TYPE:
                case O_IP_SRCPORT:
                case O_IP_DSTPORT: /* XXX artificial limit, 30 port pairs */
                        if (cmdlen < 2 || cmdlen > 31)
                                goto bad_size;
                        break;

                case O_RECV:
                case O_XMIT:
                case O_VIA:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_if))
                                goto bad_size;
                        break;

                case O_PIPE:
                case O_QUEUE:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_pipe))
                                goto bad_size;
                        goto check_action;

                case O_FORWARD_IP:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn_sa)) {
                                goto bad_size;
                        } else {
                                in_addr_t fwd_addr;

                                fwd_addr = ((ipfw_insn_sa *)cmd)->
                                           sa.sin_addr.s_addr;
                                if (IN_MULTICAST(ntohl(fwd_addr))) {
                                        kprintf("ipfw: try forwarding to "
                                                "multicast address\n");
                                        return EINVAL;
                                }
                        }
                        goto check_action;

                case O_FORWARD_MAC: /* XXX not implemented yet */
                case O_CHECK_STATE:
                case O_COUNT:
                case O_ACCEPT:
                case O_DENY:
                case O_REJECT:
                case O_SKIPTO:
                case O_DIVERT:
                case O_TEE:
                case O_DEFRAG:
                        if (cmdlen != F_INSN_SIZE(ipfw_insn))
                                goto bad_size;
check_action:
                        if (have_action) {
                                kprintf("ipfw: opcode %d, multiple actions"
                                        " not allowed\n",
                                        cmd->opcode);
                                return EINVAL;
                        }
                        have_action = 1;
                        if (l != cmdlen) {
                                kprintf("ipfw: opcode %d, action must be"
                                        " last opcode\n",
                                        cmd->opcode);
                                return EINVAL;
                        }
                        break;
                default:
                        kprintf("ipfw: opcode %d, unknown opcode\n",
                                cmd->opcode);
                        return EINVAL;
                }
        }
        if (have_action == 0) {
                kprintf("ipfw: missing action\n");
                return EINVAL;
        }
        return 0;

bad_size:
        kprintf("ipfw: opcode %d size %d wrong\n",
                cmd->opcode, cmdlen);
        return EINVAL;
}

static int
ipfw_ctl_add_rule(struct sockopt *sopt)
{
        struct ipfw_ioc_rule *ioc_rule;
        size_t size;
        uint32_t rule_flags;
        int error;

        ASSERT_NETISR0;

        size = sopt->sopt_valsize;
        if (size > (sizeof(uint32_t) * IPFW_RULE_SIZE_MAX) ||
            size < sizeof(*ioc_rule)) {
                return EINVAL;
        }
        if (size != (sizeof(uint32_t) * IPFW_RULE_SIZE_MAX)) {
                sopt->sopt_val = krealloc(sopt->sopt_val, sizeof(uint32_t) *
                                          IPFW_RULE_SIZE_MAX, M_TEMP, M_WAITOK);
        }
        ioc_rule = sopt->sopt_val;

        error = ipfw_check_ioc_rule(ioc_rule, size, &rule_flags);
        if (error)
                return error;

        ipfw_add_rule(ioc_rule, rule_flags);

        if (sopt->sopt_dir == SOPT_GET)
                sopt->sopt_valsize = IOC_RULESIZE(ioc_rule);
        return 0;
}

static void *
ipfw_copy_rule(const struct ipfw_context *ctx, const struct ip_fw *rule,
    struct ipfw_ioc_rule *ioc_rule)
{
        const struct ip_fw *sibling;
#ifdef INVARIANTS
        int i;
#endif

        ASSERT_NETISR0;
        KASSERT(rule->cpuid == 0, ("rule does not belong to cpu0"));

        ioc_rule->act_ofs = rule->act_ofs;
        ioc_rule->cmd_len = rule->cmd_len;
        ioc_rule->rulenum = rule->rulenum;
        ioc_rule->set = rule->set;
        ioc_rule->usr_flags = rule->usr_flags;

        ioc_rule->set_disable = ctx->ipfw_set_disable;
        ioc_rule->static_count = static_count;
        ioc_rule->static_len = static_ioc_len;

        /*
         * Visit (read-only) all of the rule's duplications to get
         * the necessary statistics
         */
#ifdef INVARIANTS
        i = 0;
#endif
        ioc_rule->pcnt = 0;
        ioc_rule->bcnt = 0;
        ioc_rule->timestamp = 0;
        for (sibling = rule; sibling != NULL; sibling = sibling->sibling) {
                ioc_rule->pcnt += sibling->pcnt;
                ioc_rule->bcnt += sibling->bcnt;
                if (sibling->timestamp > ioc_rule->timestamp)
                        ioc_rule->timestamp = sibling->timestamp;
#ifdef INVARIANTS
                ++i;
#endif
        }
        KASSERT(i == netisr_ncpus,
            ("static rule is not duplicated on netisr_ncpus %d", netisr_ncpus));

        bcopy(rule->cmd, ioc_rule->cmd, ioc_rule->cmd_len * 4 /* XXX */);

        return ((uint8_t *)ioc_rule + IOC_RULESIZE(ioc_rule));
}

static boolean_t
ipfw_track_copy(const struct ipfw_trkcnt *trk, struct ipfw_ioc_state *ioc_state)
{
        struct ipfw_ioc_flowid *ioc_id;

        if (trk->tc_expire == 0) {
                /* Not a scanned one. */
                return (FALSE);
        }

        ioc_state->expire = TIME_LEQ(trk->tc_expire, time_uptime) ?
            0 : trk->tc_expire - time_uptime;
        ioc_state->pcnt = 0;
        ioc_state->bcnt = 0;

        ioc_state->dyn_type = O_LIMIT_PARENT;
        ioc_state->count = trk->tc_count;

        ioc_state->rulenum = trk->tc_rulenum;

        ioc_id = &ioc_state->id;
        ioc_id->type = ETHERTYPE_IP;
        ioc_id->u.ip.proto = trk->tc_proto;
        ioc_id->u.ip.src_ip = trk->tc_saddr;
        ioc_id->u.ip.dst_ip = trk->tc_daddr;
        ioc_id->u.ip.src_port = trk->tc_sport;
        ioc_id->u.ip.dst_port = trk->tc_dport;

        return (TRUE);
}

static boolean_t
ipfw_state_copy(const struct ipfw_state *s, struct ipfw_ioc_state *ioc_state)
{
        struct ipfw_ioc_flowid *ioc_id;

        if (IPFW_STATE_SCANSKIP(s))
                return (FALSE);

        ioc_state->expire = TIME_LEQ(s->st_expire, time_uptime) ?
            0 : s->st_expire - time_uptime;
        ioc_state->pcnt = s->st_pcnt;
        ioc_state->bcnt = s->st_bcnt;

        ioc_state->dyn_type = s->st_type;
        ioc_state->count = 0;

        ioc_state->rulenum = s->st_rule->rulenum;

        ioc_id = &ioc_state->id;
        ioc_id->type = ETHERTYPE_IP;
        ioc_id->u.ip.proto = s->st_proto;
        ipfw_key_4tuple(&s->st_key,
            &ioc_id->u.ip.src_ip, &ioc_id->u.ip.src_port,
            &ioc_id->u.ip.dst_ip, &ioc_id->u.ip.dst_port);

        if (IPFW_ISXLAT(s->st_type)) {
                const struct ipfw_xlat *x = (const struct ipfw_xlat *)s;

                if (x->xlat_port == 0)
                        ioc_state->xlat_port = ioc_id->u.ip.dst_port;
                else
                        ioc_state->xlat_port = ntohs(x->xlat_port);
                ioc_state->xlat_addr = ntohl(x->xlat_addr);

                ioc_state->pcnt += x->xlat_pair->xlat_pcnt;
                ioc_state->bcnt += x->xlat_pair->xlat_bcnt;
        }

        return (TRUE);
}

static void
ipfw_state_copy_dispatch(netmsg_t nmsg)
{
        struct netmsg_cpstate *nm = (struct netmsg_cpstate *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        const struct ipfw_state *s;
        const struct ipfw_track *t;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(nm->state_cnt < nm->state_cntmax,
            ("invalid state count %d, max %d",
             nm->state_cnt, nm->state_cntmax));

        TAILQ_FOREACH(s, &ctx->ipfw_state_list, st_link) {
                if (ipfw_state_copy(s, nm->ioc_state)) {
                        nm->ioc_state++;
                        nm->state_cnt++;
                        if (nm->state_cnt == nm->state_cntmax)
                                goto done;
                }
        }

        /*
         * Prepare tracks in the global track tree for userland.
         */
        TAILQ_FOREACH(t, &ctx->ipfw_track_list, t_link) {
                struct ipfw_trkcnt *trk;

                if (t->t_count == NULL) /* anchor */
                        continue;
                trk = t->t_trkcnt;

                /*
                 * Only one netisr can run this function at
                 * any time, and only this function accesses
                 * trkcnt's tc_expire, so this is safe w/o
                 * ipfw_gd.ipfw_trkcnt_token.
                 */
                if (trk->tc_expire > t->t_expire)
                        continue;
                trk->tc_expire = t->t_expire;
        }

        /*
         * Copy tracks in the global track tree to userland in
         * the last netisr.
         */
        if (mycpuid == netisr_ncpus - 1) {
                struct ipfw_trkcnt *trk;

                KASSERT(nm->state_cnt < nm->state_cntmax,
                    ("invalid state count %d, max %d",
                     nm->state_cnt, nm->state_cntmax));

                IPFW_TRKCNT_TOKGET;
                RB_FOREACH(trk, ipfw_trkcnt_tree, &ipfw_gd.ipfw_trkcnt_tree) {
                        if (ipfw_track_copy(trk, nm->ioc_state)) {
                                nm->ioc_state++;
                                nm->state_cnt++;
                                if (nm->state_cnt == nm->state_cntmax) {
                                        IPFW_TRKCNT_TOKREL;
                                        goto done;
                                }
                        }
                }
                IPFW_TRKCNT_TOKREL;
        }
done:
        if (nm->state_cnt == nm->state_cntmax) {
                /* No more space; done. */
                netisr_replymsg(&nm->base, 0);
        } else {
                netisr_forwardmsg(&nm->base, mycpuid + 1);
        }
}

static int
ipfw_ctl_get_rules(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ip_fw *rule;
        void *bp;
        size_t size;
        int state_cnt;

        ASSERT_NETISR0;

        /*
         * pass up a copy of the current rules. Static rules
         * come first (the last of which has number IPFW_DEFAULT_RULE),
         * followed by a possibly empty list of states.
         */

        size = static_ioc_len;  /* size of static rules */

        /*
         * Size of the states.
         * XXX take tracks as state for userland compat.
         */
        state_cnt = ipfw_state_cntcoll() + ipfw_gd.ipfw_trkcnt_cnt;
        state_cnt = (state_cnt * 5) / 4; /* leave 25% headroom */
        size += state_cnt * sizeof(struct ipfw_ioc_state);

        if (sopt->sopt_valsize < size) {
                /* short length, no need to return incomplete rules */
                /* XXX: if superuser, no need to zero buffer */
                bzero(sopt->sopt_val, sopt->sopt_valsize);
                return 0;
        }
        bp = sopt->sopt_val;

        for (rule = ctx->ipfw_layer3_chain; rule; rule = rule->next)
                bp = ipfw_copy_rule(ctx, rule, bp);

        if (state_cnt) {
                struct netmsg_cpstate nm;
#ifdef INVARIANTS
                size_t old_size = size;
#endif

                netmsg_init(&nm.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_state_copy_dispatch);
                nm.ioc_state = bp;
                nm.state_cntmax = state_cnt;
                nm.state_cnt = 0;
                netisr_domsg_global(&nm.base);

                /*
                 * The # of states may be shrinked after the snapshot
                 * of the state count was taken.  To give user a correct
                 * state count, nm->state_cnt is used to recalculate
                 * the actual size.
                 */
                size = static_ioc_len +
                    (nm.state_cnt * sizeof(struct ipfw_ioc_state));
                KKASSERT(size <= old_size);
        }

        sopt->sopt_valsize = size;
        return 0;
}

static void
ipfw_set_disable_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        ctx->ipfw_set_disable = nmsg->lmsg.u.ms_result32;
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_ctl_set_disable(uint32_t disable, uint32_t enable)
{
        struct netmsg_base nmsg;
        uint32_t set_disable;

        ASSERT_NETISR0;

        /* IPFW_DEFAULT_SET is always enabled */
        enable |= (1 << IPFW_DEFAULT_SET);
        set_disable = (ipfw_ctx[mycpuid]->ipfw_set_disable | disable) & ~enable;

        bzero(&nmsg, sizeof(nmsg));
        netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_set_disable_dispatch);
        nmsg.lmsg.u.ms_result32 = set_disable;

        netisr_domsg_global(&nmsg);
}

static void
ipfw_table_create_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        int tblid = nm->lmsg.u.ms_result;

        ASSERT_NETISR_NCPUS(mycpuid);

        if (!rn_inithead(&ctx->ipfw_tables[tblid], rn_cpumaskhead(mycpuid),
                         offsetof(struct sockaddr_in, sin_addr)))
                panic("ipfw: create table%d failed", tblid);

        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static int
ipfw_table_create(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_ioc_table *tbl;
        struct netmsg_base nm;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize != sizeof(*tbl))
                return (EINVAL);

        tbl = sopt->sopt_val;
        if (tbl->tableid < 0 || tbl->tableid >= ipfw_table_max)
                return (EINVAL);

        if (ctx->ipfw_tables[tbl->tableid] != NULL)
                return (EEXIST);

        netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_table_create_dispatch);
        nm.lmsg.u.ms_result = tbl->tableid;
        netisr_domsg_global(&nm);

        return (0);
}

static void
ipfw_table_killent(struct radix_node *rn)
{
        struct ipfw_tblent *te;

        te = (struct ipfw_tblent *)rn;
        kfree(te, M_IPFW);
}

static void
ipfw_table_flush_oncpu(struct ipfw_context *ctx, int tableid,
    int destroy)
{
        struct radix_node_head *rnh;

        ASSERT_NETISR_NCPUS(mycpuid);

        rnh = ctx->ipfw_tables[tableid];
        rn_flush(rnh, ipfw_table_killent);
        if (destroy) {
                rn_freehead(rnh);
                ctx->ipfw_tables[tableid] = NULL;
        }
}

static void
ipfw_table_flush_dispatch(netmsg_t nmsg)
{
        struct netmsg_tblflush *nm = (struct netmsg_tblflush *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        ipfw_table_flush_oncpu(ctx, nm->tableid, nm->destroy);
        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static void
ipfw_table_flushall_oncpu(struct ipfw_context *ctx, int destroy)
{
        int i;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (i = 0; i < ipfw_table_max; ++i) {
                if (ctx->ipfw_tables[i] != NULL)
                        ipfw_table_flush_oncpu(ctx, i, destroy);
        }
}

static void
ipfw_table_flushall_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        ipfw_table_flushall_oncpu(ctx, 0);
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_table_flush(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_ioc_table *tbl;
        struct netmsg_tblflush nm;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize != sizeof(*tbl))
                return (EINVAL);

        tbl = sopt->sopt_val;
        if (sopt->sopt_name == IP_FW_TBL_FLUSH && tbl->tableid < 0) {
                netmsg_init(&nm.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_table_flushall_dispatch);
                netisr_domsg_global(&nm.base);
                return (0);
        }

        if (tbl->tableid < 0 || tbl->tableid >= ipfw_table_max)
                return (EINVAL);

        if (ctx->ipfw_tables[tbl->tableid] == NULL)
                return (ENOENT);

        netmsg_init(&nm.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_table_flush_dispatch);
        nm.tableid = tbl->tableid;
        nm.destroy = 0;
        if (sopt->sopt_name == IP_FW_TBL_DESTROY)
                nm.destroy = 1;
        netisr_domsg_global(&nm.base);

        return (0);
}

static int
ipfw_table_cntent(struct radix_node *rn __unused, void *xcnt)
{
        int *cnt = xcnt;

        (*cnt)++;
        return (0);
}

static int
ipfw_table_cpent(struct radix_node *rn, void *xcp)
{
        struct ipfw_table_cp *cp = xcp;
        struct ipfw_tblent *te = (struct ipfw_tblent *)rn;
        struct ipfw_ioc_tblent *ioc_te;
#ifdef INVARIANTS
        int cnt;
#endif

        KASSERT(cp->te_idx < cp->te_cnt, ("invalid table cp idx %d, cnt %d",
            cp->te_idx, cp->te_cnt));
        ioc_te = &cp->te[cp->te_idx];

        if (te->te_nodes->rn_mask != NULL) {
                memcpy(&ioc_te->netmask, te->te_nodes->rn_mask,
                    *te->te_nodes->rn_mask);
        } else {
                ioc_te->netmask.sin_len = 0;
        }
        memcpy(&ioc_te->key, &te->te_key, sizeof(ioc_te->key));

        ioc_te->use = te->te_use;
        ioc_te->last_used = te->te_lastuse;
#ifdef INVARIANTS
        cnt = 1;
#endif

        while ((te = te->te_sibling) != NULL) {
#ifdef INVARIANTS
                ++cnt;
#endif
                ioc_te->use += te->te_use;
                if (te->te_lastuse > ioc_te->last_used)
                        ioc_te->last_used = te->te_lastuse;
        }
        KASSERT(cnt == netisr_ncpus,
            ("invalid # of tblent %d, should be %d", cnt, netisr_ncpus));

        cp->te_idx++;

        return (0);
}

static int
ipfw_table_get(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct radix_node_head *rnh;
        struct ipfw_ioc_table *tbl;
        struct ipfw_ioc_tblcont *cont;
        struct ipfw_table_cp cp;
        int cnt = 0, sz;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize < sizeof(*tbl))
                return (EINVAL);

        tbl = sopt->sopt_val;
        if (tbl->tableid < 0) {
                struct ipfw_ioc_tbllist *list;
                int i;

                /*
                 * List available table ids.
                 */
                for (i = 0; i < ipfw_table_max; ++i) {
                        if (ctx->ipfw_tables[i] != NULL)
                                ++cnt;
                }

                sz = __offsetof(struct ipfw_ioc_tbllist, tables[cnt]);
                if (sopt->sopt_valsize < sz) {
                        bzero(sopt->sopt_val, sopt->sopt_valsize);
                        return (E2BIG);
                }
                list = sopt->sopt_val;
                list->tablecnt = cnt;

                cnt = 0;
                for (i = 0; i < ipfw_table_max; ++i) {
                        if (ctx->ipfw_tables[i] != NULL) {
                                KASSERT(cnt < list->tablecnt,
                                    ("invalid idx %d, cnt %d",
                                     cnt, list->tablecnt));
                                list->tables[cnt++] = i;
                        }
                }
                sopt->sopt_valsize = sz;
                return (0);
        } else if (tbl->tableid >= ipfw_table_max) {
                return (EINVAL);
        }

        rnh = ctx->ipfw_tables[tbl->tableid];
        if (rnh == NULL)
                return (ENOENT);
        rnh->rnh_walktree(rnh, ipfw_table_cntent, &cnt);

        sz = __offsetof(struct ipfw_ioc_tblcont, ent[cnt]);
        if (sopt->sopt_valsize < sz) {
                bzero(sopt->sopt_val, sopt->sopt_valsize);
                return (E2BIG);
        }
        cont = sopt->sopt_val;
        cont->entcnt = cnt;

        cp.te = cont->ent;
        cp.te_idx = 0;
        cp.te_cnt = cnt;
        rnh->rnh_walktree(rnh, ipfw_table_cpent, &cp);

        sopt->sopt_valsize = sz;
        return (0);
}

static void
ipfw_table_add_dispatch(netmsg_t nmsg)
{
        struct netmsg_tblent *nm = (struct netmsg_tblent *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct radix_node_head *rnh;
        struct ipfw_tblent *te;

        ASSERT_NETISR_NCPUS(mycpuid);

        rnh = ctx->ipfw_tables[nm->tableid];

        te = kmalloc(sizeof(*te), M_IPFW, M_WAITOK | M_ZERO);
        te->te_nodes->rn_key = (char *)&te->te_key;
        memcpy(&te->te_key, nm->key, sizeof(te->te_key));

        if (rnh->rnh_addaddr(&te->te_key, nm->netmask, rnh, te->te_nodes)
            == NULL) {
                if (mycpuid == 0) {
                        kfree(te, M_IPFW);
                        netisr_replymsg(&nm->base, EEXIST);
                        return;
                }
                panic("rnh_addaddr failed");
        }

        /* Link siblings. */
        if (nm->sibling != NULL)
                nm->sibling->te_sibling = te;
        nm->sibling = te;

        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static void
ipfw_table_del_dispatch(netmsg_t nmsg)
{
        struct netmsg_tblent *nm = (struct netmsg_tblent *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct radix_node_head *rnh;
        struct radix_node *rn;

        ASSERT_NETISR_NCPUS(mycpuid);

        rnh = ctx->ipfw_tables[nm->tableid];
        rn = rnh->rnh_deladdr(nm->key, nm->netmask, rnh);
        if (rn == NULL) {
                if (mycpuid == 0) {
                        netisr_replymsg(&nm->base, ESRCH);
                        return;
                }
                panic("rnh_deladdr failed");
        }
        kfree(rn, M_IPFW);

        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static int
ipfw_table_alt(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_ioc_tblcont *tbl;
        struct ipfw_ioc_tblent *te;
        struct sockaddr_in key0;
        struct sockaddr *netmask = NULL, *key;
        struct netmsg_tblent nm;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize != sizeof(*tbl))
                return (EINVAL);
        tbl = sopt->sopt_val;

        if (tbl->tableid < 0  || tbl->tableid >= ipfw_table_max)
                return (EINVAL);
        if (tbl->entcnt != 1)
                return (EINVAL);

        if (ctx->ipfw_tables[tbl->tableid] == NULL)
                return (ENOENT);
        te = &tbl->ent[0];

        if (te->key.sin_family != AF_INET ||
            te->key.sin_port != 0 ||
            te->key.sin_len != sizeof(struct sockaddr_in))
                return (EINVAL);
        key = (struct sockaddr *)&te->key;

        if (te->netmask.sin_len != 0) {
                if (te->netmask.sin_port != 0 ||
                    te->netmask.sin_len > sizeof(struct sockaddr_in))
                        return (EINVAL);
                netmask = (struct sockaddr *)&te->netmask;
                sa_maskedcopy(key, (struct sockaddr *)&key0, netmask);
                key = (struct sockaddr *)&key0;
        }

        if (sopt->sopt_name == IP_FW_TBL_ADD) {
                netmsg_init(&nm.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_table_add_dispatch);
        } else {
                netmsg_init(&nm.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_table_del_dispatch);
        }
        nm.key = key;
        nm.netmask = netmask;
        nm.tableid = tbl->tableid;
        nm.sibling = NULL;
        return (netisr_domsg_global(&nm.base));
}

static int
ipfw_table_zeroent(struct radix_node *rn, void *arg __unused)
{
        struct ipfw_tblent *te = (struct ipfw_tblent *)rn;

        te->te_use = 0;
        te->te_lastuse = 0;
        return (0);
}

static void
ipfw_table_zero_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct radix_node_head *rnh;

        ASSERT_NETISR_NCPUS(mycpuid);

        rnh = ctx->ipfw_tables[nmsg->lmsg.u.ms_result];
        rnh->rnh_walktree(rnh, ipfw_table_zeroent, NULL);

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_table_zeroall_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        int i;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (i = 0; i < ipfw_table_max; ++i) {
                struct radix_node_head *rnh = ctx->ipfw_tables[i];

                if (rnh != NULL)
                        rnh->rnh_walktree(rnh, ipfw_table_zeroent, NULL);
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static int
ipfw_table_zero(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct netmsg_base nm;
        struct ipfw_ioc_table *tbl;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize != sizeof(*tbl))
                return (EINVAL);
        tbl = sopt->sopt_val;

        if (tbl->tableid < 0) {
                netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
                    ipfw_table_zeroall_dispatch);
                netisr_domsg_global(&nm);
                return (0);
        } else if (tbl->tableid >= ipfw_table_max) {
                return (EINVAL);
        } else if (ctx->ipfw_tables[tbl->tableid] == NULL) {
                return (ENOENT);
        }

        netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_table_zero_dispatch);
        nm.lmsg.u.ms_result = tbl->tableid;
        netisr_domsg_global(&nm);

        return (0);
}

static int
ipfw_table_killexp(struct radix_node *rn, void *xnm)
{
        struct netmsg_tblexp *nm = xnm;
        struct ipfw_tblent *te = (struct ipfw_tblent *)rn;
        struct radix_node *ret;

        if (te->te_expired) {
                ret = nm->rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, nm->rnh);
                if (ret != rn)
                        panic("deleted other table entry");
                kfree(ret, M_IPFW);
                nm->expcnt++;
        }
        return (0);
}

static void
ipfw_table_expire_dispatch(netmsg_t nmsg)
{
        struct netmsg_tblexp *nm = (struct netmsg_tblexp *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct radix_node_head *rnh;

        ASSERT_NETISR_NCPUS(mycpuid);

        rnh = ctx->ipfw_tables[nm->tableid];
        nm->rnh = rnh;
        rnh->rnh_walktree(rnh, ipfw_table_killexp, nm);

        KASSERT(nm->expcnt == nm->cnt * (mycpuid + 1),
            ("not all expired addresses (%d) were deleted (%d)",
             nm->cnt * (mycpuid + 1), nm->expcnt));

        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static void
ipfw_table_expireall_dispatch(netmsg_t nmsg)
{
        struct netmsg_tblexp *nm = (struct netmsg_tblexp *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        int i;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (i = 0; i < ipfw_table_max; ++i) {
                struct radix_node_head *rnh = ctx->ipfw_tables[i];

                if (rnh == NULL)
                        continue;
                nm->rnh = rnh;
                rnh->rnh_walktree(rnh, ipfw_table_killexp, nm);
        }

        KASSERT(nm->expcnt == nm->cnt * (mycpuid + 1),
            ("not all expired addresses (%d) were deleted (%d)",
             nm->cnt * (mycpuid + 1), nm->expcnt));

        netisr_forwardmsg(&nm->base, mycpuid + 1);
}

static int
ipfw_table_markexp(struct radix_node *rn, void *xnm)
{
        struct netmsg_tblexp *nm = xnm;
        struct ipfw_tblent *te;
        time_t lastuse;

        te = (struct ipfw_tblent *)rn;
        lastuse = te->te_lastuse;

        while ((te = te->te_sibling) != NULL) {
                if (te->te_lastuse > lastuse)
                        lastuse = te->te_lastuse;
        }
        if (!TIME_LEQ(lastuse + nm->expire, time_second)) {
                /* Not expired */
                return (0);
        }

        te = (struct ipfw_tblent *)rn;
        te->te_expired = 1;
        while ((te = te->te_sibling) != NULL)
                te->te_expired = 1;
        nm->cnt++;

        return (0);
}

static int
ipfw_table_expire(struct sockopt *sopt)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct netmsg_tblexp nm;
        struct ipfw_ioc_tblexp *tbl;
        struct radix_node_head *rnh;

        ASSERT_NETISR0;

        if (sopt->sopt_valsize != sizeof(*tbl))
                return (EINVAL);
        tbl = sopt->sopt_val;
        tbl->expcnt = 0;

        nm.expcnt = 0;
        nm.cnt = 0;
        nm.expire = tbl->expire;

        if (tbl->tableid < 0) {
                int i;

                for (i = 0; i < ipfw_table_max; ++i) {
                        rnh = ctx->ipfw_tables[i];
                        if (rnh == NULL)
                                continue;
                        rnh->rnh_walktree(rnh, ipfw_table_markexp, &nm);
                }
                if (nm.cnt == 0) {
                        /* No addresses can be expired. */
                        return (0);
                }
                tbl->expcnt = nm.cnt;

                netmsg_init(&nm.base, NULL, &curthread->td_msgport,
                    MSGF_PRIORITY, ipfw_table_expireall_dispatch);
                nm.tableid = -1;
                netisr_domsg_global(&nm.base);
                KASSERT(nm.expcnt == nm.cnt * netisr_ncpus,
                    ("not all expired addresses (%d) were deleted (%d)",
                     nm.cnt * netisr_ncpus, nm.expcnt));

                return (0);
        } else if (tbl->tableid >= ipfw_table_max) {
                return (EINVAL);
        }

        rnh = ctx->ipfw_tables[tbl->tableid];
        if (rnh == NULL)
                return (ENOENT);
        rnh->rnh_walktree(rnh, ipfw_table_markexp, &nm);
        if (nm.cnt == 0) {
                /* No addresses can be expired. */
                return (0);
        }
        tbl->expcnt = nm.cnt;

        netmsg_init(&nm.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_table_expire_dispatch);
        nm.tableid = tbl->tableid;
        netisr_domsg_global(&nm.base);
        KASSERT(nm.expcnt == nm.cnt * netisr_ncpus,
            ("not all expired addresses (%d) were deleted (%d)",
             nm.cnt * netisr_ncpus, nm.expcnt));
        return (0);
}

static void
ipfw_crossref_free_dispatch(netmsg_t nmsg)
{
        struct ip_fw *rule = nmsg->lmsg.u.ms_resultp;

        KKASSERT((rule->rule_flags &
            (IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID)) ==
            (IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID));
        ipfw_free_rule(rule);

        netisr_replymsg(&nmsg->base, 0);
}

static void
ipfw_crossref_reap(void)
{
        struct ip_fw *rule, *prev = NULL;

        ASSERT_NETISR0;

        rule = ipfw_gd.ipfw_crossref_free;
        while (rule != NULL) {
                uint64_t inflight = 0;
                int i;

                for (i = 0; i < netisr_ncpus; ++i)
                        inflight += rule->cross_rules[i]->cross_refs;
                if (inflight == 0) {
                        struct ip_fw *f = rule;

                        /*
                         * Unlink.
                         */
                        rule = rule->next;
                        if (prev != NULL)
                                prev->next = rule;
                        else
                                ipfw_gd.ipfw_crossref_free = rule;

                        /*
                         * Free.
                         */
                        for (i = 1; i < netisr_ncpus; ++i) {
                                struct netmsg_base nm;

                                netmsg_init(&nm, NULL, &curthread->td_msgport,
                                    MSGF_PRIORITY, ipfw_crossref_free_dispatch);
                                nm.lmsg.u.ms_resultp = f->cross_rules[i];
                                netisr_domsg(&nm, i);
                        }
                        KKASSERT((f->rule_flags &
                            (IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID)) ==
                            (IPFW_RULE_F_CROSSREF | IPFW_RULE_F_INVALID));
                        ipfw_unref_rule(f);
                } else {
                        prev = rule;
                        rule = rule->next;
                }
        }

        if (ipfw_gd.ipfw_crossref_free != NULL) {
                callout_reset(&ipfw_gd.ipfw_crossref_ch, hz,
                    ipfw_crossref_timeo, NULL);
        }
}

/*
 * {set|get}sockopt parser.
 */
static int
ipfw_ctl(struct sockopt *sopt)
{
        int error, rulenum;
        uint32_t *masks;
        size_t size;

        ASSERT_NETISR0;

        error = 0;

        switch (sopt->sopt_name) {
        case IP_FW_GET:
                error = ipfw_ctl_get_rules(sopt);
                break;

        case IP_FW_FLUSH:
                ipfw_flush(0 /* keep default rule */);
                break;

        case IP_FW_ADD:
                error = ipfw_ctl_add_rule(sopt);
                break;

        case IP_FW_DEL:
                /*
                 * IP_FW_DEL is used for deleting single rules or sets,
                 * and (ab)used to atomically manipulate sets.
                 * Argument size is used to distinguish between the two:
                 *    sizeof(uint32_t)
                 *      delete single rule or set of rules,
                 *      or reassign rules (or sets) to a different set.
                 *    2 * sizeof(uint32_t)
                 *      atomic disable/enable sets.
                 *      first uint32_t contains sets to be disabled,
                 *      second uint32_t contains sets to be enabled.
                 */
                masks = sopt->sopt_val;
                size = sopt->sopt_valsize;
                if (size == sizeof(*masks)) {
                        /*
                         * Delete or reassign static rule
                         */
                        error = ipfw_ctl_alter(masks[0]);
                } else if (size == (2 * sizeof(*masks))) {
                        /*
                         * Set enable/disable
                         */
                        ipfw_ctl_set_disable(masks[0], masks[1]);
                } else {
                        error = EINVAL;
                }
                break;

        case IP_FW_ZERO:
        case IP_FW_RESETLOG: /* argument is an int, the rule number */
                rulenum = 0;

                if (sopt->sopt_val != 0) {
                    error = soopt_to_kbuf(sopt, &rulenum,
                            sizeof(int), sizeof(int));
                    if (error)
                        break;
                }
                error = ipfw_ctl_zero_entry(rulenum,
                        sopt->sopt_name == IP_FW_RESETLOG);
                break;

        case IP_FW_TBL_CREATE:
                error = ipfw_table_create(sopt);
                break;

        case IP_FW_TBL_ADD:
        case IP_FW_TBL_DEL:
                error = ipfw_table_alt(sopt);
                break;

        case IP_FW_TBL_FLUSH:
        case IP_FW_TBL_DESTROY:
                error = ipfw_table_flush(sopt);
                break;

        case IP_FW_TBL_GET:
                error = ipfw_table_get(sopt);
                break;

        case IP_FW_TBL_ZERO:
                error = ipfw_table_zero(sopt);
                break;

        case IP_FW_TBL_EXPIRE:
                error = ipfw_table_expire(sopt);
                break;

        default:
                kprintf("ipfw_ctl invalid option %d\n", sopt->sopt_name);
                error = EINVAL;
        }

        ipfw_crossref_reap();
        return error;
}

static void
ipfw_keepalive_done(struct ipfw_context *ctx)
{

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
            ("keepalive is not in progress"));
        ctx->ipfw_flags &= ~IPFW_FLAG_KEEPALIVE;
        callout_reset(&ctx->ipfw_keepalive_ch, dyn_keepalive_period * hz,
            ipfw_keepalive, NULL);
}

static void
ipfw_keepalive_more(struct ipfw_context *ctx)
{
        struct netmsg_base *nm = &ctx->ipfw_keepalive_more;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
            ("keepalive is not in progress"));
        KASSERT(nm->lmsg.ms_flags & MSGF_DONE,
            ("keepalive more did not finish"));
        netisr_sendmsg_oncpu(nm);
}

static void
ipfw_keepalive_loop(struct ipfw_context *ctx, struct ipfw_state *anchor)
{
        struct ipfw_state *s;
        int scanned = 0, expired = 0, kept = 0;

        KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
            ("keepalive is not in progress"));

        while ((s = TAILQ_NEXT(anchor, st_link)) != NULL) {
                uint32_t ack_rev, ack_fwd;
                struct ipfw_flow_id id;
                uint8_t send_dir;

                if (scanned++ >= ipfw_state_scan_max) {
                        ipfw_keepalive_more(ctx);
                        return;
                }

                TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
                TAILQ_INSERT_AFTER(&ctx->ipfw_state_list, s, anchor, st_link);

                /*
                 * NOTE:
                 * Don't use IPFW_STATE_SCANSKIP; need to perform keepalive
                 * on slave xlat.
                 */
                if (s->st_type == O_ANCHOR)
                        continue;

                if (IPFW_STATE_ISDEAD(s)) {
                        ipfw_state_remove(ctx, s);
                        if (++expired >= ipfw_state_expire_max) {
                                ipfw_keepalive_more(ctx);
                                return;
                        }
                        continue;
                }

                /*
                 * Keep alive processing
                 */

                if (s->st_proto != IPPROTO_TCP)
                        continue;
                if ((s->st_state & IPFW_STATE_TCPSTATES) != BOTH_SYN)
                        continue;
                if (TIME_LEQ(time_uptime + dyn_keepalive_interval,
                    s->st_expire))
                        continue;       /* too early */

                ipfw_key_4tuple(&s->st_key, &id.src_ip, &id.src_port,
                    &id.dst_ip, &id.dst_port);
                ack_rev = s->st_ack_rev;
                ack_fwd = s->st_ack_fwd;

#define SEND_FWD        0x1
#define SEND_REV        0x2

                if (IPFW_ISXLAT(s->st_type)) {
                        const struct ipfw_xlat *x = (const struct ipfw_xlat *)s;

                        if (x->xlat_dir == MATCH_FORWARD)
                                send_dir = SEND_FWD;
                        else
                                send_dir = SEND_REV;
                } else {
                        send_dir = SEND_FWD | SEND_REV;
                }

                if (send_dir & SEND_REV)
                        send_pkt(&id, ack_rev - 1, ack_fwd, TH_SYN);
                if (send_dir & SEND_FWD)
                        send_pkt(&id, ack_fwd - 1, ack_rev, 0);

#undef SEND_FWD
#undef SEND_REV

                if (++kept >= ipfw_keepalive_max) {
                        ipfw_keepalive_more(ctx);
                        return;
                }
        }
        TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
        ipfw_keepalive_done(ctx);
}

static void
ipfw_keepalive_more_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_state *anchor;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE,
            ("keepalive is not in progress"));

        /* Reply ASAP */
        netisr_replymsg(&nm->base, 0);

        anchor = &ctx->ipfw_keepalive_anch;
        if (!dyn_keepalive || ctx->ipfw_state_cnt == 0) {
                TAILQ_REMOVE(&ctx->ipfw_state_list, anchor, st_link);
                ipfw_keepalive_done(ctx);
                return;
        }
        ipfw_keepalive_loop(ctx, anchor);
}

/*
 * This procedure is only used to handle keepalives. It is invoked
 * every dyn_keepalive_period
 */
static void
ipfw_keepalive_dispatch(netmsg_t nm)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ipfw_state *anchor;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT((ctx->ipfw_flags & IPFW_FLAG_KEEPALIVE) == 0,
            ("keepalive is in progress"));
        ctx->ipfw_flags |= IPFW_FLAG_KEEPALIVE;

        /* Reply ASAP */
        crit_enter();
        netisr_replymsg(&nm->base, 0);
        crit_exit();

        if (!dyn_keepalive || ctx->ipfw_state_cnt == 0) {
                ipfw_keepalive_done(ctx);
                return;
        }

        anchor = &ctx->ipfw_keepalive_anch;
        TAILQ_INSERT_HEAD(&ctx->ipfw_state_list, anchor, st_link);
        ipfw_keepalive_loop(ctx, anchor);
}

/*
 * This procedure is only used to handle keepalives. It is invoked
 * every dyn_keepalive_period
 */
static void
ipfw_keepalive(void *dummy __unused)
{
        struct netmsg_base *msg;

        KKASSERT(mycpuid < netisr_ncpus);
        msg = &ipfw_ctx[mycpuid]->ipfw_keepalive_nm;

        crit_enter();
        if (msg->lmsg.ms_flags & MSGF_DONE)
                netisr_sendmsg_oncpu(msg);
        crit_exit();
}

static void
ipfw_ip_input_dispatch(netmsg_t nmsg)
{
        struct netmsg_genpkt *nm = (struct netmsg_genpkt *)nmsg;
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct mbuf *m = nm->m;
        struct ip_fw *rule = nm->arg1;

        ASSERT_NETISR_NCPUS(mycpuid);
        KASSERT(rule->cpuid == mycpuid,
            ("rule does not belong to cpu%d", mycpuid));
        KASSERT(m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE,
            ("mbuf does not have ipfw continue rule"));

        KASSERT(ctx->ipfw_cont_rule == NULL,
            ("pending ipfw continue rule"));
        ctx->ipfw_cont_rule = rule;
        ip_input(m);

        /* May not be cleared, if ipfw was unload/disabled. */
        ctx->ipfw_cont_rule = NULL;

        /*
         * This rule is no longer used; decrement its cross_refs,
         * so this rule can be deleted.
         */
        rule->cross_refs--;
}

static void
ipfw_defrag_redispatch(struct mbuf *m, int cpuid, struct ip_fw *rule)
{
        struct netmsg_genpkt *nm;

        KASSERT(cpuid != mycpuid, ("continue on the same cpu%d", cpuid));

        /*
         * NOTE:
         * Bump cross_refs to prevent this rule and its siblings
         * from being deleted, while this mbuf is inflight.  The
         * cross_refs of the sibling rule on the target cpu will
         * be decremented, once this mbuf is going to be filtered
         * on the target cpu.
         */
        rule->cross_refs++;
        m->m_pkthdr.fw_flags |= IPFW_MBUF_CONTINUE;

        nm = &m->m_hdr.mh_genmsg;
        netmsg_init(&nm->base, NULL, &netisr_apanic_rport, 0,
            ipfw_ip_input_dispatch);
        nm->m = m;
        nm->arg1 = rule->cross_rules[cpuid];
        netisr_sendmsg(&nm->base, cpuid);
}

static void
ipfw_init_args(struct ip_fw_args *args, struct mbuf *m, struct ifnet *oif)
{

        args->flags = 0;
        args->rule = NULL;
        args->xlat = NULL;

        if (m->m_pkthdr.fw_flags & DUMMYNET_MBUF_TAGGED) {
                struct m_tag *mtag;

                /* Extract info from dummynet tag */
                mtag = m_tag_find(m, PACKET_TAG_DUMMYNET, NULL);
                KKASSERT(mtag != NULL);
                args->rule = ((struct dn_pkt *)m_tag_data(mtag))->dn_priv;
                KKASSERT(args->rule != NULL);

                m_tag_delete(m, mtag);
                m->m_pkthdr.fw_flags &= ~DUMMYNET_MBUF_TAGGED;
        } else if (m->m_pkthdr.fw_flags & IPFW_MBUF_CONTINUE) {
                struct ipfw_context *ctx = ipfw_ctx[mycpuid];

                KKASSERT(ctx->ipfw_cont_rule != NULL);
                args->rule = ctx->ipfw_cont_rule;
                ctx->ipfw_cont_rule = NULL;

                if (ctx->ipfw_cont_xlat != NULL) {
                        args->xlat = ctx->ipfw_cont_xlat;
                        ctx->ipfw_cont_xlat = NULL;
                        if (m->m_pkthdr.fw_flags & IPFW_MBUF_XLATINS) {
                                args->flags |= IP_FWARG_F_XLATINS;
                                m->m_pkthdr.fw_flags &= ~IPFW_MBUF_XLATINS;
                        }
                        if (m->m_pkthdr.fw_flags & IPFW_MBUF_XLATFWD) {
                                args->flags |= IP_FWARG_F_XLATFWD;
                                m->m_pkthdr.fw_flags &= ~IPFW_MBUF_XLATFWD;
                        }
                }
                KKASSERT((m->m_pkthdr.fw_flags &
                    (IPFW_MBUF_XLATINS | IPFW_MBUF_XLATFWD)) == 0);

                args->flags |= IP_FWARG_F_CONT;
                m->m_pkthdr.fw_flags &= ~IPFW_MBUF_CONTINUE;
        }

        args->eh = NULL;
        args->oif = oif;
        args->m = m;
}

static int
ipfw_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
        struct ip_fw_args args;
        struct mbuf *m = *m0;
        int tee = 0, error = 0, ret;

        ipfw_init_args(&args, m, NULL);

        ret = ipfw_chk(&args);
        m = args.m;
        if (m == NULL) {
                if (ret != IP_FW_REDISPATCH)
                        error = EACCES;
                goto back;
        }

        switch (ret) {
        case IP_FW_PASS:
                break;

        case IP_FW_DENY:
                m_freem(m);
                m = NULL;
                error = EACCES;
                break;

        case IP_FW_DUMMYNET:
                /* Send packet to the appropriate pipe */
                m = ipfw_dummynet_io(m, args.cookie, DN_TO_IP_IN, &args);
                break;

        case IP_FW_TEE:
                tee = 1;
                /* FALL THROUGH */

        case IP_FW_DIVERT:
                /*
                 * Must clear bridge tag when changing
                 */
                m->m_pkthdr.fw_flags &= ~BRIDGE_MBUF_TAGGED;
                if (ip_divert_p != NULL) {
                        m = ip_divert_p(m, tee, 1);
                } else {
                        m_freem(m);
                        m = NULL;
                        /* not sure this is the right error msg */
                        error = EACCES;
                }
                break;

        default:
                panic("unknown ipfw return value: %d", ret);
        }
back:
        *m0 = m;
        return error;
}

static int
ipfw_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
        struct ip_fw_args args;
        struct mbuf *m = *m0;
        int tee = 0, error = 0, ret;

        ipfw_init_args(&args, m, ifp);

        ret = ipfw_chk(&args);
        m = args.m;
        if (m == NULL) {
                if (ret != IP_FW_REDISPATCH)
                        error = EACCES;
                goto back;
        }

        switch (ret) {
        case IP_FW_PASS:
                break;

        case IP_FW_DENY:
                m_freem(m);
                m = NULL;
                error = EACCES;
                break;

        case IP_FW_DUMMYNET:
                m = ipfw_dummynet_io(m, args.cookie, DN_TO_IP_OUT, &args);
                break;

        case IP_FW_TEE:
                tee = 1;
                /* FALL THROUGH */

        case IP_FW_DIVERT:
                if (ip_divert_p != NULL) {
                        m = ip_divert_p(m, tee, 0);
                } else {
                        m_freem(m);
                        m = NULL;
                        /* not sure this is the right error msg */
                        error = EACCES;
                }
                break;

        default:
                panic("unknown ipfw return value: %d", ret);
        }
back:
        *m0 = m;
        return error;
}

static void
ipfw_hook(void)
{
        struct pfil_head *pfh;

        ASSERT_NETISR0;

        pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
        if (pfh == NULL)
                return;

        pfil_add_hook(ipfw_check_in, NULL, PFIL_IN, pfh);
        pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT, pfh);
}

static void
ipfw_dehook(void)
{
        struct pfil_head *pfh;

        ASSERT_NETISR0;

        pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
        if (pfh == NULL)
                return;

        pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN, pfh);
        pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT, pfh);
}

static int
ipfw_sysctl_dyncnt(SYSCTL_HANDLER_ARGS)
{
        int dyn_cnt;

        dyn_cnt = ipfw_state_cntcoll();
        dyn_cnt += ipfw_gd.ipfw_trkcnt_cnt;

        return (sysctl_handle_int(oidp, &dyn_cnt, 0, req));
}

static int
ipfw_sysctl_statecnt(SYSCTL_HANDLER_ARGS)
{
        int state_cnt;

        state_cnt = ipfw_state_cntcoll();
        return (sysctl_handle_int(oidp, &state_cnt, 0, req));
}

static int
ipfw_sysctl_statemax(SYSCTL_HANDLER_ARGS)
{
        int state_max, error;

        state_max = ipfw_state_max;
        error = sysctl_handle_int(oidp, &state_max, 0, req);
        if (error || req->newptr == NULL)
                return (error);

        if (state_max < 1)
                return (EINVAL);

        ipfw_state_max_set(state_max);
        return (0);
}

static int
ipfw_sysctl_dynmax(SYSCTL_HANDLER_ARGS)
{
        int dyn_max, error;

        dyn_max = ipfw_state_max + ipfw_track_max;

        error = sysctl_handle_int(oidp, &dyn_max, 0, req);
        if (error || req->newptr == NULL)
                return (error);

        if (dyn_max < 2)
                return (EINVAL);

        ipfw_state_max_set(dyn_max / 2);
        ipfw_track_max = dyn_max / 2;
        return (0);
}

static void
ipfw_sysctl_enable_dispatch(netmsg_t nmsg)
{
        int enable = nmsg->lmsg.u.ms_result;

        ASSERT_NETISR0;

        if (fw_enable == enable)
                goto reply;

        fw_enable = enable;
        if (fw_enable)
                ipfw_hook();
        else
                ipfw_dehook();
reply:
        netisr_replymsg(&nmsg->base, 0);
}

static int
ipfw_sysctl_enable(SYSCTL_HANDLER_ARGS)
{
        struct netmsg_base nmsg;
        int enable, error;

        enable = fw_enable;
        error = sysctl_handle_int(oidp, &enable, 0, req);
        if (error || req->newptr == NULL)
                return error;

        netmsg_init(&nmsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_sysctl_enable_dispatch);
        nmsg.lmsg.u.ms_result = enable;

        return netisr_domsg(&nmsg, 0);
}

static int
ipfw_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS)
{
        return sysctl_int_range(oidp, arg1, arg2, req,
               IPFW_AUTOINC_STEP_MIN, IPFW_AUTOINC_STEP_MAX);
}

static int
ipfw_sysctl_scancnt(SYSCTL_HANDLER_ARGS)
{

        return sysctl_int_range(oidp, arg1, arg2, req, 1, INT_MAX);
}

static int
ipfw_sysctl_stat(SYSCTL_HANDLER_ARGS)
{
        u_long stat = 0;
        int cpu, error;

        for (cpu = 0; cpu < netisr_ncpus; ++cpu)
                stat += *((u_long *)((uint8_t *)ipfw_ctx[cpu] + arg2));

        error = sysctl_handle_long(oidp, &stat, 0, req);
        if (error || req->newptr == NULL)
                return (error);

        /* Zero out this stat. */
        for (cpu = 0; cpu < netisr_ncpus; ++cpu)
                *((u_long *)((uint8_t *)ipfw_ctx[cpu] + arg2)) = 0;
        return (0);
}

static void
ipfw_ctx_init_dispatch(netmsg_t nmsg)
{
        struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
        struct ipfw_context *ctx;
        struct ip_fw *def_rule;

        ASSERT_NETISR_NCPUS(mycpuid);

        ctx = kmalloc(__offsetof(struct ipfw_context,
            ipfw_tables[ipfw_table_max]), M_IPFW, M_WAITOK | M_ZERO);

        RB_INIT(&ctx->ipfw_state_tree);
        TAILQ_INIT(&ctx->ipfw_state_list);

        RB_INIT(&ctx->ipfw_track_tree);
        TAILQ_INIT(&ctx->ipfw_track_list);

        callout_init_mp(&ctx->ipfw_stateto_ch);
        netmsg_init(&ctx->ipfw_stateexp_nm, NULL, &netisr_adone_rport,
            MSGF_DROPABLE | MSGF_PRIORITY, ipfw_state_expire_dispatch);
        ctx->ipfw_stateexp_anch.st_type = O_ANCHOR;
        netmsg_init(&ctx->ipfw_stateexp_more, NULL, &netisr_adone_rport,
            MSGF_DROPABLE, ipfw_state_expire_more_dispatch);

        callout_init_mp(&ctx->ipfw_trackto_ch);
        netmsg_init(&ctx->ipfw_trackexp_nm, NULL, &netisr_adone_rport,
            MSGF_DROPABLE | MSGF_PRIORITY, ipfw_track_expire_dispatch);
        netmsg_init(&ctx->ipfw_trackexp_more, NULL, &netisr_adone_rport,
            MSGF_DROPABLE, ipfw_track_expire_more_dispatch);

        callout_init_mp(&ctx->ipfw_keepalive_ch);
        netmsg_init(&ctx->ipfw_keepalive_nm, NULL, &netisr_adone_rport,
            MSGF_DROPABLE | MSGF_PRIORITY, ipfw_keepalive_dispatch);
        ctx->ipfw_keepalive_anch.st_type = O_ANCHOR;
        netmsg_init(&ctx->ipfw_keepalive_more, NULL, &netisr_adone_rport,
            MSGF_DROPABLE, ipfw_keepalive_more_dispatch);

        callout_init_mp(&ctx->ipfw_xlatreap_ch);
        netmsg_init(&ctx->ipfw_xlatreap_nm, NULL, &netisr_adone_rport,
            MSGF_DROPABLE | MSGF_PRIORITY, ipfw_xlat_reap_dispatch);
        TAILQ_INIT(&ctx->ipfw_xlatreap);

        ipfw_ctx[mycpuid] = ctx;

        def_rule = kmalloc(sizeof(*def_rule), M_IPFW, M_WAITOK | M_ZERO);

        def_rule->act_ofs = 0;
        def_rule->rulenum = IPFW_DEFAULT_RULE;
        def_rule->cmd_len = 1;
        def_rule->set = IPFW_DEFAULT_SET;

        def_rule->cmd[0].len = 1;
#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
        def_rule->cmd[0].opcode = O_ACCEPT;
#else
        if (filters_default_to_accept)
                def_rule->cmd[0].opcode = O_ACCEPT;
        else
                def_rule->cmd[0].opcode = O_DENY;
#endif

        def_rule->refcnt = 1;
        def_rule->cpuid = mycpuid;

        /* Install the default rule */
        ctx->ipfw_default_rule = def_rule;
        ctx->ipfw_layer3_chain = def_rule;

        /* Link rule CPU sibling */
        ipfw_link_sibling(fwmsg, def_rule);

        /* Statistics only need to be updated once */
        if (mycpuid == 0)
                ipfw_inc_static_count(def_rule);

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_crossref_reap_dispatch(netmsg_t nmsg)
{

        crit_enter();
        /* Reply ASAP */
        netisr_replymsg(&nmsg->base, 0);
        crit_exit();
        ipfw_crossref_reap();
}

static void
ipfw_crossref_timeo(void *dummy __unused)
{
        struct netmsg_base *msg = &ipfw_gd.ipfw_crossref_nm;

        KKASSERT(mycpuid == 0);

        crit_enter();
        if (msg->lmsg.ms_flags & MSGF_DONE)
                netisr_sendmsg_oncpu(msg);
        crit_exit();
}

static void
ipfw_ifaddr_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];
        struct ifnet *ifp = nmsg->lmsg.u.ms_resultp;
        struct ip_fw *f;

        ASSERT_NETISR_NCPUS(mycpuid);

        for (f = ctx->ipfw_layer3_chain; f != NULL; f = f->next) {
                int l, cmdlen;
                ipfw_insn *cmd;

                if ((f->rule_flags & IPFW_RULE_F_DYNIFADDR) == 0)
                        continue;

                for (l = f->cmd_len, cmd = f->cmd; l > 0;
                     l -= cmdlen, cmd += cmdlen) {
                        cmdlen = F_LEN(cmd);
                        if (cmd->opcode == O_IP_SRC_IFIP ||
                            cmd->opcode == O_IP_DST_IFIP) {
                                if (strncmp(ifp->if_xname,
                                    ((ipfw_insn_ifip *)cmd)->ifname,
                                    IFNAMSIZ) == 0)
                                        cmd->arg1 &= ~IPFW_IFIP_VALID;
                        }
                }
        }
        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_ifaddr(void *arg __unused, struct ifnet *ifp,
    enum ifaddr_event event __unused, struct ifaddr *ifa __unused)
{
        struct netmsg_base nm;

        netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_ifaddr_dispatch);
        nm.lmsg.u.ms_resultp = ifp;
        netisr_domsg_global(&nm);
}

static void
ipfw_init_dispatch(netmsg_t nmsg)
{
        struct netmsg_ipfw fwmsg;
        int error = 0, cpu;

        ASSERT_NETISR0;

        if (IPFW_LOADED) {
                kprintf("IP firewall already loaded\n");
                error = EEXIST;
                goto reply;
        }

        if (ipfw_table_max > UINT16_MAX || ipfw_table_max <= 0)
                ipfw_table_max = UINT16_MAX;

        /* Initialize global track tree. */
        RB_INIT(&ipfw_gd.ipfw_trkcnt_tree);
        IPFW_TRKCNT_TOKINIT;

        /* GC for freed crossref rules. */
        callout_init_mp(&ipfw_gd.ipfw_crossref_ch);
        netmsg_init(&ipfw_gd.ipfw_crossref_nm, NULL, &netisr_adone_rport,
            MSGF_PRIORITY | MSGF_DROPABLE, ipfw_crossref_reap_dispatch);

        ipfw_state_max_set(ipfw_state_max);
        ipfw_state_headroom = 8 * netisr_ncpus;

        bzero(&fwmsg, sizeof(fwmsg));
        netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_ctx_init_dispatch);
        netisr_domsg_global(&fwmsg.base);

        ip_fw_chk_ptr = ipfw_chk;
        ip_fw_ctl_ptr = ipfw_ctl;
        ip_fw_dn_io_ptr = ipfw_dummynet_io;

        kprintf("ipfw2 initialized, default to %s, logging ",
                ipfw_ctx[mycpuid]->ipfw_default_rule->cmd[0].opcode ==
                O_ACCEPT ? "accept" : "deny");

#ifdef IPFIREWALL_VERBOSE
        fw_verbose = 1;
#endif
#ifdef IPFIREWALL_VERBOSE_LIMIT
        verbose_limit = IPFIREWALL_VERBOSE_LIMIT;
#endif
        if (fw_verbose == 0) {
                kprintf("disabled\n");
        } else if (verbose_limit == 0) {
                kprintf("unlimited\n");
        } else {
                kprintf("limited to %d packets/entry by default\n",
                        verbose_limit);
        }

        ip_fw_loaded = 1;
        for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
                callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_stateto_ch, hz,
                    ipfw_state_expire_ipifunc, NULL, cpu);
                callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_trackto_ch, hz,
                    ipfw_track_expire_ipifunc, NULL, cpu);
                callout_reset_bycpu(&ipfw_ctx[cpu]->ipfw_keepalive_ch, hz,
                    ipfw_keepalive, NULL, cpu);
        }

        if (fw_enable)
                ipfw_hook();

        ipfw_ifaddr_event = EVENTHANDLER_REGISTER(ifaddr_event, ipfw_ifaddr,
            NULL, EVENTHANDLER_PRI_ANY);
        if (ipfw_ifaddr_event == NULL)
                kprintf("ipfw: ifaddr_event register failed\n");

reply:
        netisr_replymsg(&nmsg->base, error);
}

static int
ipfw_init(void)
{
        struct netmsg_base smsg;

        netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_init_dispatch);
        return netisr_domsg(&smsg, 0);
}

#ifdef KLD_MODULE

static void
ipfw_ctx_fini_dispatch(netmsg_t nmsg)
{
        struct ipfw_context *ctx = ipfw_ctx[mycpuid];

        ASSERT_NETISR_NCPUS(mycpuid);

        callout_cancel(&ctx->ipfw_stateto_ch);
        callout_cancel(&ctx->ipfw_trackto_ch);
        callout_cancel(&ctx->ipfw_keepalive_ch);
        callout_cancel(&ctx->ipfw_xlatreap_ch);

        crit_enter();
        netisr_dropmsg(&ctx->ipfw_stateexp_more);
        netisr_dropmsg(&ctx->ipfw_stateexp_nm);
        netisr_dropmsg(&ctx->ipfw_trackexp_more);
        netisr_dropmsg(&ctx->ipfw_trackexp_nm);
        netisr_dropmsg(&ctx->ipfw_keepalive_more);
        netisr_dropmsg(&ctx->ipfw_keepalive_nm);
        netisr_dropmsg(&ctx->ipfw_xlatreap_nm);
        crit_exit();

        ipfw_table_flushall_oncpu(ctx, 1);

        netisr_forwardmsg(&nmsg->base, mycpuid + 1);
}

static void
ipfw_fini_dispatch(netmsg_t nmsg)
{
        struct netmsg_base nm;
        int error = 0, cpu;

        ASSERT_NETISR0;

        ipfw_crossref_reap();

        if (ipfw_gd.ipfw_refcnt != 0) {
                error = EBUSY;
                goto reply;
        }

        ip_fw_loaded = 0;
        ipfw_dehook();

        /* Synchronize any inflight state/track expire IPIs. */
        lwkt_synchronize_ipiqs("ipfwfini");

        netmsg_init(&nm, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_ctx_fini_dispatch);
        netisr_domsg_global(&nm);

        callout_cancel(&ipfw_gd.ipfw_crossref_ch);
        crit_enter();
        netisr_dropmsg(&ipfw_gd.ipfw_crossref_nm);
        crit_exit();

        if (ipfw_ifaddr_event != NULL)
                EVENTHANDLER_DEREGISTER(ifaddr_event, ipfw_ifaddr_event);

        ip_fw_chk_ptr = NULL;
        ip_fw_ctl_ptr = NULL;
        ip_fw_dn_io_ptr = NULL;
        ipfw_flush(1 /* kill default rule */);

        /* Free pre-cpu context */
        for (cpu = 0; cpu < netisr_ncpus; ++cpu)
                kfree(ipfw_ctx[cpu], M_IPFW);

        kprintf("IP firewall unloaded\n");
reply:
        netisr_replymsg(&nmsg->base, error);
}

static void
ipfw_fflush_dispatch(netmsg_t nmsg)
{

        ipfw_flush(0 /* keep default rule */);
        ipfw_crossref_reap();
        netisr_replymsg(&nmsg->base, 0);
}

static int
ipfw_fini(void)
{
        struct netmsg_base smsg;
        int i = 0;

        for (;;) {
                netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
                    ipfw_fflush_dispatch);
                netisr_domsg(&smsg, 0);

                if (ipfw_gd.ipfw_refcnt == 0)
                        break;
                kprintf("ipfw: flush pending %d\n", ++i);
                tsleep(&smsg, 0, "ipfwff", (3 * hz) / 2);
        }

        netmsg_init(&smsg, NULL, &curthread->td_msgport, MSGF_PRIORITY,
            ipfw_fini_dispatch);
        return netisr_domsg(&smsg, 0);
}

#endif  /* KLD_MODULE */

static int
ipfw_modevent(module_t mod, int type, void *unused)
{
        int err = 0;

        switch (type) {
        case MOD_LOAD:
                err = ipfw_init();
                break;

        case MOD_UNLOAD:
#ifndef KLD_MODULE
                kprintf("ipfw statically compiled, cannot unload\n");
                err = EBUSY;
#else
                err = ipfw_fini();
#endif
                break;
        default:
                break;
        }
        return err;
}

static moduledata_t ipfwmod = {
        "ipfw",
        ipfw_modevent,
        0
};
DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PROTO_END, SI_ORDER_ANY);
MODULE_VERSION(ipfw, 1);