Implement dynamic NPF extensions interface. An extension consists of trunk
authorrmind <rmind@NetBSD.org>
Sun, 16 Sep 2012 13:47:41 +0000
branchtrunk
changeset 213403 81697c6dfc49
parent 213402 5eb5994913e6
child 213404 870b668c3897
Implement dynamic NPF extensions interface. An extension consists of dynamically loaded module (.so) supplementing npfctl(8) and a kernel module. Move normalisation and logging functionality into their own extensions. More improvements to come.
distrib/sets/lists/base/shl.mi
distrib/sets/lists/modules/mi
lib/Makefile
lib/libnpf/Makefile
lib/libnpf/npf.c
lib/libnpf/npf.h
lib/npf/Makefile
lib/npf/Makefile.inc
lib/npf/ext_log/Makefile
lib/npf/ext_log/npfext_log.c
lib/npf/ext_log/shlib_version
lib/npf/ext_normalise/Makefile
lib/npf/ext_normalise/npfext_normalise.c
lib/npf/ext_normalise/shlib_version
sys/modules/Makefile
sys/modules/npf/Makefile
sys/modules/npf_ext_log/Makefile
sys/modules/npf_ext_normalise/Makefile
sys/net/npf/files.npf
sys/net/npf/npf.c
sys/net/npf/npf.h
sys/net/npf/npf_ctl.c
sys/net/npf/npf_ext_log.c
sys/net/npf/npf_ext_normalise.c
sys/net/npf/npf_handler.c
sys/net/npf/npf_impl.h
sys/net/npf/npf_inet.c
sys/net/npf/npf_log.c
sys/net/npf/npf_rproc.c
sys/rump/net/lib/libnpf/Makefile
usr.sbin/npf/npfctl/Makefile
usr.sbin/npf/npfctl/npf_build.c
usr.sbin/npf/npfctl/npf_extmod.c
usr.sbin/npf/npfctl/npf_parse.y
usr.sbin/npf/npfctl/npf_scan.l
usr.sbin/npf/npfctl/npf_var.h
usr.sbin/npf/npfctl/npfctl.c
usr.sbin/npf/npfctl/npfctl.h
--- a/distrib/sets/lists/base/shl.mi	Sun Sep 16 13:46:49 2012 +0000
+++ b/distrib/sets/lists/base/shl.mi	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: shl.mi,v 1.635 2012/08/08 14:08:02 christos Exp $
+# $NetBSD: shl.mi,v 1.636 2012/09/16 13:47:43 rmind Exp $
 #
 # Note:	Don't delete entries from here - mark them as "obsolete" instead,
 #	unless otherwise stated below.
@@ -726,6 +726,12 @@
 ./usr/lib/libzpool_pic.a			base-zfs-shlib		zfs,dynamicroot
 ./usr/lib/lua/5.1/gpio.so                    	base-sys-shlib
 ./usr/lib/lua/5.1/sqlite.so                    	base-sys-shlib
+./usr/lib/npf/ext_log.so			base-npf-shlib		npf
+./usr/lib/npf/ext_log.so.0			base-npf-shlib		npf
+./usr/lib/npf/ext_log.so.0.0			base-npf-shlib		npf
+./usr/lib/npf/ext_normalise.so			base-npf-shlib		npf
+./usr/lib/npf/ext_normalise.so.0		base-npf-shlib		npf
+./usr/lib/npf/ext_normalise.so.0.0		base-npf-shlib		npf
 ./usr/lib/nss_mdns.so.0				base-obsolete		obsolete
 ./usr/lib/nss_mdnsd.so.0			base-mdns-shlib		mdns
 ./usr/lib/nss_multicast_dns.so.0		base-mdns-shlib		mdns
--- a/distrib/sets/lists/modules/mi	Sun Sep 16 13:46:49 2012 +0000
+++ b/distrib/sets/lists/modules/mi	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.47 2012/08/06 10:44:08 martin Exp $
+# $NetBSD: mi,v 1.48 2012/09/16 13:47:43 rmind Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -115,6 +115,10 @@
 ./@MODULEDIR@/npf/npf.kmod			base-kernel-modules	kmod
 ./@MODULEDIR@/npf_alg_icmp			base-kernel-modules	kmod
 ./@MODULEDIR@/npf_alg_icmp/npf_alg_icmp.kmod	base-kernel-modules	kmod
+./@MODULEDIR@/npf_ext_log			base-kernel-modules	kmod
+./@MODULEDIR@/npf_ext_log/npf_ext_log.kmod	base-kernel-modules	kmod
+./@MODULEDIR@/npf_ext_normalise			base-kernel-modules	kmod
+./@MODULEDIR@/npf_ext_normalise/npf_ext_normalise.kmod	base-kernel-modules	kmod
 ./@MODULEDIR@/ntfs				base-kernel-modules	kmod
 ./@MODULEDIR@/ntfs/ntfs.kmod			base-kernel-modules	kmod
 ./@MODULEDIR@/null				base-kernel-modules	kmod
--- a/lib/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/lib/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.188 2012/08/17 16:22:27 joerg Exp $
+#	$NetBSD: Makefile,v 1.189 2012/09/16 13:47:41 rmind Exp $
 #	from: @(#)Makefile	5.25.1.1 (Berkeley) 5/7/91
 
 .include <bsd.own.mk>
@@ -110,6 +110,7 @@
 
 .if (${MKNPF} != "no")
 SUBDIR+=	libnpf		# depends on libprop
+SUBDIR+=	npf
 .endif
 
 .if (${MKCRYPTO} != "no")
--- a/lib/libnpf/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/lib/libnpf/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.2 2012/03/21 05:37:42 matt Exp $
+# $NetBSD: Makefile,v 1.3 2012/09/16 13:47:42 rmind Exp $
 
 .include <bsd.own.mk>
 
@@ -14,7 +14,7 @@
 LDADD+=		-lprop
 DPADD+=		${LIBPROP}
 
-WARNS?=		5
-NOLINT=		# defined (note: deliberately)
+WARNS=		5
+NOLINT=		# disabled deliberately
 
 .include <bsd.lib.mk>
--- a/lib/libnpf/npf.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/lib/libnpf/npf.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.c,v 1.12 2012/08/15 18:44:56 rmind Exp $	*/
+/*	$NetBSD: npf.c,v 1.13 2012/09/16 13:47:42 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.12 2012/08/15 18:44:56 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.13 2012/09/16 13:47:42 rmind Exp $");
 
 #include <sys/types.h>
 #include <netinet/in_systm.h>
@@ -78,6 +78,11 @@
 	prop_dictionary_t	ntl_dict;
 };
 
+struct nl_ext {
+	const char *		nxt_name;
+	prop_dictionary_t	nxt_dict;
+};
+
 /*
  * CONFIGURATION INTERFACE.
  */
@@ -250,6 +255,43 @@
 }
 
 /*
+ * NPF EXTENSION INTERFACE.
+ */
+
+nl_ext_t *
+npf_ext_construct(const char *name)
+{
+	nl_ext_t *ext;
+
+	ext = malloc(sizeof(*ext));
+	if (ext == NULL) {
+		return NULL;
+	}
+	ext->nxt_name = strdup(name);
+	if (ext->nxt_name == NULL) {
+		free(ext);
+		return NULL;
+	}
+	ext->nxt_dict = prop_dictionary_create();
+
+	return ext;
+}
+
+void
+npf_ext_param_u32(nl_ext_t *ext, const char *key, uint32_t val)
+{
+	prop_dictionary_t extdict = ext->nxt_dict;
+	prop_dictionary_set_uint32(extdict, key, val);
+}
+
+void
+npf_ext_param_bool(nl_ext_t *ext, const char *key, bool val)
+{
+	prop_dictionary_t extdict = ext->nxt_dict;
+	prop_dictionary_set_bool(extdict, key, val);
+}
+
+/*
  * RULE INTERFACE.
  */
 
@@ -367,6 +409,7 @@
 
 		subrules = prop_dictionary_get(rldict, "subrules");
 		(void)_npf_rule_foreach1(subrules, nlevel + 1, func);
+		prop_object_release(subrules);
 	}
 	prop_object_iterator_release(it);
 	return 0;
@@ -428,6 +471,7 @@
 npf_rproc_create(const char *name)
 {
 	prop_dictionary_t rpdict;
+	prop_array_t extcalls;
 	nl_rproc_t *nrp;
 
 	nrp = malloc(sizeof(nl_rproc_t));
@@ -440,10 +484,36 @@
 		return NULL;
 	}
 	prop_dictionary_set_cstring(rpdict, "name", name);
+
+	extcalls = prop_array_create();
+	if (extcalls == NULL) {
+		prop_object_release(rpdict);
+		free(nrp);
+		return NULL;
+	}
+	prop_dictionary_set(rpdict, "extcalls", extcalls);
+	prop_object_release(extcalls);
+
 	nrp->nrp_dict = rpdict;
 	return nrp;
 }
 
+int
+npf_rproc_extcall(nl_rproc_t *rp, nl_ext_t *ext)
+{
+	prop_dictionary_t rpdict = rp->nrp_dict;
+	prop_dictionary_t extdict = ext->nxt_dict;
+	prop_array_t extcalls;
+
+	extcalls = prop_dictionary_get(rpdict, "extcalls");
+	if (_npf_prop_array_lookup(extcalls, "name", ext->nxt_name)) {
+		return EEXIST;
+	}
+	prop_dictionary_set_cstring(extdict, "name", ext->nxt_name);
+	prop_array_add(extcalls, extdict);
+	return 0;
+}
+
 bool
 npf_rproc_exists_p(nl_config_t *ncf, const char *name)
 {
@@ -452,36 +522,6 @@
 }
 
 int
