npfkern/libnpf: Add support for the table replace/swap operation.

Contributed by Timshel Knoll-Miller.
This commit is contained in:
rmind 2019-08-21 21:45:47 +00:00
parent f916b9b1ff
commit 0e1944da36
8 changed files with 211 additions and 64 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: libnpf.3,v 1.9 2019/07/23 14:18:20 wiz Exp $
.\" $NetBSD: libnpf.3,v 1.10 2019/08/21 21:45:47 rmind 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 April 14, 2019
.Dd August 21, 2019
.Dt LIBNPF 3
.Os
.Sh NAME
@ -41,7 +41,7 @@
.Ft nl_config_t *
.Fn npf_config_create "void"
.Ft int
.Fn npf_config_submit "nl_config_t *ncf" "int fd" "nl_error_t *errinfo"
.Fn npf_config_submit "nl_config_t *ncf" "int fd" "npf_error_t *errinfo"
.Ft nl_config_t *
.Fn npf_config_retrieve "int fd"
.Ft int
@ -104,6 +104,8 @@
"const npf_addr_t *addr" "const npf_netmask_t mask"
.Ft int
.Fn npf_table_insert "nl_config_t *ncf" "nl_table_t *tl"
.Ft int
.Fn npf_table_replace "int fd" "nl_table_t *tl" "npf_error_t *errinfo"
.Ft void
.Fn npf_table_destroy "nl_table_t *tl"
.\" -----
@ -347,7 +349,9 @@ for IPv4 or
for IPv6 address.
Additionally,
.Fa mask
may be specified to indicate the translation network.
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
.Fn npf_nat_setalgo
function.
@ -423,11 +427,25 @@ must be either
for IPv4 or
.Dv AF_INET6
for IPv6 address.
If there is no mask, then
.Fa mask
should be set to
.Dv NPF_NO_NETMASK .
.\" ---
.It Fn npf_table_insert "ncf" "tl"
Add the table to the configuration object.
This routine performs a check for duplicate table IDs.
The table must not be referenced after insertion.
.\" ---
.It Fn npf_table_replace "fd" "tl" "errinfo"
Submit the table object, specified by
.Fa tl ,
to the kernel, to replace the existing table with the
corresponding table name and ID.
On failure, the error information is written into the structure
specified by
.Fa errinfo .
.\" ---
.It Fn npf_table_destroy "tl"
Destroy the specified table.
.El

View File

