Commit 8016abd6 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'selftests-bpf-migrate-a-few-bpftool-testing-scripts'



Alexis Lothoré says:

====================
selftests/bpf: migrate a few bpftool testing scripts

this is the v4 for some bpftool tests conversion. The new tests are
being integrated in test_progs so that they can be executed on each CI
run.

- First commit introduces a few dedicated helpers to execute bpftool
  commands, with or without retrieving the generated stdout output
- Second commit integrates test_bpftool_metadata.sh into test_progs
- Third commit integrates test_bpftool_map.sh into test_progs

Signed-off-by: default avatarAlexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
Changes in v4:
- Port missing map access test in bpftool_metadata
- Link to v3: https://lore.kernel.org/r/20260121-bpftool-tests-v3-0-368632f377e5@bootlin.com

Changes in v3:
- Drop commit reordering objects in Makefile
- Rebased series on ci/bpf-next_base to fix conflict
- Link to v2: https://lore.kernel.org/r/20260121-bpftool-tests-v2-0-64edb47e91ae@bootlin.com

Changes in v2:
- drop standalone runner in favor of test_progs
- Link to v1: https://lore.kernel.org/r/20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com

====================

Link: https://patch.msgid.link/20260123-bpftool-tests-v4-0-a6653a7f28e7@bootlin.com


Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 78980b4c 2d96bbdf
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -108,8 +108,6 @@ TEST_PROGS := test_kmod.sh \
	test_xdping.sh \
	test_bpftool_build.sh \
	test_bpftool.sh \
	test_bpftool_map.sh \
	test_bpftool_metadata.sh \
	test_doc_build.sh \
	test_xsk.sh \
	test_xdp_features.sh
@@ -747,7 +745,8 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
			 json_writer.c 		\
			 $(VERIFY_SIG_HDR)		\
			 flow_dissector_load.h	\
			 ip_check_defrag_frags.h
			 ip_check_defrag_frags.h	\
			 bpftool_helpers.c
TRUNNER_LIB_SOURCES := find_bit.c
TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read				\
		       $(OUTPUT)/liburandom_read.so			\
+74 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
#include "bpftool_helpers.h"
#include <unistd.h>
#include <string.h>
#include <stdbool.h>

#define BPFTOOL_PATH_MAX_LEN		64
#define BPFTOOL_FULL_CMD_MAX_LEN	512

#define BPFTOOL_DEFAULT_PATH		"tools/sbin/bpftool"

static int detect_bpftool_path(char *buffer)
{
	char tmp[BPFTOOL_PATH_MAX_LEN];

	/* Check default bpftool location (will work if we are running the
	 * default flavor of test_progs)
	 */
	snprintf(tmp, BPFTOOL_PATH_MAX_LEN, "./%s", BPFTOOL_DEFAULT_PATH);
	if (access(tmp, X_OK) == 0) {
		strncpy(buffer, tmp, BPFTOOL_PATH_MAX_LEN);
		return 0;
	}

	/* Check alternate bpftool location (will work if we are running a
	 * specific flavor of test_progs, e.g. cpuv4 or no_alu32)
	 */
	snprintf(tmp, BPFTOOL_PATH_MAX_LEN, "../%s", BPFTOOL_DEFAULT_PATH);
	if (access(tmp, X_OK) == 0) {
		strncpy(buffer, tmp, BPFTOOL_PATH_MAX_LEN);
		return 0;
	}

	/* Failed to find bpftool binary */
	return 1;
}

static int run_command(char *args, char *output_buf, size_t output_max_len)
{
	static char bpftool_path[BPFTOOL_PATH_MAX_LEN] = {0};
	bool suppress_output = !(output_buf && output_max_len);
	char command[BPFTOOL_FULL_CMD_MAX_LEN];
	FILE *f;
	int ret;

	/* Detect and cache bpftool binary location */
	if (bpftool_path[0] == 0 && detect_bpftool_path(bpftool_path))
		return 1;

	ret = snprintf(command, BPFTOOL_FULL_CMD_MAX_LEN, "%s %s%s",
		       bpftool_path, args,
		       suppress_output ? " > /dev/null 2>&1" : "");

	f = popen(command, "r");
	if (!f)
		return 1;

	if (!suppress_output)
		fread(output_buf, 1, output_max_len, f);
	ret = pclose(f);

	return ret;
}

