root/usr.sbin/npf/npftest/libnpftest/npf_nat_test.c
/*
 * NPF NAT 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

#define NPF_BINAT       (NPF_NATIN | NPF_NATOUT)

#define RANDOM_PORT     18791

static const struct test_case {
        const char *    src;
        in_port_t       sport;
        const char *    dst;
        in_port_t       dport;
        int             ttype;
        const char *    ifname;
        int             di;
        int             ret;
        int             af;
        const char *    taddr;
        in_port_t       tport;
} test_cases[] = {

        /*
         * Traditional NAPT (outbound NAT):
         *      map $ext_if dynamic $local_net -> $pub_ip1
         */
        {
                LOCAL_IP1,      15000,          REMOTE_IP1,     7000,
                NPF_NATOUT,     IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP1,        RANDOM_PORT
        },
        {
                LOCAL_IP1,      15000,          REMOTE_IP1,     7000,
                NPF_NATOUT,     IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP1,        RANDOM_PORT
        },
        {
                LOCAL_IP1,      15000,          REMOTE_IP1,     7000,
                NPF_NATOUT,     IFNAME_EXT,     PFIL_IN,
                RESULT_BLOCK,   AF_INET,        NULL,           0
        },
        {
                REMOTE_IP1,     7000,           LOCAL_IP1,      15000,
                NPF_NATOUT,     IFNAME_EXT,     PFIL_IN,
                RESULT_BLOCK,   AF_INET,        NULL,           0
        },
        {
                REMOTE_IP1,     7000,           PUB_IP1,        RANDOM_PORT,
                NPF_NATOUT,     IFNAME_INT,     PFIL_IN,
                RESULT_BLOCK,   AF_INET,        NULL,           0
        },
        {
                REMOTE_IP1,     7000,           PUB_IP1,        RANDOM_PORT,
                NPF_NATOUT,     IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET,        LOCAL_IP1,      15000
        },

        /*
         * NAT redirect (inbound NAT):
         *      map $ext_if dynamic $local_ip1 port 6000 <- $pub_ip1 port 8000
         */
        {
                REMOTE_IP2,     16000,          PUB_IP1,        8000,
                NPF_NATIN,      IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET,        LOCAL_IP1,      6000
        },
        {
                LOCAL_IP1,      6000,           REMOTE_IP2,     16000,
                NPF_NATIN,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP1,        8000
        },

        /*
         * Bi-directional NAT (inbound + outbound NAT):
         *      map $ext_if dynamic $local_ip2 <-> $pub_ip2
         */
        {
                REMOTE_IP2,     17000,          PUB_IP2,        9000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET,        LOCAL_IP2,      9000
        },
        {
                LOCAL_IP2,      9000,           REMOTE_IP2,     17000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP2,        9000
        },
        {
                LOCAL_IP2,      18000,          REMOTE_IP2,     9000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP2,        18000
        },
        {
                REMOTE_IP2,     9000,           PUB_IP2,        18000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET,        LOCAL_IP2,      18000
        },

        /*
         * Static NAT: plain translation both ways.
         *      map $ext_if static $local_ip3 <-> $pub_ip3
         */
        {
                LOCAL_IP3,      19000,          REMOTE_IP3,     10000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        PUB_IP3,        19000
        },
        {
                REMOTE_IP3,     10000,          PUB_IP3,        19000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET,        LOCAL_IP3,      19000
        },

        /*
         * NETMAP case:
         *      map $ext_if static algo netmap $net_a <-> $net_b
         */
        {
                NET_A_IP1,      12345,          REMOTE_IP4,     12345,
                NPF_BINAT,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET,        NET_B_IP1,      12345
        },

        /*
         * NPTv6 case:
         *      map $ext_if static algo npt66 $net6_inner <-> $net6_outer
         */
        {
                LOCAL_IP6,      1000,           REMOTE_IP6,     1001,
                NPF_BINAT,      IFNAME_EXT,     PFIL_OUT,
                RESULT_PASS,    AF_INET6,       EXPECTED_IP6,   1000
        },
        {
                REMOTE_IP6,     1001,           EXPECTED_IP6,   1000,
                NPF_BINAT,      IFNAME_EXT,     PFIL_IN,
                RESULT_PASS,    AF_INET6,       LOCAL_IP6,      1000
        },

};

static bool
match_addr(int af, const char *saddr, const npf_addr_t *addr2)
{
        npf_addr_t addr1;
        size_t len;

        npf_inet_pton(af, saddr, &addr1);
        len = af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
        return memcmp(&addr1, addr2, len) == 0;
}

static bool
checkresult(bool verbose, unsigned i, struct mbuf *m, ifnet_t *ifp, int error)
{
        const struct test_case *t = &test_cases[i];
        const int af = t->af;
        npf_cache_t npc;
        nbuf_t nbuf;

        if (verbose) {
                printf("packet %d (expected %d ret %d)\n", i+1, t->ret, error);
        }
        if (error) {
                return error == t->ret;
        }

        nbuf_init(npf_getkernctx(), &nbuf, m, ifp);
        memset(&npc, 0, sizeof(npf_cache_t));
        npc.npc_ctx = npf_getkernctx();
        npc.npc_nbuf = &nbuf;

        if (!npf_cache_all(&npc)) {
                printf("error: could not fetch the packet data");
                return false;
        }

        const struct udphdr *uh = npc.npc_l4.udp;

        if (verbose) {
                char sbuf[64], dbuf[64];

                npf_inet_ntop(af, npc.npc_ips[NPF_SRC], sbuf, sizeof(sbuf));
                npf_inet_ntop(af, npc.npc_ips[NPF_DST], dbuf, sizeof(dbuf));

                printf("\tpost-translation: ");
                printf("src %s (%d) ", sbuf, ntohs(uh->uh_sport));
                printf("dst %s (%d)\n", dbuf, ntohs(uh->uh_dport));
        }
        if (error != t->ret) {
                return false;
        }

        const bool forw = t->di == PFIL_OUT;
        const char *saddr = forw ? t->taddr : t->src;
        const char *daddr = forw ? t->dst : t->taddr;
        in_addr_t sport = forw ? t->tport : t->sport;
        in_addr_t dport = forw ? t->dport : t->tport;

        CHECK_TRUE(match_addr(af, saddr, npc.npc_ips[NPF_SRC]));
        CHECK_TRUE(sport == ntohs(uh->uh_sport));
        CHECK_TRUE(match_addr(af, daddr, npc.npc_ips[NPF_DST]));
        CHECK_TRUE(dport == ntohs(uh->uh_dport));

        return true;
}

bool
npf_nat_test(bool verbose)
{
        npf_t *npf = npf_getkernctx();

        for (unsigned i = 0; i < __arraycount(test_cases); i++) {
                const struct test_case *t = &test_cases[i];
                ifnet_t *ifp = npf_test_getif(t->ifname);
                struct mbuf *m;
                int error;
                bool ret;

                if (ifp == NULL) {
                        printf("Interface %s is not configured.\n", t->ifname);
                        return false;
                }
                m = mbuf_get_pkt(t->af, IPPROTO_UDP,
                    t->src, t->dst, t->sport, t->dport);
                error = npfk_packet_handler(npf, &m, ifp, t->di);
                ret = checkresult(verbose, i, m, ifp, error);
                if (m) {
                        m_freem(m);
                }
                CHECK_TRUE(ret);
        }
        return true;
}