NPF: add support for static (stateless) NAT. trunk
authorrmind <rmind@NetBSD.org>
Fri, 07 Feb 2014 23:45:22 +0000
branchtrunk
changeset 224472 b463d4c6d43d
parent 224471 845d2033205f
child 224473 bd37be841fb5
NPF: add support for static (stateless) NAT.
lib/libnpf/npf.c
lib/libnpf/npf.h
sys/net/npf/npf.h
sys/net/npf/npf_nat.c
usr.sbin/npf/npfctl/npf_build.c
usr.sbin/npf/npfctl/npf_show.c
usr.sbin/npf/npftest/libnpftest/npf_nat_test.c
usr.sbin/npf/npftest/libnpftest/npf_test.h
usr.sbin/npf/npftest/npftest.conf
--- a/lib/libnpf/npf.c	Fri Feb 07 23:18:04 2014 +0000
+++ b/lib/libnpf/npf.c	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.c,v 1.26 2014/02/06 02:51:28 rmind Exp $	*/
+/*	$NetBSD: npf.c,v 1.27 2014/02/07 23:45:22 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.26 2014/02/06 02:51:28 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.27 2014/02/07 23:45:22 rmind Exp $");
 
 #include <sys/types.h>
 #include <netinet/in_systm.h>
@@ -874,6 +874,16 @@
 	return type;
 }
 
+u_int
+npf_nat_getflags(nl_nat_t *nt)
+{
+	prop_dictionary_t rldict = nt->nrl_dict;
+	unsigned flags = 0;
+
+	prop_dictionary_get_uint32(rldict, "flags", &flags);
+	return flags;
+}
+
 void
 npf_nat_getmap(nl_nat_t *nt, npf_addr_t *addr, size_t *alen, in_port_t *port)
 {
--- a/lib/libnpf/npf.h	Fri Feb 07 23:18:04 2014 +0000
+++ b/lib/libnpf/npf.h	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.23 2014/02/06 02:51:28 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.24 2014/02/07 23:45:22 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2011-2013 The NetBSD Foundation, Inc.
@@ -136,6 +136,7 @@
 
 nl_nat_t *	npf_nat_iterate(nl_config_t *);
 int		npf_nat_gettype(nl_nat_t *);
+unsigned	npf_nat_getflags(nl_nat_t *);
 void		npf_nat_getmap(nl_nat_t *, npf_addr_t *, size_t *, in_port_t *);
 
 nl_rproc_t *	npf_rproc_iterate(nl_config_t *);
--- a/sys/net/npf/npf.h	Fri Feb 07 23:18:04 2014 +0000
+++ b/sys/net/npf/npf.h	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf.h,v 1.35 2014/02/06 02:51:28 rmind Exp $	*/
+/*	$NetBSD: npf.h,v 1.36 2014/02/07 23:45:22 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2009-2013 The NetBSD Foundation, Inc.
@@ -87,7 +87,7 @@
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
 
-#define	NPC_IP4		0x01	/* Indicates fetched IPv4 header. */
+#define	NPC_IP4		0x01	/* Indicates IPv4 header. */
 #define	NPC_IP6		0x02	/* Indicates IPv6 header. */
 #define	NPC_IPFRAG	0x04	/* IPv4/IPv6 fragment. */
 #define	NPC_LAYER4	0x08	/* Layer 4 has been fetched. */
@@ -235,6 +235,7 @@
 
 #define	NPF_NAT_PORTS			0x01
 #define	NPF_NAT_PORTMAP			0x02
+#define	NPF_NAT_STATIC			0x04
 
 /* Table types. */
 #define	NPF_TABLE_HASH			1
--- a/sys/net/npf/npf_nat.c	Fri Feb 07 23:18:04 2014 +0000
+++ b/sys/net/npf/npf_nat.c	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_nat.c,v 1.23 2013/12/06 01:33:37 rmind Exp $	*/
+/*	$NetBSD: npf_nat.c,v 1.24 2014/02/07 23:45:22 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2010-2013 The NetBSD Foundation, Inc.
@@ -70,7 +70,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.23 2013/12/06 01:33:37 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_nat.c,v 1.24 2014/02/07 23:45:22 rmind Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -423,8 +423,8 @@
 {
 	/*
 	 * Outbound NAT rewrites:
-	 * - Source on "forwards" stream.
-	 * - Destination on "backwards" stream.
+	 * - Source (NPF_SRC) on "forwards" stream.
+	 * - Destination (NPF_DST) on "backwards" stream.
 	 * Inbound NAT is other way round.
 	 */
 	if (type == NPF_NATOUT) {
@@ -433,7 +433,7 @@
 		KASSERT(type == NPF_NATIN);
 	}
 	CTASSERT(NPF_SRC == 0 && NPF_DST == 1);