-_npf_rproc_setnorm(nl_rproc_t *rp, bool rnd, bool no_df, u_int minttl,
-    u_int maxmss)
-{
-	prop_dictionary_t rpdict = rp->nrp_dict;
-	uint32_t fl = 0;
-
-	prop_dictionary_set_bool(rpdict, "randomize-id", rnd);
-	prop_dictionary_set_bool(rpdict, "no-df", no_df);
-	prop_dictionary_set_uint32(rpdict, "min-ttl", minttl);
-	prop_dictionary_set_uint32(rpdict, "max-mss", maxmss);
-
-	prop_dictionary_get_uint32(rpdict, "flags", &fl);
-	prop_dictionary_set_uint32(rpdict, "flags", fl | NPF_RPROC_NORMALIZE);
-	return 0;
-}
-
-int
-_npf_rproc_setlog(nl_rproc_t *rp, u_int if_idx)
-{
-	prop_dictionary_t rpdict = rp->nrp_dict;
-	uint32_t fl = 0;
-
-	prop_dictionary_set_uint32(rpdict, "log-interface", if_idx);
-
-	prop_dictionary_get_uint32(rpdict, "flags", &fl);
-	prop_dictionary_set_uint32(rpdict, "flags", fl | NPF_RPROC_LOG);
-	return 0;
-}
-
-int
 npf_rproc_insert(nl_config_t *ncf, nl_rproc_t *rp)
 {
 	prop_dictionary_t rpdict = rp->nrp_dict;
--- a/lib/libnpf/npf.h	Sun Sep 16 13:46:49 2012 +0000
+++ b/lib/libnpf/npf.h	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.10 2012/08/12 03:35:14 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.11 2012/09/16 13:47:42 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -35,10 +35,6 @@
 #include <sys/types.h>
 #include <net/npf.h>
 
-#ifdef _NPF_TESTING
-#include "testing.h"
-#endif
-
 __BEGIN_DECLS
 
 struct nl_config;
@@ -53,6 +49,12 @@
 
 typedef struct nl_rule		nl_nat_t;
 
+typedef struct nl_ext		nl_ext_t;
+
+typedef int (*npfext_initfunc_t)(void);
+typedef nl_ext_t *(*npfext_consfunc_t)(const char *);
+typedef int (*npfext_paramfunc_t)(nl_ext_t *, const char *, const char *);
+
 #ifdef _NPF_PRIVATE
 
 typedef struct {
@@ -81,6 +83,10 @@
 nl_config_t *	npf_config_retrieve(int, bool *, bool *);
 int		npf_config_flush(int);
 
+nl_ext_t *	npf_ext_construct(const char *name);
+void		npf_ext_param_u32(nl_ext_t *, const char *, uint32_t);
+void		npf_ext_param_bool(nl_ext_t *, const char *, bool);
+
 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 *);
@@ -89,6 +95,7 @@
 void		npf_rule_destroy(nl_rule_t *);
 
 nl_rproc_t *	npf_rproc_create(const char *);
+int		npf_rproc_extcall(nl_rproc_t *, nl_ext_t *);
 bool		npf_rproc_exists_p(nl_config_t *, const char *);
 int		npf_rproc_insert(nl_config_t *, nl_rproc_t *);
 
@@ -120,8 +127,6 @@
 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);
 
 void		_npf_debug_addif(nl_config_t *, struct ifaddrs *, u_int);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,11 @@
+# $NetBSD: Makefile,v 1.1 2012/09/16 13:47:41 rmind Exp $
+
+.include <bsd.own.mk>
+
+.if ${MKPIC} != "no"
+
+SUBDIR=		ext_log ext_normalise
+
+.endif
+
+.include <bsd.subdir.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/Makefile.inc	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,8 @@
+# $NetBSD: Makefile.inc,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+WARNS=		5
+MKLINT=		no
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_log/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,13 @@
+# $NetBSD: Makefile,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+.include <bsd.own.mk>
+
+LIBISMODULE= yes
+LIBDIR=	/usr/lib/npf
+
+LIB=	ext_log
+
+SRCS=	npfext_log.c
+WARNS=	5
+
+.include <bsd.lib.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_log/npfext_log.c	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,75 @@
+/*	$NetBSD: npfext_log.c,v 1.1 2012/09/16 13:47:42 rmind Exp $	*/
+
+/*-
+ * Copyright (c) 2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Mindaugas Rasiukevicius.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: npfext_log.c,v 1.1 2012/09/16 13:47:42 rmind Exp $");
+
+#include <sys/types.h>
+#include <net/if.h>
+
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <npf.h>
+
+int		npfext_log_init(void);
+nl_ext_t *	npfext_log_construct(const char *);
+int		npfext_log_param(nl_ext_t *, const char *, const char *);
+
+int
+npfext_log_init(void)
+{
+	/* Nothing to initialise. */
+	return 0;
+}
+
+nl_ext_t *
+npfext_log_construct(const char *name)
+{
+	assert(strcmp(name, "log") == 0);
+	return npf_ext_construct(name);
+}
+
+int
+npfext_log_param(nl_ext_t *ext, const char *param, const char *val __unused)
+{
+	unsigned long if_idx;
+
+	assert(param != NULL);
+
+	if_idx = if_nametoindex(param);
+	if (if_idx == 0) {
+		return EINVAL;
+	}
+	npf_ext_param_u32(ext, "log-interface", if_idx);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_log/shlib_version	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,4 @@
+# $NetBSD: shlib_version,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+major=0
+minor=0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_normalise/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,13 @@
+# $NetBSD: Makefile,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+.include <bsd.own.mk>
+
+LIBISMODULE= yes
+LIBDIR=	/usr/lib/npf
+
+LIB=	ext_normalise
+
+SRCS=	npfext_normalise.c
+WARNS=	5
+
+.include <bsd.lib.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_normalise/npfext_normalise.c	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,100 @@
+/*	$NetBSD: npfext_normalise.c,v 1.1 2012/09/16 13:47:42 rmind Exp $	*/
+
+/*-
+ * Copyright (c) 2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: npfext_normalise.c,v 1.1 2012/09/16 13:47:42 rmind Exp $");
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <npf.h>
+
+int		npfext_normalise_init(void);
+nl_ext_t *	npfext_normalise_construct(const char *);
+int		npfext_normalise_param(nl_ext_t *, const char *, const char *);
+
+int
+npfext_normalise_init(void)
+{
+	/* Nothing to initialise. */
+	return 0;
+}
+
+nl_ext_t *
+npfext_normalise_construct(const char *name)
+{
+	assert(strcmp(name, "normalise") == 0);
+	return npf_ext_construct(name);
+}
+
+int
+npfext_normalise_param(nl_ext_t *ext, const char *param, const char *val)
+{
+	enum ptype {
+		PARAM_BOOL,
+		PARAM_U32
+	};
+	static const struct param {
+		const char *	name;
+		enum ptype	type;
+		bool		reqval;
+	} params[] = {
+		{ "random-id",	PARAM_BOOL,	false	},
+		{ "no-df",	PARAM_BOOL,	false	},
+		{ "min-ttl",	PARAM_U32,	true	},
+		{ "max-mss",	PARAM_U32,	true	},
+	};
+
+	for (unsigned i = 0; i < __arraycount(params); i++) {
+		const char *name = params[i].name;
+
+		if (strcmp(name, param) != 0) {
+			continue;
+		}
+		if (val == NULL && params[i].reqval) {
+			return EINVAL;
+		}
+
+		switch (params[i].type) {
+		case PARAM_BOOL:
+			npf_ext_param_bool(ext, name, true);
+			break;
+		case PARAM_U32:
+			npf_ext_param_u32(ext, name, atol(val));
+			break;
+		default:
+			assert(false);
+		}
+		return 0;
+	}
+
+	/* Invalid parameter, if not found. */
+	return EINVAL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/npf/ext_normalise/shlib_version	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,4 @@
+# $NetBSD: shlib_version,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+major=0
+minor=0
--- a/sys/modules/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/modules/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.110 2012/08/06 10:31:41 martin Exp $
+#	$NetBSD: Makefile,v 1.111 2012/09/16 13:47:41 rmind Exp $
 
 .include <bsd.own.mk>
 
@@ -48,6 +48,8 @@
 SUBDIR+=	nilfs
 SUBDIR+=	npf
 SUBDIR+=	npf_alg_icmp
+SUBDIR+=	npf_ext_log
+SUBDIR+=	npf_ext_normalise
 SUBDIR+=	ntfs
 SUBDIR+=	null
 SUBDIR+=	onewire
--- a/sys/modules/npf/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/modules/npf/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.10 2012/08/12 03:35:14 rmind Exp $
+# $NetBSD: Makefile,v 1.11 2012/09/16 13:47:42 rmind Exp $
 
 .include "../Makefile.inc"
 
@@ -7,7 +7,7 @@
 KMOD=		npf
 
 SRCS=		npf.c npf_alg.c npf_ctl.c npf_handler.c
-SRCS+=		npf_inet.c npf_instr.c npf_log.c npf_mbuf.c npf_nat.c
+SRCS+=		npf_inet.c npf_instr.c npf_mbuf.c npf_nat.c
 SRCS+=		npf_processor.c npf_ruleset.c npf_rproc.c npf_sendpkt.c
 SRCS+=		npf_session.c npf_state.c npf_state_tcp.c
 SRCS+=		npf_tableset.c npf_tableset_ptree.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/modules/npf_ext_log/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,11 @@
+# $NetBSD: Makefile,v 1.1 2012/09/16 13:47:42 rmind Exp $
+
+.include "../Makefile.inc"
+
+.PATH:		${S}/net/npf
+
+KMOD=		npf_ext_log
+
+SRCS=		npf_ext_log.c
+
+.include <bsd.kmodule.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/modules/npf_ext_normalise/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,11 @@
+# $NetBSD: Makefile,v 1.1 2012/09/16 13:47:43 rmind Exp $
+
+.include "../Makefile.inc"
+
+.PATH:		${S}/net/npf
+
+KMOD=		npf_ext_normalise
+
+SRCS=		npf_ext_normalise.c
+
+.include <bsd.kmodule.mk>
--- a/sys/net/npf/files.npf	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/files.npf	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: files.npf,v 1.7 2012/07/15 00:22:59 rmind Exp $
+# $NetBSD: files.npf,v 1.8 2012/09/16 13:47:41 rmind Exp $
 #
 # Public Domain.
 #
@@ -27,7 +27,10 @@
 file	net/npf/npf_nat.c			npf
 file	net/npf/npf_alg.c			npf
 file	net/npf/npf_sendpkt.c			npf
-file	net/npf/npf_log.c			npf
+
+# Built-in extensions.
+file	net/npf/npf_ext_log.c			npf
+file	net/npf/npf_ext_normalise.c		npf
 
 # ALGs
 file	net/npf/npf_alg_icmp.c			npf
--- a/sys/net/npf/npf.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf.c,v 1.12 2012/07/15 00:23:00 rmind Exp $	*/
+/*	$NetBSD: npf.c,v 1.13 2012/09/16 13:47:41 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.c,v 1.12 2012/07/15 00:23:00 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.13 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -107,7 +107,7 @@
 	npf_session_sysinit();
 	npf_nat_sysinit();
 	npf_alg_sysinit();
-	npflogattach(1);
+	npf_ext_sysinit();
 
 	/* Load empty configuration. */
 	dict = prop_dictionary_create();
@@ -136,7 +136,6 @@
 #ifdef _MODULE
 	devsw_detach(NULL, &npf_cdevsw);
 #endif
-	npflogdetach();
 	npf_pfil_unregister();
 
 	/* Flush all sessions, destroy configuration (ruleset, etc). */
@@ -144,6 +143,7 @@
 	npf_core_destroy(npf_core);
 
 	/* Finally, safe to destroy the subsystems. */
+	npf_ext_sysfini();
 	npf_alg_sysfini();
 	npf_nat_sysfini();
 	npf_session_sysfini();
@@ -171,7 +171,7 @@
 	case MODULE_CMD_FINI:
 		return npf_fini();
 	case MODULE_CMD_AUTOUNLOAD:
-		if (npf_pfil_registered_p() || !npf_default_pass()) {
+		if (npf_autounload_p()) {
 			return EBUSY;
 		}
 		break;
@@ -370,6 +370,12 @@
 	return npf_core->n_default_pass;
 }
 
+bool
+npf_autounload_p(void)
+{
+	return !npf_pfil_registered_p() && npf_default_pass();
+}
+
 /*
  * NPF statistics interface.
  */
--- a/sys/net/npf/npf.h	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf.h	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.20 2012/07/19 21:52:29 spz Exp $	*/
+/*	$NetBSD: npf.h,v 1.21 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -45,7 +45,7 @@
 #include <netinet/in_systm.h>
 #include <netinet/in.h>
 
-#define	NPF_VERSION		5
+#define	NPF_VERSION		6
 
 /*
  * Public declarations and definitions.
@@ -60,11 +60,11 @@
 
 #if defined(_KERNEL)
 
-/* Network buffer. */
-typedef void			nbuf_t;
+#define	NPF_DECISION_BLOCK	0
+#define	NPF_DECISION_PASS	1
 
-struct npf_rproc;
-typedef struct npf_rproc	npf_rproc_t;
+#define	NPF_EXT_MODULE(name, req)	\
+    MODULE(MODULE_CLASS_MISC, name, "npf," req)
 
 /*
  * Packet information cache.
@@ -133,7 +133,12 @@
 	return npc->npc_hlen;
 }
 
-/* Network buffer interface. */
+/*
+ * Network buffer interface.
+ */
+
+typedef void	nbuf_t;
+
 void *		nbuf_dataptr(void *);
 void *		nbuf_advance(nbuf_t **, void *, u_int);
 int		nbuf_advfetch(nbuf_t **, void **, u_int, size_t, void *);
@@ -144,6 +149,32 @@
 int		nbuf_add_tag(nbuf_t *, uint32_t, uint32_t);
 int		nbuf_find_tag(nbuf_t *, uint32_t, void **);
 
+/*
+ * NPF extensions and rule procedure interface.
+ */
+
+struct npf_rproc;
+typedef struct npf_rproc	npf_rproc_t;
+
+void		npf_rproc_assign(npf_rproc_t *, void *);
+
+typedef struct {
+	unsigned int	version;
+	void *		ctx;
+	int		(*ctor)(npf_rproc_t *, prop_dictionary_t);
+	void		(*dtor)(npf_rproc_t *, void *);
+	void		(*proc)(npf_cache_t *, nbuf_t *, void *, int *);
+} npf_ext_ops_t;
+
+void *		npf_ext_register(const char *, const npf_ext_ops_t *);
+int		npf_ext_unregister(void *);
+
+/*
+ * Misc.
+ */
+
+bool		npf_autounload_p(void);
+
 #endif	/* _KERNEL */
 
 /* Rule attributes. */
@@ -158,10 +189,6 @@
 #define	NPF_RULE_OUT			0x20000000
 #define	NPF_RULE_DIMASK			(NPF_RULE_IN | NPF_RULE_OUT)
 
-/* Rule procedure flags. */
-#define	NPF_RPROC_LOG			0x0001
-#define	NPF_RPROC_NORMALIZE		0x0002
-
 /* Address translation types and flags. */
 #define	NPF_NATIN			1
 #define	NPF_NATOUT			2
@@ -216,9 +243,6 @@
 	/* Raced packets. */
 	NPF_STAT_RACE_SESSION,
 	NPF_STAT_RACE_NAT,
-	/* Rule procedure cases. */
-	NPF_STAT_RPROC_LOG,
-	NPF_STAT_RPROC_NORM,
 	/* Fragments. */
 	NPF_STAT_FRAGMENTS,
 	NPF_STAT_REASSEMBLY,
--- a/sys/net/npf/npf_ctl.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf_ctl.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_ctl.c,v 1.17 2012/08/15 18:44:56 rmind Exp $	*/
+/*	$NetBSD: npf_ctl.c,v 1.18 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -37,7 +37,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.17 2012/08/15 18:44:56 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.18 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/conf.h>
@@ -162,29 +162,51 @@
 npf_mk_rproc(prop_array_t rprocs, const char *rpname)
 {
 	prop_object_iterator_t it;
-	prop_dictionary_t rpdict;
+	prop_dictionary_t rpdict, extdict;
+	prop_array_t extlist;
 	npf_rproc_t *rp;
+	const char *name;
 	uint64_t rpval;
 
 	it = prop_array_iterator(rprocs);
 	while ((rpdict = prop_object_iterator_next(it)) != NULL) {
-		const char *iname;
-		prop_dictionary_get_cstring_nocopy(rpdict, "name", &iname);
-		KASSERT(iname != NULL);
-		if (strcmp(rpname, iname) == 0)
+		prop_dictionary_get_cstring_nocopy(rpdict, "name", &name);
+		KASSERT(name != NULL);
+		if (strcmp(rpname, name) == 0)
 			break;
 	}
 	prop_object_iterator_release(it);
-	if (rpdict == NULL) {
+	if (!rpdict) {
 		return NULL;
 	}
 	CTASSERT(sizeof(uintptr_t) <= sizeof(uint64_t));
-	if (!prop_dictionary_get_uint64(rpdict, "rproc-ptr", &rpval)) {
-		rp = npf_rproc_create(rpdict);
+	if (prop_dictionary_get_uint64(rpdict, "rproc-ptr", &rpval)) {
+		return (npf_rproc_t *)(uintptr_t)rpval;
+	}
+
+	extlist = prop_dictionary_get(rpdict, "extcalls");
+	if (prop_object_type(extlist) != PROP_TYPE_ARRAY) {
+		return NULL;
+	}
+
+	rp = npf_rproc_create(rpdict);
+	if (!rp) {
+		return NULL;
+	}
+	it = prop_array_iterator(extlist);
+	while ((extdict = prop_object_iterator_next(it)) != NULL) {
+		if (!prop_dictionary_get_cstring_nocopy(extdict,
+		    "name", &name) || npf_ext_construct(name, rp, extdict)) {
+			npf_rproc_release(rp);
+			rp = NULL;
+			break;
+		}
+	}
+	prop_object_iterator_release(it);
+
+	if (rp) {
 		rpval = (uint64_t)(uintptr_t)rp;
 		prop_dictionary_set_uint64(rpdict, "rproc-ptr", rpval);
-	} else {
-		rp = (npf_rproc_t *)(uintptr_t)rpval;
 	}
 	return rp;
 }
@@ -242,6 +264,18 @@
 		return EINVAL;
 	}
 
+	/* Make the rule procedure, if any. */
+	if (rps && prop_dictionary_get_cstring_nocopy(rldict, "rproc", &rnm)) {
+		rp = npf_mk_rproc(rps, rnm);
+		if (rp == NULL) {
+			NPF_ERR_DEBUG(errdict);
+			error = EINVAL;
+			goto err;
+		}
+	} else {
+		rp = NULL;
+	}
+
 	error = 0;
 	obj = prop_dictionary_get(rldict, "ncode");
 	if (obj) {
@@ -256,26 +290,14 @@
 		nc_size = 0;
 	}
 
-	/* Check for rule procedure. */
-	if (rps && prop_dictionary_get_cstring_nocopy(rldict, "rproc", &rnm)) {
-		rp = npf_mk_rproc(rps, rnm);
-		if (rp == NULL) {
-			if (nc) {
-				npf_ncode_free(nc, nc_size);	/* XXX */
-			}
-			NPF_ERR_DEBUG(errdict);
-			error = EINVAL;
-			goto err;
-		}
-	} else {
-		rp = NULL;
-	}
-
 	/* Finally, allocate and return the rule. */
 	*rl = npf_rule_alloc(rldict, rp, nc, nc_size);
 	KASSERT(*rl != NULL);
 	return 0;
 err:
+	if (rp) {
+		npf_rproc_release(rp);
+	}
 	prop_dictionary_get_int32(rldict, "priority", &p); /* XXX */
 	prop_dictionary_set_int32(errdict, "id", p);
 	return error;
@@ -511,12 +533,13 @@
 	}
 
 	/* Error report. */
