/*
 * Copyright (c) 1993 Daniel Boulet
 * Copyright (c) 1994 Ugen J.S.Antsilevich
 * Copyright (c) 2002 Luigi Rizzo, Universita` di Pisa
 * Copyright (c) 2014 - 2018 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Bill Yuan <bycn82@dragonflybsd.org>
 *
 * 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.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

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

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

#include <net/if.h>
#include <net/radix.h>
#include <net/route.h>
#include <net/pfil.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_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>

#include <net/ipfw3/ip_fw.h>
#include <net/ipfw3/ip_fw3_set.h>
#include <net/ipfw3_basic/ip_fw3_log.h>
#include <net/ipfw3_basic/ip_fw3_table.h>
#include <net/ipfw3_basic/ip_fw3_sync.h>
#include <net/ipfw3_basic/ip_fw3_basic.h>
#include <net/ipfw3_basic/ip_fw3_state.h>
#include <net/ipfw3_nat/ip_fw3_nat.h>
#include <net/dummynet3/ip_dummynet3.h>

MALLOC_DEFINE(M_IPFW3, "IPFW3", "ipfw3 module");

#define MAX_MODULE		10
#define MAX_OPCODE_PER_MODULE	100

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


struct netmsg_ipfw {
	struct netmsg_base base;
	const struct ipfw_ioc_rule *ioc_rule;
	struct ip_fw	*rule;
	struct ip_fw	*next_rule;
	struct ip_fw	*prev_rule;
	struct ip_fw	*sibling;	/* sibling in prevous CPU */
};

struct netmsg_del {
	struct netmsg_base base;
	struct ip_fw	*rule;
	struct ip_fw	*start_rule;
	struct ip_fw	*prev_rule;
	struct ipfw_ioc_state *ioc_state;
	uint16_t	rulenum;
	uint8_t		set_from;
	uint8_t		set_to;
	int		kill_default;
};

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

ip_fw_ctl_t	*ip_fw3_ctl_nat_ptr = NULL;
ip_fw_ctl_t	*ip_fw3_ctl_state_ptr = NULL;
ip_fw_ctl_t	*ip_fw3_ctl_table_ptr = NULL;
ip_fw_ctl_t	*ip_fw3_ctl_sync_ptr = NULL;
ip_fw_log_t	*ip_fw3_log_ptr = NULL;

extern int ip_fw_loaded;
extern struct ipfw3_state_context 	*fw3_state_ctx[MAXCPU];
int 			sysctl_var_fw3_enable = 1;
int 			sysctl_var_fw3_one_pass = 1;
int 			sysctl_var_fw3_verbose = 0;
static int 		sysctl_var_fw3_flushing;
static int 		sysctl_var_fw3_debug;
static int 		sysctl_var_autoinc_step = IPFW_AUTOINC_STEP_DEF;

int	ip_fw3_sysctl_enable(SYSCTL_HANDLER_ARGS);
int	ip_fw3_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS);

SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw3, CTLFLAG_RW, 0, "Firewall");
SYSCTL_PROC(_net_inet_ip_fw3, OID_AUTO, enable, CTLTYPE_INT | CTLFLAG_RW,
	&sysctl_var_fw3_enable, 0, ip_fw3_sysctl_enable, "I", "Enable ipfw");
SYSCTL_PROC(_net_inet_ip_fw3, OID_AUTO, sysctl_var_autoinc_step,
	CTLTYPE_INT | CTLFLAG_RW, &sysctl_var_autoinc_step, 0,
	ip_fw3_sysctl_autoinc_step, "I", "Rule number autincrement step");
SYSCTL_INT(_net_inet_ip_fw3, OID_AUTO,one_pass,CTLFLAG_RW,
	&sysctl_var_fw3_one_pass, 0,
	"Only do a single pass through ipfw3 when using dummynet(4)");
SYSCTL_INT(_net_inet_ip_fw3, OID_AUTO, debug, CTLFLAG_RW,
	&sysctl_var_fw3_debug, 0, "Enable printing of debug ip_fw statements");
SYSCTL_INT(_net_inet_ip_fw3, OID_AUTO, verbose, CTLFLAG_RW,
	&sysctl_var_fw3_verbose, 0, "Log matches to ipfw3 rules");


filter_func 			filter_funcs[MAX_MODULE][MAX_OPCODE_PER_MODULE];
struct ipfw3_module 		fw3_modules[MAX_MODULE];
struct ipfw3_context 		*fw3_ctx[MAXCPU];
struct ipfw3_sync_context 	fw3_sync_ctx;


