root/usr.sbin/npf/npftest/libnpftest/npf_table_test.c
/*
 * NPF tableset tests.
 *
 * Public Domain.
 */

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

#ifdef __linux__
#include <endian.h>
#else
#include <sys/endian.h>
#endif

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

static const char *ip_list[] = {
        "192.168.1.1",
        "10.0.0.1",
        "192.168.2.1",
        "10.1.0.1",
        "192.168.100.253",
        "10.0.5.1",
        "192.168.128.127",
        "10.0.0.2",
};

static const uint8_t ip6_list[][16] = {
        {
                0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x02, 0xa0, 0xc0, 0xff, 0xfe, 0x10, 0x12, 0x34
        },
        {
                0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x02, 0xa0, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00,
        },
        {
                0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        },
        {
                0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x02, 0xa0, 0xc0, 0xff, 0xfe, 0x10, 0x12, 0x30
        }
};

#define IPSET_TID               0
#define IPSET_NAME              "ipset-table"

#define LPM_TID                 1
#define LPM_NAME                "lpm-table"

#define CDB_TID                 2
#define CDB_NAME                "cdb-table"

#define IFADDR_TID              3
#define IFADDR_NAME             ".ifaddr-eth0"

///////////////////////////////////////////////////////////////////////////

static bool
check_ip4(const npf_addr_t *addr, const char *ipstr)
{
        npf_addr_t addr_storage, *test_addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        test_addr->word32[0] = inet_addr(ipstr);
        return memcmp(addr, test_addr, alen) == 0;
}

static bool
ip4list_insert_lookup(npf_table_t *t, unsigned i)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        int error;

        addr->word32[0] = inet_addr(ip_list[i]);
        error = npf_table_insert(t, alen, addr, NPF_NO_NETMASK);
        CHECK_TRUE(error == 0);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);
        return true;
}

static bool
fill_with_ip4(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        const int nm = NPF_NO_NETMASK;

        for (unsigned i = 0; i < __arraycount(ip_list); i++) {
                npf_table_t *t;
                int error;

                addr->word32[0] = inet_addr(ip_list[i]);

                t = npf_tableset_getbyname(tblset, IPSET_NAME);
                error = npf_table_insert(t, alen, addr, nm);
                CHECK_TRUE(error == 0);
                error = npf_table_insert(t, alen, addr, nm);
                CHECK_TRUE(error != 0); // duplicate

                t = npf_tableset_getbyname(tblset, LPM_NAME);
                error = npf_table_insert(t, alen, addr, nm);
                CHECK_TRUE(error == 0);
                error = npf_table_insert(t, alen, addr, nm);
                CHECK_TRUE(error != 0); // duplicate
        }
        return true;
}

static bool
verify_ip4(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const size_t alen = sizeof(struct in_addr);
        const int nm = NPF_NO_NETMASK;
        npf_table_t *t;
        int error;

        /* Attempt to add duplicates - should fail. */
        addr->word32[0] = inet_addr(ip_list[0]);

        t = npf_tableset_getbyname(tblset, IPSET_NAME);
        error = npf_table_insert(t, alen, addr, nm);
        CHECK_TRUE(error != 0);

        t = npf_tableset_getbyname(tblset, LPM_NAME);
        error = npf_table_insert(t, alen, addr, nm);
        CHECK_TRUE(error != 0);

        /* Match (validate) each IP entry. */
        for (unsigned i = 0; i < __arraycount(ip_list); i++) {
                addr->word32[0] = inet_addr(ip_list[i]);

                t = npf_tableset_getbyname(tblset, IPSET_NAME);
                error = npf_table_lookup(t, alen, addr);
                CHECK_TRUE(error == 0);

                t = npf_tableset_getbyname(tblset, LPM_NAME);
                error = npf_table_lookup(t, alen, addr);
                CHECK_TRUE(error == 0);
        }
        return true;
}

static bool
clear_ip4(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        const int nm = NPF_NO_NETMASK;

        for (unsigned i = 0; i < __arraycount(ip_list); i++) {
                npf_table_t *t;
                int error;

                addr->word32[0] = inet_addr(ip_list[i]);

                t = npf_tableset_getbyname(tblset, IPSET_NAME);
                error = npf_table_remove(t, alen, addr, nm);
                CHECK_TRUE(error == 0);

                error = npf_table_remove(t, alen, addr, nm);
                CHECK_TRUE(error != 0);

                t = npf_tableset_getbyname(tblset, LPM_NAME);
                error = npf_table_remove(t, alen, addr, nm);
                CHECK_TRUE(error == 0);

                error = npf_table_remove(t, alen, addr, nm);
                CHECK_TRUE(error != 0);
        }
        return true;
}

///////////////////////////////////////////////////////////////////////////

