#ifdef _KERNEL
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf_conndb.c,v 1.9 2020/05/30 14:16:56 rmind Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/thmap.h>
#endif
#define __NPF_CONN_PRIVATE
#include "npf_conn.h"
#include "npf_impl.h"
struct npf_conndb {
thmap_t * cd_map;
npf_conn_t * cd_new;
LIST_HEAD(, npf_conn) cd_list;
LIST_HEAD(, npf_conn) cd_gclist;
npf_conn_t * cd_marker;
};
typedef struct {
int step;
int interval_min;
int interval_max;
} npf_conndb_params_t;
#define CONNDB_FORW_BIT ((uintptr_t)0x1)
#define CONNDB_ISFORW_P(p) (((uintptr_t)(p) & CONNDB_FORW_BIT) != 0)
#define CONNDB_GET_PTR(p) ((void *)((uintptr_t)(p) & ~CONNDB_FORW_BIT))
void
npf_conndb_sysinit(npf_t *npf)
{
npf_conndb_params_t *params = npf_param_allocgroup(npf,
NPF_PARAMS_CONNDB, sizeof(npf_conndb_params_t));
npf_param_t param_map[] = {
{
"gc.step",
¶ms->step,
.default_val = 256,
.min = 1, .max = INT_MAX
},
{
"gc.interval_min",
¶ms->interval_min,
.default_val = 50,
.min = 10, .max = 10000
},
{
"gc.interval_max",
¶ms->interval_max,
.default_val = 5000,
.min = 10, .max = 10000
},
};
npf_param_register(npf, param_map, __arraycount(param_map));
}
void
npf_conndb_sysfini(npf_t *npf)
{
const size_t len = sizeof(npf_conndb_params_t);
npf_param_freegroup(npf, NPF_PARAMS_CONNDB, len);
}
npf_conndb_t *
npf_conndb_create(void)
{
npf_conndb_t *cd;
cd = kmem_zalloc(sizeof(npf_conndb_t), KM_SLEEP);
cd->cd_map = thmap_create(0, NULL, THMAP_NOCOPY);
KASSERT(cd->cd_map != NULL);
LIST_INIT(&cd->cd_list);
LIST_INIT(&cd->cd_gclist);
return cd;
}
void
npf_conndb_destroy(npf_conndb_t *cd)
{
KASSERT(cd->cd_new == NULL);
KASSERT(cd->cd_marker == NULL);
KASSERT(LIST_EMPTY(&cd->cd_list));
KASSERT(LIST_EMPTY(&cd->cd_gclist));
thmap_destroy(cd->cd_map);
kmem_free(cd, sizeof(npf_conndb_t));
}
npf_conn_t *
npf_conndb_lookup(npf_t *npf, const npf_connkey_t *ck, npf_flow_t *flow)
{
npf_conndb_t *cd = atomic_load_relaxed(&npf->conn_db);
const unsigned keylen = NPF_CONNKEY_LEN(ck);
npf_conn_t *con;
void *val;
int s = npf_config_read_enter(npf);
val = thmap_get(cd->cd_map, ck->ck_key, keylen);
if (!val) {
npf_config_read_exit(npf, s);
return NULL;
}
*flow = CONNDB_ISFORW_P(val) ? NPF_FLOW_FORW : NPF_FLOW_BACK;
con = CONNDB_GET_PTR(val);
KASSERT(con != NULL);
atomic_inc_uint(&con->c_refcnt);
npf_config_read_exit(npf, s);
return con;
}
bool
npf_conndb_insert(npf_conndb_t *cd, const npf_connkey_t *ck,
npf_conn_t *con, npf_flow_t flow)
{
const unsigned keylen = NPF_CONNKEY_LEN(ck);
const uintptr_t tag = (CONNDB_FORW_BIT * !flow);
void *val;
bool ok;
KASSERT(!CONNDB_ISFORW_P(con));
val = (void *)((uintptr_t)(void *)con | tag);
int s = splsoftnet();
ok = thmap_put(cd->cd_map, ck->ck_key, keylen, val) == val;
splx(s);
return ok;
}
npf_conn_t *
npf_conndb_remove(npf_conndb_t *cd, npf_connkey_t *ck)
{
const unsigned keylen = NPF_CONNKEY_LEN(ck);
void *val;
int s = splsoftnet();
val = thmap_del(cd->cd_map, ck->ck_key, keylen);
splx(s);
return CONNDB_GET_PTR(val);
}
void
npf_conndb_enqueue(npf_conndb_t *cd, npf_conn_t *con)
{
npf_conn_t *head;
do {
head = atomic_load_relaxed(&cd->cd_new);
atomic_store_relaxed(&con->c_next, head);
} while (atomic_cas_ptr(&cd->cd_new, head, con) != head);
}
static void
npf_conndb_update(npf_conndb_t *cd)
{
npf_conn_t *con;
con = atomic_swap_ptr(&cd->cd_new, NULL);
while (con) {
npf_conn_t *next = atomic_load_relaxed(&con->c_next);
LIST_INSERT_HEAD(&cd->cd_list, con, c_entry);
con = next;
}
}
npf_conn_t *
npf_conndb_getlist(npf_conndb_t *cd)
{
npf_conndb_update(cd);
return LIST_FIRST(&cd->cd_list);
}
npf_conn_t *
npf_conndb_getnext(npf_conndb_t *cd, npf_conn_t *con)
{
if (con == NULL || (con = LIST_NEXT(con, c_entry)) == NULL) {
con = LIST_FIRST(&cd->cd_list);
}
return con;
}
static unsigned
npf_conndb_gc_incr(npf_t *npf, npf_conndb_t *cd, const time_t now)
{
const npf_conndb_params_t *params = npf->params[NPF_PARAMS_CONNDB];
unsigned target = params->step;
unsigned gc_conns = 0;
npf_conn_t *con;
KASSERT(mutex_owned(&npf->conn_lock));
if ((con = cd->cd_marker) == NULL) {
con = npf_conndb_getnext(cd, NULL);
cd->cd_marker = con;
}
while (con && target--) {
npf_conn_t *next = npf_conndb_getnext(cd, con);
if (npf_conn_expired(npf, con, now)) {
LIST_REMOVE(con, c_entry);
LIST_INSERT_HEAD(&cd->cd_gclist, con, c_entry);
npf_conn_remove(cd, con);
gc_conns++;
if (con == next) {
next = NULL;
}
if (con == cd->cd_marker) {
cd->cd_marker = next;
con = next;
continue;
}
}
con = next;
if (con == cd->cd_marker) {
break;
}
}
cd->cd_marker = con;
return gc_conns;
}
static unsigned
gc_freq_tune(const npf_t *npf, const npf_conndb_t *cd, const unsigned n)
{
const npf_conndb_params_t *params = npf->params[NPF_PARAMS_CONNDB];
int wtime = npf->worker_wait_time;
wtime = n ? (wtime >> 1) : (wtime << 1);
return MAX(MIN(wtime, params->interval_max), params->interval_min);
}
void
npf_conndb_gc(npf_t *npf, npf_conndb_t *cd, bool flush, bool sync)
{
struct timespec tsnow;
unsigned gc_conns = 0;
npf_conn_t *con;
void *gcref;
getnanouptime(&tsnow);
mutex_enter(&npf->conn_lock);
npf_conndb_update(cd);
if (flush) {
while ((con = LIST_FIRST(&cd->cd_list)) != NULL) {
LIST_REMOVE(con, c_entry);
LIST_INSERT_HEAD(&cd->cd_gclist, con, c_entry);
npf_conn_remove(cd, con);
}
cd->cd_marker = NULL;
} else {
gc_conns = npf_conndb_gc_incr(npf, cd, tsnow.tv_sec);
}
mutex_exit(&npf->conn_lock);
gcref = thmap_stage_gc(cd->cd_map);
if (sync && (gcref || !LIST_EMPTY(&cd->cd_gclist))) {
npf_config_enter(npf);
npf_config_sync(npf);
npf_config_exit(npf);
}
thmap_gc(cd->cd_map, gcref);
npf->worker_wait_time = gc_freq_tune(npf, cd, gc_conns);
if (LIST_EMPTY(&cd->cd_gclist)) {
return;
}
while ((con = LIST_FIRST(&cd->cd_gclist)) != NULL) {
const unsigned refcnt = atomic_load_relaxed(&con->c_refcnt);
if (__predict_false(refcnt)) {
if (flush) {
kpause("npfcongc", false, 1, NULL);
continue;
}
break;
}
LIST_REMOVE(con, c_entry);
npf_conn_destroy(npf, con);
}
}