NPF improvements: trunk
authorrmind <rmind@NetBSD.org>
Sun, 01 Jul 2012 23:21:06 +0000
branchtrunk
changeset 211783 39099b801bde
parent 211782 c78cfda9319e
child 211784 6503e4249f0e
NPF improvements: - Add NPF_OPCODE_PROTO to match the address and/or protocol only. - Update parser to support arbitrary "pass proto <name/number>". - Fix IPv6 address and protocol handling (add a regression test). - Fix few theorethical races in session handling module. - Misc fixes, simplifications and some clean up.
lib/libnpf/npf.3
lib/libnpf/npf.c
lib/libnpf/npf.h
share/man/man9/npf_ncode.9
sys/net/npf/npf.h
sys/net/npf/npf_handler.c
sys/net/npf/npf_impl.h
sys/net/npf/npf_inet.c
sys/net/npf/npf_instr.c
sys/net/npf/npf_nat.c
sys/net/npf/npf_ncode.h
sys/net/npf/npf_processor.c
sys/net/npf/npf_ruleset.c
sys/net/npf/npf_session.c
sys/net/npf/npf_state.c
sys/net/npf/npf_tableset.c
usr.sbin/npf/npfctl/npf.conf.5
usr.sbin/npf/npfctl/npf_build.c
usr.sbin/npf/npfctl/npf_data.c
usr.sbin/npf/npfctl/npf_disassemble.c
usr.sbin/npf/npfctl/npf_ncgen.c
usr.sbin/npf/npfctl/npf_parse.y
usr.sbin/npf/npfctl/npf_scan.l
usr.sbin/npf/npfctl/npfctl.8
usr.sbin/npf/npfctl/npfctl.c
usr.sbin/npf/npfctl/npfctl.h
usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c
usr.sbin/npf/npftest/libnpftest/npf_processor_test.c
usr.sbin/npf/npftest/libnpftest/npf_state_test.c
usr.sbin/npf/npftest/libnpftest/npf_table_test.c
usr.sbin/npf/npftest/libnpftest/npf_test.h
--- a/lib/libnpf/npf.3	Sun Jul 01 22:04:44 2012 +0000
+++ b/lib/libnpf/npf.3	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-.\"	$NetBSD: npf.3,v 1.4 2012/01/15 00:49:47 rmind Exp $
+.\"	$NetBSD: npf.3,v 1.5 2012/07/01 23:21:07 rmind Exp $
 .\"
 .\" Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd January 14, 2012
+.Dd July 1, 2012
 .Dt NPF 3
 .Os
 .Sh NAME
@@ -136,7 +136,7 @@
 .It Dv NPF_RULE_FINAL
 Indicates that on rule match, further processing of the
 ruleset should be stopped and this rule applied instantly.
-.It Dv NPF_RULE_KEEPSTATE
+.It Dv NPF_RULE_STATEFUL
 Create a state (session) on match, track the connection and
 therefore pass the backwards stream without inspection.
 .It Dv NPF_RULE_RETRST
--- a/lib/libnpf/npf.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/lib/libnpf/npf.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.c,v 1.8 2012/04/01 19:16:24 rmind Exp $	*/
+/*	$NetBSD: npf.c,v 1.9 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2010-2012 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.8 2012/04/01 19:16:24 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.9 2012/07/01 23:21:07 rmind Exp $");
 
 #include <sys/types.h>
 #include <netinet/in_systm.h>
@@ -554,6 +554,29 @@
 	return 0;
 }
 
+int
+_npf_nat_foreach(nl_config_t *ncf, nl_rule_callback_t func)
+{
+
+	return _npf_rule_foreach1(ncf->ncf_nat_list, 0, func);
+}
+
+void
+_npf_nat_getinfo(nl_nat_t *nt, int *type, u_int *flags, npf_addr_t *addr,
+    size_t *alen, in_port_t *port)
+{
+	prop_dictionary_t rldict = nt->nrl_dict;
+
+	prop_dictionary_get_int32(rldict, "type", type);
+	prop_dictionary_get_uint32(rldict, "flags", flags);
+
+	prop_object_t obj = prop_dictionary_get(rldict, "translation-ip");
+	*alen = prop_data_size(obj);
+	memcpy(addr, prop_data_data_nocopy(obj), *alen);
+
+	prop_dictionary_get_uint16(rldict, "translation-port", port);
+}
+
 /*
  * TABLE INTERFACE.
  */
@@ -654,6 +677,24 @@
 	free(tl);
 }
 
+void
+_npf_table_foreach(nl_config_t *ncf, nl_table_callback_t func)
+{
+	prop_dictionary_t tldict;
+	prop_object_iterator_t it;
+
+	it = prop_array_iterator(ncf->ncf_table_list);
+	while ((tldict = prop_object_iterator_next(it)) != NULL) {
+		u_int id;
+		int type;
+
+		prop_dictionary_get_uint32(tldict, "id", &id);
+		prop_dictionary_get_int32(tldict, "type", &type);
+		(*func)(id, type);
+	}
+	prop_object_iterator_release(it);
+}
+
 /*
  * MISC.
  */
--- a/lib/libnpf/npf.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/lib/libnpf/npf.h	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.7 2012/04/01 19:16:24 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.8 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -64,6 +64,7 @@
 } nl_error_t;
 
 typedef void (*nl_rule_callback_t)(nl_rule_t *, unsigned);
+typedef void (*nl_table_callback_t)(unsigned, int);
 
 #endif
 
@@ -79,33 +80,18 @@
 void		npf_config_destroy(nl_config_t *);
 nl_config_t *	npf_config_retrieve(int, bool *, bool *);
 int		npf_config_flush(int);
-#ifdef _NPF_PRIVATE
-void		_npf_config_error(nl_config_t *, nl_error_t *);
-void		_npf_config_setsubmit(nl_config_t *, const char *);
-#endif
 
 nl_rule_t *	npf_rule_create(const char *, uint32_t, u_int);
 int		npf_rule_setcode(nl_rule_t *, int, const void *, size_t);
 int		npf_rule_setproc(nl_config_t *, nl_rule_t *, const char *);
 bool		npf_rule_exists_p(nl_config_t *, const char *);
 int		npf_rule_insert(nl_config_t *, nl_rule_t *, nl_rule_t *, pri_t);
-#ifdef _NPF_PRIVATE
-int		_npf_rule_foreach(nl_config_t *, nl_rule_callback_t);
-pri_t		_npf_rule_getinfo(nl_rule_t *, const char **, uint32_t *, u_int *);
-const void *	_npf_rule_ncode(nl_rule_t *, size_t *);
-const char *	_npf_rule_rproc(nl_rule_t *);
-#endif
 void		npf_rule_destroy(nl_rule_t *);
 
 nl_rproc_t *	npf_rproc_create(const char *);
 bool		npf_rproc_exists_p(nl_config_t *, const char *);
 int		npf_rproc_insert(nl_config_t *, nl_rproc_t *);
 
-#ifdef _NPF_PRIVATE
-int		_npf_rproc_setnorm(nl_rproc_t *, bool, bool, u_int, u_int);
-int		_npf_rproc_setlog(nl_rproc_t *, u_int);
-#endif
-
 nl_nat_t *	npf_nat_create(int, u_int, u_int, npf_addr_t *, int, in_port_t);
 int		npf_nat_insert(nl_config_t *, nl_nat_t *, pri_t);
 
@@ -119,6 +105,22 @@
 int		npf_sessions_send(int, const char *);
 int		npf_sessions_recv(int, const char *);
 
+#ifdef _NPF_PRIVATE
+void		_npf_config_error(nl_config_t *, nl_error_t *);
+void		_npf_config_setsubmit(nl_config_t *, const char *);
+int		_npf_rule_foreach(nl_config_t *, nl_rule_callback_t);
+pri_t		_npf_rule_getinfo(nl_rule_t *, const char **, uint32_t *,
+		    u_int *);
+const void *	_npf_rule_ncode(nl_rule_t *, size_t *);
+const char *	_npf_rule_rproc(nl_rule_t *);
+int		_npf_nat_foreach(nl_config_t *, nl_rule_callback_t);
+void		_npf_nat_getinfo(nl_nat_t *, int *, u_int *, npf_addr_t *,
+		    size_t *, in_port_t *);
+int		_npf_rproc_setnorm(nl_rproc_t *, bool, bool, u_int, u_int);
+int		_npf_rproc_setlog(nl_rproc_t *, u_int);
+void		_npf_table_foreach(nl_config_t *, nl_table_callback_t);
+#endif
+
 __END_DECLS
 
 #endif	/* _NPF_LIB_H_ */
--- a/share/man/man9/npf_ncode.9	Sun Jul 01 22:04:44 2012 +0000
+++ b/share/man/man9/npf_ncode.9	Sun Jul 01 23:21:06 2012 +0000
@@ -1,6 +1,6 @@
-.\"	$NetBSD: npf_ncode.9,v 1.8 2011/12/23 20:53:31 rmind Exp $
+.\"	$NetBSD: npf_ncode.9,v 1.9 2012/07/01 23:21:06 rmind Exp $
 .\"
-.\" Copyright (c) 2009-2011 The NetBSD Foundation, Inc.
+.\" Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
 .\" All rights reserved.
 .\"
 .\" This material is based upon work partially supported by The
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd December 23, 2011
+.Dd July 1, 2012
 .Dt NPF_NCODE 9
 .Os
 .Sh NAME
@@ -222,6 +222,14 @@
 the value passed in the argument.
 Return value to advance to layer 3 header in R3.
 .\" -
+.It Sy 0x81 NPF_OPCODE_PROTO <protocol>
+Match the IP address length and the protocol.
+The values for both are represented by lower 16 bits.
+The higher 8 bits represent IP address length.
+If zero is specified, the length is not matched.
+The lower 8 bits represent the protocol.
+If 0xff is specified, the protocol is not matched.
+.\" -
 .It Sy 0x90 NPF_OPCODE_IP4MASK <s/d>, <network address>, <subnet>
 Match passed network address with subnet against source or destination
 address in the IPv4 header.
--- a/sys/net/npf/npf.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf.h	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.17 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.18 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -148,7 +148,7 @@
 #define	NPF_RULE_PASS			0x0001
 #define	NPF_RULE_DEFAULT		0x0002
 #define	NPF_RULE_FINAL			0x0004
-#define	NPF_RULE_KEEPSTATE		0x0008
+#define	NPF_RULE_STATEFUL		0x0008
 #define	NPF_RULE_RETRST			0x0010
 #define	NPF_RULE_RETICMP		0x0020
 
--- a/sys/net/npf/npf_handler.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_handler.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_handler.c,v 1.17 2012/05/30 21:38:03 rmind Exp $	*/
+/*	$NetBSD: npf_handler.c,v 1.18 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_handler.c,v 1.17 2012/05/30 21:38:03 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_handler.c,v 1.18 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/param.h>
@@ -54,7 +54,7 @@
 #include "npf_impl.h"
 
 /*
- * If npf_ph_if != NULL, pfil hooks are registers.  If NULL, not registered.
+ * If npf_ph_if != NULL, pfil hooks are registered.  If NULL, not registered.
  * Used to check the state.  Locked by: softnet_lock + KERNEL_LOCK (XXX).
  */
 static struct pfil_head *	npf_ph_if = NULL;
