Skip to content

Commit 371e4fc

Browse files
rgushchinborkmann
authored andcommitted
selftests/bpf: cgroup local storage-based network counters
This commit adds a bpf kselftest, which demonstrates how percpu and shared cgroup local storage can be used for efficient lookup-free network accounting. Cgroup local storage provides generic memory area with a very efficient lookup free access. To avoid expensive atomic operations for each packet, per-cpu cgroup local storage is used. Each packet is initially charged to a per-cpu counter, and only if the counter reaches certain value (32 in this case), the charge is moved into the global atomic counter. This allows to amortize atomic operations, keeping reasonable accuracy. The test also implements a naive network traffic throttling, mostly to demonstrate the possibility of bpf cgroup--based network bandwidth control. Expected output: ./test_netcnt test_netcnt:PASS Signed-off-by: Roman Gushchin <guro@fb.com> Acked-by: Song Liu <songliubraving@fb.com> Cc: Daniel Borkmann <daniel@iogearbox.net> Cc: Alexei Starovoitov <ast@kernel.org> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
1 parent 5fcbd29 commit 371e4fc

File tree

4 files changed

+257
-2
lines changed

4 files changed

+257
-2
lines changed

tools/testing/selftests/bpf/Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ $(TEST_CUSTOM_PROGS): $(OUTPUT)/%: %.c
2323
TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
2424
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
2525
test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
26-
test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names
26+
test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
27+
test_netcnt
2728

2829
TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
2930
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
@@ -35,7 +36,7 @@ TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test
3536
test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
3637
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
3738
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
38-
test_skb_cgroup_id_kern.o bpf_flow.o
39+
test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o
3940

