Add initial NPF regression tests integrated with RUMP framework (running the

kernel part of NPF in userland).  Other tests will be added once converted to
RUMP framework.  All tests are in the public domain.

Some Makefile fixes from christos@.
This commit is contained in:
rmind 2012-04-14 21:57:29 +00:00
parent 16bec229c7
commit 9ffbe6bd3d
9 changed files with 757 additions and 0 deletions

View File

@ -0,0 +1,27 @@
# $NetBSD: Makefile,v 1.1 2012/04/14 21:57:29 rmind Exp $
#
# Public Domain
#
PROG= npftest
SRCS= npftest.c
CPPFLAGS+= -I${.CURDIR}
LIBNPFTEST!= cd ${.CURDIR}/libnpftest && ${MAKE} -V .OBJDIR
DPADD+= ${LIBNPFTEST}/libnpftest.a
LDADD+= -L${LIBNPFTEST} -lnpftest
LDADD+= -lrump -lrumpvfs -lrumpnet -lrump -lrumpnet_net
LDADD+= -lrumpnet_virtif -lrumpdev_npf -lpthread
WARNS= 4
NOMAN= # no man page
NOLINT= # disabled (note: deliberately)
SUBDIR+= libnpftest
${LIBNPFTEST}/libnpftest.a: all-libnpftest
.include <bsd.subdir.mk>
.include <bsd.prog.mk>

View File

@ -0,0 +1,24 @@
# $NetBSD: Makefile,v 1.1 2012/04/14 21:57:29 rmind Exp $
#
# Public Domain
#
RUMPTOP= ${.CURDIR}/../../../../sys/rump
LIB= npftest
LIBISPRIVATE= yes
SRCS+= npf_mbuf_subr.c
SRCS+= npf_nbuf_test.c
SRCS+= npf_processor_test.c
SRCS+= npf_table_test.c
CPPFLAGS+= -I${.CURDIR}/../../../../sys/net/npf
CPPFLAGS+= -I${RUMPTOP}/librump/rumpkern
WARNS= 4
.include "${RUMPTOP}/Makefile.rump"
.include <bsd.lib.mk>
.include <bsd.klinks.mk>

View File

@ -0,0 +1,110 @@
/* $NetBSD: npf_mbuf_subr.c,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* NPF testing - helper routines.
*
* Public Domain.
*/
#include <sys/types.h>
#include <sys/kmem.h>
#include "npf_impl.h"
#include "npf_test.h"
struct mbuf *
mbuf_getwithdata(void *data, size_t len)
{
struct mbuf *m;
m = kmem_zalloc(sizeof(struct mbuf), KM_SLEEP);
assert(m != NULL);
m->m_data = data;
m->m_len = len;
return m;
}
struct mbuf *
mbuf_construct_ether(int proto)
{
struct mbuf *m0, *m1;
struct ether_header *ethdr;
m0 = m_gethdr(M_WAITOK, MT_HEADER);
ethdr = mtod(m0, struct ether_header *);
ethdr->ether_type = htons(ETHERTYPE_IP);
m0->m_len = sizeof(struct ether_header);
m1 = mbuf_construct(proto);
m0->m_next = m1;
m1->m_next = NULL;
return m0;
}
struct mbuf *
mbuf_construct(int proto)
{
struct mbuf *m;
struct ip *iphdr;
struct tcphdr *th;
int size;
m = m_gethdr(M_WAITOK, MT_HEADER);
iphdr = mtod(m, struct ip *);
iphdr->ip_v = IPVERSION;
iphdr->ip_hl = sizeof(struct ip) >> 2;
iphdr->ip_off = 0;
iphdr->ip_ttl = 64;
iphdr->ip_p = proto;
size = sizeof(struct ip);
switch (proto) {
case IPPROTO_TCP:
th = (void *)(iphdr + 1);
th->th_off = sizeof(struct tcphdr) >> 2;
size += sizeof(struct tcphdr);
break;
case IPPROTO_UDP:
size += sizeof(struct udphdr);
break;
case IPPROTO_ICMP:
size += offsetof(struct icmp, icmp_data);
break;
}
iphdr->ip_len = htons(size);
m->m_len = size;
m->m_next = NULL;
return m;
}
void *
mbuf_return_hdrs(struct mbuf *m, bool ether, struct ip **ip)
{
struct ip *iphdr;
if (ether) {
struct mbuf *mn = m->m_next;
iphdr = mtod(mn, struct ip *);
} else {
iphdr = mtod(m, struct ip *);
}
*ip = iphdr;
return (void *)(iphdr + 1);
}
void
mbuf_icmp_append(struct mbuf *m, struct mbuf *m_orig)
{
struct ip *iphdr = mtod(m, struct ip *);
const size_t hlen = iphdr->ip_hl << 2;
struct icmp *ic = (struct icmp *)((uint8_t *)iphdr + hlen);
const size_t addlen = m_orig->m_len;
iphdr->ip_len = htons(ntohs(iphdr->ip_len) + addlen);
memcpy(&ic->icmp_ip, mtod(m_orig, struct ip *), addlen);
m->m_len += addlen;
m_freem(m_orig);
}

