/*
* NPF connection tests.
*
* Public Domain.
*/
#ifdef _KERNEL
#include <sys/types.h>
#include <sys/kmem.h>
#endif
#include "npf.h"
#include "npf_impl.h"
#include "npf_conn.h"
#include "npf_test.h"
static bool lverbose = false;
static unsigned
count_conns(npf_conndb_t *cd)
{
npf_conn_t *head = npf_conndb_getlist(cd), *conn = head;
unsigned n = 0;
while (conn) {
n++;
conn = npf_conndb_getnext(cd, conn);
if (conn == head) {
break;
}
}
return n;
}
static struct mbuf *
get_packet(unsigned i)
{
struct mbuf *m;
struct ip *ip;
m = mbuf_get_pkt(AF_INET, IPPROTO_UDP,
"10.0.0.1", "172.16.0.1", 9000, 9000);
(void)mbuf_return_hdrs(m, false, &ip);
ip->ip_src.s_addr += i;
return m;
}
static bool
enqueue_connection(unsigned i, bool expire)
{
struct mbuf *m = get_packet(i);
npf_cache_t *npc = get_cached_pkt(m, NULL, NPF_RULE_LAYER_3);
npf_conn_t *con;
con = npf_conn_establish(npc, PFIL_IN, true);
CHECK_TRUE(con != NULL);
if (expire) {
npf_conn_expire(con);
}
npf_conn_release(con);
put_cached_pkt(npc);
return true;
}
static bool
run_conn_gc(unsigned active, unsigned expired, unsigned expected)
{
npf_t *npf = npf_getkernctx();
npf_conndb_t *cd = npf_conndb_create();
unsigned total, n = 0;
npf->conn_db = cd;
/*
* Insert the given number of active and expired connections..
*/
total = active + expired;
while (active || expired) {
if (active) {
enqueue_connection(n++, false);
active--;
}
if (expired) {
enqueue_connection(n++, true);
expired--;
}
}
/* Verify the count. */
n = count_conns(cd);
CHECK_TRUE(n == total);
/*
* Run G/C. Check the remaining.
*/
npf_conndb_gc(npf, cd, false, false);
n = count_conns(cd);
if (lverbose) {
printf("in conndb -- %u (expected %u)\n", n, expected);
}
CHECK_TRUE(n == expected);
/* Flush and destroy. */
npf_conndb_gc(npf, cd, true, false);
npf_conndb_destroy(cd);
npf->conn_db = NULL;
return true;
}
static bool
run_gc_tests(void)
{
bool ok;
int val;
/* Check the default value. */
npfk_param_get(npf_getkernctx(), "gc.step", &val);
CHECK_TRUE(val == 256);
/* Empty => GC => 0 in conndb. */
ok = run_conn_gc(0, 0, 0);
CHECK_TRUE(ok);
/* 1 active => GC => 1 in conndb. */
ok = run_conn_gc(1, 0, 1);
CHECK_TRUE(ok);
/* 1 expired => GC => 0 in conndb. */
ok = run_conn_gc(0, 1, 0);
CHECK_TRUE(ok);
/* 1 active and 1 expired => GC => 1 in conndb. */
ok = run_conn_gc(1, 1, 1);
CHECK_TRUE(ok);
/* 2 expired => GC => 0 in conndb. */
ok = run_conn_gc(0, 2, 0);
CHECK_TRUE(ok);
/* 128 expired => GC => 0 in conndb. */
ok = run_conn_gc(0, 128, 0);
CHECK_TRUE(ok);
/* 512 expired => GC => 256 in conndb. */
ok = run_conn_gc(0, 512, 256);
CHECK_TRUE(ok);
/* 512 expired => GC => 127 in conndb. */
npfk_param_set(npf_getkernctx(), "gc.step", 128);
ok = run_conn_gc(0, 512, 384);
CHECK_TRUE(ok);
return true;
}
static bool
run_conndb_tests(npf_t *npf)
{
npf_conndb_t *orig_cd = npf->conn_db;
bool ok;
npf_config_enter(npf);
npf_conn_tracking(npf, true);
npf_config_exit(npf);
ok = run_gc_tests();
/* We *MUST* restore the valid conndb. */
npf->conn_db = orig_cd;
return ok;
}
static void
worker_test_task(npf_t *npf)
{
bool *done = atomic_load_acquire(&npf->arg);
atomic_store_release(done, true);
}
static bool
run_worker_tests(npf_t *npf)
{
unsigned n = 100;
int error;
/* Spawn a worker thread. */
error = npf_worker_sysinit(1);
assert(error == 0);
/*
* Enlist/discharge an instance, trying to trigger a race.
*/
while (n--) {
bool task_done = false;
unsigned retry = 100;
npf_t *test_npf;
/*
* Initialize a dummy NPF instance and add a test task.
* We will (ab)use npf_t::arg here.
*
* XXX/TODO: We should use:
*
* npfk_create(NPF_NO_GC, &npftest_mbufops,
* &npftest_ifops, &task_done);
*
* However, it resets the interface state and breaks
* other tests; to be refactor.
*/
test_npf = kmem_zalloc(sizeof(npf_t), KM_SLEEP);
atomic_store_release(&test_npf->arg, &task_done);
test_npf->ebr = npf_ebr_create();
error = npf_worker_addfunc(test_npf, worker_test_task);
assert(error == 0);
/* Enlist the NPF instance. */
npf_worker_enlist(test_npf);
/* Wait for the task to be done. */
while (!atomic_load_acquire(&task_done) && retry--) {
npf_worker_signal(test_npf);
kpause("gctest", false, MAX(1, mstohz(1)), NULL);
}
CHECK_TRUE(atomic_load_acquire(&task_done));
npf_worker_discharge(test_npf);
/* Clear the parameter and signal again. */
atomic_store_release(&test_npf->arg, NULL);
npf_worker_signal(test_npf);
npf_ebr_destroy(test_npf->ebr);
kmem_free(test_npf, sizeof(npf_t)); // npfk_destroy()
}
/*
* Destroy the worker.
*
* Attempts to enlist, discharge or signal should have no effect.
*/
npf_worker_sysfini();
npf_worker_enlist(npf);
npf_worker_signal(npf);
npf_worker_discharge(npf);
return true;
}
bool
npf_gc_test(bool verbose)
{
npf_t *npf = npf_getkernctx();
bool ok;
lverbose = verbose;
ok = run_conndb_tests(npf);
CHECK_TRUE(ok);
ok = run_worker_tests(npf);
CHECK_TRUE(ok);
return ok;
}