From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oo1-f47.google.com (mail-oo1-f47.google.com [209.85.161.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E62B6382F13 for ; Wed, 7 Oct 2026 19:59:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.47 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791403191; cv=none; b=nYNnfRbOoOM7U1MszRMHLJ1uj0QB+xueyBKDVy2yHtKuyxbi8Brn6jt5Ow51njebbOC4iihroH1uaYHOg9e8XyqPmueIGufF+ETuPhj2fInselI3M2HklDT96LriS+40HIzwtAjNAA29OLdEN8cSJOxT6VdVVTrNX3gKdfE1heU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791403191; c=relaxed/simple; bh=qgOqAIrzkFVv3pR41i4HnBt20Mw4cWVHW1Yivzd/Wx0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jkf4siUTPsiNw287H/lkNX9VtnKalRg+joO8KFBtsjrD+dPSSkstmb6hg8wd5CWHICky9otwDDBtSuPkokjM2L8gud/f4FLCk3upPQ8QhV6q1/UfvZjbb3UxvjLIOIyF/cqbyL6k0dFYWCI1f8FapOkc7CHdjbs30qLpwiH4ais= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=hammerspace.com; spf=pass smtp.mailfrom=hammerspace.com; dkim=pass (2048-bit key) header.d=hammerspace.com header.i=@hammerspace.com header.b=QOX2Ajx7; arc=none smtp.client-ip=209.85.161.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=hammerspace.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=hammerspace.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=hammerspace.com header.i=@hammerspace.com header.b="QOX2Ajx7" Received: by mail-oo1-f47.google.com with SMTP id 006d021491bc7-6deb848ddd7so3152307eaf.0 for ; Wed, 07 Oct 2026 12:59:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=hammerspace.com; s=google; t=1791403189; x=1792007989; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=owCAAy82Opl9I0OXoUtQLC3+594nC+cfFTkiDVH7AB8=; b=QOX2Ajx7/9WZNf10PN17G4P7FU+UHEJR94i5lBnwO7tzlUV9iq3nHfqcaoy7ruHvSg CbDKD1fOTcwtgIDMqXdzGjPOTPWNWBXvuBYBOLJWZNX5xuWX2RgDsvobIA3DW2axk/HU faZU6qRj9BAKQwqJ3DBxFvoMlAuoJg0rvYsIB1/7BHzGE3sFqVWofnldO/Gg1DMeJ0lG x3UAepsdI3EoKmfOPp9jQiL/cSw2WMhOo4/YcxACNb+X8ktJNxXI+X4Yf6WXVY8txSmt /7NTKAud6wNPjMjqxvAPYggHHKJxEqmQyZqx7JXVQDm3bZ7mF1i7AtNDpZiMa2L1dadI HWsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791403189; x=1792007989; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=owCAAy82Opl9I0OXoUtQLC3+594nC+cfFTkiDVH7AB8=; b=HbfMpTY0fKvzrxkBfoo95V9jMXyWZTMOsKoQH8kHjB+47RsXJBrOEjiMtc/MrH5Bkm Srj+0GsqwHY8sxnS6pjHPXkqbPHP+6GbUzfZBeLImsfNNUUei4RB7Q4HwKqokgC/WT5p 6M08I0olS3my1zTsBv8UNTrR6P04TUX6yZWWHRVYxeuNOFAYdEopovCAPOlIPHxZoy88 Bpn4v9Aw3LVAqEUmz41KTrMPcIZPKTylytffIKN2RaZnKdtaTRTZQLHa7sj4So80/qme ItfibdrwoZY75pG6EQ6sfGvOj7jvw1Y4vldINQHWYF2ec1dAupm+W2oBIH2sLtWfXv2X RRkg== X-Forwarded-Encrypted: i=1; AKwUvBx91+pkxVXVvSkvbsAo5DHul2Ek+n3TPKTcO9VpQQ5L8j1oUy6z0DOIiEzwgO4DjlzGrQA=@vger.kernel.org X-Gm-Message-State: AFuF++kIOfK6lxhrNpJqLUR7nyb+JqKLjZ4yQDDnMEZlft/QKBve1ACt DOBrtHTolqude4EbdjZGP6TDaxS0fzaGcO5RhPZSnPvEVVM56UVVMaXU9MmN4JJnesA= X-Gm-Gg: AYBFou22tYzpxQbXBc+thFVB6TF2AL1q5zwA/NLfuczwsL4BLusdmYp18DIQZKN/hEO Row1xzQt+qeA38YTGgxbH0QS1CzZAHhC2rMv89yWX6SiIjJie38NDQYo061y4BdnqCk3p2V/NY/ sXesRMe57dqTaHTvzbVacF/lI2cRoEKgLjrs9Rno2a2XxiIpanKt10CPQ3lhYLer3oCBktLlppR BpH36Fo8+RgV9nSwwOU7SotYhhPMaVdUsvggqBuBOIZ+dt936b8zy3nky1cyBLyvqHm68JCvF2q Awn+B+NAwwRgMrFO/OW2y0OTUd/8Zl0cN3UjDTfpVj3iFLa2mGbhGfuhKp47q17j7vkA/LATr0I UBCz4JfFundiZO1yHxkZ7JdkOIZ8b8fzfUIN8+A7yNVQxcw9XTtn+Q2YmuoCljFYiZ3yecERqM/ GJRC9rUbrtIKJWb6+lHnJsSQwEEEAxR+iDmUaqS8oX1j1t/0NGVKPDoFfgEZ0kIKaH+mj4Zz5v8 9TFStDmk7wmx6hNsOi956xcss2YSt3YTMpRchbnBg== X-Received: by 2002:a05:6820:4cc4:b0:6cd:3ffc:e330 with SMTP id 006d021491bc7-6e7a7c25072mr3182451eaf.78.1791403188654; Wed, 07 Oct 2026 12:59:48 -0700 (PDT) Received: from bcodding.csb.hammerspace.com ([66.97.168.37]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4a274c9c520sm1084285fac.16.2026.10.07.12.59.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Oct 2026 12:59:48 -0700 (PDT) From: Benjamin Coddington To: Chuck Lever , Jeff Layton , NeilBrown Cc: linux-nfs@vger.kernel.org, Daire Byrne , bpf@vger.kernel.org, Martin KaFai Lau Subject: [PATCH RFC v2 7/9] selftests/bpf: add svc_classifier tests Date: Wed, 7 Oct 2026 15:59:32 -0400 Message-ID: X-Mailer: git-send-email 2.53.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Benjamin Coddington A classifier for RPC service transports that looks the peer address up in an LPM trie keyed by {family, address}, the reference for the sunrpc svc_classifier struct_ops, and tests of the attach model: a second attach in the same network namespace fails with -EBUSY, a link update replaces the classifier, a detached instance can be attached again, a classifier attached inside a namespace does not occupy the initial one, and a namespace that exits leaves its link to detach into nothing. Skipped where the struct_ops is not registered. Signed-off-by: Benjamin Coddington --- tools/testing/selftests/bpf/config | 2 + .../selftests/bpf/prog_tests/svc_classifier.c | 104 ++++++++++++++++++ .../selftests/bpf/progs/bpf_svc_classifier.c | 80 ++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/svc_classifier.c create mode 100644 tools/testing/selftests/bpf/progs/bpf_svc_classifier.c diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index ea7044f30adc..2cb7080d7da2 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -114,6 +114,7 @@ CONFIG_IP_NF_IPTABLES=y CONFIG_IP6_NF_IPTABLES=y CONFIG_IP6_NF_FILTER=y CONFIG_NF_NAT=y +CONFIG_NFS_FS=y CONFIG_PACKET=y CONFIG_RC_CORE=y CONFIG_SAMPLES=y @@ -133,6 +134,7 @@ CONFIG_TCP_CONG_BBR=y CONFIG_INFINIBAND=y CONFIG_SMC=y CONFIG_SMC_HS_CTRL_BPF=y +CONFIG_SUNRPC_BPF_CLASSIFY=y CONFIG_DIBS=y CONFIG_DIBS_LO=y CONFIG_PM_WAKELOCKS=y diff --git a/tools/testing/selftests/bpf/prog_tests/svc_classifier.c b/tools/testing/selftests/bpf/prog_tests/svc_classifier.c new file mode 100644 index 000000000000..887af29b21c7 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/svc_classifier.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The svc_classifier struct_ops: one per network namespace, bound to + * the attaching task's namespace; replace by link update; detach by + * link close; a namespace that exits leaves its link empty. + */ +#include +#include "bpf_svc_classifier.skel.h" + +static struct bpf_svc_classifier *load(void) +{ + struct bpf_svc_classifier *skel; + + skel = bpf_svc_classifier__open_and_load(); + if (!skel && (errno == ENOENT || errno == EINVAL || errno == EOPNOTSUPP)) { + test__skip(); + return NULL; + } + ASSERT_OK_PTR(skel, "bpf_svc_classifier__open_and_load"); + return skel; +} + +static void test_attach(void) +{ + struct bpf_svc_classifier *one, *two; + struct bpf_link *link, *busy; + int err; + + one = load(); + if (!one) + return; + two = load(); + if (!two) + goto out_one; + + link = bpf_map__attach_struct_ops(one->maps.prefix); + if (!ASSERT_OK_PTR(link, "attach")) + goto out_two; + + busy = bpf_map__attach_struct_ops(two->maps.prefix); + ASSERT_ERR_PTR(busy, "second attach"); + ASSERT_EQ(errno, EBUSY, "second attach errno"); + + err = bpf_link__update_map(link, two->maps.prefix); + ASSERT_OK(err, "replace"); + + /* the replaced instance can be attached again once released */ + bpf_link__destroy(link); + link = bpf_map__attach_struct_ops(one->maps.prefix); + ASSERT_OK_PTR(link, "attach after detach"); + bpf_link__destroy(link); +out_two: + bpf_svc_classifier__destroy(two); +out_one: + bpf_svc_classifier__destroy(one); +} + +static void test_netns(void) +{ + struct bpf_svc_classifier *inner, *outer; + struct netns_obj *ns; + struct bpf_link *link, *link2; + + inner = load(); + if (!inner) + return; + outer = load(); + if (!outer) + goto out_inner; + + ns = netns_new("svc_classifier_ns", true); + if (!ASSERT_OK_PTR(ns, "netns_new")) + goto out_outer; + + /* attached inside the namespace */ + link = bpf_map__attach_struct_ops(inner->maps.prefix); + if (!ASSERT_OK_PTR(link, "attach in netns")) + goto out_ns; + + /* the initial namespace is free */ + netns_free(ns); + ns = NULL; + link2 = bpf_map__attach_struct_ops(outer->maps.prefix); + ASSERT_OK_PTR(link2, "attach in init_net"); + bpf_link__destroy(link2); + + /* the link of the dead namespace detaches into nothing */ + bpf_link__destroy(link); +out_ns: + if (ns) + netns_free(ns); +out_outer: + bpf_svc_classifier__destroy(outer); +out_inner: + bpf_svc_classifier__destroy(inner); +} + +void test_svc_classifier(void) +{ + if (test__start_subtest("attach")) + test_attach(); + if (test__start_subtest("netns")) + test_netns(); +} diff --git a/tools/testing/selftests/bpf/progs/bpf_svc_classifier.c b/tools/testing/selftests/bpf/progs/bpf_svc_classifier.c new file mode 100644 index 000000000000..bae9d115ee74 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/bpf_svc_classifier.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A svc_classifier for RPC service transports: the peer address is looked + * up in an LPM trie keyed by {family, address}; a hit returns the class + * word stored there, a miss returns 0, the anonymous client. A key of + * prefixlen 0 is the default for every family, 8 the default for one. + */ +#include +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +#define AF_INET 2 +#define AF_INET6 10 + +#define SVC_CLASSIFIER_NAME_MAX 16 + +struct svc_xprt___local { + struct __kernel_sockaddr_storage xpt_remote; +} __attribute__((preserve_access_index)); + +struct svc_classifier___local { + char name[SVC_CLASSIFIER_NAME_MAX]; + __u32 (*classify)(const struct svc_xprt___local *xprt); +}; + +struct prefix_key { + __u32 prefixlen; /* 8 (the family byte) + address bits */ + __u8 family; + __u8 addr[16]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_LPM_TRIE); + __type(key, struct prefix_key); + __type(value, __u32); + __uint(max_entries, 1024); + __uint(map_flags, BPF_F_NO_PREALLOC); +} prefixes SEC(".maps"); + +__u64 classified; + +SEC("struct_ops/classify") +__u32 BPF_PROG(prefix_classify, const struct svc_xprt___local *xprt) +{ + struct __kernel_sockaddr_storage ss; + struct prefix_key key = {}; + __u32 *class; + + if (bpf_core_read(&ss, sizeof(ss), &xprt->xpt_remote)) + return 0; + + key.family = ss.ss_family; + switch (ss.ss_family) { + case AF_INET: + /* struct sockaddr_in: port at 2, address at 4 */ + __builtin_memcpy(key.addr, &ss.__data[2], 4); + key.prefixlen = 8 + 32; + break; + case AF_INET6: + /* struct sockaddr_in6: port at 2, flowinfo at 4, address at 8 */ + __builtin_memcpy(key.addr, &ss.__data[6], 16); + key.prefixlen = 8 + 128; + break; + default: + return 0; + } + + __sync_fetch_and_add(&classified, 1); + class = bpf_map_lookup_elem(&prefixes, &key); + return class ? *class : 0; +} + +SEC(".struct_ops.link") +struct svc_classifier___local prefix = { + .name = "prefix", + .classify = (void *)prefix_classify, +}; -- 2.53.0