@ -28,7 +28,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.46 2019/07/23 00:52:01 rmind Exp $");
__KERNEL_RCSID(0, "$NetBSD: npf.c,v 1.47 2019/08/21 21:45:47 rmind Exp $");
#include <sys/types.h>
#include <sys/mman.h>
@ -202,6 +202,30 @@ _npf_rules_process(nl_config_t *ncf, nvlist_t *dict, const char *key)
free(items);
}
/*
* _npf_extract_error: check the error number field and extract the
* error details into the npf_error_t structure.
*/
static int
_npf_extract_error(nvlist_t *resp, npf_error_t *errinfo)
{
int error;
error = dnvlist_get_number(resp, "errno", 0);
if (error && errinfo) {
memset(errinfo, 0, sizeof(npf_error_t));
errinfo->id = dnvlist_get_number(resp, "id", 0);
errinfo->error_msg =
dnvlist_take_string(resp, "error-msg", NULL);
errinfo->source_file =
dnvlist_take_string(resp, "source-file", NULL);
errinfo->source_line =
dnvlist_take_number(resp, "source-line", 0);
}
return error;
}
/*
* CONFIGURATION INTERFACE.
*/
@ -233,17 +257,7 @@ npf_config_submit(nl_config_t *ncf, int fd, npf_error_t *errinfo)
assert(errnv == NULL);
return errno;
}
error = dnvlist_get_number(errnv, "errno", 0);
if (error && errinfo) {
memset(errinfo, 0, sizeof(npf_error_t));
errinfo->id = dnvlist_get_number(errnv, "id", 0);
errinfo->error_msg =
dnvlist_take_string(errnv, "error-msg", NULL);
errinfo->source_file =
dnvlist_take_string(errnv, "source-file", NULL);
errinfo->source_line =
dnvlist_take_number(errnv, "source-line", 0);
}
error = _npf_extract_error(errnv, errinfo);
nvlist_destroy(errnv);
return error;
}
@ -949,7 +963,7 @@ npf_table_add_entry(nl_table_t *tl, int af, const npf_addr_t *addr,
}
static inline int
_npf_table_build(nl_table_t *tl)
_npf_table_build_const(nl_table_t *tl)
{
struct cdbw *cdbw;
const nvlist_t * const *entries;
@ -959,6 +973,10 @@ _npf_table_build(nl_table_t *tl)
struct stat sb;
char sfn[32];
if (dnvlist_get_number(tl->table_dict, "type", 0) != NPF_TABLE_CONST) {
return 0;
}
if (!nvlist_exists_nvlist_array(tl->table_dict, "entries")) {
return 0;
}
@ -1050,10 +1068,8 @@ npf_table_insert(nl_config_t *ncf, nl_table_t *tl)
if (_npf_dataset_lookup(ncf->ncf_dict, "tables", "name", name)) {
return EEXIST;
}
if (dnvlist_get_number(tl->table_dict, "type", 0) == NPF_TABLE_CONST) {
if ((error = _npf_table_build(tl)) != 0) {
return error;
}
if ((error = _npf_table_build_const(tl)) != 0) {
return error;
}
nvlist_append_nvlist_array(ncf->ncf_dict, "tables", tl->table_dict);
nvlist_destroy(tl->table_dict);
@ -1061,6 +1077,27 @@ npf_table_insert(nl_config_t *ncf, nl_table_t *tl)
return 0;
}
int
npf_table_replace(int fd, nl_table_t *tl, npf_error_t *errinfo)
{
nvlist_t *errnv = NULL;
int error;
/* Ensure const tables are built. */
if ((error = _npf_table_build_const(tl)) != 0) {
return error;
}
if (nvlist_xfer_ioctl(fd, IOC_NPF_TABLE_REPLACE,
tl->table_dict, &errnv) == -1) {
assert(errnv == NULL);
return errno;
}
error = _npf_extract_error(errnv, errinfo);
nvlist_destroy(errnv);
return error;
}
nl_table_t *
npf_table_iterate(nl_config_t *ncf, nl_iter_t *iter)
{

View File

@ -146,6 +146,8 @@ int npf_table_add_entry(nl_table_t *, int,
int npf_table_insert(nl_config_t *, nl_table_t *);
void npf_table_destroy(nl_table_t *);
int npf_table_replace(int, nl_table_t *, npf_error_t *);
#ifdef _NPF_PRIVATE
#include <ifaddrs.h>

View File

@ -310,6 +310,7 @@ typedef struct npf_ioctl_table {
#define IOC_NPF_SAVE _IOR('N', 105, nvlist_ref_t)
#define IOC_NPF_RULE _IOWR('N', 107, nvlist_ref_t)
#define IOC_NPF_CONN_LOOKUP _IOWR('N', 108, nvlist_ref_t)
#define IOC_NPF_TABLE_REPLACE _IOWR('N', 109, nvlist_ref_t)
/*
* NPF error report.

View File

@ -36,7 +36,7 @@
#ifdef _KERNEL
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.55 2019/08/11 20:26:33 rmind Exp $");
__KERNEL_RCSID(0, "$NetBSD: npf_ctl.c,v 1.56 2019/08/21 21:45:47 rmind Exp $");
#include <sys/param.h>
#include <sys/conf.h>
@ -178,6 +178,63 @@ npf_mk_table_entries(npf_table_t *t, const nvlist_t *table, nvlist_t *errdict)
return error;
}
/*
* npf_mk_table: create a table from provided nvlist.
*/
static int __noinline
npf_mk_table(npf_t *npf, const nvlist_t *tbl_dict, nvlist_t *errdict,
npf_tableset_t *tblset, npf_table_t **tblp, bool replacing)
{
npf_table_t *t;
const char *name;
const void *blob;
uint64_t tid;
size_t size;
int type;
int error = 0;
KASSERT(tblp != NULL);
/* Table name, ID and type. Validate them. */
name = dnvlist_get_string(tbl_dict, "name", NULL);
if (!name) {
NPF_ERR_DEBUG(errdict);
error = EINVAL;
goto out;
}
tid = dnvlist_get_number(tbl_dict, "id", UINT64_MAX);
type = dnvlist_get_number(tbl_dict, "type", UINT64_MAX);
error = npf_table_check(tblset, name, tid, type, replacing);
if (error) {
NPF_ERR_DEBUG(errdict);
goto out;
}
/* Get the entries or binary data. */
blob = dnvlist_get_binary(tbl_dict, "data", &size, NULL, 0);
if (type == NPF_TABLE_CONST && (blob == NULL || size == 0)) {
NPF_ERR_DEBUG(errdict);
error = EINVAL;
goto out;
}
t = npf_table_create(name, (u_int)tid, type, blob, size);
if (t == NULL) {
NPF_ERR_DEBUG(errdict);
error = ENOMEM;
goto out;
}
if ((error = npf_mk_table_entries(t, tbl_dict, errdict)) != 0) {
npf_table_destroy(t);
goto out;
}
*tblp = t;
out:
return error;
}
static int __noinline
npf_mk_tables(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
npf_tableset_t **tblsetp)
@ -200,49 +257,15 @@ npf_mk_tables(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict,
tblset = npf_tableset_create(nitems);
for (unsigned i = 0; i < nitems; i++) {
const nvlist_t *table = tables[i];
const char *name;
const void *blob;
npf_table_t *t;
uint64_t tid;
size_t size;
int type;
/* Table name, ID and type. Validate them. */
name = dnvlist_get_string(table, "name", NULL);
if (!name) {
NPF_ERR_DEBUG(errdict);
error = EINVAL;
break;
}
tid = dnvlist_get_number(table, "id", UINT64_MAX);
type = dnvlist_get_number(table, "type", UINT64_MAX);
error = npf_table_check(tblset, name, tid, type);
error = npf_mk_table(npf, table, errdict, tblset, &t, 0);
if (error) {
NPF_ERR_DEBUG(errdict);
break;
}
/* Get the entries or binary data. */
blob = dnvlist_get_binary(table, "data", &size, NULL, 0);
if (type == NPF_TABLE_CONST && (blob == NULL || size == 0)) {
NPF_ERR_DEBUG(errdict);
error = EINVAL;
break;
}
/* Create and insert the table. */
t = npf_table_create(name, (u_int)tid, type, blob, size);
if (t == NULL) {
NPF_ERR_DEBUG(errdict);
error = ENOMEM;
break;
}
error = npf_tableset_insert(tblset, t);
KASSERT(error == 0);
if ((error = npf_mk_table_entries(t, table, errdict)) != 0) {
break;
}
}
*tblsetp = tblset;
return error;
@ -732,6 +755,63 @@ out:
return error;
}
/*
* npfctl_table_replace_nvlist: atomically replace a table's contents
* with the passed table data.
*/
static int __noinline
npfctl_table_replace_nvlist(npf_t *npf, nvlist_t *npf_dict, nvlist_t *errdict)
{
npf_table_t *tbl, *gc_tbl = NULL;
npf_tableset_t *tblset;
int error = 0;
npf_config_enter(npf);
tblset = npf_config_tableset(npf);
/* Get the entries or binary data. */
error = npf_mk_table(npf, npf_dict, errdict, tblset, &tbl, true);
if (error) {
goto err;
}
gc_tbl = npf_tableset_swap(tblset, tbl);
if (gc_tbl == NULL) {
error = EINVAL;
gc_tbl = tbl;
goto err;
}
npf_config_sync(npf);
err:
npf_config_exit(npf);
if (gc_tbl) {
npf_table_destroy(gc_tbl);
}
return error;
}
int
npfctl_table_replace(npf_t *npf, u_long cmd, void *data)
{
nvlist_t *request, *response;
int error;
/*
* Retrieve the configuration and check the version.
* Construct a response with error reporting.
*/
error = npf_nvlist_copyin(npf, data, &request);
if (error) {
return error;
}
response = nvlist_create(0);
error = npfctl_table_replace_nvlist(npf, request, response);
nvlist_add_number(response, "errno", error);
error = npf_nvlist_copyout(npf, data, response);
nvlist_destroy(request);
return error;
}
/*
* npfctl_conn_lookup: lookup a connection in the list of connections
*/

View File

@ -292,6 +292,7 @@ int npfctl_save(npf_t *, u_long, void *);
int npfctl_load(npf_t *, u_long, void *);
int npfctl_rule(npf_t *, u_long, void *);
int npfctl_conn_lookup(npf_t *, u_long, void *);
int npfctl_table_replace(npf_t *, u_long, void *);
int npfctl_table(npf_t *, void *);
void npf_stats_inc(npf_t *, npf_stats_t);
@ -379,7 +380,7 @@ npf_table_t * npf_table_create(const char *, u_int, int, const void *, size_t);
void npf_table_destroy(npf_table_t *);
u_int npf_table_getid(npf_table_t *);
int npf_table_check(npf_tableset_t *, const char *, uint64_t, uint64_t);
int npf_table_check(npf_tableset_t *, const char *, uint64_t, uint64_t, bool);
int npf_table_insert(npf_table_t *, const int,
const npf_addr_t *, const npf_netmask_t);
int npf_table_remove(npf_table_t *, const int,

View File

@ -33,7 +33,7 @@
#ifdef _KERNEL
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf_os.c,v 1.14 2019/08/11 20:26:34 rmind Exp $");
__KERNEL_RCSID(0, "$NetBSD: npf_os.c,v 1.15 2019/08/21 21:45:47 rmind Exp $");
#ifdef _KERNEL_OPT
#include "pf.h"
@ -259,6 +259,9 @@ npf_dev_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
case IOC_NPF_CONN_LOOKUP:
error = npfctl_conn_lookup(npf, cmd, data);
break;
case IOC_NPF_TABLE_REPLACE:
error = npfctl_table_replace(npf, cmd, data);
break;
case IOC_NPF_VERSION:
*(int *)data = NPF_VERSION;
error = 0;

View File

@ -46,7 +46,7 @@
#ifdef _KERNEL
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf_tableset.c,v 1.33 2019/07/23 00:52:01 rmind Exp $");
__KERNEL_RCSID(0, "$NetBSD: npf_tableset.c,v 1.34 2019/08/21 21:45:47 rmind Exp $");
#include <sys/param.h>
#include <sys/types.h>
@ -456,12 +456,15 @@ npf_table_getid(npf_table_t *t)
* npf_table_check: validate the name, ID and type.
*/
int
npf_table_check(npf_tableset_t *ts, const char *name, uint64_t tid, uint64_t type)
npf_table_check(npf_tableset_t *ts, const char *name, uint64_t tid,
uint64_t type, bool replacing)
{
const npf_table_t *t;
if (tid >= ts->ts_nitems) {
return EINVAL;
}
if (ts->ts_map[tid] != NULL) {
if (!replacing && ts->ts_map[tid] != NULL) {
return EEXIST;
}
switch (type) {
@ -476,8 +479,10 @@ npf_table_check(npf_tableset_t *ts, const char *name, uint64_t tid, uint64_t typ
if (strlen(name) >= NPF_TABLE_MAXNAMELEN) {
return ENAMETOOLONG;
}
if (npf_tableset_getbyname(ts, name)) {
return EEXIST;
if ((t = npf_tableset_getbyname(ts, name)) != NULL) {
if (!replacing || t->t_id != tid) {
return EEXIST;
}
}
return 0;
}