-	KASSERT(forw == 0 || forw == 1);
+	KASSERT(forw == NPF_SRC || forw == NPF_DST);
 	return (u_int)forw;
 }
 
@@ -528,14 +528,56 @@
 }
 
 /*
- * npf_nat_translate: perform address and/or port translation.
+ * npf_nat_rwr: perform address and/or port translation.
+ */
+static int
+npf_nat_rwr(npf_cache_t *npc, const npf_natpolicy_t *np,
+    const npf_addr_t *addr, const in_addr_t port, bool forw)
+{
+	const unsigned proto = npc->npc_proto;
+	const u_int which = npf_nat_which(np->n_type, forw);
+
+	/*
+	 * Rewrite IP and/or TCP/UDP checksums first, since we need the
+	 * current (old) address/port for the calculations.  Then perform
+	 * the address translation i.e. rewrite source or destination.
+	 */
+	if (!npf_rwrcksum(npc, which, addr, port)) {
+		return EINVAL;
+	}
+	if (!npf_rwrip(npc, which, addr)) {
+		return EINVAL;
+	}
+	if ((np->n_flags & NPF_NAT_PORTS) == 0) {
+		/* Done. */
+		return 0;
+	}
+
+	switch (proto) {
+	case IPPROTO_TCP:
+	case IPPROTO_UDP:
+		/* Rewrite source/destination port. */
+		if (!npf_rwrport(npc, which, port)) {
+			return EINVAL;
+		}
+		break;
+	case IPPROTO_ICMP:
+		KASSERT(npf_iscached(npc, NPC_ICMP));
+		/* Nothing. */
+		break;
+	default:
+		return ENOTSUP;
+	}
+	return 0;
+}
+
+/*
+ * npf_nat_translate: perform translation given the state data.
  */
 int
 npf_nat_translate(npf_cache_t *npc, nbuf_t *nbuf, npf_nat_t *nt, bool forw)
 {
-	const int proto = npc->npc_proto;
 	const npf_natpolicy_t *np = nt->nt_natpolicy;
-	const u_int which = npf_nat_which(np->n_type, forw);
 	const npf_addr_t *addr;
 	in_port_t port;
 
@@ -560,39 +602,8 @@
 		npf_alg_exec(npc, nbuf, nt, forw);
 	}
 
-	/*
-	 * Rewrite IP and/or TCP/UDP checksums first, since we need the
-	 * current (old) address/port for the calculations.  Then perform
-	 * the address translation i.e. rewrite source or destination.
-	 */
-	if (!npf_rwrcksum(npc, which, addr, port)) {
-		return EINVAL;
-	}
-	if (!npf_rwrip(npc, which, addr)) {
-		return EINVAL;
-	}
-
-	if ((np->n_flags & NPF_NAT_PORTS) == 0) {
-		/* Done. */
-		return 0;
-	}
-
-	switch (proto) {
-	case IPPROTO_TCP:
-	case IPPROTO_UDP:
-		/* Rewrite source/destination port. */
-		if (!npf_rwrport(npc, which, port)) {
-			return EINVAL;
-		}
-		break;
-	case IPPROTO_ICMP:
-		KASSERT(npf_iscached(npc, NPC_ICMP));
-		/* Nothing. */
-		break;
-	default:
-		return ENOTSUP;
-	}
-	return 0;
+	/* Finally, perform the translation. */
+	return npf_nat_rwr(npc, np, addr, port, forw);
 }
 
 /*
@@ -640,6 +651,16 @@
 	}
 	forw = true;
 
+	/* Static NAT - just perform the translation. */
+	if (np->n_flags & NPF_NAT_STATIC) {
+		if (nbuf_cksum_barrier(nbuf, di)) {
+			npf_recache(npc, nbuf);
+		}
+		error = npf_nat_rwr(npc, np, &np->n_taddr, np->n_tport, forw);
+		atomic_dec_uint(&np->n_refcnt);
+		return error;
+	}
+
 	/*
 	 * If there is no local session (no "stateful" rule - unusual, but
 	 * possible configuration), establish one before translation.  Note
@@ -688,11 +709,11 @@
 	/* Perform the translation. */
 	error = npf_nat_translate(npc, nbuf, nt, forw);
 out:
-	if (error && nse) {
-		/* It created for NAT - just expire. */
-		npf_session_expire(nse);
-	}
-	if (nse) {
+	if (__predict_false(nse)) {
+		if (error) {
+			/* It created for NAT - just expire. */
+			npf_session_expire(nse);
+		}
 		npf_session_release(nse);
 	}
 	return error;
--- a/usr.sbin/npf/npfctl/npf_build.c	Fri Feb 07 23:18:04 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_build.c	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_build.c,v 1.34 2014/02/06 18:48:09 christos Exp $	*/
+/*	$NetBSD: npf_build.c,v 1.35 2014/02/07 23:45:22 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.34 2014/02/06 18:48:09 christos Exp $");
+__RCSID("$NetBSD: npf_build.c,v 1.35 2014/02/07 23:45:22 rmind Exp $");
 
 #include <sys/types.h>
 #include <sys/mman.h>
@@ -535,7 +535,7 @@
  */
 static void
 npfctl_build_nat(int type, const char *ifname, sa_family_t family,
-    const addr_port_t *ap, const filt_opts_t *fopts, bool binat)
+    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;
@@ -551,36 +551,16 @@
 		yyerror("IPv6 NAT is not supported");
 	}
 
-	switch (type) {
-	case NPF_NATOUT:
-		/*
-		 * Outbound NAT (or source NAT) policy, usually used for the
-		 * traditional NAPT.  If it is a half for bi-directional NAT,
-		 * then no port translation with mapping.
-		 */
-		nat = npf_nat_create(NPF_NATOUT, !binat ?
-		    (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0,
-		    ifname, &am->fam_addr, am->fam_family, 0);
-		break;
-	case NPF_NATIN:
-		/*
-		 * Inbound NAT (or destination NAT).  Unless bi-NAT, a port
-		 * must be specified, since it has to be redirection.
-		 */
+	if (ap->ap_portrange) {
+		port = npfctl_get_singleport(ap->ap_portrange);
+		flags &= ~NPF_NAT_PORTMAP;
+		flags |= NPF_NAT_PORTS;
+	} else {
 		port = 0;
-		if (!binat) {
-			if (!ap->ap_portrange) {
-				yyerror("inbound port is not specified");
-			}
-			port = npfctl_get_singleport(ap->ap_portrange);
-		}
-		nat = npf_nat_create(NPF_NATIN, !binat ? NPF_NAT_PORTS : 0,
-		    ifname, &am->fam_addr, am->fam_family, port);
-		break;
-	default:
-		assert(false);
 	}
 
+	nat = npf_nat_create(type, flags, ifname,
+	    &am->fam_addr, am->fam_family, port);
 	npfctl_build_code(nat, family, &op, fopts);
 	npf_nat_insert(npf_conf, nat, NPF_PRI_LAST);
 }
@@ -595,21 +575,33 @@
 {
 	sa_family_t af = AF_INET;
 	filt_opts_t imfopts;
+	u_int flags;
 	bool binat;
 
-	if (sd == NPFCTL_NAT_STATIC) {
-		yyerror("static NAT is not yet supported");
-	}
-	assert(sd == NPFCTL_NAT_DYNAMIC);
 	assert(ifname != NULL);
 
 	/*
 	 * Bi-directional NAT is a combination of inbound NAT and outbound
-	 * NAT policies.  Note that the translation address is local IP and
-	 * the filter criteria is inverted accordingly.
+	 * NAT policies with the translation segments inverted respectively.
 	 */
 	binat = (NPF_NATIN | NPF_NATOUT) == type;
 
+	switch (sd) {
+	case NPFCTL_NAT_DYNAMIC:
+		/*
+		 * Dynamic NAT: traditional NAPT is expected.  Unless it
+		 * is bi-directional NAT, perform port mapping.
+		 */
+		flags = !binat ? (NPF_NAT_PORTS | NPF_NAT_PORTMAP) : 0;
+		break;
+	case NPFCTL_NAT_STATIC:
+		/* Static NAT: mechanic translation. */
+		flags = NPF_NAT_STATIC;
+		break;
+	default:
+		abort();
+	}
+
 	/*
 	 * If the filter criteria is not specified explicitly, apply implicit
 	 * filtering according to the given network segments.
@@ -623,12 +615,12 @@
 	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, binat);
+		npfctl_build_nat(NPF_NATIN, ifname, af, 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, binat);
+		npfctl_build_nat(NPF_NATOUT, ifname, af, ap2, fopts, flags);
 	}
 }
 
--- a/usr.sbin/npf/npfctl/npf_show.c	Fri Feb 07 23:18:04 2014 +0000
+++ b/usr.sbin/npf/npfctl/npf_show.c	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_show.c,v 1.8 2013/11/22 18:42:02 rmind Exp $	*/
+/*	$NetBSD: npf_show.c,v 1.9 2014/02/07 23:45:22 rmind Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -36,7 +36,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_show.c,v 1.8 2013/11/22 18:42:02 rmind Exp $");
+__RCSID("$NetBSD: npf_show.c,v 1.9 2014/02/07 23:45:22 rmind Exp $");
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -378,6 +378,7 @@
 	npf_addr_t addr;
 	in_port_t port;
 	size_t alen;
+	u_int flags;
 	char *seg;
 
 	/* Get the interface. */
@@ -405,12 +406,14 @@
 		seg2 = seg;
 		break;
 	default:
-		assert(false);
+		abort();
 	}
+	flags = npf_nat_getflags(nt);
 
 	/* Print out the NAT policy with the filter criteria. */
-	fprintf(ctx->fp, "map %s dynamic %s %s %s pass ",
-	    ifname, seg1, arrow, seg2);
+	fprintf(ctx->fp, "map %s %s %s %s %s pass ",
+	    ifname, (flags & NPF_NAT_STATIC) ? "static" : "dynamic",
+	    seg1, arrow, seg2);
 	npfctl_print_filter(ctx, rl);
 	fputs("\n", ctx->fp);
 	free(seg);
--- a/usr.sbin/npf/npftest/libnpftest/npf_nat_test.c	Fri Feb 07 23:18:04 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_nat_test.c	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-/*	$NetBSD: npf_nat_test.c,v 1.6 2014/02/05 03:49:48 rmind Exp $	*/
+/*	$NetBSD: npf_nat_test.c,v 1.7 2014/02/07 23:45:22 rmind Exp $	*/
 
 /*
  * NPF NAT test.
@@ -68,7 +68,7 @@
 
 	/*
 	 * NAT redirect (inbound NAT):
-	 *	map $ext_if dynamic $local_ip1 port 8000 <- $pub_ip1 port 8000
+	 *	map $ext_if dynamic $local_ip1 port 6000 <- $pub_ip1 port 8000
 	 */
 	{
 		REMOTE_IP2,	16000,		PUB_IP1,	8000,
@@ -106,6 +106,21 @@
 		RESULT_PASS,	LOCAL_IP2,	18000
 	},
 
+	/*
+	 * Static NAT: plain translation both ways.
+	 *	map $ext_if static $local_ip3 <-> $pub_ip3
+	 */
+	{
+		LOCAL_IP3,	19000,		REMOTE_IP3,	10000,
+		NPF_BINAT,	IFNAME_EXT,	PFIL_OUT,
+		RESULT_PASS,	PUB_IP3,	19000
+	},
+	{
+		REMOTE_IP3,	10000,		PUB_IP3,	19000,
+		NPF_BINAT,	IFNAME_EXT,	PFIL_IN,
+		RESULT_PASS,	LOCAL_IP3,	19000
+	},
+
 };
 
 static bool