+#ifndef _NPF_TESTING
 	prop_dictionary_set_int32(errdict, "errno", error);
-#ifndef _NPF_TESTING
 	prop_dictionary_copyout_ioctl(pref, cmd, errdict);
+	prop_object_release(errdict);
+	error = 0;
 #endif
-	prop_object_release(errdict);
-	return 0;
+	return error;
 }
 
 int
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/net/npf/npf_ext_log.c	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,254 @@
+/*	$NetBSD: npf_ext_log.c,v 1.1 2012/09/16 13:47:41 rmind Exp $	*/
+
+/*-
+ * Copyright (c) 2010-2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This material is based upon work partially supported by The
+ * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * NPF logging extension.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: npf_ext_log.c,v 1.1 2012/09/16 13:47:41 rmind Exp $");
+
+#include <sys/types.h>
+#include <sys/module.h>
+
+#include <sys/conf.h>
+#include <sys/kmem.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/bpf.h>
+
+#include "npf_impl.h"
+
+NPF_EXT_MODULE(npf_ext_log, "");
+
+#define	NPFEXT_LOG_VER		1
+
+static void *		npf_ext_log_id;
+
+typedef struct {
+	unsigned int	if_idx;
+} npf_ext_log_t;
+
+typedef struct npflog_softc {
+	LIST_ENTRY(npflog_softc)	sc_entry;
+	kmutex_t			sc_lock;
+	ifnet_t				sc_if;
+	int				sc_unit;
+} npflog_softc_t;
+
+static int	npflog_clone_create(struct if_clone *, int);
+static int	npflog_clone_destroy(ifnet_t *);
+
+static LIST_HEAD(, npflog_softc)	npflog_if_list	__cacheline_aligned;
+static struct if_clone			npflog_cloner =
+    IF_CLONE_INITIALIZER("npflog", npflog_clone_create, npflog_clone_destroy);
+
+void
+npflogattach(int nunits)
+{
+
+	LIST_INIT(&npflog_if_list);
+	if_clone_attach(&npflog_cloner);
+}
+
+void
+npflogdetach(void)
+{
+	npflog_softc_t *sc;
+
+	while ((sc = LIST_FIRST(&npflog_if_list)) != NULL) {
+		npflog_clone_destroy(&sc->sc_if);
+	}
+	if_clone_detach(&npflog_cloner);
+}
+
+static int
+npflog_ioctl(ifnet_t *ifp, u_long cmd, void *data)
+{
+	npflog_softc_t *sc = ifp->if_softc;
+	int error = 0;
+
+	mutex_enter(&sc->sc_lock);
+	switch (cmd) {
+	case SIOCINITIFADDR:
+		ifp->if_flags |= (IFF_UP | IFF_RUNNING);
+		break;
+	default:
+		error = ifioctl_common(ifp, cmd, data);
+		break;
+	}
+	mutex_exit(&sc->sc_lock);
+	return error;
+}
+
+static int
+npflog_clone_create(struct if_clone *ifc, int unit)
+{
+	npflog_softc_t *sc;
+	ifnet_t *ifp;
+
+	sc = kmem_zalloc(sizeof(npflog_softc_t), KM_SLEEP);
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTNET);
+
+	ifp = &sc->sc_if;
+	ifp->if_softc = sc;
+
+	if_initname(ifp, "npflog", unit);
+	ifp->if_type = IFT_OTHER;
+	ifp->if_dlt = DLT_NULL;
+	ifp->if_ioctl = npflog_ioctl;
+
+	KERNEL_LOCK(1, NULL);
+	if_attach(ifp);
+	if_alloc_sadl(ifp);
+	bpf_attach(ifp, DLT_NULL, 0);
+	LIST_INSERT_HEAD(&npflog_if_list, sc, sc_entry);
+	KERNEL_UNLOCK_ONE(NULL);
+
+	return 0;
+}
+
+static int
+npflog_clone_destroy(ifnet_t *ifp)
+{
+	npflog_softc_t *sc = ifp->if_softc;
+
+	KERNEL_LOCK(1, NULL);
+	LIST_REMOVE(sc, sc_entry);
+	bpf_detach(ifp);
+	if_detach(ifp);
+	KERNEL_UNLOCK_ONE(NULL);
+
+	mutex_destroy(&sc->sc_lock);
+	kmem_free(sc, sizeof(npflog_softc_t));
+	return 0;
+}
+
+static int
+npf_log_ctor(npf_rproc_t *rp, prop_dictionary_t params)
+{
+	npf_ext_log_t *meta;
+
+	meta = kmem_zalloc(sizeof(npf_ext_log_t), KM_SLEEP);
+	prop_dictionary_get_uint32(params, "log-interface", &meta->if_idx);
+	npf_rproc_assign(rp, meta);
+	return 0;
+}
+
+static void
+npf_log_dtor(npf_rproc_t *rp, void *meta)
+{
+	kmem_free(meta, sizeof(npf_ext_log_t));
+}
+
+static void
+npf_log(npf_cache_t *npc, nbuf_t *nbuf, void *meta, int *decision)
+{
+	const npf_ext_log_t *log = meta;
+	struct mbuf *m = nbuf;
+	ifnet_t *ifp;
+	int family;
+
+	/* Set the address family. */
+	if (npf_iscached(npc, NPC_IP4)) {
+		family = AF_INET;
+	} else if (npf_iscached(npc, NPC_IP6)) {
+		family = AF_INET6;
+	} else {
+		family = AF_UNSPEC;
+	}
+
+	KERNEL_LOCK(1, NULL);
+
+	/* Find a pseudo-interface to log. */
+	ifp = if_byindex(log->if_idx);
+	if (ifp == NULL) {
+		/* No interface. */
+		KERNEL_UNLOCK_ONE(NULL);
+		return;
+	}
+
+	/* Pass through BPF. */
+	ifp->if_opackets++;
+	ifp->if_obytes += m->m_pkthdr.len;
+	bpf_mtap_af(ifp, family, m);
+	KERNEL_UNLOCK_ONE(NULL);
+}
+
+/*
+ * Module interface.
+ */
+static int
+npf_ext_log_modcmd(modcmd_t cmd, void *arg)
+{
+	static const npf_ext_ops_t npf_log_ops = {
+		.version	= NPFEXT_LOG_VER,
+		.ctx		= NULL,
+		.ctor		= npf_log_ctor,
+		.dtor		= npf_log_dtor,
+		.proc		= npf_log
+	};
+	int error;
+
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+		/*
+		 * Initialise the NPF logging extension.
+		 */
+		npflogattach(1);
+		npf_ext_log_id = npf_ext_register("log", &npf_log_ops);
+		if (!npf_ext_log_id) {
+			npflogdetach();
+			return EEXIST;
+		}
+		break;
+
+	case MODULE_CMD_FINI:
+		error = npf_ext_unregister(npf_ext_log_id);
+		if (error) {
+			return error;
+		}
+		npflogdetach();
+		break;
+
+	case MODULE_CMD_AUTOUNLOAD:
+		/* Allow auto-unload only if NPF permits it. */
+		return npf_autounload_p() ? 0 : EBUSY;
+
+	default:
+		return ENOTTY;
+	}
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/net/npf/npf_ext_normalise.c	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,255 @@
+/*	$NetBSD: npf_ext_normalise.c,v 1.1 2012/09/16 13:47:41 rmind Exp $	*/
+
+/*-
+ * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: npf_ext_normalise.c,v 1.1 2012/09/16 13:47:41 rmind Exp $");
+
+#include <sys/types.h>
+#include <sys/module.h>
+#include <sys/kmem.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+
+#include "npf.h"
+#include "npf_impl.h"
+
+/*
+ * NPF extension module definition and the identifier.
+ */
+NPF_EXT_MODULE(npf_ext_normalise, "");
+
+#define	NPFEXT_NORMALISE_VER	1
+
+static void *		npf_ext_normalise_id;
+
+/*
+ * Normalisation parameters.
+ */
+typedef struct {
+	u_int		n_minttl;
+	u_int		n_maxmss;
+	bool		n_random_id;
+	bool		n_no_df;
+} npf_normalise_t;
+
+/*
+ * npf_normalise_ctor: a constructor for the normalisation rule procedure
+ * with the given parameters.
+ */
+static int
+npf_normalise_ctor(npf_rproc_t *rp, prop_dictionary_t params)
+{
+	npf_normalise_t *np;
+
+	/* Create a structure for normalisation parameters. */
+	np = kmem_zalloc(sizeof(npf_normalise_t), KM_SLEEP);
+
+	/* IP ID randomisation and IP_DF flag cleansing. */
+	prop_dictionary_get_bool(params, "random-id", &np->n_random_id);
+	prop_dictionary_get_bool(params, "no-df", &np->n_no_df);
+
+	/* Minimum IP TTL and maximum TCP MSS. */
+	prop_dictionary_get_uint32(params, "min-ttl", &np->n_minttl);
+	prop_dictionary_get_uint32(params, "max-mss", &np->n_maxmss);
+
+	/* Assign the parameters for this rule procedure. */
+	npf_rproc_assign(rp, np);
+	return 0;
+}
+
+/*
+ * npf_normalise_dtor: a destructor for a normalisation rule procedure.
+ */
+static void
+npf_normalise_dtor(npf_rproc_t *rp, void *params)
+{
+	/* Free our meta-data, associated with the procedure. */
+	kmem_free(params, sizeof(npf_normalise_t));
+}
+
+/*
+ * npf_normalise_ip4: routine to normalise IPv4 header (randomise ID,
+ * clear "don't fragment" and/or enforce minimum TTL).
+ */
+static inline bool
+npf_normalise_ip4(npf_cache_t *npc, nbuf_t *nbuf, npf_normalise_t *np)
+{
+	void *n_ptr = nbuf_dataptr(nbuf);
+	struct ip *ip = &npc->npc_ip.v4;
+	uint16_t cksum = ip->ip_sum;
+	uint16_t ip_off = ip->ip_off;
+	uint8_t ttl = ip->ip_ttl;
+	u_int minttl = np->n_minttl;
+	u_int offby = 0;
+
+	KASSERT(np->n_random_id || np->n_no_df || minttl);
+
+	/* Randomise IPv4 ID. */
+	if (np->n_random_id) {
+		uint16_t oid = ip->ip_id, nid;
+
+		nid = htons(ip_randomid(ip_ids, 0));
+		offby = offsetof(struct ip, ip_id);
+		if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(nid), &nid)) {
+			return false;
+		}
+		cksum = npf_fixup16_cksum(cksum, oid, nid);
+		ip->ip_id = nid;
+	}
+
+	/* IP_DF flag cleansing. */
+	if (np->n_no_df && (ip_off & htons(IP_DF)) != 0) {
+		uint16_t nip_off = ip_off & ~htons(IP_DF);
+
+		if (nbuf_advstore(&nbuf, &n_ptr,
+		    offsetof(struct ip, ip_off) - offby,
+		    sizeof(uint16_t), &nip_off)) {
+			return false;
+		}
+		cksum = npf_fixup16_cksum(cksum, ip_off, nip_off);
+		ip->ip_off = nip_off;
+		offby = offsetof(struct ip, ip_off);
+	}
+
+	/* Enforce minimum TTL. */
+	if (minttl && ttl < minttl) {
+		if (nbuf_advstore(&nbuf, &n_ptr,
+		    offsetof(struct ip, ip_ttl) - offby,
+		    sizeof(uint8_t), &minttl)) {
+			return false;
+		}
+		cksum = npf_fixup16_cksum(cksum, ttl, minttl);
+		ip->ip_ttl = minttl;
+		offby = offsetof(struct ip, ip_ttl);
+	}
+
+	/* Update IPv4 checksum. */
+	offby = offsetof(struct ip, ip_sum) - offby;
+	if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(cksum), &cksum)) {
+		return false;
+	}
+	ip->ip_sum = cksum;
+	return true;
+}
+
+/*
+ * npf_normalise: the main routine to normalise IPv4 and/or TCP headers.
+ */
+static void
+npf_normalise(npf_cache_t *npc, nbuf_t *nbuf, void *params, int *decision)
+{
+	npf_normalise_t *np = params;
+	void *n_ptr = nbuf_dataptr(nbuf);
+	struct tcphdr *th = &npc->npc_l4.tcp;
+	u_int offby, maxmss = np->n_maxmss;
+	uint16_t cksum, mss;
+	int wscale;
+
+	/* Skip, if already blocking. */
+	if (*decision == NPF_DECISION_BLOCK) {
+		return;
+	}
+
+	/* Normalise IPv4. */
+	if (npf_iscached(npc, NPC_IP4) && (np->n_random_id || np->n_minttl)) {
+		if (!npf_normalise_ip4(npc, nbuf, np)) {
+			return;
+		}
+	} else if (!npf_iscached(npc, NPC_IP6)) {
+		/* If not IPv6, then nothing to do. */
+		return;
+	}
+
+	/*
+	 * TCP Maximum Segment Size (MSS) "clamping".  Only if SYN packet.
+	 * Fetch MSS and check whether rewrite to lower is needed.
+	 */
+	if (maxmss == 0 || !npf_iscached(npc, NPC_TCP) ||
+	    (th->th_flags & TH_SYN) == 0) {
+		/* Not required; done. */
+		return;
+	}
+	mss = 0;
+	if (!npf_fetch_tcpopts(npc, nbuf, &mss, &wscale)) {
+		return;
+	}
+	if (ntohs(mss) <= maxmss) {
+		/* Nothing else to do. */
+		return;
+	}
+
+	/* Calculate TCP checksum, then rewrite MSS and the checksum. */
+	maxmss = htons(maxmss);
+	cksum = npf_fixup16_cksum(th->th_sum, mss, maxmss);
+	th->th_sum = cksum;
+	mss = maxmss;
+	if (!npf_fetch_tcpopts(npc, nbuf, &mss, &wscale)) {
+		return;
+	}
+	offby = npf_cache_hlen(npc) + offsetof(struct tcphdr, th_sum);
+	if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(cksum), &cksum)) {
+		return;
+	}
+}
+
+static int
+npf_ext_normalise_modcmd(modcmd_t cmd, void *arg)
+{
+	static const npf_ext_ops_t npf_normalise_ops = {
+		.version	= NPFEXT_NORMALISE_VER,
+		.ctx		= NULL,
+		.ctor		= npf_normalise_ctor,
+		.dtor		= npf_normalise_dtor,
+		.proc		= npf_normalise
+	};
+
+	switch (cmd) {
+	case MODULE_CMD_INIT:
+		/*
+		 * Initialise normalisation module.  Register the "normalise"
+		 * extension and its calls.
+		 */
+		npf_ext_normalise_id =
+		    npf_ext_register("normalise", &npf_normalise_ops);
+		return npf_ext_normalise_id ? 0 : EEXIST;
+
+	case MODULE_CMD_FINI:
+		/* Unregister the normalisation rule procedure. */
+		return npf_ext_unregister(npf_ext_normalise_id);
+
+	case MODULE_CMD_AUTOUNLOAD:
+		return npf_autounload_p() ? 0 : EBUSY;
+
+	default:
+		return ENOTTY;
+	}
+	return 0;
+}
--- a/sys/net/npf/npf_handler.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf_handler.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_handler.c,v 1.21 2012/08/12 03:35:14 rmind Exp $	*/
+/*	$NetBSD: npf_handler.c,v 1.22 2012/09/16 13:47:41 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.21 2012/08/12 03:35:14 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_handler.c,v 1.22 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/param.h>
@@ -207,10 +207,11 @@
 	error = npf_do_nat(&npc, se, nbuf, ifp, di);
 block:
 	/*
-	 * Execute rule procedure, if any.
+	 * Execute the rule procedure, if any is associated.
+	 * It may reverse the decision from pass to block.
 	 */
 	if (rp) {
-		npf_rproc_run(&npc, nbuf, rp, error);
+		npf_rproc_run(&npc, nbuf, rp, &decision);
 	}
 out:
 	/*
--- a/sys/net/npf/npf_impl.h	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf_impl.h	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_impl.h,v 1.22 2012/08/15 19:47:38 rmind Exp $	*/
+/*	$NetBSD: npf_impl.h,v 1.23 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -94,9 +94,6 @@
  * DEFINITIONS.
  */
 