void
ip_fw3_register_module(int module_id,char *module_name)
{
	struct ipfw3_module *tmp;
	int i;

	tmp = fw3_modules;
	for (i=0; i < MAX_MODULE; i++) {
		if (tmp->type == 0) {
			tmp->type = 1;
			tmp->id = module_id;
			strncpy(tmp->name, module_name, strlen(module_name));
			break;
		}
		tmp++;
	}
	kprintf("ipfw3 module %s loaded\n", module_name);
}

int
ip_fw3_unregister_module(int module_id)
{
	struct ipfw3_module *tmp;
	struct ip_fw *fw;
	ipfw_insn *cmd;
	int i, len, cmdlen, found;

	found = 0;
	tmp = fw3_modules;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	fw = ctx->rules;
	for (; fw; fw = fw->next) {
		for (len = fw->cmd_len, cmd = fw->cmd; len > 0;
			len -= cmdlen,
			cmd = (ipfw_insn *)((uint32_t *)cmd + cmdlen)) {
			cmdlen = F_LEN(cmd);
			if (cmd->module == 0 &&
				(cmd->opcode == 0 || cmd->opcode == 1)) {
				//action accept or deny
			} else if (cmd->module == module_id) {
				found = 1;
				goto decide;
			}
		}
	}
decide:
	if (found) {
		return 1;
	} else {
		for (i = 0; i < MAX_MODULE; i++) {
			if (tmp->type == 1 && tmp->id == module_id) {
				tmp->type = 0;
				kprintf("ipfw3 module %s unloaded\n",
						tmp->name);
				break;
			}
			tmp++;
		}

		for (i = 0; i < MAX_OPCODE_PER_MODULE; i++) {
			if (module_id == 0) {
				if (i ==0 || i == 1) {
					continue;
				}
			}
			filter_funcs[module_id][i] = NULL;
		}
		return 0;
	}
}

void
ip_fw3_register_filter_funcs(int module, int opcode, filter_func func)
{
	filter_funcs[module][opcode] = func;
}

void
check_accept(int *cmd_ctl, int *cmd_val, struct ip_fw_args **args,
		struct ip_fw **f, ipfw_insn *cmd, uint16_t ip_len)
{
	*cmd_val = IP_FW_PASS;
	*cmd_ctl = IP_FW_CTL_DONE;
	if (cmd->arg3 && ip_fw3_log_ptr != NULL) {
		ip_fw3_log_ptr((*args)->m, (*args)->eh, cmd->arg1);
	}
}

void
check_deny(int *cmd_ctl, int *cmd_val, struct ip_fw_args **args,
		struct ip_fw **f, ipfw_insn *cmd, uint16_t ip_len)
{
	*cmd_val = IP_FW_DENY;
	*cmd_ctl = IP_FW_CTL_DONE;
	if (cmd->arg3 && ip_fw3_log_ptr != NULL) {
		ip_fw3_log_ptr((*args)->m, (*args)->eh, cmd->arg1);
	}
}

void
init_module(void)
{
	memset(fw3_modules, 0, sizeof(struct ipfw3_module) * MAX_MODULE);
	memset(filter_funcs, 0, sizeof(filter_func) *
			MAX_OPCODE_PER_MODULE * MAX_MODULE);
	ip_fw3_register_filter_funcs(0, O_BASIC_ACCEPT,
			(filter_func)check_accept);
	ip_fw3_register_filter_funcs(0, O_BASIC_DENY, (filter_func)check_deny);
}

int
ip_fw3_free_rule(struct ip_fw *rule)
{
	kfree(rule, M_IPFW3);
	rule = NULL;
	return 1;
}

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 ((int)cmd->module == MODULE_BASIC_ID &&
		(int)cmd->opcode == O_BASIC_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;
}

/*
 * rules are stored in ctx->ipfw_rule_chain.
 * and each rule is combination of multiple cmds.(ipfw_insn)
 * in each rule, it begin with filter cmds. and end with action cmds.
 * 'outer/inner loop' are looping the rules/cmds.
 * it will invoke the cmds relatived function according to the cmd's
 * module id and opcode id. and process according to return value.
 */