View File

@ -0,0 +1,176 @@
/* $NetBSD: npf_nbuf_test.c,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* NPF nbuf interface test.
*
* Public Domain.
*/
#include <sys/types.h>
#include <sys/kmem.h>
#include "npf_impl.h"
#include "npf_test.h"
#define MBUF_CHAIN_LEN 128
CTASSERT((MBUF_CHAIN_LEN % sizeof(uint32_t)) == 0);
static char *
parse_nbuf_chain(void *nbuf, void *n_ptr)
{
char *s = kmem_zalloc(MBUF_CHAIN_LEN + 1, KM_SLEEP);
int n, error;
for (n = 0; ; ) {
char d[4 + 1];
error = nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint32_t), d);
if (error) {
return NULL;
}
d[sizeof(d) - 1] = '\0';
strcat(s, d);
if (n + sizeof(uint32_t) == MBUF_CHAIN_LEN) {
assert(nbuf_advance(&nbuf, n_ptr,
sizeof(uint32_t) - 1));
break;
}
n_ptr = nbuf_advance(&nbuf, n_ptr, sizeof(uint32_t));
if (n_ptr == NULL) {
return NULL;
}
n += sizeof(uint32_t);
}
return s;
}
static char *
mbuf_getstring(struct mbuf *m)
{
char *s = kmem_zalloc(MBUF_CHAIN_LEN + 1, KM_SLEEP);
u_int tlen = 0;
while (m) {
int len = m->m_len;
char *d = m->m_data;
while (len--) {
s[tlen++] = *d++;
}
m = m->m_next;
}
return s;
}
static struct mbuf *
mbuf_alloc_with_off(size_t off, int len)
{
struct mbuf *m;
m = kmem_zalloc(sizeof(struct mbuf) + off + len, KM_SLEEP);
m->m_data = (char *)m + sizeof(struct mbuf) + off;
m->m_len = len;
return m;
}
/*
* Create an mbuf chain, each of 1 byte size.
*/
static struct mbuf *
mbuf_bytesize(size_t clen)
{
struct mbuf *m0 = NULL, *m = NULL;
u_int i, n;
/* Chain of clen (e.g. 128) mbufs, each storing 1 byte of data. */
for (i = 0, n = 0; i < clen; i++) {
/* Range of offset: 0 .. 15. */
m0 = mbuf_alloc_with_off(n & 0xf, 1);
/* Fill data with letters from 'a' to 'z'. */
memset(m0->m_data, 'a' + n, 1);
n = ('a' + n) != 'z' ? n + 1 : 0;
/* Next mbuf.. */
m0->m_next = m;
m = m0;
}
return m0;
}
/*
* Generate random amount of mbufs, with random offsets and lengths.
*/
static struct mbuf *
mbuf_random_len(size_t chain_len)
{
struct mbuf *m0 = NULL, *m = NULL;
u_int tlen = 0, n = 0;
while (tlen < chain_len) {
u_int off, len;
char *d;
/* Random offset and length range: 1 .. 16. */
off = (random() % 16) + 1;
len = (random() % 16) + 1;
/* Do not exceed 128 bytes of total length. */
if (tlen + len > chain_len) {
len = chain_len - tlen;
}
tlen += len;
/* Consistently fill data with letters from 'a' to 'z'. */
m0 = mbuf_alloc_with_off(off, len);
d = m0->m_data;
while (len--) {
*d++ = ('a' + n);
n = ('a' + n) != 'z' ? n + 1 : 0;
}
/* Next mbuf.. */
m0->m_next = m;
m = m0;
}
assert(tlen == chain_len);
return m0;
}
static bool
validate_mbuf_data(struct mbuf *m, bool verbose, char *bufa, char *bufb)
{
bool ret = (strcmp(bufa, bufb) == 0);
if (verbose) {
printf("Buffer A: %s\nBuffer B: %s\n", bufa, bufb);
}
/* XXX free m */
kmem_free(bufa, MBUF_CHAIN_LEN + 1);
kmem_free(bufb, MBUF_CHAIN_LEN + 1);
return ret;
}
bool
npf_nbuf_test(bool verbose)
{
struct mbuf *m1, *m2;
char *bufa, *bufb;
m1 = mbuf_random_len(MBUF_CHAIN_LEN);
bufa = mbuf_getstring(m1);
bufb = parse_nbuf_chain(m1, m1->m_data);
if (!validate_mbuf_data(m1, verbose, bufa, bufb)) {
return false;
}
m2 = mbuf_bytesize(MBUF_CHAIN_LEN);
bufa = mbuf_getstring(m2);
bufb = parse_nbuf_chain(m2, m2->m_data);
if (!validate_mbuf_data(m2, verbose, bufa, bufb)) {
return false;
}
return true;
}