-#define	NPF_DECISION_BLOCK	0
-#define	NPF_DECISION_PASS	1
-
 typedef bool (*npf_algfunc_t)(npf_cache_t *, nbuf_t *, void *);
 
 #define	NPF_NCODE_LIMIT		1024
@@ -157,7 +154,6 @@
 void		npf_pfil_unregister(void);
 bool		npf_pfil_registered_p(void);
 int		npf_packet_handler(void *, struct mbuf **, ifnet_t *, int);
-void		npf_log_packet(npf_cache_t *, nbuf_t *, int);
 
 /* Protocol helpers. */
 bool		npf_fetch_ip(npf_cache_t *, nbuf_t *, void *);
@@ -186,7 +182,6 @@
 		    uint32_t *);
 bool		npf_fetch_tcpopts(const npf_cache_t *, nbuf_t *,
 		    uint16_t *, int *);
-bool		npf_normalize(npf_cache_t *, nbuf_t *, bool, bool, u_int, u_int);
 bool		npf_return_block(npf_cache_t *, nbuf_t *, const int);
 
 /* Complex instructions. */
@@ -252,10 +247,15 @@
 void		npf_rule_setnat(npf_rule_t *, npf_natpolicy_t *);
 npf_rproc_t *	npf_rule_getrproc(npf_rule_t *);
 
