NPF: add support for IPv6-to-IPv6 Network Prefix Translation (NPTv6), trunk
authorrmind <rmind@NetBSD.org>
Thu, 13 Feb 2014 03:34:40 +0000
branchtrunk
changeset 224532 9b57ba5421f7
parent 224531 f77d89987307
child 224533 a0886779f518
NPF: add support for IPv6-to-IPv6 Network Prefix Translation (NPTv6), as per RFC 6296. Add a unit test. Also, bump NPF_VERSION. Thanks to S.P.Zeidler for the help with NPTv6 work!
lib/libnpf/npf.c
lib/libnpf/npf.h
sys/net/npf/npf.h
sys/net/npf/npf_impl.h
sys/net/npf/npf_inet.c
sys/net/npf/npf_nat.c
usr.sbin/npf/npfctl/npf_build.c
usr.sbin/npf/npfctl/npf_data.c
usr.sbin/npf/npfctl/npf_parse.y
usr.sbin/npf/npfctl/npf_scan.l
usr.sbin/npf/npfctl/npfctl.h
usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c
usr.sbin/npf/npftest/libnpftest/npf_nat_test.c
usr.sbin/npf/npftest/libnpftest/npf_test.h
usr.sbin/npf/npftest/libnpftest/npf_test_subr.c
usr.sbin/npf/npftest/npftest.c
usr.sbin/npf/npftest/npftest.conf
usr.sbin/npf/npftest/npftest.h
--- a/lib/libnpf/npf.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/lib/libnpf/npf.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.c,v 1.27 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf.c,v 1.28 2014/02/13 03:34:41 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2010-2014 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.27 2014/02/07 23:45:22 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.28 2014/02/13 03:34:41 rmind Exp $");
 
 #include <sys/types.h>
 #include <netinet/in_systm.h>
@@ -802,7 +802,7 @@
 
 nl_nat_t *
 npf_nat_create(int type, u_int flags, const char *ifname,
-    npf_addr_t *addr, int af, in_port_t port)
+    int af, npf_addr_t *addr, npf_netmask_t mask, in_port_t port)
 {
 	nl_rule_t *rl;
 	prop_dictionary_t rldict;
@@ -832,13 +832,14 @@
 	prop_dictionary_set_int32(rldict, "type", type);
 	prop_dictionary_set_uint32(rldict, "flags", flags);
 
-	/* Translation IP. */
+	/* Translation IP and mask. */
 	addrdat = prop_data_create_data(addr, sz);
 	if (addrdat == NULL) {
 		npf_rule_destroy(rl);
 		return NULL;
 	}
 	prop_dictionary_set(rldict, "translation-ip", addrdat);
+	prop_dictionary_set_uint32(rldict, "translation-mask", mask);
 	prop_object_release(addrdat);
 
 	/* Translation port (for redirect case). */
@@ -865,6 +866,27 @@
 }
 
 int
+npf_nat_setalgo(nl_nat_t *nt, u_int algo)
+{
+	prop_dictionary_t rldict = nt->nrl_dict;
+	prop_dictionary_set_uint32(rldict, "translation-algo", algo);
+	return 0;
+}
+
+int
+npf_nat_setnpt66(nl_nat_t *nt, uint16_t adj)
+{
+	prop_dictionary_t rldict = nt->nrl_dict;
+	int error;
+
+	if ((error = npf_nat_setalgo(nt, NPF_ALGO_NPT66)) != 0) {
+		return error;
+	}
+	prop_dictionary_set_uint16(rldict, "npt66-adjustment", adj);
+	return 0;
+}
+
+int
 npf_nat_gettype(nl_nat_t *nt)
 {
 	prop_dictionary_t rldict = nt->nrl_dict;
--- a/lib/libnpf/npf.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/lib/libnpf/npf.h	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf.h,v 1.24 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.25 2014/02/13 03:34:41 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2011-2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2011-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -105,7 +105,7 @@
 int		npf_rproc_insert(nl_config_t *, nl_rproc_t *);
 
 nl_nat_t *	npf_nat_create(int, u_int, const char *,
-		    npf_addr_t *, int, in_port_t);
+		    int, npf_addr_t *, npf_netmask_t, in_port_t);
 int		npf_nat_insert(nl_config_t *, nl_nat_t *, pri_t);
 
 nl_table_t *	npf_table_create(const char *, u_int, int);
@@ -139,6 +139,9 @@
 unsigned	npf_nat_getflags(nl_nat_t *);
 void		npf_nat_getmap(nl_nat_t *, npf_addr_t *, size_t *, in_port_t *);
 
+int		npf_nat_setalgo(nl_nat_t *, u_int);
+int		npf_nat_setnpt66(nl_nat_t *, uint16_t);
+
 nl_rproc_t *	npf_rproc_iterate(nl_config_t *);
 const char *	npf_rproc_getname(nl_rproc_t *);
 
--- a/sys/net/npf/npf.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/sys/net/npf/npf.h	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf.h,v 1.36 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.37 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2009-2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -45,7 +45,7 @@
 #include <netinet/in_systm.h>
 #include <netinet/in.h>
 