int run_bpftool_command(char *args)
{
	return run_command(args, NULL, 0);
}

int get_bpftool_command_output(char *args, char *output_buf, size_t output_max_len)
{
	return run_command(args, output_buf, output_max_len);
}
+11 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
#pragma once

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#define MAX_BPFTOOL_CMD_LEN	(256)

int run_bpftool_command(char *args);
int get_bpftool_command_output(char *args, char *output_buf, size_t output_max_len);
+371 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <linux/bpf.h>
#include <bpf/libbpf.h>
#include <bpftool_helpers.h>
#include <test_progs.h>
#include <bpf/bpf.h>
#include "security_bpf_map.skel.h"

#define PROTECTED_MAP_NAME	"prot_map"
#define UNPROTECTED_MAP_NAME	"not_prot_map"
#define BPF_ITER_FILE		"bpf_iter_map_elem.bpf.o"
#define BPFFS_PIN_DIR		"/sys/fs/bpf/test_bpftool_map"
#define INNER_MAP_NAME		"inner_map_tt"
#define OUTER_MAP_NAME		"outer_map_tt"

#define MAP_NAME_MAX_LEN	64
#define PATH_MAX_LEN		128

enum map_protection {
	PROTECTED,
	UNPROTECTED
};

struct test_desc {
	char *name;
	enum map_protection protection;
	struct bpf_map *map;
	char *map_name;
	bool pinned;
	char pin_path[PATH_MAX_LEN];
	bool write_must_fail;
};

static struct security_bpf_map *general_setup(void)
{
	struct security_bpf_map *skel;
	uint32_t key, value;
	int ret, i;

	skel = security_bpf_map__open_and_load();
	if (!ASSERT_OK_PTR(skel, "open and load skeleton"))
		goto end;

	struct bpf_map *maps[] = {skel->maps.prot_map, skel->maps.not_prot_map};

	ret = security_bpf_map__attach(skel);
	if (!ASSERT_OK(ret, "attach maps security programs"))
		goto end_destroy;

	for (i = 0; i < sizeof(maps)/sizeof(struct bpf_map *); i++) {
		for (key = 0; key < 2; key++) {
			int ret = bpf_map__update_elem(maps[i], &key,
					sizeof(key), &key, sizeof(key),
					0);
			if (!ASSERT_OK(ret, "set initial map value"))
				goto end_destroy;
		}
	}

	key = 0;
	value = 1;
	ret = bpf_map__update_elem(skel->maps.prot_status_map, &key,
			sizeof(key), &value, sizeof(value), 0);
	if (!ASSERT_OK(ret, "configure map protection"))
		goto end_destroy;

	if (!ASSERT_OK(mkdir(BPFFS_PIN_DIR, S_IFDIR), "create bpffs pin dir"))
		goto end_destroy;

	return skel;
end_destroy:
	security_bpf_map__destroy(skel);
end:
	return NULL;
}

static void general_cleanup(struct security_bpf_map *skel)
{
	rmdir(BPFFS_PIN_DIR);
	security_bpf_map__destroy(skel);
}

static void update_test_desc(struct security_bpf_map *skel,
			      struct test_desc *test)
{
	/* Now that the skeleton is loaded, update all missing fields to
	 * have the subtest properly configured
	 */
	if (test->protection == PROTECTED) {
		test->map = skel->maps.prot_map;
		test->map_name = PROTECTED_MAP_NAME;
	} else {
		test->map = skel->maps.not_prot_map;
		test->map_name = UNPROTECTED_MAP_NAME;
	}
}