int
ip_fw3_chk(struct ip_fw_args *args)
{
	struct tcphdr *tcp;
	struct udphdr *udp;
	struct icmp *icmp;

	struct mbuf *m = args->m;
	struct ip *ip = mtod(m, struct ip *);
	struct ip_fw *f = NULL;		/* matching rule */
	int cmd_val = IP_FW_PASS;
	struct m_tag *mtag;
	struct divert_info *divinfo;

	/*
	 * 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 */

	/*
	 * 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 = 0;

	uint8_t proto;
	uint16_t src_port = 0, dst_port = 0;	/* NOTE: host format	*/
	struct in_addr src_ip, dst_ip;		/* NOTE: network format	*/
	uint16_t ip_len = 0;
	uint8_t prev_module = -1, prev_opcode = -1; /* previous module & opcode */
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];

	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;

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

	proto = args->f_id.proto = ip->ip_p;
	src_ip = ip->ip_src;
	dst_ip = ip->ip_dst;
	offset = ntohs(ip->ip_off) & IP_OFFMASK;
	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)			\
				goto pullup_failed;	\
		ip = mtod(m, struct ip *);		\
	}						\
} while (0)

	if (offset == 0) {
		switch (proto) {
			case IPPROTO_TCP:
				PULLUP_TO(hlen + sizeof(struct tcphdr));
				tcp = L3HDR(struct tcphdr, ip);
				dst_port = tcp->th_dport;
				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);
				dst_port = udp->uh_dport;
				src_port = udp->uh_sport;
				break;
			case IPPROTO_ICMP:
				PULLUP_TO(hlen + 4);
				icmp = L3HDR(struct icmp, ip);
  				args->f_id.flags = icmp->icmp_type;
				dst_port = icmp->icmp_id;
				src_port = dst_port;
				break;
			default:
				break;
		}
	}

