summaryrefslogtreecommitdiffstats
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c220
1 files changed, 220 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..8075592
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2024-2025 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or any
+ * later) as published by the Free Software Foundation.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "test.h"
+
+static int run_test_dir(const char *filename, struct nft_test_ctx *ctx,
+ struct nft_test_stats *stats)
+{
+ struct dirent *dent;
+ char path[PATH_MAX];
+ struct stat st;
+ int ret = 0;
+ DIR *d;
+
+ d = opendir(filename);
+ if (!d) {
+ perror("cannot open directory");
+ return -1;
+ }
+
+ dent = readdir(d);
+ while (dent) {
+ if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
+ dent = readdir(d);
+ continue;
+ }
+ snprintf(path, sizeof(path), "%s/%s", filename, dent->d_name);
+ if (stat(path, &st) < 0) {
+ fprintf(stderr, "cannot open file %s: %s\n",
+ path, strerror(errno));
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ if (run_test_dir(path, ctx, stats) < 0) {
+ ret = -1;
+ break;
+ }
+ dent = readdir(d);
+ continue;
+ } else if (!S_ISREG(st.st_mode)) {
+ dent = readdir(d);
+ continue;
+ }
+ if (run_test(path, ctx) < 0)
+ stats->failed++;
+ else
+ stats->ok++;
+
+ if (ctx->fatal) {
+ ret = -1;
+ break;
+ }
+
+ dent = readdir(d);
+ }
+ closedir(d);
+
+ return ret;
+}
+
+static int parse_fuzz_mode(char *str, uint32_t *type, uint32_t *num_types)
+{
+ char *x, *token;
+ int a, idx = 0;
+
+ x = strchr(str, ',');
+
+ do {
+ if (idx >= FUZZ_STACK_MAX) {
+ printf("too many modes\n");
+ return -1;
+ }
+
+ if (!x) {
+ token = str;
+ a = fuzz_type_to_value(token);
+ if (a < 0) {
+ printf("unknown fuzzing mode %s\n", token);
+ printf("Expected:\n", token);
+ print_fuzz_modes();
+ return -1;
+ }
+ type[idx++] = a;
+ break;
+ }
+
+ *x = '\0';
+ token = str;
+ a = fuzz_type_to_value(token);
+ if (a < 0) {
+ printf("unknown fuzzing mode %s\n", token);
+ printf("Expected:\n", token);
+ print_fuzz_modes();
+ return -1;
+ }
+ type[idx++] = a;
+
+ str = x + 1;
+
+ x = strchr(str, ',');
+ } while (1);
+
+ *num_types = idx;
+
+ return idx;
+}
+
+bool errout;
+bool noflush;
+
+int main(int argc, char *argv[])
+{
+ struct timeval tv_start, tv_end, tv_diff;
+ struct nft_test_stats stats = {};
+ struct nft_test_ctx ctx = {};
+ const char *filename;
+ struct stat st;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nf:hedc")) != -1) {
+ switch (opt) {
+ case 'n':
+ noflush = true;
+ break;
+ case 'f':
+ ctx.fuzz = true;
+ if (parse_fuzz_mode(optarg, ctx.fuzz_ctx.type, &ctx.fuzz_ctx.num_types) < 0)
+ exit(EXIT_FAILURE);
+ break;
+ case 'd':
+ ctx.fuzz_ctx.debug = true;
+ break;
+ case 'e':
+ errout = true;
+ break;
+ case 'c':
+ ctx.dryrun = true;
+ break;
+ case 'h':
+ default:
+ fprintf(stderr, "Usage: %s [-e] [-d] [-n] [-c] [-f <mode,...>] <input>\n",
+ argv[0]);
+ printf("Options:\n");
+ printf("\t-e\tdisplay error reported by kernel\n");
+ printf("\t-n\tdo not flush previous ruleset\n");
+ printf("\t-d\tdisplay debugging information\n");
+ printf("\t-c\tdry run\n");
+ printf("\t-f <mode>\tfuzz test, see fuzz modes\n");
+ printf("Available fuzz modes:\n");
+ print_fuzz_modes();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "ERROR: specify path to file or folder with tests\n");
+ exit(EXIT_FAILURE);
+ }
+
+ filename = argv[optind];
+
+ /* TODO: add dummy device with netlink. */
+ system("sudo modprobe xt_cpu");
+ system("sudo ip link add dummy0 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy1 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy2 type dummy > /dev/null 2>&1");
+ system("sudo ip link add dummy3 type dummy > /dev/null 2>&1");
+
+ gettimeofday(&tv_start, NULL);
+
+ if (!strcmp(filename, "-"))
+ filename = "/dev/stdin";
+
+ if (stat(filename, &st) < 0) {
+ fprintf(stderr, "cannot open test file %s: %s\n",
+ filename, strerror(errno));
+ return EXIT_FAILURE;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ run_test_dir(filename, &ctx, &stats);
+ printf("results: [\x1b[32mOK\x1b[37m] %d [\x1b[31mFAILED\x1b[37m] %d\n",
+ stats.ok, stats.failed);
+ } else if (S_ISREG(st.st_mode)) {
+ printf("%s...\n", filename);
+ run_test(filename, &ctx);
+ }
+
+ gettimeofday(&tv_end, NULL);
+ timersub(&tv_end, &tv_start, &tv_diff);
+
+ if (ctx.fuzz)
+ printf("total fuzz steps: %u in %u.%06u seconds\n", ctx.fuzz_ctx.total_fuzz_steps, tv_diff.tv_sec, tv_diff.tv_usec);
+ else
+ printf("done in %u.%06u seconds\n", tv_diff.tv_sec, tv_diff.tv_usec);
+
+ return EXIT_SUCCESS;
+}