View File

@ -0,0 +1,163 @@
/* $NetBSD: npf_processor_test.c,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* NPF n-code processor test.
*
* Public Domain.
*/
#include <sys/types.h>
#include "npf_impl.h"
#include "npf_ncode.h"
#include "npf_test.h"
/*
* In network byte order:
* 192.168.2.0 == 0x0002a8c0
* 192.168.2.1 == 0x0102a8c0
* 192.168.2.100 == 0x6402a8c0
* htons(ETHERTYPE_IP) == 0x08
* (htons(80) << 16) | htons(80) == 0x50005000
* (htons(80) << 16) | htons(15000) == 0x5000983a
*/
static uint32_t nc_match[ ] __aligned(4) = {
NPF_OPCODE_CMP, NPF_LAYER_3, 0,
NPF_OPCODE_BEQ, 0x0c,
NPF_OPCODE_ETHER, 0x00, 0x00, 0x08,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_ADVR, 3,
NPF_OPCODE_IP4MASK, 0x01, 0x0002a8c0, 24,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_TCP_PORTS, 0x00, 0x50005000,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_RET, 0x00
};
static uint32_t nc_nmatch[ ] __aligned(4) = {
NPF_OPCODE_CMP, NPF_LAYER_3, 0,
NPF_OPCODE_BEQ, 0x0c,
NPF_OPCODE_ETHER, 0x00, 0x00, 0x08,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_ADVR, 3,
NPF_OPCODE_IP4MASK, 0x01, 0x0102a8c0, 32,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_RET, 0x00
};
static uint32_t nc_rmatch[ ] __aligned(4) = {
NPF_OPCODE_MOVE, offsetof(struct ip, ip_src), 1,
NPF_OPCODE_ADVR, 1,
NPF_OPCODE_LW, sizeof(in_addr_t), 0,
NPF_OPCODE_CMP, 0x6402a8c0, 0,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_MOVE, sizeof(struct ip) - offsetof(struct ip, ip_src)
+ offsetof(struct tcphdr, th_sport), 1,
NPF_OPCODE_ADVR, 1,
NPF_OPCODE_LW, 2 * sizeof(in_port_t), 0,
NPF_OPCODE_CMP, 0x5000983a, 0,
NPF_OPCODE_BEQ, 0x04,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_RET, 0x01
};
static uint32_t nc_inval[ ] __aligned(4) = {
NPF_OPCODE_BEQ, 0x05,
NPF_OPCODE_RET, 0xff,
NPF_OPCODE_RET, 0x01
};
static void
fill_packet(struct mbuf *m, bool ether)
{
struct ip *ip;
struct tcphdr *th;
th = mbuf_return_hdrs(m, ether, &ip);
ip->ip_src.s_addr = inet_addr("192.168.2.100");
ip->ip_dst.s_addr = inet_addr("10.0.0.1");
th->th_sport = htons(15000);
th->th_dport = htons(80);
}
static bool
validate_retcode(const char *msg, bool verbose, int ret, int expected)
{
bool ok = (ret == expected);
if (verbose) {
printf("%-25s\t%-4d == %4d\t-> %s\n",
msg, ret, expected, ok ? "ok" : "fail");
}
return ok;
}
bool
npf_processor_test(bool verbose)
{
npf_cache_t npc;
struct mbuf *m;
int errat, ret;
/* Layer 2 (Ethernet + IP + TCP). */
m = mbuf_construct_ether(IPPROTO_TCP);
fill_packet(m, true);
ret = npf_ncode_validate(nc_match, sizeof(nc_match), &errat);
if (!validate_retcode("Ether validation", verbose, ret, 0)) {
return false;
}
memset(&npc, 0, sizeof(npf_cache_t));
ret = npf_ncode_process(&npc, nc_match, m, NPF_LAYER_2);
if (!validate_retcode("Ether", verbose, ret, 0)) {
return false;
}
m_freem(m);
/* Layer 3 (IP + TCP). */
m = mbuf_construct(IPPROTO_TCP);
fill_packet(m, false);
memset(&npc, 0, sizeof(npf_cache_t));
ret = npf_ncode_process(&npc, nc_match, m, NPF_LAYER_3);
if (!validate_retcode("IPv4 mask 1", verbose, ret, 0)) {
return false;
}
/* Non-matching IPv4 case. */
ret = npf_ncode_validate(nc_nmatch, sizeof(nc_nmatch), &errat);
if (!validate_retcode("IPv4 mask 2 validation", verbose, ret, 0)) {
return false;
}
memset(&npc, 0, sizeof(npf_cache_t));
ret = npf_ncode_process(&npc, nc_nmatch, m, NPF_LAYER_3);
if (!validate_retcode("IPv4 mask 2", verbose, ret, 255)) {
return false;
}
/* Invalid n-code case. */
ret = npf_ncode_validate(nc_inval, sizeof(nc_inval), &errat);
if (!validate_retcode("Invalid n-code", verbose, ret, NPF_ERR_JUMP)) {
return false;
}
/* RISC-like insns. */
ret = npf_ncode_validate(nc_rmatch, sizeof(nc_rmatch), &errat);
if (!validate_retcode("RISC-like n-code validation", verbose, ret, 0)) {
return false;
}
memset(&npc, 0, sizeof(npf_cache_t));
ret = npf_ncode_process(&npc, nc_rmatch, m, NPF_LAYER_3);
if (!validate_retcode("RISC-like n-code", verbose, ret, 1)) {
return false;
}
m_freem(m);
return true;
}