@@ -100,24 +100,25 @@
 
 	/* Cache everything.  Determine whether it is an IP fragment. */
 	if (npf_cache_all(&npc, nbuf) & NPC_IPFRAG) {
-		int ret = -1;
+		/*
+		 * Pass to IPv4 or IPv6 reassembly mechanism.
+		 */
+		error = EINVAL;
 
-		/* Pass to IPv4 or IPv6 reassembly mechanism. */
 		if (npf_iscached(&npc, NPC_IP4)) {
 			struct ip *ip = nbuf_dataptr(*mp);
-			ret = ip_reass_packet(mp, ip);
+			error = ip_reass_packet(mp, ip);
 		} else if (npf_iscached(&npc, NPC_IP6)) {
 #ifdef INET6
 			/*
-			 * Note: frag6_input() offset is the start of the
-			 * fragment header.
+			 * Note: ip6_reass_packet() offset is the start of
+			 * the fragment header.
 			 */
 			const u_int hlen = npf_cache_hlen(&npc);
-			ret = ip6_reass_packet(mp, hlen);
+			error = ip6_reass_packet(mp, hlen);
 #endif
 		}
-		if (ret) {
-			error = EINVAL;
+		if (error) {
 			se = NULL;
 			goto out;
 		}
@@ -133,7 +134,7 @@
 		nbuf = (nbuf_t *)*mp;
 		npc.npc_info = 0;
 
-		ret = npf_cache_all(&npc, nbuf);
+		int ret = npf_cache_all(&npc, nbuf);
 		KASSERT((ret & NPC_IPFRAG) == 0);
 	}
 
@@ -188,7 +189,7 @@
 	 * Note: the reference on the rule procedure is transfered to the
 	 * session.  It will be released on session destruction.
 	 */
-	if ((retfl & NPF_RULE_KEEPSTATE) != 0 && !se) {
+	if ((retfl & NPF_RULE_STATEFUL) != 0 && !se) {
 		se = npf_session_establish(&npc, nbuf, di);
 		if (se) {
 			npf_session_setpass(se, rp);
--- a/sys/net/npf/npf_impl.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_impl.h	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_impl.h,v 1.16 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_impl.h,v 1.17 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -41,6 +41,12 @@
 #error "Kernel-level header only"
 #endif
 
+#ifdef _KERNEL_OPT
+/* For INET/INET6 definitions. */
+#include "opt_inet.h"
+#include "opt_inet6.h"
+#endif
+
 #include <sys/types.h>
 #include <sys/queue.h>
 #include <sys/hash.h>
@@ -178,9 +184,9 @@
 uint16_t	npf_addr_cksum(uint16_t, int, npf_addr_t *, npf_addr_t *);
 uint32_t	npf_addr_sum(const int, const npf_addr_t *, const npf_addr_t *);
 int		npf_addr_cmp(const npf_addr_t *, const npf_netmask_t,
-		    const npf_addr_t *, const npf_netmask_t);
+		    const npf_addr_t *, const npf_netmask_t, const int);
 void		npf_addr_mask(const npf_addr_t *, const npf_netmask_t,
-		    npf_addr_t *);
+		    const int, npf_addr_t *);
 
 int		npf_tcpsaw(const npf_cache_t *, tcp_seq *, tcp_seq *,
 		    uint32_t *);
@@ -191,6 +197,7 @@
 
 /* Complex instructions. */
 int		npf_match_ether(nbuf_t *, int, int, uint16_t, uint32_t *);
+int		npf_match_proto(npf_cache_t *, nbuf_t *, void *, uint32_t);
 int		npf_match_table(npf_cache_t *, nbuf_t *, void *,
 		    const int, const u_int);
 int		npf_match_ipmask(npf_cache_t *, nbuf_t *, void *,
@@ -313,9 +320,10 @@
 bool		npf_alg_sessionid(npf_cache_t *, nbuf_t *, npf_cache_t *);
 
 /* Debugging routines. */
-void		npf_rulenc_dump(npf_rule_t *);
+void		npf_addr_dump(const npf_addr_t *);
+void		npf_rulenc_dump(const npf_rule_t *);
 void		npf_sessions_dump(void);
-void		npf_state_dump(npf_state_t *);
-void		npf_nat_dump(npf_nat_t *);
+void		npf_state_dump(const npf_state_t *);
+void		npf_nat_dump(const npf_nat_t *);
 
 #endif	/* _NPF_IMPL_H_ */
--- a/sys/net/npf/npf_inet.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_inet.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_inet.c,v 1.12 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_inet.c,v 1.13 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -39,7 +39,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.12 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.13 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -126,75 +126,56 @@
 	return mix;
 }
 
-static inline void
-npf_generate_mask(npf_addr_t *out, const npf_netmask_t mask)
+/*
+ * npf_addr_mask: apply the mask to a given address and store the result.
+ */
+void
+npf_addr_mask(const npf_addr_t *addr, const npf_netmask_t mask,
+    const int alen, npf_addr_t *out)
 {
+	const int nwords = alen >> 2;
 	uint_fast8_t length = mask;
 
 	/* Note: maximum length is 32 for IPv4 and 128 for IPv6. */
 	KASSERT(length <= NPF_MAX_NETMASK);
 
-	for (int i = 0; i < 4; i++) {
-		if (length >= 32) {
-			out->s6_addr32[i] = htonl(0xffffffff);
-			length -= 32;
-		} else {
-			out->s6_addr32[i] = htonl(0xffffffff << (32 - length));
-			length = 0;
-		}
-	}
-}
+	for (int i = 0; i < nwords; i++) {
+		uint32_t wordmask;
 
-/*
- * npf_addr_mask: apply the mask to a given address and store the result.
- */
-void
-npf_addr_mask(const npf_addr_t *addr, const npf_netmask_t mask, npf_addr_t *out)
-{
-	npf_addr_t realmask;
-
-	npf_generate_mask(&realmask, mask);
-
-	for (int i = 0; i < 4; i++) {
-		out->s6_addr32[i] = addr->s6_addr32[i] & realmask.s6_addr32[i];
+		if (length >= 32) {
+			wordmask = htonl(0xffffffff);
+			length -= 32;
+		} else if (length) {
+			wordmask = htonl(0xffffffff << (32 - length));
+			length = 0;
+		} else {
+			wordmask = 0;
+		}
+		out->s6_addr32[i] = addr->s6_addr32[i] & wordmask;
 	}
 }
 
 /*
  * npf_addr_cmp: compare two addresses, either IPv4 or IPv6.
  *
+ * => Return 0 if equal and negative/positive if less/greater accordingly.
  * => Ignore the mask, if NPF_NO_NETMASK is specified.
- * => Return 0 if equal and -1 or 1 if less or greater accordingly.
  */
 int
 npf_addr_cmp(const npf_addr_t *addr1, const npf_netmask_t mask1,
-    const npf_addr_t *addr2, const npf_netmask_t mask2)
+    const npf_addr_t *addr2, const npf_netmask_t mask2, const int alen)
 {
-	npf_addr_t realmask1, realmask2;
+	npf_addr_t realaddr1, realaddr2;
 
 	if (mask1 != NPF_NO_NETMASK) {
-		npf_generate_mask(&realmask1, mask1);
+		npf_addr_mask(addr1, mask1, alen, &realaddr1);
+		addr1 = &realaddr1;
 	}
 	if (mask2 != NPF_NO_NETMASK) {
-		npf_generate_mask(&realmask2, mask2);
+		npf_addr_mask(addr2, mask2, alen, &realaddr2);
+		addr2 = &realaddr2;
 	}
-
-	for (int i = 0; i < 4; i++) {
-		const uint32_t x = mask1 != NPF_NO_NETMASK ?
-		    addr1->s6_addr32[i] & realmask1.s6_addr32[i] :
-		    addr1->s6_addr32[i];
-		const uint32_t y = mask2 != NPF_NO_NETMASK ?
-		    addr2->s6_addr32[i] & realmask2.s6_addr32[i] :
-		    addr2->s6_addr32[i];
-		if (x < y) {
-			return -1;
-		}
-		if (x > y) {
-			return 1;
-		}
-	}
-
-	return 0;
+	return memcmp(addr1, addr2, alen);
 }
 
 /*
@@ -352,51 +333,49 @@
 
 	case (IPV6_VERSION >> 4): {
 		struct ip6_hdr *ip6 = &npc->npc_ip.v6;
-		size_t toskip;
-		bool done;
+		size_t hlen = sizeof(struct ip6_hdr);
+		struct ip6_ext ip6e;
 
-		/* Fetch IPv6 header. */
-		if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(struct ip6_hdr), ip6)) {
+		/* Fetch IPv6 header and set initial next-protocol value. */
+		if (nbuf_fetch_datum(nbuf, n_ptr, hlen, ip6)) {
 			return false;
 		}
-
-		/* Initial next-protocol value. */
 		npc->npc_next_proto = ip6->ip6_nxt;
-		toskip = sizeof(struct ip6_hdr);
-		npc->npc_hlen = 0;
-		done = false;
+		npc->npc_hlen = hlen;
 
 		/*
-		 * Advance by the length of the previous known header and
-		 * fetch by the lengh of next extension header.
+		 * Advance by the length of the current header and
+		 * prefetch the extension header.
 		 */
-		do {
-			struct ip6_ext ip6e;
-
-			if (nbuf_advfetch(&nbuf, &n_ptr, toskip,
-			    sizeof(struct ip6_ext), &ip6e)) {
-				return false;
-			}
+		while (nbuf_advfetch(&nbuf, &n_ptr, hlen,
+		    sizeof(struct ip6_ext), &ip6e) == 0) {
+			/*
+			 * Determine whether we are going to continue.
+			 */
 			switch (npc->npc_next_proto) {
+			case IPPROTO_HOPOPTS:
 			case IPPROTO_DSTOPTS:
 			case IPPROTO_ROUTING:
-				toskip = (ip6e.ip6e_len + 1) << 3;
+				hlen = (ip6e.ip6e_len + 1) << 3;
 				break;
 			case IPPROTO_FRAGMENT:
 				npc->npc_info |= NPC_IPFRAG;
-				toskip = sizeof(struct ip6_frag);
+				hlen = sizeof(struct ip6_frag);
 				break;
 			case IPPROTO_AH:
-				toskip = (ip6e.ip6e_len + 2) << 2;
+				hlen = (ip6e.ip6e_len + 2) << 2;
 				break;
 			default:
-				done = true;
+				hlen = 0;
 				break;
 			}
-			npc->npc_hlen += toskip;
+
+			if (!hlen) {
+				break;
+			}
 			npc->npc_next_proto = ip6e.ip6e_nxt;
-
-		} while (!done);
+			npc->npc_hlen += hlen;
+		}
 
 		/* Cache: layer 3 - IPv6. */
 		npc->npc_ipsz = sizeof(struct in6_addr);
@@ -790,3 +769,15 @@
 	}
 	return true;
 }
