Skip to content

Commit 6c55191

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 require more than 1000 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 the test is ran concurrently with other tests using TCP/UDP sockets. Signed-off-by: Kuniyuki Iwashima <[email protected]>
1 parent ad4abb5 commit 6c55191

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-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 128
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+
ASSERT_GT(memory_allocated[1], memory_allocated[0] + 1000, "not isolated");
138+
139+
close:
140+
for (i = 0; i < ARRAY_SIZE(sk); i++)
141+
close(sk[i]);
142+
143+
/* Let RCU destruct sockets */
144+
sleep(1);
145+
out:
146+
return err;
147+
}
148+
149+
void run_test(struct test_case *test_case)
150+
{
151+
struct sk_memcg *skel;
152+
int cgroup, err;
153+
154+
skel = sk_memcg__open_and_load();
155+
if (!ASSERT_OK_PTR(skel, "open_and_load"))
156+
return;
157+
158+
cgroup = test__join_cgroup("/sk_memcg");
159+
if (!ASSERT_GE(cgroup, 0, "join_cgroup"))
160+
goto destroy_skel;
161+
162+
err = check_isolated(test_case, false);
163+
if (!ASSERT_EQ(err, 0, "test_isolated(false)"))
164+
goto close_cgroup;
165+
166+
skel->links.sock_create = bpf_program__attach_cgroup(skel->progs.sock_create, cgroup);
167+
if (!ASSERT_OK_PTR(skel->links.sock_create, "attach_cgroup(sock_create)"))
168+
goto close_cgroup;
169+
170+
skel->links.skops_setsockopt = bpf_program__attach_cgroup(skel->progs.skops_setsockopt, cgroup);
171+
if (!ASSERT_OK_PTR(skel->links.skops_setsockopt, "attach_cgroup(skops_setsockopt)"))
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 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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
7+
void isolate_memcg(void *ctx)
8+
{
9+
int flags = SK_BPF_MEMCG_SOCK_ISOLATED;
10+
11+
bpf_setsockopt(ctx, SOL_SOCKET, SK_BPF_MEMCG_FLAGS,
12+
&flags, sizeof(flags));
13+
}
14+
15+
SEC("cgroup/sock_create")
16+
int sock_create(struct bpf_sock *ctx)
17+
{
18+
isolate_memcg(ctx);
19+
return 1;
20+
}
21+
22+
SEC("sockops")
23+
int skops_setsockopt(struct bpf_sock_ops *skops)
24+
{
25+
if (skops->op == BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB)
26+
isolate_memcg(skops);
27+
return 1;
28+
}
29+
30+
char LICENSE[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)