static bool
test_basic(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        npf_table_t *t;
        int error;

        /* Basic IP set. */
        t = npf_table_create(IPSET_NAME, IPSET_TID, NPF_TABLE_IPSET, NULL, 0);
        CHECK_TRUE(t != NULL);
        error = npf_tableset_insert(tblset, t);
        CHECK_TRUE(error == 0);

        /* Check for double-insert. */
        error = npf_tableset_insert(tblset, t);
        CHECK_TRUE(error != 0);

        /* Longest-prefix match (LPM). */
        t = npf_table_create(LPM_NAME, LPM_TID, NPF_TABLE_LPM, NULL, 0);
        CHECK_TRUE(t != NULL);
        error = npf_tableset_insert(tblset, t);
        CHECK_TRUE(error == 0);

        /* Table for interface addresses. */
        t = npf_table_create(IFADDR_NAME, IFADDR_TID, NPF_TABLE_IFADDR, NULL, 0);
        CHECK_TRUE(t != NULL);
        error = npf_tableset_insert(tblset, t);
        CHECK_TRUE(error == 0);

        /*
         * Attempt to match some non-existing entries - should fail.
         */
        addr->word32[0] = inet_addr(ip_list[0]);

        t = npf_tableset_getbyname(tblset, IPSET_NAME);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error != 0);

        t = npf_tableset_getbyname(tblset, LPM_NAME);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error != 0);

        return true;
}

static bool
test_nocopy(npf_tableset_t *tblset)
{
        const int alen = sizeof(struct in_addr);
        const char *tables[] = { IPSET_NAME, LPM_NAME, IFADDR_NAME };
        npf_addr_t *addr, lookup_addr;

        for (unsigned i = 0; i < __arraycount(tables); i++) {
                npf_table_t *t;
                int error;

                addr = kmem_zalloc(sizeof(npf_addr_t), KM_SLEEP);
                assert(addr != NULL);
                addr->word32[0] = inet_addr("172.16.90.10");

                t = npf_tableset_getbyname(tblset, tables[i]);
                (void)npf_table_flush(t);

                error = npf_table_insert(t, alen, addr, NPF_NO_NETMASK);
                CHECK_TRUE(error == 0);

                memcpy(&lookup_addr, addr, alen);
                memset(addr, 0xa5, alen); // explicit memset

                error = npf_table_lookup(t, alen, &lookup_addr);
                CHECK_TRUE(error == 0);

                CHECK_TRUE(*(volatile unsigned char *)addr == 0xa5);
                kmem_free(addr, sizeof(npf_addr_t));
        }
        return true;
}

static bool
test_ip6(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const size_t alen = sizeof(struct in6_addr);
        const int nm = NPF_NO_NETMASK;
        npf_table_t *t;
        int error;

        /* IPv6 addresses. */
        memcpy(addr, ip6_list[0], sizeof(ip6_list[0]));

        t = npf_tableset_getbyname(tblset, IPSET_NAME);
        error = npf_table_insert(t, alen, addr, nm);
        CHECK_TRUE(error == 0);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);
        error = npf_table_remove(t, alen, addr, nm);
        CHECK_TRUE(error == 0);

        t = npf_tableset_getbyname(tblset, LPM_NAME);
        error = npf_table_insert(t, alen, addr, nm);
        CHECK_TRUE(error == 0);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);
        error = npf_table_remove(t, alen, addr, nm);
        CHECK_TRUE(error == 0);

        return true;
}

static bool
test_lpm_masks4(npf_tableset_t *tblset)
{
        npf_table_t *t = npf_tableset_getbyname(tblset, LPM_NAME);
        npf_addr_t addr_storage, *addr = &addr_storage;
        const size_t alen = sizeof(struct in_addr);
        int error;

        addr->word32[0] = inet_addr("172.16.90.0");
        error = npf_table_insert(t, alen, addr, 25);
        CHECK_TRUE(error == 0);

        addr->word32[0] = inet_addr("172.16.90.126");
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);

        addr->word32[0] = inet_addr("172.16.90.128");
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error != 0);

        return true;
}