+
+#if defined(DDB) || defined(_NPF_TESTING)
+
+void
+npf_addr_dump(const npf_addr_t *addr)
+{
+	printf("IP[%x:%x:%x:%x]\n",
+	    addr->s6_addr32[0], addr->s6_addr32[1],
+	    addr->s6_addr32[2], addr->s6_addr32[3]);
+}
+
+#endif
--- a/sys/net/npf/npf_instr.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_instr.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_instr.c,v 1.11 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_instr.c,v 1.12 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2009-2010 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_instr.c,v 1.11 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_instr.c,v 1.12 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -90,7 +90,29 @@
 }
 
 /*
- * npf_match_ip4table: match IPv4 address against NPF table.
+ * npf_match_proto: match IP address length and/or layer 4 protocol.
+ */
+int
+npf_match_proto(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, uint32_t ap)
+{
+	const int addrlen = (ap >> 8) & 0xff;
+	const int proto = ap & 0xff;
+
+	if (!npf_iscached(npc, NPC_IP46)) {
+		if (!npf_fetch_ip(npc, nbuf, n_ptr)) {
+			return -1;
+		}
+		KASSERT(npf_iscached(npc, NPC_IP46));
+	}
+
+	if (addrlen && npc->npc_ipsz != addrlen) {
+		return -1;
+	}
+	return (proto != 0xff && npf_cache_ipproto(npc) != proto) ? -1 : 0;
+}
+
+/*
+ * npf_match_table: match IP address against NPF table.
  */
 int
 npf_match_table(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr,
@@ -117,9 +139,10 @@
  */
 int
 npf_match_ipmask(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr,
-    const int sd, const npf_addr_t *netaddr, npf_netmask_t mask)
+    const int szsd, const npf_addr_t *maddr, npf_netmask_t mask)
 {
-	npf_addr_t *addr, cmpaddr;
+	const int alen = szsd >> 1;
+	npf_addr_t *addr;
 
 	if (!npf_iscached(npc, NPC_IP46)) {
 		if (!npf_fetch_ip(npc, nbuf, n_ptr)) {
@@ -127,12 +150,11 @@
 		}
 		KASSERT(npf_iscached(npc, NPC_IP46));
 	}
-	addr = sd ? npc->npc_srcip : npc->npc_dstip;
-	if (mask != NPF_NO_NETMASK) {
-		npf_addr_mask(addr, mask, &cmpaddr);
-		addr = &cmpaddr;
+	if (npc->npc_ipsz != alen) {
+		return -1;
 	}
-	return memcmp(netaddr, addr, npc->npc_ipsz) ? -1 : 0;
+	addr = (szsd & 0x1) ? npc->npc_srcip : npc->npc_dstip;
+	return npf_addr_cmp(maddr, NPF_NO_NETMASK, addr, mask, alen) ? -1 : 0;
 }
 
 /*
--- a/sys/net/npf/npf_nat.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_nat.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_nat.c,v 1.13 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_nat.c,v 1.14 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2010-2011 The NetBSD Foundation, Inc.
+ * Copyright (c) 2010-2012 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -76,7 +76,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.13 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.14 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -510,18 +510,10 @@
 
 	if (forw) {
 		/* "Forwards" stream: use translation address/port. */
-		KASSERT(
-		    (np->n_type == NPF_NATIN && di == PFIL_IN) ^
-		    (np->n_type == NPF_NATOUT && di == PFIL_OUT)
-		);
 		addr = &np->n_taddr;
 		port = nt->nt_tport;
 	} else {
 		/* "Backwards" stream: use original address/port. */
-		KASSERT(
-		    (np->n_type == NPF_NATIN && di == PFIL_OUT) ^
-		    (np->n_type == NPF_NATOUT && di == PFIL_IN)
-		);
 		addr = &nt->nt_oaddr;
 		port = nt->nt_oport;
 	}
@@ -635,7 +627,7 @@
 	}
 
 	/*
-	 * If there is no local session (no "keep state" rule - unusual, but
+	 * If there is no local session (no "stateful" rule - unusual, but
 	 * possible configuration), establish one before translation.  Note
 	 * that it is not a "pass" session, therefore passing of "backwards"
 	 * stream depends on other, stateless filtering rules.
@@ -671,7 +663,7 @@
 			/* Will free the structure and return the port. */
 			npf_nat_expire(nt);
 		}
-		if (nse != NULL) {
+		if (nse) {
 			npf_session_release(nse);
 		}
 	}
@@ -837,9 +829,9 @@
 #if defined(DDB) || defined(_NPF_TESTING)
 
 void
-npf_nat_dump(npf_nat_t *nt)
+npf_nat_dump(const npf_nat_t *nt)
 {
-	npf_natpolicy_t *np;
+	const npf_natpolicy_t *np;
 	struct in_addr ip;
 
 	np = nt->nt_natpolicy;
--- a/sys/net/npf/npf_ncode.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_ncode.h	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_ncode.h,v 1.8 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_ncode.h,v 1.9 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2010 The NetBSD Foundation, Inc.
@@ -112,6 +112,7 @@
  */
 
 #define	NPF_OPCODE_ETHER		0x80
+#define	NPF_OPCODE_PROTO		0x81
 
 #define	NPF_OPCODE_IP4MASK		0x90
 #define	NPF_OPCODE_TABLE		0x91
@@ -141,6 +142,7 @@
 # define	NPF_OPERAND_ICMP4_TYPE_CODE	12
 # define	NPF_OPERAND_TCP_FLAGS_MASK	13
 # define	NPF_OPERAND_PORT_RANGE		14
+# define	NPF_OPERAND_PROTO		15
 
 static const struct npf_instruction {
 	const char *	name;
@@ -177,7 +179,7 @@
 	[NPF_OPCODE_MOVE] = {
 		.name = "move",
 		.op = {
-			[0] = NPF_OPERAND_VALUE, 
+			[0] = NPF_OPERAND_VALUE,
 			[1] = NPF_OPERAND_REGISTER,
 		},
 	},
@@ -304,6 +306,12 @@
 			[2] = NPF_OPERAND_ETHER_TYPE,
 		},
 	},