4041
# Order correspond to 'make run_tests' order
4142
TEST_PROGS := test_kmod.sh \
@@ -72,6 +73,7 @@ $(OUTPUT)/test_tcpbpf_user: cgroup_helpers.c
7273
$(OUTPUT)/test_progs: trace_helpers.c
7374
$(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c
7475
$(OUTPUT)/test_cgroup_storage: cgroup_helpers.c
76+
$(OUTPUT)/test_netcnt: cgroup_helpers.c
7577

7678
.PHONY: force
7779

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#ifndef __NETCNT_COMMON_H
3+
#define __NETCNT_COMMON_H
4+
5+
#include <linux/types.h>
6+
7+
#define MAX_PERCPU_PACKETS 32
8+
9+
struct percpu_net_cnt {
10+
__u64 packets;
11+
__u64 bytes;
12+
13+
__u64 prev_ts;
14+
15+
__u64 prev_packets;
16+
__u64 prev_bytes;
17+
};
18+
19+
struct net_cnt {
20+
__u64 packets;
21+
__u64 bytes;
22+
};
23+
24+
#endif
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include <linux/bpf.h>
3+
#include <linux/version.h>
4+
5+
#include "bpf_helpers.h"
6+
#include "netcnt_common.h"
7+
8+
#define MAX_BPS (3 * 1024 * 1024)
9+
10+
#define REFRESH_TIME_NS 100000000
11+
#define NS_PER_SEC 1000000000
12+
13+
struct bpf_map_def SEC("maps") percpu_netcnt = {
14+
.type = BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
15+
.key_size = sizeof(struct bpf_cgroup_storage_key),
16+
.value_size = sizeof(struct percpu_net_cnt),
17+
};
18+
19+
struct bpf_map_def SEC("maps") netcnt = {
20+
.type = BPF_MAP_TYPE_CGROUP_STORAGE,
21+
.key_size = sizeof(struct bpf_cgroup_storage_key),
22+
.value_size = sizeof(struct net_cnt),
23+
};
24+
25+
SEC("cgroup/skb")
26+
int bpf_nextcnt(struct __sk_buff *skb)
27+
{
28+
struct percpu_net_cnt *percpu_cnt;
29+
char fmt[] = "%d %llu %llu\n";
30+
struct net_cnt *cnt;
31+
__u64 ts, dt;
32+
int ret;
33+
34+
cnt = bpf_get_local_storage(&netcnt, 0);
35+
percpu_cnt = bpf_get_local_storage(&percpu_netcnt, 0);
36+
37+
percpu_cnt->packets++;
38+
percpu_cnt->bytes += skb->len;
39+
40+
if (percpu_cnt->packets > MAX_PERCPU_PACKETS) {
41+
__sync_fetch_and_add(&cnt->packets,
42+
percpu_cnt->packets);
43+
percpu_cnt->packets = 0;
44+
45+
__sync_fetch_and_add(&cnt->bytes,
46+
percpu_cnt->bytes);
47+
percpu_cnt->bytes = 0;
48+
}
49+
50+
ts = bpf_ktime_get_ns();
51+
dt = ts - percpu_cnt->prev_ts;
52+
53+
dt *= MAX_BPS;
54+
dt /= NS_PER_SEC;
55+
56+
if (cnt->bytes + percpu_cnt->bytes - percpu_cnt->prev_bytes < dt)
57+
ret = 1;
58+
else
59+
ret = 0;
60+
61+
if (dt > REFRESH_TIME_NS) {
62+
percpu_cnt->prev_ts = ts;
63+
percpu_cnt->prev_packets = cnt->packets;
64+
percpu_cnt->prev_bytes = cnt->bytes;
65+
}
66+
67+
return !!ret;
68+
}
69+
70+
char _license[] SEC("license") = "GPL";
71+
__u32 _version SEC("version") = LINUX_VERSION_CODE;
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <errno.h>
6+
#include <assert.h>
7+
#include <sys/sysinfo.h>
8+
#include <sys/time.h>
9+
10+
#include <linux/bpf.h>
11+
#include <bpf/bpf.h>
12+
#include <bpf/libbpf.h>
13+
14+
#include "cgroup_helpers.h"
15+
#include "bpf_rlimit.h"
16+
#include "netcnt_common.h"
17+
18+
#define BPF_PROG "./netcnt_prog.o"
19+
#define TEST_CGROUP "/test-network-counters/"
20+
21+
static int bpf_find_map(const char *test, struct bpf_object *obj,
22+
const char *name)
23+
{
24+
struct bpf_map *map;
25+
26+
map = bpf_object__find_map_by_name(obj, name);
27+
if (!map) {
28+
printf("%s:FAIL:map '%s' not found\n", test, name);
29+
return -1;
30+
}
31+
return bpf_map__fd(map);
32+
}
33+
34+
int main(int argc, char **argv)
35+
{
36+
struct percpu_net_cnt *percpu_netcnt;
37+
struct bpf_cgroup_storage_key key;
38+
int map_fd, percpu_map_fd;
39+
int error = EXIT_FAILURE;
40+
struct net_cnt netcnt;
41+
struct bpf_object *obj;
42+
int prog_fd, cgroup_fd;
43+
unsigned long packets;
44+
unsigned long bytes;
45+
int cpu, nproc;
46+
__u32 prog_cnt;
47+
48+
nproc = get_nprocs_conf();
49+
percpu_netcnt = malloc(sizeof(*percpu_netcnt) * nproc);
50+
if (!percpu_netcnt) {
51+
printf("Not enough memory for per-cpu area (%d cpus)\n", nproc);
52+
goto err;
53+
}
54+
55+
if (bpf_prog_load(BPF_PROG, BPF_PROG_TYPE_CGROUP_SKB,
56+
&obj, &prog_fd)) {
57+
printf("Failed to load bpf program\n");
58+
goto out;
59+
}
60+
61+
if (setup_cgroup_environment()) {
62+
printf("Failed to load bpf program\n");
63+
goto err;
64+
}
65+
66+
/* Create a cgroup, get fd, and join it */
67+
cgroup_fd = create_and_get_cgroup(TEST_CGROUP);
68+
if (!cgroup_fd) {
69+
printf("Failed to create test cgroup\n");
70+
goto err;
71+
}
72+
73+
if (join_cgroup(TEST_CGROUP)) {
74+
printf("Failed to join cgroup\n");
75+
goto err;
76+
}
77+
78+
/* Attach bpf program */
79+
if (bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_INET_EGRESS, 0)) {
80+
printf("Failed to attach bpf program");
81+
goto err;
82+
}
83+
84+
assert(system("ping localhost -6 -c 10000 -f -q > /dev/null") == 0);
85+
86+
if (bpf_prog_query(cgroup_fd, BPF_CGROUP_INET_EGRESS, 0, NULL, NULL,
87+
&prog_cnt)) {
88+
printf("Failed to query attached programs");
89+
goto err;
90+
}
91+
92+
map_fd = bpf_find_map(__func__, obj, "netcnt");
93+
if (map_fd < 0) {
94+
printf("Failed to find bpf map with net counters");
95+
goto err;
96+
}
97+
98+
percpu_map_fd = bpf_find_map(__func__, obj, "percpu_netcnt");
99+
if (percpu_map_fd < 0) {
100+
printf("Failed to find bpf map with percpu net counters");
101+
goto err;
102+
}
103+
104+
if (bpf_map_get_next_key(map_fd, NULL, &key)) {
105+
printf("Failed to get key in cgroup storage\n");
106+
goto err;
107+
}
108+
109+
if (bpf_map_lookup_elem(map_fd, &key, &netcnt)) {
110+
printf("Failed to lookup cgroup storage\n");
111+
goto err;
112+
}
113+
114+
if (bpf_map_lookup_elem(percpu_map_fd, &key, &percpu_netcnt[0])) {
115+
printf("Failed to lookup percpu cgroup storage\n");
116+
goto err;
117+
}
118+
119+
/* Some packets can be still in per-cpu cache, but not more than
120+
* MAX_PERCPU_PACKETS.
121+
*/
122+
packets = netcnt.packets;
123+
bytes = netcnt.bytes;
124+
for (cpu = 0; cpu < nproc; cpu++) {
125+
if (percpu_netcnt[cpu].packets > MAX_PERCPU_PACKETS) {
126+
printf("Unexpected percpu value: %llu\n",
127+
percpu_netcnt[cpu].packets);
128+
goto err;
129+
}
130+
131+
packets += percpu_netcnt[cpu].packets;
132+
bytes += percpu_netcnt[cpu].bytes;
133+
}
134+
135+
/* No packets should be lost */
136+
if (packets != 10000) {
137+
printf("Unexpected packet count: %lu\n", packets);
138+
goto err;
139+
}
140+
141+
/* Let's check that bytes counter matches the number of packets
142+
* multiplied by the size of ipv6 ICMP packet.
143+
*/
144+
if (bytes != packets * 104) {
145+
printf("Unexpected bytes count: %lu\n", bytes);
146+
goto err;
147+
}
148+
149+
error = 0;
150+
printf("test_netcnt:PASS\n");
151+
152+
err:
153+
cleanup_cgroup_environment();
154+
free(percpu_netcnt);
155+
156+
out:
157+
return error;
158+
}

0 commit comments

Comments
 (0)