Initial import with encap-forward example

Signed-off-by: Toke Høiland-Jørgensen <[email protected]>
This commit is contained in:
Toke Høiland-Jørgensen
2020-10-06 15:53:55 +02:00
commit 4513664ca3
30 changed files with 7626 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
config.mk
.ccls-cache
compile_commands.json
*.ll
*.o
+3
View File
@@ -0,0 +1,3 @@
[submodule "lib/libbpf"]
path = lib/libbpf
url = https://github.com/xdp-project/libbpf.git
+62
View File
@@ -0,0 +1,62 @@
# SPDX-License-Identifier: GPL-2.0
# Top level Makefile for xdp-tools
ifeq ("$(origin V)", "command line")
VERBOSE = $(V)
endif
ifndef VERBOSE
VERBOSE = 0
endif
ifeq ($(VERBOSE),0)
MAKEFLAGS += --no-print-directory
endif
SUBDIRS := encap-forward
.PHONY: check_submodule help clobber distclean clean $(SUBDIRS)
all: $(SUBDIRS)
lib: config.mk check_submodule
@echo; echo $@; $(MAKE) -C $@
$(SUBDIRS):
@echo; echo $@; $(MAKE) -C $@
help:
@echo "Make Targets:"
@echo " all - build binaries"
@echo " clean - remove products of build"
@echo " distclean - remove configuration and build"
@echo " install - install binaries on local machine"
@echo " test - run test suite"
@echo " archive - create tarball of all sources"
@echo ""
@echo "Make Arguments:"
@echo " V=[0|1] - set build verbosity level"
config.mk:
sh configure
check_submodule:
@if [ -d .git ] && `git submodule status lib/libbpf | grep -q '^+'`; then \
echo "" ;\
echo "** WARNING **: git submodule SHA-1 out-of-sync" ;\
echo " consider running: git submodule update" ;\
echo "" ;\
fi\
clobber:
touch config.mk
$(MAKE) clean
rm -f config.mk cscope.* compile_commands.json
distclean: clobber
clean: check_submodule
@for i in $(SUBDIRS); \
do $(MAKE) -C $$i clean; done
compile_commands.json: clean
compiledb make V=1
Vendored Executable
+206
View File
@@ -0,0 +1,206 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# This is not an autoconf generated configure
#
# Output file which is input to Makefile
CONFIG_FINAL=config.mk
CONFIG=".${CONFIG}.tmp"
# Make a temp directory in build tree.
TMPDIR=$(mktemp -d config.XXXXXX)
trap 'status=$?; rm -rf $TMPDIR; rm -f $CONFIG; exit $status' EXIT HUP INT QUIT TERM
check_toolchain()
{
local emacs_version
: ${PKG_CONFIG:=pkg-config}
: ${CC=gcc}
: ${CLANG=clang}
: ${LLC=llc}
: ${M4=m4}
: ${EMACS=emacs}
for TOOL in $PKG_CONFIG $CC $CLANG $LLC $M4; do
if [ ! $(command -v ${TOOL} 2>/dev/null) ]; then
echo "*** ERROR: Cannot find tool ${TOOL}" ;
exit 1;
fi;
done
if ! command -v $EMACS >/dev/null 2>&1; then
EMACS=""
else
emacs_version=$($EMACS --version 2>/dev/null | head -n 1)
if echo $emacs_version | grep -Eq 'GNU Emacs 2[67]'; then
echo "using emacs: $emacs_version"
else
echo "not using emacs: $emacs_version"
EMACS=""
fi
fi
if [ -z "$EMACS" ] && [ "${FORCE_EMACS:-0}" -eq "1" ]; then
echo "FORCE_EMACS is set, but no usable emacs found on system"
rm -f "$CONFIG"
exit 1
fi
echo "PKG_CONFIG:=${PKG_CONFIG}" >>$CONFIG
echo "CC:=${CC}" >>$CONFIG
echo "CLANG:=${CLANG}" >>$CONFIG
echo "LLC:=${LLC}" >>$CONFIG
echo "M4:=${M4}" >>$CONFIG
echo "EMACS:=${EMACS}" >>$CONFIG
}
check_elf()
{
if ${PKG_CONFIG} libelf --exists; then
echo "HAVE_ELF:=y" >>$CONFIG
echo "yes"
echo 'CFLAGS += -DHAVE_ELF' `${PKG_CONFIG} libelf --cflags` >> $CONFIG
echo 'LDLIBS += ' `${PKG_CONFIG} libelf --libs` >>$CONFIG
else
echo "missing - this is required"
return 1
fi
}
check_zlib()
{
if ${PKG_CONFIG} zlib --exists; then
echo "HAVE_ZLIB:=y" >>$CONFIG
echo "yes"
echo 'CFLAGS += -DHAVE_ZLIB' `${PKG_CONFIG} zlib --cflags` >> $CONFIG
echo 'LDLIBS += ' `${PKG_CONFIG} zlib --libs` >>$CONFIG
else
echo "missing - this is required"
return 1
fi
}
check_libbpf()
{
local libbpf_err
if ${PKG_CONFIG} libbpf --exists || [ -n "$LIBBPF_DIR" ]; then
if [ -n "$LIBBPF_DIR" ]; then
LIBBPF_CFLAGS="-I${LIBBPF_DIR}/include -L${LIBBPF_DIR}/lib"
LIBBPF_LDLIBS="-lbpf"
else
LIBBPF_CFLAGS=$(${PKG_CONFIG} libbpf --cflags)
LIBBPF_LDLIBS=$(${PKG_CONFIG} libbpf --libs)
fi
cat >$TMPDIR/libbpftest.c <<EOF
#include <bpf/libbpf.h>
int main(int argc, char **argv) {
void *ptr;
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts, .pin_root_path = "/path");
DECLARE_LIBBPF_OPTS(bpf_xdp_set_link_opts, lopts, .old_fd = -1);
(void) bpf_object__open_file("file", &opts);
(void) bpf_program__name(ptr);
(void) bpf_map__set_initial_value(ptr, ptr, 0);
(void) bpf_set_link_xdp_fd_opts(0, 0, 0, &lopts);
return 0;
}
EOF
libbpf_err=$($CC -o $TMPDIR/libbpftest $TMPDIR/libbpftest.c $LIBBPF_CFLAGS -lbpf 2>&1)
if [ "$?" -eq "0" ]; then
echo "SYSTEM_LIBBPF:=y" >>$CONFIG
echo 'CFLAGS += ' $LIBBPF_CFLAGS >> $CONFIG
echo 'LDLIBS += ' $LIBBPF_LDLIBS >>$CONFIG
echo 'OBJECT_LIBBPF = ' >>$CONFIG
echo system
echo -n "perf_buffer__consume support: "
check_perf_consume
return 0
fi
else
libbpf_err="${PKG_CONFIG} couldn't find libbpf"
fi
if [ "${FORCE_SYSTEM_LIBBPF:-0}" -eq "1" ]; then
echo "FORCE_SYSTEM_LIBBPF is set, but no usable libbpf found on system"
echo "error: $libbpf_err"
rm -f "$CONFIG"
exit 1
fi
echo submodule
echo "SYSTEM_LIBBPF:=n" >> $CONFIG
echo 'CFLAGS += -I$(LIB_DIR)/libbpf/src/root/usr/include' >>$CONFIG
echo 'BPF_CFLAGS += -I$(LIB_DIR)/libbpf/src/root/usr/include' >>$CONFIG
echo 'LDFLAGS += -L$(LIB_DIR)/libbpf/src' >>$CONFIG
echo 'LDLIBS += -l:libbpf.a' >>$CONFIG
echo 'OBJECT_LIBBPF = $(LIB_DIR)/libbpf/src/libbpf.a' >>$CONFIG
if ! [ -d "lib/libbpf/src" ] && [ -f ".gitmodules" ] && [ -e ".git" ]; then
git submodule init && git submodule update
fi
echo -n "ELF support: "
check_elf || exit 1
echo -n "zlib support: "
check_zlib || exit 1
# For the build submodule library we know it does support this API, so we
# hard code it. Also due to the fact it's hard to build a test app as
# libbpf.a has not been build at configure time.
echo "HAVE_LIBBPF_PERF_BUFFER__CONSUME:=y" >>"$CONFIG"
}
quiet_config()
{
cat <<EOF
# user can control verbosity similar to kernel builds (e.g., V=1)
ifeq ("\$(origin V)", "command line")
VERBOSE = \$(V)
endif
ifndef VERBOSE
VERBOSE = 0
endif
ifeq (\$(VERBOSE),1)
Q =
else
Q = @
endif
ifeq (\$(VERBOSE),0)
MAKEFLAGS += --no-print-directory
endif
ifeq (\$(VERBOSE), 0)
QUIET_CC = @echo ' CC '\$@;
QUIET_CLANG = @echo ' CLANG '\$@;
QUIET_LLC = @echo ' LLC '\$@;
QUIET_LINK = @echo ' LINK '\$@;
QUIET_INSTALL = @echo ' INSTALL '\$@;
QUIET_M4 = @echo ' M4 '\$@;
QUIET_GEN = @echo ' GEN '\$@;
endif
EOF
}
echo "# Generated config" >$CONFIG
quiet_config >> $CONFIG
check_toolchain
echo -n "libbpf support: "
check_libbpf
if [ -n "$KERNEL_HEADERS" ]; then
echo "kernel headers: $KERNEL_HEADERS"
echo "CFLAGS += -I$KERNEL_HEADERS" >>$CONFIG
echo "BPF_CFLAGS += -I$KERNEL_HEADERS" >>$CONFIG
fi
mv $CONFIG $CONFIG_FINAL
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
USER_TARGETS :=
BPF_TARGETS := xdp_encap tc_bpf_encap
EXTRA_DEPS := encap.h
LIB_DIR = ../lib
include $(LIB_DIR)/common.mk
+62
View File
@@ -0,0 +1,62 @@
#define NEXTHDR_IPV6 41
static void encap_ipv6(volatile void *data, volatile void *data_end)
{
volatile struct ipv6hdr *ip6h;
size_t len;
struct ipv6hdr encap_hdr = {
.version = 6,
.nexthdr = NEXTHDR_IPV6,
.hop_limit = 16,
.saddr = { .s6_addr = { 0xfc, 0x00, 0xde, 0xad, 0xca, 0xfe,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02 } },
.daddr = { .s6_addr = { 0xfc, 0x00, 0xde, 0xad, 0xca, 0xfe,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01 } },
};
ip6h = data + sizeof(struct ethhdr);
if (ip6h + 1 > data_end)
return;
*ip6h = encap_hdr;
len = (data_end - data);
ip6h->payload_len = bpf_htons(len - sizeof(struct ethhdr) - sizeof(*ip6h));
}
static __always_inline __u16 csum_fold_helper(__u32 csum)
{
__u32 sum;
sum = (csum >> 16) + (csum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
static void encap_ipv4(volatile void *data, volatile void *data_end)
{
volatile struct iphdr *iph;
size_t len;
struct iphdr encap_hdr = {
.version = 4,
.ihl = 5,
.protocol = NEXTHDR_IPV6,
.ttl = 16,
.saddr = bpf_htonl(0x0a0b0202),
.daddr = bpf_htonl(0x0a0b0201),
};
iph = data + sizeof(struct ethhdr);
if (iph + 1 > data_end)
return;
*iph = encap_hdr;
len = (data_end - data);
iph->tot_len = bpf_htons(len - sizeof(struct ethhdr));
iph->check = csum_fold_helper(bpf_csum_diff((__be32 *)iph, 0, (__be32 *)iph, sizeof(*iph), 0));
}
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
set -o errexit
TESTENV=../lib/testenv/testenv.sh
if [[ "$1" == "teardown" ]] || [[ "$1" == "reset" ]]; then
$TESTENV teardown -n bpf-encap
[[ "$1" == "teardown" ]] && exit 0
fi
$TESTENV setup -n bpf-encap --legacy-ip
ip link add dev bpf-encap2 type veth peer name veth1 netns bpf-encap
ip link set dev bpf-encap2 up
ip a add dev bpf-encap2 10.11.2.1/24
ip a add dev bpf-encap2 fc00:dead:cafe:2::1/64
$TESTENV exec ip link set dev veth1 up
$TESTENV exec ip a add 10.11.2.2/24 dev veth1
$TESTENV exec ip a add fc00:dead:cafe:2::2/64 dev veth1
$TESTENV exec -- sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1 net.ipv4.conf.veth0.rp_filter=2 net.ipv4.conf.veth0.accept_local=1
#$TESTENV exec -- ip link set dev veth0 xdp object xdp_encap.o
$TESTENV exec -- tc qdisc add dev veth0 clsact
$TESTENV exec -- tc filter add dev veth0 ingress bpf da obj tc_bpf_encap.o
+27
View File
@@ -0,0 +1,27 @@
#include <linux/bpf.h>
#include <linux/in6.h>
#include <bpf/bpf_helpers.h>
#include <xdp/parsing_helpers.h>
#include "encap.h"
SEC("classifier") int tc_encap(struct __sk_buff *skb)
{
volatile void *data, *data_end;
int ret = BPF_DROP;
size_t offset = sizeof(struct iphdr);
if (bpf_skb_adjust_room(skb, offset, BPF_ADJ_ROOM_MAC, BPF_F_ADJ_ROOM_ENCAP_L3_IPV4))
goto out;
data = (void *)(long)skb->data;
data_end = (void *)(long)skb->data_end;
// encap_ipv6(ctx);
encap_ipv4(data, data_end);
ret = BPF_OK;
out:
return ret;
}
char _license[] SEC("license") = "GPL";
+43
View File
@@ -0,0 +1,43 @@
#include <linux/bpf.h>
#include <linux/in6.h>
#include <bpf/bpf_helpers.h>
#include <xdp/parsing_helpers.h>
#include "encap.h"
SEC("prog") int xdp_encap(struct xdp_md *ctx)
{
volatile struct ethhdr *ehdr, old_ehdr = {};
volatile void *data, *data_end;
int ret = XDP_ABORTED;
size_t offset = sizeof(struct iphdr);
data = (void *)(long)ctx->data;
data_end = (void *)(long)ctx->data_end;
ehdr = data;
if (ehdr + 1 > data_end)
goto out;
old_ehdr = *ehdr;
if (bpf_xdp_adjust_head(ctx, -offset))
goto out;
data = (void *)(long)ctx->data;
data_end = (void *)(long)ctx->data_end;
ehdr = data;
if (ehdr + 1 > data_end)
goto out;
*ehdr = old_ehdr;
ehdr->h_proto = bpf_htons(ETH_P_IP);
// encap_ipv6(data, data_end);
encap_ipv4(data, data_end);
ret = XDP_PASS;
out:
return ret;
}
char _license[] SEC("license") = "GPL";
+120
View File
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_TRACE_HELPERS_H
#define __BPF_TRACE_HELPERS_H
#include <bpf/bpf_helpers.h>
#define ___bpf_concat(a, b) a ## b
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
#define ___bpf_narg(...) \
___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define ___bpf_empty(...) \
___bpf_nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0)
#define ___bpf_ctx_cast0() ctx
#define ___bpf_ctx_cast1(x) ___bpf_ctx_cast0(), (void *)ctx[0]
#define ___bpf_ctx_cast2(x, args...) ___bpf_ctx_cast1(args), (void *)ctx[1]
#define ___bpf_ctx_cast3(x, args...) ___bpf_ctx_cast2(args), (void *)ctx[2]
#define ___bpf_ctx_cast4(x, args...) ___bpf_ctx_cast3(args), (void *)ctx[3]
#define ___bpf_ctx_cast5(x, args...) ___bpf_ctx_cast4(args), (void *)ctx[4]
#define ___bpf_ctx_cast6(x, args...) ___bpf_ctx_cast5(args), (void *)ctx[5]
#define ___bpf_ctx_cast7(x, args...) ___bpf_ctx_cast6(args), (void *)ctx[6]
#define ___bpf_ctx_cast8(x, args...) ___bpf_ctx_cast7(args), (void *)ctx[7]
#define ___bpf_ctx_cast9(x, args...) ___bpf_ctx_cast8(args), (void *)ctx[8]
#define ___bpf_ctx_cast10(x, args...) ___bpf_ctx_cast9(args), (void *)ctx[9]
#define ___bpf_ctx_cast11(x, args...) ___bpf_ctx_cast10(args), (void *)ctx[10]
#define ___bpf_ctx_cast12(x, args...) ___bpf_ctx_cast11(args), (void *)ctx[11]
#define ___bpf_ctx_cast(args...) \
___bpf_apply(___bpf_ctx_cast, ___bpf_narg(args))(args)
/*
* BPF_PROG is a convenience wrapper for generic tp_btf/fentry/fexit and
* similar kinds of BPF programs, that accept input arguments as a single
* pointer to untyped u64 array, where each u64 can actually be a typed
* pointer or integer of different size. Instead of requring user to write
* manual casts and work with array elements by index, BPF_PROG macro
* allows user to declare a list of named and typed input arguments in the
* same syntax as for normal C function. All the casting is hidden and
* performed transparently, while user code can just assume working with
* function arguments of specified type and name.
*
* Original raw context argument is preserved as well as 'ctx' argument.
* This is useful when using BPF helpers that expect original context
* as one of the parameters (e.g., for bpf_perf_event_output()).
*/
#define BPF_PROG(name, args...) \
name(unsigned long long *ctx); \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args); \
typeof(name(0)) name(unsigned long long *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_ctx_cast(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args)
struct pt_regs;
#define ___bpf_kprobe_args0() ctx
#define ___bpf_kprobe_args1(x) \
___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
#define ___bpf_kprobe_args2(x, args...) \
___bpf_kprobe_args1(args), (void *)PT_REGS_PARM2(ctx)
#define ___bpf_kprobe_args3(x, args...) \
___bpf_kprobe_args2(args), (void *)PT_REGS_PARM3(ctx)
#define ___bpf_kprobe_args4(x, args...) \
___bpf_kprobe_args3(args), (void *)PT_REGS_PARM4(ctx)
#define ___bpf_kprobe_args5(x, args...) \
___bpf_kprobe_args4(args), (void *)PT_REGS_PARM5(ctx)
#define ___bpf_kprobe_args(args...) \
___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
/*
* BPF_KPROBE serves the same purpose for kprobes as BPF_PROG for
* tp_btf/fentry/fexit BPF programs. It hides the underlying platform-specific
* low-level way of getting kprobe input arguments from struct pt_regs, and
* provides a familiar typed and named function arguments syntax and
* semantics of accessing kprobe input paremeters.
*
* Original struct pt_regs* context is preserved as 'ctx' argument. This might
* be necessary when using BPF helpers like bpf_perf_event_output().
*/
#define BPF_KPROBE(name, args...) \
name(struct pt_regs *ctx); \
static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args);\
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_kprobe_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args)
#define ___bpf_kretprobe_args0() ctx
#define ___bpf_kretprobe_argsN(x, args...) \
___bpf_kprobe_args(args), (void *)PT_REGS_RET(ctx)
#define ___bpf_kretprobe_args(args...) \
___bpf_apply(___bpf_kretprobe_args, ___bpf_empty(args))(args)
/*
* BPF_KRETPROBE is similar to BPF_KPROBE, except, in addition to listing all
* input kprobe arguments, one last extra argument has to be specified, which
* captures kprobe return value.
*/
#define BPF_KRETPROBE(name, args...) \
name(struct pt_regs *ctx); \
static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args);\
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_kretprobe_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args)
#endif
+4201
View File
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/* Copyright (c) 2018 Facebook */
#ifndef __LINUX_BTF_H__
#define __LINUX_BTF_H__
#include <linux/types.h>
#define BTF_MAGIC 0xeB9F
#define BTF_VERSION 1
struct btf_header {
__u16 magic;
__u8 version;
__u8 flags;
__u32 hdr_len;
/* All offsets are in bytes relative to the end of this header */
__u32 type_off; /* offset of type section */
__u32 type_len; /* length of type section */
__u32 str_off; /* offset of string section */
__u32 str_len; /* length of string section */
};
/* Max # of type identifier */
#define BTF_MAX_TYPE 0x000fffff
/* Max offset into the string section */
#define BTF_MAX_NAME_OFFSET 0x00ffffff
/* Max # of struct/union/enum members or func args */
#define BTF_MAX_VLEN 0xffff
struct btf_type {
__u32 name_off;
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members)
* bits 16-23: unused
* bits 24-27: kind (e.g. int, ptr, array...etc)
* bits 28-30: unused
* bit 31: kind_flag, currently used by
* struct, union and fwd
*/
__u32 info;
/* "size" is used by INT, ENUM, STRUCT, UNION and DATASEC.
* "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC, FUNC_PROTO and VAR.
* "type" is a type_id referring to another type.
*/
union {
__u32 size;
__u32 type;
};
};
#define BTF_INFO_KIND(info) (((info) >> 24) & 0x0f)
#define BTF_INFO_VLEN(info) ((info) & 0xffff)
#define BTF_INFO_KFLAG(info) ((info) >> 31)
#define BTF_KIND_UNKN 0 /* Unknown */
#define BTF_KIND_INT 1 /* Integer */
#define BTF_KIND_PTR 2 /* Pointer */
#define BTF_KIND_ARRAY 3 /* Array */
#define BTF_KIND_STRUCT 4 /* Struct */
#define BTF_KIND_UNION 5 /* Union */
#define BTF_KIND_ENUM 6 /* Enumeration */
#define BTF_KIND_FWD 7 /* Forward */
#define BTF_KIND_TYPEDEF 8 /* Typedef */
#define BTF_KIND_VOLATILE 9 /* Volatile */
#define BTF_KIND_CONST 10 /* Const */
#define BTF_KIND_RESTRICT 11 /* Restrict */
#define BTF_KIND_FUNC 12 /* Function */
#define BTF_KIND_FUNC_PROTO 13 /* Function Proto */
#define BTF_KIND_VAR 14 /* Variable */
#define BTF_KIND_DATASEC 15 /* Section */
#define BTF_KIND_MAX BTF_KIND_DATASEC
#define NR_BTF_KINDS (BTF_KIND_MAX + 1)
/* For some specific BTF_KIND, "struct btf_type" is immediately
* followed by extra data.
*/
/* BTF_KIND_INT is followed by a u32 and the following
* is the 32 bits arrangement:
*/
#define BTF_INT_ENCODING(VAL) (((VAL) & 0x0f000000) >> 24)
#define BTF_INT_OFFSET(VAL) (((VAL) & 0x00ff0000) >> 16)
#define BTF_INT_BITS(VAL) ((VAL) & 0x000000ff)
/* Attributes stored in the BTF_INT_ENCODING */
#define BTF_INT_SIGNED (1 << 0)
#define BTF_INT_CHAR (1 << 1)
#define BTF_INT_BOOL (1 << 2)
/* BTF_KIND_ENUM is followed by multiple "struct btf_enum".
* The exact number of btf_enum is stored in the vlen (of the
* info in "struct btf_type").
*/
struct btf_enum {
__u32 name_off;
__s32 val;
};
/* BTF_KIND_ARRAY is followed by one "struct btf_array" */
struct btf_array {
__u32 type;
__u32 index_type;
__u32 nelems;
};
/* BTF_KIND_STRUCT and BTF_KIND_UNION are followed
* by multiple "struct btf_member". The exact number
* of btf_member is stored in the vlen (of the info in
* "struct btf_type").
*/
struct btf_member {
__u32 name_off;
__u32 type;
/* If the type info kind_flag is set, the btf_member offset
* contains both member bitfield size and bit offset. The
* bitfield size is set for bitfield members. If the type
* info kind_flag is not set, the offset contains only bit
* offset.
*/
__u32 offset;
};
/* If the struct/union type info kind_flag is set, the
* following two macros are used to access bitfield_size
* and bit_offset from btf_member.offset.
*/
#define BTF_MEMBER_BITFIELD_SIZE(val) ((val) >> 24)
#define BTF_MEMBER_BIT_OFFSET(val) ((val) & 0xffffff)
/* BTF_KIND_FUNC_PROTO is followed by multiple "struct btf_param".
* The exact number of btf_param is stored in the vlen (of the
* info in "struct btf_type").
*/
struct btf_param {
__u32 name_off;
__u32 type;
};
enum {
BTF_VAR_STATIC = 0,
BTF_VAR_GLOBAL_ALLOCATED = 1,
BTF_VAR_GLOBAL_EXTERN = 2,
};
enum btf_func_linkage {
BTF_FUNC_STATIC = 0,
BTF_FUNC_GLOBAL = 1,
BTF_FUNC_EXTERN = 2,
};
/* BTF_KIND_VAR is followed by a single "struct btf_var" to describe
* additional information related to the variable such as its linkage.
*/
struct btf_var {
__u32 linkage;
};
/* BTF_KIND_DATASEC is followed by multiple "struct btf_var_secinfo"
* to describe all BTF_KIND_VAR types it contains along with it's
* in-section offset as well as size.
*/
struct btf_var_secinfo {
__u32 type;
__u32 offset;
__u32 size;
};
#endif /* __LINUX_BTF_H__ */
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __LINUX_ERR_H
#define __LINUX_ERR_H
#include <stdbool.h>
#include <linux/types.h>
#include <asm/errno.h>
#define MAX_ERRNO 4095
#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO)
static inline void * ERR_PTR(long error_)
{
return (void *) error_;
}
static inline long PTR_ERR(const void *ptr)
{
return (long) ptr;
}
static inline bool IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
static inline bool IS_ERR_OR_NULL(const void *ptr)
{
return (!ptr) || IS_ERR_VALUE((unsigned long)ptr);
}
#endif
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* if_xdp: XDP socket user-space interface
* Copyright(c) 2018 Intel Corporation.
*
* Author(s): Björn Töpel <[email protected]>
* Magnus Karlsson <[email protected]>
*/
#ifndef _LINUX_IF_XDP_H
#define _LINUX_IF_XDP_H
#include <linux/types.h>
/* Options for the sxdp_flags field */
#define XDP_SHARED_UMEM (1 << 0)
#define XDP_COPY (1 << 1) /* Force copy-mode */
#define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */
/* If this option is set, the driver might go sleep and in that case
* the XDP_RING_NEED_WAKEUP flag in the fill and/or Tx rings will be
* set. If it is set, the application need to explicitly wake up the
* driver with a poll() (Rx and Tx) or sendto() (Tx only). If you are
* running the driver and the application on the same core, you should
* use this option so that the kernel will yield to the user space
* application.
*/
#define XDP_USE_NEED_WAKEUP (1 << 3)
/* Flags for xsk_umem_config flags */
#define XDP_UMEM_UNALIGNED_CHUNK_FLAG (1 << 0)
struct sockaddr_xdp {
__u16 sxdp_family;
__u16 sxdp_flags;
__u32 sxdp_ifindex;
__u32 sxdp_queue_id;
__u32 sxdp_shared_umem_fd;
};
/* XDP_RING flags */
#define XDP_RING_NEED_WAKEUP (1 << 0)
struct xdp_ring_offset {
__u64 producer;
__u64 consumer;
__u64 desc;
__u64 flags;
};
struct xdp_mmap_offsets {
struct xdp_ring_offset rx;
struct xdp_ring_offset tx;
struct xdp_ring_offset fr; /* Fill */
struct xdp_ring_offset cr; /* Completion */
};
/* XDP socket options */
#define XDP_MMAP_OFFSETS 1
#define XDP_RX_RING 2
#define XDP_TX_RING 3
#define XDP_UMEM_REG 4
#define XDP_UMEM_FILL_RING 5
#define XDP_UMEM_COMPLETION_RING 6
#define XDP_STATISTICS 7
#define XDP_OPTIONS 8
struct xdp_umem_reg {
__u64 addr; /* Start of packet data area */
__u64 len; /* Length of packet data area */
__u32 chunk_size;
__u32 headroom;
__u32 flags;
};
struct xdp_statistics {
__u64 rx_dropped; /* Dropped for reasons other than invalid desc */
__u64 rx_invalid_descs; /* Dropped due to invalid descriptor */
__u64 tx_invalid_descs; /* Dropped due to invalid descriptor */
};
struct xdp_options {
__u32 flags;
};
/* Flags for the flags field of struct xdp_options */
#define XDP_OPTIONS_ZEROCOPY (1 << 0)
/* Pgoff for mmaping the rings */
#define XDP_PGOFF_RX_RING 0
#define XDP_PGOFF_TX_RING 0x80000000
#define XDP_UMEM_PGOFF_FILL_RING 0x100000000ULL
#define XDP_UMEM_PGOFF_COMPLETION_RING 0x180000000ULL
/* Masks for unaligned chunks mode */
#define XSK_UNALIGNED_BUF_OFFSET_SHIFT 48
#define XSK_UNALIGNED_BUF_ADDR_MASK \
((1ULL << XSK_UNALIGNED_BUF_OFFSET_SHIFT) - 1)
/* Rx/Tx descriptor */
struct xdp_desc {
__u64 addr;
__u32 len;
__u32 options;
};
/* UMEM descriptor is __u64 */
#endif /* _LINUX_IF_XDP_H */
+172
View File
@@ -0,0 +1,172 @@
#ifndef _LINUX_JHASH_H
#define _LINUX_JHASH_H
/* Copied from $(LINUX)/include/linux/jhash.h (kernel 4.18) */
/* jhash.h: Jenkins hash support.
*
* Copyright (C) 2006. Bob Jenkins ([email protected])
*
* http://burtleburtle.net/bob/hash/
*
* These are the credits from Bob's sources:
*
* lookup3.c, by Bob Jenkins, May 2006, Public Domain.
*
* These are functions for producing 32-bit hashes for hash table lookup.
* hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
* are externally useful functions. Routines to test the hash are included
* if SELF_TEST is defined. You can use this free for any purpose. It's in
* the public domain. It has no warranty.
*
* Copyright (C) 2009-2010 Jozsef Kadlecsik ([email protected])
*/
static inline __u32 rol32(__u32 word, unsigned int shift)
{
return (word << shift) | (word >> ((-shift) & 31));
}
/* copy paste of jhash from kernel sources (include/linux/jhash.h) to make sure
* LLVM can compile it into valid sequence of BPF instructions
*/
#define __jhash_mix(a, b, c) \
{ \
a -= c; a ^= rol32(c, 4); c += b; \
b -= a; b ^= rol32(a, 6); a += c; \
c -= b; c ^= rol32(b, 8); b += a; \
a -= c; a ^= rol32(c, 16); c += b; \
b -= a; b ^= rol32(a, 19); a += c; \
c -= b; c ^= rol32(b, 4); b += a; \
}
#define __jhash_final(a, b, c) \
{ \
c ^= b; c -= rol32(b, 14); \
a ^= c; a -= rol32(c, 11); \
b ^= a; b -= rol32(a, 25); \
c ^= b; c -= rol32(b, 16); \
a ^= c; a -= rol32(c, 4); \
b ^= a; b -= rol32(a, 14); \
c ^= b; c -= rol32(b, 24); \
}
#define JHASH_INITVAL 0xdeadbeef
typedef unsigned int u32;
/* jhash - hash an arbitrary key
* @k: sequence of bytes as key
* @length: the length of the key
* @initval: the previous hash, or an arbitray value
*
* The generic version, hashes an arbitrary sequence of bytes.
* No alignment or length assumptions are made about the input key.
*
* Returns the hash value of the key. The result depends on endianness.
*/
static inline u32 jhash(const void *key, u32 length, u32 initval)
{
u32 a, b, c;
const unsigned char *k = key;
/* Set up the internal state */
a = b = c = JHASH_INITVAL + length + initval;
/* All but the last block: affect some 32 bits of (a,b,c) */
while (length > 12) {
a += *(u32 *)(k);
b += *(u32 *)(k + 4);
c += *(u32 *)(k + 8);
__jhash_mix(a, b, c);
length -= 12;
k += 12;
}
/* Last block: affect all 32 bits of (c) */
switch (length) {
case 12: c += (u32)k[11]<<24; /* fall through */
case 11: c += (u32)k[10]<<16; /* fall through */
case 10: c += (u32)k[9]<<8; /* fall through */
case 9: c += k[8]; /* fall through */
case 8: b += (u32)k[7]<<24; /* fall through */
case 7: b += (u32)k[6]<<16; /* fall through */
case 6: b += (u32)k[5]<<8; /* fall through */
case 5: b += k[4]; /* fall through */
case 4: a += (u32)k[3]<<24; /* fall through */
case 3: a += (u32)k[2]<<16; /* fall through */
case 2: a += (u32)k[1]<<8; /* fall through */
case 1: a += k[0];
__jhash_final(a, b, c);
case 0: /* Nothing left to add */
break;
}
return c;
}
/* jhash2 - hash an array of u32's
* @k: the key which must be an array of u32's
* @length: the number of u32's in the key
* @initval: the previous hash, or an arbitray value
*
* Returns the hash value of the key.
*/
static inline u32 jhash2(const u32 *k, u32 length, u32 initval)
{
u32 a, b, c;
/* Set up the internal state */
a = b = c = JHASH_INITVAL + (length<<2) + initval;
/* Handle most of the key */
while (length > 3) {
a += k[0];
b += k[1];
c += k[2];
__jhash_mix(a, b, c);
length -= 3;
k += 3;
}
/* Handle the last 3 u32's */
switch (length) {
case 3: c += k[2]; /* fall through */
case 2: b += k[1]; /* fall through */
case 1: a += k[0];
__jhash_final(a, b, c);
case 0: /* Nothing left to add */
break;
}
return c;
}
/* __jhash_nwords - hash exactly 3, 2 or 1 word(s) */
static inline u32 __jhash_nwords(u32 a, u32 b, u32 c, u32 initval)
{
a += initval;
b += initval;
c += initval;
__jhash_final(a, b, c);
return c;
}
static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval)
{
return __jhash_nwords(a, b, c, initval + JHASH_INITVAL + (3 << 2));
}
static inline u32 jhash_2words(u32 a, u32 b, u32 initval)
{
return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2));
}
static inline u32 jhash_1word(u32 a, u32 initval)
{
return __jhash_nwords(a, 0, 0, initval + JHASH_INITVAL + (1 << 2));
}
#endif /* _LINUX_JHASH_H */
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copied from $(LINUX)/tools/perf/perf-sys.h (kernel 4.18) */
#ifndef _PERF_SYS_H
#define _PERF_SYS_H
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <linux/types.h>
#include <linux/perf_event.h>
/*
* remove the following headers to allow for userspace program compilation
* #include <linux/compiler.h>
* #include <asm/barrier.h>
*/
#ifdef __powerpc__
#define CPUINFO_PROC {"cpu"}
#endif
#ifdef __s390__
#define CPUINFO_PROC {"vendor_id"}
#endif
#ifdef __sh__
#define CPUINFO_PROC {"cpu type"}
#endif
#ifdef __hppa__
#define CPUINFO_PROC {"cpu"}
#endif
#ifdef __sparc__
#define CPUINFO_PROC {"cpu"}
#endif
#ifdef __alpha__
#define CPUINFO_PROC {"cpu model"}
#endif
#ifdef __arm__
#define CPUINFO_PROC {"model name", "Processor"}
#endif
#ifdef __mips__
#define CPUINFO_PROC {"cpu model"}
#endif
#ifdef __arc__
#define CPUINFO_PROC {"Processor"}
#endif
#ifdef __xtensa__
#define CPUINFO_PROC {"core ID"}
#endif
#ifndef CPUINFO_PROC
#define CPUINFO_PROC { "model name", }
#endif
static inline int
sys_perf_event_open(struct perf_event_attr *attr,
pid_t pid, int cpu, int group_fd,
unsigned long flags)
{
int fd;
fd = syscall(__NR_perf_event_open, attr, pid, cpu,
group_fd, flags);
#ifdef HAVE_ATTR_TEST
if (unlikely(test_attr__enabled))
test_attr__open(attr, pid, cpu, fd, group_fd, flags);
#endif
return fd;
}
#endif /* _PERF_SYS_H */
+102
View File
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/*
* XDP management utility functions
*
* Copyright (C) 2020 Toke Høiland-Jørgensen <[email protected]>
*/
#ifndef __LIBXDP_LIBXDP_H
#define __LIBXDP_LIBXDP_H
#include <stdio.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include "xdp_helpers.h"
#define XDP_BPFFS_ENVVAR "LIBXDP_BPFFS"
#define XDP_OBJECT_ENVVAR "LIBXDP_OBJECT_PATH"
enum xdp_attach_mode {
XDP_MODE_UNSPEC = 0,
XDP_MODE_NATIVE,
XDP_MODE_SKB,
XDP_MODE_HW
};
/* This is compatible with libbpf logging levels */
enum libxdp_print_level {
LIBXDP_WARN,
LIBXDP_INFO,
LIBXDP_DEBUG,
};
typedef int (*libxdp_print_fn_t)(enum libxdp_print_level level,
const char *, va_list ap);
libxdp_print_fn_t libxdp_set_print(libxdp_print_fn_t fn);
struct xdp_program;
struct xdp_multiprog;
long libxdp_get_error(const void *ptr);
int libxdp_strerror(int err, char *buf, size_t size);
struct xdp_program *xdp_program__from_bpf_obj(struct bpf_object *obj,
const char *section_name);
struct xdp_program *xdp_program__find_file(const char *filename,
const char *section_name,
struct bpf_object_open_opts *opts);
struct xdp_program *xdp_program__open_file(const char *filename,
const char *section_name,
struct bpf_object_open_opts *opts);
struct xdp_program *xdp_program__from_fd(int fd);
struct xdp_program *xdp_program__from_id(__u32 prog_id);
struct xdp_program *xdp_program__from_pin(const char *pin_path);
void xdp_program__close(struct xdp_program *xdp_prog);
enum xdp_attach_mode xdp_program__is_attached(const struct xdp_program *xdp_prog,
int ifindex);
const char *xdp_program__name(const struct xdp_program *xdp_prog);
const unsigned char *xdp_program__tag(const struct xdp_program *xdp_prog);
struct bpf_object *xdp_program__bpf_obj(struct xdp_program *xdp_prog);
const struct btf *xdp_program__btf(struct xdp_program *xdp_prog);
uint32_t xdp_program__id(const struct xdp_program *xdp_prog);
int xdp_program__fd(const struct xdp_program *xdp_prog);
unsigned int xdp_program__run_prio(const struct xdp_program *xdp_prog);
int xdp_program__set_run_prio(struct xdp_program *xdp_prog,
unsigned int run_prio);
bool xdp_program__chain_call_enabled(const struct xdp_program *xdp_prog,
enum xdp_action action);
int xdp_program__set_chain_call_enabled(struct xdp_program *prog,
unsigned int action,
bool enabled);
int xdp_program__print_chain_call_actions(const struct xdp_program *prog,
char *buf,
size_t buf_len);
int xdp_program__pin(struct xdp_program *xdp_prog, const char *pin_path);
int xdp_program__attach(struct xdp_program *xdp_prog,
int ifindex, enum xdp_attach_mode mode,
unsigned int flags);
int xdp_program__attach_multi(struct xdp_program **progs, size_t num_progs,
int ifindex, enum xdp_attach_mode mode,
unsigned int flags);
int xdp_program__detach(struct xdp_program *xdp_prog,
int ifindex, enum xdp_attach_mode mode,
unsigned int flags);
int xdp_program__detach_multi(struct xdp_program **progs, size_t num_progs,
int ifindex, enum xdp_attach_mode mode,
unsigned int flags);
struct xdp_multiprog *xdp_multiprog__get_from_ifindex(int ifindex);
struct xdp_program *xdp_multiprog__next_prog(const struct xdp_program *prog,
const struct xdp_multiprog *mp);
void xdp_multiprog__close(struct xdp_multiprog *mp);
int xdp_multiprog__detach(struct xdp_multiprog *mp);
enum xdp_attach_mode xdp_multiprog__attach_mode(const struct xdp_multiprog *mp);
struct xdp_program *xdp_multiprog__main_prog(const struct xdp_multiprog *mp);
bool xdp_multiprog__is_legacy(const struct xdp_multiprog *mp);
#endif
+279
View File
@@ -0,0 +1,279 @@
/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */
/*
* This file contains parsing functions that can be used in eXDP programs. The
* functions are marked as __always_inline, and fully defined in this header
* file to be included in the BPF program.
*
* Each helper parses a packet header, including doing bounds checking, and
* returns the type of its contents if successful, and -1 otherwise.
*
* For Ethernet and IP headers, the content type is the type of the payload
* (h_proto for Ethernet, nexthdr for IPv6), for ICMP it is the ICMP type field.
* All return values are in host byte order.
*/
#ifndef __PARSING_HELPERS_H
#define __PARSING_HELPERS_H
#include <stddef.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <bpf/bpf_endian.h>
/* Header cursor to keep track of current parsing position */
struct hdr_cursor {
void *pos;
};
/*
* struct vlan_hdr - vlan header
* @h_vlan_TCI: priority and VLAN ID
* @h_vlan_encapsulated_proto: packet type ID or len
*/
struct vlan_hdr {
__be16 h_vlan_TCI;
__be16 h_vlan_encapsulated_proto;
};
/*
* Struct icmphdr_common represents the common part of the icmphdr and icmp6hdr
* structures.
*/
struct icmphdr_common {
__u8 type;
__u8 code;
__sum16 cksum;
};
/* Allow users of header file to redefine VLAN max depth */
#ifndef VLAN_MAX_DEPTH
#define VLAN_MAX_DEPTH 4
#endif
/* Longest chain of IPv6 extension headers to resolve */
#ifndef IPV6_EXT_MAX_CHAIN
#define IPV6_EXT_MAX_CHAIN 6
#endif
static __always_inline int proto_is_vlan(__u16 h_proto)
{
return !!(h_proto == bpf_htons(ETH_P_8021Q) ||
h_proto == bpf_htons(ETH_P_8021AD));
}
/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns
* next header EtherType, BUT the ethhdr pointer supplied still points to the
* Ethernet header. Thus, caller can look at eth->h_proto to see if this was a
* VLAN tagged packet.
*/
static __always_inline int parse_ethhdr(struct hdr_cursor *nh, void *data_end,
struct ethhdr **ethhdr)
{
struct ethhdr *eth = nh->pos;
struct vlan_hdr *vlh;
__u16 h_proto;
int i;
if (eth + 1 > data_end)
return -1;
nh->pos = eth + 1;
*ethhdr = eth;
vlh = nh->pos;
h_proto = eth->h_proto;
/* Use loop unrolling to avoid the verifier restriction on loops;
* support up to VLAN_MAX_DEPTH layers of VLAN encapsulation.
*/
#pragma unroll
for (i = 0; i < VLAN_MAX_DEPTH; i++) {
if (!proto_is_vlan(h_proto))
break;
if (vlh + 1 > data_end)
break;
h_proto = vlh->h_vlan_encapsulated_proto;
vlh++;
}
nh->pos = vlh;
return h_proto; /* network-byte-order */
}
static __always_inline int skip_ip6hdrext(struct hdr_cursor *nh,
void *data_end,
__u8 next_hdr_type)
{
for (int i = 0; i < IPV6_EXT_MAX_CHAIN; ++i) {
struct ipv6_opt_hdr *hdr = nh->pos;
if (hdr + 1 > data_end)
return -1;
switch (next_hdr_type) {
case IPPROTO_HOPOPTS:
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
case IPPROTO_MH:
nh->pos = (char *)hdr + (hdr->hdrlen + 1) * 8;
next_hdr_type = hdr->nexthdr;
break;
case IPPROTO_AH:
nh->pos = (char *)hdr + (hdr->hdrlen + 2) * 4;
next_hdr_type = hdr->nexthdr;
break;
case IPPROTO_FRAGMENT:
nh->pos = (char *)hdr + 8;
next_hdr_type = hdr->nexthdr;
break;
default:
/* Found a header that is not an IPv6 extension header */
return next_hdr_type;
}
}
return -1;
}
static __always_inline int parse_ip6hdr(struct hdr_cursor *nh,
void *data_end,
struct ipv6hdr **ip6hdr)
{
struct ipv6hdr *ip6h = nh->pos;
/* Pointer-arithmetic bounds check; pointer +1 points to after end of
* thing being pointed to. We will be using this style in the remainder
* of the tutorial.
*/
if (ip6h + 1 > data_end)
return -1;
nh->pos = ip6h + 1;
*ip6hdr = ip6h;
return skip_ip6hdrext(nh, data_end, ip6h->nexthdr);
}
static __always_inline int parse_iphdr(struct hdr_cursor *nh,
void *data_end,
struct iphdr **iphdr)
{
struct iphdr *iph = nh->pos;
int hdrsize;
if (iph + 1 > data_end)
return -1;
hdrsize = iph->ihl * 4;
/* Variable-length IPv4 header, need to use byte-based arithmetic */
if (nh->pos + hdrsize > data_end)
return -1;
nh->pos += hdrsize;
*iphdr = iph;
return iph->protocol;
}
static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh,
void *data_end,
struct icmp6hdr **icmp6hdr)
{
struct icmp6hdr *icmp6h = nh->pos;
if (icmp6h + 1 > data_end)
return -1;
nh->pos = icmp6h + 1;
*icmp6hdr = icmp6h;
return icmp6h->icmp6_type;
}
static __always_inline int parse_icmphdr(struct hdr_cursor *nh,
void *data_end,
struct icmphdr **icmphdr)
{
struct icmphdr *icmph = nh->pos;
if (icmph + 1 > data_end)
return -1;
nh->pos = icmph + 1;
*icmphdr = icmph;
return icmph->type;
}
static __always_inline int parse_icmphdr_common(struct hdr_cursor *nh,
void *data_end,
struct icmphdr_common **icmphdr)
{
struct icmphdr_common *h = nh->pos;
if (h + 1 > data_end)
return -1;
nh->pos = h + 1;
*icmphdr = h;
return h->type;
}
/*
* parse_udphdr: parse the udp header and return the length of the udp payload
*/
static __always_inline int parse_udphdr(struct hdr_cursor *nh,
void *data_end,
struct udphdr **udphdr)
{
int len;
struct udphdr *h = nh->pos;
if (h + 1 > data_end)
return -1;
nh->pos = h + 1;
*udphdr = h;
len = bpf_ntohs(h->len) - sizeof(struct udphdr);
if (len < 0)
return -1;
return len;
}
/*
* parse_tcphdr: parse and return the length of the tcp header
*/
static __always_inline int parse_tcphdr(struct hdr_cursor *nh,
void *data_end,
struct tcphdr **tcphdr)
{
int len;
struct tcphdr *h = nh->pos;
if (h + 1 > data_end)
return -1;
len = h->doff * 4;
if ((void *) h + len > data_end)
return -1;
nh->pos = h + 1;
*tcphdr = h;
return len;
}
#endif /* __PARSING_HELPERS_H */
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */
#ifndef __PROG_DISPATCHER_H
#define __PROG_DISPATCHER_H
#include <linux/types.h>
#define XDP_METADATA_SECTION "xdp_metadata"
#define XDP_DISPATCHER_VERSION 1
/* default retval for dispatcher corresponds to the highest bit in the
* chain_call_actions bitmap; we use this to make sure the dispatcher always
* continues the calls chain if a function does not have an freplace program
* attached.
*/
#define XDP_DISPATCHER_RETVAL 31
#ifndef MAX_DISPATCHER_ACTIONS
#define MAX_DISPATCHER_ACTIONS 10
#endif
struct xdp_dispatcher_config {
__u8 num_progs_enabled;
__u32 chain_call_actions[MAX_DISPATCHER_ACTIONS];
__u32 run_prios[MAX_DISPATCHER_ACTIONS];
};
#endif
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */
#ifndef __XDP_HELPERS_H
#define __XDP_HELPERS_H
#define _CONCAT(x,y) x ## y
#define XDP_RUN_CONFIG(f) _CONCAT(_,f) SEC(".xdp_run_config")
#define XDP_DEFAULT_RUN_PRIO 50
#define XDP_DEFAULT_CHAIN_CALL_ACTIONS (1<<XDP_PASS)
#endif
+46
View File
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Used *ONLY* by BPF-prog running kernel side. */
#ifndef __XDP_STATS_KERN_H
#define __XDP_STATS_KERN_H
/* Data record type 'struct datarec' is defined in common/xdp_stats_kern_user.h,
* programs using this header must first include that file.
*/
#ifndef __XDP_STATS_KERN_USER_H
#warning "You forgot to #include <../common/xdp_stats_kern_user.h>"
#include <../common/xdp_stats_kern_user.h>
#endif
/* Keeps stats per (enum) xdp_action */
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, XDP_ACTION_MAX);
__type(key, __u32);
__type(value, struct datarec);
__uint(pinning, LIBBPF_PIN_BY_NAME);
} XDP_STATS_MAP_NAME SEC(".maps");
static __always_inline
__u32 xdp_stats_record_action(struct xdp_md *ctx, __u32 action)
{
if (action >= XDP_ACTION_MAX)
return XDP_ABORTED;
/* Lookup in kernel BPF-side return pointer to actual data record */
struct datarec *rec = bpf_map_lookup_elem(&xdp_stats_map, &action);
if (!rec)
return XDP_ABORTED;
/* BPF_MAP_TYPE_PERCPU_ARRAY returns a data record specific to current
* CPU and XDP hooks runs under Softirq, which makes it safe to update
* without atomic operations.
*/
rec->rx_packets++;
rec->rx_bytes += (ctx->data_end - ctx->data);
return action;
}
#endif /* __XDP_STATS_KERN_H */
+21
View File
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Used by BPF-prog kernel side BPF-progs and userspace programs,
* for sharing xdp_stats common struct and DEFINEs.
*/
#ifndef __XDP_STATS_KERN_USER_H
#define __XDP_STATS_KERN_USER_H
/* This is the data record stored in the map */
struct datarec {
__u64 rx_packets;
__u64 rx_bytes;
};
#ifndef XDP_ACTION_MAX
#define XDP_ACTION_MAX (XDP_REDIRECT + 1)
#endif
#define XDP_STATS_MAP_NAME xdp_stats_map
#endif /* __XDP_STATS_KERN_USER_H */
+87
View File
@@ -0,0 +1,87 @@
# Common Makefile parts for BPF-building with libbpf
# --------------------------------------------------
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
#
# This file should be included from your Makefile like:
# LIB_DIR = ../lib/
# include $(LIB_DIR)/common.mk
#
# It is expected that you define the variables:
# BPF_TARGETS and USER_TARGETS
# as a space-separated list
#
BPF_C = ${BPF_TARGETS:=.c}
BPF_OBJ = ${BPF_C:.c=.o}
USER_C := ${USER_TARGETS:=.c}
USER_OBJ := ${USER_C:.c=.o}
BPF_OBJ_INSTALL ?= $(BPF_OBJ)
# Expect this is defined by including Makefile, but define if not
LIB_DIR ?= ../lib
LDLIBS ?= $(USER_LIBS)
include $(LIB_DIR)/defines.mk
# Extend if including Makefile already added some
LIB_OBJS += $(foreach obj,$(UTIL_OBJS),$(LIB_DIR)/util/$(obj))
EXTRA_DEPS +=
EXTRA_USER_DEPS +=
# Detect submodule libbpf source file changes
ifeq ($(SYSTEM_LIBBPF),n)
LIBBPF_SOURCES := $(wildcard $(LIBBPF_DIR)/src/*.[ch])
endif
# BPF-prog kern and userspace shares struct via header file:
KERN_USER_H ?= $(wildcard common_kern_user.h)
CFLAGS += -I$(HEADER_DIR) -I$(LIB_DIR)/util
BPF_CFLAGS += -I$(HEADER_DIR)
BPF_HEADERS := $(wildcard $(HEADER_DIR)/bpf/*.h) $(wildcard $(HEADER_DIR)/xdp/*.h)
all: $(USER_TARGETS) $(BPF_OBJ) $(EXTRA_TARGETS)
.PHONY: clean
clean::
$(Q)rm -f $(USER_TARGETS) $(BPF_OBJ) $(USER_OBJ) $(USER_GEN) *.ll
$(OBJECT_LIBBPF): $(LIBBPF_SOURCES)
$(Q)$(MAKE) -C $(LIB_DIR) libbpf
$(CONFIGMK):
$(Q)$(MAKE) -C $(LIB_DIR)/.. config.mk
# Create expansions for dependencies
LIB_H := ${LIB_OBJS:.o=.h}
# Detect if any of common obj changed and create dependency on .h-files
$(LIB_OBJS): %.o: %.c %.h $(LIB_H)
$(Q)$(MAKE) -C $(dir $@) $(notdir $@)
$(USER_TARGETS): %: %.c $(OBJECT_LIBBPF) $(OBJECT_LIBXDP) $(LIBMK) $(LIB_OBJS) $(KERN_USER_H) $(EXTRA_DEPS) $(EXTRA_USER_DEPS)
$(QUIET_CC)$(CC) -Wall $(CFLAGS) $(LDFLAGS) -o $@ $(LIB_OBJS) \
$< $(LDLIBS)
$(BPF_OBJ): %.o: %.c $(KERN_USER_H) $(EXTRA_DEPS) $(BPF_HEADERS) $(LIBMK)
$(QUIET_CLANG)$(CLANG) -S \
-target bpf \
-D __BPF_TRACING__ \
$(BPF_CFLAGS) \
-Wall \
-Wno-unused-value \
-Wno-pointer-sign \
-Wno-compare-distinct-pointer-types \
-O2 -emit-llvm -c -g -o ${@:.o=.ll} $<
$(QUIET_LLC)$(LLC) -march=bpf -filetype=obj -o $@ ${@:.o=.ll}
.PHONY: test
ifeq ($(TEST_FILE),)
test:
@echo " No tests defined"
else
test: all
$(Q)$(TEST_DIR)/test_runner.sh $(TEST_FILE)
endif
+33
View File
@@ -0,0 +1,33 @@
CFLAGS ?= -O2 -g
BPF_CFLAGS ?= -Wno-visibility
include $(LIB_DIR)/../config.mk
PREFIX?=/usr/local
LIBDIR?=$(PREFIX)/lib
SBINDIR?=$(PREFIX)/sbin
HDRDIR?=$(PREFIX)/include/xdp
DATADIR?=$(PREFIX)/share
MANDIR?=$(DATADIR)/man
BPF_DIR_MNT ?=/sys/fs/bpf
BPF_OBJECT_DIR ?=$(LIBDIR)/bpf
MAX_DISPATCHER_ACTIONS ?=10
HEADER_DIR = $(LIB_DIR)/../headers
TEST_DIR = $(LIB_DIR)/testing
LIBBPF_DIR := $(LIB_DIR)/libbpf
DEFINES := -DBPF_DIR_MNT=\"$(BPF_DIR_MNT)\" -DBPF_OBJECT_PATH=\"$(BPF_OBJECT_DIR)\"
ifneq ($(PRODUCTION),1)
DEFINES += -DDEBUG
endif
HAVE_FEATURES :=
CFLAGS += $(DEFINES)
BPF_CFLAGS += $(DEFINES)
CONFIGMK := $(LIB_DIR)/../config.mk
LIBMK := Makefile $(CONFIGMK) $(LIB_DIR)/defines.mk $(LIB_DIR)/common.mk
Submodule
+1
Submodule lib/libbpf added at bbe442da7a
+79
View File
@@ -0,0 +1,79 @@
# -*- fill-column: 76; -*-
#+TITLE: Test environment script
#+OPTIONS: ^:nil
This directory contains a setup script that you can use to create test
environments for testing your XDP programs. It works by creating virtual
ethernet (veth) interface pairs and moving one end of each pair to another
network namespace. You can load the XDP program in the other namespace and
send traffic to it through the interface that is visible in the root
namespace.
Run =./testenv.sh= with no parameter to get a list of available commands, or
run =./testenv.sh --help= to get the full help listing with all options. The
script can maintain several environments active at the same time, and you
can switch between them using the =--name= option.
If you don't specify a name, the most recently used environment will be
used. If you don't specify a name when setting up a new environment, a
random name will be generated for you.
Examples:
Setup new environment named "test":
=./testenv.sh setup --name=test=
Create a shell alias for easy use of script from anywhere:
=eval $(./testenv.sh alias)=
See the currently active environment, and a list of all active environment
names (with alias defined as above):
=t status=
Enter the currently active environment:
=t enter=
Execute a command inside the environment:
=t exec -- ip a=
Teardown the environment:
=t teardown=
* Understanding the network topology
When setting up a test environment, there will be a virtual link between the
environment inside the new namespace, and the interface visible from the
host system root namespace. The new namespace will be named after the
environment name passed to the script, as will the interface visible in the
outer namespace. The interface *inside* the namespace will always be named
'veth0'.
To illustrate this, creating a test environment with the name 'test01' (with
=t setup --name test01= will result in the following environment being set
up:
#+begin_example
+-----------------------------+ +-----------------------------+
| Root namespace | | Testenv namespace 'test01' |
| | From 'test01' | |
| +--------+ TX-> RX-> +--------+ |
| | test01 +--------------------------+ veth0 | |
| +--------+ <-RX <-TX +--------+ |
| | From 'veth0' | |
+-----------------------------+ +-----------------------------+
#+end_example
The 'test01' interface visible in the root namespace is the one we will be
installing XDP programs on in the tutorial lessons. The XDP program will see
packets being *received* on this interface; as you can see from the diagram,
this means all packets being transmitted from inside the new namespace.
The setup is created this way to simulate the case where the host machine
have physical interfaces; but instead of the traffic arriving from outside
hosts on physical interfaces, they will arrive from inside the namespace on
the virtual interface. This also means that when you generate traffic to
test your XDP programs, you need to generate it from *inside* the test
environment. The =t ping= command will start the ping inside the test
environment by default, and you can run arbitrary programs inside the
environment by using =t exec -- <command>=, or simply spawning a shell with
=t enter=.
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# These are the config options for the testlab
SETUP_SCRIPT="$(dirname "$0")/setup-env.sh"
STATEDIR="${TMPDIR:-/tmp}/bpf-examples"
IP6_SUBNET=fc00:dead:cafe # must have exactly three :-separated elements
IP6_PREFIX_SIZE=64 # Size of assigned prefixes
IP6_FULL_PREFIX_SIZE=48 # Size of IP6_SUBNET
IP4_SUBNET=10.11
IP4_PREFIX_SIZE=24 # Size of assigned prefixes
IP4_FULL_PREFIX_SIZE=16 # Size of IP4_SUBNET
VLAN_IDS=(1 2)
GENERATED_NAME_PREFIX="bpfex"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Script to setup things inside a test environment, used by testenv.sh for
# executing commands.
#
# Author: Toke Høiland-Jørgensen ([email protected])
# Date: 7 March 2019
# Copyright (c) 2019 Red Hat
die()
{
echo "$1" >&2
exit 1
}
[ -n "$TESTENV_NAME" ] || die "TESTENV_NAME missing from environment"
[ -n "$1" ] || die "Usage: $0 <command to execute>"
set -o nounset
mount -t bpf bpf /sys/fs/bpf/ || die "Unable to mount /sys/fs/bpf inside test environment"
exec "$@"
+550
View File
@@ -0,0 +1,550 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Script to setup and manage test environment for the XDP tutorial.
# See README.org for instructions on how to use.
#
# Author: Toke Høiland-Jørgensen ([email protected])
# Date: 6 March 2019
# Copyright (c) 2019 Red Hat
set -o errexit
set -o nounset
umask 077
source "$(dirname "$0")/config.sh"
NEEDED_TOOLS="ethtool ip tc ping"
MAX_NAMELEN=15
# Global state variables that will be set by options etc below
GENERATE_NEW=0
CLEANUP_FUNC=
STATEFILE=
CMD=
NS=
LEGACY_IP=0
USE_VLAN=0
RUN_ON_INNER=0
# State variables that are written to and read from statefile
STATEVARS=(IP6_PREFIX IP4_PREFIX
INSIDE_IP6 INSIDE_IP4 INSIDE_MAC
OUTSIDE_IP6 OUTSIDE_IP4 OUTSIDE_MAC
ENABLE_IPV4 ENABLE_VLAN)
IP6_PREFIX=
IP4_PREFIX=
INSIDE_IP6=
INSIDE_IP4=
INSIDE_MAC=
OUTSIDE_IP6=
OUTSIDE_IP4=
OUTSIDE_MAC=
ENABLE_IPV4=0
ENABLE_VLAN=0
die()
{
echo "$1" >&2
exit 1
}
check_prereq()
{
local max_locked_mem=$(ulimit -l)
for t in $NEEDED_TOOLS; do
which "$t" > /dev/null || die "Missing required tools: $t"
done
if [ "$EUID" -ne "0" ]; then
die "This script needs root permissions to run."
fi
[ -d "$STATEDIR" ] || mkdir -p "$STATEDIR" || die "Unable to create state dir $STATEDIR"
if [ "$max_locked_mem" != "unlimited" ]; then
ulimit -l unlimited || die "Unable to set ulimit"
fi
}
get_nsname()
{
local GENERATE=${1:-0}
if [ -z "$NS" ]; then
[ -f "$STATEDIR/current" ] && NS=$(< "$STATEDIR/current")
if [ "$GENERATE" -eq "1" ] && [ -z "$NS" -o "$GENERATE_NEW" -eq "1" ]; then
NS=$(printf "%s-%04x" "$GENERATED_NAME_PREFIX" $RANDOM)
fi
fi
if [ "${#NS}" -gt "$MAX_NAMELEN" ]; then
die "Environment name '$NS' is too long (max $MAX_NAMELEN)"
fi
STATEFILE="$STATEDIR/${NS}.state"
}
ensure_nsname()
{
[ -z "$NS" ] && die "No environment selected; use --name to select one or 'setup' to create one"
[ -e "$STATEFILE" ] || die "Environment for $NS doesn't seem to exist"
echo "$NS" > "$STATEDIR/current"
read_statefile
}
get_num()
{
local num=1
if [ -f "$STATEDIR/highest_num" ]; then
num=$(( 1 + $(< "$STATEDIR/highest_num" )))
fi
echo $num > "$STATEDIR/highest_num"
printf "%x" $num
}
write_statefile()
{
[ -z "$STATEFILE" ] && return 1
echo > "$STATEFILE"
for var in "${STATEVARS[@]}"; do
echo "${var}='$(eval echo '$'$var)'" >> "$STATEFILE"
done
}
read_statefile()
{
local value
for var in "${STATEVARS[@]}"; do
value=$(source "$STATEFILE"; eval echo '$'$var)
eval "$var=\"$value\""
done
}
cleanup_setup()
{
echo "Error during setup, removing partially-configured environment '$NS'" >&2
set +o errexit
ip netns del "$NS" 2>/dev/null
ip link del dev "$NS" 2>/dev/null
rm -f "$STATEFILE"
}
cleanup_teardown()
{
echo "Warning: Errors during teardown, partial environment may be left" >&2
}
cleanup()
{
[ -n "$CLEANUP_FUNC" ] && $CLEANUP_FUNC
[ -d "$STATEDIR" ] || return 0
local statefiles=("$STATEDIR"/*.state)
if [ "${#statefiles[*]}" -eq 1 ] && [ ! -e "${statefiles[0]}" ]; then
rm -f "${STATEDIR}/highest_num" "${STATEDIR}/current"
rmdir "$STATEDIR"
fi
}
iface_macaddr()
{
local iface="$1"
local ns="${2:-}"
local output
if [ -n "$ns" ]; then
output=$(ip -br -n "$ns" link show dev "$iface")
else
output=$(ip -br link show dev "$iface")
fi
echo "$output" | awk '{print $3}'
}
set_sysctls()
{
local iface="$1"
local in_ns="${2:-}"
local nscmd=
[ -n "$in_ns" ] && nscmd="ip netns exec $in_ns"
local sysctls=(accept_dad
accept_ra
mldv1_unsolicited_report_interval
mldv2_unsolicited_report_interval)
for s in ${sysctls[*]}; do
$nscmd sysctl -w net.ipv6.conf.$iface.${s}=0 >/dev/null
done
}
wait_for_dev()
{
local iface="$1"
local in_ns="${2:-}"
local retries=5 # max retries
local nscmd=
[ -n "$in_ns" ] && nscmd="ip netns exec $in_ns"
while [ "$retries" -gt "0" ]; do
if ! $nscmd ip addr show dev $iface | grep -q tentative; then return 0; fi
sleep 0.5
retries=$((retries -1))
done
}
get_vlan_prefix()
{
# Split the IPv6 prefix, and add the VLAN ID to the upper byte of the fourth
# element in the prefix. This will break if the global prefix config doesn't
# have exactly three elements in it.
local prefix="$1"
local vid="$2"
(IFS=:; set -- $prefix; printf "%s:%s:%s:%x::" "$1" "$2" "$3" $(($4 + $vid * 4096)))
}
setup()
{
get_nsname 1
echo "Setting up new environment '$NS'"
[ -e "$STATEFILE" ] && die "Environment for '$NS' already exists"
local NUM=$(get_num "$NS")
local PEERNAME="testl-ve-$NUM"
[ -z "$IP6_PREFIX" ] && IP6_PREFIX="${IP6_SUBNET}:${NUM}::"
[ -z "$IP4_PREFIX" ] && IP4_PREFIX="${IP4_SUBNET}.$((0x$NUM))."
INSIDE_IP6="${IP6_PREFIX}2"
INSIDE_IP4="${IP4_PREFIX}2"
OUTSIDE_IP6="${IP6_PREFIX}1"
OUTSIDE_IP4="${IP4_PREFIX}1"
CLEANUP_FUNC=cleanup_setup
if ! mount | grep -q /sys/fs/bpf; then
mount -t bpf bpf /sys/fs/bpf/
fi
ip netns add "$NS"
ip link add dev "$NS" type veth peer name veth0 netns "$NS"
OUTSIDE_MAC=$(iface_macaddr "$NS")
INSIDE_MAC=$(iface_macaddr veth0 "$NS")
set_sysctls $NS
ip link set dev "$NS" up
ip addr add dev "$NS" "${OUTSIDE_IP6}/${IP6_PREFIX_SIZE}"
ethtool -K "$NS" rxvlan off txvlan off
# Prevent neighbour queries on the link
ip neigh add "$INSIDE_IP6" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
set_sysctls veth0 "$NS"
ip -n "$NS" link set dev lo up
ip -n "$NS" link set dev veth0 up
ip -n "$NS" addr add dev veth0 "${INSIDE_IP6}/${IP6_PREFIX_SIZE}"
ip netns exec "$NS" ethtool -K veth0 rxvlan off txvlan off
# Prevent neighbour queries on the link
ip -n "$NS" neigh add "$OUTSIDE_IP6" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
# Add route for whole test subnet, to make it easier to communicate between
# namespaces
ip -n "$NS" route add "${IP6_SUBNET}::/$IP6_FULL_PREFIX_SIZE" via "$OUTSIDE_IP6" dev veth0
if [ "$LEGACY_IP" -eq "1" ]; then
ip addr add dev "$NS" "${OUTSIDE_IP4}/${IP4_PREFIX_SIZE}"
ip -n "$NS" addr add dev veth0 "${INSIDE_IP4}/${IP4_PREFIX_SIZE}"
ip neigh add "$INSIDE_IP4" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
ip -n "$NS" neigh add "$OUTSIDE_IP4" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
ip -n "$NS" route add "${IP4_SUBNET}/${IP4_FULL_PREFIX_SIZE}" via "$OUTSIDE_IP4" dev veth0
ENABLE_IPV4=1
else
ENABLE_IPV4=0
fi
if [ "$USE_VLAN" -eq "1" ]; then
ENABLE_VLAN=1
for vid in "${VLAN_IDS[@]}"; do
local vlpx="$(get_vlan_prefix "$IP6_PREFIX" "$vid")"
local inside_ip="${vlpx}2"
local outside_ip="${vlpx}1"
ip link add dev "${NS}.$vid" link "$NS" type vlan id "$vid"
ip link set dev "${NS}.$vid" up
ip addr add dev "${NS}.$vid" "${outside_ip}/${IP6_PREFIX_SIZE}"
ip neigh add "$inside_ip" lladdr "$INSIDE_MAC" dev "${NS}.$vid" nud permanent
set_sysctls "${NS}/$vid"
ip -n "$NS" link add dev "veth0.$vid" link "veth0" type vlan id "$vid"
ip -n "$NS" link set dev "veth0.$vid" up
ip -n "$NS" addr add dev "veth0.$vid" "${inside_ip}/${IP6_PREFIX_SIZE}"
ip -n "$NS" neigh add "$outside_ip" lladdr "$OUTSIDE_MAC" dev "veth0.$vid" nud permanent
set_sysctls "veth0/$vid" "$NS"
done
else
ENABLE_VLAN=0
fi
write_statefile
CLEANUP_FUNC=
echo -n "Setup environment '$NS' with peer ip ${INSIDE_IP6}"
[ "$ENABLE_IPV4" -eq "1" ] && echo " and ${INSIDE_IP4}." || echo "."
echo "Waiting for interface configuration to settle..."
echo ""
wait_for_dev "$NS" && wait_for_dev veth0 "$NS"
LEGACY_IP=0 USE_VLAN=0 run_ping -c 1
echo "$NS" > "$STATEDIR/current"
}
teardown()
{
get_nsname && ensure_nsname "$NS"
echo "Tearing down environment '$NS'"
CLEANUP_FUNC=cleanup_teardown
ip link del dev "$NS"
ip netns del "$NS"
rm -f "$STATEFILE"
[ -d "/sys/fs/bpf/$NS" ] && rmdir "/sys/fs/bpf/$NS" || true
if [ -f "$STATEDIR/current" ]; then
local CUR=$(< "$STATEDIR/current" )
[[ "$CUR" == "$NS" ]] && rm -f "$STATEDIR/current"
fi
CLEANUP_FUNC=
}
reset()
{
teardown && setup
}
ns_exec()
{
get_nsname && ensure_nsname "$NS"
ip netns exec "$NS" env TESTENV_NAME="$NS" "$SETUP_SCRIPT" "$@"
}
enter()
{
ns_exec "${SHELL:-bash}"
}
run_ping()
{
local PING
local IP
get_nsname && ensure_nsname "$NS"
echo "Running ping from inside test environment:"
echo ""
if [ "$LEGACY_IP" -eq "1" ]; then
PING=$(which ping)
IP="${OUTSIDE_IP4}"
[ "$USE_VLAN" -eq "0" ] || die "Can't use --legacy-ip and --vlan at the same time."
[ "$ENABLE_IPV4" -eq "1" ] || die "No legacy IP addresses configured in environment."
else
PING=$(which ping6 2>/dev/null || which ping)
if [ "$USE_VLAN" -eq "0" ]; then
IP="${OUTSIDE_IP6}"
else
[ "$ENABLE_VLAN" -eq "1" ] || die "No VLANs configured in environment."
IP="$(get_vlan_prefix "$IP6_PREFIX" "${VLAN_IDS[0]}")1"
fi
fi
ns_exec "$PING" "$IP" "$@"
}
run_tcpdump()
{
get_nsname && ensure_nsname "$NS"
if [ "$RUN_ON_INNER" -eq "1" ]; then
ns_exec tcpdump -nei veth0 "$@"
else
tcpdump -nei "$NS" "$@"
fi
}
status()
{
get_nsname
echo "Currently selected environment: ${NS:-None}"
if [ -n "$NS" ] && [ -e "$STATEFILE" ]; then
read_statefile
echo -n " Namespace: "; ip netns | grep "^$NS"
echo " Prefix: ${IP6_PREFIX}/${IP6_PREFIX_SIZE}"
[ "$ENABLE_IPV4" -eq "1" ] && echo " Legacy prefix: ${IP4_PREFIX}0/${IP4_PREFIX_SIZE}"
echo -n " Iface: "; ip -br a show dev "$NS" | sed 's/\s\+/ /g'
fi
echo ""
echo "All existing environments:"
for f in "$STATEDIR"/*.state; do
if [ ! -e "$f" ]; then
echo " No environments exist"
break
fi
NAME=$(basename "$f" .state)
echo " $NAME"
done
}
print_alias()
{
local scriptname="$(readlink -e "$0")"
local sudo=
[ -t 1 ] && echo "Eval this with \`eval \$($0 alias)\` to create shell alias" >&2
if [ "$EUID" -ne "0" ]; then
sudo="sudo "
echo "WARNING: Creating sudo alias; be careful, this script WILL execute arbitrary programs" >&2
fi
echo "" >&2
echo "alias t='$sudo$scriptname'"
}
usage()
{
local FULL=${1:-}
echo "Usage: $0 [options] <command> [param]"
echo ""
echo "Commands:"
echo "setup Setup and initialise new environment"
echo "teardown Tear down existing environment"
echo "reset Reset environment to original state"
echo "exec <command> Exec <command> inside test environment"
echo "enter Execute shell inside test environment"
echo "ping Run ping inside test environment"
echo "status (or st) Show status of test environment"
echo "load Load XDP program on outer interface"
echo "unload Unload XDP program on outer interface"
echo "tcpdump Run on outer interface (or inner with --inner)"
echo ""
if [ -z "$FULL" ] ; then
echo "Use --help to see the list of options."
exit 1
fi
echo "Options:"
echo "-h, --help Show this usage text"
echo ""
echo "-n, --name <name> Set name of test environment. If not set, the last used"
echo " name will be used, or a new one generated."
echo ""
echo "-g, --gen-new Generate a new test environment name even though an existing"
echo " environment is selected as the current one."
echo ""
echo " --legacy-ip Enable legacy IP (IPv4) support."
echo " For setup and reset commands this enables configuration of legacy"
echo " IP addresses on the interface, for the ping command it switches to"
echo " legacy ping."
echo ""
echo " --vlan Enable VLAN support."
echo " When used with the setup and reset commands, these VLAN IDs will"
echo " be configured: ${VLAN_IDS[*]}. The VLAN interfaces are named as"
echo " <ifname>.<vlid>."
echo " When used with the ping command, the pings will be sent on the"
echo " first VLAN ID (${VLAN_IDS[0]})."
echo ""
echo " --inner Use with tcpdump command to run on inner interface."
echo ""
exit 1
}
OPTS="hn:gl:s:"
LONGOPTS="help,name:,gen-new,legacy-ip,vlan,inner"
OPTIONS=$(getopt -o "$OPTS" --long "$LONGOPTS" -- "$@")
[ "$?" -ne "0" ] && usage >&2 || true
eval set -- "$OPTIONS"
while true; do
arg="$1"
shift
case "$arg" in
-h | --help)
usage full >&2
;;
-n | --name)
NS="$1"
shift
;;
-g | --gen-new)
GENERATE_NEW=1
;;
--legacy-ip)
LEGACY_IP=1
;;
--vlan)
USE_VLAN=1
;;
--inner)
RUN_ON_INNER=1
;;
-- )
break
;;
esac
done
[ "$#" -eq 0 ] && usage >&2
case "$1" in
st|sta|status)
CMD=status
;;
setup|teardown|reset|enter)
CMD="$1"
;;
"exec")
CMD=ns_exec
;;
ping|tcpdump)
CMD="run_$1"
;;
"alias")
print_alias
exit 0
;;
"help")
usage full >&2
;;
*)
usage >&2
;;
esac
shift
trap cleanup EXIT
check_prereq
$CMD "$@"