+void		npf_ext_sysinit(void);
+void		npf_ext_sysfini(void);
+int		npf_ext_construct(const char *,
+		    npf_rproc_t *, prop_dictionary_t);
+
 npf_rproc_t *	npf_rproc_create(prop_dictionary_t);
 void		npf_rproc_acquire(npf_rproc_t *);
 void		npf_rproc_release(npf_rproc_t *);
-void		npf_rproc_run(npf_cache_t *, nbuf_t *, npf_rproc_t *, int);
+void		npf_rproc_run(npf_cache_t *, nbuf_t *, npf_rproc_t *, int *);
 
 /* Session handling interface. */
 void		npf_session_sysinit(void);
--- a/sys/net/npf/npf_inet.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf_inet.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_inet.c,v 1.16 2012/07/21 17:11:01 rmind Exp $	*/
+/*	$NetBSD: npf_inet.c,v 1.17 2012/09/16 13:47:41 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.16 2012/07/21 17:11:01 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.17 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -51,7 +51,6 @@
 
 #include <netinet/in_systm.h>
 #include <netinet/in.h>
-#include <netinet/in_var.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
 #include <netinet/tcp.h>
@@ -660,119 +659,6 @@
 	return true;
 }
 
-static inline bool
-npf_normalize_ip4(npf_cache_t *npc, nbuf_t *nbuf,
-    bool rnd, bool no_df, int minttl)
-{
-	void *n_ptr = nbuf_dataptr(nbuf);
-	struct ip *ip = &npc->npc_ip.v4;
-	uint16_t cksum = ip->ip_sum;
-	uint16_t ip_off = ip->ip_off;
-	uint8_t ttl = ip->ip_ttl;
-	u_int offby = 0;
-
-	KASSERT(rnd || minttl || no_df);
-
-	/* Randomize IPv4 ID. */
-	if (rnd) {
-		uint16_t oid = ip->ip_id, nid;
-
-		nid = htons(ip_randomid(ip_ids, 0));
-		offby = offsetof(struct ip, ip_id);
-		if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(nid), &nid)) {
-			return false;
-		}
-		cksum = npf_fixup16_cksum(cksum, oid, nid);
-		ip->ip_id = nid;
-	}
-
-	/* IP_DF flag cleansing. */
-	if (no_df && (ip_off & htons(IP_DF)) != 0) {
-		uint16_t nip_off = ip_off & ~htons(IP_DF);
-
-		if (nbuf_advstore(&nbuf, &n_ptr,
-		    offsetof(struct ip, ip_off) - offby,
-		    sizeof(uint16_t), &nip_off)) {
-			return false;
-		}
-		cksum = npf_fixup16_cksum(cksum, ip_off, nip_off);
-		ip->ip_off = nip_off;
-		offby = offsetof(struct ip, ip_off);
-	}
-
-	/* Enforce minimum TTL. */
-	if (minttl && ttl < minttl) {
-		if (nbuf_advstore(&nbuf, &n_ptr,
-		    offsetof(struct ip, ip_ttl) - offby,
-		    sizeof(uint8_t), &minttl)) {
-			return false;
-		}
-		cksum = npf_fixup16_cksum(cksum, ttl, minttl);
-		ip->ip_ttl = minttl;
-		offby = offsetof(struct ip, ip_ttl);
-	}
-
-	/* Update IP checksum. */
-	offby = offsetof(struct ip, ip_sum) - offby;
-	if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(cksum), &cksum)) {
-		return false;
-	}
-	ip->ip_sum = cksum;
-	return true;
-}
-
-bool
-npf_normalize(npf_cache_t *npc, nbuf_t *nbuf,
-    bool no_df, bool rnd, u_int minttl, u_int maxmss)
-{
-	void *n_ptr = nbuf_dataptr(nbuf);
-	struct tcphdr *th = &npc->npc_l4.tcp;
-	uint16_t cksum, mss;
-	u_int offby;
-	int wscale;
-
-	/* Normalize IPv4. */
-	if (npf_iscached(npc, NPC_IP4) && (rnd || minttl)) {
-		if (!npf_normalize_ip4(npc, nbuf, rnd, no_df, minttl)) {
-			return false;
-		}
-	} else if (!npf_iscached(npc, NPC_IP4)) {
-		/* XXX: no IPv6 */
-		return false;
-	}
-
-	/*
-	 * TCP Maximum Segment Size (MSS) "clamping".  Only if SYN packet.
-	 * Fetch MSS and check whether rewrite to lower is needed.
-	 */
-	if (maxmss == 0 || !npf_iscached(npc, NPC_TCP) ||
-	    (th->th_flags & TH_SYN) == 0) {
-		/* Not required; done. */
-		return true;
-	}
-	mss = 0;
-	if (!npf_fetch_tcpopts(npc, nbuf, &mss, &wscale)) {
-		return false;
-	}
-	if (ntohs(mss) <= maxmss) {
-		return true;
-	}
-
-	/* Calculate TCP checksum, then rewrite MSS and the checksum. */
-	maxmss = htons(maxmss);
-	cksum = npf_fixup16_cksum(th->th_sum, mss, maxmss);
-	th->th_sum = cksum;
-	mss = maxmss;
-	if (!npf_fetch_tcpopts(npc, nbuf, &mss, &wscale)) {
-		return false;
-	}
-	offby = npf_cache_hlen(npc) + offsetof(struct tcphdr, th_sum);
-	if (nbuf_advstore(&nbuf, &n_ptr, offby, sizeof(cksum), &cksum)) {
-		return false;
-	}
-	return true;
-}
-
 #if defined(DDB) || defined(_NPF_TESTING)
 
 void
