From nobody Sun Feb 8 07:07:34 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C34E330E82B; Wed, 14 Jan 2026 08:59:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381184; cv=none; b=WovMNrvKT/lzLlQSayXKiGbgF1Lo/lMhlDckhm79iTClD6cQhgKArnVTCHCxnHAVxdOzAbb2MGz/w8x2MonBi7eh3OVUWZzbsQFFHTgBijkxEATbETlUwkwVutLVIeCaESR16swRlnjUCncvPcQ/BKyXpMJP5NZVJkZjEhQ/XEk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381184; c=relaxed/simple; bh=AXcncB8cVGGHhldgQyoX8ZrEQuko2sGpm2O0f9IUnOA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=n2W6ugfB/Fjj82Q7ruBeGWMPECCfc93onxXlL41x1rwghyA1FFTgtNSPs698MRu1thk6uarwWHtZ8S0uVMUI8w8lXpnYcHYVM1cbyqYTk+8nLhhRsJMeFryL+05dWhK5zEQaQ4UwneMyzIUKM1S+MN7yIdIIxjG5DC0X8kp74d4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=SOX5GWwr; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="SOX5GWwr" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 537F54E420CF; Wed, 14 Jan 2026 08:59:36 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 2A4876074A; Wed, 14 Jan 2026 08:59:36 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id ED12F103C8396; Wed, 14 Jan 2026 09:59:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1768381175; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=NcEoWUb5aJwsVAuuDv3WRH6UAsZs2JrlSDjQHU9FbrI=; b=SOX5GWwrY+UGf2RNQEqIdd7Uzqc3R+5gGFaB+sM1Vitseb08i2V0goabiyvkt8BANxPmff kUwfxTnEXckoqIel3NtTvQf/Z9ry5+W8QjhH5F9f5mWnGPMqhiLHjPFHDPE8FjZDfaP7x4 LvmeSGZ4SQjhbLQCALKHxiFz2hgVfWlF5cjDCywfkFrlBA4WeVP675JSB5wk3DQSZCaqBD bU6HuKx8XFUsri9EUbOME2B7QK7AExv9hPqcM+H1LXUDGJXctYyZ92G4j9tTdBFSKI4pSd kAGV01sLIvd1EkVZris3FAkhnUQAB0g9ncKIBBIMPnnHVMFwmMy2rDXkmkTtwQ== From: =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= Date: Wed, 14 Jan 2026 09:59:12 +0100 Subject: [PATCH bpf-next 1/4] bpf/selftests: move assert macros into a dedicated header Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260114-bpftool-tests-v1-1-cfab1cc9beaf@bootlin.com> References: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> In-Reply-To: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> To: Andrii Nakryiko , Eduard Zingerman , Alexei Starovoitov , Daniel Borkmann , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan Cc: ebpf@linuxfoundation.org, Bastien Curutchet , Thomas Petazzoni , linux-kernel@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The test_progs runner defines a large set of convenient assert macros to perform all the tests. Writing a new runner involves rewriting some macros if we want some basic testing features like standardized failure log. Export those assert macros from test_progs into a dedicated header so that we can use those in any test_runner. The sole requirement to be able to use those macros is to define a test__fail function in the runner that will be called whenever an assert fails. Signed-off-by: Alexis Lothor=C3=A9 (eBPF Foundation) Acked-by: Quentin Monnet --- tools/testing/selftests/bpf/assert_helpers.h | 231 +++++++++++++++++++++++= ++++ tools/testing/selftests/bpf/test_progs.h | 226 +----------------------= --- 2 files changed, 232 insertions(+), 225 deletions(-) diff --git a/tools/testing/selftests/bpf/assert_helpers.h b/tools/testing/s= elftests/bpf/assert_helpers.h new file mode 100644 index 000000000000..93ab5bf39431 --- /dev/null +++ b/tools/testing/selftests/bpf/assert_helpers.h @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#pragma once +#include +#include +#include + +#define _CHECK(condition, tag, duration, format...) ({ \ + int __ret =3D !!(condition); \ + int __save_errno =3D errno; \ + if (__ret) { \ + test__fail(); \ + fprintf(stdout, "%s:FAIL:%s ", __func__, tag); \ + fprintf(stdout, ##format); \ + } else { \ + fprintf(stdout, "%s:PASS:%s %d nsec\n", \ + __func__, tag, duration); \ + } \ + errno =3D __save_errno; \ + __ret; \ +}) + +#define CHECK_FAIL(condition) ({ \ + int __ret =3D !!(condition); \ + int __save_errno =3D errno; \ + if (__ret) { \ + test__fail(); \ + fprintf(stdout, "%s:FAIL:%d\n", __func__, __LINE__); \ + } \ + errno =3D __save_errno; \ + __ret; \ +}) + +#define CHECK(condition, tag, format...) \ + _CHECK(condition, tag, duration, format) +#define CHECK_ATTR(condition, tag, format...) \ + _CHECK(condition, tag, tattr.duration, format) + +#define ASSERT_FAIL(fmt, args...) ({ \ + static int duration; \ + CHECK(false, "", fmt"\n", ##args); \ + false; \ +}) + +#define ASSERT_TRUE(actual, name) ({ \ + static int duration; \ + bool ___ok =3D (actual); \ + CHECK(!___ok, (name), "unexpected %s: got FALSE\n", (name)); \ + ___ok; \ +}) + +#define ASSERT_FALSE(actual, name) ({ \ + static int duration; \ + bool ___ok =3D !(actual); \ + CHECK(!___ok, (name), "unexpected %s: got TRUE\n", (name)); \ + ___ok; \ +}) + +#define ASSERT_EQ(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act =3D=3D ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld !=3D expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_NEQ(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act !=3D ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld =3D=3D expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_LT(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act < ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld >=3D expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_LE(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act <=3D ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld > expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_GT(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act > ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld <=3D expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_GE(actual, expected, name) ({ \ + static int duration; \ + typeof(actual) ___act =3D (actual); \ + typeof(expected) ___exp =3D (expected); \ + bool ___ok =3D ___act >=3D ___exp; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual %lld < expected %lld\n", \ + (name), (long long)(___act), (long long)(___exp)); \ + ___ok; \ +}) + +#define ASSERT_STREQ(actual, expected, name) ({ \ + static int duration; \ + const char *___act =3D actual; \ + const char *___exp =3D expected; \ + bool ___ok =3D strcmp(___act, ___exp) =3D=3D 0; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual '%s' !=3D expected '%s'\n", \ + (name), ___act, ___exp); \ + ___ok; \ +}) + +#define ASSERT_STRNEQ(actual, expected, len, name) ({ \ + static int duration; \ + const char *___act =3D actual; \ + const char *___exp =3D expected; \ + int ___len =3D len; \ + bool ___ok =3D strncmp(___act, ___exp, ___len) =3D=3D 0; \ + CHECK(!___ok, (name), \ + "unexpected %s: actual '%.*s' !=3D expected '%.*s'\n", \ + (name), ___len, ___act, ___len, ___exp); \ + ___ok; \ +}) + +#define ASSERT_HAS_SUBSTR(str, substr, name) ({ \ + static int duration; \ + const char *___str =3D str; \ + const char *___substr =3D substr; \ + bool ___ok =3D strstr(___str, ___substr) !=3D NULL; \ + CHECK(!___ok, (name), \ + "unexpected %s: '%s' is not a substring of '%s'\n", \ + (name), ___substr, ___str); \ + ___ok; \ +}) + +#define ASSERT_MEMEQ(actual, expected, len, name) ({ \ + static int duration; \ + const void *__act =3D actual; \ + const void *__exp =3D expected; \ + int __len =3D len; \ + bool ___ok =3D memcmp(__act, __exp, __len) =3D=3D 0; \ + CHECK(!___ok, (name), "unexpected memory mismatch\n"); \ + fprintf(stdout, "actual:\n"); \ + hexdump("\t", __act, __len); \ + fprintf(stdout, "expected:\n"); \ + hexdump("\t", __exp, __len); \ + ___ok; \ +}) + +#define ASSERT_OK(res, name) ({ \ + static int duration; \ + long long ___res =3D (res); \ + bool ___ok =3D ___res =3D=3D 0; \ + CHECK(!___ok, (name), "unexpected error: %lld (errno %d)\n", \ + ___res, errno); \ + ___ok; \ +}) + +#define ASSERT_ERR(res, name) ({ \ + static int duration; \ + long long ___res =3D (res); \ + bool ___ok =3D ___res < 0; \ + CHECK(!___ok, (name), "unexpected success: %lld\n", ___res); \ + ___ok; \ +}) + +#define ASSERT_NULL(ptr, name) ({ \ + static int duration; \ + const void *___res =3D (ptr); \ + bool ___ok =3D !___res; \ + CHECK(!___ok, (name), "unexpected pointer: %p\n", ___res); \ + ___ok; \ +}) + +#define ASSERT_OK_PTR(ptr, name) ({ \ + static int duration; \ + const void *___res =3D (ptr); \ + int ___err =3D libbpf_get_error(___res); \ + bool ___ok =3D ___err =3D=3D 0; \ + CHECK(!___ok, (name), "unexpected error: %d\n", ___err); \ + ___ok; \ +}) + +#define ASSERT_ERR_PTR(ptr, name) ({ \ + static int duration; \ + const void *___res =3D (ptr); \ + int ___err =3D libbpf_get_error(___res); \ + bool ___ok =3D ___err !=3D 0; \ + CHECK(!___ok, (name), "unexpected pointer: %p\n", ___res); \ + ___ok; \ +}) + +#define ASSERT_OK_FD(fd, name) ({ \ + static int duration; \ + int ___fd =3D (fd); \ + bool ___ok =3D ___fd >=3D 0; \ + CHECK(!___ok, (name), "unexpected fd: %d (errno %d)\n", \ + ___fd, errno); \ + ___ok; \ +}) + +#define ASSERT_ERR_FD(fd, name) ({ \ + static int duration; \ + int ___fd =3D (fd); \ + bool ___ok =3D ___fd < 0; \ + CHECK(!___ok, (name), "unexpected fd: %d\n", ___fd); \ + ___ok; \ +}) + diff --git a/tools/testing/selftests/bpf/test_progs.h b/tools/testing/selft= ests/bpf/test_progs.h index eebfc18cdcd2..bb876d8f6bcc 100644 --- a/tools/testing/selftests/bpf/test_progs.h +++ b/tools/testing/selftests/bpf/test_progs.h @@ -42,6 +42,7 @@ typedef __u16 __sum16; #include #include "trace_helpers.h" #include "testing_helpers.h" +#include "assert_helpers.h" =20 enum verbosity { VERBOSE_NONE, @@ -195,231 +196,6 @@ void hexdump(const char *prefix, const void *buf, siz= e_t len); fprintf(stdout, ##format); \ }) =20 -#define _CHECK(condition, tag, duration, format...) ({ \ - int __ret =3D !!(condition); \ - int __save_errno =3D errno; \ - if (__ret) { \ - test__fail(); \ - fprintf(stdout, "%s:FAIL:%s ", __func__, tag); \ - fprintf(stdout, ##format); \ - } else { \ - fprintf(stdout, "%s:PASS:%s %d nsec\n", \ - __func__, tag, duration); \ - } \ - errno =3D __save_errno; \ - __ret; \ -}) - -#define CHECK_FAIL(condition) ({ \ - int __ret =3D !!(condition); \ - int __save_errno =3D errno; \ - if (__ret) { \ - test__fail(); \ - fprintf(stdout, "%s:FAIL:%d\n", __func__, __LINE__); \ - } \ - errno =3D __save_errno; \ - __ret; \ -}) - -#define CHECK(condition, tag, format...) \ - _CHECK(condition, tag, duration, format) -#define CHECK_ATTR(condition, tag, format...) \ - _CHECK(condition, tag, tattr.duration, format) - -#define ASSERT_FAIL(fmt, args...) ({ \ - static int duration =3D 0; \ - CHECK(false, "", fmt"\n", ##args); \ - false; \ -}) - -#define ASSERT_TRUE(actual, name) ({ \ - static int duration =3D 0; \ - bool ___ok =3D (actual); \ - CHECK(!___ok, (name), "unexpected %s: got FALSE\n", (name)); \ - ___ok; \ -}) - -#define ASSERT_FALSE(actual, name) ({ \ - static int duration =3D 0; \ - bool ___ok =3D !(actual); \ - CHECK(!___ok, (name), "unexpected %s: got TRUE\n", (name)); \ - ___ok; \ -}) - -#define ASSERT_EQ(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act =3D=3D ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld !=3D expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_NEQ(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act !=3D ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld =3D=3D expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_LT(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act < ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld >=3D expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_LE(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act <=3D ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld > expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_GT(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act > ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld <=3D expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_GE(actual, expected, name) ({ \ - static int duration =3D 0; \ - typeof(actual) ___act =3D (actual); \ - typeof(expected) ___exp =3D (expected); \ - bool ___ok =3D ___act >=3D ___exp; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual %lld < expected %lld\n", \ - (name), (long long)(___act), (long long)(___exp)); \ - ___ok; \ -}) - -#define ASSERT_STREQ(actual, expected, name) ({ \ - static int duration =3D 0; \ - const char *___act =3D actual; \ - const char *___exp =3D expected; \ - bool ___ok =3D strcmp(___act, ___exp) =3D=3D 0; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual '%s' !=3D expected '%s'\n", \ - (name), ___act, ___exp); \ - ___ok; \ -}) - -#define ASSERT_STRNEQ(actual, expected, len, name) ({ \ - static int duration =3D 0; \ - const char *___act =3D actual; \ - const char *___exp =3D expected; \ - int ___len =3D len; \ - bool ___ok =3D strncmp(___act, ___exp, ___len) =3D=3D 0; \ - CHECK(!___ok, (name), \ - "unexpected %s: actual '%.*s' !=3D expected '%.*s'\n", \ - (name), ___len, ___act, ___len, ___exp); \ - ___ok; \ -}) - -#define ASSERT_HAS_SUBSTR(str, substr, name) ({ \ - static int duration =3D 0; \ - const char *___str =3D str; \ - const char *___substr =3D substr; \ - bool ___ok =3D strstr(___str, ___substr) !=3D NULL; \ - CHECK(!___ok, (name), \ - "unexpected %s: '%s' is not a substring of '%s'\n", \ - (name), ___substr, ___str); \ - ___ok; \ -}) - -#define ASSERT_MEMEQ(actual, expected, len, name) ({ \ - static int duration =3D 0; \ - const void *__act =3D actual; \ - const void *__exp =3D expected; \ - int __len =3D len; \ - bool ___ok =3D memcmp(__act, __exp, __len) =3D=3D 0; \ - CHECK(!___ok, (name), "unexpected memory mismatch\n"); \ - fprintf(stdout, "actual:\n"); \ - hexdump("\t", __act, __len); \ - fprintf(stdout, "expected:\n"); \ - hexdump("\t", __exp, __len); \ - ___ok; \ -}) - -#define ASSERT_OK(res, name) ({ \ - static int duration =3D 0; \ - long long ___res =3D (res); \ - bool ___ok =3D ___res =3D=3D 0; \ - CHECK(!___ok, (name), "unexpected error: %lld (errno %d)\n", \ - ___res, errno); \ - ___ok; \ -}) - -#define ASSERT_ERR(res, name) ({ \ - static int duration =3D 0; \ - long long ___res =3D (res); \ - bool ___ok =3D ___res < 0; \ - CHECK(!___ok, (name), "unexpected success: %lld\n", ___res); \ - ___ok; \ -}) - -#define ASSERT_NULL(ptr, name) ({ \ - static int duration =3D 0; \ - const void *___res =3D (ptr); \ - bool ___ok =3D !___res; \ - CHECK(!___ok, (name), "unexpected pointer: %p\n", ___res); \ - ___ok; \ -}) - -#define ASSERT_OK_PTR(ptr, name) ({ \ - static int duration =3D 0; \ - const void *___res =3D (ptr); \ - int ___err =3D libbpf_get_error(___res); \ - bool ___ok =3D ___err =3D=3D 0; \ - CHECK(!___ok, (name), "unexpected error: %d\n", ___err); \ - ___ok; \ -}) - -#define ASSERT_ERR_PTR(ptr, name) ({ \ - static int duration =3D 0; \ - const void *___res =3D (ptr); \ - int ___err =3D libbpf_get_error(___res); \ - bool ___ok =3D ___err !=3D 0; \ - CHECK(!___ok, (name), "unexpected pointer: %p\n", ___res); \ - ___ok; \ -}) - -#define ASSERT_OK_FD(fd, name) ({ \ - static int duration =3D 0; \ - int ___fd =3D (fd); \ - bool ___ok =3D ___fd >=3D 0; \ - CHECK(!___ok, (name), "unexpected fd: %d (errno %d)\n", \ - ___fd, errno); \ - ___ok; \ -}) - -#define ASSERT_ERR_FD(fd, name) ({ \ - static int duration =3D 0; \ - int ___fd =3D (fd); \ - bool ___ok =3D ___fd < 0; \ - CHECK(!___ok, (name), "unexpected fd: %d\n", ___fd); \ - ___ok; \ -}) - #define SYS(goto_label, fmt, ...) \ ({ \ char cmd[1024]; \ --=20 2.52.0 From nobody Sun Feb 8 07:07:34 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A9EA83803CF; Wed, 14 Jan 2026 08:59:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381185; cv=none; b=IHik1xopHncpTkpubEKAmpfDGTgf7lavhDzyD5SCrwp2ZS8lxZ8lkEyMNLCPRB01cwT8Dcb7Gr+5ab1jp4yDzkMuOy1tj81HR2OAh8Wig9IUPgVCN2YuPgvO4ZeDJHmugDSmwKI1guxVQw7f6mJxKf5AyXNTbUNoBQSz0xblW+o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381185; c=relaxed/simple; bh=WcMk7alXumIyFEgbvJ5w7LuChwzuYaHIhqpgBRJqB2A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=p8xV+q+d6Vg9NXse3++v2ZGyUwruce5zYy7/1xyofkS5AVumACxNFpZ7qHHLDPkAw7ddYs7O3h+JNeWB8fbaFjneRSkIM48x4YAflCWlbFWwwbZeHCVSEn4QGxLa16tWwMvzry1K5lKrPbckekLzyRp3ltWG3rsCrBs61YpRgWI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=dVWWmeht; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="dVWWmeht" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id ABAE74E420D1; Wed, 14 Jan 2026 08:59:38 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 8213F6074A; Wed, 14 Jan 2026 08:59:38 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 56DDF103C8951; Wed, 14 Jan 2026 09:59:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1768381177; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=tWTcbB27fFrJZCiVPQKclLlVwus6WH184KbRKM2lRug=; b=dVWWmehtCWq1YLrNWFFAKjWuqBRTnkCu9IbRn7yfjedK5GoMQ6nUD487rZCPrehe7Eclir 2hpqJXbrV8plkZP+ba5erb35itVP1lqMJ7gEdwTrM2JNNnuPt4qDDbxHyWvAQtTDCYd9CP /fp+NoivhOAffDaRFLfjySt6nNr6pL9/JLhSD9TFZMGiflsV3vMnRmEx+6ac++iMEjVzyK 0gRsq8FcMRm8GB3faOipR2gIi2nXx21ePtKq/tyQH+zX47XVAPQhj6X3Jge1b9crMgEQEY 9yLv4GWFU0vh3/ZAO5Sv4R/78UbItu2M/uB+edJcyiXK62OJIv7XXNspPF1pUA== From: =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= Date: Wed, 14 Jan 2026 09:59:13 +0100 Subject: [PATCH bpf-next 2/4] bpf/selftests: introduce bptool test runner and a first test Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260114-bpftool-tests-v1-2-cfab1cc9beaf@bootlin.com> References: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> In-Reply-To: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> To: Andrii Nakryiko , Eduard Zingerman , Alexei Starovoitov , Daniel Borkmann , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan Cc: ebpf@linuxfoundation.org, Bastien Curutchet , Thomas Petazzoni , linux-kernel@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The tools/testing/selftests/bpf directory contains multiple scripts (shell, python, c code, etc) that aim to test some specific features from bpftool. Those isolated tests are currently not executed by any CI automation. Create a dedicated runner for any bpftool-related test that can then be added to the list of executed runners in bpf CI automation. This new runner (and the corresponding Makefile tooling) is highly inspired from test_progs, but kept a bit simpler. This version supports the following features: - autodetection of bpftool test stored in the in bpftool_tests directory - bpftool binary under test is passed as runner argument - a few helpers to allow to easily run abpftool commands while possibly collecting the output - usage of assert macros shared with test_progs - basic sub-tests management - logs collection, logs being dumped only for failed tests - exit code reflecting whether all tests have passed or not As this runner needs at least one test to be implemented to properly compile, also bring bpftool_metadata, which is the conversion of test_bpftool_metadata.sh: this test validates that the output of some basic prog/map listings done with bpftool properly returns the metadata collected from the .rodata section of eBPF programs. This new runner gives an output similar to the one generated by test_progs: #2/1 metadata/metadata_unused: OK #2/2 metadata/metadata_used: OK #2 metadata: OK Summary: 1 PASSED, 0 FAILED Signed-off-by: Alexis Lothor=C3=A9 (eBPF Foundation) --- tools/testing/selftests/bpf/.gitignore | 1 + tools/testing/selftests/bpf/Makefile | 14 ++- tools/testing/selftests/bpf/bpftool_helpers.c | 114 ++++++++++++++++++ tools/testing/selftests/bpf/bpftool_helpers.h | 19 +++ .../testing/selftests/bpf/bpftool_tests/.gitignore | 2 + .../selftests/bpf/bpftool_tests/bpftool_metadata.c | 128 +++++++++++++++++= ++++ tools/testing/selftests/bpf/test_bpftool.c | 126 +++++++++++++++++= +++ tools/testing/selftests/bpf/test_bpftool.h | 36 ++++++ 8 files changed, 439 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftes= ts/bpf/.gitignore index b8bf51b7a0b0..9498cc11de97 100644 --- a/tools/testing/selftests/bpf/.gitignore +++ b/tools/testing/selftests/bpf/.gitignore @@ -2,6 +2,7 @@ bpftool bpf-helpers* bpf-syscall* +test_bpftool test_verifier test_maps test_lru_map diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests= /bpf/Makefile index fd42b7193d4e..a1fe94efa53c 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -76,7 +76,8 @@ endif TEST_GEN_PROGS =3D test_verifier test_tag test_maps test_lru_map test_prog= s \ test_sockmap \ test_tcpnotify_user \ - test_progs-no_alu32 + test_progs-no_alu32 \ + test_bpftool TEST_INST_SUBDIRS :=3D no_alu32 =20 # Also test bpf-gcc, if present @@ -791,6 +792,17 @@ TRUNNER_BPF_BUILD_RULE :=3D $$(error no BPF objects sh= ould be built) TRUNNER_BPF_CFLAGS :=3D $(eval $(call DEFINE_TEST_RUNNER,test_maps)) =20 +# Define bpftool test runner. +TRUNNER_TESTS_DIR :=3D bpftool_tests +TRUNNER_BPF_PROGS_DIR :=3D progs +TRUNNER_EXTRA_SOURCES :=3D test_bpftool.c \ + bpftool_helpers.c +TRUNNER_LIB_SOURCES :=3D +TRUNNER_EXTRA_FILES :=3D +TRUNNER_BPF_BUILD_RULE :=3D CLANG_BPF_BUILD_RULE +TRUNNER_BPF_CFLAGS :=3D +$(eval $(call DEFINE_TEST_RUNNER,test_bpftool)) + # Define test_verifier test runner. # It is much simpler than test_maps/test_progs and sufficiently different = from # them (e.g., test.h is using completely pattern), that it's worth just diff --git a/tools/testing/selftests/bpf/bpftool_helpers.c b/tools/testing/= selftests/bpf/bpftool_helpers.c new file mode 100644 index 000000000000..ff8084d9a121 --- /dev/null +++ b/tools/testing/selftests/bpf/bpftool_helpers.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "bpftool_helpers.h" +#include "test_bpftool.h" +#include +#include +#include +#include + +#define BPFTOOL_PATH "./tools/sbin/bpftool" +#define BPFTOOL_CMD_MAX_LEN 256 + +static int run_command(char *command, bool get_output, char *output_buf, s= ize_t output_max_len) +{ + FILE *f; + int ret; + + f =3D popen(command, "r"); + if (!f) + return 1; + + if (get_output) + fread(output_buf, 1, output_max_len, f); + ret =3D pclose(f); + + return ret; +} + +int run_bpftool_command(char *args) +{ + char cmd[BPFTOOL_CMD_MAX_LEN]; + int ret; + + ret =3D snprintf(cmd, BPFTOOL_CMD_MAX_LEN, "%s %s > /dev/null 2>&1", + env.bpftool_path, args); + if (ret !=3D + strlen(env.bpftool_path) + 1 + strlen(args) + strlen(" > /dev/null 2>= &1")) { + fprintf(stderr, "Failed to generate bpftool command\n"); + return 1; + } + + return run_command(cmd, false, NULL, 0); +} + +int get_bpftool_command_output(char *args, char *output_buf, size_t output= _max_len) +{ + int ret; + char cmd[BPFTOOL_CMD_MAX_LEN]; + + ret =3D snprintf(cmd, BPFTOOL_CMD_MAX_LEN, "%s %s", env.bpftool_path, + args); + if (ret !=3D strlen(args) + strlen(env.bpftool_path) + 1) { + fprintf(stderr, "Failed to generate bpftool command"); + return 1; + } + + return run_command(cmd, true, output_buf, output_max_len); +} + +void hijack_stdio(void) +{ + fflush(stdout); + fflush(stderr); + if (env.current_subtest) { + env.current_test->saved_stdout =3D stdout; + env.current_test->saved_stderr =3D stderr; + stdout =3D open_memstream(&env.current_subtest->log, + &env.current_subtest->log_size); + + } else { + env.saved_stdout =3D stdout; + env.saved_stderr =3D stderr; + stdout =3D open_memstream(&env.current_test->log, + &env.current_test->log_size); + } + stderr =3D stdout; +} + +void restore_stdio(void) +{ + fclose(stdout); + if (env.current_subtest) { + stdout =3D env.current_test->saved_stdout; + stderr =3D env.current_test->saved_stderr; + + } else { + stdout =3D env.saved_stdout; + stderr =3D env.saved_stderr; + } + +} + +void test__start_subtest(const char *subtest_name) +{ + test__end_subtest(); + env.current_test->subtests_count++; + env.subtest_states =3D realloc(env.subtest_states, + env.current_test->subtests_count * + sizeof(struct subtest_state)); + env.current_subtest =3D + &env.subtest_states[env.current_test->subtests_count - 1]; + memset(env.current_subtest, 0, sizeof(struct subtest_state)); + env.current_subtest->name =3D strdup(subtest_name); + + hijack_stdio(); +} + +void test__end_subtest(void) +{ + if (env.current_subtest) { + restore_stdio(); + env.current_subtest =3D NULL; + } +} + diff --git a/tools/testing/selftests/bpf/bpftool_helpers.h b/tools/testing/= selftests/bpf/bpftool_helpers.h new file mode 100644 index 000000000000..1eacec7936ba --- /dev/null +++ b/tools/testing/selftests/bpf/bpftool_helpers.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#pragma once + +#include +#include +#include + +#define MAX_BPFTOOL_CMD_LEN (256) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +int run_bpftool_command(char *args); +int get_bpftool_command_output(char *args, char *output_buf, size_t output= _max_len); +void test__start_subtest(const char *subtests_name); +void test__end_subtest(void); +void hijack_stdio(void); +void restore_stdio(void); diff --git a/tools/testing/selftests/bpf/bpftool_tests/.gitignore b/tools/t= esting/selftests/bpf/bpftool_tests/.gitignore new file mode 100644 index 000000000000..89c4a3d37544 --- /dev/null +++ b/tools/testing/selftests/bpf/bpftool_tests/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +tests.h diff --git a/tools/testing/selftests/bpf/bpftool_tests/bpftool_metadata.c b= /tools/testing/selftests/bpf/bpftool_tests/bpftool_metadata.c new file mode 100644 index 000000000000..e7146b26f298 --- /dev/null +++ b/tools/testing/selftests/bpf/bpftool_tests/bpftool_metadata.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 MAX_BPFTOOL_OUTPUT_LEN (100*1000) + +#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]; +}; + +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 =3D 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]; + + snprintf(cmd, MAX_BPFTOOL_CMD_LEN, "prog load %s %s", + test->bpf_prog, test->bpffs_path); + ret =3D run_bpftool_command(cmd); + if (!ASSERT_OK(ret, "load program")) + return; + + /* Check output with default format */ + ret =3D get_bpftool_command_output("prog show name prog", output, + MAX_BPFTOOL_OUTPUT_LEN); + if (ASSERT_OK(ret, "get program info")) { + ret =3D check_metadata(output, test->expected_output, + ARRAY_SIZE(test->expected_output)); + ASSERT_OK(ret, "find metadata"); + } + + /* Check output with json format */ + ret =3D get_bpftool_command_output("prog -j show name prog", output, + MAX_BPFTOOL_OUTPUT_LEN); + if (ASSERT_OK(ret, "get program info in json")) { + ret =3D check_metadata(output, test->expected_output_json, + ARRAY_SIZE(test->expected_output_json)); + ASSERT_OK(ret, "find metadata in json"); + } + +} + +struct test_desc tests[] =3D { + { + .name =3D "metadata_unused", + .bpf_prog =3D BPF_FILE_UNUSED, + .bpffs_path =3D BPFFS_UNUSED, + .expected_output =3D { + "a =3D \"foo\"", + "b =3D 1" + }, + .expected_output_json =3D { + "\"metadata\":{\"a\":\"foo\",\"b\":1}" + } + }, + { + .name =3D "metadata_used", + .bpf_prog =3D BPF_FILE_USED, + .bpffs_path =3D BPFFS_USED, + .expected_output =3D { + "a =3D \"bar\"", + "b =3D 2" + }, + .expected_output_json =3D { + "\"metadata\":{\"a\":\"bar\",\"b\":2}" + } + } +}; + +static const int tests_count =3D ARRAY_SIZE(tests); + +void test_metadata(void) +{ + int i, ret; + + for (i =3D 0; i < tests_count; i++) { + test__start_subtest(tests[i].name); + ret =3D setup(&tests[i]); + if (!ASSERT_OK(ret, "setup bpffs pin dir")) + continue; + run_test(&tests[i]); + cleanup(&tests[i]); + } + +} + diff --git a/tools/testing/selftests/bpf/test_bpftool.c b/tools/testing/sel= ftests/bpf/test_bpftool.c new file mode 100644 index 000000000000..b5fb17d5ea2d --- /dev/null +++ b/tools/testing/selftests/bpf/test_bpftool.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include + +struct bpftool_runner_env env =3D {0}; + +#define DEFINE_TEST(name) extern void test_##name(void); +#include +#undef DEFINE_TEST + +struct prog_test_def { + char *test_name; + void (*run_test)(void); +}; + +static struct prog_test_def prog_test_defs[] =3D { +#define DEFINE_TEST(name) { \ + .test_name =3D #name, \ + .run_test =3D &test_##name, \ +}, +#include +#undef DEFINE_TEST +}; + + +static const int tests_count =3D ARRAY_SIZE(prog_test_defs); + +/* Needed method for the assert macros exposed by assert_helpers.h */ +void test__fail(void) +{ + if (env.current_subtest) + env.current_subtest->failed =3D true; + if (!env.current_test->failed) + env.failure_cnt++; + env.current_test->failed =3D true; +} + +static void test_setup(struct test_state *test, char *name) +{ + env.current_test =3D test; + env.current_test->name =3D strdup(name); +} + +static void dump_results(struct test_state *test, int test_index) +{ + int j; + + if (test->failed) + fprintf(stdout, "%s\n", test->log); + free(test->log); + for (j =3D 0; j < test->subtests_count; j++) { + if (env.subtest_states[j].failed) + fprintf(stdout, "%s\n", env.subtest_states[j].log); + free(env.subtest_states[j].log); + fprintf(stdout, "#%d/%d\t%s/%s: %s\n", test_index+1, j+1, + env.current_test->name, + env.subtest_states[j].name, + env.subtest_states[j].failed ? "KO" : "OK"); + free(env.subtest_states[j].name); + } + if (env.current_test->subtests_count) { + free(env.subtest_states); + env.subtest_states =3D NULL; + } + fprintf(stdout, "#%d\t%s: %s\n", test_index + 1, test->name, + test->failed ? "KO" : "OK"); +} + +static void test_teardown(struct test_state *test, int test_index) +{ + dump_results(test, test_index); + free(env.current_test->name); + env.current_test =3D NULL; +} + +static int parse_args(int argc, char *argv[]) +{ + if (argc !=3D 2) + return 1; + if (access(argv[1], R_OK|X_OK)) + return 1; + env.bpftool_path =3D argv[1]; + + return 0; +} + +static void usage(char *prog) +{ + fprintf(stdout, "Usage: %s \n", prog); + fprintf(stdout, "\t: path to the bpftool binary to test\n"); +} + +int main(int argc, char *argv[]) +{ + struct test_state *ctx =3D NULL; + int i; + + if (parse_args(argc, argv)) { + fprintf(stderr, "Invalid arguments\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + ctx =3D calloc(tests_count, sizeof(struct test_state)); + if (!ctx) + exit(EXIT_FAILURE); + + for (i =3D 0; i < tests_count; i++) { + test_setup(&ctx[i], prog_test_defs[i].test_name); + hijack_stdio(); + prog_test_defs[i].run_test(); + test__end_subtest(); + restore_stdio(); + test_teardown(&ctx[i], i); + } + + fprintf(stdout, "Summary: %d PASSED, %d FAILED\n", + tests_count - env.failure_cnt, env.failure_cnt); + free(ctx); + return env.failure_cnt ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/bpf/test_bpftool.h b/tools/testing/sel= ftests/bpf/test_bpftool.h new file mode 100644 index 000000000000..a78659eeaf2b --- /dev/null +++ b/tools/testing/selftests/bpf/test_bpftool.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#pragma once + +#include +#include + +extern struct bpftool_runner_env env; + +void test__fail(void); + +struct test_state { + char *name; + char *log; + size_t log_size; + bool failed; + int subtests_count; + int subtests_failures; + FILE *saved_stdout; + FILE *saved_stderr; +}; + +struct subtest_state { + char *name; + char *log; + size_t log_size; + bool failed; +}; +struct bpftool_runner_env { + char *bpftool_path; + int failure_cnt; + FILE *saved_stdout; + FILE *saved_stderr; + struct test_state *current_test; + struct subtest_state *current_subtest; + struct subtest_state *subtest_states; +}; --=20 2.52.0 From nobody Sun Feb 8 07:07:34 2026 Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2B5703803EA for ; Wed, 14 Jan 2026 08:59:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.171.202.116 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381187; cv=none; b=c7txdeS7J7xxvUqma9ca3DjskDeKwifqF6zM/7OyKUkO3cRjKLwrLsJwEQC4m/Lu53A80E8205gwoJqH6eOnjN4LLK727svrrVCbQetD7DapZw2CkkY0Ip0Tm7hE5QJAloWq2+kR4+XC4/kRgOvuJTCglvFP6F3fA7UEqRLEAZY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381187; c=relaxed/simple; bh=pF6GTNh+YrF6fCPuY/fGt+pk0amUlEXY6u+97ttpg5M=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pVT12h1cV0wNqyHDKcJT6tpPQUd8b0KBqpcF0Sm+Vl9Mf69xe9ZY1CVJCnC7R287GLq9w2FZEy+pbLwSwnFXX6CirsF/3NVsNzC++kUmPT7ctLeDX7F+hE7dm0kktdhe29UApHYYr+1/6gYXlJUFEFrW8gw+n+iVsNlDu1hGoEY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=rjWoZByx; arc=none smtp.client-ip=185.171.202.116 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="rjWoZByx" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id 63842C1F141; Wed, 14 Jan 2026 08:59:14 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 094866074A; Wed, 14 Jan 2026 08:59:41 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id C4BA4103C8744; Wed, 14 Jan 2026 09:59:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1768381179; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=fn4bq2kcP/cFXCxYv1aQ9Vv1ntTqnx0kn8qj9OdtK5U=; b=rjWoZByxsGSf3X+3j8F5YT0rOePv+vXyZbabJ/DZZ6pWHS5JzK9jthr1ffzZEjAVW2ewS0 JdjVOp0ud8GQfwN2dgPgMpkVX/G/LD8qIii4vAjiknBPlwQtRl5OTgIQ31f+lT0t2TKGPa NkPLl8MiTWRbIDUQzRjHK7h+8D8R56mNlxoTs6K5T8UHvSSEIaMc3Wo+o1d8WhNgL+wyJV sOd2GJCDyNXR17adEWqn7UmPsreAg8xPQMmva9LHNlh/X4lGS57MRkRRItm+Q93YoMHc3R 2Tjh8Q+rdRT1MP82sYKK/4SEWikHc5WhlzBWzSZ5osocAxPeVH6lJAWiE6RuwA== From: =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= Date: Wed, 14 Jan 2026 09:59:14 +0100 Subject: [PATCH bpf-next 3/4] selftests/bpf: add bpftool map manipulations tests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260114-bpftool-tests-v1-3-cfab1cc9beaf@bootlin.com> References: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> In-Reply-To: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> To: Andrii Nakryiko , Eduard Zingerman , Alexei Starovoitov , Daniel Borkmann , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan Cc: ebpf@linuxfoundation.org, Bastien Curutchet , Thomas Petazzoni , linux-kernel@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The test_bpftool_map.sh script tests that maps read/write accesses are being properly allowed/refused by the kernel depending on a specific fmod_ret program being attached on security_bpf_map function. Rewrite this test to integrate it in the new test_bpftool runner. The new test spawns a few subtests: #1/1 maps_access/unprotected_unpinned: OK #1/2 maps_access/unprotected_pinned: OK #1/3 maps_access/protected_unpinned: OK #1/4 maps_access/protected_pinned: OK #1/5 maps_access/nested_maps: OK #1/6 maps_access/btf_list: OK #1 maps_access: OK #2/1 metadata/metadata_unused: OK #2/2 metadata/metadata_used: OK #2 metadata: OK Summary: 2 PASSED, 0 FAILED Signed-off-by: Alexis Lothor=C3=A9 (eBPF Foundation) Acked-by: Quentin Monnet --- .../bpf/bpftool_tests/bpftool_maps_access.c | 370 +++++++++++++++++= ++++ 1 file changed, 370 insertions(+) diff --git a/tools/testing/selftests/bpf/bpftool_tests/bpftool_maps_access.= c b/tools/testing/selftests/bpf/bpftool_tests/bpftool_maps_access.c new file mode 100644 index 000000000000..da4277c79e22 --- /dev/null +++ b/tools/testing/selftests/bpf/bpftool_tests/bpftool_maps_access.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 =3D security_bpf_map__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + goto end; + + struct bpf_map *maps[] =3D {skel->maps.prot_map, skel->maps.not_prot_map}; + + ret =3D security_bpf_map__attach(skel); + if (!ASSERT_OK(ret, "attach maps security programs")) + goto end_destroy; + + for (i =3D 0; i < sizeof(maps)/sizeof(struct bpf_map *); i++) { + for (key =3D 0; key < 2; key++) { + int ret =3D 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 =3D 0; + value =3D 1; + ret =3D 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 =3D=3D PROTECTED) { + test->map =3D skel->maps.prot_map; + test->map_name =3D PROTECTED_MAP_NAME; + } else { + test->map =3D skel->maps.not_prot_map; + test->map_name =3D UNPROTECTED_MAP_NAME; + } +} + +static int test_setup(struct security_bpf_map *skel, struct test_desc *des= c) +{ + int ret; + + update_test_desc(skel, desc); + + if (desc->pinned) { + ret =3D 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 =3D 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 =3D 0; + + ret =3D 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 =3D 0; + + ret =3D 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 =3D 0; + + ret =3D 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 =3D 0; + + ret =3D 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 =3D 0; + + + ret =3D 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 cration cmd")) + return 1; + ret =3D run_bpftool_command(cmd); + if (ret) + return ret; + ret =3D snprintf(cmd, MAP_NAME_MAX_LEN, "cat %s", iter_pin_path); + if (ret < 0) + goto cleanup; + ret =3D system(cmd); + +cleanup: + unlink(iter_pin_path); + return ret; +} + +static int create_inner_map(void) +{ + char cmd[MAX_BPFTOOL_CMD_LEN]; + int ret =3D 0; + + ret =3D 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 =3D 0; + + ret =3D snprintf( + cmd, MAX_BPFTOOL_CMD_LEN, + "map create %s/%s type hash_of_maps key 4 value 4 entries 2 name %s inne= r_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 =3D snprintf(pin_path, PATH_MAX_LEN, "%s/%s", BPFFS_PIN_DIR, + map_name); + if (ret >=3D 0) + unlink(pin_path); +} + +static int add_outer_map_entry(int key) +{ + char cmd[MAX_BPFTOOL_CMD_LEN]; + int ret =3D 0; + + ret =3D 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 =3D snprintf(map_handle, MAP_NAME_MAX_LEN, "pinned %s", + desc->pin_path); + else + ret =3D snprintf(map_handle, MAP_NAME_MAX_LEN, "name %s", + desc->map_name); + if (!ASSERT_GT(ret, 0, "format map handle")) + return; + + ret =3D lookup_map_value(map_handle); + ASSERT_OK(ret, "read map value"); + + ret =3D read_map_btf_data(map_handle); + ASSERT_OK(ret, "read map btf data"); + + ret =3D write_map_value(map_handle); + ASSERT_OK(desc->write_must_fail ? !ret : ret, "write map value"); + + ret =3D 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 =3D snprintf(iter_pin_path, PATH_MAX_LEN, "%s/iter", BPFFS_PIN_DIR); + if (ASSERT_GT(ret, 0, "format iter pin path")) { + ret =3D 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[] =3D { + { + .name =3D "unprotected_unpinned", + .protection =3D UNPROTECTED, + .map_name =3D UNPROTECTED_MAP_NAME, + .pinned =3D false, + .write_must_fail =3D false, + }, + { + .name =3D "unprotected_pinned", + .protection =3D UNPROTECTED, + .map_name =3D UNPROTECTED_MAP_NAME, + .pinned =3D true, + .write_must_fail =3D false, + }, + { + .name =3D "protected_unpinned", + .protection =3D PROTECTED, + .map_name =3D UNPROTECTED_MAP_NAME, + .pinned =3D false, + .write_must_fail =3D true, + }, + { + .name =3D "protected_pinned", + .protection =3D PROTECTED, + .map_name =3D UNPROTECTED_MAP_NAME, + .pinned =3D true, + .write_must_fail =3D true, + } +}; + +static const size_t tests_count =3D ARRAY_SIZE(tests); + +void test_maps_access(void) +{ + struct security_bpf_map *skel; + struct test_desc *current; + int i; + + skel =3D general_setup(); + if (!ASSERT_OK_PTR(skel, "prepare programs")) + goto cleanup; + + for (i =3D 0; i < tests_count; i++) { + current =3D &tests[i]; + test__start_subtest(current->name); + if (ASSERT_OK(test_setup(skel, current), "subtest setup")) + test_basic_access(current); + test_cleanup(current); + } + test__start_subtest("nested_maps"); + test_create_nested_maps(); + test__start_subtest("btf_list"); + test_btf_list(); + +cleanup: + general_cleanup(skel); +} + --=20 2.52.0 From nobody Sun Feb 8 07:07:34 2026 Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A72AD37F744 for ; Wed, 14 Jan 2026 08:59:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.171.202.116 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381190; cv=none; b=mHPnrv+yf+ht1JEkiFP+c+OA1ddu0x1S2Mjuf4hSWAobLYgFGYuHw2Z24/qIOp4+V127Br0Jy2UJr7nEVc6rgq34Uw8iRyt+Yn4PQZB91FT8CrD3RvrcJrW0eE41rqV/uMFZTcsB+n1gomfQxDaX4y0LDQ1HBcNl/0sGWW25bTg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768381190; c=relaxed/simple; bh=+QQgEf1Y/XnGVu++CXloRLQoIT5Nyn8siqamkif/2cw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TdrQhHS2Wm7qVpXHaI4tKvMM8T3r7fRs8gzQsdFnCzbx7MIv87ey1WQ/Lc5QMaUqJKY12/bNIA2PFd9swn9DJ5NLy+VbdrusrA8vHok2HBYheS6bvcyOX/4LsJmyPmwrV9FbTfvFMubaXpYI/UnfNALpG1EqsDuQXIPKTEU2Nl4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=D94Rtc98; arc=none smtp.client-ip=185.171.202.116 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="D94Rtc98" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id 85719C1F142; Wed, 14 Jan 2026 08:59:16 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 2A2616074A; Wed, 14 Jan 2026 08:59:43 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 496BC103C837F; Wed, 14 Jan 2026 09:59:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1768381182; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=dUCnkB6VNpqbfeiNJ2XDuKKIu5INNxLaLuHfVEMFXL0=; b=D94Rtc98NOGhG/x2cHAL8V3P7+NOOvheePy8m7d7hmA0fDW83eNWjRIudJhPFB4GZyjsJd smnht+kRopDPe+yzbvGUdWcaWKJ4IV3iSB4zEvs1Vft0Z0MUzCC5svyibg+Eb+M53jpOdi OtxGKH8QvD/iUqU4nzDMbRhIPAJVSHnevWN9bJHgOmLUoqV8gdrmTiwZl2/Iu37HkOqOQ8 4mwBxSjVOeMnYMAnnMDEM+W0XmKKW6Y98WWJFxCqetKhHXBzAXybxeqWfkVcEEDvTRBNDl UWk+yqq5Z7BxXDHiv0LpziAQoZXtgcVGEB2dWnXeoY+ZXe+FY94L+wT+sWCl/w== From: =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= Date: Wed, 14 Jan 2026 09:59:15 +0100 Subject: [PATCH bpf-next 4/4] selftests/bpf: remove converted bpftool test scripts Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260114-bpftool-tests-v1-4-cfab1cc9beaf@bootlin.com> References: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> In-Reply-To: <20260114-bpftool-tests-v1-0-cfab1cc9beaf@bootlin.com> To: Andrii Nakryiko , Eduard Zingerman , Alexei Starovoitov , Daniel Borkmann , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan Cc: ebpf@linuxfoundation.org, Bastien Curutchet , Thomas Petazzoni , linux-kernel@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, =?utf-8?q?Alexis_Lothor=C3=A9_=28eBPF_Foundation=29?= X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 Now that test_bpftool_map.sh and test_bpftool_metadata.sh are integrated into test_bpftool runner, remove those. Signed-off-by: Alexis Lothor=C3=A9 (eBPF Foundation) Acked-by: Quentin Monnet --- tools/testing/selftests/bpf/Makefile | 2 - tools/testing/selftests/bpf/test_bpftool_map.sh | 398 -----------------= ---- .../testing/selftests/bpf/test_bpftool_metadata.sh | 85 ----- 3 files changed, 485 deletions(-) diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests= /bpf/Makefile index a1fe94efa53c..daa25d6c8fbb 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -109,8 +109,6 @@ TEST_PROGS :=3D 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 diff --git a/tools/testing/selftests/bpf/test_bpftool_map.sh b/tools/testin= g/selftests/bpf/test_bpftool_map.sh deleted file mode 100755 index 515b1df0501e..000000000000 --- a/tools/testing/selftests/bpf/test_bpftool_map.sh +++ /dev/null @@ -1,398 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 - -# Kselftest framework requirement - SKIP code is 4. -ksft_skip=3D4 - -TESTNAME=3D"bpftool_map" -BPF_FILE=3D"security_bpf_map.bpf.o" -BPF_ITER_FILE=3D"bpf_iter_map_elem.bpf.o" -PROTECTED_MAP_NAME=3D"prot_map" -NOT_PROTECTED_MAP_NAME=3D"not_prot_map" -BPF_FS_TMP_PARENT=3D"/tmp" -BPF_FS_PARENT=3D$(awk '$3 =3D=3D "bpf" {print $2; exit}' /proc/mounts) -BPF_FS_PARENT=3D${BPF_FS_PARENT:-$BPF_FS_TMP_PARENT} -# bpftool will mount bpf file system under BPF_DIR if it is not mounted -# under BPF_FS_PARENT. -BPF_DIR=3D"$BPF_FS_PARENT/test_$TESTNAME" -SCRIPT_DIR=3D$(dirname $(realpath "$0")) -BPF_FILE_PATH=3D"$SCRIPT_DIR/$BPF_FILE" -BPF_ITER_FILE_PATH=3D"$SCRIPT_DIR/$BPF_ITER_FILE" -BPFTOOL_PATH=3D"bpftool" -# Assume the script is located under tools/testing/selftests/bpf/ -KDIR_ROOT_DIR=3D$(realpath "$SCRIPT_DIR"/../../../../) - -_cleanup() -{ - set +eu - - # If BPF_DIR is a mount point this will not remove the mount point itself. - [ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2> /dev/null - - # Unmount if BPF filesystem was temporarily created. - if [ "$BPF_FS_PARENT" =3D "$BPF_FS_TMP_PARENT" ]; then - # A loop and recursive unmount are required as bpftool might - # create multiple mounts. For example, a bind mount of the directory - # to itself. The bind mount is created to change mount propagation - # flags on an actual mount point. - max_attempts=3D3 - attempt=3D0 - while mountpoint -q "$BPF_DIR" && [ $attempt -lt $max_attempts ]; do - umount -R "$BPF_DIR" 2>/dev/null - attempt=3D$((attempt+1)) - done - - # The directory still exists. Remove it now. - [ -d "$BPF_DIR" ] && rm -rf "$BPF_DIR" 2>/dev/null - fi -} - -cleanup_skip() -{ - echo "selftests: $TESTNAME [SKIP]" - _cleanup - - exit $ksft_skip -} - -cleanup() -{ - if [ "$?" =3D 0 ]; then - echo "selftests: $TESTNAME [PASS]" - else - echo "selftests: $TESTNAME [FAILED]" - fi - _cleanup -} - -check_root_privileges() { - if [ $(id -u) -ne 0 ]; then - echo "Need root privileges" - exit $ksft_skip - fi -} - -# Function to verify bpftool path. -# Parameters: -# $1: bpftool path -verify_bpftool_path() { - local bpftool_path=3D"$1" - if ! "$bpftool_path" version > /dev/null 2>&1; then - echo "Could not run test without bpftool" - exit $ksft_skip - fi -} - -# Function to verify BTF support. -# The test requires BTF support for fmod_ret programs. -verify_btf_support() { - if [ ! -f /sys/kernel/btf/vmlinux ]; then - echo "Could not run test without BTF support" - exit $ksft_skip - fi -} - -# Function to initialize map entries with keys [0..2] and values set to 0. -# Parameters: -# $1: Map name -# $2: bpftool path -initialize_map_entries() { - local map_name=3D"$1" - local bpftool_path=3D"$2" - - for key in 0 1 2; do - "$bpftool_path" map update name "$map_name" key $key 0 0 0 value 0 0 0 $= key - done -} - -# Test read access to the map. -# Parameters: -# $1: Name command (name/pinned) -# $2: Map name -# $3: bpftool path -# $4: key -access_for_read() { - local name_cmd=3D"$1" - local map_name=3D"$2" - local bpftool_path=3D"$3" - local key=3D"$4" - - # Test read access to the map. - if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/n= ull; then - echo " Read access to $key in $map_name failed" - exit 1 - fi - - # Test read access to map's BTF data. - if ! "$bpftool_path" btf dump map "$name_cmd" "$map_name" 1>/dev/null; th= en - echo " Read access to $map_name for BTF data failed" - exit 1 - fi -} - -# Test write access to the map. -# Parameters: -# $1: Name command (name/pinned) -# $2: Map name -# $3: bpftool path -# $4: key -# $5: Whether write should succeed (true/false) -access_for_write() { - local name_cmd=3D"$1" - local map_name=3D"$2" - local bpftool_path=3D"$3" - local key=3D"$4" - local write_should_succeed=3D"$5" - local value=3D"1 1 1 1" - - if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \ - $value 2>/dev/null; then - if [ "$write_should_succeed" =3D "false" ]; then - echo " Write access to $key in $map_name succeeded but should have fail= ed" - exit 1 - fi - else - if [ "$write_should_succeed" =3D "true" ]; then - echo " Write access to $key in $map_name failed but should have succeed= ed" - exit 1 - fi - fi -} - -# Test entry deletion for the map. -# Parameters: -# $1: Name command (name/pinned) -# $2: Map name -# $3: bpftool path -# $4: key -# $5: Whether write should succeed (true/false) -access_for_deletion() { - local name_cmd=3D"$1" - local map_name=3D"$2" - local bpftool_path=3D"$3" - local key=3D"$4" - local write_should_succeed=3D"$5" - local value=3D"1 1 1 1" - - # Test deletion by key for the map. - # Before deleting, check the key exists. - if ! "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/n= ull; then - echo " Key $key does not exist in $map_name" - exit 1 - fi - - # Delete by key. - if "$bpftool_path" map delete "$name_cmd" "$map_name" key $key 2>/dev/nul= l; then - if [ "$write_should_succeed" =3D "false" ]; then - echo " Deletion for $key in $map_name succeeded but should have failed" - exit 1 - fi - else - if [ "$write_should_succeed" =3D "true" ]; then - echo " Deletion for $key in $map_name failed but should have succeeded" - exit 1 - fi - fi - - # After deleting, check the entry existence according to the expected sta= tus. - if "$bpftool_path" map lookup "$name_cmd" "$map_name" key $key 1>/dev/nul= l; then - if [ "$write_should_succeed" =3D "true" ]; then - echo " Key $key for $map_name was not deleted but should have been dele= ted" - exit 1 - fi - else - if [ "$write_should_succeed" =3D "false" ]; then - echo "Key $key for $map_name was deleted but should have not been delet= ed" - exit 1 - fi - fi - - # Test creation of map's deleted entry, if deletion was successful. - # Otherwise, the entry exists. - if "$bpftool_path" map update "$name_cmd" "$map_name" key $key value \ - $value 2>/dev/null; then - if [ "$write_should_succeed" =3D "false" ]; then - echo " Write access to $key in $map_name succeeded after deletion attem= pt but should have failed" - exit 1 - fi - else - if [ "$write_should_succeed" =3D "true" ]; then - echo " Write access to $key in $map_name failed after deletion attempt = but should have succeeded" - exit 1 - fi - fi -} - -# Test map elements iterator. -# Parameters: -# $1: Name command (name/pinned) -# $2: Map name -# $3: bpftool path -# $4: BPF_DIR -# $5: bpf iterator object file path -iterate_map_elem() { - local name_cmd=3D"$1" - local map_name=3D"$2" - local bpftool_path=3D"$3" - local bpf_dir=3D"$4" - local bpf_file=3D"$5" - local pin_path=3D"$bpf_dir/map_iterator" - - "$bpftool_path" iter pin "$bpf_file" "$pin_path" map "$name_cmd" "$map_na= me" - if [ ! -f "$pin_path" ]; then - echo " Failed to pin iterator to $pin_path" - exit 1 - fi - - cat "$pin_path" 1>/dev/null - rm "$pin_path" 2>/dev/null -} - -# Function to test map access with configurable write expectations -# Parameters: -# $1: Name command (name/pinned) -# $2: Map name -# $3: bpftool path -# $4: key for rw -# $5: key to delete -# $6: Whether write should succeed (true/false) -# $7: BPF_DIR -# $8: bpf iterator object file path -access_map() { - local name_cmd=3D"$1" - local map_name=3D"$2" - local bpftool_path=3D"$3" - local key_for_rw=3D"$4" - local key_to_del=3D"$5" - local write_should_succeed=3D"$6" - local bpf_dir=3D"$7" - local bpf_iter_file_path=3D"$8" - - access_for_read "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw" - access_for_write "$name_cmd" "$map_name" "$bpftool_path" "$key_for_rw" \ - "$write_should_succeed" - access_for_deletion "$name_cmd" "$map_name" "$bpftool_path" "$key_to_del"= \ - "$write_should_succeed" - iterate_map_elem "$name_cmd" "$map_name" "$bpftool_path" "$bpf_dir" \ - "$bpf_iter_file_path" -} - -# Function to test map access with configurable write expectations -# Parameters: -# $1: Map name -# $2: bpftool path -# $3: BPF_DIR -# $4: Whether write should succeed (true/false) -# $5: bpf iterator object file path -test_map_access() { - local map_name=3D"$1" - local bpftool_path=3D"$2" - local bpf_dir=3D"$3" - local pin_path=3D"$bpf_dir/${map_name}_pinned" - local write_should_succeed=3D"$4" - local bpf_iter_file_path=3D"$5" - - # Test access to the map by name. - access_map "name" "$map_name" "$bpftool_path" "0 0 0 0" "1 0 0 0" \ - "$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path" - - # Pin the map to the BPF filesystem - "$bpftool_path" map pin name "$map_name" "$pin_path" - if [ ! -e "$pin_path" ]; then - echo " Failed to pin $map_name" - exit 1 - fi - - # Test access to the pinned map. - access_map "pinned" "$pin_path" "$bpftool_path" "0 0 0 0" "2 0 0 0" \ - "$write_should_succeed" "$bpf_dir" "$bpf_iter_file_path" -} - -# Function to test map creation and map-of-maps -# Parameters: -# $1: bpftool path -# $2: BPF_DIR -test_map_creation_and_map_of_maps() { - local bpftool_path=3D"$1" - local bpf_dir=3D"$2" - local outer_map_name=3D"outer_map_tt" - local inner_map_name=3D"inner_map_tt" - - "$bpftool_path" map create "$bpf_dir/$inner_map_name" type array key 4 \ - value 4 entries 4 name "$inner_map_name" - if [ ! -f "$bpf_dir/$inner_map_name" ]; then - echo " Failed to create inner map file at $bpf_dir/$outer_map_name" - return 1 - fi - - "$bpftool_path" map create "$bpf_dir/$outer_map_name" type hash_of_maps \ - key 4 value 4 entries 2 name "$outer_map_name" inner_map name "$inner_ma= p_name" - if [ ! -f "$bpf_dir/$outer_map_name" ]; then - echo " Failed to create outer map file at $bpf_dir/$outer_map_name" - return 1 - fi - - # Add entries to the outer map by name and by pinned path. - "$bpftool_path" map update pinned "$bpf_dir/$outer_map_name" key 0 0 0 0 \ - value pinned "$bpf_dir/$inner_map_name" - "$bpftool_path" map update name "$outer_map_name" key 1 0 0 0 value \ - name "$inner_map_name" - - # The outer map should be full by now. - # The following map update command is expected to fail. - if "$bpftool_path" map update name "$outer_map_name" key 2 0 0 0 value na= me \ - "$inner_map_name" 2>/dev/null; then - echo " Update for $outer_map_name succeeded but should have failed" - exit 1 - fi -} - -# Function to test map access with the btf list command -# Parameters: -# $1: bpftool path -test_map_access_with_btf_list() { - local bpftool_path=3D"$1" - - # The btf list command iterates over maps for - # loaded BPF programs. - if ! "$bpftool_path" btf list 1>/dev/null; then - echo " Failed to access btf data" - exit 1 - fi -} - -set -eu - -trap cleanup_skip EXIT - -check_root_privileges - -verify_bpftool_path "$BPFTOOL_PATH" - -verify_btf_support - -trap cleanup EXIT - -# Load and attach the BPF programs to control maps access. -"$BPFTOOL_PATH" prog loadall "$BPF_FILE_PATH" "$BPF_DIR" autoattach - -initialize_map_entries "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH" -initialize_map_entries "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH" - -# Activate the map protection mechanism. Protection status is controlled -# by a value stored in the prot_status_map at index 0. -"$BPFTOOL_PATH" map update name prot_status_map key 0 0 0 0 value 1 0 0 0 - -# Test protected map (write should fail). -test_map_access "$PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "false" \ - "$BPF_ITER_FILE_PATH" - -# Test not protected map (write should succeed). -test_map_access "$NOT_PROTECTED_MAP_NAME" "$BPFTOOL_PATH" "$BPF_DIR" "true= " \ - "$BPF_ITER_FILE_PATH" - -test_map_creation_and_map_of_maps "$BPFTOOL_PATH" "$BPF_DIR" - -test_map_access_with_btf_list "$BPFTOOL_PATH" - -exit 0 diff --git a/tools/testing/selftests/bpf/test_bpftool_metadata.sh b/tools/t= esting/selftests/bpf/test_bpftool_metadata.sh deleted file mode 100755 index b5520692f41b..000000000000 --- a/tools/testing/selftests/bpf/test_bpftool_metadata.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 - -# Kselftest framework requirement - SKIP code is 4. -ksft_skip=3D4 - -BPF_FILE_USED=3D"metadata_used.bpf.o" -BPF_FILE_UNUSED=3D"metadata_unused.bpf.o" - -TESTNAME=3Dbpftool_metadata -BPF_FS=3D$(awk '$3 =3D=3D "bpf" {print $2; exit}' /proc/mounts) -BPF_DIR=3D$BPF_FS/test_$TESTNAME - -_cleanup() -{ - set +e - rm -rf $BPF_DIR 2> /dev/null -} - -cleanup_skip() -{ - echo "selftests: $TESTNAME [SKIP]" - _cleanup - - exit $ksft_skip -} - -cleanup() -{ - if [ "$?" =3D 0 ]; then - echo "selftests: $TESTNAME [PASS]" - else - echo "selftests: $TESTNAME [FAILED]" - fi - _cleanup -} - -if [ $(id -u) -ne 0 ]; then - echo "selftests: $TESTNAME [SKIP] Need root privileges" - exit $ksft_skip -fi - -if [ -z "$BPF_FS" ]; then - echo "selftests: $TESTNAME [SKIP] Could not run test without bpffs mounte= d" - exit $ksft_skip -fi - -if ! bpftool version > /dev/null 2>&1; then - echo "selftests: $TESTNAME [SKIP] Could not run test without bpftool" - exit $ksft_skip -fi - -set -e - -trap cleanup_skip EXIT - -mkdir $BPF_DIR - -trap cleanup EXIT - -bpftool prog load $BPF_FILE_UNUSED $BPF_DIR/unused - -METADATA_PLAIN=3D"$(bpftool prog)" -echo "$METADATA_PLAIN" | grep 'a =3D "foo"' > /dev/null -echo "$METADATA_PLAIN" | grep 'b =3D 1' > /dev/null - -bpftool prog --json | grep '"metadata":{"a":"foo","b":1}' > /dev/null - -bpftool map | grep 'metadata.rodata' > /dev/null - -rm $BPF_DIR/unused - -bpftool prog load $BPF_FILE_USED $BPF_DIR/used - -METADATA_PLAIN=3D"$(bpftool prog)" -echo "$METADATA_PLAIN" | grep 'a =3D "bar"' > /dev/null -echo "$METADATA_PLAIN" | grep 'b =3D 2' > /dev/null - -bpftool prog --json | grep '"metadata":{"a":"bar","b":2}' > /dev/null - -bpftool map | grep 'metadata.rodata' > /dev/null - -rm $BPF_DIR/used - -exit 0 --=20 2.52.0