Skip to content

Commit 2ff7054

Browse files
q2venKernel Patches Daemon
authored andcommitted
selftest: bpf: Add test for SK_BPF_MEMCG_SOCK_ISOLATED.
The test does the following for IPv4/IPv6 x TCP/UDP sockets with/without BPF prog. 1. Create socket pairs 2. Send a bunch of data that requires more than 256 pages 3. Read memory_allocated from the 3rd column in /proc/net/protocols 4. Check if unread data is charged to memory_allocated If BPF prog is attached, memory_allocated should not be changed, but we allow a small error (up to 10 pages) in case other processes on the host use some amounts of TCP/UDP memory. At 2., the test actually sends more than 1024 pages because the sysctl net.core.mem_pcpu_rsv is 256 is by default, which means 256 pages are buffered per cpu before reporting to sk->sk_prot->memory_allocated. BUF_SINGLE (1024) * NR_SEND (128) * NR_SOCKETS (64) / 4096 = 2048 pages When it's reduced to 1024 pages, the following assertion for the non-isolated case got flaky. ASSERT_GT(memory_allocated[1], memory_allocated[0] + 256, ...) We use kern_sync_rcu() for UDP because UDP recv queue is destroyed after 1 RCU grace period. The test takes ~0.5s on QEMU w/ KVM but takes 2s w/o KVM. # time ./test_progs -t sk_memcg #370/1 sk_memcg/TCP :OK #370/2 sk_memcg/UDP :OK #370/3 sk_memcg/TCPv6 :OK #370/4 sk_memcg/UDPv6 :OK #370 sk_memcg:OK Summary: 1/4 PASSED, 0 SKIPPED, 0 FAILED real 0m0.473s user 0m0.009s sys 0m0.201s Signed-off-by: Kuniyuki Iwashima <[email protected]>
1 parent 6277d9b commit 2ff7054

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright 2025 Google LLC */
3+
4+
#include <test_progs.h>
5+
#include "sk_memcg.skel.h"
6+
#include "network_helpers.h"
7+
8+
#define NR_SOCKETS 64
9+
#define NR_SEND 128
10+
#define BUF_SINGLE 1024
11+
#define BUF_TOTAL (BUF_SINGLE * NR_SEND)
12+
13+
struct test_case {
14+
char name[10]; /* protocols (%-9s) in /proc/net/protocols, see proto_seq_printf(). */
15+
int family;
16+
int type;
17+
int (*create_sockets)(struct test_case *test_case, int sk[], int len);
18+
};
19+
20+
static int tcp_create_sockets(struct test_case *test_case, int sk[], int len)
21+
{
22+
int server, i;
23+
24+
server = start_server(test_case->family, test_case->type, NULL, 0, 0);
25+
ASSERT_GE(server, 0, "start_server_str");
26+
27+
for (i = 0; i < len / 2; i++) {
28+
sk[i * 2] = connect_to_fd(server, 0);
29+
if (!ASSERT_GE(sk[i * 2], 0, "connect_to_fd"))
30+
return sk[i * 2];
31+
32+
sk[i * 2 + 1] = accept(server, NULL, NULL);
33+
if (!ASSERT_GE(sk[i * 2 + 1], 0, "accept"))
34+
return sk[i * 2 + 1];
35+
}
36+
37+
close(server);
38+
39+
return 0;
40+
}
41+
42+
static int udp_create_sockets(struct test_case *test_case, int sk[], int len)
43+
{
44+
int i, err, rcvbuf = BUF_TOTAL;
45+
46+
for (i = 0; i < len / 2; i++) {
47+
sk[i * 2] = start_server(test_case->family, test_case->type, NULL, 0, 0);
48+
if (!ASSERT_GE(sk[i * 2], 0, "start_server"))
49+
return sk[i * 2];
50+
51+
sk[i * 2 + 1] = connect_to_fd(sk[i * 2], 0);
52+
if (!ASSERT_GE(sk[i * 2 + 1], 0, "connect_to_fd"))
53+
return sk[i * 2 + 1];
54+
55+
err = connect_fd_to_fd(sk[i * 2], sk[i * 2 + 1], 0);
56+
if (!ASSERT_EQ(err, 0, "connect_fd_to_fd"))
57+
return err;
58+
59+
err = setsockopt(sk[i * 2], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int));
60+
if (!ASSERT_EQ(err, 0, "setsockopt(SO_RCVBUF)"))
61+
return err;
62+
63+
err = setsockopt(sk[i * 2 + 1], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int));
64+
if (!ASSERT_EQ(err, 0, "setsockopt(SO_RCVBUF)"))
65+
return err;
66+
}
67+
68+
return 0;
69+
}
70+
71+
static int get_memory_allocated(struct test_case *test_case)
72+
{
73+
long memory_allocated = -1;
74+
char *line = NULL;
75+
size_t unused;
76+
FILE *f;
77+
78+
f = fopen("/proc/net/protocols", "r");
79+
if (!ASSERT_OK_PTR(f, "fopen"))
80+
goto out;
81+
82+
while (getline(&line, &unused, f) != -1) {
83+
unsigned int unused_0;
84+
int unused_1;
85+
int ret;
86+
87+
if (strncmp(line, test_case->name, sizeof(test_case->name)))
88+
continue;
89+
90+
ret = sscanf(line + sizeof(test_case->name), "%4u %6d %6ld",
91+
&unused_0, &unused_1, &memory_allocated);
92+
ASSERT_EQ(ret, 3, "sscanf");
93+
break;
94+
}
95+
96+
ASSERT_NEQ(memory_allocated, -1, "get_memory_allocated");
97+
98+
free(line);
99+
fclose(f);
100+
out:
101+
return memory_allocated;
102+
}
103+
104+
static int check_isolated(struct test_case *test_case, bool isolated)
105+
{
106+
char buf[BUF_SINGLE] = {};
107+
long memory_allocated[2];
108+
int sk[NR_SOCKETS] = {};
109+
int err = -1, i, j;
110+
111+
memory_allocated[0] = get_memory_allocated(test_case);
112+
if (!ASSERT_GE(memory_allocated[0], 0, "memory_allocated[0]"))
113+
goto out;
114+
115+
err = test_case->create_sockets(test_case, sk, ARRAY_SIZE(sk));
116+
if (err)
117+
goto close;
118+
119+
/* Must allocate pages >= net.core.mem_pcpu_rsv */
120+
for (i = 0; i < ARRAY_SIZE(sk); i++) {
121+
for (j = 0; j < NR_SEND; j++) {
122+
int bytes = send(sk[i], buf, sizeof(buf), 0);
123+
124+
/* Avoid too noisy logs when something failed. */
125+
if (bytes != sizeof(buf))
126+
ASSERT_EQ(bytes, sizeof(buf), "send");
127+
}
128+
}
129+
130+
memory_allocated[1] = get_memory_allocated(test_case);
131+
if (!ASSERT_GE(memory_allocated[1], 0, "memory_allocated[1]"))
132+
goto close;
133+
134+
if (isolated) {
135+
ASSERT_LE(memory_allocated[1], memory_allocated[0] + 10, "isolated");
136+
} else {
137+
/* By default, net.core.mem_pcpu_rsv == 256 pages */
138+
ASSERT_GT(memory_allocated[1], memory_allocated[0] + 256, "not isolated");
139+
}
140+
141+
close:
142+
for (i = 0; i < ARRAY_SIZE(sk); i++)
143+
close(sk[i]);
144+
145+
if (test_case->type == SOCK_DGRAM) {
146+
/* Give 150ms to let RCU destruct UDP sockets */
147+
kern_sync_rcu();
148+
}
149+
out:
150+
return err;
151+
}
152+
153+
void run_test(struct test_case *test_case)
154+
{
155+
struct sk_memcg *skel;
156+
int cgroup, err;
157+
158+
skel = sk_memcg__open_and_load();
159+
if (!ASSERT_OK_PTR(skel, "open_and_load"))
160+
return;
161+
162+
cgroup = test__join_cgroup("/sk_memcg");
163+
if (!ASSERT_GE(cgroup, 0, "join_cgroup"))
164+
goto destroy_skel;
165+
166+
err = check_isolated(test_case, false);
167+
if (!ASSERT_EQ(err, 0, "test_isolated(false)"))
168+
goto close_cgroup;
169+
170+
skel->links.sock_create = bpf_program__attach_cgroup(skel->progs.sock_create, cgroup);
171+
if (!ASSERT_OK_PTR(skel->links.sock_create, "attach_cgroup(sock_create)"))
172+
goto close_cgroup;
173+
174+
err = check_isolated(test_case, true);
175+
ASSERT_EQ(err, 0, "test_isolated(false)");
176+
177+
close_cgroup:
178+
close(cgroup);
179+
destroy_skel:
180+
sk_memcg__destroy(skel);
181+
}
182+
183+
struct test_case test_cases[] = {
184+
{
185+
.name = "TCP ",
186+
.family = AF_INET,
187+
.type = SOCK_STREAM,
188+
.create_sockets = tcp_create_sockets,
189+
},
190+
{
191+
.name = "UDP ",
192+
.family = AF_INET,
193+
.type = SOCK_DGRAM,
194+
.create_sockets = udp_create_sockets,
195+
},
196+
{
197+
.name = "TCPv6 ",
198+
.family = AF_INET6,
199+
.type = SOCK_STREAM,
200+
.create_sockets = tcp_create_sockets,
201+
},
202+
{
203+
.name = "UDPv6 ",
204+
.family = AF_INET6,
205+
.type = SOCK_DGRAM,
206+
.create_sockets = udp_create_sockets,
207+
},
208+
};
209+
210+
void serial_test_sk_memcg(void)
211+
{
212+
int i;
213+
214+
for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
215+
test__start_subtest(test_cases[i].name);
216+
run_test(&test_cases[i]);
217+
}
218+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright 2025 Google LLC */
3+
4+
#include "bpf_tracing_net.h"
5+
#include <bpf/bpf_helpers.h>
6+
#include <errno.h>
7+
8+
SEC("cgroup/sock_create")
9+
int sock_create(struct bpf_sock *ctx)
10+
{
11+
u32 flags = SK_BPF_MEMCG_SOCK_ISOLATED;
12+
int err;
13+
14+
err = bpf_setsockopt(ctx, SOL_SOCKET, SK_BPF_MEMCG_FLAGS,
15+
&flags, sizeof(flags));
16+
if (err)
17+
goto err;
18+
19+
flags = 0;
20+
21+
err = bpf_getsockopt(ctx, SOL_SOCKET, SK_BPF_MEMCG_FLAGS,
22+
&flags, sizeof(flags));
23+
if (err)
24+
goto err;
25+
26+
if (flags != SK_BPF_MEMCG_SOCK_ISOLATED) {
27+
err = -EINVAL;
28+
goto err;
29+
}
30+
31+
return 1;
32+
33+
err:
34+
bpf_set_retval(err);
35+
return 0;
36+
}
37+
38+
char LICENSE[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)