-#define	NPF_VERSION		12
+#define	NPF_VERSION		13
 
 /*
  * Public declarations and definitions.
@@ -237,6 +237,8 @@
 #define	NPF_NAT_PORTMAP			0x02
 #define	NPF_NAT_STATIC			0x04
 
+#define	NPF_ALGO_NPT66			1
+
 /* Table types. */
 #define	NPF_TABLE_HASH			1
 #define	NPF_TABLE_TREE			2
--- a/sys/net/npf/npf_impl.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/sys/net/npf/npf_impl.h	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_impl.h,v 1.46 2014/02/06 02:51:28 rmind Exp $	*/
+/*	$NetBSD: npf_impl.h,v 1.47 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2009-2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -192,6 +192,8 @@
 bool		npf_rwrport(const npf_cache_t *, u_int, const in_port_t);
 bool		npf_rwrcksum(const npf_cache_t *, u_int,
 		    const npf_addr_t *, const in_port_t);
+int		npf_npt66_rwr(const npf_cache_t *, u_int, const npf_addr_t *,
+		    npf_netmask_t, uint16_t);
 
 uint16_t	npf_fixup16_cksum(uint16_t, uint16_t, uint16_t);
 uint16_t	npf_fixup32_cksum(uint16_t, uint32_t, uint32_t);
--- a/sys/net/npf/npf_inet.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/sys/net/npf/npf_inet.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_inet.c,v 1.28 2013/12/06 01:33:37 rmind Exp $	*/
+/*	$NetBSD: npf_inet.c,v 1.29 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This material is based upon work partially supported by The
@@ -39,7 +39,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.28 2013/12/06 01:33:37 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.29 2014/02/13 03:34:40 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -616,6 +616,79 @@
 	return true;
 }
 
+/*
+ * IPv6-to-IPv6 Network Prefix Translation (NPTv6), as per RFC 6296.
+ */
+
+int
+npf_npt66_rwr(const npf_cache_t *npc, u_int which, const npf_addr_t *pref,
+    npf_netmask_t len, uint16_t adj)
+{
+	npf_addr_t *addr = npc->npc_ips[which];
+	unsigned remnant, word, preflen = len >> 4;
+	uint32_t sum;
+
+	KASSERT(which == NPF_SRC || which == NPF_DST);
+
+	if (!npf_iscached(npc, NPC_IP6)) {
+		return EINVAL;
+	}
+	if (len <= 48) {
+		/*
+		 * The word to adjust.  Cannot translate the 0xffff
+		 * subnet if /48 or shorter.
+		 */
+		word = 3;
+		if (addr->s6_addr16[word] == 0xffff) {
+			return EINVAL;
+		}
+	} else {
+		/*
+		 * Also, all 0s or 1s in the host part are disallowed for
+		 * longer than /48 prefixes.
+		 */
+		if ((addr->s6_addr32[2] == 0 && addr->s6_addr32[3] == 0) ||
+		    (addr->s6_addr32[2] == ~0U && addr->s6_addr32[3] == ~0U))
+			return EINVAL;
+
+		/* Determine the 16-bit word to adjust. */
+		for (word = 4; word < 8; word++)
+			if (addr->s6_addr16[word] != 0xffff)
+				break;
+	}
+
+	/* Rewrite the prefix. */
+	for (unsigned i = 0; i < preflen; i++) {
+		addr->s6_addr16[i] = pref->s6_addr16[i];
+	}
+
+	/*
+	 * If prefix length is within a 16-bit word (not dividable by 16),
+	 * then prepare a mask, determine the word and adjust it.
+	 */
+	if ((remnant = len - (preflen << 4)) != 0) {
+		const uint16_t wordmask = (1U << remnant) - 1;
+		const unsigned i = preflen;
+
+		addr->s6_addr16[i] = (pref->s6_addr16[i] & wordmask) |
+		    (addr->s6_addr16[i] & ~wordmask);
+	}
+
+	/*
+	 * Performing 1's complement sum/difference.
+	 */
+	sum = addr->s6_addr16[word] + adj;
+	while (sum >> 16) {
+		sum = (sum >> 16) + (sum & 0xffff);
+	}
+	if (sum == 0xffff) {
+		/* RFC 1071. */
+		sum = 0x0000;
+	}
+	addr->s6_addr16[word] = sum;
+	return 0;
+}
+
 #if defined(DDB) || defined(_NPF_TESTING)
 
 void
--- a/sys/net/npf/npf_nat.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/sys/net/npf/npf_nat.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,6 +1,7 @@
-/*	$NetBSD: npf_nat.c,v 1.24 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf_nat.c,v 1.25 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
+ * Copyright (c) 2014 Mindaugas Rasiukevicius <rmind at netbsd org>
  * Copyright (c) 2010-2013 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
@@ -70,7 +71,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.24 2014/02/07 23:45:22 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.25 2014/02/13 03:34:40 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -100,7 +101,7 @@
 /* Portmap range: [ 1024 .. 65535 ] */
 #define	PORTMAP_FIRST		(1024)
 #define	PORTMAP_SIZE		((65536 - PORTMAP_FIRST) / 32)
-#define	PORTMAP_FILLED		((uint32_t)~0)
+#define	PORTMAP_FILLED		((uint32_t)~0U)
 #define	PORTMAP_MASK		(31)
 #define	PORTMAP_SHIFT		(5)
 
@@ -121,7 +122,12 @@
 	u_int			n_flags;
 	size_t			n_addr_sz;
 	npf_addr_t		n_taddr;