static bool
test_lpm_masks6(npf_tableset_t *tblset)
{
        npf_table_t *t = npf_tableset_getbyname(tblset, LPM_NAME);
        npf_addr_t addr_storage, *addr = &addr_storage;
        const size_t alen = sizeof(struct in6_addr);
        int error;

        /*
         * 96
         */
        memcpy(addr, ip6_list[1], sizeof(ip6_list[1]));
        error = npf_table_insert(t, alen, addr, 96);
        CHECK_TRUE(error == 0);

        memcpy(addr, ip6_list[0], sizeof(ip6_list[0]));
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);

        memcpy(addr, ip6_list[1], sizeof(ip6_list[1]));
        error = npf_table_remove(t, alen, addr, 96);
        CHECK_TRUE(error == 0);

        /*
         * 32
         */
        memcpy(addr, ip6_list[2], sizeof(ip6_list[2]));
        error = npf_table_insert(t, alen, addr, 32);
        CHECK_TRUE(error == 0);

        memcpy(addr, ip6_list[0], sizeof(ip6_list[0]));
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);

        memcpy(addr, ip6_list[2], sizeof(ip6_list[2]));
        error = npf_table_remove(t, alen, addr, 32);
        CHECK_TRUE(error == 0);

        /*
         * 126
         */
        memcpy(addr, ip6_list[3], sizeof(ip6_list[3]));
        error = npf_table_insert(t, alen, addr, 126);
        CHECK_TRUE(error == 0);

        memcpy(addr, ip6_list[0], sizeof(ip6_list[0]));
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error != 0);

        memcpy(addr, ip6_list[3], sizeof(ip6_list[3]));
        error = npf_table_remove(t, alen, addr, 126);
        CHECK_TRUE(error == 0);

        return true;
}

static bool
test_const_table(npf_tableset_t *tblset, void *blob, size_t size)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        const int alen = sizeof(struct in_addr);
        npf_table_t *t;
        int error;

        t = npf_table_create(CDB_NAME, CDB_TID, NPF_TABLE_CONST, blob, size);
        CHECK_TRUE(t != NULL);

        error = npf_tableset_insert(tblset, t);
        CHECK_TRUE(error == 0);

        addr->word32[0] = inet_addr(ip_list[0]);
        error = npf_table_lookup(t, alen, addr);
        CHECK_TRUE(error == 0);

        for (unsigned i = 1; i < __arraycount(ip_list) - 1; i++) {
                addr->word32[0] = inet_addr(ip_list[i]);
                error = npf_table_lookup(t, alen, addr);
                CHECK_TRUE(error != 0);
        }
        return true;
}

static bool
test_ifaddr_table(npf_tableset_t *tblset)
{
        npf_addr_t addr_storage, *addr = &addr_storage;
        npf_table_t *t = npf_tableset_getbyname(tblset, IFADDR_NAME);
        int error;
        bool ok;

        /* Two IPv4 addresses. */
        ok = ip4list_insert_lookup(t, 0);
        CHECK_TRUE(ok);

        ok = ip4list_insert_lookup(t, 1);
        CHECK_TRUE(ok);

        /* And one IPv6 address. */
        memcpy(addr, ip6_list[0], sizeof(ip6_list[0]));
        error = npf_table_insert(t, sizeof(struct in6_addr), addr, NPF_NO_NETMASK);
        CHECK_TRUE(error == 0);

        /*
         * Get IPv4 addresses.
         */
        addr = npf_table_getsome(t, sizeof(struct in_addr), 0);
        ok = check_ip4(addr, "192.168.1.1");
        CHECK_TRUE(ok);

        addr = npf_table_getsome(t, sizeof(struct in_addr), 1);
        ok = check_ip4(addr, "10.0.0.1");
        CHECK_TRUE(ok);

        addr = npf_table_getsome(t, sizeof(struct in_addr), 2);
        ok = check_ip4(addr, "192.168.1.1");
        CHECK_TRUE(ok);

        return true;
}

static void
test_ipset_gc(npf_tableset_t *tblset)
{
        npf_table_t *t = npf_tableset_getbyname(tblset, IPSET_NAME);
        npf_t *npf = npf_getkernctx();

        npf_config_enter(npf);
        npf_table_gc(npf, t);
        npf_table_flush(t);
        npf_config_exit(npf);
}

bool
npf_table_test(bool verbose, void *blob, size_t size)
{
        npf_tableset_t *tblset;
        bool ok;

        (void)verbose;

        tblset = npf_tableset_create(4);
        CHECK_TRUE(tblset != NULL);

        ok = test_basic(tblset);
        CHECK_TRUE(ok);

        /*
         * Fill IPSET and LPM tables with IPv4 addresses.
         * Keep them in the table during the other tests.
         */
        ok = fill_with_ip4(tblset);
        CHECK_TRUE(ok);

        ok = verify_ip4(tblset);
        CHECK_TRUE(ok);

        ok = test_ip6(tblset);
        CHECK_TRUE(ok);

        ok = test_lpm_masks4(tblset);
        CHECK_TRUE(ok);

        ok = test_lpm_masks6(tblset);
        CHECK_TRUE(ok);

        ok = test_const_table(tblset, blob, size);
        CHECK_TRUE(ok);

        ok = test_ifaddr_table(tblset);
        CHECK_TRUE(ok);

        /*
         * Remove the above IPv4 addresses -- they must have been untouched.
         */
        ok = clear_ip4(tblset);
        CHECK_TRUE(ok);

        ok = test_nocopy(tblset);
        CHECK_TRUE(ok);

        test_ipset_gc(tblset);

        npf_tableset_destroy(tblset);
        return true;
}