--- a/sys/net/npf/npf_log.c	Sun Sep 16 13:46:49 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/*	$NetBSD: npf_log.c,v 1.4 2012/06/22 13:43:17 rmind Exp $	*/
-
-/*-
- * Copyright (c) 2010-2011 The NetBSD Foundation, Inc.
- * All rights reserved.
- *
- * This material is based upon work partially supported by The
- * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
- */
-
-/*
- * NPF logging interface.
- */
-
-#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_log.c,v 1.4 2012/06/22 13:43:17 rmind Exp $");
-
-#include <sys/param.h>
-#include <sys/types.h>
-
-#include <sys/conf.h>
-#include <sys/kmem.h>
-#include <sys/mbuf.h>
-#include <sys/mutex.h>
-#include <sys/queue.h>
-
-#include <net/if.h>
-#include <net/if_types.h>
-#include <net/bpf.h>
-
-#include "npf_impl.h"
-
-typedef struct npflog_softc {
-	LIST_ENTRY(npflog_softc)	sc_entry;
-	kmutex_t			sc_lock;
-	ifnet_t				sc_if;
-	int				sc_unit;
-} npflog_softc_t;
-
-static int	npflog_clone_create(struct if_clone *, int);
-static int	npflog_clone_destroy(ifnet_t *);
-
-static LIST_HEAD(, npflog_softc)	npflog_if_list	__cacheline_aligned;
-static struct if_clone			npflog_cloner =
-    IF_CLONE_INITIALIZER("npflog", npflog_clone_create, npflog_clone_destroy);
-
-void
-npflogattach(int nunits)
-{
-
-	LIST_INIT(&npflog_if_list);
-	if_clone_attach(&npflog_cloner);
-}
-
-void
-npflogdetach(void)
-{
-	npflog_softc_t *sc;
-
-	while ((sc = LIST_FIRST(&npflog_if_list)) != NULL) {
-		npflog_clone_destroy(&sc->sc_if);
-	}
-	if_clone_detach(&npflog_cloner);
-}
-
-static int
-npflog_ioctl(ifnet_t *ifp, u_long cmd, void *data)
-{
-	npflog_softc_t *sc = ifp->if_softc;
-	int error = 0;
-
-	mutex_enter(&sc->sc_lock);
-	switch (cmd) {
-	case SIOCINITIFADDR:
-		ifp->if_flags |= (IFF_UP | IFF_RUNNING);
-		break;
-	default:
-		error = ifioctl_common(ifp, cmd, data);
-		break;
-	}
-	mutex_exit(&sc->sc_lock);
-	return error;
-}
-
-static int
-npflog_clone_create(struct if_clone *ifc, int unit)
-{
-	npflog_softc_t *sc;
-	ifnet_t *ifp;
-
-	sc = kmem_zalloc(sizeof(npflog_softc_t), KM_SLEEP);
-	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTNET);
-
-	ifp = &sc->sc_if;
-	ifp->if_softc = sc;
-
-	if_initname(ifp, "npflog", unit);
-	ifp->if_type = IFT_OTHER;
-	ifp->if_dlt = DLT_NULL;
-	ifp->if_ioctl = npflog_ioctl;
-
-	KERNEL_LOCK(1, NULL);
-	if_attach(ifp);
-	if_alloc_sadl(ifp);
-	bpf_attach(ifp, DLT_NULL, 0);
-	LIST_INSERT_HEAD(&npflog_if_list, sc, sc_entry);
-	KERNEL_UNLOCK_ONE(NULL);
-
-	return 0;
-}
-
-static int
-npflog_clone_destroy(ifnet_t *ifp)
-{
-	npflog_softc_t *sc = ifp->if_softc;
-
-	KERNEL_LOCK(1, NULL);
-	LIST_REMOVE(sc, sc_entry);
-	bpf_detach(ifp);
-	if_detach(ifp);
-	KERNEL_UNLOCK_ONE(NULL);
-
-	mutex_destroy(&sc->sc_lock);
-	kmem_free(sc, sizeof(npflog_softc_t));
-	return 0;
-}
-
-void
-npf_log_packet(npf_cache_t *npc, nbuf_t *nbuf, int if_idx)
-{
-	struct mbuf *m = nbuf;
-	ifnet_t *ifp;
-	int family;
-
-	/* Set the address family. */
-	if (npf_iscached(npc, NPC_IP4)) {
-		family = AF_INET;
-	} else if (npf_iscached(npc, NPC_IP6)) {
-		family = AF_INET6;
-	} else {
-		family = AF_UNSPEC;
-	}
-
-	KERNEL_LOCK(1, NULL);
-
-	/* Find a pseudo-interface to log. */
-	ifp = if_byindex(if_idx);
-	if (ifp == NULL) {
-		/* No interface. */
-		KERNEL_UNLOCK_ONE(NULL);
-		return;
-	}
-
-	/* Pass through BPF. */
-	ifp->if_opackets++;
-	ifp->if_obytes += m->m_pkthdr.len;
-	bpf_mtap_af(ifp, family, m);
-	KERNEL_UNLOCK_ONE(NULL);
-}
--- a/sys/net/npf/npf_rproc.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/net/npf/npf_rproc.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_rproc.c,v 1.2 2012/02/20 00:18:20 rmind Exp $	*/
+/*	$NetBSD: npf_rproc.c,v 1.3 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 /*
- * NPF rule procedure interface.
+ * NPF extension and rule procedure interface.
  */
 
 #include <sys/cdefs.h>
@@ -41,55 +41,176 @@
 
 #include <sys/atomic.h>
 #include <sys/kmem.h>
+#include <sys/mutex.h>
 
 #include "npf_impl.h"
 
-#define	NPF_RNAME_LEN		16
+#define	EXT_NAME_LEN		32
 
-/* Rule procedure structure. */
+typedef struct npf_ext {
+	char			ext_callname[EXT_NAME_LEN];
+	LIST_ENTRY(npf_ext)	ext_entry;
+	const npf_ext_ops_t *	ext_ops;
+	unsigned		ext_refcnt;
+} npf_ext_t;
+
+#define	RPROC_NAME_LEN		32
+#define	RPROC_EXT_COUNT		16
+
 struct npf_rproc {
-	/* Name. */
-	char			rp_name[NPF_RNAME_LEN];
-	/* Reference count. */
+	/* Name, reference count and flags. */
+	char			rp_name[RPROC_NAME_LEN];
 	u_int			rp_refcnt;
 	uint32_t		rp_flags;
-	/* Normalisation options. */
-	bool			rp_rnd_ipid;
-	bool			rp_no_df;
-	u_int			rp_minttl;
-	u_int			rp_maxmss;
-	/* Logging interface. */
-	u_int			rp_log_ifid;
+	/* Associated extensions and their metadata . */
+	unsigned		rp_ext_count;
+	npf_ext_t *		rp_ext[RPROC_EXT_COUNT];
+	void *			rp_ext_meta[RPROC_EXT_COUNT];
 };
 
+static LIST_HEAD(, npf_ext)	ext_list	__cacheline_aligned;
+static kmutex_t			ext_lock	__cacheline_aligned;
+
+void
+npf_ext_sysinit(void)
+{
+	mutex_init(&ext_lock, MUTEX_DEFAULT, IPL_NONE);
+	LIST_INIT(&ext_list);
+}
+
+void
+npf_ext_sysfini(void)
+{
+	KASSERT(LIST_EMPTY(&ext_list));
+	mutex_destroy(&ext_lock);
+}
+
+/*
+ * NPF extension management for the rule procedures.
+ */
+
+static npf_ext_t *
+npf_ext_lookup(const char *name)
+{
+	npf_ext_t *ext = NULL;
+
+	KASSERT(mutex_owned(&ext_lock));
+
+	LIST_FOREACH(ext, &ext_list, ext_entry)
+		if (strcmp(ext->ext_callname, name) == 0)
+			break;
+	return ext;
+}
+
+void *
+npf_ext_register(const char *name, const npf_ext_ops_t *ops)
+{
+	npf_ext_t *ext;
+
+	ext = kmem_zalloc(sizeof(npf_ext_t), KM_SLEEP);
+	strlcpy(ext->ext_callname, name, EXT_NAME_LEN);
+	ext->ext_ops = ops;
+
+	mutex_enter(&ext_lock);
+	if (npf_ext_lookup(name)) {
+		mutex_exit(&ext_lock);
+		kmem_free(ext, sizeof(npf_ext_t));
+		return NULL;
+	}
+	LIST_INSERT_HEAD(&ext_list, ext, ext_entry);
+	mutex_exit(&ext_lock);
+
+	return (void *)ext;
+}
+
+int
+npf_ext_unregister(void *extid)
+{
+	npf_ext_t *ext = extid;
+
+	/*
+	 * Check if in-use first (re-check with the lock held).
+	 */
+	if (ext->ext_refcnt) {
+		return EBUSY;
+	}
+
+	mutex_enter(&ext_lock);
+	if (ext->ext_refcnt) {
+		mutex_exit(&ext_lock);
+		return EBUSY;
+	}
+	KASSERT(npf_ext_lookup(ext->ext_callname));
+	LIST_REMOVE(ext, ext_entry);
+	mutex_exit(&ext_lock);
+
+	kmem_free(ext, sizeof(npf_ext_t));
+	return 0;
+}
+
+int
+npf_ext_construct(const char *name, npf_rproc_t *rp, prop_dictionary_t params)
+{
+	const npf_ext_ops_t *extops;
+	npf_ext_t *ext;
+	unsigned i;
+	int error;
+
+	if (rp->rp_ext_count >= RPROC_EXT_COUNT) {
+		return ENOSPC;
+	}
+
+	mutex_enter(&ext_lock);
+	ext = npf_ext_lookup(name);
+	if (ext) {
+		atomic_inc_uint(&ext->ext_refcnt);
+		extops = ext->ext_ops;
+		KASSERT(extops != NULL);
+	}
+	mutex_exit(&ext_lock);
+	if (!ext) {
+		return ENOENT;
+	}
+
+	error = extops->ctor(rp, params);
+	if (error) {
+		atomic_dec_uint(&ext->ext_refcnt);
+		return error;
+	}
+	i = rp->rp_ext_count++;
+	rp->rp_ext[i] = ext;
+	return 0;
+}
+
+/*
+ * Rule procedure management.
+ */
+
+/*
+ * npf_rproc_create: construct a new rule procedure, lookup and associate
+ * the extension calls with it.
+ */
 npf_rproc_t *
 npf_rproc_create(prop_dictionary_t rpdict)
 {
+	const char *name;
 	npf_rproc_t *rp;
-	const char *rname;
+
+	if (!prop_dictionary_get_cstring_nocopy(rpdict, "name", &name)) {
+		return NULL;
+	}
 
 	rp = kmem_intr_zalloc(sizeof(npf_rproc_t), KM_SLEEP);
 	rp->rp_refcnt = 1;
 
-	/* Name and flags. */
-	prop_dictionary_get_cstring_nocopy(rpdict, "name", &rname);
-	strlcpy(rp->rp_name, rname, NPF_RNAME_LEN);
+	strlcpy(rp->rp_name, name, RPROC_NAME_LEN);
 	prop_dictionary_get_uint32(rpdict, "flags", &rp->rp_flags);
-
-	/* Logging interface ID (integer). */
-	prop_dictionary_get_uint32(rpdict, "log-interface", &rp->rp_log_ifid);
-
-	/* IP ID randomisation and IP_DF flag cleansing. */
-	prop_dictionary_get_bool(rpdict, "randomize-id", &rp->rp_rnd_ipid);
-	prop_dictionary_get_bool(rpdict, "no-df", &rp->rp_no_df);
-
-	/* Minimum IP TTL and maximum TCP MSS. */
-	prop_dictionary_get_uint32(rpdict, "min-ttl", &rp->rp_minttl);
-	prop_dictionary_get_uint32(rpdict, "max-mss", &rp->rp_maxmss);
-
 	return rp;
 }
 
+/*
+ * npf_rproc_acquire: acquire the reference on the rule procedure.
+ */
 void
 npf_rproc_acquire(npf_rproc_t *rp)
 {
@@ -97,36 +218,56 @@
 	atomic_inc_uint(&rp->rp_refcnt);
 }
 
+/*
+ * npf_rproc_release: drop the reference count and destroy the rule
+ * procedure on the last reference.
+ */
 void
 npf_rproc_release(npf_rproc_t *rp)
 {
 
-	/* Destroy on last reference. */
 	KASSERT(rp->rp_refcnt > 0);
 	if (atomic_dec_uint_nv(&rp->rp_refcnt) != 0) {
 		return;
 	}
+	/* XXXintr */
+	for (unsigned i = 0; i < rp->rp_ext_count; i++) {
+		npf_ext_t *ext = rp->rp_ext[i];
+		const npf_ext_ops_t *extops = ext->ext_ops;
+
+		extops->dtor(rp, rp->rp_ext_meta[i]);
+		atomic_dec_uint(&ext->ext_refcnt);
+	}
 	kmem_intr_free(rp, sizeof(npf_rproc_t));
 }
 
 void