+	npf_netmask_t		n_tmask;
 	in_port_t		n_tport;
+	u_int			n_algo;
+	union {
+		uint16_t	n_npt66_adj;
+	};
 };
 
 #define	NPF_NP_CMP_START	offsetof(npf_natpolicy_t, n_type)
@@ -186,21 +192,33 @@
 
 	/* Should be exclusively either inbound or outbound NAT. */
 	if (((np->n_type == NPF_NATIN) ^ (np->n_type == NPF_NATOUT)) == 0) {
-		kmem_free(np, sizeof(npf_natpolicy_t));
-		return NULL;
+		goto err;
 	}
 	mutex_init(&np->n_lock, MUTEX_DEFAULT, IPL_SOFTNET);
 	cv_init(&np->n_cv, "npfnatcv");
 	LIST_INIT(&np->n_nat_list);
 
-	/* Translation IP. */
+	/* Translation IP, mask and port (if applicable). */
 	obj = prop_dictionary_get(natdict, "translation-ip");
 	np->n_addr_sz = prop_data_size(obj);
-	KASSERT(np->n_addr_sz > 0 && np->n_addr_sz <= sizeof(npf_addr_t));
+	if (np->n_addr_sz == 0 || np->n_addr_sz > sizeof(npf_addr_t)) {
+		goto err;
+	}
 	memcpy(&np->n_taddr, prop_data_data_nocopy(obj), np->n_addr_sz);
+	prop_dictionary_get_uint8(natdict, "translation-mask", &np->n_tmask);
+	prop_dictionary_get_uint16(natdict, "translation-port", &np->n_tport);
 
-	/* Translation port (for redirect case). */
-	prop_dictionary_get_uint16(natdict, "translation-port", &np->n_tport);
+	prop_dictionary_get_uint32(natdict, "translation-algo", &np->n_algo);
+	switch (np->n_algo) {
+	case NPF_ALGO_NPT66:
+		prop_dictionary_get_uint16(natdict, "npt66-adjustment",
+		    &np->n_npt66_adj);
+		break;
+	default:
+		if (np->n_tmask != NPF_NO_NETMASK)
+			goto err;
+		break;
+	}
 
 	/* Determine if port map is needed. */
 	np->n_portmap = NULL;
@@ -223,6 +241,9 @@
 		KASSERT(np->n_portmap != NULL);
 	}
 	return np;
+err:
+	kmem_free(np, sizeof(npf_natpolicy_t));
+	return NULL;
 }
 
 /*
@@ -607,6 +628,29 @@
 }
 
 /*
+ * npf_nat_algo: perform the translation given the algorithm.
+ */
+static inline int 
+npf_nat_algo(npf_cache_t *npc, const npf_natpolicy_t *np, bool forw)
+{
+	u_int which;
+	int error;
+
+	switch (np->n_algo) {
+	case NPF_ALGO_NPT66:
+		which = npf_nat_which(np->n_type, forw);
+		error = npf_npt66_rwr(npc, which, &np->n_taddr,
+		    np->n_tmask, np->n_npt66_adj);
+		break;
+	default:
+		error = npf_nat_rwr(npc, np, &np->n_taddr, np->n_tport, forw);
+		break;
+	}
+
+	return error;
+}	
+
+/*
  * npf_do_nat:
  *	- Inspect packet for a NAT policy, unless a session with a NAT
  *	  association already exists.  In such case, determine whether it
@@ -656,7 +700,7 @@
 		if (nbuf_cksum_barrier(nbuf, di)) {
 			npf_recache(npc, nbuf);
 		}
-		error = npf_nat_rwr(npc, np, &np->n_taddr, np->n_tport, forw);
+		error = npf_nat_algo(npc, np, forw);
 		atomic_dec_uint(&np->n_refcnt);
 		return error;
 	}
--- a/usr.sbin/npf/npfctl/npf_build.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_build.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_build.c,v 1.35 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf_build.c,v 1.36 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2014 The NetBSD Foundation, Inc.
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_build.c,v 1.35 2014/02/07 23:45:22 rmind Exp $");
+__RCSID("$NetBSD: npf_build.c,v 1.36 2014/02/13 03:34:40 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/mman.h>
@@ -533,24 +533,15 @@
  * npfctl_build_nat: create a single NAT policy of a specified
  * type with a given filter options.
  */