--- a/usr.sbin/npf/npftest/libnpftest/npf_test.h	Fri Feb 07 23:18:04 2014 +0000
+++ b/usr.sbin/npf/npftest/libnpftest/npf_test.h	Fri Feb 07 23:45:22 2014 +0000
@@ -36,8 +36,11 @@
 /* Note: RFC 5737 compliant addresses. */
 #define	PUB_IP1		"192.0.2.1"
 #define	PUB_IP2		"192.0.2.2"
-#define	REMOTE_IP1	"192.0.2.3"
-#define	REMOTE_IP2	"192.0.2.4"
+#define	PUB_IP3		"192.0.2.3"
+
+#define	REMOTE_IP1	"192.0.2.101"
+#define	REMOTE_IP2	"192.0.2.102"
+#define	REMOTE_IP3	"192.0.2.103"
 
 void		npf_test_init(long (*)(void));
 int		npf_test_load(const void *);
--- a/usr.sbin/npf/npftest/npftest.conf	Fri Feb 07 23:18:04 2014 +0000
+++ b/usr.sbin/npf/npftest/npftest.conf	Fri Feb 07 23:45:22 2014 +0000
@@ -1,4 +1,4 @@
-# $NetBSD: npftest.conf,v 1.3 2013/09/23 15:30:32 rmind Exp $
+# $NetBSD: npftest.conf,v 1.4 2014/02/07 23:45:22 rmind Exp $
 
 $ext_if = "npftest0"
 $int_if = "npftest1"
@@ -9,6 +9,7 @@
 
 $pub_ip1 = 192.0.2.1
 $pub_ip2 = 192.0.2.2
+$pub_ip3 = 192.0.2.3
 
 $local_ip1 = 10.1.1.1
 $local_ip2 = 10.1.1.2
@@ -18,11 +19,15 @@
 $local_net = { 10.1.1.0/24 }
 $ports = { 8000, 9000 }
 
+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
 
 group "ext" on $ext_if {
+	pass out final from $local_ip3
+	pass in final to $pub_ip3
+
 	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