static int test_setup(struct security_bpf_map *skel, struct test_desc *desc)
{
	int ret;

	update_test_desc(skel, desc);

	if (desc->pinned) {
		ret = snprintf(desc->pin_path, PATH_MAX_LEN, "%s/%s", BPFFS_PIN_DIR,
				desc->name);
		if (!ASSERT_GT(ret, 0, "format pin path"))
			return 1;
		ret = bpf_map__pin(desc->map, desc->pin_path);
		if (!ASSERT_OK(ret, "pin map"))
			return 1;
	}

	return 0;
}

static void test_cleanup(struct test_desc *desc)
{
	if (desc->pinned)
		bpf_map__unpin(desc->map, NULL);
}

static int lookup_map_value(char *map_handle)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "map lookup %s key 0 0 0 0",
			map_handle);
	if (!ASSERT_GT(ret, 0, "format map lookup cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static int read_map_btf_data(char *map_handle)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "btf dump map %s",
			map_handle);
	if (!ASSERT_GT(ret, 0, "format map btf dump cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static int write_map_value(char *map_handle)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN,
		       "map update %s key 0 0 0 0 value 1 1 1 1", map_handle);
	if (!ASSERT_GT(ret, 0, "format value write cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static int delete_map_value(char *map_handle)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN,
		       "map delete %s key 0 0 0 0", map_handle);
	if (!ASSERT_GT(ret, 0, "format value deletion cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static int iterate_on_map_values(char *map_handle, char *iter_pin_path)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;


	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "iter pin %s %s map %s",
		       BPF_ITER_FILE, iter_pin_path, map_handle);
	if (!ASSERT_GT(ret, 0, "format iterator creation cmd"))
		return 1;
	ret = run_bpftool_command(cmd);
	if (ret)
		return ret;
	ret = snprintf(cmd, MAP_NAME_MAX_LEN, "cat %s", iter_pin_path);
	if (ret < 0)
		goto cleanup;
	ret = system(cmd);

cleanup:
	unlink(iter_pin_path);
	return ret;
}

static int create_inner_map(void)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(
		cmd, MAX_BPFTOOL_CMD_LEN,
		"map create %s/%s type array key 4 value 4 entries 4 name %s",
		BPFFS_PIN_DIR, INNER_MAP_NAME, INNER_MAP_NAME);
	if (!ASSERT_GT(ret, 0, "format inner map create cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static int create_outer_map(void)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(
		cmd, MAX_BPFTOOL_CMD_LEN,
		"map create %s/%s type hash_of_maps key 4 value 4 entries 2 name %s inner_map name %s",
		BPFFS_PIN_DIR, OUTER_MAP_NAME, OUTER_MAP_NAME, INNER_MAP_NAME);
	if (!ASSERT_GT(ret, 0, "format outer map create cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static void delete_pinned_map(char *map_name)
{
	char pin_path[PATH_MAX_LEN];
	int ret;

	ret = snprintf(pin_path, PATH_MAX_LEN, "%s/%s", BPFFS_PIN_DIR,
		       map_name);
	if (ret >= 0)
		unlink(pin_path);
}

static int add_outer_map_entry(int key)
{
	char cmd[MAX_BPFTOOL_CMD_LEN];
	int ret = 0;

	ret = snprintf(
		cmd, MAX_BPFTOOL_CMD_LEN,
		"map update pinned %s/%s key %d 0 0 0 value name %s",
		BPFFS_PIN_DIR, OUTER_MAP_NAME, key, INNER_MAP_NAME);
	if (!ASSERT_GT(ret, 0, "format outer map value addition cmd"))
		return 1;
	return run_bpftool_command(cmd);
}

static void test_basic_access(struct test_desc *desc)
{
	char map_handle[MAP_NAME_MAX_LEN];
	char iter_pin_path[PATH_MAX_LEN];
	int ret;

	if (desc->pinned)
		ret = snprintf(map_handle, MAP_NAME_MAX_LEN, "pinned %s",
			       desc->pin_path);
	else
		ret = snprintf(map_handle, MAP_NAME_MAX_LEN, "name %s",
			       desc->map_name);
	if (!ASSERT_GT(ret, 0, "format map handle"))
		return;

	ret = lookup_map_value(map_handle);
	ASSERT_OK(ret, "read map value");

	ret = read_map_btf_data(map_handle);
	ASSERT_OK(ret, "read map btf data");

	ret = write_map_value(map_handle);
	ASSERT_OK(desc->write_must_fail ? !ret : ret, "write map value");

	ret = delete_map_value(map_handle);
	ASSERT_OK(desc->write_must_fail ? !ret : ret, "delete map value");
	/* Restore deleted value */
	if (!ret)
		write_map_value(map_handle);

	ret = snprintf(iter_pin_path, PATH_MAX_LEN, "%s/iter", BPFFS_PIN_DIR);
	if (ASSERT_GT(ret, 0, "format iter pin path")) {
		ret = iterate_on_map_values(map_handle, iter_pin_path);
		ASSERT_OK(ret, "iterate on map values");
	}
}

static void test_create_nested_maps(void)
{
	if (!ASSERT_OK(create_inner_map(), "create inner map"))
		return;
	if (!ASSERT_OK(create_outer_map(), "create outer map"))
		goto end_cleanup_inner;
	ASSERT_OK(add_outer_map_entry(0), "add a first entry in outer map");
	ASSERT_OK(add_outer_map_entry(1), "add a second entry in outer map");
	ASSERT_NEQ(add_outer_map_entry(2), 0, "add a third entry in outer map");

	delete_pinned_map(OUTER_MAP_NAME);
end_cleanup_inner:
	delete_pinned_map(INNER_MAP_NAME);
}

static void test_btf_list(void)
{
	ASSERT_OK(run_bpftool_command("btf list"), "list btf data");
}

static struct test_desc tests[] = {
	{
		.name = "unprotected_unpinned",
		.protection = UNPROTECTED,
		.map_name = UNPROTECTED_MAP_NAME,
		.pinned = false,
		.write_must_fail = false,
	},
	{
		.name = "unprotected_pinned",
		.protection = UNPROTECTED,
		.map_name = UNPROTECTED_MAP_NAME,
		.pinned = true,
		.write_must_fail = false,
	},
	{
		.name = "protected_unpinned",
		.protection = PROTECTED,
		.map_name = UNPROTECTED_MAP_NAME,
		.pinned = false,
		.write_must_fail = true,
	},
	{
		.name = "protected_pinned",
		.protection = PROTECTED,
		.map_name = UNPROTECTED_MAP_NAME,
		.pinned = true,
		.write_must_fail = true,
	}
};

static const size_t tests_count = ARRAY_SIZE(tests);

void test_bpftool_maps_access(void)
{
	struct security_bpf_map *skel;
	struct test_desc *current;
	int i;

	skel = general_setup();
	if (!ASSERT_OK_PTR(skel, "prepare programs"))
		goto cleanup;

	for (i = 0; i < tests_count; i++) {
		current = &tests[i];
		if (!test__start_subtest(current->name))
			continue;
		if (ASSERT_OK(test_setup(skel, current), "subtest setup")) {
			test_basic_access(current);
			test_cleanup(current);
		}
	}
	if (test__start_subtest("nested_maps"))
		test_create_nested_maps();
	if (test__start_subtest("btf_list"))
		test_btf_list();

cleanup:
	general_cleanup(skel);
}
+144 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
#include <bpftool_helpers.h>
#include <test_progs.h>
#include <linux/bpf.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdbool.h>

#define BPFFS_DIR	"/sys/fs/bpf/test_metadata"
#define BPFFS_USED	BPFFS_DIR "/used"
#define BPFFS_UNUSED	BPFFS_DIR "/unused"

#define BPF_FILE_USED		"metadata_used.bpf.o"
#define BPF_FILE_UNUSED		"metadata_unused.bpf.o"
#define METADATA_MAP_NAME	"metadata.rodata"

#define MAX_BPFTOOL_OUTPUT_LEN	(64*1024)

#define MAX_TOKENS_TO_CHECK	3
static char output[MAX_BPFTOOL_OUTPUT_LEN];

struct test_desc {
	char *name;
	char *bpf_prog;
	char *bpffs_path;
	char *expected_output[MAX_TOKENS_TO_CHECK];
	char *expected_output_json[MAX_TOKENS_TO_CHECK];
	char *metadata_map_name;
};

static int setup(struct test_desc *test)
{
	return mkdir(BPFFS_DIR, 0700);
}

static void cleanup(struct test_desc *test)
{
	unlink(test->bpffs_path);
	rmdir(BPFFS_DIR);
}

static int check_metadata(char *buf, char * const *tokens, int count)
{
	int i;

	for (i = 0; i < count && tokens[i]; i++)
		if (!strstr(buf, tokens[i]))
			return 1;

	return 0;
}

static void run_test(struct test_desc *test)
{
	int ret;
	char cmd[MAX_BPFTOOL_CMD_LEN];

	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "prog load %s %s",
			test->bpf_prog, test->bpffs_path);
	if (!ASSERT_GT(ret, 0, "format prog insert command"))
		return;
	ret = run_bpftool_command(cmd);
	if (!ASSERT_OK(ret, "load program"))
		return;

	/* Check output with default format */
	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "prog show pinned %s",
		       test->bpffs_path);
	if (!ASSERT_GT(ret, 0, "format pinned prog check command"))
		return;
	ret = get_bpftool_command_output(cmd, output,
			MAX_BPFTOOL_OUTPUT_LEN);
	if (ASSERT_OK(ret, "get program info")) {
		ret = check_metadata(output, test->expected_output,
				ARRAY_SIZE(test->expected_output));
		ASSERT_OK(ret, "find metadata");
	}

	/* Check output with json format */
	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "prog -j show pinned %s",
		       test->bpffs_path);
	if (!ASSERT_GT(ret, 0, "format pinned prog check command in json"))
		return;
	ret = get_bpftool_command_output(cmd, output,
					 MAX_BPFTOOL_OUTPUT_LEN);
	if (ASSERT_OK(ret, "get program info in json")) {
		ret = check_metadata(output, test->expected_output_json,
				ARRAY_SIZE(test->expected_output_json));
		ASSERT_OK(ret, "find metadata in json");
	}

	/* Check that the corresponding map can be found and accessed */
	ret = snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "map show name %s",
		       test->metadata_map_name);
	if (!ASSERT_GT(ret, 0, "format map check command"))
		return;
	ASSERT_OK(run_bpftool_command(cmd), "access metadata map");
}

static struct test_desc tests[] = {
	{
		.name = "metadata_unused",
		.bpf_prog = BPF_FILE_UNUSED,
		.bpffs_path = BPFFS_UNUSED,
		.expected_output = {
			"a = \"foo\"",
			"b = 1"
		},
		.expected_output_json = {
			"\"metadata\":{\"a\":\"foo\",\"b\":1}"
		},
		.metadata_map_name = METADATA_MAP_NAME
	},
	{
		.name = "metadata_used",
		.bpf_prog = BPF_FILE_USED,
		.bpffs_path = BPFFS_USED,
		.expected_output = {
			"a = \"bar\"",
			"b = 2"
		},
		.expected_output_json = {
			"\"metadata\":{\"a\":\"bar\",\"b\":2}"
		},
		.metadata_map_name = METADATA_MAP_NAME
	}
};
static const int tests_count = ARRAY_SIZE(tests);

void test_bpftool_metadata(void)
{
	int i;

	for (i = 0; i < tests_count; i++) {
		if (!test__start_subtest(tests[i].name))
			continue;
		if (ASSERT_OK(setup(&tests[i]), "setup bpffs pin dir")) {
			run_test(&tests[i]);
			cleanup(&tests[i]);
		}
	}
}
Loading