-npf_rproc_run(npf_cache_t *npc, nbuf_t *nbuf, npf_rproc_t *rp, int error)
+npf_rproc_assign(npf_rproc_t *rp, void *params)
 {
-	const uint32_t flags = rp->rp_flags;
+	unsigned i = rp->rp_ext_count;
+
+	/* Note: params may be NULL. */
+	KASSERT(i < RPROC_EXT_COUNT);
+	rp->rp_ext_meta[i] = params;
+}
+
+/*
+ * npf_rproc_run: run the rule procedure by executing each extension call.
+ *
+ * => Reference on the rule procedure must be held.
+ */
+void
+npf_rproc_run(npf_cache_t *npc, nbuf_t *nbuf, npf_rproc_t *rp, int *decision)
+{
+	const unsigned extcount = rp->rp_ext_count;
 
 	KASSERT(rp->rp_refcnt > 0);
 
-	/* Normalise the packet, if required. */
-	if ((flags & NPF_RPROC_NORMALIZE) != 0 && !error) {
-		(void)npf_normalize(npc, nbuf,
-		    rp->rp_rnd_ipid, rp->rp_no_df,
-		    rp->rp_minttl, rp->rp_maxmss);
-		npf_stats_inc(NPF_STAT_RPROC_NORM);
-	}
+	for (unsigned i = 0; i < extcount; i++) {
+		const npf_ext_t *ext = rp->rp_ext[i];
+		const npf_ext_ops_t *extops = ext->ext_ops;
 
-	/* Log packet, if required. */
-	if ((flags & NPF_RPROC_LOG) != 0) {
-		npf_log_packet(npc, nbuf, rp->rp_log_ifid);
-		npf_stats_inc(NPF_STAT_RPROC_LOG);
+		KASSERT(ext->ext_refcnt > 0);
+		extops->proc(npc, nbuf, rp->rp_ext_meta[i], decision);
 	}
 }
--- a/sys/rump/net/lib/libnpf/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/sys/rump/net/lib/libnpf/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.1 2012/08/14 22:31:44 rmind Exp $
+#	$NetBSD: Makefile,v 1.2 2012/09/16 13:47:42 rmind Exp $
 #
 # Public Domain.
 #
@@ -8,14 +8,18 @@
 LIB=	rumpnet_npf
 
 SRCS=	npf.c npf_alg.c npf_ctl.c npf_handler.c
-SRCS+=	npf_inet.c npf_instr.c npf_log.c npf_mbuf.c npf_nat.c
+SRCS+=	npf_inet.c npf_instr.c npf_mbuf.c npf_nat.c
 SRCS+=	npf_processor.c npf_ruleset.c npf_rproc.c npf_sendpkt.c
 SRCS+=	npf_session.c npf_state.c npf_state_tcp.c
 SRCS+=	npf_tableset.c npf_tableset_ptree.c
 
+SRCS+=	npf_alg_icmp.c
+
+SRCS+=	npf_ext_log.c npf_ext_normalise.c
+
 SRCS+=	component.c
 
-WARNS=	4
+WARNS=	5
 
 CPPFLAGS+=	-D_NPF_TESTING
 CPPFLAGS+=	-I${.CURDIR}/../../../librump/rumpvfs
--- a/usr.sbin/npf/npfctl/Makefile	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/Makefile	Sun Sep 16 13:47:41 2012 +0000
@@ -1,10 +1,10 @@
-# $NetBSD: Makefile,v 1.7 2012/05/30 21:30:07 rmind Exp $
+# $NetBSD: Makefile,v 1.8 2012/09/16 13:47:41 rmind Exp $
 
 PROG=		npfctl
 MAN=		npfctl.8 npf.conf.5
 
 SRCS=		npfctl.c npf_var.c npf_data.c npf_ncgen.c npf_build.c \
-		npf_disassemble.c
+		npf_extmod.c npf_disassemble.c
 
 CPPFLAGS+=	-I${.CURDIR}
 SRCS+=		npf_scan.l npf_parse.y
@@ -13,7 +13,7 @@
 LDADD+=		-lnpf -lprop -lutil -ly
 DPADD+=		${LIBNPF} ${LIBPROP} ${LIBUTIL}
 
-WARNS?=		4
-NOLINT=		# disabled (note: deliberately)
+WARNS=		5
+NOLINT=		# disabled deliberately
 
 .include <bsd.prog.mk>
--- a/usr.sbin/npf/npfctl/npf_build.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_build.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_build.c,v 1.13 2012/08/12 03:35:13 rmind Exp $	*/
+/*	$NetBSD: npf_build.c,v 1.14 2012/09/16 13:47:41 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.13 2012/08/12 03:35:13 rmind Exp $");
+__RCSID("$NetBSD: npf_build.c,v 1.14 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/ioctl.h>
@@ -42,6 +42,7 @@
 #include <stdlib.h>
 #include <inttypes.h>
 #include <string.h>
+#include <errno.h>
 #include <err.h>
 
 #include "npfctl.h"
@@ -377,58 +378,36 @@
 static void
 npfctl_build_rpcall(nl_rproc_t *rp, const char *name, npfvar_t *args)
 {
-	/*
-	 * XXX/TODO: Hardcoded for the first release.  However,
-	 * rule procedures will become fully dynamic modules.
-	 */
+	npf_extmod_t *extmod;
+	nl_ext_t *extcall;
+	int error;
 
-	bool log = false, norm = false;
-	bool rnd = false, no_df = false;
-	int minttl = 0, maxmss = 0;
-
-	if (strcmp(name, "log") == 0) {
-		log = true;
-	} else if (strcmp(name, "normalise") == 0) {
-		norm = true;
-	} else {
+	extmod = npf_extmod_get(name, &extcall);
+	if (extmod == NULL) {
 		yyerror("unknown rule procedure '%s'", name);
 	}
 
 	for (size_t i = 0; i < npfvar_get_count(args); i++) {
-		module_arg_t *arg;
-		const char *aval;
-
-		arg = npfvar_get_data(args, NPFVAR_MODULE_ARG, i);
-		aval = arg->ma_name;
+		const char *param, *value;
+		proc_param_t *p;
 
-		if (log) {
-			u_int if_idx = npfctl_find_ifindex(aval);
-			_npf_rproc_setlog(rp, if_idx);
-			return;
-		}
+		p = npfvar_get_data(args, NPFVAR_PROC_PARAM, i);
+		param = p->pp_param;
+		value = p->pp_value;
 
-		const int type = npfvar_get_type(arg->ma_opts, 0);
-		if (type != -1 && type != NPFVAR_NUM) {
-			yyerror("option '%s' is not numeric", aval);
-		}
-		unsigned long *opt;
-
-		if (strcmp(aval, "random-id") == 0) {
-			rnd = true;
-		} else if (strcmp(aval, "min-ttl") == 0) {
-			opt = npfvar_get_data(arg->ma_opts, NPFVAR_NUM, 0);
-			minttl = *opt;
-		} else if (strcmp(aval, "max-mss") == 0) {
-			opt = npfvar_get_data(arg->ma_opts, NPFVAR_NUM, 0);
-			maxmss = *opt;
-		} else if (strcmp(aval, "no-df") == 0) {
-			no_df = true;
-		} else {
-			yyerror("unknown argument '%s'", aval);
+		error = npf_extmod_param(extmod, extcall, param, value);
+		switch (error) {
+		case EINVAL:
+			yyerror("invalid parameter '%s'", param);
+		default:
+			break;
 		}
 	}
-	assert(norm == true);
-	_npf_rproc_setnorm(rp, rnd, no_df, minttl, maxmss);
+	error = npf_rproc_extcall(rp, extcall);
+	if (error) {
+		yyerror(error == EEXIST ?
+		    "duplicate procedure call" : "unexpected error");
+	}
 }
 
 /*
@@ -447,8 +426,8 @@
 	npf_rproc_insert(npf_conf, rp);
 
 	for (i = 0; i < npfvar_get_count(procs); i++) {
-		proc_op_t *po = npfvar_get_data(procs, NPFVAR_PROC_OP, i);
-		npfctl_build_rpcall(rp, po->po_name, po->po_opts);
+		proc_call_t *pc = npfvar_get_data(procs, NPFVAR_PROC, i);
+		npfctl_build_rpcall(rp, pc->pc_name, pc->pc_opts);
 	}
 }
 
@@ -498,7 +477,7 @@
 }
 
 /*
- * npfctl_build_onenat: create a single NAT policy of a specified
+ * npfctl_build_nat: create a single NAT policy of a specified
  * type with a given filter options.
  */
 static void
@@ -554,7 +533,7 @@
 }
 
 /*
- * npfctl_build_nat: validate and create NAT policies.
+ * npfctl_build_natseg: validate and create NAT policies.
  */
 void
 npfctl_build_natseg(int sd, int type, u_int if_idx, const addr_port_t *ap1,
@@ -579,11 +558,11 @@
 
 	/*
 	 * If the filter criteria is not specified explicitly, apply implicit
-	 * filtering according to the given network segements.
+	 * filtering according to the given network segments.
 	 *
 	 * Note: filled below, depending on the type.
 	 */
-	if (!fopts) {
+	if (__predict_true(!fopts)) {
 		fopts = &imfopts;
 	}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr.sbin/npf/npfctl/npf_extmod.c	Sun Sep 16 13:47:41 2012 +0000
@@ -0,0 +1,127 @@
+/*	$NetBSD: npf_extmod.c,v 1.1 2012/09/16 13:47:41 rmind Exp $	*/
+
+/*-
+ * Copyright (c) 2012 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Mindaugas Rasiukevicius.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * npfctl(8) extension loading interface.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: npf_extmod.c,v 1.1 2012/09/16 13:47:41 rmind Exp $");
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <err.h>
+#include <dlfcn.h>
+
+#include "npfctl.h"
+
+struct npf_extmod {
+	char *			name;
+	npfext_initfunc_t	init;
+	npfext_consfunc_t	cons;
+	npfext_paramfunc_t	param;
+	struct npf_extmod *	next;
+};
+
+static npf_extmod_t *		npf_extmod_list;
+
+static void *
+npf_extmod_sym(void *handle, const char *name, const char *func)
+{
+	char buf[64];
+	void *sym;
+
+	snprintf(buf, sizeof(buf), "npfext_%s_%s", name, func);
+	sym = dlsym(handle, buf);
+	if (sym == NULL) {
+		errx(EXIT_FAILURE, "dlsym: %s", dlerror());
+	}
+	return sym;
+}
+
+static npf_extmod_t *
+npf_extmod_load(const char *name)
+{
+	npf_extmod_t *ext;
+	void *handle;
+	char extlib[PATH_MAX];
+
+	snprintf(extlib, sizeof(extlib), "/usr/lib/npf/ext_%s.so", name);
+	handle = dlopen(extlib, RTLD_LAZY | RTLD_LOCAL);
+	if (handle == NULL) {
+		errx(EXIT_FAILURE, "dlopen: %s", dlerror());
+	}
+
+	ext = zalloc(sizeof(npf_extmod_t));
+	ext->name = xstrdup(name);
+	ext->init = npf_extmod_sym(handle, name, "init");
+	ext->cons = npf_extmod_sym(handle, name, "construct");
+	ext->param = npf_extmod_sym(handle, name, "param");
+
+	/* Initialise the module. */
+	if (ext->init() != 0) {
+		free(ext);
+		return NULL;
+	}
+
+	ext->next = npf_extmod_list;
+	npf_extmod_list = ext;
+	return ext;
+}
+
+npf_extmod_t *
+npf_extmod_get(const char *name, nl_ext_t **extcall)
+{
+	npf_extmod_t *extmod = npf_extmod_list;
+
+	while (extmod) {
+		if ((strcmp(extmod->name, name) == 0) &&
+		    (*extcall = extmod->cons(name)) != NULL) {
+			return extmod;
+		}
+		extmod = extmod->next;
+	}
+
+	extmod = npf_extmod_load(name);
+	if (extmod && (*extcall = extmod->cons(name)) != NULL) {
+		return extmod;
+	}
+
+	return NULL;
+}
+
+int
+npf_extmod_param(npf_extmod_t *extmod, nl_ext_t *ext,
+    const char *param, const char *val)
+{
+	return extmod->param(ext, param, val);
+}
--- a/usr.sbin/npf/npfctl/npf_parse.y	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_parse.y	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_parse.y,v 1.12 2012/08/12 03:35:13 rmind Exp $	*/
+/*	$NetBSD: npf_parse.y,v 1.13 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -130,18 +130,19 @@
 %token	<str>		IPV4ADDR
 %token	<str>		IPV6ADDR
 %token	<num>		NUM
+%token	<fpnum>		FPNUM
 %token	<str>		STRING
 %token	<str>		TABLE_ID
 %token	<str>		VAR_ID
 
 %type	<str>		addr, some_name, list_elem, table_store
-%type	<str>		opt_apply
+%type	<str>		proc_param_val, opt_apply
 %type	<num>		ifindex, port, opt_final, on_iface
 %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
-%type	<var>		modulearg_opts, procs, proc_op, modulearg, moduleargs
+%type	<var>		procs, proc_call, proc_param_list, proc_param
 %type	<addrport>	mapseg
 %type	<filtopts>	filt_opts, all_or_filt_opts
 %type	<optproto>	opt_proto
@@ -150,6 +151,7 @@
 %union {
 	char *		str;
 	unsigned long	num;
+	double		fpnum;
 	addr_port_t	addrport;
 	filt_opts_t	filtopts;
 	npfvar_t *	var;
@@ -295,64 +297,53 @@
 	;
 
 procs
-	: proc_op SEPLINE procs	{ $$ = npfvar_add_elements($1, $3); }
-	| proc_op		{ $$ = $1; }
+	: proc_call SEPLINE procs
+	{
+		$$ = npfvar_add_elements($1, $3);
+	}
+	| proc_call	{ $$ = $1; }
 	;
 
-proc_op
-	: IDENTIFIER COLON moduleargs
+proc_call
+	: IDENTIFIER COLON proc_param_list
 	{
-		proc_op_t po;
+		proc_call_t pc;
 
-		po.po_name = xstrdup($1);
-		po.po_opts = $3;
-		$$ = npfvar_create(".proc_ops");
-		npfvar_add_element($$, NPFVAR_PROC_OP, &po, sizeof(po));
+		pc.pc_name = xstrdup($1);
+		pc.pc_opts = $3;
+		$$ = npfvar_create(".proc_call");
+		npfvar_add_element($$, NPFVAR_PROC, &pc, sizeof(pc));
 	}
 	|	{ $$ = NULL; }
 	;
 
-moduleargs
-	: modulearg COMMA moduleargs
+proc_param_list
+	: proc_param COMMA proc_param_list
 	{
 		$$ = npfvar_add_elements($1, $3);
 	}
-	| modulearg	{ $$ = $1; }
+	| proc_param	{ $$ = $1; }
 	|		{ $$ = NULL; }
 	;
 
-modulearg
-	: some_name modulearg_opts
+proc_param
+	/* Key and value pair. */
+	: some_name proc_param_val
 	{
-		module_arg_t ma;
+		proc_param_t pp;
 
-		ma.ma_name = xstrdup($1);
-		ma.ma_opts = $2;
-		$$ = npfvar_create(".module_arg");
-		npfvar_add_element($$, NPFVAR_MODULE_ARG, &ma, sizeof(ma));
+		pp.pp_param = xstrdup($1);
+		pp.pp_value = $2 ? xstrdup($2) : NULL;
+		$$ = npfvar_create(".proc_param");
+		npfvar_add_element($$, NPFVAR_PROC_PARAM, &pp, sizeof(pp));
 	}
 	;
 
-modulearg_opts
-	: STRING modulearg_opts
-	{
-		npfvar_t *vp = npfvar_create(".modstring");
-		npfvar_add_element(vp, NPFVAR_STRING, $1, strlen($1) + 1);
-		$$ = $2 ? npfvar_add_elements($2, vp) : vp;
-	}
-	| IDENTIFIER modulearg_opts
-	{
-		npfvar_t *vp = npfvar_create(".modident");
-		npfvar_add_element(vp, NPFVAR_IDENTIFIER, $1, strlen($1) + 1);
-		$$ = $2 ? npfvar_add_elements($2, vp) : vp;
-	}
-	| NUM modulearg_opts
-	{
-		npfvar_t *vp = npfvar_create(".modnum");
-		npfvar_add_element(vp, NPFVAR_NUM, &$1, sizeof($1));
-		$$ = $2 ? npfvar_add_elements($2, vp) : vp;
-	}
-	|	{ $$ = NULL; }
+proc_param_val
+	: some_name	{ $$ = $1; }
+	| NUM		{ (void)asprintf(&$$, "%ld", $1); }
+	| FPNUM		{ (void)asprintf(&$$, "%lf", $1); }
+	|		{ $$ = NULL; }
 	;
 
 group
--- a/usr.sbin/npf/npfctl/npf_scan.l	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_scan.l	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_scan.l,v 1.5 2012/07/19 21:52:29 spz Exp $	*/
+/*	$NetBSD: npf_scan.l,v 1.6 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -31,6 +31,7 @@
 
 %{
 #include <stdio.h>
+#include <stdlib.h>
 #include <err.h>
 
 #include "npfctl.h"
@@ -115,6 +116,13 @@
 			return HEX;
 		}
 
+{NUMBER}"."{NUMBER} {
+			char *endp, *buf = xstrndup(yytext, yyleng);
+			yylval.fpnum = strtod(buf, &endp);
+			free(buf);
+			return FPNUM;
+		}
+
 [0-9a-fA-F]+":"[0-9a-fA-F:]* {
 			yylval.str = xstrndup(yytext, yyleng);
 			return IPV6ADDR;
--- a/usr.sbin/npf/npfctl/npf_var.h	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npf_var.h	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_var.h,v 1.3 2012/07/19 21:52:29 spz Exp $	*/
+/*	$NetBSD: npf_var.h,v 1.4 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -35,25 +35,35 @@
 #define	NPFVAR_STRING		0
 #define	NPFVAR_IDENTIFIER	1
 #define	NPFVAR_VAR_ID		2
-#define NPFVAR_NUM		3
-#define NPFVAR_PORT_RANGE	4
+#define	NPFVAR_NUM		3
+#define	NPFVAR_PORT_RANGE	4
 
 /* Note: primitive types are equivalent. */
-#define NPFVAR_PRIM		NPFVAR_PORT_RANGE
-#define NPFVAR_TYPE(x)		(((x) > NPFVAR_PRIM) ? (x) : 0)
+#define	NPFVAR_PRIM		NPFVAR_PORT_RANGE
+#define	NPFVAR_TYPE(x)		(((x) > NPFVAR_PRIM) ? (x) : 0)
 
 #define	NPFVAR_TABLE		5
 #define	NPFVAR_FAM		6
-#define	NPFVAR_TCPFLAG		7
-#define	NPFVAR_ICMP		8
-#define	NPFVAR_PROC_OP		9
-#define	NPFVAR_MODULE_ARG	10
+#define	NPFVAR_PROC		7
+#define	NPFVAR_PROC_PARAM	8
+#define	NPFVAR_TCPFLAG		9
+#define	NPFVAR_ICMP		10
 #define	NPFVAR_ICMP6		11
 
 #ifdef _NPFVAR_PRIVATE
 static const char *npfvar_types[ ] = {
-	"string", "identifier", "var_id", "num", "table", "fam", "port_range",
-	"tcpflag", "icmp", "proc_op", "module_arg", "icmp6"
+	[NPFVAR_STRING]		= "string",
+	[NPFVAR_IDENTIFIER]	= "identifier",
+	[NPFVAR_VAR_ID]		= "var_id",
+	[NPFVAR_NUM]		= "num",
+	[NPFVAR_PORT_RANGE]	= "port-range",
+	[NPFVAR_TABLE]		= "table",
+	[NPFVAR_FAM]		= "fam",
+	[NPFVAR_PROC]		= "proc",
+	[NPFVAR_PROC_PARAM]	= "proc_param",
+	[NPFVAR_TCPFLAG]	= "tcpflag",
+	[NPFVAR_ICMP]		= "icmp",
+	[NPFVAR_ICMP6]		= "icmp6"
 };
 #endif
 
--- a/usr.sbin/npf/npfctl/npfctl.c	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.c	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npfctl.c,v 1.19 2012/09/01 19:08:01 rmind Exp $	*/
+/*	$NetBSD: npfctl.c,v 1.20 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npfctl.c,v 1.19 2012/09/01 19:08:01 rmind Exp $");
+__RCSID("$NetBSD: npfctl.c,v 1.20 2012/09/16 13:47:41 rmind Exp $");
 
 #include <sys/ioctl.h>
 #include <sys/stat.h>
@@ -201,10 +201,6 @@
 		{ NPF_STAT_RACE_NAT,		"NAT association race"	},
 		{ NPF_STAT_RACE_SESSION,	"duplicate session race"},
 
-		{ -1, "Rule procedure cases"				},
-		{ NPF_STAT_RPROC_LOG,		"packets logged"	},
-		{ NPF_STAT_RPROC_NORM,		"packets normalised"	},
-
 		{ -1, "Fragmentation"					},
 		{ NPF_STAT_FRAGMENTS,		"fragments"		},
 		{ NPF_STAT_REASSEMBLY,		"reassembled"		},
--- a/usr.sbin/npf/npfctl/npfctl.h	Sun Sep 16 13:46:49 2012 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.h	Sun Sep 16 13:47:41 2012 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npfctl.h,v 1.19 2012/08/12 03:35:13 rmind Exp $	*/
+/*	$NetBSD: npfctl.h,v 1.20 2012/09/16 13:47:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
@@ -79,15 +79,15 @@
 	u_int		rg_ifnum;
 } rule_group_t;
 
-typedef struct proc_op {
-	const char *	po_name;
-	npfvar_t *	po_opts;
-} proc_op_t;
+typedef struct proc_call {
+	const char *	pc_name;
+	npfvar_t *	pc_opts;
+} proc_call_t;
 
-typedef struct module_arg {
-	const char *	ma_name;
-	npfvar_t *	ma_opts;
-} module_arg_t;
+typedef struct proc_param {
+	const char *	pp_param;
+	const char *	pp_value;
+} proc_param_t;
 
 void		yyerror(const char *, ...) __printflike(1, 2) __dead;
 void *		zalloc(size_t);
@@ -113,6 +113,16 @@
 bool		npfctl_parse_cidr(char *, fam_addr_mask_t *, int *);
 
 /*
+ * NPF extension loading.
+ */
+
+typedef struct npf_extmod npf_extmod_t;
+
+npf_extmod_t *	npf_extmod_get(const char *, nl_ext_t **);
+int		npf_extmod_param(npf_extmod_t *, nl_ext_t *,
+		    const char *, const char *);
+
+/*
  * N-code generation interface.
  */