root/usr.sbin/npf/npftest/libnpftest/npf_rid_test.c
/*
 * NPF socket User/group id tests.
 *
 * Public Domain.
 */

#ifdef _KERNEL
#include <sys/types.h>
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: npf_rid_test.c,v 1.4 2026/03/08 16:59:24 christos Exp $");
#endif

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

#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/kauth.h>
#include <sys/socketvar.h>
#include <sys/lwp.h>
#include <sys/cpu.h>

#define RESULT_PASS     0
#define RESULT_BLOCK    ENETUNREACH

/* this port number suitable for testing */
#define REMOTE_PORT     65500
#define LOCAL_PORT      65000
#define LOCAL_IP        "127.0.0.1"
#define REMOTE_IP       LOCAL_IP

static const struct test_case {
        int             af;
        const char *    src;
        uint16_t        sport;
        const char *    dst;
        uint16_t        dport;
        uint32_t        uid;
        uint32_t        gid;
        const char *    ifname;
        int             di;
        int     ret;
        int     stateful_ret;
} test_cases[] = {
        {
                /* pass in final from $local_ip4 user $Kojo = 1001 group $wheel = 20 */
                .af = AF_INET,
                .src = "10.1.1.4",                      .sport = 9000,
                .dst = LOCAL_IP,                        .dport = LOCAL_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_IN,
                .uid = 1001,                            .gid = 20, /* matches so pass it */
                .ret = RESULT_PASS,                     .stateful_ret = RESULT_PASS
        },
        {
                /* connect on different UID and block */
                .af = AF_INET,
                .src = "10.1.1.4",                      .sport = 9000,
                .dst = LOCAL_IP,                        .dport = LOCAL_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_IN,
                .uid = 1001,                            .gid = 10, /* mismatch gid so block it */
                .ret = RESULT_BLOCK,                    .stateful_ret = RESULT_BLOCK
        },
        {
                .af = AF_INET,
                .src = "10.1.1.4",                      .sport = 9000,
                .dst = LOCAL_IP,                        .dport = LOCAL_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_IN,
                .uid = 100,                             .gid = 20, /* mismatch uid so block it */
                .ret = RESULT_BLOCK,                    .stateful_ret = RESULT_BLOCK
        },


        /* block out final to 127.0.0.1 user > $Kojo( > 1001) group 1 >< $wheel( IRG 1 >< 20) */
        {
                .af = AF_INET,
                .src = LOCAL_IP,                        .sport = LOCAL_PORT,
                .dst = REMOTE_IP,                       .dport = REMOTE_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_OUT,
                .uid = 1005,                            .gid = 14, /* matches so blocks it */
                .ret = RESULT_BLOCK,                    .stateful_ret = RESULT_BLOCK
        },
        {
                .af = AF_INET,
                .src = LOCAL_IP,                        .sport = LOCAL_PORT,
                .dst = REMOTE_IP,                       .dport = REMOTE_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_OUT,
                .uid = 1005,                            .gid = 30, /* mismatch gid so pass it */
                .ret = RESULT_PASS,                     .stateful_ret = RESULT_PASS
        },
        {
                .af = AF_INET,
                .src = LOCAL_IP,                        .sport = LOCAL_PORT,
                .dst = REMOTE_IP,                       .dport = REMOTE_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_OUT,
                .uid = 100,                             .gid = 15, /* mismatch uid so pass it */
                .ret = RESULT_PASS,                     .stateful_ret = RESULT_PASS
        },
        {
                .af = AF_INET,
                .src = LOCAL_IP,                        .sport = LOCAL_PORT,
                .dst = REMOTE_IP,                       .dport = REMOTE_PORT,
                .ifname = IFNAME_EXT,                   .di = PFIL_OUT,
                .uid = 1010,                            .gid = 11, /* matches so blocks it */
                .ret = RESULT_BLOCK,                    .stateful_ret = RESULT_BLOCK
        },
};

static int
run_raw_testcase(unsigned i, bool verbose)
{
        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, t->sport, t->dport);
        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;
                int id_match;

                mutex_enter(softnet_lock);
                id_match = npf_rule_match_rid(rl, npc, t->di);
                mutex_exit(softnet_lock);
                error = npf_rule_conclude(rl, &mi);
                if (verbose)
                        printf("id match is ...%d\n", id_match);
                if (id_match != -1 && !id_match) {
                        error = npf_rule_reverse(npc, &mi, error);
                }

        } 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, t->sport, t->dport);
        mutex_enter(softnet_lock);
        error = npfk_packet_handler(npf, &m, ifp, t->di);
        mutex_exit(softnet_lock);
        if (m) {
                m_freem(m);
        }
        return error;
}

/*
 * we create our specific server socket here which listens on
 * loopback address and port 65000. easier to test pcb lookup here since
 * it will be loaded into the protocol table.
 */
static struct socket *
test_socket(int dir, uid_t uid, gid_t gid)
{
        struct sockaddr_in server;
        struct lwp *cur = curlwp;
        void *p, *rp;

        memset(&server, 0, sizeof(server));

        server.sin_len = sizeof(server);
        server.sin_family = AF_INET;
        p = &server.sin_addr.s_addr;
        npf_inet_pton(AF_INET, LOCAL_IP, p); /* we bind to 127.0.0.1 */
        server.sin_port = htons(LOCAL_PORT);

        struct socket *so;
        int error = socreate(AF_INET, &so, SOCK_DGRAM, 0, cur, NULL);
        if (error) {
                printf("socket creation failed: error is %d\n", error);
                return NULL;
        }

        solock(so);

        kauth_cred_t cred = kauth_cred_alloc();
        kauth_cred_seteuid(cred, uid);
        kauth_cred_setegid(cred, gid);

        kauth_cred_t old = so->so_cred;
        so->so_cred = kauth_cred_dup(cred);
        kauth_cred_free(old);

        sounlock(so);

        if ((error = sobind(so, (struct sockaddr *)&server, cur)) != 0) {
                printf("bind failed %d\n", error);
                return NULL;
        }

        if (dir == PFIL_OUT) {
                /* connect to an additional remote address to set the 4 tuple addr-port state */
                struct sockaddr_in remote;
                memset(&remote, 0, sizeof(remote));

                remote.sin_len = sizeof(remote);
                remote.sin_family = AF_INET;
                rp = &remote.sin_addr.s_addr;
                npf_inet_pton(AF_INET, REMOTE_IP, rp); /* we connect to 127.0.0.1 */
                remote.sin_port = htons(REMOTE_PORT);

                solock(so);
                if ((error = soconnect(so, (struct sockaddr *)&remote, cur)) != 0) {
                        printf("connect failed :%d\n", error);
                        return NULL;
                }
                sounlock(so);
        }

        return so;
}

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

                so = test_socket(t->di, t->uid, t->gid);
                if (so == NULL) {
                        printf("socket:\n");
                        return false;
                }

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

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

                if (verbose) {
                        printf("rule test %zu:\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)

                soclose(so);
        }
        return true;
}

bool
npf_guid_test(bool verbose)
{
        soinit1();

        bool ok;

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

        return true;
}