View File

@ -0,0 +1,117 @@
/* $NetBSD: npf_table_test.c,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* NPF tableset test.
*
* Public Domain.
*/
#include <sys/types.h>
#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",
};
#define HASH_TID 1
#define TREE_TID 2
bool
npf_table_test(bool verbose)
{
npf_addr_t addr_storage, *addr = &addr_storage;
npf_tableset_t *tblset;
npf_table_t *t1, *t2;
int error;
u_int i;
npf_tableset_sysinit();
tblset = npf_tableset_create();
assert(tblset != NULL);
/* Table ID 1, using hash table with 256 lists. */
t1 = npf_table_create(HASH_TID, NPF_TABLE_HASH, 256);
assert(t1 != NULL);
error = npf_tableset_insert(tblset, t1);
assert(error == 0);
/* Check for double-insert. */
error = npf_tableset_insert(tblset, t1);
assert(error != 0);
/* Table ID 2, using RB-tree. */
t2 = npf_table_create(TREE_TID, NPF_TABLE_TREE, 0);
assert(t2 != NULL);
error = npf_tableset_insert(tblset, t2);
assert(error == 0);
/* Fill both tables with IP addresses. */
for (i = 0; i < __arraycount(ip_list); i++) {
addr->s6_addr32[0] = inet_addr(ip_list[i]);
error = npf_table_add_cidr(tblset, HASH_TID, addr, 32);
assert(error == 0);
error = npf_table_add_cidr(tblset, HASH_TID, addr, 32);
assert(error != 0);
error = npf_table_add_cidr(tblset, TREE_TID, addr, 32);
assert(error == 0);
error = npf_table_add_cidr(tblset, TREE_TID, addr, 32);
assert(error != 0);
}
/* Attempt to add duplicates - should fail. */
addr->s6_addr32[0] = inet_addr(ip_list[0]);
error = npf_table_add_cidr(tblset, HASH_TID, addr, 32);
assert(error != 0);
error = npf_table_add_cidr(tblset, TREE_TID, addr, 32);
assert(error != 0);
/* Reference checks. */
t1 = npf_table_get(tblset, HASH_TID);
assert(t1 != NULL);
npf_table_put(t1);
t2 = npf_table_get(tblset, TREE_TID);
assert(t2 != NULL);
npf_table_put(t2);
/* Match (validate) each IP entry. */
for (i = 0; i < __arraycount(ip_list); i++) {
addr->s6_addr32[0] = inet_addr(ip_list[i]);
error = npf_table_match_addr(tblset, HASH_TID, addr);
assert(error == 0);
error = npf_table_match_addr(tblset, TREE_TID, addr);
assert(error == 0);
}
/* Remove all entries. */
for (i = 0; i < __arraycount(ip_list); i++) {
addr->s6_addr32[0] = inet_addr(ip_list[i]);
error = npf_table_rem_cidr(tblset, HASH_TID, addr, 32);
assert(error == 0);
error = npf_table_rem_cidr(tblset, TREE_TID, addr, 32);
assert(error == 0);
}
npf_tableset_destroy(tblset);
npf_tableset_sysfini();
return true;
}

