// SPDX-License-Identifier: GPL-2.0
/*
* Randomized tests for eBPF longest-prefix-match maps
*
* This program runs randomized tests against the lpm-bpf-map. It implements a
* "Trivial Longest Prefix Match" (tlpm) based on simple, linear, singly linked
* lists. The implementation should be pretty straightforward.
*
* Based on tlpm, this inserts randomized data into bpf-lpm-maps and verifies
* the trie-based bpf-map implementation behaves the same way as tlpm.
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <linux/bpf.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <bpf/bpf.h>
#include "bpf_util.h"
#include "bpf_rlimit.h"
struct tlpm_node {
struct tlpm_node *next;
size_t n_bits;
uint8_t key[];
};
static struct tlpm_node *tlpm_match(struct tlpm_node *list,
const uint8_t *key,
size_t n_bits);
static struct tlpm_node *tlpm_add(struct tlpm_node *list,
const uint8_t *key,
size_t n_bits)
{
struct tlpm_node *node;
size_t n;
n = (n_bits + 7) / 8;
/* 'overwrite' an equivalent entry if one already exists */
node = tlpm_match(list, key, n_bits);
if (node && node->n_bits == n_bits) {
memcpy(node->key, key, n);
return list;
}
/* add new entry with @key/@n_bits to @list and return new head */
node = malloc(sizeof(*node) + n);
assert(node);
node->next = list;
node->n_bits = n_bits;
memcpy(node->key, key, n);
return node;
}
static void tlpm_clear(struct tlpm_node *list)
{
struct tlpm_node *node;
/* free all entries in @list */
while ((node = list)) {
list = list->next;
free(node);
}
}
static struct tlpm_node *tlpm_match(struct tlpm_node *list,
const uint8_t *key,
size_t n_bits)
{
struct tlpm_node *best = NULL;
size_t i;
/* Perform longest prefix-match on @key/@n_bits. That is, iterate all
* entries and match each prefix against @key. Remember the "best"
* entry we find (i.e., the longest prefix that matches) and return it
* to the caller when done.
*/
for ( ; list; list = list->next) {
for (i = 0; i < n_bits && i < list->n_bits; ++i) {
if ((key[i / 8] & (1 << (7 - i % 8))) !=
(list->key[i / 8] & (1 << (7 - i % 8))))
break;
}
if (i >= list->n_bits) {
if (!best || i > best->n_bits)
best = list;
}
}
return best;
}
static struct tlpm_node *tlpm_delete(struct tlpm_node *list,
const uint8_t *key,
size_t n_bits)
{
struct tlpm_node *best = tlpm_match(list, key, n_bits);
struct tlpm_node *node;
if (!best || best->n_bits != n_bits)
return list;
if (best == list) {
node = best->next;
free(best);
return node;
}
for (node = list; node; node = node->next) {
if (node->next == best) {
node->next = best->next;
free(best);
return list;
}
}
/* should never get here */
assert(0);
return list;
}
static void test_lpm_basic(void)
{
struct tlpm_node *list = NULL, *t1, *t2;
/* very basic, static tests to verify tlpm works as expected */
assert(!tlpm_match(list, (uint8_t[]){ 0xff }, 8));
t1 = list = tlpm_add(list, (uint8_t[]){ 0xff }, 8);
assert(t1 == tlpm_match(list, (uint8_t[]){ 0xff }, 8));
assert(t1 == tlpm_match(list, (uint8_t[]){ 0xff, 0xff }, 16));
assert(t1 == tlpm_match(list, (uint8_t[]){ 0xff, 0x00 }, 16));
assert(!tlpm_match(list, (uint8_t[]){ 0x7f }, 8));
assert(!tlpm_match(list, (uint8_t[]){ 0xfe }, 8));
assert(!tlpm_match(list, (uint8_t[]){ 0xff }, 7));
t2 = list = tlpm_add(list, (uint8_t[]){ 0xff, 0xff }, 16);
assert(t1 == tlpm_match(list, (uint8_t[]){ 0xff }, 8));
assert(t2 == tlpm_match(list, (uint8_t[]){ 0xff, 0xff }, 16));
assert(t1 == tlpm_match(list, (uint8_t[]){ 0xff, 0xff }, 15));
assert(!tlpm_match(list, (uint8_t[]){ 0x7f, 0xff }, 16));
list = tlpm_delete(list, (uint8_t[]){