-static void
-npfctl_build_nat(int type, const char *ifname, sa_family_t family,
-    const addr_port_t *ap, const filt_opts_t *fopts, u_int flags)
+static nl_nat_t *
+npfctl_build_nat(int type, const char *ifname, const addr_port_t *ap,
+    const filt_opts_t *fopts, u_int flags)
 {
 	const opt_proto_t op = { .op_proto = -1, .op_opts = NULL };
-	fam_addr_mask_t *am;
+	fam_addr_mask_t *am = npfctl_get_singlefam(ap->ap_netaddr);
 	in_port_t port;
 	nl_nat_t *nat;
 
-	if (!ap->ap_netaddr) {
-		yyerror("%s network segment is not specified",
-		    type == NPF_NATIN ? "inbound" : "outbound");
-	}
-	am = npfctl_get_singlefam(ap->ap_netaddr);
-	if (am->fam_family != family) {
-		yyerror("IPv6 NAT is not supported");
-	}
-
 	if (ap->ap_portrange) {
 		port = npfctl_get_singleport(ap->ap_portrange);
 		flags &= ~NPF_NAT_PORTMAP;
@@ -559,10 +550,11 @@
 		port = 0;
 	}
 
-	nat = npf_nat_create(type, flags, ifname,
-	    &am->fam_addr, am->fam_family, port);
-	npfctl_build_code(nat, family, &op, fopts);
+	nat = npf_nat_create(type, flags, ifname, am->fam_family,
+	    &am->fam_addr, am->fam_mask, port);
+	npfctl_build_code(nat, am->fam_family, &op, fopts);
 	npf_nat_insert(npf_conf, nat, NPF_PRI_LAST);
+	return nat;
 }
 
 /*
@@ -571,10 +563,12 @@
 void
 npfctl_build_natseg(int sd, int type, const char *ifname,
     const addr_port_t *ap1, const addr_port_t *ap2,
-    const filt_opts_t *fopts)
+    const filt_opts_t *fopts, u_int algo)
 {
-	sa_family_t af = AF_INET;
+	fam_addr_mask_t *am1 = NULL, *am2 = NULL;
+	nl_nat_t *nt1 = NULL, *nt2 = NULL;
 	filt_opts_t imfopts;
+	uint16_t adj = 0;
 	u_int flags;
 	bool binat;
 
@@ -603,6 +597,38 @@
 	}
 
 	/*
+	 * Validate the mappings and their configuration.
+	 */
+
+	if ((type & NPF_NATIN) != 0) {
+		if (!ap1->ap_netaddr)
+			yyerror("inbound network segment is not specified");
+		am1 = npfctl_get_singlefam(ap1->ap_netaddr);
+	}
+	if ((type & NPF_NATOUT) != 0) {
+		if (!ap2->ap_netaddr)
+			yyerror("outbound network segment is not specified");
+		am2 = npfctl_get_singlefam(ap2->ap_netaddr);
+	}
+
+	switch (algo) {
+	case NPF_ALGO_NPT66:
+		if (am1 == NULL || am2 == NULL)
+			yyerror("1:1 mapping of two segments must be "
+			    "used for NPTv6");
+		if (am1->fam_mask != am2->fam_mask)
+			yyerror("asymmetric translation is not supported");
+		adj = npfctl_npt66_calcadj(am1->fam_mask,
+		    &am1->fam_addr, &am2->fam_addr);
+		break;
+	default:
+		if ((am1 && am1->fam_mask != NPF_NO_NETMASK) ||
+		    (am2 && am2->fam_mask != NPF_NO_NETMASK))
+			yyerror("net-to-net translation is not supported");
+		break;
+	}
+
+	/*
 	 * If the filter criteria is not specified explicitly, apply implicit
 	 * filtering according to the given network segments.
 	 *
@@ -615,12 +641,17 @@
 	if (type & NPF_NATIN) {
 		memset(&imfopts, 0, sizeof(filt_opts_t));
 		memcpy(&imfopts.fo_to, ap2, sizeof(addr_port_t));
-		npfctl_build_nat(NPF_NATIN, ifname, af, ap1, fopts, flags);
+		nt1 = npfctl_build_nat(NPF_NATIN, ifname, ap1, fopts, flags);
 	}
 	if (type & NPF_NATOUT) {
 		memset(&imfopts, 0, sizeof(filt_opts_t));
 		memcpy(&imfopts.fo_from, ap1, sizeof(addr_port_t));
-		npfctl_build_nat(NPF_NATOUT, ifname, af, ap2, fopts, flags);
+		nt2 = npfctl_build_nat(NPF_NATOUT, ifname, ap2, fopts, flags);
+	}
+
+	if (algo == NPF_ALGO_NPT66) {
+		npf_nat_setnpt66(nt1, ~adj);
+		npf_nat_setnpt66(nt2, adj);
 	}
 }
 
--- a/usr.sbin/npf/npfctl/npf_data.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_data.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_data.c,v 1.24 2014/02/03 02:21:52 rmind Exp $	*/
+/*	$NetBSD: npf_data.c,v 1.25 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2009-2012 The NetBSD Foundation, Inc.
+ * Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_data.c,v 1.24 2014/02/03 02:21:52 rmind Exp $");
+__RCSID("$NetBSD: npf_data.c,v 1.25 2014/02/13 03:34:40 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/null.h>
@@ -585,3 +585,49 @@
 	npfvar_destroy(vp);
 	return NULL;
 }
+
+/*
+ * npfctl_npt66_calcadj: calculate the adjustment for NPTv6 as per RFC 6296.
+ */
+uint16_t
+npfctl_npt66_calcadj(npf_netmask_t len, const npf_addr_t *pref_in,
+    const npf_addr_t *pref_out)
+{
+	const uint16_t *addr6_in = (const uint16_t *)pref_in;
+	const uint16_t *addr6_out = (const uint16_t *)pref_out;
+	unsigned i, remnant, wordmask, preflen = len >> 4;
+	uint32_t adj, isum = 0, osum = 0;
+
+	/*
+	 * Extract the bits within a 16-bit word (when prefix length is
+	 * not dividable by 16) and include them into the sum.
+	 */
+	remnant = len - (preflen << 4);
+	wordmask = (1U << remnant) - 1;
+	assert(wordmask == 0 || (len % 16) != 0);
+
+	/* Inner prefix - sum and fold. */
+	for (i = 0; i < preflen; i++) {
+		isum += addr6_in[i];
+	}
+	isum += addr6_in[i] & wordmask;
+	while (isum >> 16) {
+		isum = (isum >> 16) + (isum & 0xffff);
+	}
+
+	/* Outer prefix - sum and fold. */
+	for (i = 0; i < preflen; i++) {
+		osum += addr6_out[i];
+	}
+	osum += addr6_out[i] & wordmask;
+	while (osum >> 16) {
+		osum = (osum >> 16) + (osum & 0xffff);
+	}
+
+	/* Calculate 1's complement difference. */
+	adj = isum + ~osum;
+	while (adj >> 16) {
+		adj = (adj >> 16) + (adj & 0xffff);
+	}
+	return (uint16_t)adj;
+}
--- a/usr.sbin/npf/npfctl/npf_parse.y	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_parse.y	Thu Feb 13 03:34:40 2014 +0000
@@ -1,7 +1,7 @@
-/*	$NetBSD: npf_parse.y,v 1.31 2014/02/08 01:20:09 rmind Exp $	*/
+/*	$NetBSD: npf_parse.y,v 1.32 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
- * Copyright (c) 2011-2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2011-2014 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
@@ -84,6 +84,7 @@
 %}
 
 %token			ALG
+%token			ALGO
 %token			ALL
 %token			ANY
 %token			APPLY
@@ -115,6 +116,7 @@
 %token			MAP
 %token			MINUS
 %token			NAME
+%token			NPT66
 %token			ON
 %token			OUT
 %token			PAR_CLOSE
@@ -156,7 +158,8 @@
 %type	<str>		proc_param_val, opt_apply, ifname, on_ifname, ifref
 %type	<num>		port, opt_final, number, afamily, opt_family
 %type	<num>		block_or_pass, rule_dir, group_dir, block_opts
-%type	<num>		opt_stateful, icmp_type, table_type, map_sd, map_type
+%type	<num>		opt_stateful, icmp_type, table_type
+%type	<num>		map_sd, map_algo, map_type
 %type	<var>		ifaddrs, addr_or_ifaddr, port_range, icmp_type_and_code
 %type	<var>		filt_addr, addr_and_mask, tcp_flags, tcp_flags_and_mask
 %type	<var>		procs, proc_call, proc_param_list, proc_param
@@ -296,6 +299,11 @@
 	|		{ $$ = NPFCTL_NAT_DYNAMIC; }
 	;
 
+map_algo
+	: ALGO NPT66	{ $$ = NPF_ALGO_NPT66; }
+	|		{ $$ = 0; }
+	;
+
 map_type
 	: ARROWBOTH	{ $$ = NPF_NATIN | NPF_NATOUT; }
 	| ARROWLEFT	{ $$ = NPF_NATIN; }
@@ -311,13 +319,13 @@
 	;
 
 map
-	: MAP ifref map_sd mapseg map_type mapseg PASS filt_opts
+	: MAP ifref map_sd map_algo mapseg map_type mapseg PASS filt_opts
 	{
-		npfctl_build_natseg($3, $5, $2, &$4, &$6, &$8);
+		npfctl_build_natseg($3, $6, $2, &$5, &$7, &$9, $4);
 	}
-	| MAP ifref map_sd mapseg map_type mapseg
+	| MAP ifref map_sd map_algo mapseg map_type mapseg
 	{
-		npfctl_build_natseg($3, $5, $2, &$4, &$6, NULL);
+		npfctl_build_natseg($3, $6, $2, &$5, &$7, NULL, $4);
 	}
 	| MAP RULESET group_opts
 	{
--- a/usr.sbin/npf/npfctl/npf_scan.l	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_scan.l	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_scan.l,v 1.18 2014/02/08 01:20:09 rmind Exp $	*/
+/*	$NetBSD: npf_scan.l,v 1.19 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2012 The NetBSD Foundation, Inc.
@@ -99,6 +99,8 @@
 "<->"			return ARROWBOTH;
 "<-"			return ARROWLEFT;
 "->"			return ARROWRIGHT;
+algo			return ALGO;
+npt66			return NPT66;
 "-"			return MINUS;
 procedure		return PROCEDURE;
 \\\n			yylineno++; yycolumn = 0;
--- a/usr.sbin/npf/npfctl/npfctl.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npfctl/npfctl.h	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npfctl.h,v 1.35 2014/02/03 02:21:52 rmind Exp $	*/
+/*	$NetBSD: npfctl.h,v 1.36 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2013 The NetBSD Foundation, Inc.
@@ -126,6 +126,8 @@
 npfvar_t *	npfctl_parse_fam_addr_mask(const char *, const char *,
 		    unsigned long *);
 bool		npfctl_parse_cidr(char *, fam_addr_mask_t *, int *);
+uint16_t	npfctl_npt66_calcadj(npf_netmask_t, const npf_addr_t *,
+		    const npf_addr_t *);
 
 /*
  * NPF extension loading.
@@ -193,7 +195,7 @@
 		    const char *, const char *);
 void		npfctl_build_natseg(int, int, const char *,
 		    const addr_port_t *, const addr_port_t *,
-		    const filt_opts_t *);
+		    const filt_opts_t *, unsigned);
 void		npfctl_build_maprset(const char *, int, const char *);
 void		npfctl_build_table(const char *, u_int, const char *);
 
--- a/usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_mbuf_subr.c	Thu Feb 13 03:34:40 2014 +0000
@@ -136,6 +136,15 @@
 	return (void *)(iphdr + 1);
 }
 
+void *
+mbuf_return_hdrs6(struct mbuf *m, struct ip6_hdr **ip6)
+{
+	struct ip6_hdr *ip6hdr = mtod(m, struct ip6_hdr *);
+
+	*ip6 = ip6hdr;
+	return (void *)(ip6hdr + 1);
+}
+
 void
 mbuf_icmp_append(struct mbuf *m, struct mbuf *m_orig)
 {
--- a/usr.sbin/npf/npftest/libnpftest/npf_nat_test.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_nat_test.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_nat_test.c,v 1.7 2014/02/07 23:45:22 rmind Exp $	*/
+/*	$NetBSD: npf_nat_test.c,v 1.8 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*
  * NPF NAT test.
@@ -27,6 +27,7 @@
 	const char *	ifname;
 	int		di;
 	int		ret;
+	int		af;
 	const char *	taddr;
 	in_port_t	tport;
 } test_cases[] = {
@@ -38,32 +39,32 @@
 	{
 		LOCAL_IP1,	15000,		REMOTE_IP1,	7000,
 		NPF_NATOUT,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP1,	RANDOM_PORT
+		RESULT_PASS,	AF_INET,	PUB_IP1,	RANDOM_PORT
 	},
 	{
 		LOCAL_IP1,	15000,		REMOTE_IP1,	7000,
 		NPF_NATOUT,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP1,	RANDOM_PORT
+		RESULT_PASS,	AF_INET,	PUB_IP1,	RANDOM_PORT
 	},
 	{
 		LOCAL_IP1,	15000,		REMOTE_IP1,	7000,
 		NPF_NATOUT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_BLOCK,	NULL,		0
+		RESULT_BLOCK,	AF_INET,	NULL,		0
 	},
 	{
 		REMOTE_IP1,	7000,		LOCAL_IP1,	15000,
 		NPF_NATOUT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_BLOCK,	NULL,		0
+		RESULT_BLOCK,	AF_INET,	NULL,		0
 	},
 	{
 		REMOTE_IP1,	7000,		PUB_IP1,	RANDOM_PORT,
 		NPF_NATOUT,	IFNAME_INT,	PFIL_IN,
-		RESULT_BLOCK,	NULL,		0
+		RESULT_BLOCK,	AF_INET,	NULL,		0
 	},
 	{
 		REMOTE_IP1,	7000,		PUB_IP1,	RANDOM_PORT,
 		NPF_NATOUT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_PASS,	LOCAL_IP1,	15000
+		RESULT_PASS,	AF_INET,	LOCAL_IP1,	15000
 	},
 
 	/*
@@ -73,12 +74,12 @@
 	{
 		REMOTE_IP2,	16000,		PUB_IP1,	8000,
 		NPF_NATIN,	IFNAME_EXT,	PFIL_IN,
-		RESULT_PASS,	LOCAL_IP1,	6000
+		RESULT_PASS,	AF_INET,	LOCAL_IP1,	6000
 	},
 	{
 		LOCAL_IP1,	6000,		REMOTE_IP2,	16000,
 		NPF_NATIN,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP1,	8000
+		RESULT_PASS,	AF_INET,	PUB_IP1,	8000
 	},
 
 	/*
@@ -88,22 +89,22 @@
 	{
 		REMOTE_IP2,	17000,		PUB_IP2,	9000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_PASS,	LOCAL_IP2,	9000
+		RESULT_PASS,	AF_INET,	LOCAL_IP2,	9000
 	},
 	{
 		LOCAL_IP2,	9000,		REMOTE_IP2,	17000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP2,	9000
+		RESULT_PASS,	AF_INET,	PUB_IP2,	9000
 	},
 	{
 		LOCAL_IP2,	18000,		REMOTE_IP2,	9000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP2,	18000
+		RESULT_PASS,	AF_INET,	PUB_IP2,	18000
 	},
 	{
 		REMOTE_IP2,	9000,		PUB_IP2,	18000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_PASS,	LOCAL_IP2,	18000
+		RESULT_PASS,	AF_INET,	LOCAL_IP2,	18000
 	},
 
 	/*
@@ -113,21 +114,40 @@
 	{
 		LOCAL_IP3,	19000,		REMOTE_IP3,	10000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_OUT,
-		RESULT_PASS,	PUB_IP3,	19000
+		RESULT_PASS,	AF_INET,	PUB_IP3,	19000
 	},
 	{
 		REMOTE_IP3,	10000,		PUB_IP3,	19000,
 		NPF_BINAT,	IFNAME_EXT,	PFIL_IN,
-		RESULT_PASS,	LOCAL_IP3,	19000
+		RESULT_PASS,	AF_INET,	LOCAL_IP3,	19000
+	},
+
+	/*
+	 * NPTv6 case:
+	 *	map $ext_if static algo npt66 $net6_inner <-> $net6_outer
+	 */
+	{
+		LOCAL_IP6,	1000,		REMOTE_IP6,	1001,
+		NPF_BINAT,	IFNAME_EXT,	PFIL_OUT,
+		RESULT_PASS,	AF_INET6,	EXPECTED_IP6,	1000
+	},
+	{
+		REMOTE_IP6,	1001,		EXPECTED_IP6,	1000,
+		NPF_BINAT,	IFNAME_EXT,	PFIL_IN,
+		RESULT_PASS,	AF_INET6,	LOCAL_IP6,	1000
 	},
 
 };
 
 static bool
