root/usr.sbin/npf/npftest/libnpftest/npf_rule_test.c
/*
 * NPF ruleset tests.
 *
 * Public Domain.
 */

#ifdef _KERNEL
#include <sys/types.h>
#endif

#include "npf_impl.h"
#include "npf_test.h"

#define RESULT_PASS     0
#define RESULT_BLOCK    ENETUNREACH

static const struct test_case {
        int             af;
        const char *    src;
        const char *    dst;
        const char *    ifname;
        int             di;
        int             stateful_ret;
        int             ret;
} test_cases[] = {

        /* Stateful pass. */
        {
                .af = AF_INET,
                .src = "10.1.1.1",              .dst = "10.1.1.2",
                .ifname = IFNAME_INT,           .di = PFIL_OUT,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {
                .af = AF_INET,
                .src = "10.1.1.2",              .dst = "10.1.1.1",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_BLOCK
        },

        /* Pass forwards stream only. */
        {
                .af = AF_INET,
                .src = "10.1.1.1",              .dst = "10.1.1.3",
                .ifname = IFNAME_INT,           .di = PFIL_OUT,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {
                .af = AF_INET,
                .src = "10.1.1.3",              .dst = "10.1.1.1",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
                /* PR bin/59511 */
        {
                .af = AF_INET,
                .src = "192.168.100.5",         .dst = "10.1.1.5",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {
                .af = AF_INET,
                .src = "192.168.100.8",         .dst = "10.1.1.5",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {
                .af = AF_INET,
                .src = "192.168.64.3",          .dst = "10.1.1.5",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {
                .af = AF_INET,
                .src = "192.168.64.9",          .dst = "10.1.1.5",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },

        /*
         * Pass in from any of the { fe80::1, fe80:1000:0:0/95,
         * fe80::2, fe80::2000:0:0/96, fe80::3, fe80::3000:0:0/97 }
         * group.
         */
        {                       /* fe80::1 */
                .af = AF_INET6,
                .src = "fe80::1", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::1000:0:0/95 */
                .af = AF_INET6,
                .src = "fe80::1001:0:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::1000:0:0/95, one bit off */
                .af = AF_INET6,
                .src = "fe80::1003:0:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {                       /* fe80::2 */
                .af = AF_INET6,
                .src = "fe80::2", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::2000:0:0/96 */
                .af = AF_INET6,
                .src = "fe80::2000:8000:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::2000:0:0/96, one bit off */
                .af = AF_INET6,
                .src = "fe80::2001:8000:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {                       /* fe80::3 */
                .af = AF_INET6,
                .src = "fe80::3", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::3000:0:0/97 */
                .af = AF_INET6,
                .src = "fe80::3000:7fff:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::3000:0:0/97, one bit off */
                .af = AF_INET6,
                .src = "fe80::3000:ffff:0", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {
                .af = AF_INET6,
                .src = "fe80::4", .dst = "fe80::adec:c91c:d116:7592",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },

        /*
         * Pass in from anywhere _not_ in that group, as long as it is
         * to that group.
         */
        {                       /* fe80::1 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::1000:0:0/95 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1001:0:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::1000:0:0/95, one bit off */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::1003:0:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {                       /* fe80::2 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::2000:0:0/96 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2000:8000:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::2000:0:0/96, one bit off */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::2001:8000:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {                       /* fe80::3 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::3000:0:0/97 */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3000:7fff:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_PASS,    .ret = RESULT_PASS
        },
        {                       /* fe80::3000:0:0/97, one bit off */
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::3000:ffff:0",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {
                .af = AF_INET6,
                .src = "fe80::adec:c91c:d116:7592", .dst = "fe80::4",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },

        /* Block. */
        {
                .af = AF_INET,
                .src = "10.1.1.1",              .dst = "10.1.1.4",
                .ifname = IFNAME_INT,           .di = PFIL_OUT,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },
        {       .src = "10.1.1.3",              .dst = "10.1.1.1",
                .ifname = IFNAME_INT,           .di = PFIL_IN,
                .stateful_ret = RESULT_BLOCK,   .ret = RESULT_BLOCK
        },

};

static int
run_raw_testcase(unsigned i)
{
        const struct test_case *t = &test_cases[i];
        npf_t *npf = npf_getkernctx();
        npf_cache_t *npc;
        struct mbuf *m;
        npf_rule_t *rl;
        int slock, error;

        m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
        npc = get_cached_pkt(m, t->ifname, NPF_RULE_LAYER_3);

        slock = npf_config_read_enter(npf);
        rl = npf_ruleset_inspect(npc, npf_config_ruleset(npf), t->di, NPF_RULE_LAYER_3);
        if (rl) {
                npf_match_info_t mi;
                error = npf_rule_conclude(rl, &mi);
        } else {
                error = ENOENT;
        }
        npf_config_read_exit(npf, slock);

        put_cached_pkt(npc);
        return error;
}

static int
run_handler_testcase(unsigned i)
{
        const struct test_case *t = &test_cases[i];
        ifnet_t *ifp = npf_test_getif(t->ifname);
        npf_t *npf = npf_getkernctx();
        struct mbuf *m;
        int error;

        m = mbuf_get_pkt(t->af, IPPROTO_UDP, t->src, t->dst, 9000, 9000);
        error = npfk_packet_handler(npf, &m, ifp, t->di);
        if (m) {
                m_freem(m);
        }
        return error;
}

static npf_rule_t *
npf_blockall_rule(void)
{
        npf_t *npf = npf_getkernctx();
        nvlist_t *rule = nvlist_create(0);
        npf_rule_t *rl;

        nvlist_add_number(rule, "attr",
            NPF_RULE_IN | NPF_RULE_OUT | NPF_RULE_DYNAMIC);
        rl = npf_rule_alloc(npf, rule);
        nvlist_destroy(rule);
        return rl;
}

static bool
test_static(bool verbose)
{
        for (unsigned i = 0; i < __arraycount(test_cases); i++) {
                const struct test_case *t = &test_cases[i];
                int error, serror;

                if (npf_test_getif(t->ifname) == NULL) {
                        printf("Interface %s is not configured.\n", t->ifname);
                        return false;
                }

                error = run_raw_testcase(i);
                serror = run_handler_testcase(i);

                if (verbose) {
                        printf("rule test %d:\texpected %d (stateful) and %d\n"
                            "\t\t-> returned %d and %d\n",
                            i + 1, t->stateful_ret, t->ret, serror, error);
                }
                CHECK_TRUE(error == t->ret);
                CHECK_TRUE(serror == t->stateful_ret)
        }
        return true;
}

static bool
test_dynamic(void)
{
        npf_t *npf = npf_getkernctx();
        npf_ruleset_t *rlset;
        npf_rule_t *rl;
        uint64_t id;
        int error;

        /*
         * Test dynamic NPF rules.
         */

        error = run_raw_testcase(0);
        CHECK_TRUE(error == RESULT_PASS);

        npf_config_enter(npf);
        rlset = npf_config_ruleset(npf);

        rl = npf_blockall_rule();
        error = npf_ruleset_add(rlset, "test-rules", rl);
        CHECK_TRUE(error == 0);

        error = run_raw_testcase(0);
        CHECK_TRUE(error == RESULT_BLOCK);

        id = npf_rule_getid(rl);
        error = npf_ruleset_remove(rlset, "test-rules", id);
        CHECK_TRUE(error == 0);

        npf_config_exit(npf);

        error = run_raw_testcase(0);
        CHECK_TRUE(error == RESULT_PASS);

        return true;
}

bool
npf_rule_test(bool verbose)
{
        bool ok;

        ok = test_static(verbose);
        CHECK_TRUE(ok);

        ok = test_dynamic();
        CHECK_TRUE(ok);

        return true;
}