+	[NPF_OPCODE_PROTO] = {
+		.name = "proto",
+		.op = {
+			[0] = NPF_OPERAND_PROTO,
+		},
+	},
 	[NPF_OPCODE_IP4MASK] = {
 		.name = "ip4mask",
 		.op = {
--- a/sys/net/npf/npf_processor.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_processor.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_processor.c,v 1.10 2012/02/20 00:18:20 rmind Exp $	*/
+/*	$NetBSD: npf_processor.c,v 1.11 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2010 The NetBSD Foundation, Inc.
@@ -50,7 +50,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_processor.c,v 1.10 2012/02/20 00:18:20 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_processor.c,v 1.11 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -271,18 +271,13 @@
 	 * CISC-like instructions.
 	 */
 	switch (d) {
-	case NPF_OPCODE_ETHER:
-		/* Source/destination, reserved, ethernet type. */
-		i_ptr = nc_fetch_word(i_ptr, &d);
-		i_ptr = nc_fetch_double(i_ptr, &n, &i);
-		cmpval = npf_match_ether(nbuf, d, n, i, &regs[NPF_NREGS - 1]);
-		break;
 	case NPF_OPCODE_IP4MASK:
 		/* Source/destination, network address, subnet. */
 		i_ptr = nc_fetch_word(i_ptr, &d);
 		i_ptr = nc_fetch_double(i_ptr, &addr.s6_addr32[0], &n);
-		cmpval = npf_match_ipmask(npc, nbuf, n_ptr, d, &addr,
-		    (npf_netmask_t)n);
+		cmpval = npf_match_ipmask(npc, nbuf, n_ptr,
+		    (sizeof(struct in_addr) << 1) | (d & 0x1),
+		    &addr, (npf_netmask_t)n);
 		break;
 	case NPF_OPCODE_IP6MASK:
 		/* Source/destination, network address, subnet. */
@@ -292,7 +287,8 @@
 		i_ptr = nc_fetch_double(i_ptr,
 		    &addr.s6_addr32[2], &addr.s6_addr32[3]);
 		i_ptr = nc_fetch_word(i_ptr, &n);
-		cmpval = npf_match_ipmask(npc, nbuf, n_ptr, d,
+		cmpval = npf_match_ipmask(npc, nbuf, n_ptr,
+		    (sizeof(struct in6_addr) << 1) | (d & 0x1),
 		    &addr, (npf_netmask_t)n);
 		break;
 	case NPF_OPCODE_TABLE:
@@ -320,6 +316,16 @@
 		i_ptr = nc_fetch_word(i_ptr, &n);
 		cmpval = npf_match_icmp4(npc, nbuf, n_ptr, n);
 		break;
+	case NPF_OPCODE_PROTO:
+		i_ptr = nc_fetch_word(i_ptr, &n);
+		cmpval = npf_match_proto(npc, nbuf, n_ptr, n);
+		break;
+	case NPF_OPCODE_ETHER:
+		/* Source/destination, reserved, ethernet type. */
+		i_ptr = nc_fetch_word(i_ptr, &d);
+		i_ptr = nc_fetch_double(i_ptr, &n, &i);
+		cmpval = npf_match_ether(nbuf, d, n, i, &regs[NPF_NREGS - 1]);
+		break;
 	default:
 		/* Invalid instruction. */
 		KASSERT(false);
@@ -443,9 +449,6 @@
 	/*
 	 * CISC-like instructions.
 	 */
-	case NPF_OPCODE_ETHER:
-		error = nc_ptr_check(&iptr, nc, sz, 3, NULL, 0);
-		break;
 	case NPF_OPCODE_IP4MASK:
 		error = nc_ptr_check(&iptr, nc, sz, 3, &val, 3);
 		if (error) {
@@ -479,6 +482,12 @@
 	case NPF_OPCODE_ICMP4:
 		error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0);
 		break;
+	case NPF_OPCODE_PROTO:
+		error = nc_ptr_check(&iptr, nc, sz, 1, NULL, 0);
+		break;
+	case NPF_OPCODE_ETHER:
+		error = nc_ptr_check(&iptr, nc, sz, 3, NULL, 0);
+		break;
 	default:
 		/* Invalid instruction. */
 		return NPF_ERR_OPCODE;
--- a/sys/net/npf/npf_ruleset.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_ruleset.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_ruleset.c,v 1.11 2012/02/20 00:18:20 rmind Exp $	*/
+/*	$NetBSD: npf_ruleset.c,v 1.12 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.11 2012/02/20 00:18:20 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.12 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -49,7 +49,7 @@
 #include "npf_ncode.h"
 #include "npf_impl.h"
 
-/* Ruleset structre (queue and default rule). */
+/* Ruleset structure (queue and default rule). */
 struct npf_ruleset {
 	TAILQ_HEAD(, npf_rule)	rs_queue;
 	npf_rule_t *		rs_default;
@@ -407,9 +407,9 @@
 #if defined(DDB) || defined(_NPF_TESTING)
 
 void
-npf_rulenc_dump(npf_rule_t *rl)
+npf_rulenc_dump(const npf_rule_t *rl)
 {
-	uint32_t *op = rl->r_ncode;
+	const uint32_t *op = rl->r_ncode;
 	size_t n = rl->r_nc_size;
 
 	while (n) {
--- a/sys/net/npf/npf_session.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_session.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_session.c,v 1.13 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_session.c,v 1.14 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2010-2012 The NetBSD Foundation, Inc.
@@ -44,7 +44,7 @@
  *	Note that entry may contain translated values in a case of NAT.
  *
  *	Sessions can serve two purposes: "pass" or "NAT".  Sessions for the
- *	former purpose are created according to the rules with "keep state"
+ *	former purpose are created according to the rules with "stateful"
  *	attribute and are used for stateful filtering.  Such sessions
  *	indicate that the packet of the backwards stream should be passed
  *	without inspection of the ruleset.  Another purpose is to associate
@@ -71,10 +71,16 @@
  *	the packet cache (npf_cache_t) representing the IDs.  It is done
  *	via npf_alg_sessionid() call.  In such case, ALGs are responsible
  *	for correct filling of protocol, addresses and ports/IDs.
+ *
+ * Lock order
+ *
+ *	sess_lock ->
+ *		npf_sehash_t::sh_lock ->
+ *			npf_state_t::nst_lock
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_session.c,v 1.13 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_session.c,v 1.14 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -149,15 +155,21 @@
 /*
  * Session flags:
  * - PFIL_IN and PFIL_OUT values are reserved for direction.
- * - SE_PASSING: a "pass" session.
+ * - SE_ACTIVE: session is active i.e. visible on inspection.
+ * - SE_PASS: a "pass" session.
  * - SE_EXPIRE: explicitly expire the session.
  * - SE_REMOVING: session is being removed (indicate need to enter G/C list).
  */
 CTASSERT(PFIL_ALL == (0x001 | 0x002));
-#define	SE_PASSSING		0x004
-#define	SE_EXPIRE		0x008
-#define	SE_REMOVING		0x010
+#define	SE_ACTIVE		0x004
+#define	SE_PASS			0x008
+#define	SE_EXPIRE		0x010
+#define	SE_REMOVING		0x020
 
+/*
+ * Session tracking state: disabled (off), enabled (on) or flush request.
+ */
+enum { SESS_TRACKING_OFF, SESS_TRACKING_ON, SESS_TRACKING_FLUSH };
 static int			sess_tracking	__cacheline_aligned;
 
 /* Session hash table, lock and session cache. */
@@ -185,10 +197,14 @@
 npf_session_sysinit(void)
 {
 
+	sess_cache = pool_cache_init(sizeof(npf_session_t), coherency_unit,
+	    0, 0, "npfsespl", NULL, IPL_NET, NULL, NULL, NULL);
 	mutex_init(&sess_lock, MUTEX_DEFAULT, IPL_NONE);
 	cv_init(&sess_cv, "npfgccv");
+	sess_hashtbl = NULL;
 	sess_gc_lwp = NULL;
-	sess_tracking = 0;
+
+	sess_tracking = SESS_TRACKING_OFF;
 }
 
 void
@@ -197,9 +213,15 @@
 
 	/* Disable tracking, flush all sessions. */
 	npf_session_tracking(false);
-	KASSERT(sess_tracking == 0);
+	KASSERT(sess_tracking == SESS_TRACKING_OFF);
 	KASSERT(sess_gc_lwp == NULL);
 
+	/* Sessions might have been restored while the tracking is off. */
+	if (sess_hashtbl) {
+		sess_htable_destroy(sess_hashtbl);
+	}
+
+	pool_cache_destroy(sess_cache);
 	cv_destroy(&sess_cv);
 	mutex_destroy(&sess_lock);
 }
@@ -311,18 +333,25 @@
 {
 	npf_sehash_t *oldstbl;
 
+	/* Flush all existing entries. */
 	mutex_enter(&sess_lock);
-	/* Flush all existing entries. */
-	sess_tracking = -1;	/* XXX */
-	cv_signal(&sess_cv);
-	cv_wait(&sess_cv, &sess_lock);
-	sess_tracking = 1;
+	if (sess_gc_lwp) {
+		sess_tracking = SESS_TRACKING_FLUSH;
+		cv_broadcast(&sess_cv);
+	}
+	while (sess_tracking == SESS_TRACKING_FLUSH) {
+		cv_wait(&sess_cv, &sess_lock);
+	}
+
 	/* Set a new session table. */
 	oldstbl = sess_hashtbl;
 	sess_hashtbl = stbl;
 	mutex_exit(&sess_lock);
+
 	/* Destroy the old table. */
-	sess_htable_destroy(oldstbl);
+	if (oldstbl) {
+		sess_htable_destroy(oldstbl);
+	}
 }
 
 /*
@@ -332,20 +361,23 @@
 static int
 sess_tracking_start(void)
 {
+	npf_sehash_t *nstbl;
 
-	sess_cache = pool_cache_init(sizeof(npf_session_t), coherency_unit,
-	    0, 0, "npfsespl", NULL, IPL_NET, NULL, NULL, NULL);
-	if (sess_cache == NULL)
-		return ENOMEM;
-
-	sess_hashtbl = sess_htable_create();
-	if (sess_hashtbl == NULL) {
-		pool_cache_destroy(sess_cache);
+	nstbl = sess_htable_create();
+	if (nstbl == NULL) {
 		return ENOMEM;
 	}
 
-	/* Make it visible before thread start. */
-	sess_tracking = 1;
+	/* Note: should be visible before thread start. */
+	mutex_enter(&sess_lock);
+	if (sess_tracking != SESS_TRACKING_OFF) {
+		mutex_exit(&sess_lock);
+		sess_htable_destroy(nstbl);
+		return EEXIST;
+	}
+	sess_hashtbl = nstbl;
+	sess_tracking = SESS_TRACKING_ON;
+	mutex_exit(&sess_lock);
 
 	if (kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
 	    npf_session_worker, NULL, &sess_gc_lwp, "npfgc")) {
@@ -359,33 +391,38 @@
 sess_tracking_stop(void)
 {
 
-	/* Notify G/C thread to flush all sessions, wait for the exit. */
 	mutex_enter(&sess_lock);
-	sess_tracking = 0;
-	cv_signal(&sess_cv);
+	if (sess_tracking == SESS_TRACKING_OFF) {
+		mutex_exit(&sess_lock);
+		return;
+	}
+
+	/* Notify G/C thread to flush all sessions. */
+	sess_tracking = SESS_TRACKING_OFF;
+	cv_broadcast(&sess_cv);
+
+	/* Wait for the exit. */
 	while (sess_gc_lwp != NULL) {
 		cv_wait(&sess_cv, &sess_lock);
 	}
 	mutex_exit(&sess_lock);
 
 	sess_htable_destroy(sess_hashtbl);
-	pool_cache_destroy(sess_cache);
+	pool_cache_invalidate(sess_cache);
 }
 
 /*
  * npf_session_tracking: enable/disable session tracking.
- *
- * => XXX: serialize at upper layer; ignore for now.
  */
 int
 npf_session_tracking(bool track)
 {
 
-	if (!sess_tracking && track) {
+	if (sess_tracking == SESS_TRACKING_OFF && track) {
 		/* Disabled -> Enable. */
 		return sess_tracking_start();
 	}
-	if (sess_tracking && !track) {
+	if (sess_tracking == SESS_TRACKING_ON && !track) {
 		/* Enabled -> Disable. */
 		sess_tracking_stop();
 		return 0;
@@ -404,13 +441,16 @@
 	npf_sehash_t *sh;
 	npf_sentry_t *sen;
 	npf_session_t *se;
+	int flags;
 
 	/*
-	 * If layer 3 and 4 are not cached - protocol is not supported
-	 * or packet is invalid.
+	 * Check if session tracking is on.  Also, if layer 3 and 4 are not
+	 * cached - protocol is not supported or packet is invalid.
 	 */
-	if (!sess_tracking || !npf_iscached(npc, NPC_IP46) ||
-	    !npf_iscached(npc, NPC_LAYER4)) {
+	if (sess_tracking == SESS_TRACKING_OFF) {
+		return NULL;
+	}
+	if (!npf_iscached(npc, NPC_IP46) || !npf_iscached(npc, NPC_LAYER4)) {
 		return NULL;
 	}
 
@@ -467,17 +507,24 @@
 		return NULL;
 	}
 	se = sen->se_backptr;
+	flags = se->s_flags;
 
-	/* Match direction and check if not explicitly expired. */
-	const bool forw = (sen == &se->s_forw_entry);
-	const int se_di = se->s_flags & PFIL_ALL;
-	if (forw != (se_di == di) || (se->s_flags & SE_EXPIRE) != 0) {
+	/* Check if session is active and not expired. */
+	if (__predict_false((flags & (SE_ACTIVE | SE_EXPIRE)) != SE_ACTIVE)) {
+		rw_exit(&sh->sh_lock);
+		return NULL;
+	}
+
+	/* Match session entry and packet directions. */
+	const bool sforw = (sen == &se->s_forw_entry);
+	const bool pforw = (flags & PFIL_ALL) == di;
+	if (__predict_false(sforw != pforw)) {
 		rw_exit(&sh->sh_lock);
 		return NULL;
 	}
 
 	/* Inspect the protocol data and handle state changes. */
-	if (npf_state_inspect(npc, nbuf, &se->s_state, forw)) {
+	if (npf_state_inspect(npc, nbuf, &se->s_state, sforw)) {
 		/* Update the last activity time and hold a reference. */
 		getnanouptime(&se->s_atime);
 		atomic_inc_uint(&se->s_refcnt);
@@ -495,6 +542,7 @@
  * npf_establish_session: create a new session, insert into the global list.
  *
  * => Session is created with the reference held for the caller.
+ * => Session will be activated on the first reference release.
  */
 npf_session_t *
 npf_session_establish(const npf_cache_t *npc, nbuf_t *nbuf, const int di)
@@ -508,11 +556,13 @@
 	bool ok;
 
 	/*
-	 * If layer 3 and 4 are not cached - protocol is not supported
-	 * or packet is invalid.
+	 * Check if session tracking is on.  Also, if layer 3 and 4 are not
+	 * cached - protocol is not supported or packet is invalid.
 	 */
-	if (!sess_tracking || !npf_iscached(npc, NPC_IP46) ||
-	    !npf_iscached(npc, NPC_LAYER4)) {
+	if (sess_tracking == SESS_TRACKING_OFF) {
+		return NULL;
+	}
+	if (!npf_iscached(npc, NPC_IP46) || !npf_iscached(npc, NPC_LAYER4)) {
 		return NULL;
 	}
 
@@ -723,7 +773,7 @@
 {
 
 	/* KASSERT(se->s_refcnt > 0); XXX: npf_nat_freepolicy() */
-	se->s_flags |= SE_EXPIRE;		/* XXXSMP */
+	atomic_or_uint(&se->s_flags, SE_EXPIRE);
 }
 
 /*
@@ -734,7 +784,7 @@
 {
 
 	KASSERT(se->s_refcnt > 0);
-	if ((se->s_flags & SE_PASSSING) != 0) {
+	if ((se->s_flags & SE_PASS) != 0) {
 		*rp = se->s_rproc;
 		return true;
 	}
@@ -749,9 +799,12 @@
 npf_session_setpass(npf_session_t *se, npf_rproc_t *rp)
 {
 
+	KASSERT((se->s_flags & SE_ACTIVE) == 0);
 	KASSERT(se->s_refcnt > 0);
 	KASSERT(se->s_rproc == NULL);
-	se->s_flags |= SE_PASSSING;		/* XXXSMP */
+
+	/* No need for atomic since the session is not yet active. */
+	se->s_flags |= SE_PASS;
 	se->s_rproc = rp;
 }
 
@@ -764,6 +817,10 @@
 {
 
 	KASSERT(se->s_refcnt > 0);
+	if ((se->s_flags & SE_ACTIVE) == 0) {
+		/* Activate: after this point, session is globally visible. */
+		se->s_flags |= SE_ACTIVE;
+	}
 	atomic_dec_uint(&se->s_refcnt);
 }
 
@@ -836,18 +893,15 @@
 			sh->sh_count--;
 
 			/*
-			 * Remove session, if forwards entry.  Set removal bit
-			 * when first entry is removed.  If it is already set,
-			 * then it is a second entry removal, therefore move
-			 * the session into the G/C list.
+			 * Set removal bit when the first entry is removed.
+			 * If already set, then second entry has been removed,
+			 * therefore move the session into the G/C list.
 			 */
-			if (sen == &se->s_forw_entry) {
+			if (se->s_flags & SE_REMOVING) {
 				LIST_REMOVE(se, s_list);
-			}
-			if (se->s_flags & SE_REMOVING) {
 				LIST_INSERT_HEAD(gc_list, se, s_list);
 			} else {
-				se->s_flags |= SE_REMOVING;
+				atomic_or_uint(&se->s_flags, SE_REMOVING);
 			}
 
 			/* Next.. */
@@ -892,18 +946,19 @@
 	do {
 		/* Periodically wake up, unless get notified. */
 		mutex_enter(&sess_lock);
-		if (flushreq) {
-			/* Flush was performed, notify waiter. */
-			cv_signal(&sess_cv);
+		(void)cv_timedwait(&sess_cv, &sess_lock, SESS_GC_INTERVAL);
+		flushreq = (sess_tracking != SESS_TRACKING_ON);
+		npf_session_gc(&gc_list, flushreq);
+		if (sess_tracking == SESS_TRACKING_FLUSH) {
+			/* Flush was requested - on again, notify waiter. */
+			sess_tracking = SESS_TRACKING_ON;
+			cv_broadcast(&sess_cv);
 		}
-		(void)cv_timedwait(&sess_cv, &sess_lock, SESS_GC_INTERVAL);
-		flushreq = (sess_tracking != 1);	/* XXX */
-		npf_session_gc(&gc_list, flushreq);
 		mutex_exit(&sess_lock);
 
 		npf_session_freelist(&gc_list);
 
-	} while (sess_tracking);
+	} while (sess_tracking != SESS_TRACKING_OFF);
 
 	/* Wait for any referenced sessions to be released. */
 	while (!LIST_EMPTY(&gc_list)) {
@@ -914,7 +969,7 @@
 	/* Notify that we are done. */
 	mutex_enter(&sess_lock);
 	sess_gc_lwp = NULL;
-	cv_signal(&sess_cv);
+	cv_broadcast(&sess_cv);
 	mutex_exit(&sess_lock);
 
 	kthread_exit(0);
@@ -932,7 +987,9 @@
 	int error = 0, i;
 
 	/* If not tracking - empty. */
-	if (!sess_tracking) {
+	mutex_enter(&sess_lock);
+	if (sess_tracking == SESS_TRACKING_OFF) {
+		mutex_exit(&sess_lock);
 		return 0;
 	}
 
@@ -941,7 +998,6 @@
 	 * expiring and removing.  Therefore, no need to exclusively lock
 	 * the entire hash table.
 	 */
-	mutex_enter(&sess_lock);
 	for (i = 0; i < SESS_HASH_BUCKETS; i++) {
 		sh = &sess_hashtbl[i];
 		if (sh->sh_count == 0) {
--- a/sys/net/npf/npf_state.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_state.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_state.c,v 1.8 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_state.c,v 1.9 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2010-2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_state.c,v 1.8 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_state.c,v 1.9 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -179,10 +179,11 @@
 }
 
 void
-npf_state_dump(npf_state_t *nst)
+npf_state_dump(const npf_state_t *nst)
 {
 #if defined(DDB) || defined(_NPF_TESTING)
-	npf_tcpstate_t *fst = &nst->nst_tcpst[0], *tst = &nst->nst_tcpst[1];
+	const npf_tcpstate_t *fst = &nst->nst_tcpst[0];
+	const npf_tcpstate_t *tst = &nst->nst_tcpst[1];
 
 	printf("\tstate (%p) %d:\n\t\t"
 	    "F { end %u maxend %u mwin %u wscale %u }\n\t\t"
--- a/sys/net/npf/npf_tableset.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/sys/net/npf/npf_tableset.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_tableset.c,v 1.11 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_tableset.c,v 1.12 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -33,13 +33,13 @@
  * NPF tableset module.
  *
  * TODO:
- * - Currently, code is modeled to handle IPv4 CIDR blocks.
+ * - Convert to Patricia tree.
  * - Dynamic hash growing/shrinking (i.e. re-hash functionality), maybe?
  * - Dynamic array resize.
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_tableset.c,v 1.11 2012/06/22 13:43:17 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_tableset.c,v 1.12 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -62,7 +62,7 @@
 		LIST_ENTRY(npf_tblent)	hashq;
 		rb_node_t		rbnode;
 	} te_entry;
-	/* IPv4 CIDR block. */
+	/* CIDR block. */
 	npf_addr_t			te_addr;
 	npf_netmask_t			te_mask;
 };
@@ -165,7 +165,7 @@
 	const npf_tblent_t * const te2 = n2;
 
 	return npf_addr_cmp(&te1->te_addr, te1->te_mask,
-	    &te2->te_addr, te2->te_mask);
+	    &te2->te_addr, te2->te_mask, sizeof(npf_addr_t));
 }
 
 static signed int
@@ -174,7 +174,8 @@
 	const npf_tblent_t * const te = n1;
 	const npf_addr_t *t2 = key;
 
-	return npf_addr_cmp(&te->te_addr, te->te_mask, t2, NPF_NO_NETMASK);
+	return npf_addr_cmp(&te->te_addr, te->te_mask,
+	    t2, NPF_NO_NETMASK, sizeof(npf_addr_t));
 }
 
 static const rb_tree_ops_t table_rbtree_ops = {
@@ -338,14 +339,14 @@
 }
 
 /*
- * npf_table_add_cidr: add an IPv4 or IPv6 CIDR into the table.
+ * npf_table_add_cidr: add an IP CIDR into the table.
  */
 int
 npf_table_add_cidr(npf_tableset_t *tset, u_int tid,
     const npf_addr_t *addr, const npf_netmask_t mask)
 {
 	struct npf_hashl *htbl;
-	npf_tblent_t *e, *it;
+	npf_tblent_t *ent, *it;
 	npf_table_t *t;
 	npf_addr_t val;
 	int error = 0;
@@ -353,21 +354,23 @@
 	if (mask > NPF_MAX_NETMASK) {
 		return EINVAL;
 	}
-	e = pool_cache_get(tblent_cache, PR_WAITOK);
-	memcpy(&e->te_addr, addr, sizeof(npf_addr_t));
-	e->te_mask = mask;
+	ent = pool_cache_get(tblent_cache, PR_WAITOK);
+	memcpy(&ent->te_addr, addr, sizeof(npf_addr_t));
+	ent->te_mask = mask;
 
 	/* Get the table (acquire the lock). */
 	t = npf_table_get(tset, tid);
 	if (t == NULL) {
-		pool_cache_put(tblent_cache, e);
+		pool_cache_put(tblent_cache, ent);
 		return EINVAL;
 	}
+
 	switch (t->t_type) {
 	case NPF_TABLE_HASH:
 		/* Generate hash value from: address & mask. */
-		npf_addr_mask(addr, mask, &val);
+		npf_addr_mask(addr, mask, sizeof(npf_addr_t), &val);
 		htbl = table_hash_bucket(t, &val, sizeof(npf_addr_t));
+
 		/* Lookup to check for duplicates. */
 		LIST_FOREACH(it, htbl, te_entry.hashq) {
 			if (it->te_mask != mask) {
@@ -377,16 +380,17 @@
 				break;
 			}
 		}
+
 		/* If no duplicate - insert entry. */
 		if (__predict_true(it == NULL)) {
-			LIST_INSERT_HEAD(htbl, e, te_entry.hashq);
+			LIST_INSERT_HEAD(htbl, ent, te_entry.hashq);
 		} else {
 			error = EEXIST;
 		}
 		break;
 	case NPF_TABLE_TREE:
 		/* Insert entry.  Returns false, if duplicate. */
-		if (rb_tree_insert_node(&t->t_rbtree, e) != e) {
+		if (rb_tree_insert_node(&t->t_rbtree, ent) != ent) {
 			error = EEXIST;
 		}
 		break;
@@ -396,23 +400,22 @@
 	npf_table_put(t);
 
 	if (error) {
-		pool_cache_put(tblent_cache, e);
+		pool_cache_put(tblent_cache, ent);
 	}
 	return error;
 }
 
 /*
- * npf_table_rem_v4cidr: remove an IPv4 CIDR from the table.
+ * npf_table_rem_cidr: remove an IP CIDR from the table.
  */
 int
 npf_table_rem_cidr(npf_tableset_t *tset, u_int tid,
     const npf_addr_t *addr, const npf_netmask_t mask)
 {
 	struct npf_hashl *htbl;
-	npf_tblent_t *e;
+	npf_tblent_t *ent;
 	npf_table_t *t;
 	npf_addr_t val;
-	int error;
 
 	if (mask > NPF_MAX_NETMASK) {
 		return EINVAL;
@@ -423,35 +426,31 @@
 	if (__predict_false(t == NULL)) {
 		return EINVAL;
 	}
-	e = NULL;
+
+	/* Key: (address & mask). */
+	npf_addr_mask(addr, mask, sizeof(npf_addr_t), &val);
+	ent = NULL;
 
 	switch (t->t_type) {
 	case NPF_TABLE_HASH:
 		/* Generate hash value from: (address & mask). */
-		npf_addr_mask(addr, mask, &val);
 		htbl = table_hash_bucket(t, &val, sizeof(npf_addr_t));
-		LIST_FOREACH(e, htbl, te_entry.hashq) {
-			if (e->te_mask != mask) {
+		LIST_FOREACH(ent, htbl, te_entry.hashq) {
+			if (ent->te_mask != mask) {
 				continue;
 			}
-			if (!memcmp(&e->te_addr, addr, sizeof(npf_addr_t))) {
+			if (!memcmp(&ent->te_addr, addr, sizeof(npf_addr_t))) {
 				break;
 			}
 		}
-		if (__predict_true(e != NULL)) {
-			LIST_REMOVE(e, te_entry.hashq);
-		} else {
-			error = ESRCH;
+		if (__predict_true(ent != NULL)) {
+			LIST_REMOVE(ent, te_entry.hashq);
 		}
 		break;
 	case NPF_TABLE_TREE:
-		/* Key: (address & mask). */
-		npf_addr_mask(addr, mask, &val);
-		e = rb_tree_find_node(&t->t_rbtree, &val);
-		if (__predict_true(e != NULL)) {
-			rb_tree_remove_node(&t->t_rbtree, e);
-		} else {
-			error = ESRCH;
+		ent = rb_tree_find_node(&t->t_rbtree, &val);
+		if (__predict_true(ent != NULL)) {
+			rb_tree_remove_node(&t->t_rbtree, ent);
 		}
 		break;
 	default:
@@ -459,10 +458,10 @@
 	}
 	npf_table_put(t);
 
-	if (e == NULL) {
+	if (ent == NULL) {
 		return ENOENT;
 	}
-	pool_cache_put(tblent_cache, e);
+	pool_cache_put(tblent_cache, ent);
 	return 0;
 }
 
@@ -474,7 +473,7 @@
 npf_table_match_addr(npf_tableset_t *tset, u_int tid, const npf_addr_t *addr)
 {
 	struct npf_hashl *htbl;
-	npf_tblent_t *e = NULL;
+	npf_tblent_t *ent = NULL;
 	npf_table_t *t;
 
 	/* Get the table (acquire the lock). */
@@ -485,24 +484,25 @@
 	switch (t->t_type) {
 	case NPF_TABLE_HASH:
 		htbl = table_hash_bucket(t, addr, sizeof(npf_addr_t));
-		LIST_FOREACH(e, htbl, te_entry.hashq) {
-			if (npf_addr_cmp(addr, e->te_mask, &e->te_addr,
-			    NPF_NO_NETMASK) == 0)
+		LIST_FOREACH(ent, htbl, te_entry.hashq) {
+			if (npf_addr_cmp(addr, ent->te_mask, &ent->te_addr,
+			    NPF_NO_NETMASK, sizeof(npf_addr_t)) == 0) {
 				break;
+			}
 		}
 		break;
 	case NPF_TABLE_TREE:
-		e = rb_tree_find_node(&t->t_rbtree, addr);
+		ent = rb_tree_find_node(&t->t_rbtree, addr);
 		break;
 	default:
 		KASSERT(false);
 	}
 	npf_table_put(t);
 
-	if (e == NULL) {
+	if (ent == NULL) {
 		return ENOENT;
 	}
-	KASSERT(npf_addr_cmp(addr, e->te_mask, &e->te_addr,
-	    NPF_NO_NETMASK) == 0);
+	KASSERT(npf_addr_cmp(addr, ent->te_mask, &ent->te_addr,
+	    NPF_NO_NETMASK, sizeof(npf_addr_t)) == 0);
 	return 0;
 }
--- a/usr.sbin/npf/npfctl/npf.conf.5	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf.conf.5	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-.\"    $NetBSD: npf.conf.5,v 1.13 2012/06/27 23:05:28 rmind Exp $
+.\"    $NetBSD: npf.conf.5,v 1.14 2012/07/01 23:21:06 rmind Exp $
 .\"
 .\" Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd June 27, 2012
+.Dd June 29, 2012
 .Dt NPF.CONF 5
 .Os
 .Sh NAME
@@ -218,6 +218,7 @@
 }
 
 group (default) {
+	pass final on lo0 all
 	block all
 }
 .Ed
--- a/usr.sbin/npf/npfctl/npf_build.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_build.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_build.c,v 1.9 2012/06/16 01:34:10 christos Exp $	*/
+/*	$NetBSD: npf_build.c,v 1.10 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_build.c,v 1.9 2012/06/16 01:34:10 christos Exp $");
+__RCSID("$NetBSD: npf_build.c,v 1.10 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/ioctl.h>
@@ -69,7 +69,9 @@
 	int error;
 
 	if (!fd) {
-		_npf_config_setsubmit(npf_conf, "./npf.plist");
+		const char *outconf = "/tmp/npf.plist";
+		_npf_config_setsubmit(npf_conf, outconf);
+		printf("\nSaving to %s\n", outconf);
 	}
 	if (!defgroup_set) {
 		errx(EXIT_FAILURE, "default group was not defined");
@@ -116,7 +118,7 @@
 	return npfvar_get_data(vp, NPFVAR_FAM, 0);
 }
 
-static void
+static bool
 npfctl_build_fam(nc_ctx_t *nc, sa_family_t family,
     fam_addr_mask_t *fam, int opts)
 {
@@ -130,7 +132,7 @@
 			yyerror("specified address is not of the required "
 			    "family %d", family);
 		}
-		return;
+		return false;
 	}
 
 	/*
@@ -139,11 +141,12 @@
 	 */
 	if (fam->fam_mask == 0) {
 		npf_addr_t zero;
+
 		memset(&zero, 0, sizeof(npf_addr_t));
 		if (memcmp(&fam->fam_addr, &zero, sizeof(npf_addr_t))) {
 			yyerror("filter criterion would never match");
 		}
-		return;
+		return false;
 	}
 
 	switch (fam->fam_family) {
@@ -158,6 +161,7 @@
 	default:
 		yyerror("family %d is not supported", fam->fam_family);
 	}
+	return true;
 }
 
 static void
@@ -202,12 +206,14 @@
 }
 
 static int
-npfctl_build_proto(nc_ctx_t *nc, const opt_proto_t *op)
+npfctl_build_proto(nc_ctx_t *nc, sa_family_t family,
+    const opt_proto_t *op, bool nof, bool nop)
 {
 	const npfvar_t *popts = op->op_opts;
+	const int proto = op->op_proto;
 	int pflag = 0;
 
-	switch (op->op_proto) {
+	switch (proto) {
 	case IPPROTO_TCP:
 		pflag = NC_MATCH_TCP;
 		if (!popts) {
@@ -221,6 +227,7 @@
 		tf = npfvar_get_data(popts, NPFVAR_TCPFLAG, 0);
 		tf_mask = npfvar_get_data(popts, NPFVAR_TCPFLAG, 1);
 		npfctl_gennc_tcpfl(nc, *tf, *tf_mask);
+		nop = false;
 		break;
 	case IPPROTO_UDP:
 		pflag = NC_MATCH_UDP;
@@ -229,18 +236,50 @@
 		/*
 		 * Build ICMP block.
 		 */
+		if (!nop) {
+			goto invop;
+		}
 		assert(npfvar_get_count(popts) == 2);
 
 		int *icmp_type, *icmp_code;
 		icmp_type = npfvar_get_data(popts, NPFVAR_ICMP, 0);
 		icmp_code = npfvar_get_data(popts, NPFVAR_ICMP, 1);
 		npfctl_gennc_icmp(nc, *icmp_type, *icmp_code);
+		nop = false;
 		break;
 	case -1:
 		pflag = NC_MATCH_TCP | NC_MATCH_UDP;
+		nop = false;
 		break;
 	default:
-		yyerror("protocol %d is not supported", op->op_proto);
+		/*
+		 * No filter options are supported for other protcols.
+		 */
+		if (nof && nop) {
+			break;
+		}
+invop:
+		yyerror("invalid filter options for protocol %d", proto);
+	}
+
+	/*
+	 * Build the protocol block, unless other blocks will implicitly
+	 * perform the family/protocol checks for us.
+	 */
+	if ((family != AF_UNSPEC && nof) || (proto != -1 && nop)) {
+		uint8_t addrlen;
+
+		switch (family) {
+		case AF_INET:
+			addrlen = sizeof(struct in_addr);
+			break;
+		case AF_INET6:
+			addrlen = sizeof(struct in6_addr);
+			break;
+		default:
+			addrlen = 0;
+		}
+		npfctl_gennc_proto(nc, nof ? addrlen : 0, nop ? proto : 0xff);
 	}
 	return pflag;
 }
@@ -251,13 +290,18 @@
 {
 	const addr_port_t *apfrom = &fopts->fo_from;
 	const addr_port_t *apto = &fopts->fo_to;
+	const int proto = op->op_proto;
+	bool nof, nop;
 	nc_ctx_t *nc;
 	void *code;
 	size_t len;
 
-	if (family == AF_UNSPEC && op->op_proto == -1 &&
-	    op->op_opts == NULL && !apfrom->ap_netaddr && !apto->ap_netaddr &&
-	    !apfrom->ap_portrange && !apto->ap_portrange)
+	/*
+	 * If none specified, no n-code.
+	 */
+	nof = !apfrom->ap_netaddr && !apto->ap_netaddr;
+	nop = !apfrom->ap_portrange && !apto->ap_portrange;
+	if (family == AF_UNSPEC && proto == -1 && !op->op_opts && nof && nop)
 		return false;
 
 	int srcflag = NC_MATCH_SRC;
@@ -270,22 +314,16 @@
 
 	nc = npfctl_ncgen_create();
 
+	/* Build layer 4 protocol blocks. */
+	int pflag = npfctl_build_proto(nc, family, op, nof, nop);
+
 	/* Build IP address blocks. */
 	npfctl_build_vars(nc, family, apfrom->ap_netaddr, srcflag);
 	npfctl_build_vars(nc, family, apto->ap_netaddr, dstflag);
 
-	/* Build layer 4 protocol blocks. */
-	int pflag = npfctl_build_proto(nc, op);
-
 	/* Build port-range blocks. */
-	if (apfrom->ap_portrange) {
-		npfctl_build_vars(nc, family, apfrom->ap_portrange,
-		    srcflag | pflag);
-	}
-	if (apto->ap_portrange) {
-		npfctl_build_vars(nc, family, apto->ap_portrange,
-		    dstflag | pflag);
-	}
+	npfctl_build_vars(nc, family, apfrom->ap_portrange, srcflag | pflag);
+	npfctl_build_vars(nc, family, apto->ap_portrange, dstflag | pflag);
 
 	/*
 	 * Complete n-code (destroys the context) and pass to the rule.
@@ -296,6 +334,8 @@
 		printf("RULE AT LINE %d\n", yylineno);
 		npfctl_ncgen_print(code, len);
 	}
+	assert(code && len > 0);
+
 	if (npf_rule_setcode(rl, NPF_CODE_NCODE, code, len) == -1) {
 		errx(EXIT_FAILURE, "npf_rule_setcode failed");
 	}
@@ -438,7 +478,7 @@
     const addr_port_t *ap2, const filt_opts_t *fopts)
 {
 	const opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
-	fam_addr_mask_t *am1, *am2;
+	fam_addr_mask_t *am1 = NULL, *am2 = NULL;
 	filt_opts_t imfopts;
 	sa_family_t family;
 	nl_nat_t *nat;
@@ -456,12 +496,11 @@
 			yyerror("inbound network segment is not specified");
 		}
 		am1 = npfctl_get_singlefam(ap1->ap_netaddr);
-		if (am1->fam_family != AF_INET) {
+		if (am1->fam_family != family) {
 			yyerror("IPv6 NAT is not supported");
 		}
 		assert(am1 != NULL);
-	} else
-		am1 = NULL;
+	}
 
 	if (type & NPF_NATOUT) {
 		if (!ap2->ap_netaddr) {
@@ -472,8 +511,7 @@
 			yyerror("IPv6 NAT is not supported");
 		}
 		assert(am2 != NULL);
-	} else
-		am2 = NULL;
+	}
 
 	/*
 	 * If filter criteria is not specified explicitly, apply implicit
--- a/usr.sbin/npf/npfctl/npf_data.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_data.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_data.c,v 1.13 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_data.c,v 1.14 2012/07/01 23:21:06 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_data.c,v 1.13 2012/06/15 23:24:08 rmind Exp $");
+__RCSID("$NetBSD: npf_data.c,v 1.14 2012/07/01 23:21:06 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/null.h>
@@ -64,6 +64,8 @@
 static bool
 npfctl_copy_address(sa_family_t fam, npf_addr_t *addr, const void *ptr)
 {
+	memset(addr, 0, sizeof(npf_addr_t));
+
 	switch (fam) {
 	case AF_INET: {
 		const struct sockaddr_in *sin = ptr;
@@ -346,6 +348,19 @@
 	return npfvar_get_data(vp, NPFVAR_FAM, 0);
 }
 
+int
+npfctl_protono(const char *proto)
+{
+	struct protoent *pe;
+
+	pe = getprotobyname(proto);
+	if (pe == NULL) {
+		yyerror("unknown protocol '%s'", proto);
+		return -1;
+	}
+	return pe->p_proto;
+}
+
 /*
  * npfctl_portno: convert port identifier (string) to a number.
  *
@@ -360,7 +375,7 @@
 
 	e = getaddrinfo(NULL, port, NULL, &rai);
 	if (e != 0) {
-		yyerror("invalid port name: '%s' (%s)", port, gai_strerror(e));
+		yyerror("invalid port name '%s' (%s)", port, gai_strerror(e));
 		return 0;
 	}
 
--- a/usr.sbin/npf/npfctl/npf_disassemble.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_disassemble.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_disassemble.c,v 1.5 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_disassemble.c,v 1.6 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2012 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_disassemble.c,v 1.5 2012/06/15 23:24:08 rmind Exp $");
+__RCSID("$NetBSD: npf_disassemble.c,v 1.6 2012/07/01 23:21:07 rmind Exp $");
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -205,6 +205,13 @@
 		snprintf(buf, bufsiz, "ether=0x%x", op);
 		break;
 
+	case NPF_OPERAND_PROTO: {
+		uint8_t addrlen = (op >> 8) & 0xff;
+		uint8_t proto = op & 0xff;
+
+		snprintf(buf, bufsiz, "addrlen=%u, proto=%u", addrlen, proto);
+		break;
+	}
 	case NPF_OPERAND_SUBNET: {
 		snprintf(buf, bufsiz, "/%d", op);
 		if (ni) {
@@ -448,7 +455,7 @@
 	{ NPF_RSTICMP,		NPF_RSTICMP,	"return",	NULL	},
 	{ NPF_RSTICMP,		NPF_RULE_RETRST,"return-rst",	NULL	},
 	{ NPF_RSTICMP,		NPF_RULE_RETICMP,"return-icmp",	NULL	},
-	{ NPF_RULE_KEEPSTATE,	NPF_RULE_KEEPSTATE,"stateful",	NULL	},
+	{ NPF_RULE_STATEFUL,	NPF_RULE_STATEFUL,"stateful",	NULL	},
 	{ NPF_RULE_DIMASK,	NPF_RULE_IN,	"in",		NULL	},
 	{ NPF_RULE_DIMASK,	NPF_RULE_OUT,	"out",		NULL	},
 	{ NPF_RULE_FINAL,	NPF_RULE_FINAL,	"final",	NULL	},
@@ -524,6 +531,38 @@
 	puts("");
 }
 
+static void
+npfctl_show_table(unsigned id, int type)
+{
+	printf("table <%u> type %s\n", id,
+		(type == NPF_TABLE_HASH) ? "hash" :
+		(type == NPF_TABLE_TREE) ? "tree" :
+		"unknown"
+	);
+}
+
+static void
+npfctl_show_nat(nl_rule_t *nrl, unsigned nlevel)
+{
+	rule_group_t rg;
+	nl_nat_t *nt = nrl;
+	npf_addr_t taddr;
+	in_port_t port;
+	size_t alen;
+	u_int flags;
+	int type;
+
+	_npf_rule_getinfo(nrl, &rg.rg_name, &rg.rg_attr, &rg.rg_ifnum);
+
+	/* Get the interface, if any. */
+	char ifnamebuf[IFNAMSIZ], *ifname = NULL;
+	if (rg.rg_ifnum) {
+		ifname = if_indextoname(rg.rg_ifnum, ifnamebuf);
+	}
+	_npf_nat_getinfo(nt, &type, &flags, &taddr, &alen, &port);
+	return; /* TODO */
+}
+
 int
 npfctl_config_show(int fd)
 {
@@ -540,8 +579,14 @@
 	    loaded ? "loaded" : "empty");
 
 	if (loaded) {
-		error = _npf_rule_foreach(ncf, npfctl_show_rule);
-		puts("}");
+		_npf_table_foreach(ncf, npfctl_show_table);
+		puts("");
+		error = _npf_nat_foreach(ncf, npfctl_show_nat);
+		puts("");
+		if (!error) {
+			error = _npf_rule_foreach(ncf, npfctl_show_rule);
+			puts("}");
+		}
 	}
 	npf_config_destroy(ncf);
 	return error;
--- a/usr.sbin/npf/npfctl/npf_ncgen.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_ncgen.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_ncgen.c,v 1.10 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_ncgen.c,v 1.11 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_ncgen.c,v 1.10 2012/06/15 23:24:08 rmind Exp $");
+__RCSID("$NetBSD: npf_ncgen.c,v 1.11 2012/07/01 23:21:07 rmind Exp $");
 
 #include <stdlib.h>
 #include <stddef.h>
@@ -385,6 +385,25 @@
 	npfctl_ncgen_putptr(ctx, nc);
 }
 
+/*
+ * npfctl_gennc_proto: fragment to match the protocol.
+ */
+void
+npfctl_gennc_proto(nc_ctx_t *ctx, uint8_t addrlen, uint8_t proto)
+{
+	uint32_t *nc = npfctl_ncgen_getptr(ctx, 4 /* words */);
+
+	/* OP, code, type (2 words) */
+	*nc++ = NPF_OPCODE_PROTO;
+	*nc++ = ((addrlen & 0xff) << 8) | (proto & 0xff);
+
+	/* Comparison block (2 words). */
+	npfctl_ncgen_addjmp(ctx, &nc);
+
+	/* + 4 words. */
+	npfctl_ncgen_putptr(ctx, nc);
+}
+
 void
 npfctl_ncgen_print(const void *code, size_t len)
 {
--- a/usr.sbin/npf/npfctl/npf_parse.y	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_parse.y	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_parse.y,v 1.8 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_parse.y,v 1.9 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -121,7 +121,6 @@
 %token			TO
 %token			TREE
 %token			TYPE
-%token			UDP
 %token			ICMP
 
 %token	<num>		HEX
@@ -133,10 +132,10 @@
 %token	<str>		TABLE_ID
 %token	<str>		VAR_ID
 
-%type	<str>		addr, iface_name, moduleargname, list_elem, table_store
+%type	<str>		addr, some_name, list_elem, table_store
 %type	<str>		opt_apply
 %type	<num>		ifindex, port, opt_final, on_iface
-%type	<num>		block_or_pass, rule_dir, block_opts, family, opt_family
+%type	<num>		block_or_pass, rule_dir, block_opts, opt_family
 %type	<num>		opt_stateful, icmp_type, table_type, map_sd, map_type
 %type	<var>		addr_or_iface, port_range, icmp_type_and_code
 %type	<var>		filt_addr, addr_and_mask, tcp_flags, tcp_flags_and_mask
@@ -321,7 +320,7 @@
 	;
 
 modulearg
-	: moduleargname modulearg_opts
+	: some_name modulearg_opts
 	{
 		module_arg_t ma;
 
@@ -332,11 +331,6 @@
 	}
 	;
 
-moduleargname
-	: STRING	{ $$ = $1; }
-	| IDENTIFIER	{ $$ = $1; }
-	;
-
 modulearg_opts
 	: STRING modulearg_opts
 	{
@@ -461,9 +455,10 @@
 	|			{ $$ = 0; }
 	;
 
-family
-	: INET			{ $$ = AF_INET; }
-	| INET6			{ $$ = AF_INET6; }
+opt_family
+	: FAMILY INET		{ $$ = AF_INET; }
+	| FAMILY INET6		{ $$ = AF_INET6; }
+	|			{ $$ = AF_UNSPEC; }
 	;
 
 opt_proto
@@ -477,9 +472,14 @@
 		$$.op_proto = IPPROTO_ICMP;
 		$$.op_opts = $3;
 	}
-	| PROTO UDP
+	| PROTO some_name
 	{
-		$$.op_proto = IPPROTO_UDP;
+		$$.op_proto = npfctl_protono($2);
+		$$.op_opts = NULL;
+	}
+	| PROTO NUM
+	{
+		$$.op_proto = $2;
 		$$.op_opts = NULL;
 	}
 	|
@@ -489,11 +489,6 @@
 	}
 	;
 
-opt_family
-	: FAMILY family		{ $$ = $2; }
-	|			{ $$ = AF_UNSPEC; }
-	;
-
 all_or_filt_opts
 	: ALL
 	{
@@ -506,7 +501,7 @@
 	;
 
 opt_stateful
-	: STATEFUL	{ $$ = NPF_RULE_KEEPSTATE; }
+	: STATEFUL	{ $$ = NPF_RULE_STATEFUL; }
 	|		{ $$ = 0; }
 	;
 
@@ -572,8 +567,12 @@
 	;
 
 addr_or_iface
-	: addr_and_mask	{ assert($1 != NULL); $$ = $1; }
-	| iface_name
+	: addr_and_mask
+	{
+		assert($1 != NULL);
+		$$ = $1;
+	}
+	| some_name
 	{
 		$$ = npfctl_parse_iface($1);
 	}
@@ -686,7 +685,7 @@
 	;
 
 ifindex
-	: iface_name
+	: some_name
 	{
 		$$ = npfctl_find_ifindex($1);
 	}
@@ -712,7 +711,7 @@
 	}
 	;
 
-iface_name
+some_name
 	: IDENTIFIER	{ $$ = $1; }
 	| STRING	{ $$ = $1; }
 	;
--- a/usr.sbin/npf/npfctl/npf_scan.l	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_scan.l	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_scan.l,v 1.3 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npf_scan.l,v 1.4 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -86,7 +86,6 @@
 family			return FAMILY;
 tcp			return TCP;
 icmp			return ICMP;
-udp			return UDP;
 return-rst		return RETURNRST;
 return-icmp		return RETURNICMP;
 return			return RETURN;
--- a/usr.sbin/npf/npfctl/npfctl.8	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.8	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-.\"	$NetBSD: npfctl.8,v 1.7 2012/06/27 23:05:28 rmind Exp $
+.\"	$NetBSD: npfctl.8,v 1.8 2012/07/01 23:21:07 rmind Exp $
 .\"
 .\" Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd June 27, 2012
+.Dd July 1, 2012
 .Dt NPFCTL 8
 .Os
 .Sh NAME
--- a/usr.sbin/npf/npfctl/npfctl.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npfctl.c,v 1.13 2012/06/27 23:05:28 rmind Exp $	*/
+/*	$NetBSD: npfctl.c,v 1.14 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npfctl.c,v 1.13 2012/06/27 23:05:28 rmind Exp $");
+__RCSID("$NetBSD: npfctl.c,v 1.14 2012/07/01 23:21:07 rmind Exp $");
 
 #include <sys/ioctl.h>
 #include <sys/stat.h>
@@ -250,8 +250,8 @@
 	ret = ioctl(fd, IOC_NPF_VERSION, &ver);
 	if (ver != NPF_VERSION) {
 		errx(EXIT_FAILURE,
-		    "incompatible NPF interface version (%d, kernel %d)",
-		    NPF_VERSION, ver);
+		    "incompatible NPF interface version (%d, kernel %d)\n"
+		    "Hint: update userland?", NPF_VERSION, ver);
 	}
 	switch (action) {
 	case NPFCTL_START:
@@ -338,7 +338,7 @@
 	cmd = argv[1];
 
 	if (strcmp(cmd, "debug") == 0) {
-		const char *cfg = argc > 2 ? argv[2] : "/tmp/npf.conf";
+		const char *cfg = argc > 2 ? argv[2] : "/etc/npf.conf";
 		npfctl_config_init(true);
 		npfctl_parsecfg(cfg);
 		npfctl_config_send(0);
--- a/usr.sbin/npf/npfctl/npfctl.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.h	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npfctl.h,v 1.15 2012/06/15 23:24:08 rmind Exp $	*/
+/*	$NetBSD: npfctl.h,v 1.16 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -96,6 +96,7 @@
 
 void		npfctl_print_error(const nl_error_t *);
 bool		npfctl_table_exists_p(const char *);
+int		npfctl_protono(const char *);
 in_port_t	npfctl_portno(const char *);
 uint8_t		npfctl_icmpcode(uint8_t, const char *);
 uint8_t		npfctl_icmptype(const char *);
@@ -139,6 +140,7 @@
 void		npfctl_gennc_icmp(nc_ctx_t *, int, int);
 void		npfctl_gennc_tbl(nc_ctx_t *, int, u_int);
 void		npfctl_gennc_tcpfl(nc_ctx_t *, uint8_t, uint8_t);
+void		npfctl_gennc_proto(nc_ctx_t *ctx, uint8_t, uint8_t);
 
 /*
  * N-code disassembler.
--- a/usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c	Sun Jul 01 23:21:06 2012 +0000
@@ -43,12 +43,34 @@
 	return m0;
 }
 
+static int
+mbuf_fill_proto(int proto, void *l4data)
+{
+	struct tcphdr *th;
+	int size = 0;
+
+	switch (proto) {
+	case IPPROTO_TCP:
+		th = l4data;
+		th->th_off = sizeof(struct tcphdr) >> 2;
+		size = sizeof(struct tcphdr);
+		break;
+	case IPPROTO_UDP:
+		size = sizeof(struct udphdr);
+		break;
+	case IPPROTO_ICMP:
+		size = offsetof(struct icmp, icmp_data);
+		break;
+	}
+	return size;
+}
+
 struct mbuf *
 mbuf_construct(int proto)
 {
 	struct mbuf *m;
 	struct ip *iphdr;
-	struct tcphdr *th;
+	void *l4data;
 	int size;
 
 	m = m_gethdr(M_WAITOK, MT_HEADER);
@@ -61,21 +83,34 @@
 	iphdr->ip_p = proto;
 
 	size = sizeof(struct ip);
+	l4data = (void *)(iphdr + 1);
+	size += mbuf_fill_proto(proto, l4data);
+	iphdr->ip_len = htons(size);
 
-	switch (proto) {
-	case IPPROTO_TCP:
-		th = (void *)(iphdr + 1);
-		th->th_off = sizeof(struct tcphdr) >> 2;
-		size += sizeof(struct tcphdr);
-		break;
-	case IPPROTO_UDP:
-		size += sizeof(struct udphdr);
-		break;
-	case IPPROTO_ICMP:
-		size += offsetof(struct icmp, icmp_data);
-		break;
-	}
-	iphdr->ip_len = htons(size);
+	m->m_len = size;
+	m->m_next = NULL;
+	return m;
+}
+
+struct mbuf *
+mbuf_construct6(int proto)
+{
+	struct mbuf *m;
+	struct ip6_hdr *ip6;
+	void *l4data;
+	int size;
+
+	m = m_gethdr(M_WAITOK, MT_HEADER);
+	ip6 = mtod(m, struct ip6_hdr *);
+
+	ip6->ip6_vfc = IPV6_VERSION;
+	ip6->ip6_nxt = proto;
+	ip6->ip6_hlim = 64;
+
+	size = sizeof(struct ip6_hdr);
+	l4data = (void *)(ip6 + 1);
+	size += mbuf_fill_proto(proto, l4data);
+	ip6->ip6_plen = htons(size);
 
 	m->m_len = size;
 	m->m_next = NULL;
--- a/usr.sbin/npf/npftest/libnpftest/npf_processor_test.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_processor_test.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_processor_test.c,v 1.1 2012/04/14 21:57:29 rmind Exp $	*/
+/*	$NetBSD: npf_processor_test.c,v 1.2 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*
  * NPF n-code processor test.
@@ -17,6 +17,14 @@
  *	192.168.2.0				== 0x0002a8c0
  *	192.168.2.1				== 0x0102a8c0
  *	192.168.2.100				== 0x6402a8c0
+ *	fe80::					== 0x000080fe
+ *						   0x00000000
+ *						   0x00000000
+ *						   0x00000000
+ *	fe80::2a0:c0ff:fe10:1234		== 0x000080fe
+ *						   0x00000000
+ *						   0xffc0a002
+ *						   0x341210fe
  *	htons(ETHERTYPE_IP)			== 0x08
  *	(htons(80) << 16) | htons(80)		== 0x50005000
  *	(htons(80) << 16) | htons(15000)	== 0x5000983a
@@ -74,17 +82,55 @@
 	NPF_OPCODE_RET,		0x01
 };
 
-static void
-fill_packet(struct mbuf *m, bool ether)
+static uint32_t nc_match6[ ] __aligned(4) = {
+	NPF_OPCODE_IP6MASK,	0x01,
+	    0x000080fe, 0x00000000, 0x00000000, 0x00000000, 10,
+	NPF_OPCODE_BEQ,		0x04,
+	NPF_OPCODE_RET,		0xff,
+	NPF_OPCODE_TCP_PORTS,	0x00,		0x50005000,
+	NPF_OPCODE_BEQ,		0x04,
+	NPF_OPCODE_RET,		0xff,
+	NPF_OPCODE_RET,		0x00
+};
+
+static struct mbuf *
+fill_packet(int proto, bool ether)
 {
+	struct mbuf *m;
 	struct ip *ip;
 	struct tcphdr *th;
 
+	if (ether) {
+		m = mbuf_construct_ether(IPPROTO_TCP);
+	} else {
+		m = mbuf_construct(IPPROTO_TCP);
+	}
 	th = mbuf_return_hdrs(m, ether, &ip);
 	ip->ip_src.s_addr = inet_addr("192.168.2.100");
 	ip->ip_dst.s_addr = inet_addr("10.0.0.1");
 	th->th_sport = htons(15000);
 	th->th_dport = htons(80);
+	return m;
+}
+
+static struct mbuf *
+fill_packet6(int proto)
+{
+	uint32_t src[] = { 0x000080fe, 0x00000000, 0xffc0a002, 0x341210fe };
+	uint32_t dst[] = { 0x000080fe, 0x00000000, 0xffc0a002, 0x111110fe };
+	struct mbuf *m;
+	struct ip6_hdr *ip;
+	struct tcphdr *th;
+
+	m = mbuf_construct6(proto);
+	(void)mbuf_return_hdrs(m, false, (struct ip **)&ip);
+	memcpy(&ip->ip6_src, src, sizeof(ip->ip6_src));
+	memcpy(&ip->ip6_dst, dst, sizeof(ip->ip6_src));
+
+	th = (void *)(ip + 1);
+	th->th_sport = htons(15000);
+	th->th_dport = htons(80);
+	return m;
 }
 
 static bool
@@ -107,8 +153,7 @@
 	int errat, ret;
 
 	/* Layer 2 (Ethernet + IP + TCP). */
-	m = mbuf_construct_ether(IPPROTO_TCP);
-	fill_packet(m, true);
+	m = fill_packet(IPPROTO_TCP, true);
 	ret = npf_ncode_validate(nc_match, sizeof(nc_match), &errat);
 	if (!validate_retcode("Ether validation", verbose, ret, 0)) {
 		return false;
@@ -121,8 +166,7 @@
 	m_freem(m);
 
 	/* Layer 3 (IP + TCP). */
-	m = mbuf_construct(IPPROTO_TCP);
-	fill_packet(m, false);
+	m = fill_packet(IPPROTO_TCP, false);
 	memset(&npc, 0, sizeof(npf_cache_t));
 	ret = npf_ncode_process(&npc, nc_match, m, NPF_LAYER_3);
 	if (!validate_retcode("IPv4 mask 1", verbose, ret, 0)) {
@@ -159,5 +203,19 @@
 
 	m_freem(m);
 
+	/* IPv6 matching. */
+	ret = npf_ncode_validate(nc_match6, sizeof(nc_match6), &errat);
+	if (!validate_retcode("IPv6 mask validation", verbose, ret, 0)) {
+		return false;
+	}
+	m = fill_packet6(IPPROTO_TCP);
+	memset(&npc, 0, sizeof(npf_cache_t));
+	ret = npf_ncode_process(&npc, nc_match6, m, NPF_LAYER_3);
+	if (!validate_retcode("IPv6 mask", verbose, ret, 0)) {
+		return false;
+	}
+
+	m_freem(m);
+
 	return true;
 }
--- a/usr.sbin/npf/npftest/libnpftest/npf_state_test.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_state_test.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_state_test.c,v 1.1 2012/06/04 00:28:34 rmind Exp $	*/
+/*	$NetBSD: npf_state_test.c,v 1.2 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*
  * NPF state tracking test.
@@ -69,6 +69,20 @@
 	{ A,	1000,		12000,	10,		1000,	OUT	},
 	{ CLEAR },
 
+	/* FIN exchange with retransmit. */
+	{ S,	0,		999,	0,		1000,	OUT	},
+	{ S|A,	0,		9,	1000,		2000,	IN	},
+	{ A,	0,		1000,	10,		1000,	OUT	},
+	/* --- */
+	{ F,	0,		10,	1000,		2000,	IN	},
+	{ F,	0,		1000,	10,		1000,	OUT	},
+	{ A,	0,		1000,	11,		1000,	OUT	},
+	/* --- */
+	{ F,	0,		1000,	11,		1000,	OUT	},
+	{ F,	0,		1000,	11,		1000,	OUT	},
+	{ A,	0,		11,	1001,		2000,	OUT	},
+	{ CLEAR },
+
 	/* Out of window. */
 	{ S,	0,		9,	0,		8760,	OUT	},
 	{ S|A,	0,		9999,	10,		1000,	IN	},
--- a/usr.sbin/npf/npftest/libnpftest/npf_table_test.c	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_table_test.c	Sun Jul 01 23:21:06 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_table_test.c,v 1.2 2012/06/22 13:43:17 rmind Exp $	*/
+/*	$NetBSD: npf_table_test.c,v 1.3 2012/07/01 23:21:07 rmind Exp $	*/
 
 /*
  * NPF tableset test.
@@ -56,6 +56,7 @@
 	assert(error == 0);
 
 	/* Attempt to match non-existing entries - should fail. */
+	memset(addr, 0, sizeof(npf_addr_t));
 	addr->s6_addr32[0] = inet_addr(ip_list[0]);
 
 	error = npf_table_match_addr(tblset, HASH_TID, addr);
@@ -66,6 +67,7 @@
 
 	/* Fill both tables with IP addresses. */
 	for (i = 0; i < __arraycount(ip_list); i++) {
+		memset(addr, 0, sizeof(npf_addr_t));
 		addr->s6_addr32[0] = inet_addr(ip_list[i]);
 
 		error = npf_table_add_cidr(tblset, HASH_TID, addr, 32);
@@ -80,6 +82,7 @@
 	}
 
 	/* Attempt to add duplicates - should fail. */
+	memset(addr, 0, sizeof(npf_addr_t));
 	addr->s6_addr32[0] = inet_addr(ip_list[0]);
 
 	error = npf_table_add_cidr(tblset, HASH_TID, addr, 32);
@@ -99,6 +102,7 @@
 
 	/* Match (validate) each IP entry. */
 	for (i = 0; i < __arraycount(ip_list); i++) {
+		memset(addr, 0, sizeof(npf_addr_t));
 		addr->s6_addr32[0] = inet_addr(ip_list[i]);
 
 		error = npf_table_match_addr(tblset, HASH_TID, addr);
@@ -110,6 +114,7 @@
 
 	/* Remove all entries. */
 	for (i = 0; i < __arraycount(ip_list); i++) {
+		memset(addr, 0, sizeof(npf_addr_t));
 		addr->s6_addr32[0] = inet_addr(ip_list[i]);
 
 		error = npf_table_rem_cidr(tblset, HASH_TID, addr, 32);
--- a/usr.sbin/npf/npftest/libnpftest/npf_test.h	Sun Jul 01 22:04:44 2012 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_test.h	Sun Jul 01 23:21:06 2012 +0000
@@ -31,6 +31,7 @@
 struct mbuf *	mbuf_getwithdata(const void *, size_t);
 struct mbuf *	mbuf_construct_ether(int);
 struct mbuf *	mbuf_construct(int);
+struct mbuf *	mbuf_construct6(int);
 void *		mbuf_return_hdrs(struct mbuf *, bool, struct ip **);
 void		mbuf_icmp_append(struct mbuf *, struct mbuf *);