#undef PULLUP_TO

	args->f_id.src_ip = ntohl(src_ip.s_addr);
	args->f_id.dst_ip = ntohl(dst_ip.s_addr);
	args->f_id.src_port = src_port = ntohs(src_port);
	args->f_id.dst_port = dst_port = ntohs(dst_port);

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

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

		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;

		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->rules;
		if (args->eh == NULL && skipto != 0) {
			/* No skipto during rule flushing */
			if (sysctl_var_fw3_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 (sysctl_var_fw3_flushing) {
			/* Rules are being flushed; skip to default rule */
			f = ctx->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.
	 */
	int prev_val;	/*  previous result of 'or' filter */
	int l, cmdlen;
	ipfw_insn *cmd;
	int cmd_ctl;
	/* foreach rule in chain */
	for (; f; f = f->next) {
again:  /* check the rule again*/
		if (ctx->sets & (1 << f->set)) {
			continue;
		}

		prev_val = -1;
		 /* foreach cmd in rule */
		for (l = f->cmd_len, cmd = f->cmd; l > 0; l -= cmdlen,
			cmd = (ipfw_insn *)((uint32_t *)cmd+ cmdlen)) {
			cmdlen = F_LEN(cmd);

			/* skip 'or' filter when already match */
			if (cmd->len & F_OR &&
				cmd->module == prev_module &&
				cmd->opcode == prev_opcode &&
				prev_val == 1) {
				goto next_cmd;
			}

check_body: /* check the body of the rule again.*/
			(filter_funcs[cmd->module][cmd->opcode])
				(&cmd_ctl, &cmd_val, &args, &f, cmd, ip_len);
			switch(cmd_ctl) {
				case IP_FW_CTL_DONE:
					if (prev_val == 0) /* but 'or' failed */
						goto next_rule;
					goto done;
				case IP_FW_CTL_AGAIN:
					goto again;
				case IP_FW_CTL_NEXT:
					goto next_rule;
				case IP_FW_CTL_NAT:
					args->rule=f;
					goto done;
				case IP_FW_CTL_CHK_STATE:
					/* update the cmd and l */
					cmd = ACTION_PTR(f);
					l = f->cmd_len - f->act_ofs;
					goto check_body;
			}
			if (cmd->len & F_NOT)
				cmd_val= !cmd_val;

			if (cmd->len & F_OR) {	/* has 'or' */
				if (!cmd_val) {	/* not matched */
					if(prev_val == -1){	/* first 'or' */
						prev_val = 0;
						prev_module = cmd->module;
						prev_opcode = cmd->opcode;
					} else if (prev_module == cmd->module &&
						prev_opcode == cmd->opcode) {
						/* continuous 'or' filter */
					} else if (prev_module != cmd->module ||
						prev_opcode != cmd->opcode) {
						/* 'or' filter changed */
						if(prev_val == 0){
							goto next_rule;
						} else {
							prev_val = 0;
							prev_module = cmd->module;
							prev_opcode = cmd->opcode;
						}
					}
				} else { /* has 'or' and matched */
					prev_val = 1;
					prev_module = cmd->module;
					prev_opcode = cmd->opcode;
				}
			} else { /* no or */
				if (!cmd_val) {	/* not matched */
					goto next_rule;
				} else {
					if (prev_val == 0) {
						/* previous 'or' not matched */
						goto next_rule;
					} else {
						prev_val = -1;
					}
				}
			}
next_cmd:;
		}	/* 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 += ip_len;
	f->timestamp = time_second;
	return cmd_val;

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

struct mbuf *
ip_fw3_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 = (ipfw_insn *)((uint32_t *)fwa->rule->cmd + fwa->rule->act_ofs);
	KASSERT(cmd->opcode == O_DUMMYNET_PIPE ||
			cmd->opcode == O_DUMMYNET_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;

	pkt->dn_priv = fwa->rule;

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

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


void
add_rule_dispatch(netmsg_t nmsg)
{
	struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct ip_fw *rule, *prev,*next;
	const struct ipfw_ioc_rule *ioc_rule;

	ioc_rule = fwmsg->ioc_rule;
	 // create rule by ioc_rule
	rule = kmalloc(RULESIZE(ioc_rule), M_IPFW3, 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;
	bcopy(ioc_rule->cmd, rule->cmd, rule->cmd_len * 4);

	for (prev = NULL, next = ctx->rules;
		next; prev = next, next = next->next) {
		if (ioc_rule->insert) {
			if (next->rulenum >= ioc_rule->rulenum) {
				break;
			}
		} else {
			if (next->rulenum > ioc_rule->rulenum) {
				break;
			}
		}
	}
	KASSERT(next != NULL, ("no default rule?!"));

	/*
	 * Insert rule into the pre-determined position
	 */
	if (prev != NULL) {
		rule->next = next;
		prev->next = rule;
	} else {
		rule->next = ctx->rules;
		ctx->rules = rule;
	}

	/*
	 * if sibiling in last CPU is exists,
	 * then it's sibling should be current rule
	 */
	if (fwmsg->sibling != NULL) {
		fwmsg->sibling->sibling = rule;
	}
	/* prepare for next CPU */
	fwmsg->sibling = rule;

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

/*
 * confirm the rulenumber
 * call dispatch function to add rule into the list
 * Update the statistic
 */
void
ip_fw3_add_rule(struct ipfw_ioc_rule *ioc_rule)
{
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct netmsg_ipfw fwmsg;
	struct netmsg_base *nmsg;
	struct ip_fw *f;

	IPFW_ASSERT_CFGPORT(&curthread->td_msgport);

	/*
	 * 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 = sysctl_var_autoinc_step;

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

		/*
		 * Locate the highest numbered rule before default
		 */
		for (f = ctx->rules; 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));

	bzero(&fwmsg, sizeof(fwmsg));
	nmsg = &fwmsg.base;
	netmsg_init(nmsg, NULL, &curthread->td_msgport,
			0, add_rule_dispatch);
	fwmsg.ioc_rule = ioc_rule;

	netisr_domsg(nmsg, 0);

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

/**
 * Free storage associated with a static rule (including derived
 * dynamic rules).
 * 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.
 * Must be called at splimp().
 */
struct ip_fw *
ip_fw3_delete_rule(struct ipfw3_context *ctx,
		 struct ip_fw *prev, struct ip_fw *rule)
{
	if (prev == NULL)
		ctx->rules = rule->next;
	else
		prev->next = rule->next;

	kfree(rule, M_IPFW3);
	rule = NULL;
	return NULL;
}

void
flush_rule_dispatch(netmsg_t nmsg)
{
	struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct ip_fw *rule, *the_rule;
	int kill_default = dmsg->kill_default;

	rule = ctx->rules;
	while (rule != NULL) {
		if (rule->rulenum == IPFW_DEFAULT_RULE && kill_default == 0) {
			ctx->rules = rule;
			break;
		}
		the_rule = rule;
		rule = rule->next;

		kfree(the_rule, M_IPFW3);
	}

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


/*
 * Deletes all rules from a chain (including the default rule
 * if the second argument is set).
 * Must be called at splimp().
 */
void
ip_fw3_ctl_flush_rule(int kill_default)
{
	struct netmsg_del dmsg;

	IPFW_ASSERT_CFGPORT(&curthread->td_msgport);

	if (!kill_default) {
		sysctl_var_fw3_flushing = 1;
		netmsg_service_sync();
	}
	/*
	 * Press the 'flush' button
	 */
	bzero(&dmsg, sizeof(dmsg));
	netmsg_init(&dmsg.base, NULL, &curthread->td_msgport,
			0, flush_rule_dispatch);
	dmsg.kill_default = kill_default;
	netisr_domsg(&dmsg.base, 0);

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

void
delete_rule_dispatch(netmsg_t nmsg)
{
	struct netmsg_del *dmsg = (struct netmsg_del *)nmsg;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct ip_fw *rule, *prev = NULL;

	rule = ctx->rules;
	while (rule!=NULL) {
		if (rule->rulenum == dmsg->rulenum) {
			ip_fw3_delete_rule(ctx, prev, rule);
			break;
		}
		prev = rule;
		rule = rule->next;
	}

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

int
ip_fw3_ctl_delete_rule(struct sockopt *sopt)
{
	struct netmsg_del dmsg;
	struct netmsg_base *nmsg;
	int *rulenum;

	rulenum = (int *) sopt->sopt_val;


	/*
	 * Get rid of the rule duplications on all CPUs
	 */
	bzero(&dmsg, sizeof(dmsg));
	nmsg = &dmsg.base;
	netmsg_init(nmsg, NULL, &curthread->td_msgport,
			0, delete_rule_dispatch);
	dmsg.rulenum = *rulenum;
	netisr_domsg(nmsg, 0);
	return 0;
}

/*
 * Clear counters for a specific rule.
 */
void
ip_fw3_clear_counters(struct ip_fw *rule)
{
	rule->bcnt = rule->pcnt = 0;
	rule->timestamp = 0;
}

void
ip_fw3_zero_entry_dispatch(netmsg_t nmsg)
{
	struct netmsg_zent *zmsg = (struct netmsg_zent *)nmsg;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct ip_fw *rule;

	if (zmsg->rulenum == 0) {
		for (rule = ctx->rules; rule; rule = rule->next) {
			ip_fw3_clear_counters(rule);
		}
	} else {
		for (rule = ctx->rules; rule; rule = rule->next) {
			if (rule->rulenum == zmsg->rulenum) {
				ip_fw3_clear_counters(rule);
			}
		}
	}
	ip_fw3_clear_counters(ctx->default_rule);
	netisr_forwardmsg_all(&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.
 */
int
ip_fw3_ctl_zero_entry(int rulenum, int log_only)
{
	struct netmsg_zent zmsg;
	struct netmsg_base *nmsg;
	const char *msg;
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];

	bzero(&zmsg, sizeof(zmsg));
	nmsg = &zmsg.base;
	netmsg_init(nmsg, NULL, &curthread->td_msgport,
			0, ip_fw3_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->rules; 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(nmsg, 0);
	KKASSERT(zmsg.start_rule == NULL);

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

/*
 * Get the ioc_rule from the sopt
 * call ip_fw3_add_rule to add the rule
 */
int
ip_fw3_ctl_add_rule(struct sockopt *sopt)
{
	struct ipfw_ioc_rule *ioc_rule;
	size_t size;

	size = sopt->sopt_valsize;
	if (size > (sizeof(uint32_t) * IPFW_RULE_SIZE_MAX) ||
			size < sizeof(*ioc_rule) - sizeof(ipfw_insn)) {
		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;

	ip_fw3_add_rule(ioc_rule);
	return 0;
}

int
ip_fw3_ctl_get_modules(struct sockopt *sopt)
{
	int i;
	struct ipfw3_module *mod;
	char module_str[1024];
	memset(module_str,0,1024);
	for (i = 0, mod = fw3_modules; i < MAX_MODULE; i++, mod++) {
		if (mod->type != 0) {
			if (i > 0)
				strcat(module_str,",");
			strcat(module_str,mod->name);
		}
	}
	bzero(sopt->sopt_val, sopt->sopt_valsize);
	bcopy(module_str, sopt->sopt_val, strlen(module_str));
	sopt->sopt_valsize = strlen(module_str);
	return 0;
}

/*
 * Copy all static rules and states on all CPU
 */
int
ip_fw3_ctl_get_rules(struct sockopt *sopt)
{
	struct ipfw3_context *ctx = fw3_ctx[mycpuid];
	struct ip_fw *rule;
	struct ipfw_ioc_rule *ioc;
	const struct ip_fw *sibling;
	int total_len = 0;

	ioc = (struct ipfw_ioc_rule *)sopt->sopt_val;

	for (rule = ctx->rules; rule; rule = rule->next) {
		total_len += IOC_RULESIZE(rule);
		if (total_len > sopt->sopt_valsize) {
			bzero(sopt->sopt_val, sopt->sopt_valsize);
			return 0;
		}
		ioc->act_ofs = rule->act_ofs;
		ioc->cmd_len = rule->cmd_len;
		ioc->rulenum = rule->rulenum;
		ioc->set = rule->set;

		ioc->sets = fw3_ctx[mycpuid]->sets;
		ioc->pcnt = 0;
		ioc->bcnt = 0;
		ioc->timestamp = 0;
		for (sibling = rule; sibling != NULL; sibling = sibling->sibling) {
			ioc->pcnt += sibling->pcnt;
			ioc->bcnt += sibling->bcnt;
			if (sibling->timestamp > ioc->timestamp)
				ioc->timestamp = sibling->timestamp;
		}
		bcopy(rule->cmd, ioc->cmd, ioc->cmd_len * 4);
		ioc = (struct ipfw_ioc_rule *)((uint8_t *)ioc + IOC_RULESIZE(ioc));
	}
	sopt->sopt_valsize = total_len;
	return 0;
}


/*
 * ip_fw3_ctl_x - extended version of ip_fw3_ctl
 * remove the x_header, and adjust the sopt_name, sopt_val and sopt_valsize.
 */
int
ip_fw3_ctl_x(struct sockopt *sopt)
{
	ip_fw_x_header *x_header;
	x_header = (ip_fw_x_header *)(sopt->sopt_val);
	sopt->sopt_name = x_header->opcode;
	sopt->sopt_valsize -= sizeof(ip_fw_x_header);
	bcopy(++x_header, sopt->sopt_val, sopt->sopt_valsize);
	return ip_fw3_ctl(sopt);
}


/**
 * {set|get}sockopt parser.
 */
int
ip_fw3_ctl(struct sockopt *sopt)
{
	int error = 0;
	switch (sopt->sopt_name) {
		case IP_FW_X:
			ip_fw3_ctl_x(sopt);
			break;
		case IP_FW_GET:
		case IP_FW_MODULE:
		case IP_FW_FLUSH:
		case IP_FW_ADD:
		case IP_FW_DEL:
		case IP_FW_ZERO:
		case IP_FW_RESETLOG:
			error = ip_fw3_ctl_sockopt(sopt);
			break;
		case IP_FW_SET_GET:
		case IP_FW_SET_MOVE_RULE:
		case IP_FW_SET_MOVE_SET:
		case IP_FW_SET_SWAP:
		case IP_FW_SET_TOGGLE:
			error = ip_fw3_ctl_set_sockopt(sopt);
			break;
		case IP_FW_NAT_ADD:
		case IP_FW_NAT_DEL:
		case IP_FW_NAT_FLUSH:
		case IP_FW_NAT_GET:
		case IP_FW_NAT_GET_RECORD:
			if (ip_fw3_ctl_nat_ptr != NULL) {
				error = ip_fw3_ctl_nat_ptr(sopt);
			}
			break;
		case IP_DUMMYNET_GET:
		case IP_DUMMYNET_CONFIGURE:
		case IP_DUMMYNET_DEL:
		case IP_DUMMYNET_FLUSH:
			error = ip_dn_sockopt(sopt);
			break;
		case IP_FW_STATE_ADD:
		case IP_FW_STATE_DEL:
		case IP_FW_STATE_FLUSH:
		case IP_FW_STATE_GET:
			if (ip_fw3_ctl_state_ptr != NULL) {
				error = ip_fw3_ctl_state_ptr(sopt);
			}
			break;
		case IP_FW_TABLE_CREATE:
		case IP_FW_TABLE_DELETE:
		case IP_FW_TABLE_APPEND:
		case IP_FW_TABLE_REMOVE:
		case IP_FW_TABLE_LIST:
		case IP_FW_TABLE_FLUSH:
		case IP_FW_TABLE_SHOW:
		case IP_FW_TABLE_TEST:
		case IP_FW_TABLE_RENAME:
			if (ip_fw3_ctl_table_ptr != NULL) {
				error = ip_fw3_ctl_table_ptr(sopt);
			}
			break;
		case IP_FW_SYNC_SHOW_CONF:
		case IP_FW_SYNC_SHOW_STATUS:
		case IP_FW_SYNC_EDGE_CONF:
		case IP_FW_SYNC_EDGE_START:
		case IP_FW_SYNC_EDGE_STOP:
		case IP_FW_SYNC_EDGE_TEST:
		case IP_FW_SYNC_EDGE_CLEAR:
		case IP_FW_SYNC_CENTRE_CONF:
		case IP_FW_SYNC_CENTRE_START:
		case IP_FW_SYNC_CENTRE_STOP:
		case IP_FW_SYNC_CENTRE_TEST:
		case IP_FW_SYNC_CENTRE_CLEAR:
			if (ip_fw3_ctl_sync_ptr != NULL) {
				error = ip_fw3_ctl_sync_ptr(sopt);
			}
			break;
		default:
			kprintf("ip_fw3_ctl invalid option %d\n",
				sopt->sopt_name);
			error = EINVAL;
	}
	return error;
}

int
ip_fw3_ctl_sockopt(struct sockopt *sopt)
{
	int error = 0, rulenum;

	switch (sopt->sopt_name) {
		case IP_FW_GET:
			error = ip_fw3_ctl_get_rules(sopt);
			break;
		case IP_FW_MODULE:
			error = ip_fw3_ctl_get_modules(sopt);
			break;
		case IP_FW_FLUSH:
			ip_fw3_ctl_flush_rule(0);
			break;
		case IP_FW_ADD:
			error = ip_fw3_ctl_add_rule(sopt);
			break;
		case IP_FW_DEL:
			error = ip_fw3_ctl_delete_rule(sopt);
			break;
		case IP_FW_ZERO:
		case IP_FW_RESETLOG: /* argument is an int, the rule number */
			rulenum = 0;
			if (sopt->sopt_valsize != 0) {
				error = soopt_to_kbuf(sopt, &rulenum,
						sizeof(int), sizeof(int));
				if (error) {
					break;
				}
			}
			error = ip_fw3_ctl_zero_entry(rulenum,
					sopt->sopt_name == IP_FW_RESETLOG);
			break;
		default:
			kprintf("ip_fw3_ctl invalid option %d\n",
				sopt->sopt_name);
			error = EINVAL;
	}
	return error;
}

int
ip_fw3_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
	struct ip_fw_args args;
	struct mbuf *m = *m0;
	struct m_tag *mtag;
	int tee = 0, error = 0, ret;
	// again:
	if (m->m_pkthdr.fw_flags & DUMMYNET_MBUF_TAGGED) {
		/* 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 {
		args.rule = NULL;
	}

	args.eh = NULL;
	args.oif = NULL;
	args.m = m;
	ret = ip_fw3_chk(&args);
	m = args.m;

	if (m == NULL) {
		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 = ip_fw3_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;
		case IP_FW_NAT:
			break;
		case IP_FW_ROUTE:
			break;
		default:
			panic("unknown ipfw3 return value: %d", ret);
	}
back:
	*m0 = m;
	return error;
}

int
ip_fw3_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir)
{
	struct ip_fw_args args;
	struct mbuf *m = *m0;
	struct m_tag *mtag;
	int tee = 0, error = 0, ret;
	// again:
	if (m->m_pkthdr.fw_flags & DUMMYNET_MBUF_TAGGED) {
		/* 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 {
		args.rule = NULL;
	}

	args.eh = NULL;
	args.m = m;
	args.oif = ifp;
	ret = ip_fw3_chk(&args);
	m = args.m;

	if (m == NULL) {
		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 = ip_fw3_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;

		case IP_FW_NAT:
			break;
		case IP_FW_ROUTE:
			break;
		default:
			panic("unknown ipfw3 return value: %d", ret);
	}
back:
	*m0 = m;
	return error;
}

void
ip_fw3_hook(void)
{
	struct pfil_head *pfh;
	IPFW_ASSERT_CFGPORT(&curthread->td_msgport);

	pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
	if (pfh == NULL)
		return;

	pfil_add_hook(ip_fw3_check_in, NULL, PFIL_IN, pfh);
	pfil_add_hook(ip_fw3_check_out, NULL, PFIL_OUT, pfh);
}

void
ip_fw3_dehook(void)
{
	struct pfil_head *pfh;

	IPFW_ASSERT_CFGPORT(&curthread->td_msgport);

	pfh = pfil_head_get(PFIL_TYPE_AF, AF_INET);
	if (pfh == NULL)
		return;

	pfil_remove_hook(ip_fw3_check_in, NULL, PFIL_IN, pfh);
	pfil_remove_hook(ip_fw3_check_out, NULL, PFIL_OUT, pfh);
}

void
ip_fw3_sysctl_enable_dispatch(netmsg_t nmsg)
{
	struct lwkt_msg *lmsg = &nmsg->lmsg;
	int enable = lmsg->u.ms_result;

	if (sysctl_var_fw3_enable == enable)
		goto reply;

	sysctl_var_fw3_enable = enable;
	if (sysctl_var_fw3_enable)
		ip_fw3_hook();
	else
		ip_fw3_dehook();

reply:
	lwkt_replymsg(lmsg, 0);
}

int
ip_fw3_sysctl_enable(SYSCTL_HANDLER_ARGS)
{
	struct netmsg_base nmsg;
	struct lwkt_msg *lmsg;
	int enable, error;

	enable = sysctl_var_fw3_enable;
	error = sysctl_handle_int(oidp, &enable, 0, req);
	if (error || req->newptr == NULL)
		return error;

	netmsg_init(&nmsg, NULL, &curthread->td_msgport,
			0, ip_fw3_sysctl_enable_dispatch);
	lmsg = &nmsg.lmsg;
	lmsg->u.ms_result = enable;

	return lwkt_domsg(IPFW_CFGPORT, lmsg, 0);
}

int
ip_fw3_sysctl_autoinc_step(SYSCTL_HANDLER_ARGS)
{
	return sysctl_int_range(oidp, arg1, arg2, req,
			IPFW_AUTOINC_STEP_MIN, IPFW_AUTOINC_STEP_MAX);
}

void
ctx_init_dispatch(netmsg_t nmsg)
{
	struct netmsg_ipfw *fwmsg = (struct netmsg_ipfw *)nmsg;
	struct ipfw3_context *ctx;
	struct ip_fw *def_rule;

	ctx = kmalloc(LEN_FW3_CTX, M_IPFW3, M_WAITOK | M_ZERO);
	fw3_ctx[mycpuid] = ctx;
	ctx->sets = IPFW_ALL_SETS;

	def_rule = kmalloc(LEN_FW3, M_IPFW3, M_WAITOK | M_ZERO);
	def_rule->act_ofs = 0;
	def_rule->rulenum = IPFW_DEFAULT_RULE;
	def_rule->cmd_len = 2;
	def_rule->set = IPFW_DEFAULT_SET;

	def_rule->cmd[0].len = LEN_OF_IPFWINSN;
	def_rule->cmd[0].module = MODULE_BASIC_ID;
#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
	def_rule->cmd[0].opcode = O_BASIC_ACCEPT;
#else
	if (filters_default_to_accept)
		def_rule->cmd[0].opcode = O_BASIC_ACCEPT;
	else
		def_rule->cmd[0].opcode = O_BASIC_DENY;
#endif

	/* Install the default rule */
	ctx->default_rule = def_rule;
	ctx->rules = def_rule;

	/*
	 * if sibiling in last CPU is exists,
	 * then it's sibling should be current rule
	 */
	if (fwmsg->sibling != NULL) {
		fwmsg->sibling->sibling = def_rule;
	}
	/* prepare for next CPU */
	fwmsg->sibling = def_rule;

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

void
init_dispatch(netmsg_t nmsg)
{
	struct netmsg_ipfw fwmsg;
	int error = 0;
	if (IPFW3_LOADED) {
		kprintf("ipfw3 already loaded\n");
		error = EEXIST;
		goto reply;
	}

	bzero(&fwmsg, sizeof(fwmsg));
	netmsg_init(&fwmsg.base, NULL, &curthread->td_msgport,
			0, ctx_init_dispatch);
	netisr_domsg(&fwmsg.base, 0);

	ip_fw_chk_ptr = ip_fw3_chk;
	ip_fw_ctl_x_ptr = ip_fw3_ctl_x;
	ip_fw_dn_io_ptr = ip_fw3_dummynet_io;

	kprintf("ipfw3 initialized, default to %s\n",
			filters_default_to_accept ? "accept" : "deny");

	ip_fw3_loaded = 1;
	if (sysctl_var_fw3_enable)
		ip_fw3_hook();
reply:
	lwkt_replymsg(&nmsg->lmsg, error);
}

int
ip_fw3_init(void)
{
	struct netmsg_base smsg;
	int error;

	init_module();
	netmsg_init(&smsg, NULL, &curthread->td_msgport,
			0, init_dispatch);
	error = lwkt_domsg(IPFW_CFGPORT, &smsg.lmsg, 0);
	return error;
}

#ifdef KLD_MODULE

void
fini_dispatch(netmsg_t nmsg)
{
	int error = 0, cpu;

	ip_fw3_loaded = 0;

	ip_fw3_dehook();
	netmsg_service_sync();
	ip_fw_chk_ptr = NULL;
	ip_fw_ctl_x_ptr = NULL;
	ip_fw_dn_io_ptr = NULL;
	ip_fw3_ctl_flush_rule(1);
	/* Free pre-cpu context */
	for (cpu = 0; cpu < ncpus; ++cpu) {
		if (fw3_ctx[cpu] != NULL) {
			kfree(fw3_ctx[cpu], M_IPFW3);
			fw3_ctx[cpu] = NULL;
		}
	}
	kprintf("ipfw3 unloaded\n");

	lwkt_replymsg(&nmsg->lmsg, error);
}

int
ip_fw3_fini(void)
{
	struct netmsg_base smsg;

	netmsg_init(&smsg, NULL, &curthread->td_msgport,
			0, fini_dispatch);
	return lwkt_domsg(IPFW_CFGPORT, &smsg.lmsg, 0);
}

#endif	/* KLD_MODULE */

static int
ip_fw3_modevent(module_t mod, int type, void *unused)
{
	int err = 0;

	switch (type) {
		case MOD_LOAD:
			err = ip_fw3_init();
			break;

		case MOD_UNLOAD:

#ifndef KLD_MODULE
			kprintf("ipfw3 statically compiled, cannot unload\n");
			err = EBUSY;
#else
			err = ip_fw3_fini();
#endif
			break;
		default:
			break;
	}
	return err;
}

static moduledata_t ipfw3mod = {
	"ipfw3",
	ip_fw3_modevent,
	0
};
/* ipfw3 must init before ipfw3_basic */
DECLARE_MODULE(ipfw3, ipfw3mod, SI_SUB_PROTO_END, SI_ORDER_FIRST);
MODULE_VERSION(ipfw3, 1);