-nmatch_addr(const char *saddr, const struct in_addr *addr2)
+nmatch_addr(int af, const char *saddr, const npf_addr_t *addr2)
 {
-	const in_addr_t addr1 = inet_addr(saddr);
-	return memcmp(&addr1, &addr2->s_addr, sizeof(in_addr_t)) != 0;
+	npf_addr_t addr1;
+	size_t len;
+
+	npf_inet_pton(af, saddr, &addr1);
+	len = af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr); 
+	return memcmp(&addr1, addr2, len) != 0;
 }
 
 static bool
@@ -135,6 +155,7 @@
 {
 	const struct test_case *t = &test_cases[i];
 	npf_cache_t npc = { .npc_info = 0 };
+	const int af = t->af;
 	nbuf_t nbuf;
 
 	if (verbose) {
@@ -150,14 +171,17 @@
 		return false;
 	}
 
-	const struct ip *ip = npc.npc_ip.v4;
 	const struct udphdr *uh = npc.npc_l4.udp;
 
 	if (verbose) {
-		printf("\tpost-translation: src %s (%d)",
-		    inet_ntoa(ip->ip_src), ntohs(uh->uh_sport));
-		printf(" dst %s (%d)\n",
-		    inet_ntoa(ip->ip_dst), ntohs(uh->uh_dport));
+		char sbuf[64], dbuf[64];
+
+		npf_inet_ntop(af, npc.npc_ips[NPF_SRC], sbuf, sizeof(sbuf));
+		npf_inet_ntop(af, npc.npc_ips[NPF_DST], dbuf, sizeof(dbuf));
+
+		printf("\tpost-translation:");
+		printf("src %s (%d) ", sbuf, ntohs(uh->uh_sport));
+		printf("dst %s (%d)\n", dbuf, ntohs(uh->uh_dport));
 	}
 	if (error != t->ret) {
 		return false;
@@ -170,10 +194,11 @@
 	in_addr_t dport = forw ? t->dport : t->tport;
 
 	bool defect = false;
-	defect |= nmatch_addr(saddr, &ip->ip_src);
+	defect |= nmatch_addr(af, saddr, npc.npc_ips[NPF_SRC]);
 	defect |= sport != ntohs(uh->uh_sport);
-	defect |= nmatch_addr(daddr, &ip->ip_dst);
+	defect |= nmatch_addr(af, daddr, npc.npc_ips[NPF_DST]);
 	defect |= dport != ntohs(uh->uh_dport);
+
 	return !defect;
 }
 
@@ -181,13 +206,25 @@
 fill_packet(const struct test_case *t)
 {
 	struct mbuf *m;
-	struct ip *ip;
+	void *ipsrc, *ipdst;
 	struct udphdr *uh;
 
-	m = mbuf_construct(IPPROTO_UDP);
-	uh = mbuf_return_hdrs(m, false, &ip);
-	ip->ip_src.s_addr = inet_addr(t->src);
-	ip->ip_dst.s_addr = inet_addr(t->dst);
+	if (t->af == AF_INET6) {
+		struct ip6_hdr *ip6;
+
+		m = mbuf_construct6(IPPROTO_UDP);
+		uh = mbuf_return_hdrs6(m, &ip6);
+		ipsrc = &ip6->ip6_src, ipdst = &ip6->ip6_dst;
+	} else {
+		struct ip *ip;
+
+		m = mbuf_construct(IPPROTO_UDP);
+		uh = mbuf_return_hdrs(m, false, &ip);
+		ipsrc = &ip->ip_src.s_addr, ipdst = &ip->ip_dst.s_addr;
+	}
+
+	npf_inet_pton(t->af, t->src, ipsrc);
+	npf_inet_pton(t->af, t->dst, ipdst);
 	uh->uh_sport = htons(t->sport);
 	uh->uh_dport = htons(t->dport);
 	return m;
--- a/usr.sbin/npf/npftest/libnpftest/npf_test.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_test.h	Thu Feb 13 03:34:40 2014 +0000
@@ -42,7 +42,13 @@
 #define	REMOTE_IP2	"192.0.2.102"
 #define	REMOTE_IP3	"192.0.2.103"
 
-void		npf_test_init(long (*)(void));
+#define	LOCAL_IP6	"fd01:203:405:1::1234"
+#define	REMOTE_IP6	"2001:db8:fefe::1010"
+#define	EXPECTED_IP6	"2001:db8:1:d550::1234"
+
+void		npf_test_init(int (*)(int, const char *, void *),
+		    const char *(*)(int, const void *, char *, socklen_t),
+		    long (*)(void));
 int		npf_test_load(const void *);
 ifnet_t *	npf_test_addif(const char *, bool, bool);
 ifnet_t *	npf_test_getif(const char *);
@@ -56,6 +62,7 @@
 struct mbuf *	mbuf_construct(int);
 struct mbuf *	mbuf_construct6(int);
 void *		mbuf_return_hdrs(struct mbuf *, bool, struct ip **);
+void *		mbuf_return_hdrs6(struct mbuf *, struct ip6_hdr **);
 void		mbuf_icmp_append(struct mbuf *, struct mbuf *);
 
 bool		npf_nbuf_test(bool);
@@ -66,4 +73,7 @@
 bool		npf_rule_test(bool);
 bool		npf_nat_test(bool);
 
+int		npf_inet_pton(int, const char *, void *);
+const char *	npf_inet_ntop(int, const void *, char *, socklen_t);
+
 #endif
--- a/usr.sbin/npf/npftest/libnpftest/npf_test_subr.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_test_subr.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_test_subr.c,v 1.8 2014/02/05 03:49:48 rmind Exp $	*/
+/*	$NetBSD: npf_test_subr.c,v 1.9 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*
  * NPF initialisation and handler routines.
@@ -19,14 +19,21 @@
 static void *		cstream_ptr;
 static bool		cstream_retval;
 
+static long		(*_random_func)(void);
+static int		(*_pton_func)(int, const char *, void *);
+static const char *	(*_ntop_func)(int, const void *, char *, socklen_t);
+
 static void		npf_state_sample(npf_state_t *, bool);
-static long		(*npf_random_func)(void) = NULL;
 
 void
-npf_test_init(long (*rndfunc)(void))
+npf_test_init(int (*pton_func)(int, const char *, void *),
+    const char *(*ntop_func)(int, const void *, char *, socklen_t),
+    long (*rndfunc)(void))
 {
 	npf_state_setsampler(npf_state_sample);
-	npf_random_func = rndfunc;
+	_pton_func = pton_func;
+	_ntop_func = ntop_func;
+	_random_func = rndfunc;
 }
 
 int
@@ -118,11 +125,23 @@
 	return 0;
 }
 
+int
+npf_inet_pton(int af, const char *src, void *dst)
+{
+	return _pton_func(af, src, dst);
+}
+
+const char *
+npf_inet_ntop(int af, const void *src, char *dst, socklen_t size)
+{
+	return _ntop_func(af, src, dst, size);
+}
+
 /*
  * Need to override for cprng_fast32() -- we need deterministic PRNG.
  */
 uint32_t
 _arc4random(void)
 {
-	return (uint32_t)(npf_random_func ? npf_random_func() : random());
+	return (uint32_t)(_random_func ? _random_func() : random());
 }
--- a/usr.sbin/npf/npftest/npftest.c	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/npftest.c	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npftest.c,v 1.16 2014/02/06 02:51:28 rmind Exp $	*/
+/*	$NetBSD: npftest.c,v 1.17 2014/02/13 03:34:40 rmind Exp $	*/
 
 /*
  * NPF testing framework.
@@ -255,7 +255,7 @@
 	rump_init();
 	rump_schedule();
 
-	rumpns_npf_test_init(random);
+	rumpns_npf_test_init(inet_pton, inet_ntop, random);
 
 	if (config) {
 		load_npf_config(config);
--- a/usr.sbin/npf/npftest/npftest.conf	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/npftest.conf	Thu Feb 13 03:34:40 2014 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: npftest.conf,v 1.4 2014/02/07 23:45:22 rmind Exp $
+# $NetBSD: npftest.conf,v 1.5 2014/02/13 03:34:40 rmind Exp $
 
 $ext_if = "npftest0"
 $int_if = "npftest1"
@@ -19,15 +19,23 @@
 $local_net = { 10.1.1.0/24 }
 $ports = { 8000, 9000 }
 
+$net6_inner = fd01:203:405::/48
+$net6_outer = 2001:db8:1::/48
+
 map $ext_if static $local_ip3 <-> $pub_ip3
 map $ext_if dynamic $local_ip2 <-> $pub_ip2
 map $ext_if dynamic $local_net -> $pub_ip1
 map $ext_if dynamic $local_ip1 port 6000 <- $pub_ip1 port 8000
 
+map $ext_if static algo npt66 $net6_inner <-> $net6_outer
+
 group "ext" on $ext_if {
 	pass out final from $local_ip3
 	pass in final to $pub_ip3
 
+	pass out final from $net6_inner
+	pass in final to $net6_outer
+
 	pass stateful out final proto tcp flags S/SA all
 	pass stateful out final from $local_net
 	pass stateful in final to any port $ports
--- a/usr.sbin/npf/npftest/npftest.h	Thu Feb 13 00:42:01 2014 +0000
+++ b/usr.sbin/npf/npftest/npftest.h	Thu Feb 13 03:34:40 2014 +0000
@@ -12,7 +12,9 @@
 
 #include <net/if.h>
 
-void		rumpns_npf_test_init(long (*)(void));
+void		rumpns_npf_test_init(int (*)(int, const char *, void *),
+		    const char *(*)(int, const void *, char *, socklen_t),
+		    long (*)(void));
 int		rumpns_npf_test_load(const void *);
 ifnet_t *	rumpns_npf_test_addif(const char *, bool, bool);
 ifnet_t *	rumpns_npf_test_getif(const char *);