View File

@ -0,0 +1,37 @@
/* $NetBSD: npf_test.h,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* Public Domain.
*/
#ifndef _LIB_NPF_TEST_H_
#define _LIB_NPF_TEST_H_
#include <sys/types.h>
#include <sys/mbuf.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet6/in6.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <net/if.h>
#include <net/if_ether.h>
#include <net/ethertypes.h>
struct mbuf * mbuf_getwithdata(void *, size_t);
struct mbuf * mbuf_construct_ether(int);
struct mbuf * mbuf_construct(int);
void * mbuf_return_hdrs(struct mbuf *, bool, struct ip **);
void mbuf_icmp_append(struct mbuf *, struct mbuf *);
bool npf_nbuf_test(bool);
bool npf_processor_test(bool);
bool npf_table_test(bool);
#endif

View File

@ -0,0 +1,87 @@
/* $NetBSD: npftest.c,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* NPF testing framework.
*
* Public Domain.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <assert.h>
#include <rump/rump.h>
#include "npftest.h"
static bool benchmark, verbose, quiet;
static void
usage(void)
{
printf("%s: [ -b ] [ -v ]\n", getprogname());
exit(EXIT_SUCCESS);
}
static void
result(const char *test, bool ok)
{
if (!quiet) {
printf("NPF %-10s\t%s\n", test, ok ? "OK" : "fail");
}
if (verbose) {
puts("-----");
}
if (!ok) {
exit(EXIT_FAILURE);
}
}
int
main(int argc, char **argv)
{
bool ok;
int ch;
benchmark = false;
verbose = false;
quiet = false;
while ((ch = getopt(argc, argv, "bqv")) != -1) {
switch (ch) {
case 'b':
benchmark = true;
break;
case 'q':
quiet = true;
break;
case 'v':
verbose = true;
break;
default:
usage();
}
}
/* XXX rn_init */
extern int rumpns_max_keylen;
rumpns_max_keylen = 1;
rump_init();
rump_schedule();
ok = rumpns_npf_nbuf_test(verbose);
result("nbuf", ok);
ok = rumpns_npf_processor_test(verbose);
result("processor", ok);
ok = rumpns_npf_table_test(verbose);
result("table", ok);
rump_unschedule();
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,16 @@
/* $NetBSD: npftest.h,v 1.1 2012/04/14 21:57:29 rmind Exp $ */
/*
* Public Domain.
*/
#ifndef _NPF_TEST_H_
#define _NPF_TEST_H_
#include <stdbool.h>
bool rumpns_npf_nbuf_test(bool);
bool rumpns_npf_processor_test(bool);
bool rumpns_npf_table_test(bool);
#endif