Commit 1b580c9b authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'samples-bpf-remove-unmaintained-xdp-sample-utilities'

Toke Høiland-Jørgensen says:

====================
samples/bpf: Remove unmaintained XDP sample utilities

The samples/bpf directory in the kernel tree started out as a way of showcasing
different aspects of BPF functionality by writing small utility programs for
each feature. However, as the BPF subsystem has matured, the preferred way of
including userspace code with a feature has become the BPF selftests, which also
have the benefit of being consistently run as part of the BPF CI system.

As a result of this shift, the utilities in samples/bpf have seen little love,
and have slowly bitrotted. There have been sporadic cleanup patches over the
years, but it's clear that the utilities are far from maintained.

For XDP in particular, some of the utilities have been used as benchmarking aids
when implementing new kernel features, which seems to be the main reason they
have stuck around; any updates the utilities have seen have been targeted at
this use case. However, as the BPF subsystem as a whole has moved on, it has
become increasingly difficult to incorporate new features into these utilities
because they predate most of the modern BPF features (such as kfuncs and BTF).

Rather than try to update these utilities and keep maintaining them in the
kernel tree, we have ported the useful features of the utilities to the
xdp-tools package. In the porting process we also updated the utilities to take
advantage of modern BPF features, integrated them with libxdp, and polished the
user interface.

As these utilities are standalone tools, maintaining them out of tree is
simpler, and we plan to keep maintaining them in the xdp-tools repo. To direct
users of these utilities to the right place, this series removes the utilities
from samples/bpf, leaving in place only a couple of utilities whose
functionality have not yet been ported to xdp-tools.

The xdp-tools repository is located on Github at the following URL:

https://github.com/xdp-project/xdp-tools

The commits in the series removes one utility each, explaining how the
equivalent functionality can be obtained with xdp-tools.

v2:
- Add equivalent xdp-tools commands for each removed utility
v3:
- Add link to xdp-tools in the README

Toke Høiland-Jørgensen (7):
  samples/bpf: Remove the xdp_monitor utility
  samples/bpf: Remove the xdp_redirect* utilities
  samples/bpf: Remove the xdp_rxq_info utility
  samples/bpf: Remove the xdp1 and xdp2 utilities
  samples/bpf: Remove the xdp_sample_pkts utility
  samples/bpf: Cleanup .gitignore
  samples/bpf: Add note to README about the XDP utilities moved to
    xdp-tools

 samples/bpf/.gitignore                    |  12 -
 samples/bpf/Makefile                      |  48 +-
 samples/bpf/README.rst                    |   6 +
 samples/bpf/xdp1_kern.c                   | 100 ----
 samples/bpf/xdp1_user.c                   | 166 ------
 samples/bpf/xdp2_kern.c                   | 125 -----
 samples/bpf/xdp_monitor.bpf.c             |   8 -
 samples/bpf/xdp_monitor_user.c            | 118 -----
 samples/bpf/xdp_redirect.bpf.c            |  49 --
 samples/bpf/xdp_redirect_cpu.bpf.c        | 539 -------------------
 samples/bpf/xdp_redirect_cpu_user.c       | 559 --------------------
 samples/bpf/xdp_redirect_map.bpf.c        |  97 ----
 samples/bpf/xdp_redirect_map_multi.bpf.c  |  77 ---
 samples/bpf/xdp_redirect_map_multi_user.c | 232 --------
 samples/bpf/xdp_redirect_map_user.c       | 228 --------
 samples/bpf/xdp_redirect_user.c           | 172 ------
 samples/bpf/xdp_rxq_info_kern.c           | 140 -----
 samples/bpf/xdp_rxq_info_user.c           | 614 ----------------------
 samples/bpf/xdp_sample_pkts_kern.c        |  57 --
 samples/bpf/xdp_sample_pkts_user.c        | 196 -------
 20 files changed, 7 insertions(+), 3536 deletions(-)
 delete mode 100644 samples/bpf/xdp1_kern.c
 delete mode 100644 samples/bpf/xdp1_user.c
 delete mode 100644 samples/bpf/xdp2_kern.c
 delete mode 100644 samples/bpf/xdp_monitor.bpf.c
 delete mode 100644 samples/bpf/xdp_monitor_user.c
 delete mode 100644 samples/bpf/xdp_redirect.bpf.c
 delete mode 100644 samples/bpf/xdp_redirect_cpu.bpf.c
 delete mode 100644 samples/bpf/xdp_redirect_cpu_user.c
 delete mode 100644 samples/bpf/xdp_redirect_map.bpf.c
 delete mode 100644 samples/bpf/xdp_redirect_map_multi.bpf.c
 delete mode 100644 samples/bpf/xdp_redirect_map_multi_user.c
 delete mode 100644 samples/bpf/xdp_redirect_map_user.c
 delete mode 100644 samples/bpf/xdp_redirect_user.c
 delete mode 100644 samples/bpf/xdp_rxq_info_kern.c
 delete mode 100644 samples/bpf/xdp_rxq_info_user.c
 delete mode 100644 samples/bpf/xdp_sample_pkts_kern.c
 delete mode 100644 samples/bpf/xdp_sample_pkts_user.c
====================

