Module Name:    src
Committed By:   martin
Date:           Fri Oct  4 08:06:35 UTC 2019

Modified Files:
        src/lib/libnpf [netbsd-9]: libnpf.3 npf.c npf.h
        src/sys/net/npf [netbsd-9]: npf_conn.c npf_ctl.c npf_if.c npf_impl.h
            npf_ruleset.c
        src/usr.sbin/npf/npfctl [netbsd-9]: npf.conf.5 npf_build.c npf_parse.y
            npf_scan.l npfctl.8 npfctl.c npfctl.h
        src/usr.sbin/npf/npftest [netbsd-9]: npftest.conf

Log Message:
Pull up following revision(s) (requested by rmind in ticket #282):

        usr.sbin/npf/npfctl/npf_build.c: revision 1.53
        lib/libnpf/npf.c: revision 1.48
        usr.sbin/npf/npfctl/npfctl.h: revision 1.50
        sys/net/npf/npf_impl.h: revision 1.80
        usr.sbin/npf/npfctl/npfctl.h: revision 1.51
        sys/net/npf/npf_ruleset.c: revision 1.49
        usr.sbin/npf/npfctl/npf.conf.5: revision 1.90
        sys/net/npf/npf_ctl.c: revision 1.59
        lib/libnpf/libnpf.3: revision 1.11
        usr.sbin/npf/npfctl/npf_parse.y: revision 1.50
        usr.sbin/npf/npftest/npftest.conf: revision 1.8
        usr.sbin/npf/npfctl/npfctl.c: revision 1.62
        usr.sbin/npf/npfctl/npfctl.c: revision 1.63
        usr.sbin/npf/npfctl/npf_scan.l: revision 1.30
        usr.sbin/npf/npfctl/npfctl.8: revision 1.22
        lib/libnpf/npf.h: revision 1.38
        usr.sbin/npf/npfctl/npfctl.8: revision 1.23
        usr.sbin/npf/npfctl/npfctl.8: revision 1.24
        sys/net/npf/npf_if.c: revision 1.11
        sys/net/npf/npf_if.c: revision 1.12
        usr.sbin/npf/npfctl/npf.conf.5: revision 1.89
        sys/net/npf/npf_conn.c: revision 1.30
        usr.sbin/npf/npfctl/npf_build.c: revision 1.52

npfctl: implement table replace subcommand.
Contributed by Timshel Knoll-Miller.

NPF ifmap: rework and fix a few small bugs.

npfctl: implement table replace subcommand.
Contributed by Timshel Knoll-Miller.
(missed a file in previous commit; cvs is so helpful..)

libnpf/npfctl: support dynamic NAT rulesets using a name prefix.

Use -width Pa for FILES.

Fix pasto in table replace -t type

Use -width Pa for FILES.

npf_ifmap_copylogname: be more defensive.


To generate a diff of this commit:
cvs rdiff -u -r1.9.2.1 -r1.9.2.2 src/lib/libnpf/libnpf.3
cvs rdiff -u -r1.46.2.1 -r1.46.2.2 src/lib/libnpf/npf.c
cvs rdiff -u -r1.36.2.1 -r1.36.2.2 src/lib/libnpf/npf.h
cvs rdiff -u -r1.27.2.1 -r1.27.2.2 src/sys/net/npf/npf_conn.c
cvs rdiff -u -r1.54.2.3 -r1.54.2.4 src/sys/net/npf/npf_ctl.c
cvs rdiff -u -r1.9.4.1 -r1.9.4.2 src/sys/net/npf/npf_if.c
cvs rdiff -u -r1.75.2.3 -r1.75.2.4 src/sys/net/npf/npf_impl.h
cvs rdiff -u -r1.48 -r1.48.2.1 src/sys/net/npf/npf_ruleset.c
cvs rdiff -u -r1.88 -r1.88.2.1 src/usr.sbin/npf/npfctl/npf.conf.5
cvs rdiff -u -r1.50.2.1 -r1.50.2.2 src/usr.sbin/npf/npfctl/npf_build.c
cvs rdiff -u -r1.49 -r1.49.2.1 src/usr.sbin/npf/npfctl/npf_parse.y
cvs rdiff -u -r1.29 -r1.29.2.1 src/usr.sbin/npf/npfctl/npf_scan.l
cvs rdiff -u -r1.21 -r1.21.2.1 src/usr.sbin/npf/npfctl/npfctl.8
cvs rdiff -u -r1.60.2.1 -r1.60.2.2 src/usr.sbin/npf/npfctl/npfctl.c
cvs rdiff -u -r1.48.2.1 -r1.48.2.2 src/usr.sbin/npf/npfctl/npfctl.h
cvs rdiff -u -r1.7 -r1.7.2.1 src/usr.sbin/npf/npftest/npftest.conf

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libnpf/libnpf.3
diff -u src/lib/libnpf/libnpf.3:1.9.2.1 src/lib/libnpf/libnpf.3:1.9.2.2
--- src/lib/libnpf/libnpf.3:1.9.2.1	Sun Sep  1 13:13:13 2019
+++ src/lib/libnpf/libnpf.3	Fri Oct  4 08:06:35 2019
@@ -1,4 +1,4 @@
-.\"	$NetBSD: libnpf.3,v 1.9.2.1 2019/09/01 13:13:13 martin Exp $
+.\"	$NetBSD: libnpf.3,v 1.9.2.2 2019/10/04 08:06:35 martin Exp $
 .\"
 .\" Copyright (c) 2011-2019 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd August 21, 2019
+.Dd August 25, 2019
 .Dt LIBNPF 3
 .Os
 .Sh NAME
@@ -108,6 +108,15 @@
 .Fn npf_table_replace "int fd" "nl_table_t *tl" "npf_error_t *errinfo"
 .Ft void
 .Fn npf_table_destroy "nl_table_t *tl"
+.\" ---
+.Ft int
+.Fn npf_ruleset_add "int fd" "const char *name" "nl_rule_t *rl" "uint64_t *id"
+.Ft int
+.Fn npf_ruleset_remove "int fd" "const char *name" "uint64_t id"
+.Ft int
+.Fn npf_ruleset_remkey "int fd" "const char *name" "const void *key" "size_t len"
+.Ft int
+.Fn npf_ruleset_flush "int fd" "const char *name"
 .\" -----
 .Sh DESCRIPTION
 The
@@ -352,7 +361,9 @@ Additionally,
 may be specified to indicate the translation network;
 otherwise, it should be set to
 .Dv NPF_NO_NETMASK .
-In such case, a custom algorithm may need to be specified using the
+.Pp
+In order to use the translation network, a custom algorithm may need to
+be specified using the
 .Fn npf_nat_setalgo
 function.
 .\" ---
@@ -368,6 +379,9 @@ Currently, the following algorithms are 
 Hash of the source and destination addresses.
 .It Dv NPF_ALGO_RR
 Round-robin for the translation addresses.
+.It Dv NPF_ALGO_NETMAP
+Network-to-network map as described below, but with state tracking.
+It is used when it is necessary to translate the ports.
 .El
 .Pp
 The following are support with static NAT:
@@ -450,6 +464,39 @@ specified by
 Destroy the specified table.
 .El
 .\" -----
+.Ss Ruleset interface
+.Bl -tag -width 4n
+.It Fn npf_ruleset_add "fd" "name" "rl" "id"
+Add a given rule, specified by
+.Fa rl ,
+into the dynamic ruleset named
+.Fa name .
+On success, return 0 and a unique rule ID in the
+.Fa id
+parameter.
+.It Fn npf_ruleset_remove "fd" "name" "id"
+Remove a rule from the dynamic ruleset, specified by
+.Fa name .
+The rule is specified by its unique ID in the
+.Fa id
+parameter.
+.It Fn npf_ruleset_remkey "fd" "name" "key" "len"
+Remove a rule from the dynamic ruleset, specified by
+.Fa name .
+The rule is specified by its key, in the
+.Fa key
+and
+.Fa len
+parameters.
+The key for the rule must have been set during its construction, using the
+.Fn npf_rule_setkey
+routine.
+.It Fn npf_ruleset_flush "fd" "name"
+Clear the dynamic ruleset, specified by
+.Fa name ,
+by removing all its rules.
+.El
+.\" -----
 .Sh SEE ALSO
 .Xr bpf 4 ,
 .Xr npf 7 ,

Index: src/lib/libnpf/npf.c
diff -u src/lib/libnpf/npf.c:1.46.2.1 src/lib/libnpf/npf.c:1.46.2.2
--- src/lib/libnpf/npf.c:1.46.2.1	Sun Sep  1 13:13:13 2019
+++ src/lib/libnpf/npf.c	Fri Oct  4 08:06:35 2019
@@ -28,7 +28,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.46.2.1 2019/09/01 13:13:13 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.46.2.2 2019/10/04 08:06:35 martin Exp $");
 
 #include <sys/types.h>
 #include <sys/mman.h>
@@ -401,14 +401,31 @@ npf_param_set(nl_config_t *ncf, const ch
  * DYNAMIC RULESET INTERFACE.
  */
 
+static inline bool
+_npf_nat_ruleset_p(const char *name)
+{
+	return strncmp(name, NPF_RULESET_MAP_PREF,
+	    sizeof(NPF_RULESET_MAP_PREF) - 1) == 0;
+}
+
 int
 npf_ruleset_add(int fd, const char *rname, nl_rule_t *rl, uint64_t *id)
 {
+	const bool natset = _npf_nat_ruleset_p(rname);
 	nvlist_t *rule_dict = rl->rule_dict;
 	nvlist_t *ret_dict;
 
+	nvlist_add_number(rule_dict, "attr",
+	    NPF_RULE_DYNAMIC | nvlist_take_number(rule_dict, "attr"));
+
+	if (natset && !dnvlist_get_bool(rule_dict, "nat-rule", false)) {
+		errno = EINVAL;
+		return errno;
+	}
 	nvlist_add_string(rule_dict, "ruleset-name", rname);
+	nvlist_add_bool(rule_dict, "nat-ruleset", natset);
 	nvlist_add_number(rule_dict, "command", NPF_CMD_RULE_ADD);
+
 	if (nvlist_xfer_ioctl(fd, IOC_NPF_RULE, rule_dict, &ret_dict) == -1) {
 		return errno;
 	}
@@ -419,11 +436,14 @@ npf_ruleset_add(int fd, const char *rnam
 int
 npf_ruleset_remove(int fd, const char *rname, uint64_t id)
 {
+	const bool natset = _npf_nat_ruleset_p(rname);
 	nvlist_t *rule_dict = nvlist_create(0);
 
 	nvlist_add_string(rule_dict, "ruleset-name", rname);
+	nvlist_add_bool(rule_dict, "nat-ruleset", natset);
 	nvlist_add_number(rule_dict, "command", NPF_CMD_RULE_REMOVE);
 	nvlist_add_number(rule_dict, "id", id);
+
 	if (nvlist_send_ioctl(fd, IOC_NPF_RULE, rule_dict) == -1) {
 		return errno;
 	}
@@ -433,11 +453,14 @@ npf_ruleset_remove(int fd, const char *r
 int
 npf_ruleset_remkey(int fd, const char *rname, const void *key, size_t len)
 {
+	const bool natset = _npf_nat_ruleset_p(rname);
 	nvlist_t *rule_dict = nvlist_create(0);
 
 	nvlist_add_string(rule_dict, "ruleset-name", rname);
+	nvlist_add_bool(rule_dict, "nat-ruleset", natset);
 	nvlist_add_number(rule_dict, "command", NPF_CMD_RULE_REMKEY);
 	nvlist_add_binary(rule_dict, "key", key, len);
+
 	if (nvlist_send_ioctl(fd, IOC_NPF_RULE, rule_dict) == -1) {
 		return errno;
 	}
@@ -447,10 +470,13 @@ npf_ruleset_remkey(int fd, const char *r
 int
 npf_ruleset_flush(int fd, const char *rname)
 {
+	const bool natset = _npf_nat_ruleset_p(rname);
 	nvlist_t *rule_dict = nvlist_create(0);
 
 	nvlist_add_string(rule_dict, "ruleset-name", rname);
+	nvlist_add_bool(rule_dict, "nat-ruleset", natset);
 	nvlist_add_number(rule_dict, "command", NPF_CMD_RULE_FLUSH);
+
 	if (nvlist_send_ioctl(fd, IOC_NPF_RULE, rule_dict) == -1) {
 		return errno;
 	}
@@ -678,10 +704,12 @@ npf_rule_getcode(nl_rule_t *rl, int *typ
 int
 _npf_ruleset_list(int fd, const char *rname, nl_config_t *ncf)
 {
+	const bool natset = _npf_nat_ruleset_p(rname);
 	nvlist_t *req, *ret;
 
 	req = nvlist_create(0);
 	nvlist_add_string(req, "ruleset-name", rname);
+	nvlist_add_bool(req, "nat-ruleset", natset);
 	nvlist_add_number(req, "command", NPF_CMD_RULE_LIST);
 
 	if (nvlist_xfer_ioctl(fd, IOC_NPF_RULE, req, &ret) == -1) {

Index: src/lib/libnpf/npf.h
diff -u src/lib/libnpf/npf.h:1.36.2.1 src/lib/libnpf/npf.h:1.36.2.2
--- src/lib/libnpf/npf.h:1.36.2.1	Sun Sep  1 13:13:13 2019
+++ src/lib/libnpf/npf.h	Fri Oct  4 08:06:35 2019
@@ -56,6 +56,12 @@ typedef struct nl_ext		nl_ext_t;
 typedef signed long		nl_iter_t;
 
 /*
+ * Ruleset prefix(es).
+ */
+
+#define	NPF_RULESET_MAP_PREF	"map:"
+
+/*
  * Extensions API types.
  */
 typedef int (*npfext_initfunc_t)(void);

Index: src/sys/net/npf/npf_conn.c
diff -u src/sys/net/npf/npf_conn.c:1.27.2.1 src/sys/net/npf/npf_conn.c:1.27.2.2
--- src/sys/net/npf/npf_conn.c:1.27.2.1	Wed Aug  7 08:28:37 2019
+++ src/sys/net/npf/npf_conn.c	Fri Oct  4 08:06:35 2019
@@ -107,7 +107,7 @@
 
 #ifdef _KERNEL
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_conn.c,v 1.27.2.1 2019/08/07 08:28:37 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_conn.c,v 1.27.2.2 2019/10/04 08:06:35 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -782,7 +782,8 @@ npf_conn_export(npf_t *npf, npf_conn_t *
 	nvlist_add_number(cdict, "flags", con->c_flags);
 	nvlist_add_number(cdict, "proto", con->c_proto);
 	if (con->c_ifid) {
-		const char *ifname = npf_ifmap_getname(npf, con->c_ifid);
+		char ifname[IFNAMSIZ];
+		npf_ifmap_copyname(npf, con->c_ifid, ifname, sizeof(ifname));
 		nvlist_add_string(cdict, "ifname", ifname);
 	}
 	nvlist_add_binary(cdict, "state", &con->c_state, sizeof(npf_state_t));

Index: src/sys/net/npf/npf_ctl.c
diff -u src/sys/net/npf/npf_ctl.c:1.54.2.3 src/sys/net/npf/npf_ctl.c:1.54.2.4
--- src/sys/net/npf/npf_ctl.c:1.54.2.3	Sun Sep  1 13:21:39 2019
+++ src/sys/net/npf/npf_ctl.c	Fri Oct  4 08:06:35 2019
@@ -36,7 +36,7 @@
 
 #ifdef _KERNEL
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.54.2.3 2019/09/01 13:21:39 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.54.2.4 2019/10/04 08:06:35 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/conf.h>
@@ -196,7 +196,7 @@ npf_mk_table(npf_t *npf, const nvlist_t 
 		goto out;
 	}
 
-	t = npf_table_create(name, (u_int)tid, type, blob, size);
+	t = npf_table_create(name, (unsigned)tid, type, blob, size);
 	if (t == NULL) {
 		NPF_ERR_DEBUG(errdict);
 		error = ENOMEM;
@@ -473,7 +473,7 @@ npf_mk_singlenat(npf_t *npf, const nvlis
 	KASSERT(rl != NULL);
 	*rlp = rl;
 
-	/* If rule is named, it is a group with NAT policies. */
+	/* If this rule is named, then it is a group with NAT policies. */
 	if (dnvlist_get_string(nat, "name", NULL)) {
 		return 0;
 	}
@@ -816,7 +816,7 @@ npfctl_rule(npf_t *npf, u_long cmd, void
 		return error;
 	}
 	rcmd = dnvlist_get_number(npf_rule, "command", 0);
-	natset = dnvlist_get_bool(npf_rule, "nat-rule", false);
+	natset = dnvlist_get_bool(npf_rule, "nat-ruleset", false);
 	ruleset_name = dnvlist_get_string(npf_rule, "ruleset-name", NULL);
 	if (!ruleset_name) {
 		error = EINVAL;

Index: src/sys/net/npf/npf_if.c
diff -u src/sys/net/npf/npf_if.c:1.9.4.1 src/sys/net/npf/npf_if.c:1.9.4.2
--- src/sys/net/npf/npf_if.c:1.9.4.1	Tue Aug 13 14:35:55 2019
+++ src/sys/net/npf/npf_if.c	Fri Oct  4 08:06:35 2019
@@ -1,4 +1,5 @@
 /*-
+ * Copyright (c) 2019 Mindaugas Rasiukevicius <rmind at noxt eu>
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
@@ -28,23 +29,34 @@
  */
 
 /*
- * NPF network interface handling module.
+ * NPF network interface handling.
  *
- * NPF uses its own interface IDs (npf-if-id).  When NPF configuration is
- * (re)loaded, each required interface name is registered and a matching
- * network interface gets an ID assigned.  If an interface is not present,
- * it gets an ID on attach.
+ * NPF uses its own interface IDs (npf-if-id).  These IDs start from 1.
+ * Zero is reserved to indicate "no interface" case or an interface of
+ * no interest (i.e. not registered).
  *
- * IDs start from 1.  Zero is reserved to indicate "no interface" case or
- * an interface of no interest (i.e. not registered).
+ * This module provides an interface to primarily handle the following:
  *
- * The IDs are mapped synchronously based on interface events which are
- * monitored using pfil(9) hooks.
+ * - Bind a symbolic interface name to NPF interface ID.
+ * - Associate NPF interface ID when the network interface is attached.
+ *
+ * When NPF configuration is (re)loaded, each referenced network interface
+ * name is registered with a unique ID.  If the network interface is already
+ * attached, then the ID is associated with it immediately; otherwise, IDs
+ * are associated/disassociated on interface events which are monitored
+ * using pfil(9) hooks.
+ *
+ * To avoid race conditions when an active NPF configuration is updated or
+ * interfaces are detached/attached, the interface names are never removed
+ * and therefore IDs are never re-assigned.  The only point when interface
+ * names and IDs are cleared is when the configuration is flushed.
+ *
+ * A linear counter is used for IDs.
  */
 
 #ifdef _KERNEL
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_if.c,v 1.9.4.1 2019/08/13 14:35:55 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_if.c,v 1.9.4.2 2019/10/04 08:06:35 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -55,9 +67,13 @@ __KERNEL_RCSID(0, "$NetBSD: npf_if.c,v 1
 #include "npf_impl.h"
 
 typedef struct npf_ifmap {
-	char		n_ifname[IFNAMSIZ];
+	char		ifname[IFNAMSIZ + 1];
 } npf_ifmap_t;
 
+#define	NPF_IFMAP_NOID			(0U)
+#define	NPF_IFMAP_SLOT2ID(npf, slot)	((npf)->ifmap_off + (slot) + 1)
+#define	NPF_IFMAP_ID2SLOT(npf, id)	((id) - (npf)->ifmap_off - 1)
+
 void
 npf_ifmap_init(npf_t *npf, const npf_ifops_t *ifops)
 {
@@ -66,8 +82,10 @@ npf_ifmap_init(npf_t *npf, const npf_ifo
 	KASSERT(ifops != NULL);
 	ifops->flush((void *)(uintptr_t)0);
 
+	mutex_init(&npf->ifmap_lock, MUTEX_DEFAULT, IPL_SOFTNET);
 	npf->ifmap = kmem_zalloc(nbytes, KM_SLEEP);
 	npf->ifmap_cnt = 0;
+	npf->ifmap_off = 0;
 	npf->ifops = ifops;
 }
 
@@ -75,128 +93,151 @@ void
 npf_ifmap_fini(npf_t *npf)
 {
 	const size_t nbytes = sizeof(npf_ifmap_t) * NPF_MAX_IFMAP;
+	mutex_destroy(&npf->ifmap_lock);
 	kmem_free(npf->ifmap, nbytes);
 }
 
-static u_int
-npf_ifmap_new(npf_t *npf)
-{
-	KASSERT(npf_config_locked_p(npf));
-
-	for (u_int i = 0; i < npf->ifmap_cnt; i++)
-		if (npf->ifmap[i].n_ifname[0] == '\0')
-			return i + 1;
-
-	if (npf->ifmap_cnt == NPF_MAX_IFMAP) {
-		printf("npf_ifmap_new: out of slots; bump NPF_MAX_IFMAP\n");
-		return 0;
-	}
-	return ++npf->ifmap_cnt;
-}
-
-static u_int
+static unsigned
 npf_ifmap_lookup(npf_t *npf, const char *ifname)
 {
-	KASSERT(npf_config_locked_p(npf));
+	KASSERT(mutex_owned(&npf->ifmap_lock));
 
-	for (u_int i = 0; i < npf->ifmap_cnt; i++) {
-		npf_ifmap_t *nim = &npf->ifmap[i];
+	for (unsigned i = 0; i < npf->ifmap_cnt; i++) {
+		npf_ifmap_t *ifmap = &npf->ifmap[i];
 
-		if (nim->n_ifname[0] && strcmp(nim->n_ifname, ifname) == 0)
-			return i + 1;
+		if (strcmp(ifmap->ifname, ifname) == 0) {
+			return NPF_IFMAP_SLOT2ID(npf, i);
+		}
 	}
-	return 0;
+	return NPF_IFMAP_NOID;
 }
 
-u_int
+/*
+ * npf_ifmap_register: register an interface name; return an assigned
+ * NPF network ID on success (non-zero).
+ *
+ * This routine is mostly called on NPF configuration (re)load for the
+ * interfaces names referenced by the rules.
+ */
+unsigned
 npf_ifmap_register(npf_t *npf, const char *ifname)
 {
-	npf_ifmap_t *nim;
+	npf_ifmap_t *ifmap;
+	unsigned id, i;
 	ifnet_t *ifp;
-	u_int i;
 
-	npf_config_enter(npf);
-	if ((i = npf_ifmap_lookup(npf, ifname)) != 0) {
+	mutex_enter(&npf->ifmap_lock);
+	if ((id = npf_ifmap_lookup(npf, ifname)) != NPF_IFMAP_NOID) {
 		goto out;
 	}
-	if ((i = npf_ifmap_new(npf)) == 0) {
+	if (npf->ifmap_cnt == NPF_MAX_IFMAP) {
+		printf("npf_ifmap_new: out of slots; bump NPF_MAX_IFMAP\n");
+		id = NPF_IFMAP_NOID;
 		goto out;
 	}
-	nim = &npf->ifmap[i - 1];
-	strlcpy(nim->n_ifname, ifname, IFNAMSIZ);
+	KASSERT(npf->ifmap_cnt < NPF_MAX_IFMAP);
+
+	/* Allocate a new slot and convert and assign an ID. */
+	i = npf->ifmap_cnt++;
+	ifmap = &npf->ifmap[i];
+	strlcpy(ifmap->ifname, ifname, IFNAMSIZ);
+	id = NPF_IFMAP_SLOT2ID(npf, i);
 
 	if ((ifp = npf->ifops->lookup(ifname)) != NULL) {
-		npf->ifops->setmeta(ifp, (void *)(uintptr_t)i);
+		npf->ifops->setmeta(ifp, (void *)(uintptr_t)id);
 	}
 out:
-	npf_config_exit(npf);
-	return i;
+	mutex_exit(&npf->ifmap_lock);
+	return id;
 }
 
 void
 npf_ifmap_flush(npf_t *npf)
 {
-	KASSERT(npf_config_locked_p(npf));
-
-	for (u_int i = 0; i < npf->ifmap_cnt; i++) {
-		npf->ifmap[i].n_ifname[0] = '\0';
+	mutex_enter(&npf->ifmap_lock);
+	npf->ifops->flush((void *)(uintptr_t)NPF_IFMAP_NOID);
+	for (unsigned i = 0; i < npf->ifmap_cnt; i++) {
+		npf->ifmap[i].ifname[0] = '\0';
 	}
 	npf->ifmap_cnt = 0;
-	npf->ifops->flush((void *)(uintptr_t)0);
+
+	/*
+	 * Reset the ID counter if reaching the overflow; this is not
+	 * realistic, but we maintain correctness.
+	 */
+	if (npf->ifmap_off < (UINT_MAX - NPF_MAX_IFMAP)) {
+		npf->ifmap_off += NPF_MAX_IFMAP;
+	} else {
+		npf->ifmap_off = 0;
+	}
+	mutex_exit(&npf->ifmap_lock);
 }
 
-u_int
+/*
+ * npf_ifmap_getid: get the ID for the given network interface.
+ *
+ * => This routine is typically called from the packet handler when
+ *    matching whether the packet is on particular network interface.
+ *
+ * => This routine is lock-free; if the NPF configuration is flushed
+ *    while the packet is in-flight, the ID will not match because we
+ *    keep the IDs linear.
+ */
+unsigned
 npf_ifmap_getid(npf_t *npf, const ifnet_t *ifp)
 {
-	const u_int i = (uintptr_t)npf->ifops->getmeta(ifp);
-	KASSERT(i <= npf->ifmap_cnt);
-	return i;
+	const unsigned id = (uintptr_t)npf->ifops->getmeta(ifp);
+	return id;
 }
 
 /*
- * This function is toxic; it can return garbage since we don't
- * lock, but it is only used temporarily and only for logging.
+ * npf_ifmap_copylogname: this function is toxic; it can return garbage
+ * as we don't lock, but it is only used temporarily and only for logging.
  */
 void
-npf_ifmap_copyname(npf_t *npf, u_int id, char *buf, size_t len)
+npf_ifmap_copylogname(npf_t *npf, unsigned id, char *buf, size_t len)
 {
-	if (id > 0 && id < npf->ifmap_cnt)
-		strlcpy(buf, npf->ifmap[id - 1].n_ifname,
-		    MIN(len, sizeof(npf->ifmap[id - 1].n_ifname)));
-	else
+	const unsigned i = NPF_IFMAP_ID2SLOT(npf, id);
+
+	membar_consumer();
+
+	if (id != NPF_IFMAP_NOID && i < NPF_MAX_IFMAP) {
+		/*
+		 * Lock-free access is safe as there is an extra byte
+		 * with a permanent NUL terminator at the end.
+		 */
+		const npf_ifmap_t *ifmap = &npf->ifmap[i];
+		strlcpy(buf, ifmap->ifname, MIN(len, IFNAMSIZ));
+	} else {
 		strlcpy(buf, "???", len);
+	}
 }
 
-const char *
-npf_ifmap_getname(npf_t *npf, const u_int id)
+void
+npf_ifmap_copyname(npf_t *npf, unsigned id, char *buf, size_t len)
 {
-	const char *ifname;
-
-	KASSERT(npf_config_locked_p(npf));
-	KASSERT(id > 0 && id <= npf->ifmap_cnt);
-
-	ifname = npf->ifmap[id - 1].n_ifname;
-	KASSERT(ifname[0] != '\0');
-	return ifname;
+	mutex_enter(&npf->ifmap_lock);
+	npf_ifmap_copylogname(npf, id, buf, len);
+	mutex_exit(&npf->ifmap_lock);
 }
 
 __dso_public void
 npfk_ifmap_attach(npf_t *npf, ifnet_t *ifp)
 {
 	const npf_ifops_t *ifops = npf->ifops;
-	u_int i;
+	unsigned id;
 
-	npf_config_enter(npf);
-	i = npf_ifmap_lookup(npf, ifops->getname(ifp));
-	ifops->setmeta(ifp, (void *)(uintptr_t)i);
-	npf_config_exit(npf);
+	mutex_enter(&npf->ifmap_lock);
+	id = npf_ifmap_lookup(npf, ifops->getname(ifp));
+	ifops->setmeta(ifp, (void *)(uintptr_t)id);
+	mutex_exit(&npf->ifmap_lock);
 }
 
 __dso_public void
 npfk_ifmap_detach(npf_t *npf, ifnet_t *ifp)
 {
 	/* Diagnostic. */
-	npf_config_enter(npf);
-	npf->ifops->setmeta(ifp, (void *)(uintptr_t)0);
-	npf_config_exit(npf);
+	mutex_enter(&npf->ifmap_lock);
+	npf->ifops->setmeta(ifp, (void *)(uintptr_t)NPF_IFMAP_NOID);
+	mutex_exit(&npf->ifmap_lock);
 }

Index: src/sys/net/npf/npf_impl.h
diff -u src/sys/net/npf/npf_impl.h:1.75.2.3 src/sys/net/npf/npf_impl.h:1.75.2.4
--- src/sys/net/npf/npf_impl.h:1.75.2.3	Sun Sep  1 13:21:39 2019
+++ src/sys/net/npf/npf_impl.h	Fri Oct  4 08:06:35 2019
@@ -234,6 +234,8 @@ struct npf {
 	const npf_ifops_t *	ifops;
 	struct npf_ifmap *	ifmap;
 	unsigned		ifmap_cnt;
+	unsigned		ifmap_off;
+	kmutex_t		ifmap_lock;
 
 	/* Associated worker thread. */
 	unsigned		worker_id;
@@ -319,8 +321,8 @@ void		npf_ifmap_fini(npf_t *);
 u_int		npf_ifmap_register(npf_t *, const char *);
 void		npf_ifmap_flush(npf_t *);
 u_int		npf_ifmap_getid(npf_t *, const ifnet_t *);
-const char *	npf_ifmap_getname(npf_t *, const u_int);
-void		npf_ifmap_copyname(npf_t *, u_int, char *, size_t);
+void		npf_ifmap_copylogname(npf_t *, unsigned, char *, size_t);
+void		npf_ifmap_copyname(npf_t *, unsigned, char *, size_t);
 
 void		npf_ifaddr_sync(npf_t *, ifnet_t *);
 void		npf_ifaddr_flush(npf_t *, ifnet_t *);

Index: src/sys/net/npf/npf_ruleset.c
diff -u src/sys/net/npf/npf_ruleset.c:1.48 src/sys/net/npf/npf_ruleset.c:1.48.2.1
--- src/sys/net/npf/npf_ruleset.c:1.48	Tue Jul 23 00:52:01 2019
+++ src/sys/net/npf/npf_ruleset.c	Fri Oct  4 08:06:35 2019
@@ -33,7 +33,7 @@
 
 #ifdef _KERNEL
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.48 2019/07/23 00:52:01 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: npf_ruleset.c,v 1.48.2.1 2019/10/04 08:06:35 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -680,7 +680,8 @@ npf_rule_export(npf_t *npf, const npf_ru
 		nvlist_add_binary(rule, "code", rl->r_code, rl->r_clen);
 	}
 	if (rl->r_ifid) {
-		const char *ifname = npf_ifmap_getname(npf, rl->r_ifid);
+		char ifname[IFNAMSIZ];
+		npf_ifmap_copyname(npf, rl->r_ifid, ifname, sizeof(ifname));
 		nvlist_add_string(rule, "ifname", ifname);
 	}
 	nvlist_add_number(rule, "id", rl->r_id);

Index: src/usr.sbin/npf/npfctl/npf.conf.5
diff -u src/usr.sbin/npf/npfctl/npf.conf.5:1.88 src/usr.sbin/npf/npfctl/npf.conf.5:1.88.2.1
--- src/usr.sbin/npf/npfctl/npf.conf.5:1.88	Tue Jul 23 14:20:22 2019
+++ src/usr.sbin/npf/npfctl/npf.conf.5	Fri Oct  4 08:06:34 2019
@@ -1,4 +1,4 @@
-.\"    $NetBSD: npf.conf.5,v 1.88 2019/07/23 14:20:22 wiz Exp $
+.\"    $NetBSD: npf.conf.5,v 1.88.2.1 2019/10/04 08:06:34 martin Exp $
 .\"
 .\" Copyright (c) 2009-2019 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd May 19, 2019
+.Dd August 25, 2019
 .Dt NPF.CONF 5
 .Os
 .Sh NAME
@@ -356,6 +356,11 @@ redirecting the public port 9022 to the 
 .Pp
 .Dl map $ext_if dynamic proto tcp 10.1.1.2 port 22 <- $ext_if port 9022
 .Pp
+In the regular dynamic NAT case, it is also possible to disable port
+translation using the
+.Cm no-ports
+flag.
+.Pp
 The translation address can also be dynamic, based on the interface.
 The following would select the IPv4 address(es) currently assigned to the
 interface:
@@ -528,13 +533,15 @@ table-def	= "table" table-id "type" ( "i
 
 # Mapping for address translation.
 
-map		= "map" interface
+map		= map-common | map-ruleset
+map-common	= "map" interface
 		  ( "static" [ "algo" map-algo ] | "dynamic" )
 		  [ map-flags ] [ proto ]
 		  map-seg ( "->" | "<-" | "<->" ) map-seg
 		  [ "pass" [ proto ] filt-opts ]
+map-ruleset	= "map" "ruleset" group-opts
 
-map-algo	= "npt66"
+map-algo	= "ip-hash" | "round-robin" | "netmap" | "npt66"
 map-flags	= "no-ports"
 map-seg		= ( addr-mask | interface ) [ port-opts ]
 
@@ -582,7 +589,7 @@ addr-mask	= addr [ "/" mask ]
 .Ed
 .\" -----
 .Sh FILES
-.Bl -tag -width /usr/share/examples/npf -compact
+.Bl -tag -width Pa -compact
 .It Pa /dev/npf
 control device
 .It Pa /etc/npf.conf

Index: src/usr.sbin/npf/npfctl/npf_build.c
diff -u src/usr.sbin/npf/npfctl/npf_build.c:1.50.2.1 src/usr.sbin/npf/npfctl/npf_build.c:1.50.2.2
--- src/usr.sbin/npf/npfctl/npf_build.c:1.50.2.1	Sun Aug 11 10:10:23 2019
+++ src/usr.sbin/npf/npfctl/npf_build.c	Fri Oct  4 08:06:34 2019
@@ -32,7 +32,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npf_build.c,v 1.50.2.1 2019/08/11 10:10:23 martin Exp $");
+__RCSID("$NetBSD: npf_build.c,v 1.50.2.2 2019/10/04 08:06:34 martin Exp $");
 
 #include <sys/types.h>
 #define	__FAVOR_BSD
@@ -151,25 +151,32 @@ npfctl_debug_addif(const char *ifname)
 	return 0;
 }
 
-unsigned
-npfctl_table_getid(const char *name)
+nl_table_t *
+npfctl_table_getbyname(nl_config_t *ncf, const char *name)
 {
-	unsigned tid = (unsigned)-1;
 	nl_iter_t i = NPF_ITER_BEGIN;
 	nl_table_t *tl;
 
 	/* XXX dynamic ruleset */
-	if (!npf_conf) {
-		return (unsigned)-1;
+	if (!ncf) {
+		return NULL;
 	}
-	while ((tl = npf_table_iterate(npf_conf, &i)) != NULL) {
+	while ((tl = npf_table_iterate(ncf, &i)) != NULL) {
 		const char *tname = npf_table_getname(tl);
 		if (strcmp(tname, name) == 0) {
-			tid = npf_table_getid(tl);
 			break;
 		}
 	}
-	return tid;
+	return tl;
+}
+
+unsigned
+npfctl_table_getid(const char *name)
+{
+	nl_table_t *tl;
+
+	tl = npfctl_table_getbyname(npf_conf, name);
+	return tl ? npf_table_getid(tl) : (unsigned)-1;
 }
 
 const char *
@@ -530,16 +537,32 @@ npfctl_build_rproc(const char *name, npf
 	npf_rproc_insert(npf_conf, rp);
 }
 
+/*
+ * npfctl_build_maprset: create and insert a NAT ruleset.
+ */
 void
 npfctl_build_maprset(const char *name, int attr, const char *ifname)
 {
 	const int attr_di = (NPF_RULE_IN | NPF_RULE_OUT);
 	nl_rule_t *rl;
+	bool natset;
+	int err;
+
+	/* Validate the prefix. */
+	err = npfctl_nat_ruleset_p(name, &natset);
+	if (!natset) {
+		yyerror("NAT ruleset names must be prefixed with `"
+		    NPF_RULESET_MAP_PREF "`");
+	}
+	if (err) {
+		yyerror("NAT ruleset is missing a name (only prefix found)");
+	}
 
 	/* If no direction is not specified, then both. */
 	if ((attr & attr_di) == 0) {
 		attr |= attr_di;
 	}
+
 	/* Allow only "in/out" attributes. */
 	attr = NPF_RULE_GROUP | NPF_RULE_DYNAMIC | (attr & attr_di);
 	rl = npf_rule_create(name, attr, ifname);
@@ -859,13 +882,22 @@ npfctl_build_natseg(int sd, int type, un
 		}
 	}
 
-	if (nt1) {
-		npf_rule_setprio(nt1, NPF_PRI_LAST);
-		npf_nat_insert(npf_conf, nt1);
-	}
-	if (nt2) {
-		npf_rule_setprio(nt2, NPF_PRI_LAST);
-		npf_nat_insert(npf_conf, nt2);
+	if (npf_conf) {
+		if (nt1) {
+			npf_rule_setprio(nt1, NPF_PRI_LAST);
+			npf_nat_insert(npf_conf, nt1);
+		}
+		if (nt2) {
+			npf_rule_setprio(nt2, NPF_PRI_LAST);
+			npf_nat_insert(npf_conf, nt2);
+		}
+	} else {
+		// XXX/TODO: need to refactor a bit to enable this..
+		if (nt1 && nt2) {
+			errx(EXIT_FAILURE, "bidirectional NAT is currently "
+			    "not yet supported in the dynamic rules");
+		}
+		the_rule = nt1 ? nt1 : nt2;
 	}
 }
 
@@ -873,15 +905,13 @@ npfctl_build_natseg(int sd, int type, un
  * npfctl_fill_table: fill NPF table with entries from a specified file.
  */
 static void
-npfctl_fill_table(nl_table_t *tl, u_int type, const char *fname)
+npfctl_fill_table(nl_table_t *tl, u_int type, const char *fname, FILE *fp)
 {
 	char *buf = NULL;
 	int l = 0;
-	FILE *fp;
 	size_t n;
 
-	fp = fopen(fname, "r");
-	if (fp == NULL) {
+	if (fp == NULL && (fp = fopen(fname, "r")) == NULL) {
 		err(EXIT_FAILURE, "open '%s'", fname);
 	}
 	while (l++, getline(&buf, &n, fp) != -1) {
@@ -908,6 +938,23 @@ npfctl_fill_table(nl_table_t *tl, u_int 
 }
 
 /*
+ * npfctl_load_table: create an NPF table and fill with contents from a file.
+ */
+nl_table_t *
+npfctl_load_table(const char *tname, int tid, u_int type,
+    const char *fname, FILE *fp)
+{
+	nl_table_t *tl;
+
+	tl = npf_table_create(tname, tid, type);
+	if (tl && fname) {
+		npfctl_fill_table(tl, type, fname, fp);
+	}
+
+	return tl;
+}
+
+/*
  * npfctl_build_table: create an NPF table, add to the configuration and,
  * if required, fill with contents from a file.
  */
@@ -916,15 +963,13 @@ npfctl_build_table(const char *tname, u_
 {
 	nl_table_t *tl;
 
-	tl = npf_table_create(tname, npfctl_tid_counter++, type);
-	assert(tl != NULL);
-
-	if (fname) {
-		npfctl_fill_table(tl, type, fname);
-	} else if (type == NPF_TABLE_CONST) {
+	if (type == NPF_TABLE_CONST && !fname) {
 		yyerror("table type 'const' must be loaded from a file");
 	}
 
+	tl = npfctl_load_table(tname, npfctl_tid_counter++, type, fname, NULL);
+	assert(tl != NULL);
+
 	if (npf_table_insert(npf_conf, tl)) {
 		yyerror("table '%s' is already defined", tname);
 	}
@@ -939,9 +984,13 @@ npfctl_ifnet_table(const char *ifname)
 {
 	char tname[NPF_TABLE_MAXNAMELEN];
 	nl_table_t *tl;
-	u_int tid;
+	unsigned tid;
 
 	snprintf(tname, sizeof(tname), NPF_IFNET_TABLE_PREF "%s", ifname);
+	if (!npf_conf) {
+		errx(EXIT_FAILURE, "expression `ifaddrs(%s)` is currently "
+		    "not yet supported in dynamic rules", ifname);
+	}
 
 	tid = npfctl_table_getid(tname);
 	if (tid == (unsigned)-1) {
@@ -949,7 +998,7 @@ npfctl_ifnet_table(const char *ifname)
 		tl = npf_table_create(tname, tid, NPF_TABLE_IFADDR);
 		(void)npf_table_insert(npf_conf, tl);
 	}
-	return npfvar_create_element(NPFVAR_TABLE, &tid, sizeof(u_int));
+	return npfvar_create_element(NPFVAR_TABLE, &tid, sizeof(unsigned));
 }
 
 /*

Index: src/usr.sbin/npf/npfctl/npf_parse.y
diff -u src/usr.sbin/npf/npfctl/npf_parse.y:1.49 src/usr.sbin/npf/npfctl/npf_parse.y:1.49.2.1
--- src/usr.sbin/npf/npfctl/npf_parse.y:1.49	Tue Jul 23 00:52:02 2019
+++ src/usr.sbin/npf/npfctl/npf_parse.y	Fri Oct  4 08:06:34 2019
@@ -42,11 +42,11 @@
 
 #define	YYSTACKSIZE	4096
 
-int			yyparsetarget;
+int			yystarttoken;
 const char *		yyfilename;
 
 extern int		yylineno, yycolumn;
-extern int		yylex(void);
+extern int		yylex(int);
 
 void
 yyerror(const char *fmt, ...)
@@ -78,14 +78,6 @@ yyerror(const char *fmt, ...)
 	exit(EXIT_FAILURE);
 }
 
-#define	CHECK_PARSER_FILE				\
-	if (yyparsetarget != NPFCTL_PARSE_FILE)		\
-		yyerror("rule must be in the group");
-
-#define	CHECK_PARSER_STRING				\
-	if (yyparsetarget != NPFCTL_PARSE_STRING)	\
-		yyerror("invalid rule syntax");
-
 %}
 
 /*
@@ -94,6 +86,17 @@ yyerror(const char *fmt, ...)
 %expect 0
 %expect-rr 0
 
+/*
+ * Depending on the mode of operation, set a different start symbol.
+ * Workaround yacc limitation by passing the start token.
+ */
+%start input
+%token RULE_ENTRY_TOKEN MAP_ENTRY_TOKEN
+%lex-param { int yystarttoken }
+
+/*
+ * General tokens.
+ */
 %token			ALG
 %token			ALGO
 %token			ALL
@@ -209,8 +212,9 @@ yyerror(const char *fmt, ...)
 %%
 
 input
-	: { CHECK_PARSER_FILE	} lines
-	| { CHECK_PARSER_STRING	} rule
+	: lines
+	| RULE_ENTRY_TOKEN	rule
+	| MAP_ENTRY_TOKEN	map
 	;
 
 lines

Index: src/usr.sbin/npf/npfctl/npf_scan.l
diff -u src/usr.sbin/npf/npfctl/npf_scan.l:1.29 src/usr.sbin/npf/npfctl/npf_scan.l:1.29.2.1
--- src/usr.sbin/npf/npfctl/npf_scan.l:1.29	Tue Jul 23 00:52:02 2019
+++ src/usr.sbin/npf/npfctl/npf_scan.l	Fri Oct  4 08:06:34 2019
@@ -39,7 +39,7 @@ int	yycolumn;
 
 #define	YY_USER_ACTION	yycolumn += yyleng;
 
-extern int		yyparsetarget;
+extern int		yystarttoken;
 extern int		yylineno;
 extern const char *	yyfilename;
 extern int		yyparse(void);
@@ -54,7 +54,7 @@ npfctl_parse_file(const char *name)
 	if (fp == NULL) {
 		err(EXIT_FAILURE, "open '%s'", name);
 	}
-	yyparsetarget = NPFCTL_PARSE_FILE;
+	yystarttoken = 0;
 	yyrestart(fp);
 	yylineno = 1;
 	yycolumn = 0;
@@ -64,11 +64,21 @@ npfctl_parse_file(const char *name)
 }
 
 void
-npfctl_parse_string(const char *str)
+npfctl_parse_string(const char *str, parse_entry_t entry)
 {
 	YY_BUFFER_STATE bs;
 
-	yyparsetarget = NPFCTL_PARSE_STRING;
+	switch (entry) {
+	case NPFCTL_PARSE_RULE:
+		yystarttoken = RULE_ENTRY_TOKEN;
+		break;
+	case NPFCTL_PARSE_MAP:
+		yystarttoken = MAP_ENTRY_TOKEN;
+		break;
+	default:
+		abort();
+	}
+
 	bs = yy_scan_string(str);
 	yyfilename = "stdin";
 	yyparse();
@@ -85,6 +95,15 @@ NUMBER	[0-9]+
 HEXDIG	[0-9a-fA-F]+
 
 %%
+%{
+	/* This is prepended to yylex(). */
+	if (yystarttoken) {
+		int token = yystarttoken;
+		yystarttoken = 0;
+		return token;
+	}
+%}
+
 alg			return ALG;
 table			return TABLE;
 type			return TYPE;

Index: src/usr.sbin/npf/npfctl/npfctl.8
diff -u src/usr.sbin/npf/npfctl/npfctl.8:1.21 src/usr.sbin/npf/npfctl/npfctl.8:1.21.2.1
--- src/usr.sbin/npf/npfctl/npfctl.8:1.21	Sat Jan 19 21:19:32 2019
+++ src/usr.sbin/npf/npfctl/npfctl.8	Fri Oct  4 08:06:34 2019
@@ -1,4 +1,4 @@
-.\"	$NetBSD: npfctl.8,v 1.21 2019/01/19 21:19:32 rmind Exp $
+.\"	$NetBSD: npfctl.8,v 1.21.2.1 2019/10/04 08:06:34 martin Exp $
 .\"
 .\" Copyright (c) 2009-2014 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -27,7 +27,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd December 10, 2017
+.Dd August 26, 2019
 .Dt NPFCTL 8
 .Os
 .Sh NAME
@@ -114,28 +114,50 @@ List all rules in the dynamic ruleset sp
 Remove all rules from the dynamic ruleset specified by
 .Ar name .
 .\" ---
-.It Ic table Ar tid Ic add Aq Ar addr/mask
+.It Ic table Ar name Ic add Aq Ar addr/mask
 In table
-.Ar tid ,
+.Ar name ,
 add the IP address and optionally netmask, specified by
 .Aq Ar addr/mask .
 Only the tables of type "lpm" support masks.
-.It Ic table Ar tid Ic rem Aq Ar addr/mask
+.It Ic table Ar name Ic rem Aq Ar addr/mask
 In table
-.Ar tid ,
+.Ar name ,
 remove the IP address and optionally netmask, specified by
 .Aq Ar addr/mask .
 Only the tables of type "lpm" support masks.
-.It Ic table Ar tid Ic test Aq Ar addr
+.It Ic table Ar name Ic test Aq Ar addr
 Query the table
-.Ar tid
+.Ar name
 for a specific IP address, specified by
 .Ar addr .
 If no mask is specified, a single host is assumed.
-.It Ic table Ar tid Ic list
+.It Ic table Ar name Ic list
 List all entries in the currently loaded table specified by
-.Ar tid .
+.Ar name .
 This operation is expensive and should be used with caution.
+.It Ic table Ar name Ic replace Oo Fl n Ar newname Oc Oo Fl t Ar type Oc Aq Ar path
+Replace the existing table specified by
+.Ar name
+with a new table built from the file specified by
+.Ar path .
+Optionally, the new table will:
+.Bl -tag -width xxxxxxxxxx -compact -offset 3n
+.It Fl n Ar newname
+be named
+.Ar newname ,
+effectively renaming the table.
+If not specified, the name of the table being replaced will be used.
+.It Fl t Ar type
+be of type
+.Ar type ;
+currently supported types are
+.Cm ipset ,
+.Cm lpm ,
+or
+.Cm const .
+If not specified, the type of the table being replaced will be used.
+.El
 .\" ---
 .It Ic save
 Save the active configuration and a snapshot of the current connections.
@@ -181,7 +203,7 @@ See
 for details.
 .\" -----
 .Sh FILES
-.Bl -tag -width /etc/npf.conf -compact
+.Bl -tag -width Pa -compact
 .It Pa /dev/npf
 control device
 .It Pa /etc/npf.conf
@@ -201,6 +223,13 @@ Addition and removal of entries in the t
 # npfctl table "vip" add 10.0.0.1
 # npfctl table "vip" rem 182.168.0.0/24
 .Ed
+.Pp
+Replacing the existing table which has ID "svr"
+with a new const table populated from file "/tmp/npf_vps_new",
+and renamed to "vps":
+.Bd -literal -offset indent
+# npfctl table "svr" replace -n "vps" -t const "/tmp/npf_vps_new"
+.Ed
 .\" -----
 .Sh SEE ALSO
 .Xr bpf 4 ,

Index: src/usr.sbin/npf/npfctl/npfctl.c
diff -u src/usr.sbin/npf/npfctl/npfctl.c:1.60.2.1 src/usr.sbin/npf/npfctl/npfctl.c:1.60.2.2
--- src/usr.sbin/npf/npfctl/npfctl.c:1.60.2.1	Sun Sep  1 13:13:14 2019
+++ src/usr.sbin/npf/npfctl/npfctl.c	Fri Oct  4 08:06:34 2019
@@ -28,7 +28,7 @@
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: npfctl.c,v 1.60.2.1 2019/09/01 13:13:14 martin Exp $");
+__RCSID("$NetBSD: npfctl.c,v 1.60.2.2 2019/10/04 08:06:34 martin Exp $");
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -54,8 +54,6 @@ __RCSID("$NetBSD: npfctl.c,v 1.60.2.1 20
 
 #include "npfctl.h"
 
-extern void		npf_yyparse_string(const char *);
-
 enum {
 	NPFCTL_START,
 	NPFCTL_STOP,
@@ -142,10 +140,14 @@ usage(void)
 	    "\t%s rule \"rule-name\" { list | flush }\n",
 	    progname);
 	fprintf(stderr,
-	    "\t%s table <tid> { add | rem | test } <address/mask>\n",
+	    "\t%s table \"table-name\" { add | rem | test } <address/mask>\n",
+	    progname);
+	fprintf(stderr,
+	    "\t%s table \"table-name\" { list | flush }\n",
 	    progname);
 	fprintf(stderr,
-	    "\t%s table <tid> { list | flush }\n",
+	    "\t%s table \"table-name\" replace [-n \"name\"]"
+	    " [-t <type>] <table-file>\n",
 	    progname);
 	fprintf(stderr,
 	    "\t%s save | load\n",
@@ -275,7 +277,98 @@ npfctl_print_addrmask(int alen, const ch
 	return buf;
 }
 
-__dead static void
+static int
+npfctl_table_type(const char *typename)
+{
+	static const struct tbltype_s {
+		const char *	name;
+		unsigned	type;
+	} tbltypes[] = {
+		{ "ipset",	NPF_TABLE_IPSET	},
+		{ "lpm",	NPF_TABLE_LPM	},
+		{ "const",	NPF_TABLE_CONST	},
+		{ NULL,		0		}
+	};
+
+	for (unsigned i = 0; tbltypes[i].name != NULL; i++) {
+		if (strcmp(typename, tbltypes[i].name) == 0) {
+			return tbltypes[i].type;
+		}
+	}
+	return 0;
+}
+
+static void
+npfctl_table_replace(int fd, int argc, char **argv)
+{
+	const char *name, *newname, *path, *typename = NULL;
+	int c, tid = -1;
+	FILE *fp;
+	nl_config_t *ncf;
+	nl_table_t *t;
+	u_int type = 0;
+
+	name = newname = argv[0];
+	optind = 2;
+	while ((c = getopt(argc, argv, "n:t:")) != -1) {
+		switch (c) {
+		case 't':
+			typename = optarg;
+			break;
+		case 'n':
+			newname = optarg;
+			break;
+		default:
+			fprintf(stderr,
+			    "Usage: %s table \"table-name\" replace "
+			    "[-n \"name\"] [-t <type>] <table-file>\n",
+			    getprogname());
+			exit(EXIT_FAILURE);
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (typename && (type = npfctl_table_type(typename)) == 0) {
+		errx(EXIT_FAILURE, "unsupported table type '%s'", typename);
+	}
+
+	if (argc != 1) {
+		usage();
+	}
+
+	path = argv[0];
+	if (strcmp(path, "-") == 0) {
+		path = "stdin";
+		fp = stdin;
+	} else if ((fp = fopen(path, "r")) == NULL) {
+		err(EXIT_FAILURE, "open '%s'", path);
+	}
+
+	/* Get existing config to lookup ID of existing table */
+	if ((ncf = npf_config_retrieve(fd)) == NULL) {
+		err(EXIT_FAILURE, "npf_config_retrieve()");
+	}
+	if ((t = npfctl_table_getbyname(ncf, name)) == NULL) {
+		errx(EXIT_FAILURE,
+		    "table '%s' not found in the active configuration", name);
+	}
+	tid = npf_table_getid(t);
+	if (!type) {
+		type = npf_table_gettype(t);
+	}
+	npf_config_destroy(ncf);
+
+	if ((t = npfctl_load_table(newname, tid, type, path, fp)) == NULL) {
+		err(EXIT_FAILURE, "table load failed");
+	}
+
+	if (npf_table_replace(fd, t, NULL)) {
+		err(EXIT_FAILURE, "npf_table_replace(<%s>)", name);
+	}
+}
+
+static void
 npfctl_table(int fd, int argc, char **argv)
 {
 	static const struct tblops_s {
@@ -383,11 +476,10 @@ again:
 		    nct.nct_cmd == NPF_CMD_TABLE_LOOKUP ?
 		    "match" : "success");
 	}
-	exit(EXIT_SUCCESS);
 }
 
 static nl_rule_t *
-npfctl_parse_rule(int argc, char **argv)
+npfctl_parse_rule(int argc, char **argv, parse_entry_t entry)
 {
 	char rule_string[1024];
 	nl_rule_t *rl;
@@ -396,7 +488,7 @@ npfctl_parse_rule(int argc, char **argv)
 	if (!join(rule_string, sizeof(rule_string), argc, argv, " ")) {
 		errx(EXIT_FAILURE, "command too long");
 	}
-	npfctl_parse_string(rule_string);
+	npfctl_parse_string(rule_string, entry);
 	if ((rl = npfctl_rule_ref()) == NULL) {
 		errx(EXIT_FAILURE, "could not parse the rule");
 	}
@@ -431,7 +523,15 @@ npfctl_generate_key(nl_rule_t *rl, void 
 	free(meta);
 }
 
-__dead static void
+int
+npfctl_nat_ruleset_p(const char *name, bool *natset)
+{
+	const size_t preflen = sizeof(NPF_RULESET_MAP_PREF) - 1;
+	*natset = strncmp(name, NPF_RULESET_MAP_PREF, preflen) == 0;
+	return (*natset && strlen(name) <= preflen) ? -1 : 0;
+}
+
+static void
 npfctl_rule(int fd, int argc, char **argv)
 {
 	static const struct ruleops_s {
@@ -451,11 +551,12 @@ npfctl_rule(int fd, int argc, char **arg
 	const char *ruleset_name = argv[0];
 	const char *cmd = argv[1];
 	int error, action = 0;
+	bool extra_arg, natset;
+	parse_entry_t entry;
 	uint64_t rule_id;
-	bool extra_arg;
 	nl_rule_t *rl;
 
-	for (int n = 0; ruleops[n].cmd != NULL; n++) {
+	for (unsigned n = 0; ruleops[n].cmd != NULL; n++) {
 		if (strcmp(cmd, ruleops[n].cmd) == 0) {
 			action = ruleops[n].action;
 			extra_arg = ruleops[n].extra_arg;
@@ -469,15 +570,22 @@ npfctl_rule(int fd, int argc, char **arg
 		usage();
 	}
 
+	if (npfctl_nat_ruleset_p(ruleset_name, &natset) != 0) {
+		errx(EXIT_FAILURE,
+		    "invalid NAT ruleset name (note: the name must be "
+		    "prefixed with `" NPF_RULESET_MAP_PREF "`)");
+	}
+	entry = natset ? NPFCTL_PARSE_MAP : NPFCTL_PARSE_RULE;
+
 	switch (action) {
 	case NPF_CMD_RULE_ADD:
-		rl = npfctl_parse_rule(argc, argv);
+		rl = npfctl_parse_rule(argc, argv, entry);
 		npfctl_generate_key(rl, key);
 		npf_rule_setkey(rl, key, sizeof(key));
 		error = npf_ruleset_add(fd, ruleset_name, rl, &rule_id);
 		break;
 	case NPF_CMD_RULE_REMKEY:
-		rl = npfctl_parse_rule(argc, argv);
+		rl = npfctl_parse_rule(argc, argv, entry);
 		npfctl_generate_key(rl, key);
 		error = npf_ruleset_remkey(fd, ruleset_name, key, sizeof(key));
 		break;
@@ -509,7 +617,6 @@ npfctl_rule(int fd, int argc, char **arg
 	if (action == NPF_CMD_RULE_ADD) {
 		printf("OK %" PRIx64 "\n", rule_id);
 	}
-	exit(EXIT_SUCCESS);
 }
 
 static bool bpfjit = true;
@@ -754,7 +861,11 @@ npfctl(int action, int argc, char **argv
 			usage();
 		}
 		argv += 2;
-		npfctl_table(fd, argc, argv);
+		if (strcmp(argv[1], "replace") == 0) {
+			npfctl_table_replace(fd, argc, argv);
+		} else {
+			npfctl_table(fd, argc, argv);
+		}
 		break;
 	case NPFCTL_RULE:
 		if ((argc -= 2) < 2) {

Index: src/usr.sbin/npf/npfctl/npfctl.h
diff -u src/usr.sbin/npf/npfctl/npfctl.h:1.48.2.1 src/usr.sbin/npf/npfctl/npfctl.h:1.48.2.2
--- src/usr.sbin/npf/npfctl/npfctl.h:1.48.2.1	Sun Aug 11 10:10:23 2019
+++ src/usr.sbin/npf/npfctl/npfctl.h	Fri Oct  4 08:06:34 2019
@@ -102,7 +102,11 @@ typedef struct proc_param {
 	const char *	pp_value;
 } proc_param_t;
 
-enum { NPFCTL_PARSE_FILE, NPFCTL_PARSE_STRING };
+typedef enum {
+	NPFCTL_PARSE_DEFAULT,
+	NPFCTL_PARSE_RULE,
+	NPFCTL_PARSE_MAP
+} parse_entry_t;
 
 #define	NPF_IFNET_TABLE_PREF		".ifnet-"
 #define	NPF_IFNET_TABLE_PREFLEN		(sizeof(NPF_IFNET_TABLE_PREF) - 1)
@@ -111,12 +115,13 @@ bool		join(char *, size_t, int, char **,
 void		yyerror(const char *, ...) __printflike(1, 2) __dead;
 void		npfctl_bpfjit(bool);
 void		npfctl_parse_file(const char *);
-void		npfctl_parse_string(const char *);
+void		npfctl_parse_string(const char *, parse_entry_t);
 
 void		npfctl_print_error(const npf_error_t *);
 char *		npfctl_print_addrmask(int, const char *, const npf_addr_t *,
 		    npf_netmask_t);
 void		npfctl_note_interface(const char *);
+nl_table_t *	npfctl_table_getbyname(nl_config_t *, const char *);
 unsigned	npfctl_table_getid(const char *);
 const char *	npfctl_table_getname(nl_config_t *, unsigned, bool *);
 int		npfctl_protono(const char *);
@@ -135,6 +140,7 @@ npfvar_t *	npfctl_parse_fam_addr_mask(co
 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 *);
+int		npfctl_nat_ruleset_p(const char *, bool *);
 
 /*
  * NPF extension loading.
@@ -196,8 +202,11 @@ void		npfctl_show_init(void);
 int		npfctl_ruleset_show(int, const char *);
 
 nl_rule_t *	npfctl_rule_ref(void);
+nl_table_t *	npfctl_table_ref(void);
 bool		npfctl_debug_addif(const char *);
 
+nl_table_t *	npfctl_load_table(const char *, int, u_int, const char *, FILE *);
+
 void		npfctl_build_alg(const char *);
 void		npfctl_build_rproc(const char *, npfvar_t *);
 void		npfctl_build_group(const char *, int, const char *, bool);

Index: src/usr.sbin/npf/npftest/npftest.conf
diff -u src/usr.sbin/npf/npftest/npftest.conf:1.7 src/usr.sbin/npf/npftest/npftest.conf:1.7.2.1
--- src/usr.sbin/npf/npftest/npftest.conf:1.7	Tue Jul 23 00:52:02 2019
+++ src/usr.sbin/npf/npftest/npftest.conf	Fri Oct  4 08:06:35 2019
@@ -1,4 +1,4 @@
-# $NetBSD: npftest.conf,v 1.7 2019/07/23 00:52:02 rmind Exp $
+# $NetBSD: npftest.conf,v 1.7.2.1 2019/10/04 08:06:35 martin Exp $
 
 $ext_if = "npftest0"
 $int_if = "npftest1"
@@ -32,7 +32,7 @@ $net_b = 10.255.0.0/16
 
 map $ext_if static algo npt66 $net6_inner <-> $net6_outer
 map $ext_if static algo netmap $net_a <-> $net_b
-map ruleset "dynamic-nat" on $ext_if
+map ruleset "map:some-daemon" on $ext_if
 
 group "ext" on $ext_if {
 	pass out final from $local_ip3

Reply via email to