1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright(c) 2017 Jesper Dangaard Brouer, Red Hat, Inc.
4 static const char *__doc__ =
5 "XDP CPU redirect tool, using BPF_MAP_TYPE_CPUMAP\n"
6 "Usage: xdp_redirect_cpu -d <IFINDEX|IFNAME> -c 0 ... -c N\n"
7 "Valid specification for CPUMAP BPF program:\n"
8 " --mprog-name/-e pass (use built-in XDP_PASS program)\n"
9 " --mprog-name/-e drop (use built-in XDP_DROP program)\n"
10 " --redirect-device/-r <ifindex|ifname> (use built-in DEVMAP redirect program)\n"
11 " Custom CPUMAP BPF program:\n"
12 " --mprog-filename/-f <filename> --mprog-name/-e <program>\n"
13 " Optionally, also pass --redirect-map/-m and --redirect-device/-r together\n"
14 " to configure DEVMAP in BPF object <filename>\n";
24 #include <sys/sysinfo.h>
28 #include <linux/limits.h>
29 #include <arpa/inet.h>
30 #include <linux/if_link.h>
32 #include <bpf/libbpf.h>
34 #include "xdp_sample_user.h"
35 #include "xdp_redirect_cpu.skel.h"
41 static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT |
42 SAMPLE_CPUMAP_ENQUEUE_CNT | SAMPLE_CPUMAP_KTHREAD_CNT |
45 DEFINE_SAMPLE_INIT(xdp_redirect_cpu);
47 static const struct option long_options[] = {
48 { "help", no_argument, NULL, 'h' },
49 { "dev", required_argument, NULL, 'd' },
50 { "skb-mode", no_argument, NULL, 'S' },
51 { "progname", required_argument, NULL, 'p' },
52 { "qsize", required_argument, NULL, 'q' },
53 { "cpu", required_argument, NULL, 'c' },
54 { "stress-mode", no_argument, NULL, 'x' },
55 { "force", no_argument, NULL, 'F' },
56 { "interval", required_argument, NULL, 'i' },
57 { "verbose", no_argument, NULL, 'v' },
58 { "stats", no_argument, NULL, 's' },
59 { "mprog-name", required_argument, NULL, 'e' },
60 { "mprog-filename", required_argument, NULL, 'f' },
61 { "redirect-device", required_argument, NULL, 'r' },
62 { "redirect-map", required_argument, NULL, 'm' },
66 static void print_avail_progs(struct bpf_object *obj)
68 struct bpf_program *pos;
70 printf(" Programs to be used for -p/--progname:\n");
71 bpf_object__for_each_program(pos, obj) {
72 if (bpf_program__type(pos) == BPF_PROG_TYPE_XDP) {
73 if (!strncmp(bpf_program__name(pos), "xdp_prognum",
74 sizeof("xdp_prognum") - 1))
75 printf(" %s\n", bpf_program__name(pos));
80 static void usage(char *argv[], const struct option *long_options,
81 const char *doc, int mask, bool error, struct bpf_object *obj)
83 sample_usage(argv, long_options, doc, mask, error);
84 print_avail_progs(obj);
87 static int create_cpu_entry(__u32 cpu, struct bpf_cpumap_val *value,
88 __u32 avail_idx, bool new)
90 __u32 curr_cpus_count = 0;
94 /* Add a CPU entry to cpumap, as this allocate a cpu entry in
95 * the kernel for the cpu.
97 ret = bpf_map_update_elem(map_fd, &cpu, value, 0);
99 fprintf(stderr, "Create CPU entry failed: %s\n", strerror(errno));
103 /* Inform bpf_prog's that a new CPU is available to select
104 * from via some control maps.
106 ret = bpf_map_update_elem(avail_fd, &avail_idx, &cpu, 0);
108 fprintf(stderr, "Add to avail CPUs failed: %s\n", strerror(errno));
112 /* When not replacing/updating existing entry, bump the count */
113 ret = bpf_map_lookup_elem(count_fd, &key, &curr_cpus_count);
115 fprintf(stderr, "Failed reading curr cpus_count: %s\n",
121 ret = bpf_map_update_elem(count_fd, &key,
122 &curr_cpus_count, 0);
124 fprintf(stderr, "Failed write curr cpus_count: %s\n",
130 printf("%s CPU: %u as idx: %u qsize: %d cpumap_prog_fd: %d (cpus_count: %u)\n",
131 new ? "Add new" : "Replace", cpu, avail_idx,
132 value->qsize, value->bpf_prog.fd, curr_cpus_count);
137 /* CPUs are zero-indexed. Thus, add a special sentinel default value
138 * in map cpus_available to mark CPU index'es not configured
140 static int mark_cpus_unavailable(void)
142 int ret, i, n_cpus = libbpf_num_possible_cpus();
143 __u32 invalid_cpu = n_cpus;
145 for (i = 0; i < n_cpus; i++) {
146 ret = bpf_map_update_elem(avail_fd, &i,
149 fprintf(stderr, "Failed marking CPU unavailable: %s\n",
158 /* Stress cpumap management code by concurrently changing underlying cpumap */
159 static void stress_cpumap(void *ctx)
161 struct bpf_cpumap_val *value = ctx;
163 /* Changing qsize will cause kernel to free and alloc a new
164 * bpf_cpu_map_entry, with an associated/complicated tear-down
168 create_cpu_entry(1, value, 0, false);
170 create_cpu_entry(1, value, 0, false);
171 value->qsize = 16000;
172 create_cpu_entry(1, value, 0, false);
175 static int set_cpumap_prog(struct xdp_redirect_cpu *skel,
176 const char *redir_interface, const char *redir_map,
177 const char *mprog_filename, const char *mprog_name)
179 if (mprog_filename) {
180 struct bpf_program *prog;
181 struct bpf_object *obj;
185 fprintf(stderr, "BPF program not specified for file %s\n",
189 if ((redir_interface && !redir_map) || (!redir_interface && redir_map)) {
190 fprintf(stderr, "--redirect-%s specified but --redirect-%s not specified\n",
191 redir_interface ? "device" : "map", redir_interface ? "map" : "device");
195 /* Custom BPF program */
196 obj = bpf_object__open_file(mprog_filename, NULL);
199 fprintf(stderr, "Failed to bpf_prog_load_xattr: %s\n",
204 ret = bpf_object__load(obj);
207 fprintf(stderr, "Failed to bpf_object__load: %s\n",
213 int err, redir_map_fd, ifindex_out, key = 0;
215 redir_map_fd = bpf_object__find_map_fd_by_name(obj, redir_map);
216 if (redir_map_fd < 0) {
217 fprintf(stderr, "Failed to bpf_object__find_map_fd_by_name: %s\n",
222 ifindex_out = if_nametoindex(redir_interface);
224 ifindex_out = strtoul(redir_interface, NULL, 0);
226 fprintf(stderr, "Bad interface name or index\n");
230 err = bpf_map_update_elem(redir_map_fd, &key, &ifindex_out, 0);
235 prog = bpf_object__find_program_by_name(obj, mprog_name);
238 fprintf(stderr, "Failed to bpf_object__find_program_by_name: %s\n",
243 return bpf_program__fd(prog);
246 if (redir_interface || redir_map) {
247 fprintf(stderr, "Need to specify --mprog-filename/-f\n");
250 if (!strcmp(mprog_name, "pass") || !strcmp(mprog_name, "drop")) {
251 /* Use built-in pass/drop programs */
252 return *mprog_name == 'p' ? bpf_program__fd(skel->progs.xdp_redirect_cpu_pass)
253 : bpf_program__fd(skel->progs.xdp_redirect_cpu_drop);
255 fprintf(stderr, "Unknown name \"%s\" for built-in BPF program\n",
261 fprintf(stderr, "Need to specify --mprog-filename, --mprog-name and"
262 " --redirect-device with --redirect-map\n");
265 if (redir_interface) {
266 /* Use built-in devmap redirect */
267 struct bpf_devmap_val val = {};
268 int ifindex_out, err;
271 if (!redir_interface)
274 ifindex_out = if_nametoindex(redir_interface);
276 ifindex_out = strtoul(redir_interface, NULL, 0);
278 fprintf(stderr, "Bad interface name or index\n");
282 if (get_mac_addr(ifindex_out, skel->bss->tx_mac_addr) < 0) {
283 printf("Get interface %d mac failed\n", ifindex_out);
287 val.ifindex = ifindex_out;
288 val.bpf_prog.fd = bpf_program__fd(skel->progs.xdp_redirect_egress_prog);
289 err = bpf_map_update_elem(bpf_map__fd(skel->maps.tx_port), &key, &val, 0);
293 return bpf_program__fd(skel->progs.xdp_redirect_cpu_devmap);
301 fprintf(stderr, "Invalid options for CPUMAP BPF program\n");
305 int main(int argc, char **argv)
307 const char *redir_interface = NULL, *redir_map = NULL;
308 const char *mprog_filename = NULL, *mprog_name = NULL;
309 struct xdp_redirect_cpu *skel;
310 struct bpf_map_info info = {};
311 struct bpf_cpumap_val value;
312 __u32 infosz = sizeof(info);
313 int ret = EXIT_FAIL_OPTION;
314 unsigned long interval = 2;
315 bool stress_mode = false;
316 struct bpf_program *prog;
317 const char *prog_name;
318 bool generic = false;
329 n_cpus = libbpf_num_possible_cpus();
331 /* Notice: Choosing the queue size is very important when CPU is
332 * configured with power-saving states.
334 * If deepest state take 133 usec to wakeup from (133/10^6). When link
335 * speed is 10Gbit/s ((10*10^9/8) in bytes/sec). How many bytes can
336 * arrive with in 133 usec at this speed: (10*10^9/8)*(133/10^6) =
337 * 166250 bytes. With MTU size packets this is 110 packets, and with
338 * minimum Ethernet (MAC-preamble + intergap) 84 bytes is 1979 packets.
340 * Setting default cpumap queue to 2048 as worst-case (small packet)
341 * should be +64 packet due kthread wakeup call (due to xdp_do_flush)
342 * worst-case is 2043 packets.
344 * Sysadm can configured system to avoid deep-sleep via:
345 * tuned-adm profile network-latency
349 skel = xdp_redirect_cpu__open();
351 fprintf(stderr, "Failed to xdp_redirect_cpu__open: %s\n",
357 ret = sample_init_pre_load(skel);
359 fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
364 if (bpf_map__set_max_entries(skel->maps.cpu_map, n_cpus) < 0) {
365 fprintf(stderr, "Failed to set max entries for cpu_map map: %s",
371 if (bpf_map__set_max_entries(skel->maps.cpus_available, n_cpus) < 0) {
372 fprintf(stderr, "Failed to set max entries for cpus_available map: %s",
378 cpu = calloc(n_cpus, sizeof(int));
380 fprintf(stderr, "Failed to allocate cpu array\n");
384 prog = skel->progs.xdp_prognum5_lb_hash_ip_pairs;
385 while ((opt = getopt_long(argc, argv, "d:si:Sxp:f:e:r:m:c:q:Fvh",
386 long_options, &longindex)) != -1) {
389 if (strlen(optarg) >= IF_NAMESIZE) {
390 fprintf(stderr, "-d/--dev name too long\n");
391 usage(argv, long_options, __doc__, mask, true, skel->obj);
394 ifindex = if_nametoindex(optarg);
396 ifindex = strtoul(optarg, NULL, 0);
398 fprintf(stderr, "Bad interface index or name (%d): %s\n",
399 errno, strerror(errno));
400 usage(argv, long_options, __doc__, mask, true, skel->obj);
405 mask |= SAMPLE_REDIRECT_MAP_CNT;
408 interval = strtoul(optarg, NULL, 0);
417 /* Selecting eBPF prog to load */
419 prog = bpf_object__find_program_by_name(skel->obj,
423 "Failed to find program %s specified by"
424 " option -p/--progname\n",
426 print_avail_progs(skel->obj);
431 mprog_filename = optarg;
437 redir_interface = optarg;
438 mask |= SAMPLE_DEVMAP_XMIT_CNT_MULTI;
444 /* Add multiple CPUs */
445 add_cpu = strtoul(optarg, NULL, 0);
446 if (add_cpu >= n_cpus) {
448 "--cpu nr too large for cpumap err (%d):%s\n",
449 errno, strerror(errno));
450 usage(argv, long_options, __doc__, mask, true, skel->obj);
453 cpu[added_cpus++] = add_cpu;
456 qsize = strtoul(optarg, NULL, 0);
462 sample_switch_mode();
467 usage(argv, long_options, __doc__, mask, error, skel->obj);
472 ret = EXIT_FAIL_OPTION;
474 fprintf(stderr, "Required option --dev missing\n");
475 usage(argv, long_options, __doc__, mask, true, skel->obj);
480 fprintf(stderr, "Required option --cpu missing\n"
481 "Specify multiple --cpu option to add more\n");
482 usage(argv, long_options, __doc__, mask, true, skel->obj);
486 skel->rodata->from_match[0] = ifindex;
488 skel->rodata->to_match[0] = if_nametoindex(redir_interface);
490 ret = xdp_redirect_cpu__load(skel);
492 fprintf(stderr, "Failed to xdp_redirect_cpu__load: %s\n",
497 ret = bpf_obj_get_info_by_fd(bpf_map__fd(skel->maps.cpu_map), &info, &infosz);
499 fprintf(stderr, "Failed bpf_obj_get_info_by_fd for cpumap: %s\n",
504 skel->bss->cpumap_map_id = info.id;
506 map_fd = bpf_map__fd(skel->maps.cpu_map);
507 avail_fd = bpf_map__fd(skel->maps.cpus_available);
508 count_fd = bpf_map__fd(skel->maps.cpus_count);
510 ret = mark_cpus_unavailable();
512 fprintf(stderr, "Unable to mark CPUs as unavailable\n");
516 ret = sample_init(skel, mask);
518 fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
523 value.bpf_prog.fd = set_cpumap_prog(skel, redir_interface, redir_map,
524 mprog_filename, mprog_name);
525 if (value.bpf_prog.fd < 0) {
526 fprintf(stderr, "Failed to set CPUMAP BPF program: %s\n",
527 strerror(-value.bpf_prog.fd));
528 usage(argv, long_options, __doc__, mask, true, skel->obj);
534 for (i = 0; i < added_cpus; i++) {
535 if (create_cpu_entry(cpu[i], &value, i, true) < 0) {
536 fprintf(stderr, "Cannot proceed, exiting\n");
537 usage(argv, long_options, __doc__, mask, true, skel->obj);
543 if (sample_install_xdp(prog, ifindex, generic, force) < 0)
546 ret = sample_run(interval, stress_mode ? stress_cpumap : NULL, &value);
548 fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
556 xdp_redirect_cpu__destroy(skel);