Link: https://lore.kernel.org/r/20230824102255.1561885-1-toke@redhat.comSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 001fedac 5a9fd0f7
......@@ -37,22 +37,10 @@ tracex4
tracex5
tracex6
tracex7
xdp1
xdp2
xdp_adjust_tail
xdp_fwd
xdp_monitor
xdp_redirect
xdp_redirect_cpu
xdp_redirect_map
xdp_redirect_map_multi
xdp_router_ipv4
xdp_rxq_info
xdp_sample_pkts
xdp_tx_iptunnel
xdpsock
xdpsock_ctrl_proc
xsk_fwd
testfile.img
hbm_out.log
iperf.*
......
......@@ -30,8 +30,6 @@ tprogs-y += test_cgrp2_array_pin
tprogs-y += test_cgrp2_attach
tprogs-y += test_cgrp2_sock
tprogs-y += test_cgrp2_sock2
tprogs-y += xdp1
tprogs-y += xdp2
tprogs-y += xdp_router_ipv4
tprogs-y += test_current_task_under_cgroup
tprogs-y += trace_event
......@@ -41,22 +39,14 @@ tprogs-y += lwt_len_hist
tprogs-y += xdp_tx_iptunnel
tprogs-y += test_map_in_map
tprogs-y += per_socket_stats_example
tprogs-y += xdp_rxq_info
tprogs-y += syscall_tp
tprogs-y += cpustat
tprogs-y += xdp_adjust_tail
tprogs-y += xdp_fwd
tprogs-y += task_fd_query
tprogs-y += xdp_sample_pkts
tprogs-y += ibumad
tprogs-y += hbm
tprogs-y += xdp_redirect_cpu
tprogs-y += xdp_redirect_map_multi
tprogs-y += xdp_redirect_map
tprogs-y += xdp_redirect
tprogs-y += xdp_monitor
# Libbpf dependencies
LIBBPF_SRC = $(TOOLS_PATH)/lib/bpf
LIBBPF_OUTPUT = $(abspath $(BPF_SAMPLES_PATH))/libbpf
......@@ -90,9 +80,6 @@ test_cgrp2_array_pin-objs := test_cgrp2_array_pin.o
test_cgrp2_attach-objs := test_cgrp2_attach.o
test_cgrp2_sock-objs := test_cgrp2_sock.o
test_cgrp2_sock2-objs := test_cgrp2_sock2.o
xdp1-objs := xdp1_user.o
# reuse xdp1 source intentionally
xdp2-objs := xdp1_user.o
test_current_task_under_cgroup-objs := $(CGROUP_HELPERS) \
test_current_task_under_cgroup_user.o
trace_event-objs := trace_event_user.o $(TRACE_HELPERS)
......@@ -102,21 +89,14 @@ lwt_len_hist-objs := lwt_len_hist_user.o
xdp_tx_iptunnel-objs := xdp_tx_iptunnel_user.o
test_map_in_map-objs := test_map_in_map_user.o
per_socket_stats_example-objs := cookie_uid_helper_example.o
xdp_rxq_info-objs := xdp_rxq_info_user.o
syscall_tp-objs := syscall_tp_user.o
cpustat-objs := cpustat_user.o
xdp_adjust_tail-objs := xdp_adjust_tail_user.o
xdp_fwd-objs := xdp_fwd_user.o
task_fd_query-objs := task_fd_query_user.o $(TRACE_HELPERS)
xdp_sample_pkts-objs := xdp_sample_pkts_user.o
ibumad-objs := ibumad_user.o
hbm-objs := hbm.o $(CGROUP_HELPERS)
xdp_redirect_map_multi-objs := xdp_redirect_map_multi_user.o $(XDP_SAMPLE)
xdp_redirect_cpu-objs := xdp_redirect_cpu_user.o $(XDP_SAMPLE)
xdp_redirect_map-objs := xdp_redirect_map_user.o $(XDP_SAMPLE)
xdp_redirect-objs := xdp_redirect_user.o $(XDP_SAMPLE)
xdp_monitor-objs := xdp_monitor_user.o $(XDP_SAMPLE)
xdp_router_ipv4-objs := xdp_router_ipv4_user.o $(XDP_SAMPLE)
# Tell kbuild to always build the programs
......@@ -145,8 +125,6 @@ always-y += test_overhead_raw_tp.bpf.o
always-y += test_overhead_kprobe.bpf.o
always-y += parse_varlen.o parse_simple.o parse_ldabs.o
always-y += test_cgrp2_tc.bpf.o
always-y += xdp1_kern.o
always-y += xdp2_kern.o
always-y += test_current_task_under_cgroup.bpf.o
always-y += trace_event_kern.o
always-y += sampleip_kern.o
......@@ -162,14 +140,12 @@ always-y += tcp_clamp_kern.o
always-y += tcp_basertt_kern.o
always-y += tcp_tos_reflect_kern.o
always-y += tcp_dumpstats_kern.o
always-y += xdp_rxq_info_kern.o
always-y += xdp2skb_meta_kern.o
always-y += syscall_tp_kern.o
always-y += cpustat_kern.o
always-y += xdp_adjust_tail_kern.o
always-y += xdp_fwd_kern.o
always-y += task_fd_query_kern.o
always-y += xdp_sample_pkts_kern.o
always-y += ibumad_kern.o
always-y += hbm_out_kern.o
always-y += hbm_edt_kern.o
......@@ -207,11 +183,6 @@ TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib
endif
TPROGS_LDLIBS += $(LIBBPF) -lelf -lz
TPROGLDLIBS_xdp_monitor += -lm
TPROGLDLIBS_xdp_redirect += -lm
TPROGLDLIBS_xdp_redirect_cpu += -lm
TPROGLDLIBS_xdp_redirect_map += -lm
TPROGLDLIBS_xdp_redirect_map_multi += -lm
TPROGLDLIBS_xdp_router_ipv4 += -lm -pthread
TPROGLDLIBS_tracex4 += -lrt
TPROGLDLIBS_trace_output += -lrt
......@@ -326,11 +297,6 @@ $(obj)/$(TRACE_HELPERS) $(obj)/$(CGROUP_HELPERS) $(obj)/$(XDP_SAMPLE): | libbpf_
.PHONY: libbpf_hdrs
$(obj)/xdp_redirect_cpu_user.o: $(obj)/xdp_redirect_cpu.skel.h
$(obj)/xdp_redirect_map_multi_user.o: $(obj)/xdp_redirect_map_multi.skel.h
$(obj)/xdp_redirect_map_user.o: $(obj)/xdp_redirect_map.skel.h
$(obj)/xdp_redirect_user.o: $(obj)/xdp_redirect.skel.h
$(obj)/xdp_monitor_user.o: $(obj)/xdp_monitor.skel.h
$(obj)/xdp_router_ipv4_user.o: $(obj)/xdp_router_ipv4.skel.h
$(obj)/tracex5.bpf.o: $(obj)/syscall_nrs.h
......@@ -383,11 +349,6 @@ endef
CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
$(obj)/xdp_redirect_cpu.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/xdp_redirect_map_multi.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/xdp_redirect_map.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/xdp_redirect.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/xdp_monitor.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/xdp_router_ipv4.bpf.o: $(obj)/xdp_sample.bpf.o
$(obj)/%.bpf.o: $(src)/%.bpf.c $(obj)/vmlinux.h $(src)/xdp_sample.bpf.h $(src)/xdp_sample_shared.h
......@@ -398,16 +359,9 @@ $(obj)/%.bpf.o: $(src)/%.bpf.c $(obj)/vmlinux.h $(src)/xdp_sample.bpf.h $(src)/x
-I$(LIBBPF_INCLUDE) $(CLANG_SYS_INCLUDES) \
-c $(filter %.bpf.c,$^) -o $@
LINKED_SKELS := xdp_redirect_cpu.skel.h xdp_redirect_map_multi.skel.h \
xdp_redirect_map.skel.h xdp_redirect.skel.h xdp_monitor.skel.h \
xdp_router_ipv4.skel.h
LINKED_SKELS := xdp_router_ipv4.skel.h
clean-files += $(LINKED_SKELS)
xdp_redirect_cpu.skel.h-deps := xdp_redirect_cpu.bpf.o xdp_sample.bpf.o
xdp_redirect_map_multi.skel.h-deps := xdp_redirect_map_multi.bpf.o xdp_sample.bpf.o
xdp_redirect_map.skel.h-deps := xdp_redirect_map.bpf.o xdp_sample.bpf.o
xdp_redirect.skel.h-deps := xdp_redirect.bpf.o xdp_sample.bpf.o
xdp_monitor.skel.h-deps := xdp_monitor.bpf.o xdp_sample.bpf.o
xdp_router_ipv4.skel.h-deps := xdp_router_ipv4.bpf.o xdp_sample.bpf.o
LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.bpf.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps)))
......
......@@ -4,6 +4,12 @@ eBPF sample programs
This directory contains a test stubs, verifier test-suite and examples
for using eBPF. The examples use libbpf from tools/lib/bpf.
Note that the XDP-specific samples have been removed from this directory and
moved to the xdp-tools repository: https://github.com/xdp-project/xdp-tools
See the commit messages removing each tool from this directory for how to
convert specific command invocations between the old samples and the utilities
in xdp-tools.
Build dependencies
==================
......
/* Copyright (c) 2016 PLUMgrid
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
#define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, long);
__uint(max_entries, 256);
} rxcnt SEC(".maps");
static int parse_ipv4(void *data, u64 nh_off, void *data_end)
{
struct iphdr *iph = data + nh_off;
if (iph + 1 > data_end)
return 0;
return iph->protocol;
}
static int parse_ipv6(void *data, u64 nh_off, void *data_end)
{
struct ipv6hdr *ip6h = data + nh_off;
if (ip6h + 1 > data_end)
return 0;
return ip6h->nexthdr;
}
#define XDPBUFSIZE 60
SEC("xdp.frags")
int xdp_prog1(struct xdp_md *ctx)
{
__u8 pkt[XDPBUFSIZE] = {};
void *data_end = &pkt[XDPBUFSIZE-1];
void *data = pkt;
struct ethhdr *eth = data;
int rc = XDP_DROP;
long *value;
u16 h_proto;
u64 nh_off;
u32 ipproto;
if (bpf_xdp_load_bytes(ctx, 0, pkt, sizeof(pkt)))
return rc;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return rc;
h_proto = eth->h_proto;
/* Handle VLAN tagged packet */
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
h_proto = vhdr->h_vlan_encapsulated_proto;
}
/* Handle double VLAN tagged packet */
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
h_proto = vhdr->h_vlan_encapsulated_proto;
}
if (h_proto == htons(ETH_P_IP))
ipproto = parse_ipv4(data, nh_off, data_end);
else if (h_proto == htons(ETH_P_IPV6))
ipproto = parse_ipv6(data, nh_off, data_end);
else
ipproto = 0;
value = bpf_map_lookup_elem(&rxcnt, &ipproto);
if (value)
*value += 1;
return rc;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2016 PLUMgrid
*/
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <net/if.h>
#include "bpf_util.h"
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
static int ifindex;
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static __u32 prog_id;
static void int_exit(int sig)
{
__u32 curr_prog_id = 0;
if (bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id)) {
printf("bpf_xdp_query_id failed\n");
exit(1);
}
if (prog_id == curr_prog_id)
bpf_xdp_detach(ifindex, xdp_flags, NULL);
else if (!curr_prog_id)
printf("couldn't find a prog id on a given interface\n");
else
printf("program on interface changed, not removing\n");
exit(0);
}
/* simple per-protocol drop counter
*/
static void poll_stats(int map_fd, int interval)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
__u64 values[nr_cpus], prev[UINT8_MAX] = { 0 };
int i;
while (1) {
__u32 key = UINT32_MAX;
sleep(interval);
while (bpf_map_get_next_key(map_fd, &key, &key) == 0) {
__u64 sum = 0;
assert(bpf_map_lookup_elem(map_fd, &key, values) == 0);
for (i = 0; i < nr_cpus; i++)
sum += values[i];
if (sum > prev[key])
printf("proto %u: %10llu pkt/s\n",
key, (sum - prev[key]) / interval);
prev[key] = sum;
}
}
}
static void usage(const char *prog)
{
fprintf(stderr,
"usage: %s [OPTS] IFACE\n\n"
"OPTS:\n"
" -S use skb-mode\n"
" -N enforce native mode\n"
" -F force loading prog\n",
prog);
}
int main(int argc, char **argv)
{
struct bpf_prog_info info = {};
__u32 info_len = sizeof(info);
const char *optstr = "FSN";
int prog_fd, map_fd, opt;
struct bpf_program *prog;
struct bpf_object *obj;
struct bpf_map *map;
char filename[256];
int err;
while ((opt = getopt(argc, argv, optstr)) != -1) {
switch (opt) {
case 'S':
xdp_flags |= XDP_FLAGS_SKB_MODE;
break;
case 'N':
/* default, set below */
break;
case 'F':
xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
break;
default:
usage(basename(argv[0]));
return 1;
}
}
if (!(xdp_flags & XDP_FLAGS_SKB_MODE))
xdp_flags |= XDP_FLAGS_DRV_MODE;
if (optind == argc) {
usage(basename(argv[0]));
return 1;
}
ifindex = if_nametoindex(argv[optind]);
if (!ifindex) {
perror("if_nametoindex");
return 1;
}
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj))
return 1;
prog = bpf_object__next_program(obj, NULL);
bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
err = bpf_object__load(obj);
if (err)
return 1;
prog_fd = bpf_program__fd(prog);
map = bpf_object__next_map(obj, NULL);
if (!map) {
printf("finding a map in obj file failed\n");
return 1;
}
map_fd = bpf_map__fd(map);
if (!prog_fd) {
printf("bpf_prog_load_xattr: %s\n", strerror(errno));
return 1;
}
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
if (bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL) < 0) {
printf("link set xdp fd failed\n");
return 1;
}
err = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
if (err) {
printf("can't get prog info - %s\n", strerror(errno));
return err;
}
prog_id = info.id;
poll_stats(map_fd, 1);
return 0;
}
/* Copyright (c) 2016 PLUMgrid
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
#define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, long);
__uint(max_entries, 256);
} rxcnt SEC(".maps");
static void swap_src_dst_mac(void *data)
{
unsigned short *p = data;
unsigned short dst[3];
dst[0] = p[0];
dst[1] = p[1];
dst[2] = p[2];
p[0] = p[3];
p[1] = p[4];
p[2] = p[5];
p[3] = dst[0];
p[4] = dst[1];
p[5] = dst[2];
}
static int parse_ipv4(void *data, u64 nh_off, void *data_end)
{
struct iphdr *iph = data + nh_off;
if (iph + 1 > data_end)
return 0;
return iph->protocol;
}
static int parse_ipv6(void *data, u64 nh_off, void *data_end)
{
struct ipv6hdr *ip6h = data + nh_off;
if (ip6h + 1 > data_end)
return 0;
return ip6h->nexthdr;
}
#define XDPBUFSIZE 60
SEC("xdp.frags")
int xdp_prog1(struct xdp_md *ctx)
{
__u8 pkt[XDPBUFSIZE] = {};
void *data_end = &pkt[XDPBUFSIZE-1];
void *data = pkt;
struct ethhdr *eth = data;
int rc = XDP_DROP;
long *value;
u16 h_proto;
u64 nh_off;
u32 ipproto;
if (bpf_xdp_load_bytes(ctx, 0, pkt, sizeof(pkt)))
return rc;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return rc;
h_proto = eth->h_proto;
/* Handle VLAN tagged packet */
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
h_proto = vhdr->h_vlan_encapsulated_proto;
}
/* Handle double VLAN tagged packet */
if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
struct vlan_hdr *vhdr;
vhdr = data + nh_off;
nh_off += sizeof(struct vlan_hdr);
if (data + nh_off > data_end)
return rc;
h_proto = vhdr->h_vlan_encapsulated_proto;
}
if (h_proto == htons(ETH_P_IP))
ipproto = parse_ipv4(data, nh_off, data_end);
else if (h_proto == htons(ETH_P_IPV6))
ipproto = parse_ipv6(data, nh_off, data_end);
else
ipproto = 0;
value = bpf_map_lookup_elem(&rxcnt, &ipproto);
if (value)
*value += 1;
if (ipproto == IPPROTO_UDP) {
swap_src_dst_mac(data);
if (bpf_xdp_store_bytes(ctx, 0, pkt, sizeof(pkt)))
return rc;
rc = XDP_TX;
}
return rc;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2017-2018 Jesper Dangaard Brouer, Red Hat Inc.
*
* XDP monitor tool, based on tracepoints
*/
#include "xdp_sample.bpf.h"
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2017 Jesper Dangaard Brouer, Red Hat, Inc. */
static const char *__doc__=
"XDP monitor tool, based on tracepoints\n";
static const char *__doc_err_only__=
" NOTICE: Only tracking XDP redirect errors\n"
" Enable redirect success stats via '-s/--stats'\n"
" (which comes with a per packet processing overhead)\n";
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <locale.h>
#include <getopt.h>
#include <net/if.h>
#include <time.h>
#include <signal.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
#include "xdp_sample_user.h"
#include "xdp_monitor.skel.h"
static int mask = SAMPLE_REDIRECT_ERR_CNT | SAMPLE_CPUMAP_ENQUEUE_CNT |
SAMPLE_CPUMAP_KTHREAD_CNT | SAMPLE_EXCEPTION_CNT |
SAMPLE_DEVMAP_XMIT_CNT | SAMPLE_DEVMAP_XMIT_CNT_MULTI;
DEFINE_SAMPLE_INIT(xdp_monitor);
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "stats", no_argument, NULL, 's' },
{ "interval", required_argument, NULL, 'i' },
{ "verbose", no_argument, NULL, 'v' },
{}
};
int main(int argc, char **argv)
{
unsigned long interval = 2;
int ret = EXIT_FAIL_OPTION;
struct xdp_monitor *skel;
bool errors_only = true;
int longindex = 0, opt;
bool error = true;
/* Parse commands line args */
while ((opt = getopt_long(argc, argv, "si:vh",
long_options, &longindex)) != -1) {
switch (opt) {
case 's':
errors_only = false;
mask |= SAMPLE_REDIRECT_CNT;
break;
case 'i':
interval = strtoul(optarg, NULL, 0);
break;
case 'v':
sample_switch_mode();
break;
case 'h':
error = false;
default:
sample_usage(argv, long_options, __doc__, mask, error);
return ret;
}
}
skel = xdp_monitor__open();
if (!skel) {
fprintf(stderr, "Failed to xdp_monitor__open: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end;
}
ret = sample_init_pre_load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = xdp_monitor__load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to xdp_monitor__load: %s\n", strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = sample_init(skel, mask);
if (ret < 0) {
fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
if (errors_only)
printf("%s", __doc_err_only__);
ret = sample_run(interval, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ret = EXIT_OK;
end_destroy:
xdp_monitor__destroy(skel);
end:
sample_exit(ret);
}
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2016 John Fastabend <john.r.fastabend@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include "vmlinux.h"
#include "xdp_sample.bpf.h"
#include "xdp_sample_shared.h"
const volatile int ifindex_out;
SEC("xdp")
int xdp_redirect_prog(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
struct datarec *rec;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
swap_src_dst_mac(data);
return bpf_redirect(ifindex_out, 0);
}
/* Redirect require an XDP bpf_prog loaded on the TX device */
SEC("xdp")
int xdp_redirect_dummy_prog(struct xdp_md *ctx)
{
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
/* XDP redirect to CPUs via cpumap (BPF_MAP_TYPE_CPUMAP)
*
* GPLv2, Copyright(c) 2017 Jesper Dangaard Brouer, Red Hat, Inc.
*/
#include "vmlinux.h"
#include "xdp_sample.bpf.h"
#include "xdp_sample_shared.h"
#include "hash_func01.h"
/* Special map type that can XDP_REDIRECT frames to another CPU */
struct {
__uint(type, BPF_MAP_TYPE_CPUMAP);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(struct bpf_cpumap_val));
} cpu_map SEC(".maps");
/* Set of maps controlling available CPU, and for iterating through
* selectable redirect CPUs.
*/
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, u32);
} cpus_available SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 1);
} cpus_count SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 1);
} cpus_iterator SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(struct bpf_devmap_val));
__uint(max_entries, 1);
} tx_port SEC(".maps");
char tx_mac_addr[ETH_ALEN];
/* Helper parse functions */
static __always_inline
bool parse_eth(struct ethhdr *eth, void *data_end,
u16 *eth_proto, u64 *l3_offset)
{
u16 eth_type;
u64 offset;
offset = sizeof(*eth);
if ((void *)eth + offset > data_end)
return false;
eth_type = eth->h_proto;
/* Skip non 802.3 Ethertypes */
if (__builtin_expect(bpf_ntohs(eth_type) < ETH_P_802_3_MIN, 0))
return false;
/* Handle VLAN tagged packet */
if (eth_type == bpf_htons(ETH_P_8021Q) ||
eth_type == bpf_htons(ETH_P_8021AD)) {
struct vlan_hdr *vlan_hdr;
vlan_hdr = (void *)eth + offset;
offset += sizeof(*vlan_hdr);
if ((void *)eth + offset > data_end)
return false;
eth_type = vlan_hdr->h_vlan_encapsulated_proto;
}
/* Handle double VLAN tagged packet */
if (eth_type == bpf_htons(ETH_P_8021Q) ||
eth_type == bpf_htons(ETH_P_8021AD)) {
struct vlan_hdr *vlan_hdr;
vlan_hdr = (void *)eth + offset;
offset += sizeof(*vlan_hdr);
if ((void *)eth + offset > data_end)
return false;
eth_type = vlan_hdr->h_vlan_encapsulated_proto;
}
*eth_proto = bpf_ntohs(eth_type);
*l3_offset = offset;
return true;
}
static __always_inline
u16 get_dest_port_ipv4_udp(struct xdp_md *ctx, u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct iphdr *iph = data + nh_off;
struct udphdr *udph;
if (iph + 1 > data_end)
return 0;
if (!(iph->protocol == IPPROTO_UDP))
return 0;
udph = (void *)(iph + 1);
if (udph + 1 > data_end)
return 0;
return bpf_ntohs(udph->dest);
}
static __always_inline
int get_proto_ipv4(struct xdp_md *ctx, u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct iphdr *iph = data + nh_off;
if (iph + 1 > data_end)
return 0;
return iph->protocol;
}
static __always_inline
int get_proto_ipv6(struct xdp_md *ctx, u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ipv6hdr *ip6h = data + nh_off;
if (ip6h + 1 > data_end)
return 0;
return ip6h->nexthdr;
}
SEC("xdp")
int xdp_prognum0_no_touch(struct xdp_md *ctx)
{
u32 key = bpf_get_smp_processor_id();
struct datarec *rec;
u32 *cpu_selected;
u32 cpu_dest = 0;
u32 key0 = 0;
/* Only use first entry in cpus_available */
cpu_selected = bpf_map_lookup_elem(&cpus_available, &key0);
if (!cpu_selected)
return XDP_ABORTED;
cpu_dest = *cpu_selected;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
SEC("xdp")
int xdp_prognum1_touch_data(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
struct datarec *rec;
u32 *cpu_selected;
u32 cpu_dest = 0;
u32 key0 = 0;
u16 eth_type;
/* Only use first entry in cpus_available */
cpu_selected = bpf_map_lookup_elem(&cpus_available, &key0);
if (!cpu_selected)
return XDP_ABORTED;
cpu_dest = *cpu_selected;
/* Validate packet length is minimum Eth header size */
if (eth + 1 > data_end)
return XDP_ABORTED;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
/* Read packet data, and use it (drop non 802.3 Ethertypes) */
eth_type = eth->h_proto;
if (bpf_ntohs(eth_type) < ETH_P_802_3_MIN) {
NO_TEAR_INC(rec->dropped);
return XDP_DROP;
}
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
SEC("xdp")
int xdp_prognum2_round_robin(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct datarec *rec;
u32 cpu_dest = 0;
u32 key0 = 0;
u32 *cpu_selected;
u32 *cpu_iterator;
u32 *cpu_max;
u32 cpu_idx;
cpu_max = bpf_map_lookup_elem(&cpus_count, &key0);
if (!cpu_max)
return XDP_ABORTED;
cpu_iterator = bpf_map_lookup_elem(&cpus_iterator, &key0);
if (!cpu_iterator)
return XDP_ABORTED;
cpu_idx = *cpu_iterator;
*cpu_iterator += 1;
if (*cpu_iterator == *cpu_max)
*cpu_iterator = 0;
cpu_selected = bpf_map_lookup_elem(&cpus_available, &cpu_idx);
if (!cpu_selected)
return XDP_ABORTED;
cpu_dest = *cpu_selected;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
SEC("xdp")
int xdp_prognum3_proto_separate(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
u8 ip_proto = IPPROTO_UDP;
struct datarec *rec;
u16 eth_proto = 0;
u64 l3_offset = 0;
u32 cpu_dest = 0;
u32 *cpu_lookup;
u32 cpu_idx = 0;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
if (!(parse_eth(eth, data_end, &eth_proto, &l3_offset)))
return XDP_PASS; /* Just skip */
/* Extract L4 protocol */
switch (eth_proto) {
case ETH_P_IP:
ip_proto = get_proto_ipv4(ctx, l3_offset);
break;
case ETH_P_IPV6:
ip_proto = get_proto_ipv6(ctx, l3_offset);
break;
case ETH_P_ARP:
cpu_idx = 0; /* ARP packet handled on separate CPU */
break;
default:
cpu_idx = 0;
}
/* Choose CPU based on L4 protocol */
switch (ip_proto) {
case IPPROTO_ICMP:
case IPPROTO_ICMPV6:
cpu_idx = 2;
break;
case IPPROTO_TCP:
cpu_idx = 0;
break;
case IPPROTO_UDP:
cpu_idx = 1;
break;
default:
cpu_idx = 0;
}
cpu_lookup = bpf_map_lookup_elem(&cpus_available, &cpu_idx);
if (!cpu_lookup)
return XDP_ABORTED;
cpu_dest = *cpu_lookup;
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
SEC("xdp")
int xdp_prognum4_ddos_filter_pktgen(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
u8 ip_proto = IPPROTO_UDP;
struct datarec *rec;
u16 eth_proto = 0;
u64 l3_offset = 0;
u32 cpu_dest = 0;
u32 *cpu_lookup;
u32 cpu_idx = 0;
u16 dest_port;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
if (!(parse_eth(eth, data_end, &eth_proto, &l3_offset)))
return XDP_PASS; /* Just skip */
/* Extract L4 protocol */
switch (eth_proto) {
case ETH_P_IP:
ip_proto = get_proto_ipv4(ctx, l3_offset);
break;
case ETH_P_IPV6:
ip_proto = get_proto_ipv6(ctx, l3_offset);
break;
case ETH_P_ARP:
cpu_idx = 0; /* ARP packet handled on separate CPU */
break;
default:
cpu_idx = 0;
}
/* Choose CPU based on L4 protocol */
switch (ip_proto) {
case IPPROTO_ICMP:
case IPPROTO_ICMPV6:
cpu_idx = 2;
break;
case IPPROTO_TCP:
cpu_idx = 0;
break;
case IPPROTO_UDP:
cpu_idx = 1;
/* DDoS filter UDP port 9 (pktgen) */
dest_port = get_dest_port_ipv4_udp(ctx, l3_offset);
if (dest_port == 9) {
NO_TEAR_INC(rec->dropped);
return XDP_DROP;
}
break;
default:
cpu_idx = 0;
}
cpu_lookup = bpf_map_lookup_elem(&cpus_available, &cpu_idx);
if (!cpu_lookup)
return XDP_ABORTED;
cpu_dest = *cpu_lookup;
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
/* Hashing initval */
#define INITVAL 15485863
static __always_inline
u32 get_ipv4_hash_ip_pair(struct xdp_md *ctx, u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct iphdr *iph = data + nh_off;
u32 cpu_hash;
if (iph + 1 > data_end)
return 0;
cpu_hash = iph->saddr + iph->daddr;
cpu_hash = SuperFastHash((char *)&cpu_hash, 4, INITVAL + iph->protocol);
return cpu_hash;
}
static __always_inline
u32 get_ipv6_hash_ip_pair(struct xdp_md *ctx, u64 nh_off)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ipv6hdr *ip6h = data + nh_off;
u32 cpu_hash;
if (ip6h + 1 > data_end)
return 0;
cpu_hash = ip6h->saddr.in6_u.u6_addr32[0] + ip6h->daddr.in6_u.u6_addr32[0];
cpu_hash += ip6h->saddr.in6_u.u6_addr32[1] + ip6h->daddr.in6_u.u6_addr32[1];
cpu_hash += ip6h->saddr.in6_u.u6_addr32[2] + ip6h->daddr.in6_u.u6_addr32[2];
cpu_hash += ip6h->saddr.in6_u.u6_addr32[3] + ip6h->daddr.in6_u.u6_addr32[3];
cpu_hash = SuperFastHash((char *)&cpu_hash, 4, INITVAL + ip6h->nexthdr);
return cpu_hash;
}
/* Load-Balance traffic based on hashing IP-addrs + L4-proto. The
* hashing scheme is symmetric, meaning swapping IP src/dest still hit
* same CPU.
*/
SEC("xdp")
int xdp_prognum5_lb_hash_ip_pairs(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
struct datarec *rec;
u16 eth_proto = 0;
u64 l3_offset = 0;
u32 cpu_dest = 0;
u32 cpu_idx = 0;
u32 *cpu_lookup;
u32 key0 = 0;
u32 *cpu_max;
u32 cpu_hash;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
cpu_max = bpf_map_lookup_elem(&cpus_count, &key0);
if (!cpu_max)
return XDP_ABORTED;
if (!(parse_eth(eth, data_end, &eth_proto, &l3_offset)))
return XDP_PASS; /* Just skip */
/* Hash for IPv4 and IPv6 */
switch (eth_proto) {
case ETH_P_IP:
cpu_hash = get_ipv4_hash_ip_pair(ctx, l3_offset);
break;
case ETH_P_IPV6:
cpu_hash = get_ipv6_hash_ip_pair(ctx, l3_offset);
break;
case ETH_P_ARP: /* ARP packet handled on CPU idx 0 */
default:
cpu_hash = 0;
}
/* Choose CPU based on hash */
cpu_idx = cpu_hash % *cpu_max;
cpu_lookup = bpf_map_lookup_elem(&cpus_available, &cpu_idx);
if (!cpu_lookup)
return XDP_ABORTED;
cpu_dest = *cpu_lookup;
if (cpu_dest >= nr_cpus) {
NO_TEAR_INC(rec->issue);
return XDP_ABORTED;
}
return bpf_redirect_map(&cpu_map, cpu_dest, 0);
}
SEC("xdp/cpumap")
int xdp_redirect_cpu_devmap(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
swap_src_dst_mac(data);
return bpf_redirect_map(&tx_port, 0, 0);
}
SEC("xdp/cpumap")
int xdp_redirect_cpu_pass(struct xdp_md *ctx)
{
return XDP_PASS;
}
SEC("xdp/cpumap")
int xdp_redirect_cpu_drop(struct xdp_md *ctx)
{
return XDP_DROP;
}
SEC("xdp/devmap")
int xdp_redirect_egress_prog(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
__builtin_memcpy(eth->h_source, (const char *)tx_mac_addr, ETH_ALEN);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2017 Jesper Dangaard Brouer, Red Hat, Inc.
*/
static const char *__doc__ =
"XDP CPU redirect tool, using BPF_MAP_TYPE_CPUMAP\n"
"Usage: xdp_redirect_cpu -d <IFINDEX|IFNAME> -c 0 ... -c N\n"
"Valid specification for CPUMAP BPF program:\n"
" --mprog-name/-e pass (use built-in XDP_PASS program)\n"
" --mprog-name/-e drop (use built-in XDP_DROP program)\n"
" --redirect-device/-r <ifindex|ifname> (use built-in DEVMAP redirect program)\n"
" Custom CPUMAP BPF program:\n"
" --mprog-filename/-f <filename> --mprog-name/-e <program>\n"
" Optionally, also pass --redirect-map/-m and --redirect-device/-r together\n"
" to configure DEVMAP in BPF object <filename>\n";
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <sys/sysinfo.h>
#include <getopt.h>
#include <net/if.h>
#include <time.h>
#include <linux/limits.h>
#include <arpa/inet.h>
#include <linux/if_link.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
#include "xdp_sample_user.h"
#include "xdp_redirect_cpu.skel.h"
static int map_fd;
static int avail_fd;
static int count_fd;
static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT |
SAMPLE_CPUMAP_ENQUEUE_CNT | SAMPLE_CPUMAP_KTHREAD_CNT |
SAMPLE_EXCEPTION_CNT;
DEFINE_SAMPLE_INIT(xdp_redirect_cpu);
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "dev", required_argument, NULL, 'd' },
{ "skb-mode", no_argument, NULL, 'S' },
{ "progname", required_argument, NULL, 'p' },
{ "qsize", required_argument, NULL, 'q' },
{ "cpu", required_argument, NULL, 'c' },
{ "stress-mode", no_argument, NULL, 'x' },
{ "force", no_argument, NULL, 'F' },
{ "interval", required_argument, NULL, 'i' },
{ "verbose", no_argument, NULL, 'v' },
{ "stats", no_argument, NULL, 's' },
{ "mprog-name", required_argument, NULL, 'e' },
{ "mprog-filename", required_argument, NULL, 'f' },
{ "redirect-device", required_argument, NULL, 'r' },
{ "redirect-map", required_argument, NULL, 'm' },
{}
};
static void print_avail_progs(struct bpf_object *obj)
{
struct bpf_program *pos;
printf(" Programs to be used for -p/--progname:\n");
bpf_object__for_each_program(pos, obj) {
if (bpf_program__type(pos) == BPF_PROG_TYPE_XDP) {
if (!strncmp(bpf_program__name(pos), "xdp_prognum",
sizeof("xdp_prognum") - 1))
printf(" %s\n", bpf_program__name(pos));
}
}
}
static void usage(char *argv[], const struct option *long_options,
const char *doc, int mask, bool error, struct bpf_object *obj)
{
sample_usage(argv, long_options, doc, mask, error);
print_avail_progs(obj);
}
static int create_cpu_entry(__u32 cpu, struct bpf_cpumap_val *value,
__u32 avail_idx, bool new)
{
__u32 curr_cpus_count = 0;
__u32 key = 0;
int ret;
/* Add a CPU entry to cpumap, as this allocate a cpu entry in
* the kernel for the cpu.
*/
ret = bpf_map_update_elem(map_fd, &cpu, value, 0);
if (ret < 0) {
fprintf(stderr, "Create CPU entry failed: %s\n", strerror(errno));
return ret;
}
/* Inform bpf_prog's that a new CPU is available to select
* from via some control maps.
*/
ret = bpf_map_update_elem(avail_fd, &avail_idx, &cpu, 0);
if (ret < 0) {
fprintf(stderr, "Add to avail CPUs failed: %s\n", strerror(errno));
return ret;
}
/* When not replacing/updating existing entry, bump the count */
ret = bpf_map_lookup_elem(count_fd, &key, &curr_cpus_count);
if (ret < 0) {
fprintf(stderr, "Failed reading curr cpus_count: %s\n",
strerror(errno));
return ret;
}
if (new) {
curr_cpus_count++;
ret = bpf_map_update_elem(count_fd, &key,
&curr_cpus_count, 0);
if (ret < 0) {
fprintf(stderr, "Failed write curr cpus_count: %s\n",
strerror(errno));
return ret;
}
}
printf("%s CPU: %u as idx: %u qsize: %d cpumap_prog_fd: %d (cpus_count: %u)\n",
new ? "Add new" : "Replace", cpu, avail_idx,
value->qsize, value->bpf_prog.fd, curr_cpus_count);
return 0;
}
/* CPUs are zero-indexed. Thus, add a special sentinel default value
* in map cpus_available to mark CPU index'es not configured
*/
static int mark_cpus_unavailable(void)
{
int ret, i, n_cpus = libbpf_num_possible_cpus();
__u32 invalid_cpu = n_cpus;
for (i = 0; i < n_cpus; i++) {
ret = bpf_map_update_elem(avail_fd, &i,
&invalid_cpu, 0);
if (ret < 0) {
fprintf(stderr, "Failed marking CPU unavailable: %s\n",
strerror(errno));
return ret;
}
}
return 0;
}
/* Stress cpumap management code by concurrently changing underlying cpumap */
static void stress_cpumap(void *ctx)
{
struct bpf_cpumap_val *value = ctx;
/* Changing qsize will cause kernel to free and alloc a new
* bpf_cpu_map_entry, with an associated/complicated tear-down
* procedure.
*/
value->qsize = 1024;
create_cpu_entry(1, value, 0, false);
value->qsize = 8;
create_cpu_entry(1, value, 0, false);
value->qsize = 16000;
create_cpu_entry(1, value, 0, false);
}
static int set_cpumap_prog(struct xdp_redirect_cpu *skel,
const char *redir_interface, const char *redir_map,
const char *mprog_filename, const char *mprog_name)
{
if (mprog_filename) {
struct bpf_program *prog;
struct bpf_object *obj;
int ret;
if (!mprog_name) {
fprintf(stderr, "BPF program not specified for file %s\n",
mprog_filename);
goto end;
}
if ((redir_interface && !redir_map) || (!redir_interface && redir_map)) {
fprintf(stderr, "--redirect-%s specified but --redirect-%s not specified\n",
redir_interface ? "device" : "map", redir_interface ? "map" : "device");
goto end;
}
/* Custom BPF program */
obj = bpf_object__open_file(mprog_filename, NULL);
if (!obj) {
ret = -errno;
fprintf(stderr, "Failed to bpf_prog_load_xattr: %s\n",
strerror(errno));
return ret;
}
ret = bpf_object__load(obj);
if (ret < 0) {
ret = -errno;
fprintf(stderr, "Failed to bpf_object__load: %s\n",
strerror(errno));
return ret;
}
if (redir_map) {
int err, redir_map_fd, ifindex_out, key = 0;
redir_map_fd = bpf_object__find_map_fd_by_name(obj, redir_map);
if (redir_map_fd < 0) {
fprintf(stderr, "Failed to bpf_object__find_map_fd_by_name: %s\n",
strerror(errno));
return redir_map_fd;
}
ifindex_out = if_nametoindex(redir_interface);
if (!ifindex_out)
ifindex_out = strtoul(redir_interface, NULL, 0);
if (!ifindex_out) {
fprintf(stderr, "Bad interface name or index\n");
return -EINVAL;
}
err = bpf_map_update_elem(redir_map_fd, &key, &ifindex_out, 0);
if (err < 0)
return err;
}
prog = bpf_object__find_program_by_name(obj, mprog_name);
if (!prog) {
ret = -errno;
fprintf(stderr, "Failed to bpf_object__find_program_by_name: %s\n",
strerror(errno));
return ret;
}
return bpf_program__fd(prog);
} else {
if (mprog_name) {
if (redir_interface || redir_map) {
fprintf(stderr, "Need to specify --mprog-filename/-f\n");
goto end;
}
if (!strcmp(mprog_name, "pass") || !strcmp(mprog_name, "drop")) {
/* Use built-in pass/drop programs */
return *mprog_name == 'p' ? bpf_program__fd(skel->progs.xdp_redirect_cpu_pass)
: bpf_program__fd(skel->progs.xdp_redirect_cpu_drop);
} else {
fprintf(stderr, "Unknown name \"%s\" for built-in BPF program\n",
mprog_name);
goto end;
}
} else {
if (redir_map) {
fprintf(stderr, "Need to specify --mprog-filename, --mprog-name and"
" --redirect-device with --redirect-map\n");
goto end;
}
if (redir_interface) {
/* Use built-in devmap redirect */
struct bpf_devmap_val val = {};
int ifindex_out, err;
__u32 key = 0;
if (!redir_interface)
return 0;
ifindex_out = if_nametoindex(redir_interface);
if (!ifindex_out)
ifindex_out = strtoul(redir_interface, NULL, 0);
if (!ifindex_out) {
fprintf(stderr, "Bad interface name or index\n");
return -EINVAL;
}
if (get_mac_addr(ifindex_out, skel->bss->tx_mac_addr) < 0) {
printf("Get interface %d mac failed\n", ifindex_out);
return -EINVAL;
}
val.ifindex = ifindex_out;
val.bpf_prog.fd = bpf_program__fd(skel->progs.xdp_redirect_egress_prog);
err = bpf_map_update_elem(bpf_map__fd(skel->maps.tx_port), &key, &val, 0);
if (err < 0)
return -errno;
return bpf_program__fd(skel->progs.xdp_redirect_cpu_devmap);
}
}
}
/* Disabled */
return 0;
end:
fprintf(stderr, "Invalid options for CPUMAP BPF program\n");
return -EINVAL;
}
int main(int argc, char **argv)
{
const char *redir_interface = NULL, *redir_map = NULL;
const char *mprog_filename = NULL, *mprog_name = NULL;
struct xdp_redirect_cpu *skel;
struct bpf_map_info info = {};
struct bpf_cpumap_val value;
__u32 infosz = sizeof(info);
int ret = EXIT_FAIL_OPTION;
unsigned long interval = 2;
bool stress_mode = false;
struct bpf_program *prog;
const char *prog_name;
bool generic = false;
bool force = false;
int added_cpus = 0;
bool error = true;
int longindex = 0;
int add_cpu = -1;
int ifindex = -1;
int *cpu, i, opt;
__u32 qsize;
int n_cpus;
n_cpus = libbpf_num_possible_cpus();
/* Notice: Choosing the queue size is very important when CPU is
* configured with power-saving states.
*
* If deepest state take 133 usec to wakeup from (133/10^6). When link
* speed is 10Gbit/s ((10*10^9/8) in bytes/sec). How many bytes can
* arrive with in 133 usec at this speed: (10*10^9/8)*(133/10^6) =
* 166250 bytes. With MTU size packets this is 110 packets, and with
* minimum Ethernet (MAC-preamble + intergap) 84 bytes is 1979 packets.
*
* Setting default cpumap queue to 2048 as worst-case (small packet)
* should be +64 packet due kthread wakeup call (due to xdp_do_flush)
* worst-case is 2043 packets.
*
* Sysadm can configured system to avoid deep-sleep via:
* tuned-adm profile network-latency
*/
qsize = 2048;
skel = xdp_redirect_cpu__open();
if (!skel) {
fprintf(stderr, "Failed to xdp_redirect_cpu__open: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end;
}
ret = sample_init_pre_load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
if (bpf_map__set_max_entries(skel->maps.cpu_map, n_cpus) < 0) {
fprintf(stderr, "Failed to set max entries for cpu_map map: %s",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
if (bpf_map__set_max_entries(skel->maps.cpus_available, n_cpus) < 0) {
fprintf(stderr, "Failed to set max entries for cpus_available map: %s",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
cpu = calloc(n_cpus, sizeof(int));
if (!cpu) {
fprintf(stderr, "Failed to allocate cpu array\n");
goto end_destroy;
}
prog = skel->progs.xdp_prognum5_lb_hash_ip_pairs;
while ((opt = getopt_long(argc, argv, "d:si:Sxp:f:e:r:m:c:q:Fvh",
long_options, &longindex)) != -1) {
switch (opt) {
case 'd':
if (strlen(optarg) >= IF_NAMESIZE) {
fprintf(stderr, "-d/--dev name too long\n");
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
ifindex = if_nametoindex(optarg);
if (!ifindex)
ifindex = strtoul(optarg, NULL, 0);
if (!ifindex) {
fprintf(stderr, "Bad interface index or name (%d): %s\n",
errno, strerror(errno));
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
break;
case 's':
mask |= SAMPLE_REDIRECT_MAP_CNT;
break;
case 'i':
interval = strtoul(optarg, NULL, 0);
break;
case 'S':
generic = true;
break;
case 'x':
stress_mode = true;
break;
case 'p':
/* Selecting eBPF prog to load */
prog_name = optarg;
prog = bpf_object__find_program_by_name(skel->obj,
prog_name);
if (!prog) {
fprintf(stderr,
"Failed to find program %s specified by"
" option -p/--progname\n",
prog_name);
print_avail_progs(skel->obj);
goto end_cpu;
}
break;
case 'f':
mprog_filename = optarg;
break;
case 'e':
mprog_name = optarg;
break;
case 'r':
redir_interface = optarg;
mask |= SAMPLE_DEVMAP_XMIT_CNT_MULTI;
break;
case 'm':
redir_map = optarg;
break;
case 'c':
/* Add multiple CPUs */
add_cpu = strtoul(optarg, NULL, 0);
if (add_cpu >= n_cpus) {
fprintf(stderr,
"--cpu nr too large for cpumap err (%d):%s\n",
errno, strerror(errno));
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
cpu[added_cpus++] = add_cpu;
break;
case 'q':
qsize = strtoul(optarg, NULL, 0);
break;
case 'F':
force = true;
break;
case 'v':
sample_switch_mode();
break;
case 'h':
error = false;
default:
usage(argv, long_options, __doc__, mask, error, skel->obj);
goto end_cpu;
}
}
ret = EXIT_FAIL_OPTION;
if (ifindex == -1) {
fprintf(stderr, "Required option --dev missing\n");
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
if (add_cpu == -1) {
fprintf(stderr, "Required option --cpu missing\n"
"Specify multiple --cpu option to add more\n");
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
skel->rodata->from_match[0] = ifindex;
if (redir_interface)
skel->rodata->to_match[0] = if_nametoindex(redir_interface);
ret = xdp_redirect_cpu__load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to xdp_redirect_cpu__load: %s\n",
strerror(errno));
goto end_cpu;
}
ret = bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.cpu_map), &info, &infosz);
if (ret < 0) {
fprintf(stderr, "Failed bpf_map_get_info_by_fd for cpumap: %s\n",
strerror(errno));
goto end_cpu;
}
skel->bss->cpumap_map_id = info.id;
map_fd = bpf_map__fd(skel->maps.cpu_map);
avail_fd = bpf_map__fd(skel->maps.cpus_available);
count_fd = bpf_map__fd(skel->maps.cpus_count);
ret = mark_cpus_unavailable();
if (ret < 0) {
fprintf(stderr, "Unable to mark CPUs as unavailable\n");
goto end_cpu;
}
ret = sample_init(skel, mask);
if (ret < 0) {
fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_cpu;
}
value.bpf_prog.fd = set_cpumap_prog(skel, redir_interface, redir_map,
mprog_filename, mprog_name);
if (value.bpf_prog.fd < 0) {
fprintf(stderr, "Failed to set CPUMAP BPF program: %s\n",
strerror(-value.bpf_prog.fd));
usage(argv, long_options, __doc__, mask, true, skel->obj);
ret = EXIT_FAIL_BPF;
goto end_cpu;
}
value.qsize = qsize;
for (i = 0; i < added_cpus; i++) {
if (create_cpu_entry(cpu[i], &value, i, true) < 0) {
fprintf(stderr, "Cannot proceed, exiting\n");
usage(argv, long_options, __doc__, mask, true, skel->obj);
goto end_cpu;
}
}
ret = EXIT_FAIL_XDP;
if (sample_install_xdp(prog, ifindex, generic, force) < 0)
goto end_cpu;
ret = sample_run(interval, stress_mode ? stress_cpumap : NULL, &value);
if (ret < 0) {
fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_cpu;
}
ret = EXIT_OK;
end_cpu:
free(cpu);
end_destroy:
xdp_redirect_cpu__destroy(skel);
end:
sample_exit(ret);
}
/* Copyright (c) 2017 Covalent IO, Inc. http://covalent.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#define KBUILD_MODNAME "foo"
#include "vmlinux.h"
#include "xdp_sample.bpf.h"
#include "xdp_sample_shared.h"
/* The 2nd xdp prog on egress does not support skb mode, so we define two
* maps, tx_port_general and tx_port_native.
*/
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
__uint(max_entries, 1);
} tx_port_general SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(struct bpf_devmap_val));
__uint(max_entries, 1);
} tx_port_native SEC(".maps");
/* store egress interface mac address */
const volatile __u8 tx_mac_addr[ETH_ALEN];
static __always_inline int xdp_redirect_map(struct xdp_md *ctx, void *redirect_map)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = bpf_get_smp_processor_id();
struct ethhdr *eth = data;
struct datarec *rec;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
swap_src_dst_mac(data);
return bpf_redirect_map(redirect_map, 0, 0);
}
SEC("xdp")
int xdp_redirect_map_general(struct xdp_md *ctx)
{
return xdp_redirect_map(ctx, &tx_port_general);
}
SEC("xdp")
int xdp_redirect_map_native(struct xdp_md *ctx)
{
return xdp_redirect_map(ctx, &tx_port_native);
}
SEC("xdp/devmap")
int xdp_redirect_map_egress(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u8 *mac_addr = (u8 *) tx_mac_addr;
struct ethhdr *eth = data;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
barrier_var(mac_addr); /* prevent optimizing out memcpy */
__builtin_memcpy(eth->h_source, mac_addr, ETH_ALEN);
return XDP_PASS;
}
/* Redirect require an XDP bpf_prog loaded on the TX device */
SEC("xdp")
int xdp_redirect_dummy_prog(struct xdp_md *ctx)
{
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
#define KBUILD_MODNAME "foo"
#include "vmlinux.h"
#include "xdp_sample.bpf.h"
#include "xdp_sample_shared.h"
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP_HASH);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
__uint(max_entries, 32);
} forward_map_general SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_DEVMAP_HASH);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(struct bpf_devmap_val));
__uint(max_entries, 32);
} forward_map_native SEC(".maps");
/* map to store egress interfaces mac addresses */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, u32);
__type(value, __be64);
__uint(max_entries, 32);
} mac_map SEC(".maps");
static int xdp_redirect_map(struct xdp_md *ctx, void *forward_map)
{
u32 key = bpf_get_smp_processor_id();
struct datarec *rec;
rec = bpf_map_lookup_elem(&rx_cnt, &key);
if (!rec)
return XDP_PASS;
NO_TEAR_INC(rec->processed);
return bpf_redirect_map(forward_map, 0,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
}
SEC("xdp")
int xdp_redirect_map_general(struct xdp_md *ctx)
{
return xdp_redirect_map(ctx, &forward_map_general);
}
SEC("xdp")
int xdp_redirect_map_native(struct xdp_md *ctx)
{
return xdp_redirect_map(ctx, &forward_map_native);
}
SEC("xdp/devmap")
int xdp_devmap_prog(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
u32 key = ctx->egress_ifindex;
struct ethhdr *eth = data;
__be64 *mac;
u64 nh_off;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
return XDP_DROP;
mac = bpf_map_lookup_elem(&mac_map, &key);
if (mac)
__builtin_memcpy(eth->h_source, mac, ETH_ALEN);
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0
static const char *__doc__ =
"XDP multi redirect tool, using BPF_MAP_TYPE_DEVMAP and BPF_F_BROADCAST flag for bpf_redirect_map\n"
"Usage: xdp_redirect_map_multi <IFINDEX|IFNAME> <IFINDEX|IFNAME> ... <IFINDEX|IFNAME>\n";
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <assert.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
#include "xdp_sample_user.h"
#include "xdp_redirect_map_multi.skel.h"
#define MAX_IFACE_NUM 32
static int ifaces[MAX_IFACE_NUM] = {};
static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT |
SAMPLE_EXCEPTION_CNT | SAMPLE_DEVMAP_XMIT_CNT |
SAMPLE_DEVMAP_XMIT_CNT_MULTI | SAMPLE_SKIP_HEADING;
DEFINE_SAMPLE_INIT(xdp_redirect_map_multi);
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "skb-mode", no_argument, NULL, 'S' },
{ "force", no_argument, NULL, 'F' },
{ "load-egress", no_argument, NULL, 'X' },
{ "stats", no_argument, NULL, 's' },
{ "interval", required_argument, NULL, 'i' },
{ "verbose", no_argument, NULL, 'v' },
{}
};
static int update_mac_map(struct bpf_map *map)
{
int mac_map_fd = bpf_map__fd(map);
unsigned char mac_addr[6];
unsigned int ifindex;
int i, ret = -1;
for (i = 0; ifaces[i] > 0; i++) {
ifindex = ifaces[i];
ret = get_mac_addr(ifindex, mac_addr);
if (ret < 0) {
fprintf(stderr, "get interface %d mac failed\n",
ifindex);
return ret;
}
ret = bpf_map_update_elem(mac_map_fd, &ifindex, mac_addr, 0);
if (ret < 0) {
fprintf(stderr, "Failed to update mac address for ifindex %d\n",
ifindex);
return ret;
}
}
return 0;
}
int main(int argc, char **argv)
{
struct bpf_devmap_val devmap_val = {};
struct xdp_redirect_map_multi *skel;
struct bpf_program *ingress_prog;
bool xdp_devmap_attached = false;
struct bpf_map *forward_map;
int ret = EXIT_FAIL_OPTION;
unsigned long interval = 2;
char ifname[IF_NAMESIZE];
unsigned int ifindex;
bool generic = false;
bool force = false;
bool tried = false;
bool error = true;
int i, opt;
while ((opt = getopt_long(argc, argv, "hSFXi:vs",
long_options, NULL)) != -1) {
switch (opt) {
case 'S':
generic = true;
/* devmap_xmit tracepoint not available */
mask &= ~(SAMPLE_DEVMAP_XMIT_CNT |
SAMPLE_DEVMAP_XMIT_CNT_MULTI);
break;
case 'F':
force = true;
break;
case 'X':
xdp_devmap_attached = true;
break;
case 'i':
interval = strtoul(optarg, NULL, 0);
break;
case 'v':
sample_switch_mode();
break;
case 's':
mask |= SAMPLE_REDIRECT_MAP_CNT;
break;
case 'h':
error = false;
default:
sample_usage(argv, long_options, __doc__, mask, error);
return ret;
}
}
if (argc <= optind + 1) {
sample_usage(argv, long_options, __doc__, mask, error);
return ret;
}
skel = xdp_redirect_map_multi__open();
if (!skel) {
fprintf(stderr, "Failed to xdp_redirect_map_multi__open: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end;
}
ret = sample_init_pre_load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = EXIT_FAIL_OPTION;
for (i = 0; i < MAX_IFACE_NUM && argv[optind + i]; i++) {
ifaces[i] = if_nametoindex(argv[optind + i]);
if (!ifaces[i])
ifaces[i] = strtoul(argv[optind + i], NULL, 0);
if (!if_indextoname(ifaces[i], ifname)) {
fprintf(stderr, "Bad interface index or name\n");
sample_usage(argv, long_options, __doc__, mask, true);
goto end_destroy;
}
skel->rodata->from_match[i] = ifaces[i];
skel->rodata->to_match[i] = ifaces[i];
}
ret = xdp_redirect_map_multi__load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to xdp_redirect_map_multi__load: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
if (xdp_devmap_attached) {
/* Update mac_map with all egress interfaces' mac addr */
if (update_mac_map(skel->maps.mac_map) < 0) {
fprintf(stderr, "Updating mac address failed\n");
ret = EXIT_FAIL;
goto end_destroy;
}
}
ret = sample_init(skel, mask);
if (ret < 0) {
fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ingress_prog = skel->progs.xdp_redirect_map_native;
forward_map = skel->maps.forward_map_native;
for (i = 0; ifaces[i] > 0; i++) {
ifindex = ifaces[i];
ret = EXIT_FAIL_XDP;
restart:
/* bind prog_fd to each interface */
if (sample_install_xdp(ingress_prog, ifindex, generic, force) < 0) {
if (generic && !tried) {
fprintf(stderr,
"Trying fallback to sizeof(int) as value_size for devmap in generic mode\n");
ingress_prog = skel->progs.xdp_redirect_map_general;
forward_map = skel->maps.forward_map_general;
tried = true;
goto restart;
}
goto end_destroy;
}
/* Add all the interfaces to forward group and attach
* egress devmap program if exist
*/
devmap_val.ifindex = ifindex;
if (xdp_devmap_attached)
devmap_val.bpf_prog.fd = bpf_program__fd(skel->progs.xdp_devmap_prog);
ret = bpf_map_update_elem(bpf_map__fd(forward_map), &ifindex, &devmap_val, 0);
if (ret < 0) {
fprintf(stderr, "Failed to update devmap value: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
}
ret = sample_run(interval, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ret = EXIT_OK;
end_destroy:
xdp_redirect_map_multi__destroy(skel);
end:
sample_exit(ret);
}
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2017 Covalent IO, Inc. http://covalent.io
*/
static const char *__doc__ =
"XDP redirect tool, using BPF_MAP_TYPE_DEVMAP\n"
"Usage: xdp_redirect_map <IFINDEX|IFNAME>_IN <IFINDEX|IFNAME>_OUT\n";
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <net/if.h>
#include <unistd.h>
#include <libgen.h>
#include <getopt.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
#include "xdp_sample_user.h"
#include "xdp_redirect_map.skel.h"
static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT |
SAMPLE_EXCEPTION_CNT | SAMPLE_DEVMAP_XMIT_CNT_MULTI;
DEFINE_SAMPLE_INIT(xdp_redirect_map);
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "skb-mode", no_argument, NULL, 'S' },
{ "force", no_argument, NULL, 'F' },
{ "load-egress", no_argument, NULL, 'X' },
{ "stats", no_argument, NULL, 's' },
{ "interval", required_argument, NULL, 'i' },
{ "verbose", no_argument, NULL, 'v' },
{}
};
static int verbose = 0;
int main(int argc, char **argv)
{
struct bpf_devmap_val devmap_val = {};
bool xdp_devmap_attached = false;
struct xdp_redirect_map *skel;
char str[2 * IF_NAMESIZE + 1];
char ifname_out[IF_NAMESIZE];
struct bpf_map *tx_port_map;
char ifname_in[IF_NAMESIZE];
int ifindex_in, ifindex_out;
unsigned long interval = 2;
int ret = EXIT_FAIL_OPTION;
struct bpf_program *prog;
bool generic = false;
bool force = false;
bool tried = false;
bool error = true;
int opt, key = 0;
while ((opt = getopt_long(argc, argv, "hSFXi:vs",
long_options, NULL)) != -1) {
switch (opt) {
case 'S':
generic = true;
/* devmap_xmit tracepoint not available */
mask &= ~(SAMPLE_DEVMAP_XMIT_CNT |
SAMPLE_DEVMAP_XMIT_CNT_MULTI);
break;
case 'F':
force = true;
break;
case 'X':
xdp_devmap_attached = true;
break;
case 'i':
interval = strtoul(optarg, NULL, 0);
break;
case 'v':
sample_switch_mode();
verbose = 1;
break;
case 's':
mask |= SAMPLE_REDIRECT_MAP_CNT;
break;
case 'h':
error = false;
default:
sample_usage(argv, long_options, __doc__, mask, error);
return ret;
}
}
if (argc <= optind + 1) {
sample_usage(argv, long_options, __doc__, mask, true);
goto end;
}
ifindex_in = if_nametoindex(argv[optind]);
if (!ifindex_in)
ifindex_in = strtoul(argv[optind], NULL, 0);
ifindex_out = if_nametoindex(argv[optind + 1]);
if (!ifindex_out)
ifindex_out = strtoul(argv[optind + 1], NULL, 0);
if (!ifindex_in || !ifindex_out) {
fprintf(stderr, "Bad interface index or name\n");
sample_usage(argv, long_options, __doc__, mask, true);
goto end;
}
skel = xdp_redirect_map__open();
if (!skel) {
fprintf(stderr, "Failed to xdp_redirect_map__open: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end;
}
ret = sample_init_pre_load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
/* Load 2nd xdp prog on egress. */
if (xdp_devmap_attached) {
ret = get_mac_addr(ifindex_out, skel->rodata->tx_mac_addr);
if (ret < 0) {
fprintf(stderr, "Failed to get interface %d mac address: %s\n",
ifindex_out, strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
if (verbose)
printf("Egress ifindex:%d using src MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
ifindex_out,
skel->rodata->tx_mac_addr[0], skel->rodata->tx_mac_addr[1],
skel->rodata->tx_mac_addr[2], skel->rodata->tx_mac_addr[3],
skel->rodata->tx_mac_addr[4], skel->rodata->tx_mac_addr[5]);
}
skel->rodata->from_match[0] = ifindex_in;
skel->rodata->to_match[0] = ifindex_out;
ret = xdp_redirect_map__load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to xdp_redirect_map__load: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = sample_init(skel, mask);
if (ret < 0) {
fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
prog = skel->progs.xdp_redirect_map_native;
tx_port_map = skel->maps.tx_port_native;
restart:
if (sample_install_xdp(prog, ifindex_in, generic, force) < 0) {
/* First try with struct bpf_devmap_val as value for generic
* mode, then fallback to sizeof(int) for older kernels.
*/
fprintf(stderr,
"Trying fallback to sizeof(int) as value_size for devmap in generic mode\n");
if (generic && !tried) {
prog = skel->progs.xdp_redirect_map_general;
tx_port_map = skel->maps.tx_port_general;
tried = true;
goto restart;
}
ret = EXIT_FAIL_XDP;
goto end_destroy;
}
/* Loading dummy XDP prog on out-device */
sample_install_xdp(skel->progs.xdp_redirect_dummy_prog, ifindex_out, generic, force);
devmap_val.ifindex = ifindex_out;
if (xdp_devmap_attached)
devmap_val.bpf_prog.fd = bpf_program__fd(skel->progs.xdp_redirect_map_egress);
ret = bpf_map_update_elem(bpf_map__fd(tx_port_map), &key, &devmap_val, 0);
if (ret < 0) {
fprintf(stderr, "Failed to update devmap value: %s\n",
strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = EXIT_FAIL;
if (!if_indextoname(ifindex_in, ifname_in)) {
fprintf(stderr, "Failed to if_indextoname for %d: %s\n", ifindex_in,
strerror(errno));
goto end_destroy;
}
if (!if_indextoname(ifindex_out, ifname_out)) {
fprintf(stderr, "Failed to if_indextoname for %d: %s\n", ifindex_out,
strerror(errno));
goto end_destroy;
}
safe_strncpy(str, get_driver_name(ifindex_in), sizeof(str));
printf("Redirecting from %s (ifindex %d; driver %s) to %s (ifindex %d; driver %s)\n",
ifname_in, ifindex_in, str, ifname_out, ifindex_out, get_driver_name(ifindex_out));
snprintf(str, sizeof(str), "%s->%s", ifname_in, ifname_out);
ret = sample_run(interval, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ret = EXIT_OK;
end_destroy:
xdp_redirect_map__destroy(skel);
end:
sample_exit(ret);
}
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2016 John Fastabend <john.r.fastabend@intel.com>
*/
static const char *__doc__ =
"XDP redirect tool, using bpf_redirect helper\n"
"Usage: xdp_redirect <IFINDEX|IFNAME>_IN <IFINDEX|IFNAME>_OUT\n";
#include <linux/bpf.h>
#include <linux/if_link.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <net/if.h>
#include <unistd.h>
#include <libgen.h>
#include <getopt.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
#include "xdp_sample_user.h"
#include "xdp_redirect.skel.h"
static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_CNT |
SAMPLE_EXCEPTION_CNT | SAMPLE_DEVMAP_XMIT_CNT_MULTI;
DEFINE_SAMPLE_INIT(xdp_redirect);
static const struct option long_options[] = {
{"help", no_argument, NULL, 'h' },
{"skb-mode", no_argument, NULL, 'S' },
{"force", no_argument, NULL, 'F' },
{"stats", no_argument, NULL, 's' },
{"interval", required_argument, NULL, 'i' },
{"verbose", no_argument, NULL, 'v' },
{}
};
int main(int argc, char **argv)
{
int ifindex_in, ifindex_out, opt;
char str[2 * IF_NAMESIZE + 1];
char ifname_out[IF_NAMESIZE];
char ifname_in[IF_NAMESIZE];
int ret = EXIT_FAIL_OPTION;
unsigned long interval = 2;
struct xdp_redirect *skel;
bool generic = false;
bool force = false;
bool error = true;
while ((opt = getopt_long(argc, argv, "hSFi:vs",
long_options, NULL)) != -1) {
switch (opt) {
case 'S':
generic = true;
mask &= ~(SAMPLE_DEVMAP_XMIT_CNT |
SAMPLE_DEVMAP_XMIT_CNT_MULTI);
break;
case 'F':
force = true;
break;
case 'i':
interval = strtoul(optarg, NULL, 0);
break;
case 'v':
sample_switch_mode();
break;
case 's':
mask |= SAMPLE_REDIRECT_CNT;
break;
case 'h':
error = false;
default:
sample_usage(argv, long_options, __doc__, mask, error);
return ret;
}
}
if (argc <= optind + 1) {
sample_usage(argv, long_options, __doc__, mask, true);
return ret;
}
ifindex_in = if_nametoindex(argv[optind]);
if (!ifindex_in)
ifindex_in = strtoul(argv[optind], NULL, 0);
ifindex_out = if_nametoindex(argv[optind + 1]);
if (!ifindex_out)
ifindex_out = strtoul(argv[optind + 1], NULL, 0);
if (!ifindex_in || !ifindex_out) {
fprintf(stderr, "Bad interface index or name\n");
sample_usage(argv, long_options, __doc__, mask, true);
goto end;
}
skel = xdp_redirect__open();
if (!skel) {
fprintf(stderr, "Failed to xdp_redirect__open: %s\n", strerror(errno));
ret = EXIT_FAIL_BPF;
goto end;
}
ret = sample_init_pre_load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to sample_init_pre_load: %s\n", strerror(-ret));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
skel->rodata->from_match[0] = ifindex_in;
skel->rodata->to_match[0] = ifindex_out;
skel->rodata->ifindex_out = ifindex_out;
ret = xdp_redirect__load(skel);
if (ret < 0) {
fprintf(stderr, "Failed to xdp_redirect__load: %s\n", strerror(errno));
ret = EXIT_FAIL_BPF;
goto end_destroy;
}
ret = sample_init(skel, mask);
if (ret < 0) {
fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ret = EXIT_FAIL_XDP;
if (sample_install_xdp(skel->progs.xdp_redirect_prog, ifindex_in,
generic, force) < 0)
goto end_destroy;
/* Loading dummy XDP prog on out-device */
sample_install_xdp(skel->progs.xdp_redirect_dummy_prog, ifindex_out,
generic, force);
ret = EXIT_FAIL;
if (!if_indextoname(ifindex_in, ifname_in)) {
fprintf(stderr, "Failed to if_indextoname for %d: %s\n", ifindex_in,
strerror(errno));
goto end_destroy;
}
if (!if_indextoname(ifindex_out, ifname_out)) {
fprintf(stderr, "Failed to if_indextoname for %d: %s\n", ifindex_out,
strerror(errno));
goto end_destroy;
}
safe_strncpy(str, get_driver_name(ifindex_in), sizeof(str));
printf("Redirecting from %s (ifindex %d; driver %s) to %s (ifindex %d; driver %s)\n",
ifname_in, ifindex_in, str, ifname_out, ifindex_out, get_driver_name(ifindex_out));
snprintf(str, sizeof(str), "%s->%s", ifname_in, ifname_out);
ret = sample_run(interval, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret));
ret = EXIT_FAIL;
goto end_destroy;
}
ret = EXIT_OK;
end_destroy:
xdp_redirect__destroy(skel);
end:
sample_exit(ret);
}
/* SPDX-License-Identifier: GPL-2.0
* Copyright (c) 2017 Jesper Dangaard Brouer, Red Hat Inc.
*
* Example howto extract XDP RX-queue info
*/
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/in.h>
#include <bpf/bpf_helpers.h>
/* Config setup from with userspace
*
* User-side setup ifindex in config_map, to verify that
* ctx->ingress_ifindex is correct (against configured ifindex)
*/
struct config {
__u32 action;
int ifindex;
__u32 options;
};
enum cfg_options_flags {
NO_TOUCH = 0x0U,
READ_MEM = 0x1U,
SWAP_MAC = 0x2U,
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct config);
__uint(max_entries, 1);
} config_map SEC(".maps");
/* Common stats data record (shared with userspace) */
struct datarec {
__u64 processed;
__u64 issue;
};
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct datarec);
__uint(max_entries, 1);
} stats_global_map SEC(".maps");
#define MAX_RXQs 64
/* Stats per rx_queue_index (per CPU) */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__type(key, u32);
__type(value, struct datarec);
__uint(max_entries, MAX_RXQs + 1);
} rx_queue_index_map SEC(".maps");
static __always_inline
void swap_src_dst_mac(void *data)
{
unsigned short *p = data;
unsigned short dst[3];
dst[0] = p[0];
dst[1] = p[1];
dst[2] = p[2];
p[0] = p[3];
p[1] = p[4];
p[2] = p[5];
p[3] = dst[0];
p[4] = dst[1];
p[5] = dst[2];
}
SEC("xdp_prog0")
int xdp_prognum0(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct datarec *rec, *rxq_rec;
int ingress_ifindex;
struct config *config;
u32 key = 0;
/* Global stats record */
rec = bpf_map_lookup_elem(&stats_global_map, &key);
if (!rec)
return XDP_ABORTED;
rec->processed++;
/* Accessing ctx->ingress_ifindex, cause BPF to rewrite BPF
* instructions inside kernel to access xdp_rxq->dev->ifindex
*/
ingress_ifindex = ctx->ingress_ifindex;
config = bpf_map_lookup_elem(&config_map, &key);
if (!config)
return XDP_ABORTED;
/* Simple test: check ctx provided ifindex is as expected */
if (ingress_ifindex != config->ifindex) {
/* count this error case */
rec->issue++;
return XDP_ABORTED;
}
/* Update stats per rx_queue_index. Handle if rx_queue_index
* is larger than stats map can contain info for.
*/
key = ctx->rx_queue_index;
if (key >= MAX_RXQs)
key = MAX_RXQs;
rxq_rec = bpf_map_lookup_elem(&rx_queue_index_map, &key);
if (!rxq_rec)
return XDP_ABORTED;
rxq_rec->processed++;
if (key == MAX_RXQs)
rxq_rec->issue++;
/* Default: Don't touch packet data, only count packets */
if (unlikely(config->options & (READ_MEM|SWAP_MAC))) {
struct ethhdr *eth = data;
if (eth + 1 > data_end)
return XDP_ABORTED;
/* Avoid compiler removing this: Drop non 802.3 Ethertypes */
if (ntohs(eth->h_proto) < ETH_P_802_3_MIN)
return XDP_ABORTED;
/* XDP_TX requires changing MAC-addrs, else HW may drop.
* Can also be enabled with --swapmac (for test purposes)
*/
if (unlikely(config->options & SWAP_MAC))
swap_src_dst_mac(data);
}
return config->action;
}
char _license[] SEC("license") = "GPL";
/* SPDX-License-Identifier: GPL-2.0
* Copyright (c) 2017 Jesper Dangaard Brouer, Red Hat Inc.
*/
static const char *__doc__ = " XDP RX-queue info extract example\n\n"
"Monitor how many packets per sec (pps) are received\n"
"per NIC RX queue index and which CPU processed the packet\n"
;
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <getopt.h>
#include <net/if.h>
#include <time.h>
#include <limits.h>
#include <arpa/inet.h>
#include <linux/if_link.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_util.h"
static int ifindex = -1;
static char ifname_buf[IF_NAMESIZE];
static char *ifname;
static __u32 prog_id;
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static struct bpf_map *stats_global_map;
static struct bpf_map *rx_queue_index_map;
/* Exit return codes */
#define EXIT_OK 0
#define EXIT_FAIL 1
#define EXIT_FAIL_OPTION 2
#define EXIT_FAIL_XDP 3
#define EXIT_FAIL_BPF 4
#define EXIT_FAIL_MEM 5
#define FAIL_MEM_SIG INT_MAX
#define FAIL_STAT_SIG (INT_MAX - 1)
static const struct option long_options[] = {
{"help", no_argument, NULL, 'h' },
{"dev", required_argument, NULL, 'd' },
{"skb-mode", no_argument, NULL, 'S' },
{"sec", required_argument, NULL, 's' },
{"no-separators", no_argument, NULL, 'z' },
{"action", required_argument, NULL, 'a' },
{"readmem", no_argument, NULL, 'r' },
{"swapmac", no_argument, NULL, 'm' },
{"force", no_argument, NULL, 'F' },
{0, 0, NULL, 0 }
};
static void int_exit(int sig)
{
__u32 curr_prog_id = 0;
if (ifindex > -1) {
if (bpf_xdp_query_id(ifindex, xdp_flags, &curr_prog_id)) {
printf("bpf_xdp_query_id failed\n");
exit(EXIT_FAIL);
}
if (prog_id == curr_prog_id) {
fprintf(stderr,
"Interrupted: Removing XDP program on ifindex:%d device:%s\n",
ifindex, ifname);
bpf_xdp_detach(ifindex, xdp_flags, NULL);
} else if (!curr_prog_id) {
printf("couldn't find a prog id on a given iface\n");
} else {
printf("program on interface changed, not removing\n");
}
}
if (sig == FAIL_MEM_SIG)
exit(EXIT_FAIL_MEM);
else if (sig == FAIL_STAT_SIG)
exit(EXIT_FAIL);
exit(EXIT_OK);
}
struct config {
__u32 action;
int ifindex;
__u32 options;
};
enum cfg_options_flags {
NO_TOUCH = 0x0U,
READ_MEM = 0x1U,
SWAP_MAC = 0x2U,
};
#define XDP_ACTION_MAX (XDP_TX + 1)
#define XDP_ACTION_MAX_STRLEN 11
static const char *xdp_action_names[XDP_ACTION_MAX] = {
[XDP_ABORTED] = "XDP_ABORTED",
[XDP_DROP] = "XDP_DROP",
[XDP_PASS] = "XDP_PASS",
[XDP_TX] = "XDP_TX",
};
static const char *action2str(int action)
{
if (action < XDP_ACTION_MAX)
return xdp_action_names[action];
return NULL;
}
static int parse_xdp_action(char *action_str)
{
size_t maxlen;
__u64 action = -1;
int i;
for (i = 0; i < XDP_ACTION_MAX; i++) {
maxlen = XDP_ACTION_MAX_STRLEN;
if (strncmp(xdp_action_names[i], action_str, maxlen) == 0) {
action = i;
break;
}
}
return action;
}
static void list_xdp_actions(void)
{
int i;
printf("Available XDP --action <options>\n");
for (i = 0; i < XDP_ACTION_MAX; i++)
printf("\t%s\n", xdp_action_names[i]);
printf("\n");
}
static char* options2str(enum cfg_options_flags flag)
{
if (flag == NO_TOUCH)
return "no_touch";
if (flag & SWAP_MAC)
return "swapmac";
if (flag & READ_MEM)
return "read";
fprintf(stderr, "ERR: Unknown config option flags");
int_exit(FAIL_STAT_SIG);
return "unknown";
}
static void usage(char *argv[])
{
int i;
printf("\nDOCUMENTATION:\n%s\n", __doc__);
printf(" Usage: %s (options-see-below)\n", argv[0]);
printf(" Listing options:\n");
for (i = 0; long_options[i].name != 0; i++) {
printf(" --%-12s", long_options[i].name);
if (long_options[i].flag != NULL)
printf(" flag (internal value:%d)",
*long_options[i].flag);
else
printf(" short-option: -%c",
long_options[i].val);
printf("\n");
}
printf("\n");
list_xdp_actions();
}
#define NANOSEC_PER_SEC 1000000000 /* 10^9 */
static __u64 gettime(void)
{
struct timespec t;
int res;
res = clock_gettime(CLOCK_MONOTONIC, &t);
if (res < 0) {
fprintf(stderr, "Error with gettimeofday! (%i)\n", res);
int_exit(FAIL_STAT_SIG);
}
return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec;
}
/* Common stats data record shared with _kern.c */
struct datarec {
__u64 processed;
__u64 issue;
};
struct record {
__u64 timestamp;
struct datarec total;
struct datarec *cpu;
};
struct stats_record {
struct record stats;
struct record *rxq;
};
static struct datarec *alloc_record_per_cpu(void)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
struct datarec *array;
array = calloc(nr_cpus, sizeof(struct datarec));
if (!array) {
fprintf(stderr, "Mem alloc error (nr_cpus:%u)\n", nr_cpus);
int_exit(FAIL_MEM_SIG);
}
return array;
}
static struct record *alloc_record_per_rxq(void)
{
unsigned int nr_rxqs = bpf_map__max_entries(rx_queue_index_map);
struct record *array;
array = calloc(nr_rxqs, sizeof(struct record));
if (!array) {
fprintf(stderr, "Mem alloc error (nr_rxqs:%u)\n", nr_rxqs);
int_exit(FAIL_MEM_SIG);
}
return array;
}
static struct stats_record *alloc_stats_record(void)
{
unsigned int nr_rxqs = bpf_map__max_entries(rx_queue_index_map);
struct stats_record *rec;
int i;
rec = calloc(1, sizeof(struct stats_record));
if (!rec) {
fprintf(stderr, "Mem alloc error\n");
int_exit(FAIL_MEM_SIG);
}
rec->rxq = alloc_record_per_rxq();
for (i = 0; i < nr_rxqs; i++)
rec->rxq[i].cpu = alloc_record_per_cpu();
rec->stats.cpu = alloc_record_per_cpu();
return rec;
}
static void free_stats_record(struct stats_record *r)
{
unsigned int nr_rxqs = bpf_map__max_entries(rx_queue_index_map);
int i;
for (i = 0; i < nr_rxqs; i++)
free(r->rxq[i].cpu);
free(r->rxq);
free(r->stats.cpu);
free(r);
}
static bool map_collect_percpu(int fd, __u32 key, struct record *rec)
{
/* For percpu maps, userspace gets a value per possible CPU */
unsigned int nr_cpus = bpf_num_possible_cpus();
struct datarec values[nr_cpus];
__u64 sum_processed = 0;
__u64 sum_issue = 0;
int i;
if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
fprintf(stderr,
"ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
return false;
}
/* Get time as close as possible to reading map contents */
rec->timestamp = gettime();
/* Record and sum values from each CPU */
for (i = 0; i < nr_cpus; i++) {
rec->cpu[i].processed = values[i].processed;
sum_processed += values[i].processed;
rec->cpu[i].issue = values[i].issue;
sum_issue += values[i].issue;
}
rec->total.processed = sum_processed;
rec->total.issue = sum_issue;
return true;
}
static void stats_collect(struct stats_record *rec)
{
int fd, i, max_rxqs;
fd = bpf_map__fd(stats_global_map);
map_collect_percpu(fd, 0, &rec->stats);
fd = bpf_map__fd(rx_queue_index_map);
max_rxqs = bpf_map__max_entries(rx_queue_index_map);
for (i = 0; i < max_rxqs; i++)
map_collect_percpu(fd, i, &rec->rxq[i]);
}
static double calc_period(struct record *r, struct record *p)
{
double period_ = 0;
__u64 period = 0;
period = r->timestamp - p->timestamp;
if (period > 0)
period_ = ((double) period / NANOSEC_PER_SEC);
return period_;
}
static __u64 calc_pps(struct datarec *r, struct datarec *p, double period_)
{
__u64 packets = 0;
__u64 pps = 0;
if (period_ > 0) {
packets = r->processed - p->processed;
pps = packets / period_;
}
return pps;
}
static __u64 calc_errs_pps(struct datarec *r,
struct datarec *p, double period_)
{
__u64 packets = 0;
__u64 pps = 0;
if (period_ > 0) {
packets = r->issue - p->issue;
pps = packets / period_;
}
return pps;
}
static void stats_print(struct stats_record *stats_rec,
struct stats_record *stats_prev,
int action, __u32 cfg_opt)
{
unsigned int nr_rxqs = bpf_map__max_entries(rx_queue_index_map);
unsigned int nr_cpus = bpf_num_possible_cpus();
double pps = 0, err = 0;
struct record *rec, *prev;
double t;
int rxq;
int i;
/* Header */
printf("\nRunning XDP on dev:%s (ifindex:%d) action:%s options:%s\n",
ifname, ifindex, action2str(action), options2str(cfg_opt));
/* stats_global_map */
{
char *fmt_rx = "%-15s %-7d %'-11.0f %'-10.0f %s\n";
char *fm2_rx = "%-15s %-7s %'-11.0f\n";
char *errstr = "";
printf("%-15s %-7s %-11s %-11s\n",
"XDP stats", "CPU", "pps", "issue-pps");
rec = &stats_rec->stats;
prev = &stats_prev->stats;
t = calc_period(rec, prev);
for (i = 0; i < nr_cpus; i++) {
struct datarec *r = &rec->cpu[i];
struct datarec *p = &prev->cpu[i];
pps = calc_pps (r, p, t);
err = calc_errs_pps(r, p, t);
if (err > 0)
errstr = "invalid-ifindex";
if (pps > 0)
printf(fmt_rx, "XDP-RX CPU",
i, pps, err, errstr);
}
pps = calc_pps (&rec->total, &prev->total, t);
err = calc_errs_pps(&rec->total, &prev->total, t);
printf(fm2_rx, "XDP-RX CPU", "total", pps, err);
}
/* rx_queue_index_map */
printf("\n%-15s %-7s %-11s %-11s\n",
"RXQ stats", "RXQ:CPU", "pps", "issue-pps");
for (rxq = 0; rxq < nr_rxqs; rxq++) {
char *fmt_rx = "%-15s %3d:%-3d %'-11.0f %'-10.0f %s\n";
char *fm2_rx = "%-15s %3d:%-3s %'-11.0f\n";
char *errstr = "";
int rxq_ = rxq;
/* Last RXQ in map catch overflows */
if (rxq_ == nr_rxqs - 1)
rxq_ = -1;
rec = &stats_rec->rxq[rxq];
prev = &stats_prev->rxq[rxq];
t = calc_period(rec, prev);
for (i = 0; i < nr_cpus; i++) {
struct datarec *r = &rec->cpu[i];
struct datarec *p = &prev->cpu[i];
pps = calc_pps (r, p, t);
err = calc_errs_pps(r, p, t);
if (err > 0) {
if (rxq_ == -1)
errstr = "map-overflow-RXQ";
else
errstr = "err";
}
if (pps > 0)
printf(fmt_rx, "rx_queue_index",
rxq_, i, pps, err, errstr);
}
pps = calc_pps (&rec->total, &prev->total, t);
err = calc_errs_pps(&rec->total, &prev->total, t);
if (pps || err)
printf(fm2_rx, "rx_queue_index", rxq_, "sum", pps, err);
}
}
/* Pointer swap trick */
static inline void swap(struct stats_record **a, struct stats_record **b)
{
struct stats_record *tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
static void stats_poll(int interval, int action, __u32 cfg_opt)
{
struct stats_record *record, *prev;
record = alloc_stats_record();
prev = alloc_stats_record();
stats_collect(record);
while (1) {
swap(&prev, &record);
stats_collect(record);
stats_print(record, prev, action, cfg_opt);
sleep(interval);
}
free_stats_record(record);
free_stats_record(prev);
}
int main(int argc, char **argv)
{
__u32 cfg_options= NO_TOUCH ; /* Default: Don't touch packet memory */
struct bpf_prog_info info = {};
__u32 info_len = sizeof(info);
int prog_fd, map_fd, opt, err;
bool use_separators = true;
struct config cfg = { 0 };
struct bpf_program *prog;
struct bpf_object *obj;
struct bpf_map *map;
char filename[256];
int longindex = 0;
int interval = 2;
__u32 key = 0;
char action_str_buf[XDP_ACTION_MAX_STRLEN + 1 /* for \0 */] = { 0 };
int action = XDP_PASS; /* Default action */
char *action_str = NULL;
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj))
return EXIT_FAIL;
prog = bpf_object__next_program(obj, NULL);
bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
err = bpf_object__load(obj);
if (err)
return EXIT_FAIL;
prog_fd = bpf_program__fd(prog);
map = bpf_object__find_map_by_name(obj, "config_map");
stats_global_map = bpf_object__find_map_by_name(obj, "stats_global_map");
rx_queue_index_map = bpf_object__find_map_by_name(obj, "rx_queue_index_map");
if (!map || !stats_global_map || !rx_queue_index_map) {
printf("finding a map in obj file failed\n");
return EXIT_FAIL;
}
map_fd = bpf_map__fd(map);
if (!prog_fd) {
fprintf(stderr, "ERR: bpf_prog_load_xattr: %s\n", strerror(errno));
return EXIT_FAIL;
}
/* Parse commands line args */
while ((opt = getopt_long(argc, argv, "FhSrmzd:s:a:",
long_options, &longindex)) != -1) {
switch (opt) {
case 'd':
if (strlen(optarg) >= IF_NAMESIZE) {
fprintf(stderr, "ERR: --dev name too long\n");
goto error;
}
ifname = (char *)&ifname_buf;
strncpy(ifname, optarg, IF_NAMESIZE);
ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
fprintf(stderr,
"ERR: --dev name unknown err(%d):%s\n",
errno, strerror(errno));
goto error;
}
break;
case 's':
interval = atoi(optarg);
break;
case 'S':
xdp_flags |= XDP_FLAGS_SKB_MODE;
break;
case 'z':
use_separators = false;
break;
case 'a':
action_str = (char *)&action_str_buf;
strncpy(action_str, optarg, XDP_ACTION_MAX_STRLEN);
break;
case 'r':
cfg_options |= READ_MEM;
break;
case 'm':
cfg_options |= SWAP_MAC;
break;
case 'F':
xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
break;
case 'h':
error:
default:
usage(argv);
return EXIT_FAIL_OPTION;
}
}
if (!(xdp_flags & XDP_FLAGS_SKB_MODE))
xdp_flags |= XDP_FLAGS_DRV_MODE;
/* Required option */
if (ifindex == -1) {
fprintf(stderr, "ERR: required option --dev missing\n");
usage(argv);
return EXIT_FAIL_OPTION;
}
cfg.ifindex = ifindex;
/* Parse action string */
if (action_str) {
action = parse_xdp_action(action_str);
if (action < 0) {
fprintf(stderr, "ERR: Invalid XDP --action: %s\n",
action_str);
list_xdp_actions();
return EXIT_FAIL_OPTION;
}
}
cfg.action = action;
/* XDP_TX requires changing MAC-addrs, else HW may drop */
if (action == XDP_TX)
cfg_options |= SWAP_MAC;
cfg.options = cfg_options;
/* Trick to pretty printf with thousands separators use %' */
if (use_separators)
setlocale(LC_NUMERIC, "en_US");
/* User-side setup ifindex in config_map */
err = bpf_map_update_elem(map_fd, &key, &cfg, 0);
if (err) {
fprintf(stderr, "Store config failed (err:%d)\n", err);
exit(EXIT_FAIL_BPF);
}
/* Remove XDP program when program is interrupted or killed */
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
if (bpf_xdp_attach(ifindex, prog_fd, xdp_flags, NULL) < 0) {
fprintf(stderr, "link set xdp fd failed\n");
return EXIT_FAIL_XDP;
}
err = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len);
if (err) {
printf("can't get prog info - %s\n", strerror(errno));
return err;
}
prog_id = info.id;
stats_poll(interval, action, cfg_options);
return EXIT_OK;
}
// SPDX-License-Identifier: GPL-2.0
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#define SAMPLE_SIZE 64ul
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(u32));
} my_map SEC(".maps");
SEC("xdp_sample")
int xdp_sample_prog(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
/* Metadata will be in the perf event before the packet data. */
struct S {
u16 cookie;
u16 pkt_len;
} __packed metadata;
if (data < data_end) {
/* The XDP perf_event_output handler will use the upper 32 bits
* of the flags argument as a number of bytes to include of the
* packet payload in the event data. If the size is too big, the
* call to bpf_perf_event_output will fail and return -EFAULT.
*
* See bpf_xdp_event_output in net/core/filter.c.
*
* The BPF_F_CURRENT_CPU flag means that the event output fd
* will be indexed by the CPU number in the event map.
*/
u64 flags = BPF_F_CURRENT_CPU;
u16 sample_size;
int ret;
metadata.cookie = 0xdead;
metadata.pkt_len = (u16)(data_end - data);
sample_size = min(metadata.pkt_len, SAMPLE_SIZE);
flags |= (u64)sample_size << 32;
ret = bpf_perf_event_output(ctx, &my_map, flags,
&metadata, sizeof(metadata));
if (ret)
bpf_printk("perf_event_output failed: %d\n", ret);
}
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/perf_event.h>
#include <linux/bpf.h>
#include <net/if.h>
#include <errno.h>
#include <assert.h>
#include <sys/sysinfo.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <libgen.h>
#include <linux/if_link.h>
#include "perf-sys.h"
static int if_idx;
static char *if_name;
static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
static __u32 prog_id;
static struct perf_buffer *pb = NULL;
static int do_attach(int idx, int fd, const char *name)
{
struct bpf_prog_info info = {};
__u32 info_len = sizeof(info);
int err;
err = bpf_xdp_attach(idx, fd, xdp_flags, NULL);
if (err < 0) {
printf("ERROR: failed to attach program to %s\n", name);
return err;
}
err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
if (err) {
printf("can't get prog info - %s\n", strerror(errno));
return err;
}
prog_id = info.id;
return err;
}
static int do_detach(int idx, const char *name)
{
__u32 curr_prog_id = 0;
int err = 0;
err = bpf_xdp_query_id(idx, xdp_flags, &curr_prog_id);
if (err) {
printf("bpf_xdp_query_id failed\n");
return err;
}
if (prog_id == curr_prog_id) {
err = bpf_xdp_detach(idx, xdp_flags, NULL);
if (err < 0)
printf("ERROR: failed to detach prog from %s\n", name);
} else if (!curr_prog_id) {
printf("couldn't find a prog id on a %s\n", name);
} else {
printf("program on interface changed, not removing\n");
}
return err;
}
#define SAMPLE_SIZE 64
static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size)
{
struct {
__u16 cookie;
__u16 pkt_len;
__u8 pkt_data[SAMPLE_SIZE];
} __packed *e = data;
int i;
if (e->cookie != 0xdead) {
printf("BUG cookie %x sized %d\n", e->cookie, size);
return;
}
printf("Pkt len: %-5d bytes. Ethernet hdr: ", e->pkt_len);
for (i = 0; i < 14 && i < e->pkt_len; i++)
printf("%02x ", e->pkt_data[i]);
printf("\n");
}
static void sig_handler(int signo)
{
do_detach(if_idx, if_name);
perf_buffer__free(pb);
exit(0);
}
static void usage(const char *prog)
{
fprintf(stderr,
"%s: %s [OPTS] <ifname|ifindex>\n\n"
"OPTS:\n"
" -F force loading prog\n"
" -S use skb-mode\n",
__func__, prog);
}
int main(int argc, char **argv)
{
const char *optstr = "FS";
int prog_fd, map_fd, opt;
struct bpf_program *prog;
struct bpf_object *obj;
struct bpf_map *map;
char filename[256];
int ret, err;
while ((opt = getopt(argc, argv, optstr)) != -1) {
switch (opt) {
case 'F':
xdp_flags &= ~XDP_FLAGS_UPDATE_IF_NOEXIST;
break;
case 'S':
xdp_flags |= XDP_FLAGS_SKB_MODE;
break;
default:
usage(basename(argv[0]));
return 1;
}
}
if (!(xdp_flags & XDP_FLAGS_SKB_MODE))
xdp_flags |= XDP_FLAGS_DRV_MODE;
if (optind == argc) {
usage(basename(argv[0]));
return 1;
}
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
obj = bpf_object__open_file(filename, NULL);
if (libbpf_get_error(obj))
return 1;
prog = bpf_object__next_program(obj, NULL);
bpf_program__set_type(prog, BPF_PROG_TYPE_XDP);
err = bpf_object__load(obj);
if (err)
return 1;
prog_fd = bpf_program__fd(prog);
map = bpf_object__next_map(obj, NULL);
if (!map) {
printf("finding a map in obj file failed\n");
return 1;
}
map_fd = bpf_map__fd(map);
if_idx = if_nametoindex(argv[optind]);
if (!if_idx)
if_idx = strtoul(argv[optind], NULL, 0);
if (!if_idx) {
fprintf(stderr, "Invalid ifname\n");
return 1;
}
if_name = argv[optind];
err = do_attach(if_idx, prog_fd, if_name);
if (err)
return err;
if (signal(SIGINT, sig_handler) ||
signal(SIGHUP, sig_handler) ||
signal(SIGTERM, sig_handler)) {
perror("signal");
return 1;
}
pb = perf_buffer__new(map_fd, 8, print_bpf_output, NULL, NULL, NULL);
err = libbpf_get_error(pb);
if (err) {
perror("perf_buffer setup failed");
return 1;
}
while ((ret = perf_buffer__poll(pb, 1000)) >= 0) {
}
kill(0, SIGINT);
return ret;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment