From fb469ef6bc6ab4a8b93968c13cc242da8f57ba01 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Mon, 13 Dec 2021 06:41:28 +0530 Subject: [PATCH 01/16] DOCS: Update release document. Signed-off-by: Sridhar K. N. Rao Change-Id: I6421cb7f2c3033607214ea8c96d6a26aa49051c1 --- docs/release/release-notes/release-notes.rst | 2 +- testcases/testcase.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst index 60da2b5c..23bc0899 100644 --- a/docs/release/release-notes/release-notes.rst +++ b/docs/release/release-notes/release-notes.rst @@ -20,7 +20,7 @@ Anuket Lakelse Release pods and corresponding service information. * Deployment of different CNIs (userspace, sriov, calico, cilium, multus, and danm) are supported. * Custom DPPD-Prox and T-Rex containers are added - source files. - * l2fwd/testpmd container is included - source files. + * l2l3fwd and VPP containers are included to support service-chain testing usecases. * Additional Features diff --git a/testcases/testcase.py b/testcases/testcase.py index 7f4ad9ac..13ada1cc 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -204,8 +204,7 @@ class TestCase(object): # If running in k8s mode. # This value is set in main vsperf file - self._k8s = S.getValue('K8S') - if self._k8s: + if S.getValue('K8S'): if S.getValue('EXT_VSWITCH'): self._evfctl = extvswitchflctl.ExtVswitchFlowCtl() -- 2.16.6 From 85211e5d65ddafa0919f299a48527ede026e78c2 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Mon, 2 May 2022 19:46:18 +0530 Subject: [PATCH 02/16] [BUGFIX] Bug-fix in Trex-Client. This patch adds the fix Signed-off-by: Sridhar K. N. Rao Change-Id: Iee914ac737d088bd06bf8e762e9c6c588bc0b03e --- tools/pkt_gen/trex/trex_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/pkt_gen/trex/trex_client.py b/tools/pkt_gen/trex/trex_client.py index 680497ec..01d4d389 100644 --- a/tools/pkt_gen/trex/trex_client.py +++ b/tools/pkt_gen/trex/trex_client.py @@ -108,7 +108,7 @@ class Trex(ITrafficGenerator): self._stlclient = None self._verification_params = None self._show_packet_data = False - self.trial_results = [] + self.trial_results = {} def show_packet_info(self, packet_a, packet_b): """ @@ -521,8 +521,8 @@ class Trex(ITrafficGenerator): return stats - @staticmethod - def calculate_results(stats): + # @staticmethod + def calculate_results(self, stats): """Calculate results from Trex statistic """ result = OrderedDict() -- 2.16.6 From 826226243eca12ea6166d740205584392fdf7cad Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Tue, 3 May 2022 05:42:21 +0530 Subject: [PATCH 03/16] [Update] Support Newer versions of DPDK and OVS. This patch adds support for DPDK and OVS. The support is not default and user has to modify the Makefiles manually Changes post-review by Al. Signed-off-by: Sridhar K. N. Rao Change-Id: I5d6d2c6d693d31f0930b469de849722e2d5ec418 --- src/dpdk/Makefile.dpdk_meson | 74 +++++++++++++++++++ src/ovs/Makefile.ovs_dpdk_meson | 123 ++++++++++++++++++++++++++++++++ src/package-list.mk.dpdk_meson | 32 +++++++++ systems/centos/build_base_machine.sh | 3 + systems/debian/build_base_machine.sh | 3 + systems/fedora/24/build_base_machine.sh | 3 + systems/fedora/25/build_base_machine.sh | 3 + systems/fedora/26/build_base_machine.sh | 3 + systems/fedora/33/build_base_machine.sh | 3 + systems/opensuse/build_base_machine.sh | 3 + systems/rhel/7.2/build_base_machine.sh | 3 + systems/rhel/7.3/build_base_machine.sh | 3 + systems/rhel/7.5/build_base_machine.sh | 3 + systems/ubuntu/build_base_machine.sh | 3 + 14 files changed, 262 insertions(+) create mode 100755 src/dpdk/Makefile.dpdk_meson create mode 100644 src/ovs/Makefile.ovs_dpdk_meson create mode 100644 src/package-list.mk.dpdk_meson diff --git a/src/dpdk/Makefile.dpdk_meson b/src/dpdk/Makefile.dpdk_meson new file mode 100755 index 00000000..d0dd9e1a --- /dev/null +++ b/src/dpdk/Makefile.dpdk_meson @@ -0,0 +1,74 @@ +# makefile to manage dpdk package +# + +# Copyright 2022 Anuket +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Contributors: +# Aihua Li, Huawei Technologies. +# Martin Klozik, Intel Corporation. +# Christian Trautman, Red Hat Inc. +# Sridhar Rao, The Linux Foundation. + +include ../mk/master.mk +include ../package-list.mk +include /etc/os-release + +.PHONY: install force_make + +ifndef VHOST_USER + VHOST_USER = n +endif +WORK_DIR = dpdk +TAG_DONE_FLAG = $(WORK_DIR)/.$(DPDK_TAG).tag.done + +all: force_make + @echo "Finished making $(WORK_DIR) " + +INSTALL_TARGET = force_make + +force_make: $(TAG_DONE_FLAG) + $(AT)cd $(WORK_DIR) && git pull $(DPDK_URL) $(DPDK_TAG) + $(AT)cd $(WORK_DIR) && meson build + $(AT)cd $(WORK_DIR) && cd build && meson configure -Denable_kmods=true && ninja + @echo "Make done" + +install: $(INSTALL_TARGET) + $(AT)sudo cp -a $(WORK_DIR)/$(DPDK_TARGET)/kmod $(INSTALL_DIR)/lib/modules/$(KERNEL_VERSION) + @echo "install done" + +# hard way to clean and clobber +clean: + $(AT)cd $(WORK_DIR) && git clean -xfd *.o +clobber: + $(AT)rm -rf $(WORK_DIR) + +# distclean is for developer who would like to keep the +# clone git repo, saving time to fetch again from url +distclean: + $(AT)cd $(WORK_DIR) && git clean -xfd && git checkout -f + +test: + @echo "Make test in $(WORK_DIR) (stub) " + +sanity: + @echo "Make sanity in $(WORK_DIR) (stub) " + +$(WORK_DIR): + $(AT)git clone $(DPDK_URL) dpdk + +$(TAG_DONE_FLAG): $(WORK_DIR) + $(AT)cd $(WORK_DIR); git checkout $(DPDK_TAG) + $(AT)touch $@ diff --git a/src/ovs/Makefile.ovs_dpdk_meson b/src/ovs/Makefile.ovs_dpdk_meson new file mode 100644 index 00000000..4b4d997d --- /dev/null +++ b/src/ovs/Makefile.ovs_dpdk_meson @@ -0,0 +1,123 @@ +# makefile to manage ovs package +# + +# Copyright 2022 Anuket +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Contributors: +# Aihua Li, Huawei Technologies. +# Sridhar Rao, The Linux Foundation + +include ../mk/master.mk +include ../package-list.mk + +WORK_DIR = ovs +TAG_DONE_FLAG = $(WORK_DIR)/.$(OVS_TAG).done +CONFIG_CMD = +CONFIG_CMD += ./configure +OVS_VANILLA = ../../src_vanilla/ovs + +# If WITH_LINUX is defined, OVS is built without DPDK but with kernel +# module +# By default, OVS is built with DPDK + +# WITH_LINUX is the Linux kernel build directory used for building +# OVS kernel module as documented in OVS --with-linux switch +ifneq ($(WITH_LINUX),) # Building with Linux kernel +CONFIG_CMD += --with-linux=$(WITH_LINUX) + +else # Building with DPDK + +# DPDK_DIR is the top directory for dpdk source tree +# it can be passed in from Makefile command +# if it is not set, try to read it in from environment +# if it is still not set, then set it using relative path +PKG_CONFIG_PATH := ../dpdk/dpdk/build/meson-private +LD_CONFIG_PATH := ../dpdk/dpdk/build/lib +export LD_CONFIG_PATH +export PKG_CONFIG_PATH + +# echo $$ENV{LD_CONFIG_PATH} + +CONFIG_CMD += --with-dpdk=shared +CONFIG_CMD += CFLAGS="-g -O2 -Wno-cast-align" + +endif # Kernel vs. DPDK + +.PHONY: install force_install config force_make + +# install depends on make +force_install: force_make + +all: force_make + @echo "Finished making $(WORK_DIR) " + +config $(WORK_DIR)/Makefile: $(WORK_DIR)/configure + $(AT)cd $(WORK_DIR); LD_CONFIG_PATH=../../dpdk/dpdk/build/lib PKG_CONFIG_PATH=../../dpdk/dpdk/build/meson-private $(CONFIG_CMD) + @echo "Configure done" + +INSTALL_TARGET = force_install force_make + +force_make: $(WORK_DIR)/Makefile + $(AT)cd $(WORK_DIR) && git pull $(OVS_URL) $(OVS_TAG) + @echo "git pull done" + $(AT)echo "WITH_LINUX = $(WITH_LINUX)" + $(AT)$(MAKE) -C $(WORK_DIR) $(MORE_MAKE_FLAGS) + @echo "Make done" + +force_install: + $(AT)sudo make -C $(WORK_DIR) modules_install + $(AT)sudo $(MAKE) -C $(WORK_DIR) install + +install: $(INSTALL_TARGET) + +# hard way to clean and clobber +clean: + $(AT)cd $(WORK_DIR) && git clean -xfd *.o +clobber: + $(AT)rm -rf $(WORK_DIR) + $(AT)rm -rf $(OVS_VANILLA) + +# distclean is for developer who would like to keep the +# clone git repo, saving time to fetch again from url +distclean: + $(AT)cd $(WORK_DIR) && git clean -xfd && git checkout -f + +test: + @echo "Make test in $(WORK_DIR) (stub) " + +sanity: + @echo "Make sanity in $(WORK_DIR) (stub) " + +.PHONY: boot +# boot ovs is the process to produce the script 'configure' +boot $(WORK_DIR)/configure: + @echo "booting up ovs" + $(AT)cd $(WORK_DIR); ./boot.sh + @echo "done booting ovs" + +boot $(WORK_DIR)/configure: $(TAG_DONE_FLAG) + +$(WORK_DIR): + $(AT)git clone $(OVS_URL) ovs + $(AT)mkdir -p $(OVS_VANILLA) + $(AT)cp -rf ./* $(OVS_VANILLA) + +$(TAG_DONE_FLAG): $(WORK_DIR) + $(AT)cd ovs; git checkout $(OVS_TAG) +ifneq ($(PATCH_FILE),) + $(AT)cd $(WORK_DIR); patch -p1 < ../$(PATCH_FILE) +endif + $(AT)touch $@ diff --git a/src/package-list.mk.dpdk_meson b/src/package-list.mk.dpdk_meson new file mode 100644 index 00000000..3c219e53 --- /dev/null +++ b/src/package-list.mk.dpdk_meson @@ -0,0 +1,32 @@ +# Copyright (c) 2016-2017 Intel corporation. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Upstream Package List +# +# Everything here is defined as its suggested default +# value, it can always be overriden when invoking Make + +# dpdk section +# DPDK_URL ?= git://dpdk.org/dpdk +DPDK_URL ?= http://dpdk.org/git/dpdk +DPDK_TAG ?= v22.03 + +# OVS section +OVS_URL ?= https://github.com/openvswitch/ovs +OVS_TAG ?= v2.17.0 + +# VPP section +VPP_URL ?= https://git.fd.io/vpp +VPP_TAG ?= v21.01 + +# QEMU section +QEMU_URL ?= https://github.com/qemu/qemu.git +QEMU_TAG ?= v3.1.1 + +# TREX section +TREX_URL ?= https://github.com/cisco-system-traffic-generator/trex-core.git +TREX_TAG ?= v2.86 diff --git a/systems/centos/build_base_machine.sh b/systems/centos/build_base_machine.sh index fdaa37b5..31b26e87 100755 --- a/systems/centos/build_base_machine.sh +++ b/systems/centos/build_base_machine.sh @@ -47,6 +47,8 @@ pciutils cifs-utils sysstat sshpass +meson +ninja-build # libs libpcap-devel @@ -79,6 +81,7 @@ yum -y install $(echo " rh-python36 rh-python36-python-tkinter python3-setuptools +python3-pyelftools git-review " | grep -v ^#) # prevent ovs vanilla from building from source due to kernel incompatibilities diff --git a/systems/debian/build_base_machine.sh b/systems/debian/build_base_machine.sh index cc3f1eb8..e41837a2 100755 --- a/systems/debian/build_base_machine.sh +++ b/systems/debian/build_base_machine.sh @@ -27,6 +27,9 @@ apt-get -y install curl apt-get -y install git apt-get -y install wget apt-get -y install python3-venv +apt-get -y install python3-pyelftools +apt-get -y install meson +apt-get -y install ninja-build # Make and Compilers apt-get -y install make diff --git a/systems/fedora/24/build_base_machine.sh b/systems/fedora/24/build_base_machine.sh index bbde9b77..12bff64a 100644 --- a/systems/fedora/24/build_base_machine.sh +++ b/systems/fedora/24/build_base_machine.sh @@ -34,6 +34,8 @@ pixman-devel openssl-devel redhat-rpm-config elfutils-libelf-devel +meson +ninja-build # tools curl @@ -63,6 +65,7 @@ python3-libs libreport-python3 abrt-python3 abrt-addon-python3 +python3-pyelftools # libs numactl diff --git a/systems/fedora/25/build_base_machine.sh b/systems/fedora/25/build_base_machine.sh index df8ae620..0ac53809 100644 --- a/systems/fedora/25/build_base_machine.sh +++ b/systems/fedora/25/build_base_machine.sh @@ -34,6 +34,8 @@ pixman-devel openssl-devel redhat-rpm-config elfutils-libelf-devel +meson +ninja-build # tools curl @@ -63,6 +65,7 @@ python3-libs libreport-python3 abrt-python3 abrt-addon-python3 +python3-pyelftools # libs numactl diff --git a/systems/fedora/26/build_base_machine.sh b/systems/fedora/26/build_base_machine.sh index 84c0695b..323824b5 100644 --- a/systems/fedora/26/build_base_machine.sh +++ b/systems/fedora/26/build_base_machine.sh @@ -34,6 +34,8 @@ pixman-devel openssl-devel redhat-rpm-config elfutils-libelf-devel +meson +ninja-build # tools curl @@ -63,6 +65,7 @@ python3-libs libreport-python3 abrt-python3 abrt-addon-python3 +python3-pyelftools # libs numactl diff --git a/systems/fedora/33/build_base_machine.sh b/systems/fedora/33/build_base_machine.sh index f521827e..f3a27644 100644 --- a/systems/fedora/33/build_base_machine.sh +++ b/systems/fedora/33/build_base_machine.sh @@ -34,6 +34,8 @@ pixman-devel openssl-devel redhat-rpm-config elfutils-libelf-devel +meson +ninja-build # tools curl @@ -62,6 +64,7 @@ python3-libs python3-libreport python3-abrt python3-abrt-addon +python3-pyelftools # libs numactl diff --git a/systems/opensuse/build_base_machine.sh b/systems/opensuse/build_base_machine.sh index 8b26440c..b7d4249a 100755 --- a/systems/opensuse/build_base_machine.sh +++ b/systems/opensuse/build_base_machine.sh @@ -50,6 +50,8 @@ sysstat java-1_8_0-openjdk git-review mlocate +meson +ninja-build # python python3 @@ -59,6 +61,7 @@ python3-setuptools python3-devel python3-tk python3-virtualenv +python3-pyelftools # libraries libnuma1 diff --git a/systems/rhel/7.2/build_base_machine.sh b/systems/rhel/7.2/build_base_machine.sh index c0f367ab..aefa5e5d 100755 --- a/systems/rhel/7.2/build_base_machine.sh +++ b/systems/rhel/7.2/build_base_machine.sh @@ -34,6 +34,8 @@ pkglist=(\ openssl-devel\ pixman-devel\ sysstat\ + meson\ + ninja-build\ ) # Tools @@ -60,6 +62,7 @@ pkglist=( pkglist=( "${pkglist[@]}"\ python-six\ + python3-pyelftools\ ) # install RHEL compatible epel for sshpass diff --git a/systems/rhel/7.3/build_base_machine.sh b/systems/rhel/7.3/build_base_machine.sh index 42c36e4c..55532781 100755 --- a/systems/rhel/7.3/build_base_machine.sh +++ b/systems/rhel/7.3/build_base_machine.sh @@ -34,6 +34,8 @@ pkglist=(\ openssl-devel\ pixman-devel\ sysstat\ + meson\ + ninja-build\ ) # Tools @@ -60,6 +62,7 @@ pkglist=( pkglist=( "${pkglist[@]}"\ python-six\ + python3-pyelftools\ ) # install RHEL compatible epel for sshpass diff --git a/systems/rhel/7.5/build_base_machine.sh b/systems/rhel/7.5/build_base_machine.sh index deb4e8a2..3dc04b15 100755 --- a/systems/rhel/7.5/build_base_machine.sh +++ b/systems/rhel/7.5/build_base_machine.sh @@ -34,6 +34,8 @@ pkglist=(\ openssl-devel\ pixman-devel\ sysstat\ + meson\ + ninja-build\ ) # Tools @@ -60,6 +62,7 @@ pkglist=( pkglist=( "${pkglist[@]}"\ python-six\ + python3-pyelftools\ ) # install RHEL compatible epel for sshpass diff --git a/systems/ubuntu/build_base_machine.sh b/systems/ubuntu/build_base_machine.sh index 2f3e7b25..910cd173 100755 --- a/systems/ubuntu/build_base_machine.sh +++ b/systems/ubuntu/build_base_machine.sh @@ -31,6 +31,9 @@ apt-get -y install libssl1.0.0 apt-get -y install libxml2 apt-get -y install zlib1g-dev apt-get -y install scapy +apt-get -y install meson +apt-get -y install ninja-build +apt-get -y install python3-pyelftools # Linux Kernel Source apt-get -y install linux-source -- 2.16.6 From 75ea2c825f858b22f79ad1535b733f4a72fd125a Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Wed, 4 May 2022 00:18:03 +0530 Subject: [PATCH 04/16] [Core] Minor Updates to the flow. This patch adds minor updates to the flow. 1. Add license Information 2. Remove spaces. Signed-off-by: Sridhar K. N. Rao Change-Id: I805ee93afc5c7b1c7c157dad643b9ddac2d4d55d --- conf/12_k8s.conf | 4 +++ conf/kubernetes/01_testcases.conf | 59 +++++++++++++++++++++++++++++++++++++++ core/pod_controller.py | 10 ++++--- testcases/testcase.py | 35 ++++++++++++++--------- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/conf/12_k8s.conf b/conf/12_k8s.conf index 04e70de4..1b51f687 100644 --- a/conf/12_k8s.conf +++ b/conf/12_k8s.conf @@ -1,3 +1,4 @@ +# Copyright 2022 The Linux Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,3 +46,6 @@ APP_NAME = 'l2fwd' EXT_VSWITCH = True EXT_VSWITCH_TYPE = 'VPP' EXT_OVS_BRIDGE = 'br0' + +# Flow Control +USCNI_INTERFACE_PAIRS = 1 diff --git a/conf/kubernetes/01_testcases.conf b/conf/kubernetes/01_testcases.conf index 9e238557..a6bb8d35 100644 --- a/conf/kubernetes/01_testcases.conf +++ b/conf/kubernetes/01_testcases.conf @@ -1,3 +1,22 @@ +# Copyright 2022 Spirent Communications, The Linux Foundation, and others. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Generic test configuration options are described at conf/01_testcases.conf +# + + K8SPERFORMANCE_TESTS = [ { "Name": "pcp_tput", @@ -31,4 +50,44 @@ K8SPERFORMANCE_TESTS = [ }, }, }, + { + "Name": "c2c_tput", + "Deployment": "pcp", + "Description": "LTD.Throughput.RFC2544.Throughput", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + }, + { + "Name": "c2c_2inf_tput", + "Deployment": "pcp", + "Description": "LTD.Throughput.RFC2544.Throughput", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + }, + { + "Name": "c2c2c_tput", + "Deployment": "pcp", + "Description": "LTD.Throughput.RFC2544.Throughput", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + }, + { + "Name": "c2c2c_2inf_tput", + "Deployment": "pcp", + "Description": "LTD.Throughput.RFC2544.Throughput", + "Parameters" : { + "TRAFFIC" : { + "traffic_type" : "rfc2544_throughput", + }, + }, + }, ] diff --git a/core/pod_controller.py b/core/pod_controller.py index e522b823..0e8e1b09 100644 --- a/core/pod_controller.py +++ b/core/pod_controller.py @@ -1,4 +1,4 @@ -# Copyright 2020 Spirent Communications +# Copyright 2022 Spirent Communications, The Linux Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -44,12 +44,14 @@ class PodController(): self._pod_class = pod_class self._deployment = deployment.lower() self._pods = [] - if 'pcp' in self._deployment: + single_pods = ['pcp', 'c2c'] + two_pods = ['pccp', 'c2c2c'] + if any(item in self._deployment for item in single_pods): pod_number = 1 - elif 'pccp'in self._deployment: + elif any(item in self._deployment for item in two_pods): pod_number = 2 print("POD COUNTING DONE") - settings.setValue('POD_COUNT', pod_number) + settings.setValue('POD_COUNT', pod_number) # we will have single controller for all pods if pod_number: self._pods.append(pod_class()) diff --git a/testcases/testcase.py b/testcases/testcase.py index 13ada1cc..12d71bc5 100644 --- a/testcases/testcase.py +++ b/testcases/testcase.py @@ -640,19 +640,28 @@ class TestCase(object): if S.getValue('K8S') and 'sriov' not in S.getValue('PLUGIN'): if 'Ovs' in S.getValue('VSWITCH'): # Add OVS Flows - logging.info("Kubernetes: Adding OVS Connections") - flow = {'table':'0', 'in_port':'1', - 'idle_timeout':'0', 'actions': ['output:3']} - vswitch.add_flow(bridge, flow) - flow = {'table':'0', 'in_port':'3', - 'idle_timeout':'0', 'actions': ['output:1']} - vswitch.add_flow(bridge, flow) - flow = {'table':'0', 'in_port':'2', - 'idle_timeout':'0', 'actions': ['output:4']} - vswitch.add_flow(bridge, flow) - flow = {'table':'0', 'in_port':'4', - 'idle_timeout':'0', 'actions': ['output:2']} - vswitch.add_flow(bridge, flow) + if S.getValue('USCNI_INTERFACE_PAIRS') == 1: + logging.info("Kubernetes: Adding 1-Pair OVS Connections") + flow = {'table':'0', 'in_port':'1', + 'idle_timeout':'0', 'actions': ['output:2']} + vswitch.add_flow(bridge, flow) + flow = {'table':'0', 'in_port':'2', + 'idle_timeout':'0', 'actions': ['output:1']} + vswitch.add_flow(bridge, flow) + elif S.getValue('USCNI_INTERFACE_PAIRS') == 2: + logging.info("Kubernetes: Adding 2-Pairs OVS Connections") + flow = {'table':'0', 'in_port':'1', + 'idle_timeout':'0', 'actions': ['output:3']} + vswitch.add_flow(bridge, flow) + flow = {'table':'0', 'in_port':'3', + 'idle_timeout':'0', 'actions': ['output:1']} + vswitch.add_flow(bridge, flow) + flow = {'table':'0', 'in_port':'2', + 'idle_timeout':'0', 'actions': ['output:4']} + vswitch.add_flow(bridge, flow) + flow = {'table':'0', 'in_port':'4', + 'idle_timeout':'0', 'actions': ['output:2']} + vswitch.add_flow(bridge, flow) elif 'vpp' in S.getValue('VSWITCH'): phy_ports = vswitch.get_ports() virt_port0 = 'memif1/0' -- 2.16.6 From 8d283a3f7cfb491b881e4393e75fc66a2c11c0dc Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Wed, 2 Mar 2022 16:11:35 +0530 Subject: [PATCH 05/16] QEMU: Fix Qemu Build. This patch fixes the qemu build issue. Signed-off-by: Sridhar K. N. Rao Change-Id: I9ffa9487b392a309141b8abfb3fa951c0b89e2fd --- src/qemu/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qemu/Makefile b/src/qemu/Makefile index b105496c..fe658c41 100755 --- a/src/qemu/Makefile +++ b/src/qemu/Makefile @@ -1,7 +1,7 @@ # makefile to manage qemu package # -# Copyright (c) 2015-2016 OPNFV and Intel Corporation. +# Copyright (c) 2015-2022 Anuket, Intel Corporation, and The Linux Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ include ../mk/master.mk include ../package-list.mk WORK_DIR = qemu +SUBMODULE_CONFIG_FILE = $(WORK_DIR)/.gitmodules TAG_DONE_FLAG = $(WORK_DIR)/.$(QEMU_TAG).done INSTALL_TARGET = force_make force_install CONFIG_CMD = @@ -40,6 +41,9 @@ config $(WORK_DIR)/Makefile: $(WORK_DIR)/configure force_make: $(WORK_DIR)/Makefile $(AT)cd $(WORK_DIR) && git pull $(QEMU_URL) $(QEMU_TAG) $(AT)echo "git pull done" + $(AT)sed -i -e 's/https\:\/\/git.qemu.org\/git\/capstone\.git/https\:\/\/github.com\/capstone-engine\/capstone\.git/g' $(SUBMODULE_CONFIG_FILE) + $(AT)sed -i -e 's/https\:\/\/git.qemu.org\/git\/dtc\.git/https\:\/\/github.com\/qemu\/dtc\.git/g' $(SUBMODULE_CONFIG_FILE) + $(AT)sed -i -e 's/https\:\/\/git.qemu.org\/git\/keycodemapdb\.git/https\:\/\/github.com\/qemu\/keycodemapdb\.git/g' $(SUBMODULE_CONFIG_FILE) $(AT)$(MAKE) -C $(WORK_DIR) $(MORE_MAKE_FLAGS) @echo "Make done" -- 2.16.6 From 19d0afa65e926d3efb3c34439699e08a670e5101 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Mon, 25 Apr 2022 19:50:33 +0530 Subject: [PATCH 06/16] [TOO] Add Cloud Information Tool. This patch add cloud information gathering tool. Add License Information Signed-off-by: Sridhar K. N. Rao Change-Id: I1b3e89620b8aa0d189a8718b4b35aeb3db4bb320 --- tools/cloudinfo.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tools/cloudinfo.py diff --git a/tools/cloudinfo.py b/tools/cloudinfo.py new file mode 100644 index 00000000..18550e78 --- /dev/null +++ b/tools/cloudinfo.py @@ -0,0 +1,94 @@ +# Copyright 2022 The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Get Cloud information and save it a file +""" + +import os +import json +from kubernetes import client, config +from kubernetes.client.rest import ApiException +from conf import settings + +def save_kubernetes_info(): + """ + Save Kubernetes Cluster Info + """ + config.load_kube_config(settings.getValue('K8S_CONFIG_FILEPATH')) + with open(os.path.join(settings.getValue('RESULTS_PATH'), + 'cloud_info.txt'), 'a+') as outf: + api = client.CoreV1Api() + try: + node_info = api.list_node() + except ApiException as err: + raise Exception from err + for ni_item in node_info.items: + outf.write("\n ******************************************** \n") + outf.write("\n System Information \n") + sinfo = {'Architecture': ni_item.status.node_info.architecture, + 'Container Runtime Version':ni_item.status.node_info.container_runtime_version, + 'kernel version':ni_item.status.node_info.kernel_version, + 'Kube Proxy Version':ni_item.status.node_info.kube_proxy_version, + 'Kubelet Version':ni_item.status.node_info.kubelet_version, + 'Operating System':ni_item.status.node_info.operating_system, + 'OS Image':ni_item.status.node_info.os_image} + json.dump(sinfo, outf, indent=4) + outf.write("\n List of Addresses \n") + addresses = [] + for addrs in ni_item.status.addresses: + entry = {'address': addrs.address, 'type': addrs.type} + addresses.append(entry) + json.dump(entry, outf, indent=4) + sinfo['List of Addresses'] = entry + outf.write("\n Allocatable Resources \n") + sinfo['Allocatable Resources'] = ni_item.status.allocatable + json.dump(ni_item.status.allocatable, outf, indent=4) + outf.write("\n Available Resources \n") + sinfo['Available Resources'] = ni_item.status.capacity + json.dump(ni_item.status.capacity, outf, indent=4) + api = client.VersionApi() + try: + version_info = api.get_code() + except ApiException as err: + raise Exception from err + outf.write("\n Version Information \n") + vinfo = {'git_commit': version_info.git_commit, 'git_version': version_info.git_version, + 'platform': version_info.platform, 'go_version': version_info.go_version} + #json.dump(vinfo, outf, indent=4) + result = {**sinfo, **vinfo} + return result + +def save_openstack_info(): + """ + Save Openstack Info + """ + return None + +def save_cloud_info(): + """ + Save Cloud Information + """ + if settings.getValue('K8S'): + return save_kubernetes_info() + elif settings.getValue('OPENSTACK'): + return save_openstack_info() + else: + print("Unsupported Cloud") + return None + return 0 + +if __name__ == "__main__": + save_cloud_info() -- 2.16.6 From b2d51c9ee5839b767d1d34a8b89e99b3dd4ec302 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Mon, 2 May 2022 19:19:18 +0530 Subject: [PATCH 07/16] [TOOL] Stressor for Kubernetes Environments. This stressor is based on Spirent-cloudstress. Add license information Signed-off-by: Sridhar K. N. Rao Change-Id: Id43fd7603ed32d88b8adcd33f8becf0106bc3f46 --- tools/k8s/load-generator/Dockerfile | 27 ++++++++++++++++ tools/k8s/load-generator/README.md | 24 ++++++++++++++ tools/k8s/load-generator/config.zpl | 52 ++++++++++++++++++++++++++++++ tools/k8s/load-generator/csdeployment.yaml | 39 ++++++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 tools/k8s/load-generator/Dockerfile create mode 100755 tools/k8s/load-generator/README.md create mode 100755 tools/k8s/load-generator/config.zpl create mode 100644 tools/k8s/load-generator/csdeployment.yaml diff --git a/tools/k8s/load-generator/Dockerfile b/tools/k8s/load-generator/Dockerfile new file mode 100644 index 00000000..6212f6b2 --- /dev/null +++ b/tools/k8s/load-generator/Dockerfile @@ -0,0 +1,27 @@ +# Copyright 2022 The Linux Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM debian:bullseye +RUN mkdir -p /opt/spirent/inception +EXPOSE 3357 +EXPOSE 13731 +EXPOSE 13771 +EXPOSE 17731 +RUN apt-get clean && \ + apt-get update && \ + apt-get install -y net-tools procps +COPY ./config.zpl /opt/spirent/inception/config.zpl +COPY ./cloudstress /opt/spirent/inception/cloudstress +WORKDIR /opt/spirent/inception +CMD ["cloudstress", "--conffile", "config.zpl"] diff --git a/tools/k8s/load-generator/README.md b/tools/k8s/load-generator/README.md new file mode 100755 index 00000000..3b03a9b0 --- /dev/null +++ b/tools/k8s/load-generator/README.md @@ -0,0 +1,24 @@ +# Stressor for Cloud-Native Usecases using Spirent Cloudstress + +## Updating the configuration + +Modify the config.zpl file according to your requirements - configure the required amount of CPU and Memory stresses. + +## Building container (if required) + +Download the cloudstress binary from the artifacts. + +Build using the following command + +```sh +$ docker build --rm -t autocloudstress . +``` + +## Using existing container. + +Pre-built container exists in dockerhub as: vsperf/autocloudstress:thoth + +## Running workloads. + +Run the built or existing container as pods. +Example deployment file: csdeployment.yaml present in this folder. diff --git a/tools/k8s/load-generator/config.zpl b/tools/k8s/load-generator/config.zpl new file mode 100755 index 00000000..a0dbc281 --- /dev/null +++ b/tools/k8s/load-generator/config.zpl @@ -0,0 +1,52 @@ +# Copyright 2022 Spirent Communications. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +#rfc.zeromq.org/spec:4/ZPL + +generator + cpu + utilization = 100.0 + running = "true" + block + writes_per_sec = 1048576 + write_size = 2M + reads_per_sec = 1048576 + read_size = 2M + queue_depth = 4 + vdev_path = "/tmp" + vdev_size = 1048576000 + file_size = 1000 + running = "true" + pattern = "random" + memory + writes_per_sec = 1048576 + write_size = 2000000 + reads_per_sec = 1048576 + read_size = 2000000 + buffer_size = 250000000 + running = "true" + pattern = "random" + network.client + connections = 10 + threads = 5 + ops_per_connection = 10 + protocol = tcp + writes_per_sec = 1043576 + write_size = 2000000 + reads_per_sec = 1043576 + read_size = 2000000 + running = "true" + network.server + running = "true" diff --git a/tools/k8s/load-generator/csdeployment.yaml b/tools/k8s/load-generator/csdeployment.yaml new file mode 100644 index 00000000..e6d32d5f --- /dev/null +++ b/tools/k8s/load-generator/csdeployment.yaml @@ -0,0 +1,39 @@ +# Copyright 2022 The Linux Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cloudstress-deployment +spec: + selector: + matchLabels: + app: cloudstress + replicas: 2 # tells deployment to run 2 pods matching the template + template: + metadata: + labels: + app: cloudstress + spec: + containers: + - name: cloudstress + image: vsperf/autocloudstress:thoth + resources: + requests: + cpu: "2000m" + memory: "2000Mi" + limits: + cpu: "2000m" + memory: "2000Mi" -- 2.16.6 From 2349ab83373ff51fe2ea6a5139f7f6f47744b9cd Mon Sep 17 00:00:00 2001 From: Daniele Zulberti Date: Tue, 3 May 2022 16:41:51 +0200 Subject: [PATCH 08/16] [docs] Kubernetes East-West Signed-off-by: Daniele Zulberti Change-Id: I1c47906ae81bf3666520c65e66920d3a475f604e --- .../Bidirectional Test with PROX.md | 226 +++++++++++++ .../userspace-rapid-pod-1-interface-OvS-DPDK.yaml | 68 ++++ .../userspace-rapid-pod-1-interface-VPP.yaml | 68 ++++ .../userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml | 68 ++++ .../userspace-rapid-pod-2-interfaces-VPP.yaml | 68 ++++ .../userspace-trex-pod-2-interfaces-VPP.yaml | 64 ++++ .../Kubernetes/userspace-ovs-netAttach.yaml | 47 +++ .../Kubernetes/userspace-vpp-netAttach-memif.yaml | 46 +++ .../Configurations/PROX/README.md | 9 + .../unidirectional/parameters.lua | 11 + .../unidirectional/prox.cfg | 61 ++++ .../bidirectional/parameters.lua | 17 + .../bidirectional/prox.cfg | 102 ++++++ .../unidirectional/parameters.lua | 11 + .../unidirectional/prox.cfg | 69 ++++ .../unidirectional/parameters.lua | 7 + .../swap-pod_1-interface/unidirectional/prox.cfg | 35 ++ .../bidirectional/parameters.lua | 8 + .../swap-pod_2-interfaces/bidirectional/prox.cfg | 51 +++ .../unidirectional/parameters.lua | 7 + .../swap-pod_2-interfaces/unidirectional/prox.cfg | 43 +++ .../Configurations/Rapid/example-bidirectional.env | 36 ++ .../Configurations/Rapid/example-bidirectional.tst | 43 +++ .../Configurations/Rapid/example.env | 33 ++ .../Configurations/Rapid/example.tst | 43 +++ .../Configurations/TRex/vsperf-trex.conf | 36 ++ .../east-west-benchmarking/Limitations.md | 10 + .../east-west-benchmarking/Requirements.md | 116 +++++++ .../Test with PROX and OVS.md | 372 ++++++++++++++++++++ .../Test with PROX and VPP.md | 375 +++++++++++++++++++++ .../Test with TRex and VPP.md | 228 +++++++++++++ .../east-west-benchmarking/Topologies-EastWest.md | 34 ++ .../images/prox-vpp-screen-swap.png | Bin 0 -> 27990 bytes .../images/prox-vpp-screen.png | Bin 0 -> 31489 bytes .../east-west-benchmarking/images/topologies.png | Bin 0 -> 79037 bytes .../east-west-benchmarking/images/topology1.png | Bin 0 -> 17692 bytes .../east-west-benchmarking/images/trex-screen.png | Bin 0 -> 26692 bytes 37 files changed, 2412 insertions(+) create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Bidirectional Test with PROX.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-OvS-DPDK.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-VPP.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/TRex-pod/userspace-trex-pod-2-interfaces-VPP.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-ovs-netAttach.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-vpp-netAttach-memif.yaml create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/README.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/parameters.lua create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/prox.cfg create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.env create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.tst create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.env create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.tst create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Configurations/TRex/vsperf-trex.conf create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Limitations.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Requirements.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Test with PROX and OVS.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Test with PROX and VPP.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Test with TRex and VPP.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/Topologies-EastWest.md create mode 100644 docs/testing/user/userguide/east-west-benchmarking/images/prox-vpp-screen-swap.png create mode 100644 docs/testing/user/userguide/east-west-benchmarking/images/prox-vpp-screen.png create mode 100644 docs/testing/user/userguide/east-west-benchmarking/images/topologies.png create mode 100644 docs/testing/user/userguide/east-west-benchmarking/images/topology1.png create mode 100644 docs/testing/user/userguide/east-west-benchmarking/images/trex-screen.png diff --git a/docs/testing/user/userguide/east-west-benchmarking/Bidirectional Test with PROX.md b/docs/testing/user/userguide/east-west-benchmarking/Bidirectional Test with PROX.md new file mode 100644 index 00000000..5f4e6f3a --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Bidirectional Test with PROX.md @@ -0,0 +1,226 @@ +# Configure PROX for bidirectional traffic + +To configure PROX for bidirectional traffic starting from a unidirectional setup just modify the `parameters.lua` and the `prox.cfg` and then modify the vSwitch configuration to connect the interfaces also in the opposite direction. + +### Modify the `parameters.lua` file + +Duplicate these parameters: + +- `local_ip` +- `local_hex_ip` +- `local_hex_mac` +- `gencores` +- `latcores` + +Adjust the names to make them unique. The file now should look like this: + +``` +... +local_ip1="192.168.30.11/24" +local_hex_ip1=convertIPToHex(local_ip1) +local_ip2="192.168.30.12/24" +local_hex_ip2=convertIPToHex(local_ip2) +... +local_hex_mac1="de ad c3 52 79 9b" +local_hex_mac2="02 09 c0 fd ba 1f" +gencores1="15" +gencores2="16" +latcores1="17" +latcores2="18" +... +``` + +### Modify the `prox.cfg` file + +In this file duplicate the gencore and the latcore tasks adjust the tasks names and the variables names according to the ones in `parameters.lua` and then invert the ports number in the new tasks. + +After these changes, the result is below: + +``` +[core $gencores1] +name=p0 +task=0 +mode=gen +tx port=p0 +bps=1250000000 +pkt inline=${local_hex_mac2} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $gencores2] +name=p1 +task=0 +mode=gen +tx port=p1 +bps=1250000000 +pkt inline=${local_hex_mac1} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip2} ${local_hex_ip1} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $latcores1] +name=lat1 +task=0 +mode=lat +;sub mode=l3 +rx port=p1 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 + +[core $latcores2] +name=lat2 +task=0 +mode=lat +;sub mode=l3 +rx port=p0 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 +``` + +### Modify VPP + +To configure the opposite direction just copy the l2patches for the unidirectional traffic and invert the ports number: + +``` +vpp# show l2patch + memif1/0 -> memif3/0 + memif4/0 -> memif2/0 +vpp# test l2patch rx memif2/0 tx memif4/0 +vpp# test l2patch rx memif3/0 tx memif1/0 +vpp# show l2patch + memif1/0 -> memif3/0 + memif2/0 -> memif4/0 + memif3/0 -> memif1/0 + memif4/0 -> memif2/0 +``` + +### Modify OvS-DPDK + +To configure the opposite direction just edit the `setup-flows.sh` copying the existing flows for the unidirectional traffic and invert the ports actions: + +``` bash +udo ovs-ofctl --timeout 10 -O OpenFlow13 del-flows vsperf-br0 +sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=1,idle_timeout=0,action=output:3 +sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=4,idle_timeout=0,action=output:2 +sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=3,idle_timeout=0,action=output:1 +sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=2,idle_timeout=0,action=output:4 +``` + +Run `sudo ./setup_flows.sh` and verify that the flows are correct by running `sudo ovs-ofctl dump-flows vsperf-br0`: + +``` bash +[opnfv@worker utilities]$ sudo ovs-ofctl dump-flows vsperf-br0 + cookie=0x0, duration=10.400s, table=0, n_packets=0, n_bytes=0, in_port=1 actions=output:3 + cookie=0x0, duration=10.348s, table=0, n_packets=0, n_bytes=0, in_port=4 actions=output:2 + cookie=0x0, duration=10.410s, table=0, n_packets=0, n_bytes=0, in_port=3 actions=output:1 + cookie=0x0, duration=10.358s, table=0, n_packets=0, n_bytes=0, in_port=2 actions=output:4 +``` + +### Configure rapid environment and files + +On Node 1 source the rapidenv and enter the rapid folder: + +```bash +[opnfv@worker ~]$ source rapidenv/bin/activate +(rapidenv) [opnfv@worker ~]$ cd rapid +(rapidenv) [opnfv@worker rapid]$ +``` + +Now we need to modify the `.env` , `.tst` files to run the `runrapid.py` script that do the tests. + +For the `topology-3.env`: + +``` +[rapid] +loglevel = DEBUG +version = 19.6.30 +total_number_of_machines = 1 + +[M1] +name = rapid-pod-1 +admin_ip = 10.244.1.157 +dp_ip1 = 192.168.30.11 +dp_mac1 = de:ad:c3:52:79:9b +dp_ip2 = 192.168.30.12 +dp_mac2 = 02:09:c0:fd:ba:1f + + +[ssh] +key=rapid_rsa_key +user = root + +[Varia] +vim = Openstack +stack = rapid +``` + +Before editing the `.tst` file, the new `prox.cfg` file should be copied under a folder in the same Node, in this case under `~/configs` and renamed as `prox-bi.cfg`. + +The `topology-3.tst`: + +``` +[TestParameters] +name = Rapid_ETSINFV_TST009 +number_of_tests = 1 +total_number_of_test_machines = 1 +lat_percentile = 99 + +[TestM1] +name = Generator +prox_launch_exit = false +config_file = configs/prox-bi.cfg +dest_vm = 1 +mcore = [14] +gencores = [15,16] +latcores = [17,18] + +[test1] +test=TST009test +warmupflowsize=128 +warmupimix=[64] +warmupspeed=1 +warmuptime=2 +imixs=[[64],[128],[256],[512],[1024],[1280],[1512]] +flows=[1] +drop_rate_threshold = 0 +MAXr = 3 +MAXz = 5000 +MAXFramesPerSecondAllIngress = 12000000 +StepSize = 10000 +``` + +### Running the tests + +Now that it's all configured, the test can be started form the rapid folder with the `runrapid.py` script, passing the two files as arguments: + +``` bash +(rapidenv) [opnfv@worker rapid]$ python runrapid.py --env topology-3.env --test topology-3.tst --map machine.map --runtime 10 --screenlog DEBUG +``` + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-OvS-DPDK.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-OvS-DPDK.yaml new file mode 100644 index 00000000..4d1532dd --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-OvS-DPDK.yaml @@ -0,0 +1,68 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Pod +metadata: + name: userspace-rapid-pod-t1-p1 + annotations: + k8s.v1.cni.cncf.io/networks: userspace-ovs-net +spec: + containers: + - name: ovs-vhost + image: opnfv/rapid:latest + imagePullPolicy: Never + securityContext: + privileged: true + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + - mountPath: /usrspcni/ + name: shared-dir + - mountPath: /opt/prox/ + name: prox-dir + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + limits: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + nodeSelector: + vswitch: ovs + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + - name: prox-dir + hostPath: + path: /opt/prox/ + - name: hugepage + emptyDir: + medium: HugePages diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-VPP.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-VPP.yaml new file mode 100644 index 00000000..e9d26667 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-1-interface-VPP.yaml @@ -0,0 +1,68 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Pod +metadata: + name: userspace-rapid-pod-t1-p1 + annotations: + k8s.v1.cni.cncf.io/networks: userspace-vpp-net +spec: + containers: + - name: vpp-vhost + image: opnfv/rapid:latest + imagePullPolicy: Never + securityContext: + privileged: true + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + - mountPath: /usrspcni/ + name: shared-dir + - mountPath: /opt/prox/ + name: prox-dir + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + limits: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + nodeSelector: + vswitch: ovs + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + - name: prox-dir + hostPath: + path: /opt/prox/ + - name: hugepage + emptyDir: + medium: HugePages diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml new file mode 100644 index 00000000..fcae5981 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml @@ -0,0 +1,68 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Pod +metadata: + name: userspace-rapid-pod-t1-p1 + annotations: + k8s.v1.cni.cncf.io/networks: userspace-ovs-net, userspace-ovs-net +spec: + containers: + - name: ovs-vhost + image: opnfv/rapid:latest + imagePullPolicy: Never + securityContext: + privileged: true + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + - mountPath: /usrspcni/ + name: shared-dir + - mountPath: /opt/prox/ + name: prox-dir + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + limits: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + nodeSelector: + vswitch: ovs + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + - name: prox-dir + hostPath: + path: /opt/prox/ + - name: hugepage + emptyDir: + medium: HugePages diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml new file mode 100644 index 00000000..a67106ab --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml @@ -0,0 +1,68 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Pod +metadata: + name: userspace-rapid-pod-t1-p1 + annotations: + k8s.v1.cni.cncf.io/networks: userspace-vpp-net, userspace-vpp-net +spec: + containers: + - name: vpp-vhost + image: opnfv/rapid:latest + imagePullPolicy: Never + securityContext: + privileged: true + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + - mountPath: /usrspcni/ + name: shared-dir + - mountPath: /opt/prox/ + name: prox-dir + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + limits: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + nodeSelector: + vswitch: ovs + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + - name: prox-dir + hostPath: + path: /opt/prox/ + - name: hugepage + emptyDir: + medium: HugePages diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/TRex-pod/userspace-trex-pod-2-interfaces-VPP.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/TRex-pod/userspace-trex-pod-2-interfaces-VPP.yaml new file mode 100644 index 00000000..418d3ede --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/TRex-pod/userspace-trex-pod-2-interfaces-VPP.yaml @@ -0,0 +1,64 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: v1 +kind: Pod +metadata: + name: userspace-trex-pod-t1-p1 + annotations: + k8s.v1.cni.cncf.io/networks: userspace-vpp-net, userspace-vpp-net +spec: + containers: + - name: vpp-vhost + image: trex:latest + imagePullPolicy: Never + securityContext: + privileged: true + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + - mountPath: /usrspcni/ + name: shared-dir + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + limits: + cpu: "6000m" + memory: "4000Mi" + hugepages-1Gi: 4Gi + nodeSelector: + vswitch: ovs + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + - name: hugepage + emptyDir: + medium: HugePages + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-ovs-netAttach.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-ovs-netAttach.yaml new file mode 100644 index 00000000..a2ee5ea8 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-ovs-netAttach.yaml @@ -0,0 +1,47 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: userspace-ovs-net +spec: + config: '{ + "cniVersion": "0.3.1", + "type": "userspace", + "name": "userspace-ovs-net-1", + "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", + "logFile": "/var/log/userspace-ovs-net.log", + "logLevel": "debug", + "host": { + "engine": "ovs-dpdk", + "iftype": "vhostuser", + "netType": "bridge", + "vhost": { + "mode": "server" + }, + "bridge": { + "bridgeName": "vsperf-br0" + } + }, + "container": { + "engine": "ovs-dpdk", + "iftype": "vhostuser", + "netType": "interface", + "vhost": { + "mode": "client" + } + } + }' diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-vpp-netAttach-memif.yaml b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-vpp-netAttach-memif.yaml new file mode 100644 index 00000000..3b199686 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Kubernetes/userspace-vpp-netAttach-memif.yaml @@ -0,0 +1,46 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: userspace-vpp-net +spec: + config: '{ + "cniVersion": "0.3.1", + "type": "userspace", + "name": "userspace-vpp-net", + "kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig", + "logFile": "/var/log/userspace-vpp-net-1-cni.log", + "logLevel": "debug", + "host": { + "engine": "vpp", + "iftype": "memif", + "netType": "interface", + "memif": { + "role": "master", + "mode": "ethernet" + } + }, + "container": { + "engine": "vpp", + "iftype": "memif", + "netType": "interface", + "memif": { + "role": "slave", + "mode": "ethernet" + } + } + }' diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/README.md b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/README.md new file mode 100644 index 00000000..f6c1c8b5 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/README.md @@ -0,0 +1,9 @@ +# PROX Configuration files + +In this folder there are all the files to configure a pod, with one or two interfaces running PROX, as a generator or a swap. + +The `eal` parameter in all `parameters.lua` files are configured for VPP and memif interfaces. To use OvS-DPDK and virtio-user interfaces replace that line with something like this: + +- One interface: `eal="--socket-mem=256,0 --vdev=virtio_user0,path=/usrspcni/563e4f024755-net1"` +- Two interface: `eal="--socket-mem=256,0 --vdev=virtio_user0,path=/usrspcni/563e4f024755-net1 --vdev=virtio_user1,path=/usrspcni/563e4f024755-net2"` + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/parameters.lua new file mode 100644 index 00000000..5e1481df --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/parameters.lua @@ -0,0 +1,11 @@ +require "helper" +name="Generator" +local_ip1="192.168.30.11/24" +local_hex_ip1=convertIPToHex(local_ip1) +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-8449692251a6-net1.sock,role=slave" +mcore="14" +local_hex_mac1="de ad c3 52 79 9b" +gencores1="15" +latcores1="16" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/prox.cfg new file mode 100644 index 00000000..91838e5a --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_1-interface/unidirectional/prox.cfg @@ -0,0 +1,61 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[variables] +$mbs=8 + +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $gencores1] +name=p0 +task=0 +mode=gen +tx port=p0 +bps=1250000000 +pkt inline=${local_hex_mac2} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $latcores1] +name=lat1 +task=0 +mode=lat +;sub mode=l3 +rx port=p0 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/parameters.lua new file mode 100644 index 00000000..adebdd1a --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/parameters.lua @@ -0,0 +1,17 @@ +require "helper" +name="Generator" +local_ip1="192.168.30.11/24" +local_hex_ip1=convertIPToHex(local_ip1) +local_ip2="192.168.30.12/24" +local_hex_ip2=convertIPToHex(local_ip2) +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-8449692251a6-net1.sock,role=slave --vdev=net_memif1,socket=/usrspcni/memif-8449692251a6-net2.sock,role=slave" +mcore="14" +local_hex_mac1="de ad c3 52 79 9b" +local_hex_mac2="02 09 c0 fd ba 1f" +gencores1="15" +gencores2="16" +latcores1="17" +latcores2="18" +latcores1="16" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/prox.cfg new file mode 100644 index 00000000..cac15bc9 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/bidirectional/prox.cfg @@ -0,0 +1,102 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[port 1] +name=p1 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=rec_tap +lsc=no + +[variables] +$mbs=8 + +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $gencores1] +name=p0 +task=0 +mode=gen +tx port=p0 +bps=1250000000 +pkt inline=${local_hex_mac2} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $gencores2] +name=p1 +task=0 +mode=gen +tx port=p1 +bps=1250000000 +pkt inline=${local_hex_mac1} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip2} ${local_hex_ip1} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $latcores1] +name=lat1 +task=0 +mode=lat +;sub mode=l3 +rx port=p1 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 + +[core $latcores2] +name=lat2 +task=0 +mode=lat +;sub mode=l3 +rx port=p0 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/parameters.lua new file mode 100644 index 00000000..784b9ccc --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/parameters.lua @@ -0,0 +1,11 @@ +require "helper" +name="Generator" +local_ip1="192.168.30.11/24" +local_hex_ip1=convertIPToHex(local_ip1) +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-8449692251a6-net1.sock,role=slave --vdev=net_memif1,socket=/usrspcni/memif-8449692251a6-net2.sock,role=slave" +mcore="14" +local_hex_mac1="de ad c3 52 79 9b" +gencores1="15" +latcores1="16" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/prox.cfg new file mode 100644 index 00000000..4520a26c --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/generator-pod_2-interfaces/unidirectional/prox.cfg @@ -0,0 +1,69 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[port 1] +name=p1 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=rec_tap +lsc=no + +[variables] +$mbs=8 + +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $gencores1] +name=p0 +task=0 +mode=gen +tx port=p0 +bps=1250000000 +pkt inline=${local_hex_mac2} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b +pkt size=60 +min bulk size=$mbs +max bulk size=16 +drop=yes +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +;arp update time=1 + +[core $latcores1] +name=lat1 +task=0 +mode=lat +;sub mode=l3 +rx port=p1 +lat pos=42 +accuracy pos=46 +packet id pos=50 +signature=0x98765432 +signature pos=56 +accuracy limit nsec=1000000 +latency bucket size=${bucket_size_exp} +;arp update time=1 diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/parameters.lua new file mode 100644 index 00000000..e9f707da --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/parameters.lua @@ -0,0 +1,7 @@ +require "helper" +name="Swap" +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-b42cff2335c2-net1.sock" +mcore="14" +swapone="17" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/prox.cfg new file mode 100644 index 00000000..98d93bb9 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_1-interface/unidirectional/prox.cfg @@ -0,0 +1,35 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[variables] +$mbs=8 +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $swapone] +name=swap1 +task=0 +mode=swap +rx port=p0 +tx port=p0 +drop=no diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/parameters.lua new file mode 100644 index 00000000..0cf4c70e --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/parameters.lua @@ -0,0 +1,8 @@ +require "helper" +name="Swap" +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-b42cff2335c2-net1.sock --vdev=net_memif1,socket=/usrspcni/memif-b42cff2335c2-net2.sock" +mcore="14" +swapone="20" +swaptwo="21" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/prox.cfg new file mode 100644 index 00000000..db6a50ff --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/bidirectional/prox.cfg @@ -0,0 +1,51 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[port 1] +name=p1 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=rec_tap +lsc=no + +[variables] +$mbs=8 +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $swapone] +name=swap1 +task=0 +mode=swap +rx port=p0 +tx port=p1 +drop=no + +[core $swaptwo] +name=swap2 +task=0 +mode=swap +rx port=p1 +tx port=p0 +drop=no diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/parameters.lua b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/parameters.lua new file mode 100644 index 00000000..cd247ec4 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/parameters.lua @@ -0,0 +1,7 @@ +require "helper" +name="Swap" +eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-b42cff2335c2-net1.sock --vdev=net_memif1,socket=/usrspcni/memif-b42cff2335c2-net2.sock" +mcore="14" +swapone="17" +bucket_size_exp="11" +heartbeat="60" diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/prox.cfg b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/prox.cfg new file mode 100644 index 00000000..97662f74 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/PROX/swap-pod_2-interfaces/unidirectional/prox.cfg @@ -0,0 +1,43 @@ +[lua] +dofile("parameters.lua") + +[eal options] +-n=4 ; force number of memory channels +no-output=no ; disable DPDK debug output +eal=--proc-type auto ${eal} + +[port 0] +name=p0 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=gen_tap +lsc=no + +[port 1] +name=p1 +rx desc=2048 +tx desc=2048 +vlan=yes +;vdev=rec_tap +lsc=no + +[variables] +$mbs=8 +[defaults] +mempool size=8K + +[global] +name=${name} +heartbeat timeout=${heartbeat} + +[core $mcore] +mode=master + +[core $swapone] +name=swap1 +task=0 +mode=swap +rx port=p0 +tx port=p1 +drop=no diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.env b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.env new file mode 100644 index 00000000..8c59560f --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.env @@ -0,0 +1,36 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[rapid] +loglevel = DEBUG +version = 19.6.30 +total_number_of_machines = 1 + +[M1] +name = rapid-pod-1 +admin_ip = 10.244.1.157 +dp_ip1 = 192.168.30.11 +dp_mac1 = de:ad:c3:52:79:9b +dp_ip2 = 192.168.30.12 +dp_mac2 = 02:09:c0:fd:ba:1f + + +[ssh] +key=rapid_rsa_key +user = root + +[Varia] +vim = Openstack +stack = rapid diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.tst b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.tst new file mode 100644 index 00000000..cf42ef52 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example-bidirectional.tst @@ -0,0 +1,43 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[TestParameters] +name = Rapid_ETSINFV_TST009 +number_of_tests = 1 +total_number_of_test_machines = 1 +lat_percentile = 99 + +[TestM1] +name = Generator +prox_launch_exit = false +config_file = configs/prox-bi.cfg +dest_vm = 1 +mcore = [14] +gencores = [15,16] +latcores = [17,18] + +[test1] +test=TST009test +warmupflowsize=128 +warmupimix=[64] +warmupspeed=1 +warmuptime=2 +imixs=[[64],[128],[256],[512],[1024],[1280],[1512]] +flows=[1] +drop_rate_threshold = 0 +MAXr = 3 +MAXz = 5000 +MAXFramesPerSecondAllIngress = 12000000 +StepSize = 10000 diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.env b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.env new file mode 100644 index 00000000..be06dc82 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.env @@ -0,0 +1,33 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[rapid] +loglevel = DEBUG +version = 19.6.30 +total_number_of_machines = 1 + +[M1] +name = rapid-pod-1 +admin_ip = 10.244.1.157 +dp_ip = 192.168.1.4 +dp_mac = de:ad:c3:52:79:9b + +[ssh] +key=rapid_rsa_key +user = root + +[Varia] +vim = Openstack +stack = rapid diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.tst b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.tst new file mode 100644 index 00000000..57575bf6 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/Rapid/example.tst @@ -0,0 +1,43 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[TestParameters] +name = Rapid_ETSINFV_TST009 +number_of_tests = 1 +total_number_of_test_machines = 1 +lat_percentile = 99 + +[TestM1] +name = Generator +prox_launch_exit = false +config_file = configs/prox.cfg +dest_vm = 1 +mcore = [14] +gencores = [15] +latcores = [16] + +[test1] +test=TST009test +warmupflowsize=128 +warmupimix=[64] +warmupspeed=1 +warmuptime=2 +imixs=[[64],[128],[256],[512],[1024],[1280],[1512]] +flows=[1] +drop_rate_threshold = 0 +MAXr = 3 +MAXz = 5000 +MAXFramesPerSecondAllIngress = 12000000 +StepSize = 10000 diff --git a/docs/testing/user/userguide/east-west-benchmarking/Configurations/TRex/vsperf-trex.conf b/docs/testing/user/userguide/east-west-benchmarking/Configurations/TRex/vsperf-trex.conf new file mode 100644 index 00000000..f69d5b89 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Configurations/TRex/vsperf-trex.conf @@ -0,0 +1,36 @@ +# Copyright 2022 Anuket, Intel Corporation, and The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +VSWITCH_BRIDGE_NAME = 'vsperf-br0' +WHITELIST_NICS = ['06:00.0', '06:00.1'] +TRAFFICGEN = 'Trex' +TRAFFICGEN_TREX_HOST_IP_ADDR = '10.244.1.187' +TRAFFICGEN_TREX_USER = 'root' +TRAFFICGEN_TREX_BASE_DIR = '/root/v2.86/' +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' +TRAFFICGEN_TREX_PORT1 = '0000:02:00.0' +TRAFFICGEN_TREX_PORT2 = '0000:02:00.1' +TRAFFICGEN_TREX_PROMISCUOUS = False +TRAFFICGEN_DURATION=10 +TRAFFICGEN_LOSSRATE=0 +TRAFFICGEN_RFC2544_TESTS=10 +#TRAFFICGEN_PKT_SIZES=(64,128,256,512,1024,1280,1518) +GUEST_TESTPMD_FWD_MODE = ['io'] +GUEST_IMAGE = ['/home/opnfv/old/vnfs/vloop-vnf-ubuntu-18.04_20180920.qcow2'] +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +TRAFFICGEN_TREX_FORCE_PORT_SPEED = True +TRAFFICGEN_TREX_PORT_SPEED = 10000 # 10 Gbps +TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION = False +TRAFFICGEN_TREX_RFC2544_MAX_REPEAT = 3 \ No newline at end of file diff --git a/docs/testing/user/userguide/east-west-benchmarking/Limitations.md b/docs/testing/user/userguide/east-west-benchmarking/Limitations.md new file mode 100644 index 00000000..758654cd --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Limitations.md @@ -0,0 +1,10 @@ +# Limitations + +There are some limitations due to the different tools used: +- TRex + - Does not support virtio driver for vhost-user interfaces needed by OvS-DPDK. + - Does not support single interface configuration (dummy interfaces) with memif. +- PROX + - In this envirorment it does not support multiple core assignement for a single task. +- Memif + - Configuration can be changed only by modifying the source code of Userspace CNI. \ No newline at end of file diff --git a/docs/testing/user/userguide/east-west-benchmarking/Requirements.md b/docs/testing/user/userguide/east-west-benchmarking/Requirements.md new file mode 100644 index 00000000..7450ec32 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Requirements.md @@ -0,0 +1,116 @@ +# Requirements for the tests + +Some requirements are needed to run the test in the five topologies and these involve CPU, Memory, shared folders, etc... + +### CPU + +The CPUs assigned to a Pod can't be assigned to other Pods: the allocation is exclusive. + +### Shared Folders + +For TRex and PROX pods it's necessary to include some volumes in their pod definition file (examples of these files are under the [Kubernetes configuration](./Configurations/Kubernetes)) that provide: + +- Annotations and metadata: Pod's details (e.g. memif/vhost socket file name) + + ```yaml + ... + spec: + containers: + ... + volumeMounts: + - mountPath: /etc/podnetinfo + name: podinfo + readOnly: false + ... + volumes: + - name: podinfo + downwardAPI: + items: + - path: "labels" + fieldRef: + fieldPath: metadata.labels + - path: "annotations" + fieldRef: + fieldPath: metadata.annotations + ... + ``` + +- Socket files for memif and vhost interfaces: (directory with some socket files for memif/vhost interfaces). It's shared between pods. + + ```yaml + ... + spec: + containers: + ... + volumeMounts: + ... + - mountPath: /usrspcni/ + name: shared-dir + ... + volumes: + ... + - name: shared-dir + hostPath: + path: /var/lib/cni/usrspcni/data/ + ... + ``` + + + +- Hugepages: + + ```yaml + ... + spec: + containers: + ... + volumeMounts: + ... + - mountPath: /dev/hugepages + name: hugepage + ... + volumes: + ... + - name: hugepage + emptyDir: + medium: HugePages + ... + ``` + + + +And only for PROX: + +- directory with the PROX configuration files: Directory that contains `parameters.lua` and `prox.cfg` files. It's shared between pods. + + ```yaml + ... + spec: + containers: + ... + volumeMounts: + ... + - mountPath: /opt/prox/ + name: prox-dir + ... + volumes: + ... + - name: prox-dir + hostPath: + path: /opt/prox/ + ... + ``` + + There are two variants of this directory: one for generator pod and the other for swap pods. The configuration files are more coherent with the role of the pod. + + - Genreator pod: `/opt/proxgen` + - Swap pods: `/opt/proxswap` + +### CNIs + +The first CNI that is needed is Multus. This CNI allow to assign more than a single interface to the Pod. +The choice of the CNIs that can be used for these additional interfaces is limited by the fact that the networking is in user space, so Userspace CNI should be used. + +### vSwitches + +As the Userspace CNI is used, the only virtual swithces that are supported are: OvS-DPDK and VPP. \ No newline at end of file diff --git a/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and OVS.md b/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and OVS.md new file mode 100644 index 00000000..cdf7ca32 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and OVS.md @@ -0,0 +1,372 @@ +# ETSI GS NFV-TST 009 Testing with PROX and OvS-DPDK in a Kubernetes Cluster + +This document shows an example of testing the dataplane in the third topology represented in the [topologies description](Topologies.md) with two pods equipped with two interfaces each that are bounded to OvS-DPDK, using PROX as traffic generator. The `userspace-rapid-pod-t3-p1.yaml` and the `userspace-rapid-pod-t3-p2.yaml` files can be created by modifying the [Rapid pod definition file](./Configuration/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-OvS-DPDK.yaml). + +### Prepare OvS-DPDK and deploy the kubernetes components + +- On Node 1: + - source vsperfenv/bin/activate + 2. cd vineperf. + + 3. ./vsperf --conf-file ~/conf/vsperf-master.conf --mode trafficgen-off phy2phy_tput - Leave this window untouched. +- On Node 2 **(After previous steps)**: + - Deploy network attachment `userspace-ovs-netAttach.yaml` + - Deploy pods applying `userspace-rapid-pod-t3-p1.yaml` and `userspace-rapid-pod-t3-p2.yaml` + +### Configure PROX inside the pods + +- Generator Pod: + + - Enter the generator pod with `kubectl exec -it userspace-rapid-pod-t3-p1 -- /bin/bash` + + - copy the prox folder (it is shared between pods) to make changes on configuration files: `cp -r /opt/prox /opt/proxgen` + + - search for the two memif socketfile names running : + + ``` + [root@userspace-rapid-pod-t3-p1 /]# cat /etc/podnetinfo/annotations | grep socketfile + userspace/configuration-data="[{\n \"containerId\": \"87042916ab4dac68611ea9d2f02bcf26bc82b5d17716ffa9ebff1cf36af1fca3\",\n \"ifName\": \"net1\",\n \"name\": \"userspace-ovs-net-1\",\n \"config\": {\n \"engine\": \"ovs-dpdk\",\n \"iftype\": \"vhostuser\",\n \"netType\": \"interface\",\n \"memif\": {},\n \"vhost\": {\n \"mode\": \"client\",\n \"socketfile\": \"87042916ab4d-net1\"\n },\n \"bridge\": {}\n },\n \"ipResult\": {\n \"interfaces\": [\n {\n \"name\": \"net1\",\n \"sandbox\": \"/proc/2910/ns/net\"\n }\n ],\n \"dns\": {}\n }\n},{\n \"containerId\": \"87042916ab4dac68611ea9d2f02bcf26bc82b5d17716ffa9ebff1cf36af1fca3\",\n \"ifName\": \"net2\",\n \"name\": \"userspace-ovs-net-1\",\n \"config\": {\n \"engine\": \"ovs-dpdk\",\n \"iftype\": \"vhostuser\",\n \"netType\": \"interface\",\n \"memif\": {},\n \"vhost\": {\n \"mode\": \"client\",\n \"socketfile\": \"87042916ab4d-net2\"\n },\n \"bridge\": {}\n },\n \"ipResult\": {\n \"interfaces\": [\n {\n \"name\": \"net2\",\n \"sandbox\": \"/proc/2910/ns/net\"\n }\n ],\n \"dns\": {}\n }\n}]" + ``` + + + + - copy the names `*-net1` and `*-net2` + + - edit the `parameters.lua` changing the socketfile name and set the CPUs number for the main core, generator cores and latency cores: + + ```lua + ... + eal="--socket-mem=256,0 --vdev=virtio_user0,path=/usrspcni/87042916ab4d-net1 --vdev=virtio_user1,path=/usrspcni/87042916ab4d-net2" + mcore="14" + ... + gencores="15" + latcores="16" + ... + + ``` + + - edit `prox.cfg` to configure the tasks properly for generating and receiving packets: + + ``` + [lua] + dofile("parameters.lua") + + [eal options] + -n=4 ; force number of memory channels + no-output=no ; disable DPDK debug output + eal=--proc-type auto ${eal} + + [port 0] + name=p0 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [port 1] + name=p1 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [variables] + $mbs=8 + + [defaults] + mempool size=8K + + [global] + name=${name} + heartbeat timeout=${heartbeat} + + [core $mcore] + mode=master + + [core $gencores] + name=p0 + task=0 + mode=gen + tx port=p0 + bps=1250000000 + pkt inline=${dest_hex_mac1} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b + pkt size=60 + min bulk size=$mbs + max bulk size=16 + drop=yes + lat pos=42 + accuracy pos=46 + packet id pos=50 + signature=0x98765432 + signature pos=56 + + [core $latcores] + name=lat + task=0 + mode=lat + rx port=p1 + lat pos=42 + accuracy pos=46 + packet id pos=50 + signature=0x98765432 + signature pos=56 + accuracy limit nsec=1000000 + latency bucket size=${bucket_size_exp} + ``` + + - Enter rapid folder and start PROX: + + ``` + [root@userspace-rapid-pod-t3-p1 /]# cd /opt/rapid/ + [root@userspace-rapid-pod-t3-p1 rapid]# ./prox -f ../proxgen/prox.cfg -et + ``` + + ``` + Usage: ./prox [-f CONFIG_FILE] [-a|-e] [-m|-s|-i] [-w DEF] [-u] [-t] + -f CONFIG_FILE : configuration file to load, ./prox.cfg by default + -e : don't autostart + -t : Listen on TCP port 8474 + ``` + + ![prox-vpp-screen](./images/prox-vpp-screen.png) + +- Swap Pod: + + - Enter the generator pod with `kubectl exec -it userspace-rapid-pod-t3-p2 -- /bin/bash` + + - copy the prox folder (it is shared between pods) to make changes on configuration files: `cp -r /opt/prox /opt/proxswap` + + - search for the two virtio socketfile names like for the Generator Pod and copy the names `*-net1` and `*-net2` + + - edit the `parameters.lua` changing the socketfile name and set the CPUs number for the main core (same as Generator Pod) and the swap cores: + + ```lua + ... + swapone="17" + ... + + ``` + + - edit `prox.cfg` to configure the tasks properly for swapping: + + ``` + [lua] + dofile("parameters.lua") + + [eal options] + -n=4 ; force number of memory channels + no-output=no ; disable DPDK debug output + eal=--proc-type auto ${eal} + + [port 0] + name=p0 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [port 1] + name=p1 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [variables] + $mbs=8 + + [defaults] + mempool size=8K + + [global] + name=${name} + heartbeat timeout=${heartbeat} + + [core $mcore] + mode=master + + [core $swapone] + name=swap1 + task=0 + mode=swap + rx port=p0 + tx port=p1 + drop=no + ``` + + - Enter rapid folder and start PROX: + + ``` + [root@userspace-rapid-pod-t3-p2 /]# cd /opt/rapid/ + [root@userspace-rapid-pod-t3-p2 rapid]# ./prox -f ../proxgen/prox.cfg + ``` + + ![prox-vpp-screen-swap](./images/prox-vpp-screen-swap.png) + +### Configure OVS-DPDK + +The default configuration is under `vineperf/conf/02_vswitch.conf`. Then it's necessary to configure the connections between the interfaces + +- Go to the utilities folder `cd /home/opnfv/vineperf/src/ovs/ovs/utilities` + +- Run `sudo ovs-vsctl -- --columns=name,ofport list Interface` : Note down the port numbers (`ofport`) mapping to the vhost interfaces. + + ``` + [opnfv@worker utilities]$ sudo ovs-vsctl -- --columns=name,ofport list Interface + name : "dpdk1" + ofport : -1 + + name : "87042916ab4d-net1" + ofport : 1 + + name : "dpdk0" + ofport : -1 + + name : "vsperf-br0" + ofport : 65534 + + name : "2d63b16eb7ac-net2" + ofport : 4 + + name : "87042916ab4d-net2" + ofport : 2 + + name : "2d63b16eb7ac-net1" + ofport : 3 + ``` + + In this case `ofport` for the first pod are 1 and 2 and for the second are 3 and 4. + +- Open `setup_flows.sh` file. Modify the `in_port=<>` and `action=output:<>` values to connect the interfaces: + + ``` bash + sudo ovs-ofctl --timeout 10 -O OpenFlow13 del-flows vsperf-br0 + sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=1,idle_timeout=0,action=output:3 + sudo ovs-ofctl --timeout 10 -O Openflow13 add-flow vsperf-br0 in_port=4,idle_timeout=0,action=output:2 + ``` + + + +- Run `sudo ./setup_flows.sh` + +- Verify that the flows are correct by running `sudo ovs-ofctl dump-flows vsperf-br0`: + + ``` bash + [opnfv@worker utilities]$ sudo ovs-ofctl dump-flows vsperf-br0 + cookie=0x0, duration=10.400s, table=0, n_packets=0, n_bytes=0, in_port=1 actions=output:3 + cookie=0x0, duration=10.348s, table=0, n_packets=0, n_bytes=0, in_port=4 actions=output:2 + ``` + + + +### Configure rapid environment and files + +On Node 1 the environment and the file for running rapid should be set-up. + +Source the rapidenv and enter the rapid folder: + +```bash +[opnfv@worker ~]$ source rapidenv/bin/activate +(rapidenv) [opnfv@worker ~]$ cd rapid +(rapidenv) [opnfv@worker rapid]$ +``` + +Now we need `.env` , `.tst` and `machine.map` files to run the `runrapid.py` script that do the tests. + +Before creating theese files the IP of the generator pod is needed. On Node 1 run this command: + +``` bash +[opnfv@master ~]$ kubectl get pods -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +userspace-rapid-pod-t3-p1 1/1 Running 0 69m 10.244.1.157 worker +userspace-rapid-pod-t3-p2 1/1 Running 0 69m 10.244.1.158 worker +``` + +For the `topology-3.env`: + +``` +[rapid] +loglevel = DEBUG +version = 19.6.30 +total_number_of_machines = 1 + +[M1] +name = rapid-pod-1 +admin_ip = 10.244.1.157 +dp_ip = 192.168.1.4 +dp_mac = de:ad:c3:52:79:9b + + +[ssh] +key=rapid_rsa_key +user = root + +[Varia] +vim = Openstack +stack = rapid +``` + +Before the `.tst` file, the `prox.cfg` file should be copied under a folder in the same Node, in this case under `~/configs`. + +The `topology-3.tst`: + +``` +[TestParameters] +name = Rapid_ETSINFV_TST009 +number_of_tests = 1 +total_number_of_test_machines = 1 +lat_percentile = 99 + +[TestM1] +name = Generator +prox_launch_exit = false +config_file = configs/prox.cfg +dest_vm = 1 +mcore = [14] +gencores = [15] +latcores = [16] + +[test1] +test=TST009test +warmupflowsize=128 +warmupimix=[64] +warmupspeed=1 +warmuptime=2 +imixs=[[64],[128],[256],[512],[1024],[1280],[1512]] +flows=[1] +drop_rate_threshold = 0 +MAXr = 3 +MAXz = 5000 +MAXFramesPerSecondAllIngress = 12000000 +StepSize = 10000 +``` + +For `machine.map`: + +``` +[DEFAULT] +machine_index=0 + +[TestM1] +machine_index=1 + +[TestM2] +machine_index=2 + +[TestM3] +machine_index=3 + +[TestM4] +machine_index=4 +``` + + + +### Running the tests + +Now that it's all configured, the test can be started form the rapid folder with the `runrapid.py` script, passing the two files as arguments: + +``` bash +(rapidenv) [opnfv@worker rapid]$ python runrapid.py --env topology-3.env --test topology-3.tst --map machine.map --runtime 10 --screenlog DEBUG +``` + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and VPP.md b/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and VPP.md new file mode 100644 index 00000000..21426ab2 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Test with PROX and VPP.md @@ -0,0 +1,375 @@ +# ETSI GS NFV-TST 009 Testing with PROX and VPP in a Kubernetes Cluster + +This document shows an example of testing the dataplane in the third topology represented in the [topologies description](Topologies.md) with two pods equipped with two interfaces each that are bounded to VPP, using PROX as traffic generator. The `userspace-rapid-pod-t3-p1.yaml` and the `userspace-rapid-pod-t3-p2.yaml` files can be created by modifying the [Rapid pod definition file](./Configuration/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml). + +### Prepare VPP and deploy the kubernetes components + +- On Node 1: + - Start `vpp.service` with `startup.conf` file under `/etc/vpp` +- On Node 2: + - Deploy network attachment `userspace-vpp-netAttach-memif.yaml` + - Deploy pods applying `userspace-rapid-pod-t3-p1.yaml` and `userspace-rapid-pod-t3-p2.yaml` + +### Configure PROX inside the pods + +- Generator Pod: + + - Enter the generator pod with `kubectl exec -it userspace-rapid-pod-t3-p1 -- /bin/bash` + + - copy the prox folder (it is shared between pods) to make changes on configuration files: `cp -r /opt/prox /opt/proxgen` + + - search for the two memif socketfile names running : + + ``` + [root@userspace-rapid-pod-t3-p1 /]# cat /etc/podnetinfo/annotations | grep socketfile + userspace/configuration-data="[{\n \"containerId\": \"d3810ce7d4dee8f4cbcbfb7f3a53b86fd33481db282446b07dcb1bf4f1d7fa49\",\n \ + + "ifName\": \"net1\",\n \"name\": \"userspace-vpp-net\",\n \"config\": {\n \"engine\": \"vpp\",\n \"iftype\": \" + + memif\",\n \"netType\": \"interface\",\n \"memif\": {\n \"role\": \"slave\",\n \"mode\": \"ethe + + rnet\",\n \"**socketfile**\": \"memif-d3810ce7d4de-net1.sock\"\n },\n \"vhost\": {},\n \"bridge\": {}\n + + ​ },\n \"ipResult\": {\n \"interfaces\": [\n {\n \"name\": \"net1\",\n \"sandb + + ox\": \"/proc/17603/ns/net\"\n }\n ],\n \"dns\": {}\n }\n},{\n \"containerId\": \"d3810ce7d4dee8f4cb + + cbfb7f3a53b86fd33481db282446b07dcb1bf4f1d7fa49\",\n \"ifName\": \"net2\",\n \"name\": \"userspace-vpp-net\",\n \"config\": + + {\n \"engine\": \"vpp\",\n \"iftype\": \"memif\",\n \"netType\": \"interface\",\n \"memif\": {\n + + ​ \"role\": \"slave\",\n \"mode\": \"ethernet\",\n \"**socketfile**\": \"memif-d3810ce7d4de-net2.sock\"\n + + ​ },\n \"vhost\": {},\n \"bridge\": {}\n },\n \"ipResult\": {\n \"interfaces\": [\n {\n + + ​ \"name\": \"net2\",\n \"sandbox\": \"/proc/17603/ns/net\"\n }\n ],\n \"dns\": { + + }\n }\n}]" + ``` + + + + - copy the names `memif-*-net1.sock` and `memif-*-net2.sock` + + - edit the `parameters.lua` changing the socketfile name and set the CPUs number for the main core, generator cores and latency cores: + + ```lua + ... + eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-d3810ce7d4de-net1.sock --vdev=net_memif1,socket=/usrspcni/memif-d3810ce7d4de-net2.sock" + mcore="14" + ... + gencores="15" + latcores="16" + ... + + ``` + + - edit `prox.cfg` to configure the tasks properly for generating and receiving packets: + + ``` + [lua] + dofile("parameters.lua") + + [eal options] + -n=4 ; force number of memory channels + no-output=no ; disable DPDK debug output + eal=--proc-type auto ${eal} + + [port 0] + name=p0 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [port 1] + name=p1 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [variables] + $mbs=8 + + [defaults] + mempool size=8K + + [global] + name=${name} + heartbeat timeout=${heartbeat} + + [core $mcore] + mode=master + + [core $gencores] + name=p0 + task=0 + mode=gen + tx port=p0 + bps=1250000000 + pkt inline=${dest_hex_mac1} 00 00 00 00 00 00 08 00 45 00 00 2e 00 01 00 00 40 11 f7 7d ${local_hex_ip1} ${local_hex_ip2} 0b b8 0b b9 00 1a 55 7b + pkt size=60 + min bulk size=$mbs + max bulk size=16 + drop=yes + lat pos=42 + accuracy pos=46 + packet id pos=50 + signature=0x98765432 + signature pos=56 + + [core $latcores] + name=lat + task=0 + mode=lat + rx port=p1 + lat pos=42 + accuracy pos=46 + packet id pos=50 + signature=0x98765432 + signature pos=56 + accuracy limit nsec=1000000 + latency bucket size=${bucket_size_exp} + ``` + + - Enter rapid folder and start PROX: + + ``` + [root@userspace-rapid-pod-t3-p1 /]# cd /opt/rapid/ + [root@userspace-rapid-pod-t3-p1 rapid]# ./prox -f ../proxgen/prox.cfg -et + ``` + + ``` + Usage: ./prox [-f CONFIG_FILE] [-a|-e] [-m|-s|-i] [-w DEF] [-u] [-t] + -f CONFIG_FILE : configuration file to load, ./prox.cfg by default + -e : don't autostart + -t : Listen on TCP port 8474 + ``` + + ![prox-vpp-screen](./images/prox-vpp-screen.png) + +- Swap Pod: + + - Enter the generator pod with `kubectl exec -it userspace-rapid-pod-t3-p2 -- /bin/bash` + - copy the prox folder (it is shared between pods) to make changes on configuration files: `cp -r /opt/prox /opt/proxswap` + - search for the two memif socketfile names like for the Generator Pod and copy the names `memif-*-net1.sock` and `memif-*-net2.sock` + + - edit the `parameters.lua` changing the socketfile name and set the CPUs number for the main core (same as Generator Pod) and the swap cores: + + ```lua + ... + swapone="17" + ... + + ``` + + - edit `prox.cfg` to configure the tasks properly for swapping: + + ``` + [lua] + dofile("parameters.lua") + + [eal options] + -n=4 ; force number of memory channels + no-output=no ; disable DPDK debug output + eal=--proc-type auto ${eal} + + [port 0] + name=p0 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [port 1] + name=p1 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [variables] + $mbs=8 + + [defaults] + mempool size=8K + + [global] + name=${name} + heartbeat timeout=${heartbeat} + + [core $mcore] + mode=master + + [core $swapone] + name=swap1 + task=0 + mode=swap + rx port=p0 + tx port=p1 + drop=no + ``` + + - Enter rapid folder and start PROX: + + ``` + [root@userspace-rapid-pod-t3-p2 /]# cd /opt/rapid/ + [root@userspace-rapid-pod-t3-p2 rapid]# ./prox -f ../proxgen/prox.cfg + ``` + + ![prox-vpp-screen-swap](./images/prox-vpp-screen-swap.png) + +### Configure VPP + +On Node 1 VPP should be configured to send the traffic between memif interfaces with `l2patch`: + +``` +[opnfv@worker ~]$ sudo vppctl + _______ _ _ _____ ___ + __/ __/ _ \ (_)__ | | / / _ \/ _ \ + _/ _// // / / / _ \ | |/ / ___/ ___/ + /_/ /____(_)_/\___/ |___/_/ /_/ + +vpp# show interface + Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count +local0 0 down 0/0/0/0 +memif1/0 1 up 9000/0/0/0 +memif2/0 2 up 9000/0/0/0 +memif3/0 3 up 9000/0/0/0 +memif4/0 4 up 9000/0/0/0 +vpp# show l2patch +no l2patch entries +vpp# test l2patch rx memif1/0 tx memif3/0 +vpp# test l2patch rx memif4/0 tx memif2/0 +vpp# show l2patch + memif1/0 -> memif3/0 + memif4/0 -> memif2/0 +``` + +To check the socketfiles of the showed memif for the patch configuration run: + +``` +vpp# show memif +sockets + id listener filename + 2 yes (1) /var/lib/cni/usrspcni/data/memif-d3810ce7d4de-net2.sock + 4 yes (1) /var/lib/cni/usrspcni/data/memif-ab82066a8020-net2.sock + 3 yes (1) /var/lib/cni/usrspcni/data/memif-ab82066a8020-net1.sock + 0 no /run/vpp/memif.sock + 1 yes (1) /var/lib/cni/usrspcni/data/memif-d3810ce7d4de-net1.sock + +... +``` + +### Configure rapid environment and files + +On Node 1 the environment and the file for running rapid should be set-up. + +Source the rapidenv and enter the rapid folder: + +```bash +[opnfv@worker ~]$ source rapidenv/bin/activate +(rapidenv) [opnfv@worker ~]$ cd rapid +(rapidenv) [opnfv@worker rapid]$ +``` + +Now we need `.env` , `.tst` and `machine.map` files to run the `runrapid.py` script that do the tests. + +Before creating theese files the IP of the generator pod is needed. On Node 1 run this command: + +```bash +[opnfv@master ~]$ kubectl get pods -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +userspace-rapid-pod-t3-p1 1/1 Running 0 69m 10.244.1.157 worker +userspace-rapid-pod-t3-p2 1/1 Running 0 69m 10.244.1.158 worker +``` + +For the `topology-3.env`: + +``` +[rapid] +loglevel = DEBUG +version = 19.6.30 +total_number_of_machines = 1 + +[M1] +name = rapid-pod-1 +admin_ip = 10.244.1.157 +dp_ip = 192.168.1.4 +dp_mac = de:ad:c3:52:79:9b + + +[ssh] +key=rapid_rsa_key +user = root + +[Varia] +vim = Openstack +stack = rapid +``` + +Before the `.tst` file, the `prox.cfg` file should be copied under a folder in the same Node, in this case under `~/configs`. + +The `topology-3.tst`: + +``` +[TestParameters] +name = Rapid_ETSINFV_TST009 +number_of_tests = 1 +total_number_of_test_machines = 1 +lat_percentile = 99 + +[TestM1] +name = Generator +prox_launch_exit = false +config_file = configs/prox.cfg +dest_vm = 1 +mcore = [14] +gencores = [15] +latcores = [16] + +[test1] +test=TST009test +warmupflowsize=128 +warmupimix=[64] +warmupspeed=1 +warmuptime=2 +imixs=[[64],[128],[256],[512],[1024],[1280],[1512]] +flows=[1] +drop_rate_threshold = 0 +MAXr = 3 +MAXz = 5000 +MAXFramesPerSecondAllIngress = 12000000 +StepSize = 10000 +``` + +For `machine.map`: + +``` +[DEFAULT] +machine_index=0 + +[TestM1] +machine_index=1 + +[TestM2] +machine_index=2 + +[TestM3] +machine_index=3 + +[TestM4] +machine_index=4 +``` + + + +### Running the tests + +Now that it's all configured, the test can be started form the rapid folder with the `runrapid.py` script, passing the two files as arguments: + +``` bash +(rapidenv) [opnfv@worker rapid]$ python runrapid.py --env topology-3.env --test topology-3.tst --map machine.map --runtime 10 --screenlog DEBUG +``` + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Test with TRex and VPP.md b/docs/testing/user/userguide/east-west-benchmarking/Test with TRex and VPP.md new file mode 100644 index 00000000..b1878b39 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Test with TRex and VPP.md @@ -0,0 +1,228 @@ +# ETSI GS NFV-TST 009 Testing with TRex and VPP in a Kubernetes Cluster + +This document shows an example of testing the dataplane in the third topology represented in the [topologies description](Topologies.md) with two pods equipped with two interfaces each that are bounded to VPP, using TRex as traffic generator. The `userspace-trex-pod-t3-p1.yaml` file can be created by modifying the [TRex pod definition file](./Configuration/Kubernetes/TRex-pod/userspace-trex-pod-2-interfaces-VPP.yaml) and for `userspace-rapid-pod-t3-p2.yaml` file use the [Rapid pod definition file](./Configuration/Kubernetes/Rapid-pod/userspace-rapid-pod-2-interfaces-VPP.yaml). + +### Prepare VPP and deploy the Kubernetes components + +- On Node 1: + - Start `vpp.service` with `startup.conf` file under `/etc/vpp` +- On Node 2: + - Deploy network attachment `userspace-vpp-netAttach-memif.yaml` + - Deploy pods applying `userspace-trex-pod-t3-p1.yaml` and `userspace-rapid-pod-t3-p2.yaml` + +### Configure TRex inside the pods + +- Generator Pod: + + - Enter the generator pod with `kubectl exec -it userspace-trex-pod-t3-p1 -- /bin/bash` + + - Run the script `vppconf.py` that generates the TRex configuration file: + + ``` + [root@userspace-trex-pod-t3-p1 ~]# python3 vppconf.py + Type=Memif Role=slave Mode=ethernet Path=/usrspcni/memif-e80434cd91fc-net1.sock + Type=Memif Role=slave Mode=ethernet Path=/usrspcni/memif-e80434cd91fc-net2.sock + ``` + + - Edit the configuration file `/etc/trex_cfg.yaml` to set the slave mode for the memif interface : + + ``` + ... + interfaces : ["--vdev=net_memif0,socket=/usrspcni/memif-e80434cd91fc-net1.sock,role=slave", "--vdev=net_memif1,socket=/usrspcni/memif-e80434cd91fc-net2.sock,role=slave"] # list of the interfaces + ... + ``` + + - Go in the TRex folder and start it: + + ``` + [root@userspace-trex-pod-t3-p1 ~]# cd v2.86/ + [root@userspace-trex-pod-t3-p1 v2.86]# ./t-rex-64 -i --no-scapy-server --nc --no-watchdog + ``` + + The TRex screen comes up: + + ![trex-screen](./images/trex-screen.png) + + + +- Swap Pod: + + - Enter the generator pod with `kubectl exec -it userspace-rapid-pod-t3-p2 -- /bin/bash` + + - copy the prox folder (it is shared between pods) to make changes on configuration files: `cp -r /opt/prox /opt/proxswap` + + - search for the two memif socketfile names running : + + ```bash + [root@userspace-rapid-pod-t3-p2 /]# cat /etc/podnetinfo/annotations | grep socketfile + userspace/configuration-data="[{\n \"containerId\": \"fbbc9e8e1c5bfb280eeb061c7c1fc6fade8cb2e0e8cda7dd0c3974ce61442246\",\n \"ifName\": \"net1\",\n \"name\": \"userspace-vpp-net\",\n \"config\": {\n \"engine\": \"vpp\",\n \"iftype\": \"memif\",\n \"netType\": \"interface\",\n \"memif\": {\n \"role\": \"slave\",\n \"mode\": \"ethernet\",\n \"socketfile\": \"memif-fbbc9e8e1c5b-net1.sock\"\n },\n \"vhost\": {},\n \"bridge\": {}\n },\n \"ipResult\": {\n \"interfaces\": [\n {\n \"name\": \"net1\",\n \"sandbox\": \"/proc/24281/ns/net\"\n }\n ],\n \"dns\": {}\n }\n},{\n \"containerId\": \"fbbc9e8e1c5bfb280eeb061c7c1fc6fade8cb2e0e8cda7dd0c3974ce61442246\",\n \"ifName\": \"net2\",\n \"name\": \"userspace-vpp-net\",\n \"config\": {\n \"engine\": \"vpp\",\n \"iftype\": \"memif\",\n \"netType\": \"interface\",\n \"memif\": {\n \"role\": \"slave\",\n \"mode\": \"ethernet\",\n \"socketfile\": \"memif-fbbc9e8e1c5b-net2.sock\"\n },\n \"vhost\": {},\n \"bridge\": {}\n },\n \"ipResult\": {\n \"interfaces\": [\n {\n \"name\": \"net2\",\n \"sandbox\": \"/proc/24281/ns/net\"\n }\n ],\n \"dns\": {}\n }\n}]" + ``` + + + + - copy the names `memif-*-net1.sock` and `memif-*-net2.sock` + + - edit the `parameters.lua` changing the socketfile name and set the CPUs number for the main core, generator cores and latency cores: + + ```lua + ... + eal="--socket-mem=256,0 --vdev=net_memif0,socket=/usrspcni/memif-fbbc9e8e1c5b-net1.sock --vdev=net_memif1,socket=/usrspcni/memif-fbbc9e8e1c5b-net2.sock" + mcore="14" + ... + swapone="17" + ... + ``` + + - edit `prox.cfg` to configure the tasks properly for swapping: + + ``` + [lua] + dofile("parameters.lua") + + [eal options] + -n=4 ; force number of memory channels + no-output=no ; disable DPDK debug output + eal=--proc-type auto ${eal} + + [port 0] + name=p0 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [port 1] + name=p1 + rx desc=2048 + tx desc=2048 + vlan=yes + lsc=no + + [variables] + $mbs=8 + + [defaults] + mempool size=8K + + [global] + name=${name} + heartbeat timeout=${heartbeat} + + [core $mcore] + mode=master + + [core $swapone] + name=swap1 + task=0 + mode=swap + rx port=p0 + tx port=p1 + drop=no + ``` + + - Enter rapid folder and start PROX: + + ```bash + [root@userspace-rapid-pod-t3-p2 /]# cd /opt/rapid/ + [root@userspace-rapid-pod-t3-p2 rapid]# ./prox -f ../proxgen/prox.cfg + ``` + + ![prox-vpp-screen-swap](./images/prox-vpp-screen-swap.png) + +### Configure VPP + +On Node 1 VPP should be configured to send the traffic between memif interfaces with `l2patch`: + +``` +[opnfv@worker ~]$ sudo vppctl + _______ _ _ _____ ___ + __/ __/ _ \ (_)__ | | / / _ \/ _ \ + _/ _// // / / / _ \ | |/ / ___/ ___/ + /_/ /____(_)_/\___/ |___/_/ /_/ + +vpp# show interface + Name Idx State MTU (L3/IP4/IP6/MPLS) Counter Count +local0 0 down 0/0/0/0 +memif1/0 1 up 9000/0/0/0 +memif2/0 2 up 9000/0/0/0 +memif3/0 3 up 9000/0/0/0 +memif4/0 4 up 9000/0/0/0 +vpp# show l2patch +no l2patch entries +vpp# test l2patch rx memif1/0 tx memif3/0 +vpp# test l2patch rx memif4/0 tx memif2/0 +vpp# show l2patch + memif1/0 -> memif3/0 + memif4/0 -> memif2/0 +``` + +To check the socketfiles of the memif shown for the patch configuration run: + +``` +vpp# show memif +sockets + id listener filename + 2 yes (1) /var/lib/cni/usrspcni/data/memif-e80434cd91fc-net2.sock + 4 yes (1) /var/lib/cni/usrspcni/data/memif-fbbc9e8e1c5b-net2.sock + 3 yes (1) /var/lib/cni/usrspcni/data/memif-fbbc9e8e1c5b-net1.sock + 0 no /run/vpp/memif.sock + 1 yes (1) /var/lib/cni/usrspcni/data/memif-e80434cd91fc-net1.sock + +... +``` + +### Configure vsperf environment and files + +On Node 1 the environment and the file for running rapid should be set-up. + +Source the rapidenv and enter the rapid folder: + +```bash +[opnfv@worker ~]$ source vsperfenv/bin/activate +(rapidenv) [opnfv@worker ~]$ cd rapid +(rapidenv) [opnfv@worker rapid]$ +``` + +Now edit the vsperf configuration file to use TRex with the test variables set-up and write the generator pod's IP for the connection. Find the IP with `kubectl get pods -o wide`: + +``` +[opnfv@master ~]$ kubectl get pods -o wide +NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES +userspace-rapid-pod-t3-p2 1/1 Running 0 22m 10.244.1.203 worker +userspace-trex-pod-t3-p1 1/1 Running 0 22m 10.244.1.202 worker +``` + +Copy the IP of the `userspace-trex-pod-t3-p1` pod and then edit the configuration file `~/conf/vsperf-trex.conf`: + +``` +VSWITCH_BRIDGE_NAME = 'vsperf-br0' +WHITELIST_NICS = ['06:00.0', '06:00.1'] +TRAFFICGEN = 'Trex' +TRAFFICGEN_TREX_HOST_IP_ADDR = '10.244.1.202' +TRAFFICGEN_TREX_USER = 'root' +TRAFFICGEN_TREX_BASE_DIR = '/root/v2.86/' +TRAFFICGEN_TREX_LINE_SPEED_GBPS = '10' +TRAFFICGEN_TREX_PORT1 = '0000:02:00.0' +TRAFFICGEN_TREX_PORT2 = '0000:02:00.1' +TRAFFICGEN_TREX_PROMISCUOUS = False +TRAFFICGEN_DURATION=10 +TRAFFICGEN_LOSSRATE=0 +TRAFFICGEN_RFC2544_TESTS=10 +TRAFFICGEN_PKT_SIZES=(64,128,256,512,1024,1280,1518) +GUEST_TESTPMD_FWD_MODE = ['io'] +GUEST_IMAGE = ['/home/opnfv/old/vnfs/vloop-vnf-ubuntu-18.04_20180920.qcow2'] +TRAFFICGEN_TREX_LATENCY_PPS = 1000 +TRAFFICGEN_TREX_FORCE_PORT_SPEED = True +TRAFFICGEN_TREX_PORT_SPEED = 10000 # 10 Gbps +TRAFFICGEN_TREX_RFC2544_BINARY_SEARCH_LOSS_VERIFICATION = True +TRAFFICGEN_TREX_RFC2544_MAX_REPEAT = 3 +``` + +### Running the tests + +Now that it's all configured, the test can be started form the vineperf folder with the `vsperf` script, passing the configuration file as argument: + +``` bash +(vsperfenv) [opnfv@worker vineperf]$ ./vsperf --conf-file ~/conf/vsperf-trex.conf --mode trafficgen phy2phy_tput +``` + diff --git a/docs/testing/user/userguide/east-west-benchmarking/Topologies-EastWest.md b/docs/testing/user/userguide/east-west-benchmarking/Topologies-EastWest.md new file mode 100644 index 00000000..84dd8725 --- /dev/null +++ b/docs/testing/user/userguide/east-west-benchmarking/Topologies-EastWest.md @@ -0,0 +1,34 @@ +# Topologies used for East-Weast traffic benchmarking + +For the East-Weast traffic benchmarking, five topologies has been tested. These topologies are composed by Pods running in the same node, and each one differs in terms of number of Pods and numbers of interfaces. + + + +### Topologies + +topologies + + + +### Single and Multi Nodes + +A System Under Test (SUT) can be composed by one or more nodes. In a single node configuration all the Pods run on the same worker node, while, in a multi node configuration there are Pods that run on different workers and the traffic flows also in the Kubernetes main network. + +### Traffic Generators as PODs + +##### Different Traffic generators + +The traffic generators available as Pods are TRex and PROX. + +##### Swaps and Routers as test-pods. + +PROX's Swap and Router modes are useful for creating complex network topologies (such as the 5 shown at the beginning of this file). + +- Swap: takes whatever arrives on a specified rx port and send to a specified tx port. The source/destination MAC and ports are swapped. +- Router: PROX defines a routing table to foward traffic on the Pod's interfaces. It finds MAC address by running the ARP protocol and than updates the table. + +##### CNIs Used + +The additional interfaces has been configured by the Userspace CNI. With that CNI the Pods can use memif or vhost-user interfaces to communicate respectively with VPP or OvS-DPDK. + +SRIOV was excluded due to lack of access to switching infrastructure. \ No newline at end of file diff --git a/docs/testing/user/userguide/east-west-benchmarking/images/prox-vpp-screen-swap.png b/docs/testing/user/userguide/east-west-benchmarking/images/prox-vpp-screen-swap.png new file mode 100644 index 0000000000000000000000000000000000000000..a0dca08abedb15ebbdc97961064b44bb354c7a90 GIT binary patch literal 27990 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 YWdByN%hYYAq}Lag*RwahfAQe|0j-d^qyPW_ literal 0 HcmV?d00001 diff --git a/docs/testing/user/userguide/east-west-benchmarking/images/topologies.png b/docs/testing/user/userguide/east-west-benchmarking/images/topologies.png new file mode 100644 index 0000000000000000000000000000000000000000..cc74b786390fc84aa441f0c8a83b87589b3e0196 GIT binary patch literal 79037 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

    AdP6yJ#rYbbTc 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 literal 0 HcmV?d00001 -- 2.16.6 From 4ead54abb4ebd848f43cd0372540f0d0f642499f Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Fri, 3 Jun 2022 12:54:00 +0530 Subject: [PATCH 09/16] [DOCS] Add Release Notes for Moselle release. This patch adds release notes for Moselle. Signed-off-by: Sridhar K. N. Rao Change-Id: I45723ede2e6fe50d2d76dcf7a4ff928f2c9332f4 --- docs/release/release-notes/release-notes.rst | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst index 23bc0899..fd77a37a 100644 --- a/docs/release/release-notes/release-notes.rst +++ b/docs/release/release-notes/release-notes.rst @@ -3,6 +3,36 @@ .. (c) OPNFV, Intel Corporation, Spirent Communications, AT&T and others. +Anuket Moselle Release +====================== + +* Supported Software Versions - DPDK:20.05, OVS:2.14.0, VPP:21.01, QEMU:3.1.1, Trex:2.86, PROX:Kali + +* Traffic Generator + + * T-Rex bug-fix. + +* Kubernetes + + * Support of East-West Traffic Performance Benchmarking. + + * Dockerfiles for traffic-generators, and traffic forwarders. + * Pod deployment files. + * Example configurations - traffic generator, forwarder, and client. + * Detailed documentation. + +* Tools + + * Stressor Tool for kubernetes environment is added. + * Cloud Information collection tool is added. + +* Miscellaneous + + * For newer version of DPDK (based on meson build) and corresponding OVS version, OS-specific + installations and separate makefiles are added. + * Qemu Build fix. + + Anuket Lakelse Release ====================== -- 2.16.6 From aa75d7b0b5041aafa9a34bc95600db5b01a5791e Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Tue, 7 Jun 2022 15:19:07 +0530 Subject: [PATCH 10/16] [VPP] Fix VPP Build issue. This Patch fixes the VPP build issue. Signed-off-by: Sridhar K. N. Rao Change-Id: I456fb698aed95a97dc86908a13a4c40dbf25e9bb --- src/vpp/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vpp/Makefile b/src/vpp/Makefile index d3146f46..5753a994 100644 --- a/src/vpp/Makefile +++ b/src/vpp/Makefile @@ -37,6 +37,7 @@ force_make: $(WORK_DIR)/Makefile # another option is to copy all required VPP dependencies into VSPERF # installation files and keep them updated $(AT)$(MAKE) -C $(WORK_DIR) UNATTENDED=y install-dep + $(AT)$(MAKE) -C $(WORK_DIR) UNATTENDED=y install-ext-deps $(AT)$(MAKE) -C $(WORK_DIR) build-release $(MORE_MAKE_FLAGS) # vppctl expects that vpp_api_test is installed in system directories # in order to execute vppctl from src/ subtree we have to use absolute path -- 2.16.6 From eb7d834d3e8a2dd7d133337f7017782320292b46 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Wed, 7 Sep 2022 14:09:11 +0530 Subject: [PATCH 11/16] INFO: Update PTL Information. This patch updates PTL information Signed-off-by: Sridhar K. N. Rao Change-Id: I02f57313d13f9217c542f2d7fcf3584ee897192e --- INFO.yaml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/INFO.yaml b/INFO.yaml index 25273afc..5ff75d22 100644 --- a/INFO.yaml +++ b/INFO.yaml @@ -4,11 +4,11 @@ project_creation_date: '' project_category: 'Integration & Testing' lifecycle_state: 'Mature' project_lead: &opnfv_vswitchperf_ptl - name: 'Sridhar Rao' - email: 'Sridhar.Rao@spirent.com' - company: 'spirent.com' - id: 'sridharkn' - timezone: 'IST' + name: 'Al Morton' + email: 'acmorton@att.com' + company: 'att.com' + id: 'acm' + timezone: 'CDT' primary_contact: *opnfv_vswitchperf_ptl issue_tracking: type: 'jira' @@ -25,23 +25,23 @@ realtime_discussion: meetings: - type: 'gotomeeting+irc' agenda: # eg: 'https://wiki.opnfv.org/display/' - url: # eg: 'https://global.gotomeeting.com/join/819733085' + url: https://zoom.us/j/2362828999 # eg: 'https://global.gotomeeting.com/join/819733085' server: 'freenode.net' channel: '#opnfv-meeting' repeats: 'weekly' - time: '15:00 UTC' # eg: '16:00 UTC' + time: '09:00 US-ET' # eg: '15:00 UTC' repositories: - 'vineperf' committers: - <<: *opnfv_vswitchperf_ptl - name: 'Maryam Tahhan' - email: 'maryam.tahhan@intel.com' - company: 'intel.com' + email: 'mtahhan@redhat.com' + company: 'redhat.com' id: 'maryamtahhan' - - name: 'Al Morton' - email: 'acmorton@att.com' - company: 'att.com' - id: 'acm' + - name: 'Sridhar Rao' + email: 'srao@linuxfoundation.org' + company: 'linuxfoundation.org' + id: 'sridharkn' - name: 'Martin Klozik' email: 'martin.klozik@tieto.com' company: 'tieto.com' -- 2.16.6 From 98177ae9f931901646403485aadbed6f6813cad0 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Sat, 1 Oct 2022 19:03:06 +0530 Subject: [PATCH 12/16] DPDK: Default to Meson-Build of DPDK. This patch makes meson based build as default. Signed-off-by: Sridhar K. N. Rao Change-Id: Ie41748342a76cf157a1afcb2e1866d8f9e01c6f2 --- src/dpdk/Makefile | 67 +---------- src/dpdk/Makefile.dpdk_meson | 74 ------------ src/dpdk/Makefile.legacy | 131 +++++++++++++++++++++ src/ovs/Makefile | 19 ++- .../{Makefile.ovs_dpdk_meson => Makefile.legacy} | 20 ++-- src/package-list.mk | 4 +- src/package-list.mk.dpdk_meson | 32 ----- systems/README.md | 24 +++- 8 files changed, 180 insertions(+), 191 deletions(-) delete mode 100755 src/dpdk/Makefile.dpdk_meson create mode 100755 src/dpdk/Makefile.legacy rename src/ovs/{Makefile.ovs_dpdk_meson => Makefile.legacy} (88%) delete mode 100644 src/package-list.mk.dpdk_meson diff --git a/src/dpdk/Makefile b/src/dpdk/Makefile index d5d91ab0..a8434943 100755 --- a/src/dpdk/Makefile +++ b/src/dpdk/Makefile @@ -1,7 +1,7 @@ # makefile to manage dpdk package # -# Copyright 2015-2016 OPNFV +# Copyright 2022 Anuket # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ # Aihua Li, Huawei Technologies. # Martin Klozik, Intel Corporation. # Christian Trautman, Red Hat Inc. +# Sridhar Rao, The Linux Foundation. include ../mk/master.mk include ../package-list.mk @@ -33,73 +34,17 @@ endif WORK_DIR = dpdk TAG_DONE_FLAG = $(WORK_DIR)/.$(DPDK_TAG).tag.done -# VHOST configuration options are stored in different files based on DPDK version -# v1.2.3r0-v1.6.0r2 - configuration inside config/defconfig_x86_64-default-linuxapp-gcc -# v1.7.0-rc1-v2.2.0 - configuration inside config/common_linuxapp -# v16 and newer - configuration split between config/common_linuxapp and config/common_base -DPDK_TAG_MAJOR = $(shell echo $(DPDK_TAG) | cut -d. -f1) -DPDK_TAG_MINOR = $(shell echo $(DPDK_TAG) | cut -d. -f2) -ifeq ($(DPDK_TAG_MAJOR),v1) -ifeq ($(DPDK_TAG_MINOR), $(filter $(DPDK_TAG_MINOR), 7 8)) - DPDK_TARGET = x86_64-native-linuxapp-gcc - CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linuxapp -else - DPDK_TARGET = x86_64-default-linuxapp-gcc - CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/defconfig_x86_64-default-linuxapp-gcc -endif -else -ifeq ($(DPDK_TAG_MAJOR),v2) - DPDK_TARGET = x86_64-native-linuxapp-gcc - CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linuxapp -else - DPDK_TARGET = x86_64-native-linuxapp-gcc - CONFIG_FILE_BASE = $(WORK_DIR)/config/common_base - CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linux -endif -endif - all: force_make @echo "Finished making $(WORK_DIR) " INSTALL_TARGET = force_make -# modify CONFIG_FILE to enable VHOST_USER build and restore original CONFIG_FILE after the build -# DPDK v16 comments: -## CONFIG_RTE_BUILD_COMBINE_LIBS has been obsoleted -## CONFIG_RTE_LIBRTE_VHOST and CONFIG_RTE_LIBRTE_KNI are listed in both config_base and config_linuxapp, -## values from config_linuxapp will be used, but options are modified at both places to avoid confusion. force_make: $(TAG_DONE_FLAG) $(AT)cd $(WORK_DIR) && git pull $(DPDK_URL) $(DPDK_TAG) -ifdef CONFIG_FILE_BASE - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_USER=.\+/CONFIG_RTE_LIBRTE_VHOST_USER=$(VHOST_USER)/g' $(CONFIG_FILE_BASE) - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST=./CONFIG_RTE_LIBRTE_VHOST=y/g' $(CONFIG_FILE_BASE) - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_KNI=./CONFIG_RTE_LIBRTE_KNI=n/g' $(CONFIG_FILE_BASE) - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_NUMA=./CONFIG_RTE_LIBRTE_VHOST_NUMA=y/g' $(CONFIG_FILE_BASE) - $(AT)sed -i -e 's/CONFIG_RTE_EAL_IGB_UIO=./CONFIG_RTE_EAL_IGB_UIO=y/g' $(CONFIG_FILE_BASE) -else - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_USER=.\+/CONFIG_RTE_LIBRTE_VHOST_USER=$(VHOST_USER)/g' $(CONFIG_FILE_LINUXAPP) - $(AT)sed -i -e 's/CONFIG_RTE_BUILD_COMBINE_LIBS=./CONFIG_RTE_BUILD_COMBINE_LIBS=y/g' $(CONFIG_FILE_LINUXAPP) -endif -# CentOS 7.3 specific config changes to compile -ifeq ($(ID),"centos") -ifeq ($(VERSION_ID),"7") - $(AT)sed -i.bak s@'SRCS-y += ethtool/igb/igb_main.c'@'#SRCS-y += ethtool/igb/igb_main.c'@g $(WORK_DIR)/kernel/linux/kni/Makefile -endif -endif -# RHEL 7.3 specific config changes to compile -ifeq ($(ID),"rhel") -ifeq ($(VERSION_ID),"7.3") - $(AT)sed -i.bak s@'SRCS-y += ethtool/igb/igb_main.c'@'#SRCS-y += ethtool/igb/igb_main.c'@g $(WORK_DIR)/kernel/linux/kni/Makefile -endif -endif - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST=./CONFIG_RTE_LIBRTE_VHOST=y/g' $(CONFIG_FILE_LINUXAPP) - $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_KNI=./CONFIG_RTE_LIBRTE_KNI=n/g' $(CONFIG_FILE_LINUXAPP) - $(AT)cd $(WORK_DIR); make install T=$(DPDK_TARGET) -j DESTDIR=$(WORK_DIR) - $(AT)cd `dirname $(CONFIG_FILE_LINUXAPP)` && git checkout `basename $(CONFIG_FILE_LINUXAPP)` && cd - -ifdef CONFIG_FILE_BASE - $(AT)cd `dirname $(CONFIG_FILE_BASE)` && git checkout `basename $(CONFIG_FILE_BASE)` && cd - -endif - $(AT)echo "VHOST_USER = $(VHOST_USER)" + $(AT)cd $(WORK_DIR) && meson --prefix=$(CURDIR)/dpdk/build -Dtests=false build + $(AT)cd $(WORK_DIR) && ninja -C build + $(AT)cd $(WORK_DIR) && ninja -C build install + $(AT)cd $(WORK_DIR) && cd build && meson configure -Denable_kmods=true && ninja @echo "Make done" install: $(INSTALL_TARGET) diff --git a/src/dpdk/Makefile.dpdk_meson b/src/dpdk/Makefile.dpdk_meson deleted file mode 100755 index d0dd9e1a..00000000 --- a/src/dpdk/Makefile.dpdk_meson +++ /dev/null @@ -1,74 +0,0 @@ -# makefile to manage dpdk package -# - -# Copyright 2022 Anuket -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# Contributors: -# Aihua Li, Huawei Technologies. -# Martin Klozik, Intel Corporation. -# Christian Trautman, Red Hat Inc. -# Sridhar Rao, The Linux Foundation. - -include ../mk/master.mk -include ../package-list.mk -include /etc/os-release - -.PHONY: install force_make - -ifndef VHOST_USER - VHOST_USER = n -endif -WORK_DIR = dpdk -TAG_DONE_FLAG = $(WORK_DIR)/.$(DPDK_TAG).tag.done - -all: force_make - @echo "Finished making $(WORK_DIR) " - -INSTALL_TARGET = force_make - -force_make: $(TAG_DONE_FLAG) - $(AT)cd $(WORK_DIR) && git pull $(DPDK_URL) $(DPDK_TAG) - $(AT)cd $(WORK_DIR) && meson build - $(AT)cd $(WORK_DIR) && cd build && meson configure -Denable_kmods=true && ninja - @echo "Make done" - -install: $(INSTALL_TARGET) - $(AT)sudo cp -a $(WORK_DIR)/$(DPDK_TARGET)/kmod $(INSTALL_DIR)/lib/modules/$(KERNEL_VERSION) - @echo "install done" - -# hard way to clean and clobber -clean: - $(AT)cd $(WORK_DIR) && git clean -xfd *.o -clobber: - $(AT)rm -rf $(WORK_DIR) - -# distclean is for developer who would like to keep the -# clone git repo, saving time to fetch again from url -distclean: - $(AT)cd $(WORK_DIR) && git clean -xfd && git checkout -f - -test: - @echo "Make test in $(WORK_DIR) (stub) " - -sanity: - @echo "Make sanity in $(WORK_DIR) (stub) " - -$(WORK_DIR): - $(AT)git clone $(DPDK_URL) dpdk - -$(TAG_DONE_FLAG): $(WORK_DIR) - $(AT)cd $(WORK_DIR); git checkout $(DPDK_TAG) - $(AT)touch $@ diff --git a/src/dpdk/Makefile.legacy b/src/dpdk/Makefile.legacy new file mode 100755 index 00000000..d5d91ab0 --- /dev/null +++ b/src/dpdk/Makefile.legacy @@ -0,0 +1,131 @@ +# makefile to manage dpdk package +# + +# Copyright 2015-2016 OPNFV +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Contributors: +# Aihua Li, Huawei Technologies. +# Martin Klozik, Intel Corporation. +# Christian Trautman, Red Hat Inc. + +include ../mk/master.mk +include ../package-list.mk +include /etc/os-release + +.PHONY: install force_make + +ifndef VHOST_USER + VHOST_USER = n +endif +WORK_DIR = dpdk +TAG_DONE_FLAG = $(WORK_DIR)/.$(DPDK_TAG).tag.done + +# VHOST configuration options are stored in different files based on DPDK version +# v1.2.3r0-v1.6.0r2 - configuration inside config/defconfig_x86_64-default-linuxapp-gcc +# v1.7.0-rc1-v2.2.0 - configuration inside config/common_linuxapp +# v16 and newer - configuration split between config/common_linuxapp and config/common_base +DPDK_TAG_MAJOR = $(shell echo $(DPDK_TAG) | cut -d. -f1) +DPDK_TAG_MINOR = $(shell echo $(DPDK_TAG) | cut -d. -f2) +ifeq ($(DPDK_TAG_MAJOR),v1) +ifeq ($(DPDK_TAG_MINOR), $(filter $(DPDK_TAG_MINOR), 7 8)) + DPDK_TARGET = x86_64-native-linuxapp-gcc + CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linuxapp +else + DPDK_TARGET = x86_64-default-linuxapp-gcc + CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/defconfig_x86_64-default-linuxapp-gcc +endif +else +ifeq ($(DPDK_TAG_MAJOR),v2) + DPDK_TARGET = x86_64-native-linuxapp-gcc + CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linuxapp +else + DPDK_TARGET = x86_64-native-linuxapp-gcc + CONFIG_FILE_BASE = $(WORK_DIR)/config/common_base + CONFIG_FILE_LINUXAPP = $(WORK_DIR)/config/common_linux +endif +endif + +all: force_make + @echo "Finished making $(WORK_DIR) " + +INSTALL_TARGET = force_make + +# modify CONFIG_FILE to enable VHOST_USER build and restore original CONFIG_FILE after the build +# DPDK v16 comments: +## CONFIG_RTE_BUILD_COMBINE_LIBS has been obsoleted +## CONFIG_RTE_LIBRTE_VHOST and CONFIG_RTE_LIBRTE_KNI are listed in both config_base and config_linuxapp, +## values from config_linuxapp will be used, but options are modified at both places to avoid confusion. +force_make: $(TAG_DONE_FLAG) + $(AT)cd $(WORK_DIR) && git pull $(DPDK_URL) $(DPDK_TAG) +ifdef CONFIG_FILE_BASE + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_USER=.\+/CONFIG_RTE_LIBRTE_VHOST_USER=$(VHOST_USER)/g' $(CONFIG_FILE_BASE) + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST=./CONFIG_RTE_LIBRTE_VHOST=y/g' $(CONFIG_FILE_BASE) + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_KNI=./CONFIG_RTE_LIBRTE_KNI=n/g' $(CONFIG_FILE_BASE) + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_NUMA=./CONFIG_RTE_LIBRTE_VHOST_NUMA=y/g' $(CONFIG_FILE_BASE) + $(AT)sed -i -e 's/CONFIG_RTE_EAL_IGB_UIO=./CONFIG_RTE_EAL_IGB_UIO=y/g' $(CONFIG_FILE_BASE) +else + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST_USER=.\+/CONFIG_RTE_LIBRTE_VHOST_USER=$(VHOST_USER)/g' $(CONFIG_FILE_LINUXAPP) + $(AT)sed -i -e 's/CONFIG_RTE_BUILD_COMBINE_LIBS=./CONFIG_RTE_BUILD_COMBINE_LIBS=y/g' $(CONFIG_FILE_LINUXAPP) +endif +# CentOS 7.3 specific config changes to compile +ifeq ($(ID),"centos") +ifeq ($(VERSION_ID),"7") + $(AT)sed -i.bak s@'SRCS-y += ethtool/igb/igb_main.c'@'#SRCS-y += ethtool/igb/igb_main.c'@g $(WORK_DIR)/kernel/linux/kni/Makefile +endif +endif +# RHEL 7.3 specific config changes to compile +ifeq ($(ID),"rhel") +ifeq ($(VERSION_ID),"7.3") + $(AT)sed -i.bak s@'SRCS-y += ethtool/igb/igb_main.c'@'#SRCS-y += ethtool/igb/igb_main.c'@g $(WORK_DIR)/kernel/linux/kni/Makefile +endif +endif + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_VHOST=./CONFIG_RTE_LIBRTE_VHOST=y/g' $(CONFIG_FILE_LINUXAPP) + $(AT)sed -i -e 's/CONFIG_RTE_LIBRTE_KNI=./CONFIG_RTE_LIBRTE_KNI=n/g' $(CONFIG_FILE_LINUXAPP) + $(AT)cd $(WORK_DIR); make install T=$(DPDK_TARGET) -j DESTDIR=$(WORK_DIR) + $(AT)cd `dirname $(CONFIG_FILE_LINUXAPP)` && git checkout `basename $(CONFIG_FILE_LINUXAPP)` && cd - +ifdef CONFIG_FILE_BASE + $(AT)cd `dirname $(CONFIG_FILE_BASE)` && git checkout `basename $(CONFIG_FILE_BASE)` && cd - +endif + $(AT)echo "VHOST_USER = $(VHOST_USER)" + @echo "Make done" + +install: $(INSTALL_TARGET) + $(AT)sudo cp -a $(WORK_DIR)/$(DPDK_TARGET)/kmod $(INSTALL_DIR)/lib/modules/$(KERNEL_VERSION) + @echo "install done" + +# hard way to clean and clobber +clean: + $(AT)cd $(WORK_DIR) && git clean -xfd *.o +clobber: + $(AT)rm -rf $(WORK_DIR) + +# distclean is for developer who would like to keep the +# clone git repo, saving time to fetch again from url +distclean: + $(AT)cd $(WORK_DIR) && git clean -xfd && git checkout -f + +test: + @echo "Make test in $(WORK_DIR) (stub) " + +sanity: + @echo "Make sanity in $(WORK_DIR) (stub) " + +$(WORK_DIR): + $(AT)git clone $(DPDK_URL) dpdk + +$(TAG_DONE_FLAG): $(WORK_DIR) + $(AT)cd $(WORK_DIR); git checkout $(DPDK_TAG) + $(AT)touch $@ diff --git a/src/ovs/Makefile b/src/ovs/Makefile index e3adc2f8..2c0c0076 100644 --- a/src/ovs/Makefile +++ b/src/ovs/Makefile @@ -1,7 +1,7 @@ # makefile to manage ovs package # -# Copyright 2015 OPNFV +# Copyright 2022 Anuket # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ # # Contributors: # Aihua Li, Huawei Technologies. +# Sridhar Rao, The Linux Foundation include ../mk/master.mk include ../package-list.mk @@ -48,10 +49,16 @@ ifeq ($(DPDK_DIR),) DPDK_DIR = ../../dpdk/dpdk endif -ifeq ($(DPDK_TARGET),) -DPDK_TARGET = x86_64-native-linuxapp-gcc -endif -CONFIG_CMD += --with-dpdk=$(DPDK_DIR)/$(DPDK_TARGET) +DPDK_LIB = $(DPDK_DIR)/build/lib/x86_64-linux-gnu +LD_CONFIG_PATH := $(DPDK_LIB)/:$(LD_CONFIG_PATH) +PKG_CONFIG_PATH := $(DPDK_LIB)/pkgconfig/:$(PKG_CONFIG_PATH) + +export LD_CONFIG_PATH +export PKG_CONFIG_PATH + +# echo $$ENV{LD_CONFIG_PATH} + +CONFIG_CMD += --with-dpdk=shared CONFIG_CMD += CFLAGS="-g -O2 -Wno-cast-align" endif # Kernel vs. DPDK @@ -65,7 +72,7 @@ all: force_make @echo "Finished making $(WORK_DIR) " config $(WORK_DIR)/Makefile: $(WORK_DIR)/configure - $(AT)cd $(WORK_DIR); $(CONFIG_CMD) + $(AT)cd $(WORK_DIR); LD_LIBRARY_PATH=$(DPDK_LIB) PKG_CONFIG_PATH=$(DPDK_LIB)/pkgconfig/ $(CONFIG_CMD) @echo "Configure done" INSTALL_TARGET = force_install force_make diff --git a/src/ovs/Makefile.ovs_dpdk_meson b/src/ovs/Makefile.legacy similarity index 88% rename from src/ovs/Makefile.ovs_dpdk_meson rename to src/ovs/Makefile.legacy index 4b4d997d..e3adc2f8 100644 --- a/src/ovs/Makefile.ovs_dpdk_meson +++ b/src/ovs/Makefile.legacy @@ -1,7 +1,7 @@ # makefile to manage ovs package # -# Copyright 2022 Anuket +# Copyright 2015 OPNFV # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ # # Contributors: # Aihua Li, Huawei Technologies. -# Sridhar Rao, The Linux Foundation include ../mk/master.mk include ../package-list.mk @@ -44,14 +43,15 @@ else # Building with DPDK # it can be passed in from Makefile command # if it is not set, try to read it in from environment # if it is still not set, then set it using relative path -PKG_CONFIG_PATH := ../dpdk/dpdk/build/meson-private -LD_CONFIG_PATH := ../dpdk/dpdk/build/lib -export LD_CONFIG_PATH -export PKG_CONFIG_PATH - -# echo $$ENV{LD_CONFIG_PATH} +DPDK_DIR ?= $(shell echo $$DPDK_DIR) +ifeq ($(DPDK_DIR),) +DPDK_DIR = ../../dpdk/dpdk +endif -CONFIG_CMD += --with-dpdk=shared +ifeq ($(DPDK_TARGET),) +DPDK_TARGET = x86_64-native-linuxapp-gcc +endif +CONFIG_CMD += --with-dpdk=$(DPDK_DIR)/$(DPDK_TARGET) CONFIG_CMD += CFLAGS="-g -O2 -Wno-cast-align" endif # Kernel vs. DPDK @@ -65,7 +65,7 @@ all: force_make @echo "Finished making $(WORK_DIR) " config $(WORK_DIR)/Makefile: $(WORK_DIR)/configure - $(AT)cd $(WORK_DIR); LD_CONFIG_PATH=../../dpdk/dpdk/build/lib PKG_CONFIG_PATH=../../dpdk/dpdk/build/meson-private $(CONFIG_CMD) + $(AT)cd $(WORK_DIR); $(CONFIG_CMD) @echo "Configure done" INSTALL_TARGET = force_install force_make diff --git a/src/package-list.mk b/src/package-list.mk index 49aa82c0..c11a04f7 100644 --- a/src/package-list.mk +++ b/src/package-list.mk @@ -13,11 +13,11 @@ # dpdk section # DPDK_URL ?= git://dpdk.org/dpdk DPDK_URL ?= http://dpdk.org/git/dpdk -DPDK_TAG ?= v20.05 +DPDK_TAG ?= v21.11 # OVS section OVS_URL ?= https://github.com/openvswitch/ovs -OVS_TAG ?= v2.14.0 +OVS_TAG ?= v2.17.2 # VPP section VPP_URL ?= https://git.fd.io/vpp diff --git a/src/package-list.mk.dpdk_meson b/src/package-list.mk.dpdk_meson deleted file mode 100644 index 3c219e53..00000000 --- a/src/package-list.mk.dpdk_meson +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2016-2017 Intel corporation. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Upstream Package List -# -# Everything here is defined as its suggested default -# value, it can always be overriden when invoking Make - -# dpdk section -# DPDK_URL ?= git://dpdk.org/dpdk -DPDK_URL ?= http://dpdk.org/git/dpdk -DPDK_TAG ?= v22.03 - -# OVS section -OVS_URL ?= https://github.com/openvswitch/ovs -OVS_TAG ?= v2.17.0 - -# VPP section -VPP_URL ?= https://git.fd.io/vpp -VPP_TAG ?= v21.01 - -# QEMU section -QEMU_URL ?= https://github.com/qemu/qemu.git -QEMU_TAG ?= v3.1.1 - -# TREX section -TREX_URL ?= https://github.com/cisco-system-traffic-generator/trex-core.git -TREX_TAG ?= v2.86 diff --git a/systems/README.md b/systems/README.md index 60f903c1..d0812a81 100644 --- a/systems/README.md +++ b/systems/README.md @@ -10,13 +10,14 @@ One time setup: On a freshly built system, run the following with a super user privilege or with password less sudo access. - -./build_base_machine.sh +``` + ./build_base_machine.sh +``` If you want to use vsperf in trafficgen-mode ONLY, then add a parameter. - -./build_base_machine.sh trafficgen - +``` + ./build_base_machine.sh trafficgen +``` Newer Kernel Versions: ---------------------- @@ -26,5 +27,16 @@ May need following changes: 1. In src/l2fwd/l2fwd.c, comment out the line with xmit_more (193). 2. In src/qemu/Makefile, In line 30, we MAY have to add the following: - --disable-werror --python='/usr/bin/python3' +``` + --disable-werror +``` +3. In src/qemu/Makefile, In line 31, we MAY have to change python flag to: +``` + --python='/usr/bin/python3' +``` +4. If Fedora 32+ is used, then change the line 52 in src/ovs/Makefile to: +``` + DPDK_LIB = $(DPDK_DIR)/build/lib64 +``` + -- 2.16.6 From b6904df9960e1cf57c3943c6391cc333a8f51446 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Sat, 1 Oct 2022 19:22:15 +0530 Subject: [PATCH 13/16] SYSTEMS: Adds build support to new OS versions. Fedora: 36 Ubuntu: 22.04 Signed-off-by: Sridhar K. N. Rao Change-Id: Ieb353ef9f45e6932e8cf65d93e72b096ff08cca9 --- systems/fedora/36/build_base_machine.sh | 78 +++++++++++++++++++++++++ systems/fedora/36/prepare_python_env.sh | 30 ++++++++++ systems/ubuntu/22.04/build_base_machine.sh | 94 ++++++++++++++++++++++++++++++ systems/ubuntu/22.04/prepare_python_env.sh | 29 +++++++++ 4 files changed, 231 insertions(+) create mode 100755 systems/fedora/36/build_base_machine.sh create mode 100755 systems/fedora/36/prepare_python_env.sh create mode 100755 systems/ubuntu/22.04/build_base_machine.sh create mode 100755 systems/ubuntu/22.04/prepare_python_env.sh diff --git a/systems/fedora/36/build_base_machine.sh b/systems/fedora/36/build_base_machine.sh new file mode 100755 index 00000000..f3a27644 --- /dev/null +++ b/systems/fedora/36/build_base_machine.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# +# Build a base machine for Fedora 26 +# +# Copyright 2017 OPNFV, Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +dnf -y install $(echo " +# Make and Compilers +make +automake +gcc +gcc-c++ +libxml2 +glibc +glib2-devel +kernel-devel +fuse-libs +fuse +fuse-devel +kernel-modules-extra +pixman-devel +openssl-devel +redhat-rpm-config +elfutils-libelf-devel +meson +ninja-build + +# tools +curl +autoconf +libtool +libpcap-devel +libnet +vim +wget +git +pciutils +cifs-utils +socat +sysstat +sshpass + +# install python packages +python3-virtualenv +python3-setuptools +python3-pip +python3-dbus +python3-devel +python3-tkinter +systemd-python3 +python3-libs +python3-libreport +python3-abrt +python3-abrt-addon +python3-pyelftools + +# libs +numactl +numactl-devel + +# install git-review tool +git-review +" | grep -v ^#) + +# Create hugepage dirs +mkdir -p /dev/hugepages diff --git a/systems/fedora/36/prepare_python_env.sh b/systems/fedora/36/prepare_python_env.sh new file mode 100755 index 00000000..becfd5ca --- /dev/null +++ b/systems/fedora/36/prepare_python_env.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# Prepare Python environment for vsperf execution on Fedora 26 systems. +# +# Copyright 2017 OPNFV, Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ -d "$VSPERFENV_DIR" ] ; then + echo "Directory $VSPERFENV_DIR already exists. Skipping python virtualenv creation." + exit +fi + +# enable virtual environment in a subshell, so QEMU build can use python 2.7 + +(virtualenv "$VSPERFENV_DIR" --python /usr/bin/python3 +source "$VSPERFENV_DIR"/bin/activate +pip install -U pip +pip install -r ../requirements.txt +pip install six) diff --git a/systems/ubuntu/22.04/build_base_machine.sh b/systems/ubuntu/22.04/build_base_machine.sh new file mode 100755 index 00000000..ab30c347 --- /dev/null +++ b/systems/ubuntu/22.04/build_base_machine.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# +# Build a base machine for Ubuntu 14.04 +# +# Copyright 2015 OPNFV, Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Contributors: +# Aihua Li, Huawei Technologies. +# Martin Klozik, Intel Corporation. +# Abdul Halim, Intel Corporation. + +apt-get update +apt-get -y install $(echo " +# Make and Compilers +make +automake +gcc +g++ +libc6 +libc6-dev +libxml2 +fuse +libfuse2 +libfuse-dev +libssl-dev +libglib2.0-dev +zlib1g-dev + +# Linux Kernel Source +linux-source +linux-headers-$(uname -r) +pkg-config + +# tools +curl +libcurl4-openssl-dev +automake +autoconf +libtool +libpcap-dev +libnet1 +libncurses5-dev +vim +wget +git +pciutils +cifs-utils +socat +libpixman-1-0 +libpixman-1-dev +sysstat +sshpass + +# Java runtime environment: Required for Ixia TclClient +default-jre + +# install python packages +python3-pip +python3-setuptools +python3-dbus +python3-dev +python3-tk +python3-venv +libpython3.8 +python3-reportlab + +# libs +libnuma1 +libnuma-dev + +# install git-review tool +git-review +" | grep -v ^#) + +# fix: Ixia TclClient installation: cannot find 'libc.s0.6' +ln -sf $(locate libc.so.6) /lib/libc.so.6 + +# Install virtualenv +pip3 install virtualenv + +# Create hugepage dirs +mkdir -p /dev/hugepages diff --git a/systems/ubuntu/22.04/prepare_python_env.sh b/systems/ubuntu/22.04/prepare_python_env.sh new file mode 100755 index 00000000..c44a3bdd --- /dev/null +++ b/systems/ubuntu/22.04/prepare_python_env.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# Prepare Python environment for vsperf execution on Ubuntu 14.04 systems +# +# Copyright 2015-2017 OPNFV, Intel Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ -d "$VSPERFENV_DIR" ] ; then + echo "Directory $VSPERFENV_DIR already exists. Skipping python virtualenv creation." + exit +fi + +# enable virtual environment in a subshell, so QEMU build can use python 2.7 + +(virtualenv "$VSPERFENV_DIR" --python /usr/bin/python3 +source "$VSPERFENV_DIR"/bin/activate +pip install -U pip +pip install -r ../requirements.txt) -- 2.16.6 From 3409364da8fa46eae8ca22a579e219dc74958079 Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Fri, 16 Dec 2022 17:24:48 +0530 Subject: [PATCH 14/16] ebpf: This patch adds ebpf-baremetal test support Remove object files and binaries Suggestions in systems/Readme. Add license headers. Signed-off-by: Sridhar K. N. Rao Change-Id: I5fd481e3ed3eb51e6b8091cbf6d1ec8e3b00cbf0 --- src/bpfswitch/Makefile | 13 + src/bpfswitch/README.md | 34 + src/bpfswitch/configure | 276 ++ src/bpfswitch/include/bpf/bpf.h | 244 ++ src/bpfswitch/include/bpf/bpf_core_read.h | 263 ++ src/bpfswitch/include/bpf/bpf_endian.h | 99 + src/bpfswitch/include/bpf/bpf_helper_defs.h | 3260 ++++++++++++++++++++++ src/bpfswitch/include/bpf/bpf_helpers.h | 80 + src/bpfswitch/include/bpf/bpf_tracing.h | 432 +++ src/bpfswitch/include/bpf/btf.h | 340 +++ src/bpfswitch/include/bpf/libbpf.h | 750 +++++ src/bpfswitch/include/bpf/libbpf_common.h | 40 + src/bpfswitch/include/bpf/libbpf_util.h | 47 + src/bpfswitch/include/bpf/xsk.h | 246 ++ src/bpfswitch/include/xdp_fdb.h | 20 + src/bpfswitch/ksrc/Makefile | 59 + src/bpfswitch/ksrc/include/asm_goto_workaround.h | 28 + src/bpfswitch/ksrc/xdp_dummy.c | 21 + src/bpfswitch/ksrc/xdp_l2fwd.c | 103 + src/bpfswitch/scripts/ifindex.py | 18 + src/bpfswitch/scripts/l2fwd.sh | 137 + src/bpfswitch/usrc/Makefile | 60 + src/bpfswitch/usrc/libbpf_helpers.c | 309 ++ src/bpfswitch/usrc/libbpf_helpers.h | 29 + src/bpfswitch/usrc/str_utils.c | 159 ++ src/bpfswitch/usrc/str_utils.h | 15 + src/bpfswitch/usrc/xdp_dummy_user.c | 116 + src/bpfswitch/usrc/xdp_l2fwd_user.c | 402 +++ systems/README.md | 29 +- systems/debian/build_base_machine.sh | 6 + systems/fedora/33/build_base_machine.sh | 4 + systems/fedora/36/build_base_machine.sh | 3 + systems/ubuntu/build_base_machine.sh | 6 + 33 files changed, 7647 insertions(+), 1 deletion(-) create mode 100644 src/bpfswitch/Makefile create mode 100644 src/bpfswitch/README.md create mode 100755 src/bpfswitch/configure create mode 100644 src/bpfswitch/include/bpf/bpf.h create mode 100644 src/bpfswitch/include/bpf/bpf_core_read.h create mode 100644 src/bpfswitch/include/bpf/bpf_endian.h create mode 100644 src/bpfswitch/include/bpf/bpf_helper_defs.h create mode 100644 src/bpfswitch/include/bpf/bpf_helpers.h create mode 100644 src/bpfswitch/include/bpf/bpf_tracing.h create mode 100644 src/bpfswitch/include/bpf/btf.h create mode 100644 src/bpfswitch/include/bpf/libbpf.h create mode 100644 src/bpfswitch/include/bpf/libbpf_common.h create mode 100644 src/bpfswitch/include/bpf/libbpf_util.h create mode 100644 src/bpfswitch/include/bpf/xsk.h create mode 100644 src/bpfswitch/include/xdp_fdb.h create mode 100644 src/bpfswitch/ksrc/Makefile create mode 100644 src/bpfswitch/ksrc/include/asm_goto_workaround.h create mode 100644 src/bpfswitch/ksrc/xdp_dummy.c create mode 100644 src/bpfswitch/ksrc/xdp_l2fwd.c create mode 100755 src/bpfswitch/scripts/ifindex.py create mode 100755 src/bpfswitch/scripts/l2fwd.sh create mode 100644 src/bpfswitch/usrc/Makefile create mode 100644 src/bpfswitch/usrc/libbpf_helpers.c create mode 100644 src/bpfswitch/usrc/libbpf_helpers.h create mode 100644 src/bpfswitch/usrc/str_utils.c create mode 100644 src/bpfswitch/usrc/str_utils.h create mode 100644 src/bpfswitch/usrc/xdp_dummy_user.c create mode 100644 src/bpfswitch/usrc/xdp_l2fwd_user.c diff --git a/src/bpfswitch/Makefile b/src/bpfswitch/Makefile new file mode 100644 index 00000000..94a58c9a --- /dev/null +++ b/src/bpfswitch/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 + +SUBDIRS = ksrc usrc + +all: + @for s in $(SUBDIRS); do \ + make -C $$s $(BUILDDIR) all; \ + done + +clean: + @for s in $(SUBDIRS); do \ + make -C $$s $(BUILDDIR) clean; \ + done diff --git a/src/bpfswitch/README.md b/src/bpfswitch/README.md new file mode 100644 index 00000000..15c3d4fd --- /dev/null +++ b/src/bpfswitch/README.md @@ -0,0 +1,34 @@ + + +# bpfswitch + +This repository is self contained. Please install linux-headers for kernel +version. + +## XDP L2 forwarding +xdp\_l2fwd handles Layer 2 forwarding between an ingress device (e.g., host +devices) and egress device (e.g., tap device for VMs or other host device). +Userspace populates an FDB (hash map) with \ pairs returning an +index into a device map which contains the device to receive the packet. +See scripts/l2fwd.sh for an example. + +This program is used for the netdev 0x14 tutorial, XDP and the cloud: Using +XDP on hosts and VMs https://netdevconf.info/0x14/session.html?tutorial-XDP-and-the-cloud + +## Using l2Fwd + +1. Go to vineperf/src/bpfswitch/ folder. +2. First run configure script and then run make +3. Go to scripts/ folder and open l2fwd.sh file +4. Update values of TGENMAC, Interfaces and their indexes. +5. Use ifindex.py to know the indexes. +5. Ensure that path of bpftool is correc in the file. +6. Save changes and run the script. +7. If successfully run the setup is ready to test loopback + +## Dummy XDP program + +xdp\_dummy is a dummy XDP program that just returns XDP\_PASS. diff --git a/src/bpfswitch/configure b/src/bpfswitch/configure new file mode 100755 index 00000000..7dc7f359 --- /dev/null +++ b/src/bpfswitch/configure @@ -0,0 +1,276 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# This is not an autoconf generated configure +# +# Influential environment variables: +# LIBBPF_DIR Location of libbpf install to use +# KVER Specify a specific kernel version to build programs +# KSRC Location of kernel headers (e.g., source tree) +# KBLD Location of kernel build files (e.g., O= directory) + +# 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() +{ + : ${PKG_CONFIG:=pkg-config} + : ${CC=gcc} + : ${CLANG=clang} + : ${LLC=llc} + + for TOOL in $PKG_CONFIG $CC $CLANG $LLC; do + if [ ! $(command -v ${TOOL} 2>/dev/null) ]; then + echo "*** ERROR: Cannot find tool ${TOOL}" ; + exit 1; + fi; + done + + echo "PKG_CONFIG:=${PKG_CONFIG}" >>$CONFIG + echo "CC:=${CC}" >>$CONFIG + echo "CLANG:=${CLANG}" >>$CONFIG + echo "LLC:=${LLC}" >>$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 + LDLIBS="$LDLIBS $(${PKG_CONFIG} libelf --libs)" + 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 + LDLIBS="$LDLIBS $(${PKG_CONFIG} zlib --libs)" + else + echo "missing - this is required" + return 1 + fi +} + +libbpf_compile_test() +{ + local libs="$*" + + cat >$TMPDIR/libbpftest.c < +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 $LIBBPF_LDLIBS $libs 2>&1) +} + +check_custom_libbpf() +{ + LIBBPF_CFLAGS="-I${LIBBPF_DIR}/usr/include" + LIBBPF_LDLIBS="$(find ${LIBBPF_DIR} -name libbpf.a)" + + echo -n "ELF support: " + check_elf || exit 1 + + echo -n "zlib support: " + check_zlib || exit 1 + + libbpf_compile_test "${LDLIBS}" + if [ "$?" -eq "0" ]; then + echo "LIBBPF_DIR=${LIBBPF_DIR}" >> $CONFIG + echo "CFLAGS += $LIBBPF_CFLAGS" >> $CONFIG + echo "LDLIBS += $LIBBPF_LDLIBS $LDLIBS" >> $CONFIG + + echo "libbpf support: custom install" + return 0 + fi + + echo "libbpf support: invalid custom install" + return 1 +} + +check_libbpf() +{ + local libbpf_err + + if [ -n "$LIBBPF_DIR" ]; then + check_custom_libbpf + return $? + fi + + echo -n "libbpf support: " + if [ "${FORCE_SUBMODULE_LIBBPF:-0}" -ne "1" ] && ${PKG_CONFIG} libbpf --exists; then + LIBBPF_CFLAGS=$(${PKG_CONFIG} libbpf --cflags) + LIBBPF_LDLIBS=$(${PKG_CONFIG} libbpf --libs) + + libbpf_compile_test + 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 + + 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-install/usr/include' >>$CONFIG + echo 'BPF_CFLAGS += -I$(LIB_DIR)/libbpf-install/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 + + echo "LDLIBS += $LDLIBS" >>$CONFIG + + # 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" +} + +find_kernel_path() +{ + local ksrc + local kbld + + : ${KVER:=$(uname -r)} + + if [ "${KSRC}" != "" ]; then + ksrc="${KSRC}" + # Fedora, Redhat and custom kernels + elif [ -e /lib/modules/${KVER}/source ]; then + ksrc="/lib/modules/${KVER}/source" + # Ubuntu, Debian + elif [ -e /usr/src/linux-headers-${KVER} ]; then + ksrc="/usr/src/linux-headers-${KVER}" + else + echo "Failed to find kernel source directory" >&2 + return 1 + fi + + if [ "${KBLD}" != "" ]; then + kbld="${KBLD}" + elif [ -e /lib/modules/${KVER}/build ]; then + kbld="/lib/modules/${KVER}/build" + else + echo "Failed to find kernel build directory" >&2 + return 1 + fi + echo "KSRC=${ksrc}" >> "$CONFIG" + echo "KBLD=${kbld}" >> "$CONFIG" +} + +quiet_config() +{ + cat <$CONFIG +quiet_config >> $CONFIG + +check_toolchain + +check_libbpf || exit 1 + +find_kernel_path || exit 1 + +mv $CONFIG $CONFIG_FINAL diff --git a/src/bpfswitch/include/bpf/bpf.h b/src/bpfswitch/include/bpf/bpf.h new file mode 100644 index 00000000..dbef24eb --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* + * common eBPF ELF operations. + * + * Copyright (C) 2013-2015 Alexei Starovoitov + * Copyright (C) 2015 Wang Nan + * Copyright (C) 2015 Huawei Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License (not later!) + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + */ +#ifndef __LIBBPF_BPF_H +#define __LIBBPF_BPF_H + +#include +#include +#include +#include + +#include "libbpf_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct bpf_create_map_attr { + const char *name; + enum bpf_map_type map_type; + __u32 map_flags; + __u32 key_size; + __u32 value_size; + __u32 max_entries; + __u32 numa_node; + __u32 btf_fd; + __u32 btf_key_type_id; + __u32 btf_value_type_id; + __u32 map_ifindex; + union { + __u32 inner_map_fd; + __u32 btf_vmlinux_value_type_id; + }; +}; + +LIBBPF_API int +bpf_create_map_xattr(const struct bpf_create_map_attr *create_attr); +LIBBPF_API int bpf_create_map_node(enum bpf_map_type map_type, const char *name, + int key_size, int value_size, + int max_entries, __u32 map_flags, int node); +LIBBPF_API int bpf_create_map_name(enum bpf_map_type map_type, const char *name, + int key_size, int value_size, + int max_entries, __u32 map_flags); +LIBBPF_API int bpf_create_map(enum bpf_map_type map_type, int key_size, + int value_size, int max_entries, __u32 map_flags); +LIBBPF_API int bpf_create_map_in_map_node(enum bpf_map_type map_type, + const char *name, int key_size, + int inner_map_fd, int max_entries, + __u32 map_flags, int node); +LIBBPF_API int bpf_create_map_in_map(enum bpf_map_type map_type, + const char *name, int key_size, + int inner_map_fd, int max_entries, + __u32 map_flags); + +struct bpf_load_program_attr { + enum bpf_prog_type prog_type; + enum bpf_attach_type expected_attach_type; + const char *name; + const struct bpf_insn *insns; + size_t insns_cnt; + const char *license; + union { + __u32 kern_version; + __u32 attach_prog_fd; + }; + union { + __u32 prog_ifindex; + __u32 attach_btf_id; + }; + __u32 prog_btf_fd; + __u32 func_info_rec_size; + const void *func_info; + __u32 func_info_cnt; + __u32 line_info_rec_size; + const void *line_info; + __u32 line_info_cnt; + __u32 log_level; + __u32 prog_flags; +}; + +/* Flags to direct loading requirements */ +#define MAPS_RELAX_COMPAT 0x01 + +/* Recommend log buffer size */ +#define BPF_LOG_BUF_SIZE (UINT32_MAX >> 8) /* verifier maximum in kernels <= 5.1 */ +LIBBPF_API int +bpf_load_program_xattr(const struct bpf_load_program_attr *load_attr, + char *log_buf, size_t log_buf_sz); +LIBBPF_API int bpf_load_program(enum bpf_prog_type type, + const struct bpf_insn *insns, size_t insns_cnt, + const char *license, __u32 kern_version, + char *log_buf, size_t log_buf_sz); +LIBBPF_API int bpf_verify_program(enum bpf_prog_type type, + const struct bpf_insn *insns, + size_t insns_cnt, __u32 prog_flags, + const char *license, __u32 kern_version, + char *log_buf, size_t log_buf_sz, + int log_level); + +LIBBPF_API int bpf_map_update_elem(int fd, const void *key, const void *value, + __u64 flags); + +LIBBPF_API int bpf_map_lookup_elem(int fd, const void *key, void *value); +LIBBPF_API int bpf_map_lookup_elem_flags(int fd, const void *key, void *value, + __u64 flags); +LIBBPF_API int bpf_map_lookup_and_delete_elem(int fd, const void *key, + void *value); +LIBBPF_API int bpf_map_delete_elem(int fd, const void *key); +LIBBPF_API int bpf_map_get_next_key(int fd, const void *key, void *next_key); +LIBBPF_API int bpf_map_freeze(int fd); + +struct bpf_map_batch_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ + __u64 elem_flags; + __u64 flags; +}; +#define bpf_map_batch_opts__last_field flags + +LIBBPF_API int bpf_map_delete_batch(int fd, void *keys, + __u32 *count, + const struct bpf_map_batch_opts *opts); +LIBBPF_API int bpf_map_lookup_batch(int fd, void *in_batch, void *out_batch, + void *keys, void *values, __u32 *count, + const struct bpf_map_batch_opts *opts); +LIBBPF_API int bpf_map_lookup_and_delete_batch(int fd, void *in_batch, + void *out_batch, void *keys, + void *values, __u32 *count, + const struct bpf_map_batch_opts *opts); +LIBBPF_API int bpf_map_update_batch(int fd, void *keys, void *values, + __u32 *count, + const struct bpf_map_batch_opts *opts); + +LIBBPF_API int bpf_obj_pin(int fd, const char *pathname); +LIBBPF_API int bpf_obj_get(const char *pathname); + +struct bpf_prog_attach_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ + unsigned int flags; + int replace_prog_fd; +}; +#define bpf_prog_attach_opts__last_field replace_prog_fd + +LIBBPF_API int bpf_prog_attach(int prog_fd, int attachable_fd, + enum bpf_attach_type type, unsigned int flags); +LIBBPF_API int bpf_prog_attach_xattr(int prog_fd, int attachable_fd, + enum bpf_attach_type type, + const struct bpf_prog_attach_opts *opts); +LIBBPF_API int bpf_prog_detach(int attachable_fd, enum bpf_attach_type type); +LIBBPF_API int bpf_prog_detach2(int prog_fd, int attachable_fd, + enum bpf_attach_type type); + +struct bpf_link_create_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ +}; +#define bpf_link_create_opts__last_field sz + +LIBBPF_API int bpf_link_create(int prog_fd, int target_fd, + enum bpf_attach_type attach_type, + const struct bpf_link_create_opts *opts); + +struct bpf_link_update_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ + __u32 flags; /* extra flags */ + __u32 old_prog_fd; /* expected old program FD */ +}; +#define bpf_link_update_opts__last_field old_prog_fd + +LIBBPF_API int bpf_link_update(int link_fd, int new_prog_fd, + const struct bpf_link_update_opts *opts); + +LIBBPF_API int bpf_iter_create(int link_fd); + +struct bpf_prog_test_run_attr { + int prog_fd; + int repeat; + const void *data_in; + __u32 data_size_in; + void *data_out; /* optional */ + __u32 data_size_out; /* in: max length of data_out + * out: length of data_out */ + __u32 retval; /* out: return code of the BPF program */ + __u32 duration; /* out: average per repetition in ns */ + const void *ctx_in; /* optional */ + __u32 ctx_size_in; + void *ctx_out; /* optional */ + __u32 ctx_size_out; /* in: max length of ctx_out + * out: length of cxt_out */ +}; + +LIBBPF_API int bpf_prog_test_run_xattr(struct bpf_prog_test_run_attr *test_attr); + +/* + * bpf_prog_test_run does not check that data_out is large enough. Consider + * using bpf_prog_test_run_xattr instead. + */ +LIBBPF_API int bpf_prog_test_run(int prog_fd, int repeat, void *data, + __u32 size, void *data_out, __u32 *size_out, + __u32 *retval, __u32 *duration); +LIBBPF_API int bpf_prog_get_next_id(__u32 start_id, __u32 *next_id); +LIBBPF_API int bpf_map_get_next_id(__u32 start_id, __u32 *next_id); +LIBBPF_API int bpf_btf_get_next_id(__u32 start_id, __u32 *next_id); +LIBBPF_API int bpf_link_get_next_id(__u32 start_id, __u32 *next_id); +LIBBPF_API int bpf_prog_get_fd_by_id(__u32 id); +LIBBPF_API int bpf_map_get_fd_by_id(__u32 id); +LIBBPF_API int bpf_btf_get_fd_by_id(__u32 id); +LIBBPF_API int bpf_link_get_fd_by_id(__u32 id); +LIBBPF_API int bpf_obj_get_info_by_fd(int bpf_fd, void *info, __u32 *info_len); +LIBBPF_API int bpf_prog_query(int target_fd, enum bpf_attach_type type, + __u32 query_flags, __u32 *attach_flags, + __u32 *prog_ids, __u32 *prog_cnt); +LIBBPF_API int bpf_raw_tracepoint_open(const char *name, int prog_fd); +LIBBPF_API int bpf_load_btf(void *btf, __u32 btf_size, char *log_buf, + __u32 log_buf_size, bool do_log); +LIBBPF_API int bpf_task_fd_query(int pid, int fd, __u32 flags, char *buf, + __u32 *buf_len, __u32 *prog_id, __u32 *fd_type, + __u64 *probe_offset, __u64 *probe_addr); + +enum bpf_stats_type; /* defined in up-to-date linux/bpf.h */ +LIBBPF_API int bpf_enable_stats(enum bpf_stats_type type); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __LIBBPF_BPF_H */ diff --git a/src/bpfswitch/include/bpf/bpf_core_read.h b/src/bpfswitch/include/bpf/bpf_core_read.h new file mode 100644 index 00000000..eae5cccf --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf_core_read.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BPF_CORE_READ_H__ +#define __BPF_CORE_READ_H__ + +/* + * enum bpf_field_info_kind is passed as a second argument into + * __builtin_preserve_field_info() built-in to get a specific aspect of + * a field, captured as a first argument. __builtin_preserve_field_info(field, + * info_kind) returns __u32 integer and produces BTF field relocation, which + * is understood and processed by libbpf during BPF object loading. See + * selftests/bpf for examples. + */ +enum bpf_field_info_kind { + BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */ + BPF_FIELD_BYTE_SIZE = 1, + BPF_FIELD_EXISTS = 2, /* field existence in target kernel */ + BPF_FIELD_SIGNED = 3, + BPF_FIELD_LSHIFT_U64 = 4, + BPF_FIELD_RSHIFT_U64 = 5, +}; + +#define __CORE_RELO(src, field, info) \ + __builtin_preserve_field_info((src)->field, BPF_FIELD_##info) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \ + bpf_probe_read((void *)dst, \ + __CORE_RELO(src, fld, BYTE_SIZE), \ + (const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET)) +#else +/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so + * for big-endian we need to adjust destination pointer accordingly, based on + * field byte size + */ +#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \ + bpf_probe_read((void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \ + __CORE_RELO(src, fld, BYTE_SIZE), \ + (const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET)) +#endif + +/* + * Extract bitfield, identified by s->field, and return its value as u64. + * All this is done in relocatable manner, so bitfield changes such as + * signedness, bit size, offset changes, this will be handled automatically. + * This version of macro is using bpf_probe_read() to read underlying integer + * storage. Macro functions as an expression and its return type is + * bpf_probe_read()'s return value: 0, on success, <0 on error. + */ +#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({ \ + unsigned long long val = 0; \ + \ + __CORE_BITFIELD_PROBE_READ(&val, s, field); \ + val <<= __CORE_RELO(s, field, LSHIFT_U64); \ + if (__CORE_RELO(s, field, SIGNED)) \ + val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \ + else \ + val = val >> __CORE_RELO(s, field, RSHIFT_U64); \ + val; \ +}) + +/* + * Extract bitfield, identified by s->field, and return its value as u64. + * This version of macro is using direct memory reads and should be used from + * BPF program types that support such functionality (e.g., typed raw + * tracepoints). + */ +#define BPF_CORE_READ_BITFIELD(s, field) ({ \ + const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \ + unsigned long long val; \ + \ + switch (__CORE_RELO(s, field, BYTE_SIZE)) { \ + case 1: val = *(const unsigned char *)p; \ + case 2: val = *(const unsigned short *)p; \ + case 4: val = *(const unsigned int *)p; \ + case 8: val = *(const unsigned long long *)p; \ + } \ + val <<= __CORE_RELO(s, field, LSHIFT_U64); \ + if (__CORE_RELO(s, field, SIGNED)) \ + val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \ + else \ + val = val >> __CORE_RELO(s, field, RSHIFT_U64); \ + val; \ +}) + +/* + * Convenience macro to check that field actually exists in target kernel's. + * Returns: + * 1, if matching field is present in target kernel; + * 0, if no matching field found. + */ +#define bpf_core_field_exists(field) \ + __builtin_preserve_field_info(field, BPF_FIELD_EXISTS) + +/* + * Convenience macro to get byte size of a field. Works for integers, + * struct/unions, pointers, arrays, and enums. + */ +#define bpf_core_field_size(field) \ + __builtin_preserve_field_info(field, BPF_FIELD_BYTE_SIZE) + +/* + * bpf_core_read() abstracts away bpf_probe_read() call and captures offset + * relocation for source address using __builtin_preserve_access_index() + * built-in, provided by Clang. + * + * __builtin_preserve_access_index() takes as an argument an expression of + * taking an address of a field within struct/union. It makes compiler emit + * a relocation, which records BTF type ID describing root struct/union and an + * accessor string which describes exact embedded field that was used to take + * an address. See detailed description of this relocation format and + * semantics in comments to struct bpf_field_reloc in libbpf_internal.h. + * + * This relocation allows libbpf to adjust BPF instruction to use correct + * actual field offset, based on target kernel BTF type that matches original + * (local) BTF, used to record relocation. + */ +#define bpf_core_read(dst, sz, src) \ + bpf_probe_read(dst, sz, \ + (const void *)__builtin_preserve_access_index(src)) + +/* + * bpf_core_read_str() is a thin wrapper around bpf_probe_read_str() + * additionally emitting BPF CO-RE field relocation for specified source + * argument. + */ +#define bpf_core_read_str(dst, sz, src) \ + bpf_probe_read_str(dst, sz, \ + (const void *)__builtin_preserve_access_index(src)) + +#define ___concat(a, b) a ## b +#define ___apply(fn, n) ___concat(fn, n) +#define ___nth(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __11, N, ...) N + +/* + * return number of provided arguments; used for switch-based variadic macro + * definitions (see ___last, ___arrow, etc below) + */ +#define ___narg(...) ___nth(_, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +/* + * return 0 if no arguments are passed, N - otherwise; used for + * recursively-defined macros to specify termination (0) case, and generic + * (N) case (e.g., ___read_ptrs, ___core_read) + */ +#define ___empty(...) ___nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0) + +#define ___last1(x) x +#define ___last2(a, x) x +#define ___last3(a, b, x) x +#define ___last4(a, b, c, x) x +#define ___last5(a, b, c, d, x) x +#define ___last6(a, b, c, d, e, x) x +#define ___last7(a, b, c, d, e, f, x) x +#define ___last8(a, b, c, d, e, f, g, x) x +#define ___last9(a, b, c, d, e, f, g, h, x) x +#define ___last10(a, b, c, d, e, f, g, h, i, x) x +#define ___last(...) ___apply(___last, ___narg(__VA_ARGS__))(__VA_ARGS__) + +#define ___nolast2(a, _) a +#define ___nolast3(a, b, _) a, b +#define ___nolast4(a, b, c, _) a, b, c +#define ___nolast5(a, b, c, d, _) a, b, c, d +#define ___nolast6(a, b, c, d, e, _) a, b, c, d, e +#define ___nolast7(a, b, c, d, e, f, _) a, b, c, d, e, f +#define ___nolast8(a, b, c, d, e, f, g, _) a, b, c, d, e, f, g +#define ___nolast9(a, b, c, d, e, f, g, h, _) a, b, c, d, e, f, g, h +#define ___nolast10(a, b, c, d, e, f, g, h, i, _) a, b, c, d, e, f, g, h, i +#define ___nolast(...) ___apply(___nolast, ___narg(__VA_ARGS__))(__VA_ARGS__) + +#define ___arrow1(a) a +#define ___arrow2(a, b) a->b +#define ___arrow3(a, b, c) a->b->c +#define ___arrow4(a, b, c, d) a->b->c->d +#define ___arrow5(a, b, c, d, e) a->b->c->d->e +#define ___arrow6(a, b, c, d, e, f) a->b->c->d->e->f +#define ___arrow7(a, b, c, d, e, f, g) a->b->c->d->e->f->g +#define ___arrow8(a, b, c, d, e, f, g, h) a->b->c->d->e->f->g->h +#define ___arrow9(a, b, c, d, e, f, g, h, i) a->b->c->d->e->f->g->h->i +#define ___arrow10(a, b, c, d, e, f, g, h, i, j) a->b->c->d->e->f->g->h->i->j +#define ___arrow(...) ___apply(___arrow, ___narg(__VA_ARGS__))(__VA_ARGS__) + +#define ___type(...) typeof(___arrow(__VA_ARGS__)) + +#define ___read(read_fn, dst, src_type, src, accessor) \ + read_fn((void *)(dst), sizeof(*(dst)), &((src_type)(src))->accessor) + +/* "recursively" read a sequence of inner pointers using local __t var */ +#define ___rd_first(src, a) ___read(bpf_core_read, &__t, ___type(src), src, a); +#define ___rd_last(...) \ + ___read(bpf_core_read, &__t, \ + ___type(___nolast(__VA_ARGS__)), __t, ___last(__VA_ARGS__)); +#define ___rd_p1(...) const void *__t; ___rd_first(__VA_ARGS__) +#define ___rd_p2(...) ___rd_p1(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p3(...) ___rd_p2(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p4(...) ___rd_p3(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p5(...) ___rd_p4(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p6(...) ___rd_p5(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p7(...) ___rd_p6(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p8(...) ___rd_p7(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___rd_p9(...) ___rd_p8(___nolast(__VA_ARGS__)) ___rd_last(__VA_ARGS__) +#define ___read_ptrs(src, ...) \ + ___apply(___rd_p, ___narg(__VA_ARGS__))(src, __VA_ARGS__) + +#define ___core_read0(fn, dst, src, a) \ + ___read(fn, dst, ___type(src), src, a); +#define ___core_readN(fn, dst, src, ...) \ + ___read_ptrs(src, ___nolast(__VA_ARGS__)) \ + ___read(fn, dst, ___type(src, ___nolast(__VA_ARGS__)), __t, \ + ___last(__VA_ARGS__)); +#define ___core_read(fn, dst, src, a, ...) \ + ___apply(___core_read, ___empty(__VA_ARGS__))(fn, dst, \ + src, a, ##__VA_ARGS__) + +/* + * BPF_CORE_READ_INTO() is a more performance-conscious variant of + * BPF_CORE_READ(), in which final field is read into user-provided storage. + * See BPF_CORE_READ() below for more details on general usage. + */ +#define BPF_CORE_READ_INTO(dst, src, a, ...) \ + ({ \ + ___core_read(bpf_core_read, dst, (src), a, ##__VA_ARGS__) \ + }) + +/* + * BPF_CORE_READ_STR_INTO() does same "pointer chasing" as + * BPF_CORE_READ() for intermediate pointers, but then executes (and returns + * corresponding error code) bpf_core_read_str() for final string read. + */ +#define BPF_CORE_READ_STR_INTO(dst, src, a, ...) \ + ({ \ + ___core_read(bpf_core_read_str, dst, (src), a, ##__VA_ARGS__)\ + }) + +/* + * BPF_CORE_READ() is used to simplify BPF CO-RE relocatable read, especially + * when there are few pointer chasing steps. + * E.g., what in non-BPF world (or in BPF w/ BCC) would be something like: + * int x = s->a.b.c->d.e->f->g; + * can be succinctly achieved using BPF_CORE_READ as: + * int x = BPF_CORE_READ(s, a.b.c, d.e, f, g); + * + * BPF_CORE_READ will decompose above statement into 4 bpf_core_read (BPF + * CO-RE relocatable bpf_probe_read() wrapper) calls, logically equivalent to: + * 1. const void *__t = s->a.b.c; + * 2. __t = __t->d.e; + * 3. __t = __t->f; + * 4. return __t->g; + * + * Equivalence is logical, because there is a heavy type casting/preservation + * involved, as well as all the reads are happening through bpf_probe_read() + * calls using __builtin_preserve_access_index() to emit CO-RE relocations. + * + * N.B. Only up to 9 "field accessors" are supported, which should be more + * than enough for any practical purpose. + */ +#define BPF_CORE_READ(src, a, ...) \ + ({ \ + ___type((src), a, ##__VA_ARGS__) __r; \ + BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \ + __r; \ + }) + +#endif + diff --git a/src/bpfswitch/include/bpf/bpf_endian.h b/src/bpfswitch/include/bpf/bpf_endian.h new file mode 100644 index 00000000..ec9db4fe --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf_endian.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BPF_ENDIAN__ +#define __BPF_ENDIAN__ + +/* + * Isolate byte #n and put it into byte #m, for __u##b type. + * E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64: + * 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx + * 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000 + * 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn + * 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000 + */ +#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8)) + +#define ___bpf_swab16(x) ((__u16)( \ + ___bpf_mvb(x, 16, 0, 1) | \ + ___bpf_mvb(x, 16, 1, 0))) + +#define ___bpf_swab32(x) ((__u32)( \ + ___bpf_mvb(x, 32, 0, 3) | \ + ___bpf_mvb(x, 32, 1, 2) | \ + ___bpf_mvb(x, 32, 2, 1) | \ + ___bpf_mvb(x, 32, 3, 0))) + +#define ___bpf_swab64(x) ((__u64)( \ + ___bpf_mvb(x, 64, 0, 7) | \ + ___bpf_mvb(x, 64, 1, 6) | \ + ___bpf_mvb(x, 64, 2, 5) | \ + ___bpf_mvb(x, 64, 3, 4) | \ + ___bpf_mvb(x, 64, 4, 3) | \ + ___bpf_mvb(x, 64, 5, 2) | \ + ___bpf_mvb(x, 64, 6, 1) | \ + ___bpf_mvb(x, 64, 7, 0))) + +/* LLVM's BPF target selects the endianness of the CPU + * it compiles on, or the user specifies (bpfel/bpfeb), + * respectively. The used __BYTE_ORDER__ is defined by + * the compiler, we cannot rely on __BYTE_ORDER from + * libc headers, since it doesn't reflect the actual + * requested byte order. + * + * Note, LLVM's BPF target has different __builtin_bswapX() + * semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE + * in bpfel and bpfeb case, which means below, that we map + * to cpu_to_be16(). We could use it unconditionally in BPF + * case, but better not rely on it, so that this header here + * can be used from application and BPF program side, which + * use different targets. + */ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define __bpf_ntohs(x) __builtin_bswap16(x) +# define __bpf_htons(x) __builtin_bswap16(x) +# define __bpf_constant_ntohs(x) ___bpf_swab16(x) +# define __bpf_constant_htons(x) ___bpf_swab16(x) +# define __bpf_ntohl(x) __builtin_bswap32(x) +# define __bpf_htonl(x) __builtin_bswap32(x) +# define __bpf_constant_ntohl(x) ___bpf_swab32(x) +# define __bpf_constant_htonl(x) ___bpf_swab32(x) +# define __bpf_be64_to_cpu(x) __builtin_bswap64(x) +# define __bpf_cpu_to_be64(x) __builtin_bswap64(x) +# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x) +# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define __bpf_ntohs(x) (x) +# define __bpf_htons(x) (x) +# define __bpf_constant_ntohs(x) (x) +# define __bpf_constant_htons(x) (x) +# define __bpf_ntohl(x) (x) +# define __bpf_htonl(x) (x) +# define __bpf_constant_ntohl(x) (x) +# define __bpf_constant_htonl(x) (x) +# define __bpf_be64_to_cpu(x) (x) +# define __bpf_cpu_to_be64(x) (x) +# define __bpf_constant_be64_to_cpu(x) (x) +# define __bpf_constant_cpu_to_be64(x) (x) +#else +# error "Fix your compiler's __BYTE_ORDER__?!" +#endif + +#define bpf_htons(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_htons(x) : __bpf_htons(x)) +#define bpf_ntohs(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_ntohs(x) : __bpf_ntohs(x)) +#define bpf_htonl(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_htonl(x) : __bpf_htonl(x)) +#define bpf_ntohl(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_ntohl(x) : __bpf_ntohl(x)) +#define bpf_cpu_to_be64(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x)) +#define bpf_be64_to_cpu(x) \ + (__builtin_constant_p(x) ? \ + __bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x)) + +#endif /* __BPF_ENDIAN__ */ diff --git a/src/bpfswitch/include/bpf/bpf_helper_defs.h b/src/bpfswitch/include/bpf/bpf_helper_defs.h new file mode 100644 index 00000000..1c5298d2 --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf_helper_defs.h @@ -0,0 +1,3260 @@ +/* This is auto-generated file. See bpf_helpers_doc.py for details. */ + +/* Forward declarations of BPF structs */ +struct bpf_fib_lookup; +struct bpf_perf_event_data; +struct bpf_perf_event_value; +struct bpf_pidns_info; +struct bpf_sock; +struct bpf_sock_addr; +struct bpf_sock_ops; +struct bpf_sock_tuple; +struct bpf_spin_lock; +struct bpf_sysctl; +struct bpf_tcp_sock; +struct bpf_tunnel_key; +struct bpf_xfrm_state; +struct pt_regs; +struct sk_reuseport_md; +struct sockaddr; +struct tcphdr; +struct seq_file; +struct tcp6_sock; +struct tcp_sock; +struct tcp_timewait_sock; +struct tcp_request_sock; +struct udp6_sock; +struct task_struct; +struct __sk_buff; +struct sk_msg_md; +struct xdp_md; + +/* + * bpf_map_lookup_elem + * + * Perform a lookup in *map* for an entry associated to *key*. + * + * Returns + * Map value associated to *key*, or **NULL** if no entry was + * found. + */ +static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1; + +/* + * bpf_map_update_elem + * + * Add or update the value of the entry associated to *key* in + * *map* with *value*. *flags* is one of: + * + * **BPF_NOEXIST** + * The entry for *key* must not exist in the map. + * **BPF_EXIST** + * The entry for *key* must already exist in the map. + * **BPF_ANY** + * No condition on the existence of the entry for *key*. + * + * Flag value **BPF_NOEXIST** cannot be used for maps of types + * **BPF_MAP_TYPE_ARRAY** or **BPF_MAP_TYPE_PERCPU_ARRAY** (all + * elements always exist), the helper would return an error. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2; + +/* + * bpf_map_delete_elem + * + * Delete entry with *key* from *map*. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_map_delete_elem)(void *map, const void *key) = (void *) 3; + +/* + * bpf_probe_read + * + * For tracing programs, safely attempt to read *size* bytes from + * kernel space address *unsafe_ptr* and store the data in *dst*. + * + * Generally, use **bpf_probe_read_user**\ () or + * **bpf_probe_read_kernel**\ () instead. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_probe_read)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 4; + +/* + * bpf_ktime_get_ns + * + * Return the time elapsed since system boot, in nanoseconds. + * Does not include time the system was suspended. + * See: **clock_gettime**\ (**CLOCK_MONOTONIC**) + * + * Returns + * Current *ktime*. + */ +static __u64 (*bpf_ktime_get_ns)(void) = (void *) 5; + +/* + * bpf_trace_printk + * + * This helper is a "printk()-like" facility for debugging. It + * prints a message defined by format *fmt* (of size *fmt_size*) + * to file *\/sys/kernel/debug/tracing/trace* from DebugFS, if + * available. It can take up to three additional **u64** + * arguments (as an eBPF helpers, the total number of arguments is + * limited to five). + * + * Each time the helper is called, it appends a line to the trace. + * Lines are discarded while *\/sys/kernel/debug/tracing/trace* is + * open, use *\/sys/kernel/debug/tracing/trace_pipe* to avoid this. + * The format of the trace is customizable, and the exact output + * one will get depends on the options set in + * *\/sys/kernel/debug/tracing/trace_options* (see also the + * *README* file under the same directory). However, it usually + * defaults to something like: + * + * :: + * + * telnet-470 [001] .N.. 419421.045894: 0x00000001: + * + * In the above: + * + * * ``telnet`` is the name of the current task. + * * ``470`` is the PID of the current task. + * * ``001`` is the CPU number on which the task is + * running. + * * In ``.N..``, each character refers to a set of + * options (whether irqs are enabled, scheduling + * options, whether hard/softirqs are running, level of + * preempt_disabled respectively). **N** means that + * **TIF_NEED_RESCHED** and **PREEMPT_NEED_RESCHED** + * are set. + * * ``419421.045894`` is a timestamp. + * * ``0x00000001`` is a fake value used by BPF for the + * instruction pointer register. + * * ```` is the message formatted with + * *fmt*. + * + * The conversion specifiers supported by *fmt* are similar, but + * more limited than for printk(). They are **%d**, **%i**, + * **%u**, **%x**, **%ld**, **%li**, **%lu**, **%lx**, **%lld**, + * **%lli**, **%llu**, **%llx**, **%p**, **%s**. No modifier (size + * of field, padding with zeroes, etc.) is available, and the + * helper will return **-EINVAL** (but print nothing) if it + * encounters an unknown specifier. + * + * Also, note that **bpf_trace_printk**\ () is slow, and should + * only be used for debugging purposes. For this reason, a notice + * bloc (spanning several lines) is printed to kernel logs and + * states that the helper should not be used "for production use" + * the first time this helper is used (or more precisely, when + * **trace_printk**\ () buffers are allocated). For passing values + * to user space, perf events should be preferred. + * + * Returns + * The number of bytes written to the buffer, or a negative error + * in case of failure. + */ +static long (*bpf_trace_printk)(const char *fmt, __u32 fmt_size, ...) = (void *) 6; + +/* + * bpf_get_prandom_u32 + * + * Get a pseudo-random number. + * + * From a security point of view, this helper uses its own + * pseudo-random internal state, and cannot be used to infer the + * seed of other random functions in the kernel. However, it is + * essential to note that the generator used by the helper is not + * cryptographically secure. + * + * Returns + * A random 32-bit unsigned value. + */ +static __u32 (*bpf_get_prandom_u32)(void) = (void *) 7; + +/* + * bpf_get_smp_processor_id + * + * Get the SMP (symmetric multiprocessing) processor id. Note that + * all programs run with preemption disabled, which means that the + * SMP processor id is stable during all the execution of the + * program. + * + * Returns + * The SMP id of the processor running the program. + */ +static __u32 (*bpf_get_smp_processor_id)(void) = (void *) 8; + +/* + * bpf_skb_store_bytes + * + * Store *len* bytes from address *from* into the packet + * associated to *skb*, at *offset*. *flags* are a combination of + * **BPF_F_RECOMPUTE_CSUM** (automatically recompute the + * checksum for the packet after storing the bytes) and + * **BPF_F_INVALIDATE_HASH** (set *skb*\ **->hash**, *skb*\ + * **->swhash** and *skb*\ **->l4hash** to 0). + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_store_bytes)(struct __sk_buff *skb, __u32 offset, const void *from, __u32 len, __u64 flags) = (void *) 9; + +/* + * bpf_l3_csum_replace + * + * Recompute the layer 3 (e.g. IP) checksum for the packet + * associated to *skb*. Computation is incremental, so the helper + * must know the former value of the header field that was + * modified (*from*), the new value of this field (*to*), and the + * number of bytes (2 or 4) for this field, stored in *size*. + * Alternatively, it is possible to store the difference between + * the previous and the new values of the header field in *to*, by + * setting *from* and *size* to 0. For both methods, *offset* + * indicates the location of the IP checksum within the packet. + * + * This helper works in combination with **bpf_csum_diff**\ (), + * which does not update the checksum in-place, but offers more + * flexibility and can handle sizes larger than 2 or 4 for the + * checksum to update. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_l3_csum_replace)(struct __sk_buff *skb, __u32 offset, __u64 from, __u64 to, __u64 size) = (void *) 10; + +/* + * bpf_l4_csum_replace + * + * Recompute the layer 4 (e.g. TCP, UDP or ICMP) checksum for the + * packet associated to *skb*. Computation is incremental, so the + * helper must know the former value of the header field that was + * modified (*from*), the new value of this field (*to*), and the + * number of bytes (2 or 4) for this field, stored on the lowest + * four bits of *flags*. Alternatively, it is possible to store + * the difference between the previous and the new values of the + * header field in *to*, by setting *from* and the four lowest + * bits of *flags* to 0. For both methods, *offset* indicates the + * location of the IP checksum within the packet. In addition to + * the size of the field, *flags* can be added (bitwise OR) actual + * flags. With **BPF_F_MARK_MANGLED_0**, a null checksum is left + * untouched (unless **BPF_F_MARK_ENFORCE** is added as well), and + * for updates resulting in a null checksum the value is set to + * **CSUM_MANGLED_0** instead. Flag **BPF_F_PSEUDO_HDR** indicates + * the checksum is to be computed against a pseudo-header. + * + * This helper works in combination with **bpf_csum_diff**\ (), + * which does not update the checksum in-place, but offers more + * flexibility and can handle sizes larger than 2 or 4 for the + * checksum to update. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_l4_csum_replace)(struct __sk_buff *skb, __u32 offset, __u64 from, __u64 to, __u64 flags) = (void *) 11; + +/* + * bpf_tail_call + * + * This special helper is used to trigger a "tail call", or in + * other words, to jump into another eBPF program. The same stack + * frame is used (but values on stack and in registers for the + * caller are not accessible to the callee). This mechanism allows + * for program chaining, either for raising the maximum number of + * available eBPF instructions, or to execute given programs in + * conditional blocks. For security reasons, there is an upper + * limit to the number of successive tail calls that can be + * performed. + * + * Upon call of this helper, the program attempts to jump into a + * program referenced at index *index* in *prog_array_map*, a + * special map of type **BPF_MAP_TYPE_PROG_ARRAY**, and passes + * *ctx*, a pointer to the context. + * + * If the call succeeds, the kernel immediately runs the first + * instruction of the new program. This is not a function call, + * and it never returns to the previous program. If the call + * fails, then the helper has no effect, and the caller continues + * to run its subsequent instructions. A call can fail if the + * destination program for the jump does not exist (i.e. *index* + * is superior to the number of entries in *prog_array_map*), or + * if the maximum number of tail calls has been reached for this + * chain of programs. This limit is defined in the kernel by the + * macro **MAX_TAIL_CALL_CNT** (not accessible to user space), + * which is currently set to 32. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12; + +/* + * bpf_clone_redirect + * + * Clone and redirect the packet associated to *skb* to another + * net device of index *ifindex*. Both ingress and egress + * interfaces can be used for redirection. The **BPF_F_INGRESS** + * value in *flags* is used to make the distinction (ingress path + * is selected if the flag is present, egress path otherwise). + * This is the only flag supported for now. + * + * In comparison with **bpf_redirect**\ () helper, + * **bpf_clone_redirect**\ () has the associated cost of + * duplicating the packet buffer, but this can be executed out of + * the eBPF program. Conversely, **bpf_redirect**\ () is more + * efficient, but it is handled through an action code where the + * redirection happens only after the eBPF program has returned. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13; + +/* + * bpf_get_current_pid_tgid + * + * + * Returns + * A 64-bit integer containing the current tgid and pid, and + * created as such: + * *current_task*\ **->tgid << 32 \|** + * *current_task*\ **->pid**. + */ +static __u64 (*bpf_get_current_pid_tgid)(void) = (void *) 14; + +/* + * bpf_get_current_uid_gid + * + * + * Returns + * A 64-bit integer containing the current GID and UID, and + * created as such: *current_gid* **<< 32 \|** *current_uid*. + */ +static __u64 (*bpf_get_current_uid_gid)(void) = (void *) 15; + +/* + * bpf_get_current_comm + * + * Copy the **comm** attribute of the current task into *buf* of + * *size_of_buf*. The **comm** attribute contains the name of + * the executable (excluding the path) for the current task. The + * *size_of_buf* must be strictly positive. On success, the + * helper makes sure that the *buf* is NUL-terminated. On failure, + * it is filled with zeroes. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_get_current_comm)(void *buf, __u32 size_of_buf) = (void *) 16; + +/* + * bpf_get_cgroup_classid + * + * Retrieve the classid for the current task, i.e. for the net_cls + * cgroup to which *skb* belongs. + * + * This helper can be used on TC egress path, but not on ingress. + * + * The net_cls cgroup provides an interface to tag network packets + * based on a user-provided identifier for all traffic coming from + * the tasks belonging to the related cgroup. See also the related + * kernel documentation, available from the Linux sources in file + * *Documentation/admin-guide/cgroup-v1/net_cls.rst*. + * + * The Linux kernel has two versions for cgroups: there are + * cgroups v1 and cgroups v2. Both are available to users, who can + * use a mixture of them, but note that the net_cls cgroup is for + * cgroup v1 only. This makes it incompatible with BPF programs + * run on cgroups, which is a cgroup-v2-only feature (a socket can + * only hold data for one version of cgroups at a time). + * + * This helper is only available is the kernel was compiled with + * the **CONFIG_CGROUP_NET_CLASSID** configuration option set to + * "**y**" or to "**m**". + * + * Returns + * The classid, or 0 for the default unconfigured classid. + */ +static __u32 (*bpf_get_cgroup_classid)(struct __sk_buff *skb) = (void *) 17; + +/* + * bpf_skb_vlan_push + * + * Push a *vlan_tci* (VLAN tag control information) of protocol + * *vlan_proto* to the packet associated to *skb*, then update + * the checksum. Note that if *vlan_proto* is different from + * **ETH_P_8021Q** and **ETH_P_8021AD**, it is considered to + * be **ETH_P_8021Q**. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_vlan_push)(struct __sk_buff *skb, __be16 vlan_proto, __u16 vlan_tci) = (void *) 18; + +/* + * bpf_skb_vlan_pop + * + * Pop a VLAN header from the packet associated to *skb*. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_vlan_pop)(struct __sk_buff *skb) = (void *) 19; + +/* + * bpf_skb_get_tunnel_key + * + * Get tunnel metadata. This helper takes a pointer *key* to an + * empty **struct bpf_tunnel_key** of **size**, that will be + * filled with tunnel metadata for the packet associated to *skb*. + * The *flags* can be set to **BPF_F_TUNINFO_IPV6**, which + * indicates that the tunnel is based on IPv6 protocol instead of + * IPv4. + * + * The **struct bpf_tunnel_key** is an object that generalizes the + * principal parameters used by various tunneling protocols into a + * single struct. This way, it can be used to easily make a + * decision based on the contents of the encapsulation header, + * "summarized" in this struct. In particular, it holds the IP + * address of the remote end (IPv4 or IPv6, depending on the case) + * in *key*\ **->remote_ipv4** or *key*\ **->remote_ipv6**. Also, + * this struct exposes the *key*\ **->tunnel_id**, which is + * generally mapped to a VNI (Virtual Network Identifier), making + * it programmable together with the **bpf_skb_set_tunnel_key**\ + * () helper. + * + * Let's imagine that the following code is part of a program + * attached to the TC ingress interface, on one end of a GRE + * tunnel, and is supposed to filter out all messages coming from + * remote ends with IPv4 address other than 10.0.0.1: + * + * :: + * + * int ret; + * struct bpf_tunnel_key key = {}; + * + * ret = bpf_skb_get_tunnel_key(skb, &key, sizeof(key), 0); + * if (ret < 0) + * return TC_ACT_SHOT; // drop packet + * + * if (key.remote_ipv4 != 0x0a000001) + * return TC_ACT_SHOT; // drop packet + * + * return TC_ACT_OK; // accept packet + * + * This interface can also be used with all encapsulation devices + * that can operate in "collect metadata" mode: instead of having + * one network device per specific configuration, the "collect + * metadata" mode only requires a single device where the + * configuration can be extracted from this helper. + * + * This can be used together with various tunnels such as VXLan, + * Geneve, GRE or IP in IP (IPIP). + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_get_tunnel_key)(struct __sk_buff *skb, struct bpf_tunnel_key *key, __u32 size, __u64 flags) = (void *) 20; + +/* + * bpf_skb_set_tunnel_key + * + * Populate tunnel metadata for packet associated to *skb.* The + * tunnel metadata is set to the contents of *key*, of *size*. The + * *flags* can be set to a combination of the following values: + * + * **BPF_F_TUNINFO_IPV6** + * Indicate that the tunnel is based on IPv6 protocol + * instead of IPv4. + * **BPF_F_ZERO_CSUM_TX** + * For IPv4 packets, add a flag to tunnel metadata + * indicating that checksum computation should be skipped + * and checksum set to zeroes. + * **BPF_F_DONT_FRAGMENT** + * Add a flag to tunnel metadata indicating that the + * packet should not be fragmented. + * **BPF_F_SEQ_NUMBER** + * Add a flag to tunnel metadata indicating that a + * sequence number should be added to tunnel header before + * sending the packet. This flag was added for GRE + * encapsulation, but might be used with other protocols + * as well in the future. + * + * Here is a typical usage on the transmit path: + * + * :: + * + * struct bpf_tunnel_key key; + * populate key ... + * bpf_skb_set_tunnel_key(skb, &key, sizeof(key), 0); + * bpf_clone_redirect(skb, vxlan_dev_ifindex, 0); + * + * See also the description of the **bpf_skb_get_tunnel_key**\ () + * helper for additional information. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_set_tunnel_key)(struct __sk_buff *skb, struct bpf_tunnel_key *key, __u32 size, __u64 flags) = (void *) 21; + +/* + * bpf_perf_event_read + * + * Read the value of a perf event counter. This helper relies on a + * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of + * the perf event counter is selected when *map* is updated with + * perf event file descriptors. The *map* is an array whose size + * is the number of available CPUs, and each cell contains a value + * relative to one CPU. The value to retrieve is indicated by + * *flags*, that contains the index of the CPU to look up, masked + * with **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to + * **BPF_F_CURRENT_CPU** to indicate that the value for the + * current CPU should be retrieved. + * + * Note that before Linux 4.13, only hardware perf event can be + * retrieved. + * + * Also, be aware that the newer helper + * **bpf_perf_event_read_value**\ () is recommended over + * **bpf_perf_event_read**\ () in general. The latter has some ABI + * quirks where error and counter value are used as a return code + * (which is wrong to do since ranges may overlap). This issue is + * fixed with **bpf_perf_event_read_value**\ (), which at the same + * time provides more features over the **bpf_perf_event_read**\ + * () interface. Please refer to the description of + * **bpf_perf_event_read_value**\ () for details. + * + * Returns + * The value of the perf event counter read from the map, or a + * negative error code in case of failure. + */ +static __u64 (*bpf_perf_event_read)(void *map, __u64 flags) = (void *) 22; + +/* + * bpf_redirect + * + * Redirect the packet to another net device of index *ifindex*. + * This helper is somewhat similar to **bpf_clone_redirect**\ + * (), except that the packet is not cloned, which provides + * increased performance. + * + * Except for XDP, both ingress and egress interfaces can be used + * for redirection. The **BPF_F_INGRESS** value in *flags* is used + * to make the distinction (ingress path is selected if the flag + * is present, egress path otherwise). Currently, XDP only + * supports redirection to the egress interface, and accepts no + * flag at all. + * + * The same effect can also be attained with the more generic + * **bpf_redirect_map**\ (), which uses a BPF map to store the + * redirect target instead of providing it directly to the helper. + * + * Returns + * For XDP, the helper returns **XDP_REDIRECT** on success or + * **XDP_ABORTED** on error. For other program types, the values + * are **TC_ACT_REDIRECT** on success or **TC_ACT_SHOT** on + * error. + */ +static long (*bpf_redirect)(__u32 ifindex, __u64 flags) = (void *) 23; + +/* + * bpf_get_route_realm + * + * Retrieve the realm or the route, that is to say the + * **tclassid** field of the destination for the *skb*. The + * indentifier retrieved is a user-provided tag, similar to the + * one used with the net_cls cgroup (see description for + * **bpf_get_cgroup_classid**\ () helper), but here this tag is + * held by a route (a destination entry), not by a task. + * + * Retrieving this identifier works with the clsact TC egress hook + * (see also **tc-bpf(8)**), or alternatively on conventional + * classful egress qdiscs, but not on TC ingress path. In case of + * clsact TC egress hook, this has the advantage that, internally, + * the destination entry has not been dropped yet in the transmit + * path. Therefore, the destination entry does not need to be + * artificially held via **netif_keep_dst**\ () for a classful + * qdisc until the *skb* is freed. + * + * This helper is available only if the kernel was compiled with + * **CONFIG_IP_ROUTE_CLASSID** configuration option. + * + * Returns + * The realm of the route for the packet associated to *skb*, or 0 + * if none was found. + */ +static __u32 (*bpf_get_route_realm)(struct __sk_buff *skb) = (void *) 24; + +/* + * bpf_perf_event_output + * + * Write raw *data* blob into a special BPF perf event held by + * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf + * event must have the following attributes: **PERF_SAMPLE_RAW** + * as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and + * **PERF_COUNT_SW_BPF_OUTPUT** as **config**. + * + * The *flags* are used to indicate the index in *map* for which + * the value must be put, masked with **BPF_F_INDEX_MASK**. + * Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU** + * to indicate that the index of the current CPU core should be + * used. + * + * The value to write, of *size*, is passed through eBPF stack and + * pointed by *data*. + * + * The context of the program *ctx* needs also be passed to the + * helper. + * + * On user space, a program willing to read the values needs to + * call **perf_event_open**\ () on the perf event (either for + * one or for all CPUs) and to store the file descriptor into the + * *map*. This must be done before the eBPF program can send data + * into it. An example is available in file + * *samples/bpf/trace_output_user.c* in the Linux kernel source + * tree (the eBPF program counterpart is in + * *samples/bpf/trace_output_kern.c*). + * + * **bpf_perf_event_output**\ () achieves better performance + * than **bpf_trace_printk**\ () for sharing data with user + * space, and is much better suitable for streaming data from eBPF + * programs. + * + * Note that this helper is not restricted to tracing use cases + * and can be used with programs attached to TC or XDP as well, + * where it allows for passing data to user space listeners. Data + * can be: + * + * * Only custom structs, + * * Only the packet payload, or + * * A combination of both. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_perf_event_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 25; + +/* + * bpf_skb_load_bytes + * + * This helper was provided as an easy way to load data from a + * packet. It can be used to load *len* bytes from *offset* from + * the packet associated to *skb*, into the buffer pointed by + * *to*. + * + * Since Linux 4.7, usage of this helper has mostly been replaced + * by "direct packet access", enabling packet data to be + * manipulated with *skb*\ **->data** and *skb*\ **->data_end** + * pointing respectively to the first byte of packet data and to + * the byte after the last byte of packet data. However, it + * remains useful if one wishes to read large quantities of data + * at once from a packet into the eBPF stack. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_load_bytes)(const void *skb, __u32 offset, void *to, __u32 len) = (void *) 26; + +/* + * bpf_get_stackid + * + * Walk a user or a kernel stack and return its id. To achieve + * this, the helper needs *ctx*, which is a pointer to the context + * on which the tracing program is executed, and a pointer to a + * *map* of type **BPF_MAP_TYPE_STACK_TRACE**. + * + * The last argument, *flags*, holds the number of stack frames to + * skip (from 0 to 255), masked with + * **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set + * a combination of the following flags: + * + * **BPF_F_USER_STACK** + * Collect a user space stack instead of a kernel stack. + * **BPF_F_FAST_STACK_CMP** + * Compare stacks by hash only. + * **BPF_F_REUSE_STACKID** + * If two different stacks hash into the same *stackid*, + * discard the old one. + * + * The stack id retrieved is a 32 bit long integer handle which + * can be further combined with other data (including other stack + * ids) and used as a key into maps. This can be useful for + * generating a variety of graphs (such as flame graphs or off-cpu + * graphs). + * + * For walking a stack, this helper is an improvement over + * **bpf_probe_read**\ (), which can be used with unrolled loops + * but is not efficient and consumes a lot of eBPF instructions. + * Instead, **bpf_get_stackid**\ () can collect up to + * **PERF_MAX_STACK_DEPTH** both kernel and user frames. Note that + * this limit can be controlled with the **sysctl** program, and + * that it should be manually increased in order to profile long + * user stacks (such as stacks for Java programs). To do so, use: + * + * :: + * + * # sysctl kernel.perf_event_max_stack= + * + * Returns + * The positive or null stack id on success, or a negative error + * in case of failure. + */ +static long (*bpf_get_stackid)(void *ctx, void *map, __u64 flags) = (void *) 27; + +/* + * bpf_csum_diff + * + * Compute a checksum difference, from the raw buffer pointed by + * *from*, of length *from_size* (that must be a multiple of 4), + * towards the raw buffer pointed by *to*, of size *to_size* + * (same remark). An optional *seed* can be added to the value + * (this can be cascaded, the seed may come from a previous call + * to the helper). + * + * This is flexible enough to be used in several ways: + * + * * With *from_size* == 0, *to_size* > 0 and *seed* set to + * checksum, it can be used when pushing new data. + * * With *from_size* > 0, *to_size* == 0 and *seed* set to + * checksum, it can be used when removing data from a packet. + * * With *from_size* > 0, *to_size* > 0 and *seed* set to 0, it + * can be used to compute a diff. Note that *from_size* and + * *to_size* do not need to be equal. + * + * This helper can be used in combination with + * **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ (), to + * which one can feed in the difference computed with + * **bpf_csum_diff**\ (). + * + * Returns + * The checksum result, or a negative error code in case of + * failure. + */ +static __s64 (*bpf_csum_diff)(__be32 *from, __u32 from_size, __be32 *to, __u32 to_size, __wsum seed) = (void *) 28; + +/* + * bpf_skb_get_tunnel_opt + * + * Retrieve tunnel options metadata for the packet associated to + * *skb*, and store the raw tunnel option data to the buffer *opt* + * of *size*. + * + * This helper can be used with encapsulation devices that can + * operate in "collect metadata" mode (please refer to the related + * note in the description of **bpf_skb_get_tunnel_key**\ () for + * more details). A particular example where this can be used is + * in combination with the Geneve encapsulation protocol, where it + * allows for pushing (with **bpf_skb_get_tunnel_opt**\ () helper) + * and retrieving arbitrary TLVs (Type-Length-Value headers) from + * the eBPF program. This allows for full customization of these + * headers. + * + * Returns + * The size of the option data retrieved. + */ +static long (*bpf_skb_get_tunnel_opt)(struct __sk_buff *skb, void *opt, __u32 size) = (void *) 29; + +/* + * bpf_skb_set_tunnel_opt + * + * Set tunnel options metadata for the packet associated to *skb* + * to the option data contained in the raw buffer *opt* of *size*. + * + * See also the description of the **bpf_skb_get_tunnel_opt**\ () + * helper for additional information. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_set_tunnel_opt)(struct __sk_buff *skb, void *opt, __u32 size) = (void *) 30; + +/* + * bpf_skb_change_proto + * + * Change the protocol of the *skb* to *proto*. Currently + * supported are transition from IPv4 to IPv6, and from IPv6 to + * IPv4. The helper takes care of the groundwork for the + * transition, including resizing the socket buffer. The eBPF + * program is expected to fill the new headers, if any, via + * **skb_store_bytes**\ () and to recompute the checksums with + * **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ + * (). The main case for this helper is to perform NAT64 + * operations out of an eBPF program. + * + * Internally, the GSO type is marked as dodgy so that headers are + * checked and segments are recalculated by the GSO/GRO engine. + * The size for GSO target is adapted as well. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_change_proto)(struct __sk_buff *skb, __be16 proto, __u64 flags) = (void *) 31; + +/* + * bpf_skb_change_type + * + * Change the packet type for the packet associated to *skb*. This + * comes down to setting *skb*\ **->pkt_type** to *type*, except + * the eBPF program does not have a write access to *skb*\ + * **->pkt_type** beside this helper. Using a helper here allows + * for graceful handling of errors. + * + * The major use case is to change incoming *skb*s to + * **PACKET_HOST** in a programmatic way instead of having to + * recirculate via **redirect**\ (..., **BPF_F_INGRESS**), for + * example. + * + * Note that *type* only allows certain values. At this time, they + * are: + * + * **PACKET_HOST** + * Packet is for us. + * **PACKET_BROADCAST** + * Send packet to all. + * **PACKET_MULTICAST** + * Send packet to group. + * **PACKET_OTHERHOST** + * Send packet to someone else. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_change_type)(struct __sk_buff *skb, __u32 type) = (void *) 32; + +/* + * bpf_skb_under_cgroup + * + * Check whether *skb* is a descendant of the cgroup2 held by + * *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*. + * + * Returns + * The return value depends on the result of the test, and can be: + * + * * 0, if the *skb* failed the cgroup2 descendant test. + * * 1, if the *skb* succeeded the cgroup2 descendant test. + * * A negative error code, if an error occurred. + */ +static long (*bpf_skb_under_cgroup)(struct __sk_buff *skb, void *map, __u32 index) = (void *) 33; + +/* + * bpf_get_hash_recalc + * + * Retrieve the hash of the packet, *skb*\ **->hash**. If it is + * not set, in particular if the hash was cleared due to mangling, + * recompute this hash. Later accesses to the hash can be done + * directly with *skb*\ **->hash**. + * + * Calling **bpf_set_hash_invalid**\ (), changing a packet + * prototype with **bpf_skb_change_proto**\ (), or calling + * **bpf_skb_store_bytes**\ () with the + * **BPF_F_INVALIDATE_HASH** are actions susceptible to clear + * the hash and to trigger a new computation for the next call to + * **bpf_get_hash_recalc**\ (). + * + * Returns + * The 32-bit hash. + */ +static __u32 (*bpf_get_hash_recalc)(struct __sk_buff *skb) = (void *) 34; + +/* + * bpf_get_current_task + * + * + * Returns + * A pointer to the current task struct. + */ +static __u64 (*bpf_get_current_task)(void) = (void *) 35; + +/* + * bpf_probe_write_user + * + * Attempt in a safe way to write *len* bytes from the buffer + * *src* to *dst* in memory. It only works for threads that are in + * user context, and *dst* must be a valid user space address. + * + * This helper should not be used to implement any kind of + * security mechanism because of TOC-TOU attacks, but rather to + * debug, divert, and manipulate execution of semi-cooperative + * processes. + * + * Keep in mind that this feature is meant for experiments, and it + * has a risk of crashing the system and running programs. + * Therefore, when an eBPF program using this helper is attached, + * a warning including PID and process name is printed to kernel + * logs. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_probe_write_user)(void *dst, const void *src, __u32 len) = (void *) 36; + +/* + * bpf_current_task_under_cgroup + * + * Check whether the probe is being run is the context of a given + * subset of the cgroup2 hierarchy. The cgroup2 to test is held by + * *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*. + * + * Returns + * The return value depends on the result of the test, and can be: + * + * * 0, if the *skb* task belongs to the cgroup2. + * * 1, if the *skb* task does not belong to the cgroup2. + * * A negative error code, if an error occurred. + */ +static long (*bpf_current_task_under_cgroup)(void *map, __u32 index) = (void *) 37; + +/* + * bpf_skb_change_tail + * + * Resize (trim or grow) the packet associated to *skb* to the + * new *len*. The *flags* are reserved for future usage, and must + * be left at zero. + * + * The basic idea is that the helper performs the needed work to + * change the size of the packet, then the eBPF program rewrites + * the rest via helpers like **bpf_skb_store_bytes**\ (), + * **bpf_l3_csum_replace**\ (), **bpf_l3_csum_replace**\ () + * and others. This helper is a slow path utility intended for + * replies with control messages. And because it is targeted for + * slow path, the helper itself can afford to be slow: it + * implicitly linearizes, unclones and drops offloads from the + * *skb*. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_change_tail)(struct __sk_buff *skb, __u32 len, __u64 flags) = (void *) 38; + +/* + * bpf_skb_pull_data + * + * Pull in non-linear data in case the *skb* is non-linear and not + * all of *len* are part of the linear section. Make *len* bytes + * from *skb* readable and writable. If a zero value is passed for + * *len*, then the whole length of the *skb* is pulled. + * + * This helper is only needed for reading and writing with direct + * packet access. + * + * For direct packet access, testing that offsets to access + * are within packet boundaries (test on *skb*\ **->data_end**) is + * susceptible to fail if offsets are invalid, or if the requested + * data is in non-linear parts of the *skb*. On failure the + * program can just bail out, or in the case of a non-linear + * buffer, use a helper to make the data available. The + * **bpf_skb_load_bytes**\ () helper is a first solution to access + * the data. Another one consists in using **bpf_skb_pull_data** + * to pull in once the non-linear parts, then retesting and + * eventually access the data. + * + * At the same time, this also makes sure the *skb* is uncloned, + * which is a necessary condition for direct write. As this needs + * to be an invariant for the write part only, the verifier + * detects writes and adds a prologue that is calling + * **bpf_skb_pull_data()** to effectively unclone the *skb* from + * the very beginning in case it is indeed cloned. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_pull_data)(struct __sk_buff *skb, __u32 len) = (void *) 39; + +/* + * bpf_csum_update + * + * Add the checksum *csum* into *skb*\ **->csum** in case the + * driver has supplied a checksum for the entire packet into that + * field. Return an error otherwise. This helper is intended to be + * used in combination with **bpf_csum_diff**\ (), in particular + * when the checksum needs to be updated after data has been + * written into the packet through direct packet access. + * + * Returns + * The checksum on success, or a negative error code in case of + * failure. + */ +static __s64 (*bpf_csum_update)(struct __sk_buff *skb, __wsum csum) = (void *) 40; + +/* + * bpf_set_hash_invalid + * + * Invalidate the current *skb*\ **->hash**. It can be used after + * mangling on headers through direct packet access, in order to + * indicate that the hash is outdated and to trigger a + * recalculation the next time the kernel tries to access this + * hash or when the **bpf_get_hash_recalc**\ () helper is called. + * + */ +static void (*bpf_set_hash_invalid)(struct __sk_buff *skb) = (void *) 41; + +/* + * bpf_get_numa_node_id + * + * Return the id of the current NUMA node. The primary use case + * for this helper is the selection of sockets for the local NUMA + * node, when the program is attached to sockets using the + * **SO_ATTACH_REUSEPORT_EBPF** option (see also **socket(7)**), + * but the helper is also available to other eBPF program types, + * similarly to **bpf_get_smp_processor_id**\ (). + * + * Returns + * The id of current NUMA node. + */ +static long (*bpf_get_numa_node_id)(void) = (void *) 42; + +/* + * bpf_skb_change_head + * + * Grows headroom of packet associated to *skb* and adjusts the + * offset of the MAC header accordingly, adding *len* bytes of + * space. It automatically extends and reallocates memory as + * required. + * + * This helper can be used on a layer 3 *skb* to push a MAC header + * for redirection into a layer 2 device. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_change_head)(struct __sk_buff *skb, __u32 len, __u64 flags) = (void *) 43; + +/* + * bpf_xdp_adjust_head + * + * Adjust (move) *xdp_md*\ **->data** by *delta* bytes. Note that + * it is possible to use a negative value for *delta*. This helper + * can be used to prepare the packet for pushing or popping + * headers. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_xdp_adjust_head)(struct xdp_md *xdp_md, int delta) = (void *) 44; + +/* + * bpf_probe_read_str + * + * Copy a NUL terminated string from an unsafe kernel address + * *unsafe_ptr* to *dst*. See **bpf_probe_read_kernel_str**\ () for + * more details. + * + * Generally, use **bpf_probe_read_user_str**\ () or + * **bpf_probe_read_kernel_str**\ () instead. + * + * Returns + * On success, the strictly positive length of the string, + * including the trailing NUL character. On error, a negative + * value. + */ +static long (*bpf_probe_read_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 45; + +/* + * bpf_get_socket_cookie + * + * If the **struct sk_buff** pointed by *skb* has a known socket, + * retrieve the cookie (generated by the kernel) of this socket. + * If no cookie has been set yet, generate a new cookie. Once + * generated, the socket cookie remains stable for the life of the + * socket. This helper can be useful for monitoring per socket + * networking traffic statistics as it provides a global socket + * identifier that can be assumed unique. + * + * Returns + * A 8-byte long non-decreasing number on success, or 0 if the + * socket field is missing inside *skb*. + */ +static __u64 (*bpf_get_socket_cookie)(void *ctx) = (void *) 46; + +/* + * bpf_get_socket_uid + * + * + * Returns + * The owner UID of the socket associated to *skb*. If the socket + * is **NULL**, or if it is not a full socket (i.e. if it is a + * time-wait or a request socket instead), **overflowuid** value + * is returned (note that **overflowuid** might also be the actual + * UID value for the socket). + */ +static __u32 (*bpf_get_socket_uid)(struct __sk_buff *skb) = (void *) 47; + +/* + * bpf_set_hash + * + * Set the full hash for *skb* (set the field *skb*\ **->hash**) + * to value *hash*. + * + * Returns + * 0 + */ +static long (*bpf_set_hash)(struct __sk_buff *skb, __u32 hash) = (void *) 48; + +/* + * bpf_setsockopt + * + * Emulate a call to **setsockopt()** on the socket associated to + * *bpf_socket*, which must be a full socket. The *level* at + * which the option resides and the name *optname* of the option + * must be specified, see **setsockopt(2)** for more information. + * The option value of length *optlen* is pointed by *optval*. + * + * *bpf_socket* should be one of the following: + * + * * **struct bpf_sock_ops** for **BPF_PROG_TYPE_SOCK_OPS**. + * * **struct bpf_sock_addr** for **BPF_CGROUP_INET4_CONNECT** + * and **BPF_CGROUP_INET6_CONNECT**. + * + * This helper actually implements a subset of **setsockopt()**. + * It supports the following *level*\ s: + * + * * **SOL_SOCKET**, which supports the following *optname*\ s: + * **SO_RCVBUF**, **SO_SNDBUF**, **SO_MAX_PACING_RATE**, + * **SO_PRIORITY**, **SO_RCVLOWAT**, **SO_MARK**, + * **SO_BINDTODEVICE**, **SO_KEEPALIVE**. + * * **IPPROTO_TCP**, which supports the following *optname*\ s: + * **TCP_CONGESTION**, **TCP_BPF_IW**, + * **TCP_BPF_SNDCWND_CLAMP**, **TCP_SAVE_SYN**, + * **TCP_KEEPIDLE**, **TCP_KEEPINTVL**, **TCP_KEEPCNT**, + * **TCP_SYNCNT**, **TCP_USER_TIMEOUT**. + * * **IPPROTO_IP**, which supports *optname* **IP_TOS**. + * * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_setsockopt)(void *bpf_socket, int level, int optname, void *optval, int optlen) = (void *) 49; + +/* + * bpf_skb_adjust_room + * + * Grow or shrink the room for data in the packet associated to + * *skb* by *len_diff*, and according to the selected *mode*. + * + * By default, the helper will reset any offloaded checksum + * indicator of the skb to CHECKSUM_NONE. This can be avoided + * by the following flag: + * + * * **BPF_F_ADJ_ROOM_NO_CSUM_RESET**: Do not reset offloaded + * checksum data of the skb to CHECKSUM_NONE. + * + * There are two supported modes at this time: + * + * * **BPF_ADJ_ROOM_MAC**: Adjust room at the mac layer + * (room space is added or removed below the layer 2 header). + * + * * **BPF_ADJ_ROOM_NET**: Adjust room at the network layer + * (room space is added or removed below the layer 3 header). + * + * The following flags are supported at this time: + * + * * **BPF_F_ADJ_ROOM_FIXED_GSO**: Do not adjust gso_size. + * Adjusting mss in this way is not allowed for datagrams. + * + * * **BPF_F_ADJ_ROOM_ENCAP_L3_IPV4**, + * **BPF_F_ADJ_ROOM_ENCAP_L3_IPV6**: + * Any new space is reserved to hold a tunnel header. + * Configure skb offsets and other fields accordingly. + * + * * **BPF_F_ADJ_ROOM_ENCAP_L4_GRE**, + * **BPF_F_ADJ_ROOM_ENCAP_L4_UDP**: + * Use with ENCAP_L3 flags to further specify the tunnel type. + * + * * **BPF_F_ADJ_ROOM_ENCAP_L2**\ (*len*): + * Use with ENCAP_L3/L4 flags to further specify the tunnel + * type; *len* is the length of the inner MAC header. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_adjust_room)(struct __sk_buff *skb, __s32 len_diff, __u32 mode, __u64 flags) = (void *) 50; + +/* + * bpf_redirect_map + * + * Redirect the packet to the endpoint referenced by *map* at + * index *key*. Depending on its type, this *map* can contain + * references to net devices (for forwarding packets through other + * ports), or to CPUs (for redirecting XDP frames to another CPU; + * but this is only implemented for native XDP (with driver + * support) as of this writing). + * + * The lower two bits of *flags* are used as the return code if + * the map lookup fails. This is so that the return value can be + * one of the XDP program return codes up to **XDP_TX**, as chosen + * by the caller. Any higher bits in the *flags* argument must be + * unset. + * + * See also **bpf_redirect**\ (), which only supports redirecting + * to an ifindex, but doesn't require a map to do so. + * + * Returns + * **XDP_REDIRECT** on success, or the value of the two lower bits + * of the *flags* argument on error. + */ +static long (*bpf_redirect_map)(void *map, __u32 key, __u64 flags) = (void *) 51; + +/* + * bpf_sk_redirect_map + * + * Redirect the packet to the socket referenced by *map* (of type + * **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and + * egress interfaces can be used for redirection. The + * **BPF_F_INGRESS** value in *flags* is used to make the + * distinction (ingress path is selected if the flag is present, + * egress path otherwise). This is the only flag supported for now. + * + * Returns + * **SK_PASS** on success, or **SK_DROP** on error. + */ +static long (*bpf_sk_redirect_map)(struct __sk_buff *skb, void *map, __u32 key, __u64 flags) = (void *) 52; + +/* + * bpf_sock_map_update + * + * Add an entry to, or update a *map* referencing sockets. The + * *skops* is used as a new value for the entry associated to + * *key*. *flags* is one of: + * + * **BPF_NOEXIST** + * The entry for *key* must not exist in the map. + * **BPF_EXIST** + * The entry for *key* must already exist in the map. + * **BPF_ANY** + * No condition on the existence of the entry for *key*. + * + * If the *map* has eBPF programs (parser and verdict), those will + * be inherited by the socket being added. If the socket is + * already attached to eBPF programs, this results in an error. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_sock_map_update)(struct bpf_sock_ops *skops, void *map, void *key, __u64 flags) = (void *) 53; + +/* + * bpf_xdp_adjust_meta + * + * Adjust the address pointed by *xdp_md*\ **->data_meta** by + * *delta* (which can be positive or negative). Note that this + * operation modifies the address stored in *xdp_md*\ **->data**, + * so the latter must be loaded only after the helper has been + * called. + * + * The use of *xdp_md*\ **->data_meta** is optional and programs + * are not required to use it. The rationale is that when the + * packet is processed with XDP (e.g. as DoS filter), it is + * possible to push further meta data along with it before passing + * to the stack, and to give the guarantee that an ingress eBPF + * program attached as a TC classifier on the same device can pick + * this up for further post-processing. Since TC works with socket + * buffers, it remains possible to set from XDP the **mark** or + * **priority** pointers, or other pointers for the socket buffer. + * Having this scratch space generic and programmable allows for + * more flexibility as the user is free to store whatever meta + * data they need. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_xdp_adjust_meta)(struct xdp_md *xdp_md, int delta) = (void *) 54; + +/* + * bpf_perf_event_read_value + * + * Read the value of a perf event counter, and store it into *buf* + * of size *buf_size*. This helper relies on a *map* of type + * **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of the perf event + * counter is selected when *map* is updated with perf event file + * descriptors. The *map* is an array whose size is the number of + * available CPUs, and each cell contains a value relative to one + * CPU. The value to retrieve is indicated by *flags*, that + * contains the index of the CPU to look up, masked with + * **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to + * **BPF_F_CURRENT_CPU** to indicate that the value for the + * current CPU should be retrieved. + * + * This helper behaves in a way close to + * **bpf_perf_event_read**\ () helper, save that instead of + * just returning the value observed, it fills the *buf* + * structure. This allows for additional data to be retrieved: in + * particular, the enabled and running times (in *buf*\ + * **->enabled** and *buf*\ **->running**, respectively) are + * copied. In general, **bpf_perf_event_read_value**\ () is + * recommended over **bpf_perf_event_read**\ (), which has some + * ABI issues and provides fewer functionalities. + * + * These values are interesting, because hardware PMU (Performance + * Monitoring Unit) counters are limited resources. When there are + * more PMU based perf events opened than available counters, + * kernel will multiplex these events so each event gets certain + * percentage (but not all) of the PMU time. In case that + * multiplexing happens, the number of samples or counter value + * will not reflect the case compared to when no multiplexing + * occurs. This makes comparison between different runs difficult. + * Typically, the counter value should be normalized before + * comparing to other experiments. The usual normalization is done + * as follows. + * + * :: + * + * normalized_counter = counter * t_enabled / t_running + * + * Where t_enabled is the time enabled for event and t_running is + * the time running for event since last normalization. The + * enabled and running times are accumulated since the perf event + * open. To achieve scaling factor between two invocations of an + * eBPF program, users can use CPU id as the key (which is + * typical for perf array usage model) to remember the previous + * value and do the calculation inside the eBPF program. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_perf_event_read_value)(void *map, __u64 flags, struct bpf_perf_event_value *buf, __u32 buf_size) = (void *) 55; + +/* + * bpf_perf_prog_read_value + * + * For en eBPF program attached to a perf event, retrieve the + * value of the event counter associated to *ctx* and store it in + * the structure pointed by *buf* and of size *buf_size*. Enabled + * and running times are also stored in the structure (see + * description of helper **bpf_perf_event_read_value**\ () for + * more details). + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_perf_prog_read_value)(struct bpf_perf_event_data *ctx, struct bpf_perf_event_value *buf, __u32 buf_size) = (void *) 56; + +/* + * bpf_getsockopt + * + * Emulate a call to **getsockopt()** on the socket associated to + * *bpf_socket*, which must be a full socket. The *level* at + * which the option resides and the name *optname* of the option + * must be specified, see **getsockopt(2)** for more information. + * The retrieved value is stored in the structure pointed by + * *opval* and of length *optlen*. + * + * *bpf_socket* should be one of the following: + * + * * **struct bpf_sock_ops** for **BPF_PROG_TYPE_SOCK_OPS**. + * * **struct bpf_sock_addr** for **BPF_CGROUP_INET4_CONNECT** + * and **BPF_CGROUP_INET6_CONNECT**. + * + * This helper actually implements a subset of **getsockopt()**. + * It supports the following *level*\ s: + * + * * **IPPROTO_TCP**, which supports *optname* + * **TCP_CONGESTION**. + * * **IPPROTO_IP**, which supports *optname* **IP_TOS**. + * * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_getsockopt)(void *bpf_socket, int level, int optname, void *optval, int optlen) = (void *) 57; + +/* + * bpf_override_return + * + * Used for error injection, this helper uses kprobes to override + * the return value of the probed function, and to set it to *rc*. + * The first argument is the context *regs* on which the kprobe + * works. + * + * This helper works by setting the PC (program counter) + * to an override function which is run in place of the original + * probed function. This means the probed function is not run at + * all. The replacement function just returns with the required + * value. + * + * This helper has security implications, and thus is subject to + * restrictions. It is only available if the kernel was compiled + * with the **CONFIG_BPF_KPROBE_OVERRIDE** configuration + * option, and in this case it only works on functions tagged with + * **ALLOW_ERROR_INJECTION** in the kernel code. + * + * Also, the helper is only available for the architectures having + * the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing, + * x86 architecture is the only one to support this feature. + * + * Returns + * 0 + */ +static long (*bpf_override_return)(struct pt_regs *regs, __u64 rc) = (void *) 58; + +/* + * bpf_sock_ops_cb_flags_set + * + * Attempt to set the value of the **bpf_sock_ops_cb_flags** field + * for the full TCP socket associated to *bpf_sock_ops* to + * *argval*. + * + * The primary use of this field is to determine if there should + * be calls to eBPF programs of type + * **BPF_PROG_TYPE_SOCK_OPS** at various points in the TCP + * code. A program of the same type can change its value, per + * connection and as necessary, when the connection is + * established. This field is directly accessible for reading, but + * this helper must be used for updates in order to return an + * error if an eBPF program tries to set a callback that is not + * supported in the current kernel. + * + * *argval* is a flag array which can combine these flags: + * + * * **BPF_SOCK_OPS_RTO_CB_FLAG** (retransmission time out) + * * **BPF_SOCK_OPS_RETRANS_CB_FLAG** (retransmission) + * * **BPF_SOCK_OPS_STATE_CB_FLAG** (TCP state change) + * * **BPF_SOCK_OPS_RTT_CB_FLAG** (every RTT) + * + * Therefore, this function can be used to clear a callback flag by + * setting the appropriate bit to zero. e.g. to disable the RTO + * callback: + * + * **bpf_sock_ops_cb_flags_set(bpf_sock,** + * **bpf_sock->bpf_sock_ops_cb_flags & ~BPF_SOCK_OPS_RTO_CB_FLAG)** + * + * Here are some examples of where one could call such eBPF + * program: + * + * * When RTO fires. + * * When a packet is retransmitted. + * * When the connection terminates. + * * When a packet is sent. + * * When a packet is received. + * + * Returns + * Code **-EINVAL** if the socket is not a full TCP socket; + * otherwise, a positive number containing the bits that could not + * be set is returned (which comes down to 0 if all bits were set + * as required). + */ +static long (*bpf_sock_ops_cb_flags_set)(struct bpf_sock_ops *bpf_sock, int argval) = (void *) 59; + +/* + * bpf_msg_redirect_map + * + * This helper is used in programs implementing policies at the + * socket level. If the message *msg* is allowed to pass (i.e. if + * the verdict eBPF program returns **SK_PASS**), redirect it to + * the socket referenced by *map* (of type + * **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and + * egress interfaces can be used for redirection. The + * **BPF_F_INGRESS** value in *flags* is used to make the + * distinction (ingress path is selected if the flag is present, + * egress path otherwise). This is the only flag supported for now. + * + * Returns + * **SK_PASS** on success, or **SK_DROP** on error. + */ +static long (*bpf_msg_redirect_map)(struct sk_msg_md *msg, void *map, __u32 key, __u64 flags) = (void *) 60; + +/* + * bpf_msg_apply_bytes + * + * For socket policies, apply the verdict of the eBPF program to + * the next *bytes* (number of bytes) of message *msg*. + * + * For example, this helper can be used in the following cases: + * + * * A single **sendmsg**\ () or **sendfile**\ () system call + * contains multiple logical messages that the eBPF program is + * supposed to read and for which it should apply a verdict. + * * An eBPF program only cares to read the first *bytes* of a + * *msg*. If the message has a large payload, then setting up + * and calling the eBPF program repeatedly for all bytes, even + * though the verdict is already known, would create unnecessary + * overhead. + * + * When called from within an eBPF program, the helper sets a + * counter internal to the BPF infrastructure, that is used to + * apply the last verdict to the next *bytes*. If *bytes* is + * smaller than the current data being processed from a + * **sendmsg**\ () or **sendfile**\ () system call, the first + * *bytes* will be sent and the eBPF program will be re-run with + * the pointer for start of data pointing to byte number *bytes* + * **+ 1**. If *bytes* is larger than the current data being + * processed, then the eBPF verdict will be applied to multiple + * **sendmsg**\ () or **sendfile**\ () calls until *bytes* are + * consumed. + * + * Note that if a socket closes with the internal counter holding + * a non-zero value, this is not a problem because data is not + * being buffered for *bytes* and is sent as it is received. + * + * Returns + * 0 + */ +static long (*bpf_msg_apply_bytes)(struct sk_msg_md *msg, __u32 bytes) = (void *) 61; + +/* + * bpf_msg_cork_bytes + * + * For socket policies, prevent the execution of the verdict eBPF + * program for message *msg* until *bytes* (byte number) have been + * accumulated. + * + * This can be used when one needs a specific number of bytes + * before a verdict can be assigned, even if the data spans + * multiple **sendmsg**\ () or **sendfile**\ () calls. The extreme + * case would be a user calling **sendmsg**\ () repeatedly with + * 1-byte long message segments. Obviously, this is bad for + * performance, but it is still valid. If the eBPF program needs + * *bytes* bytes to validate a header, this helper can be used to + * prevent the eBPF program to be called again until *bytes* have + * been accumulated. + * + * Returns + * 0 + */ +static long (*bpf_msg_cork_bytes)(struct sk_msg_md *msg, __u32 bytes) = (void *) 62; + +/* + * bpf_msg_pull_data + * + * For socket policies, pull in non-linear data from user space + * for *msg* and set pointers *msg*\ **->data** and *msg*\ + * **->data_end** to *start* and *end* bytes offsets into *msg*, + * respectively. + * + * If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a + * *msg* it can only parse data that the (**data**, **data_end**) + * pointers have already consumed. For **sendmsg**\ () hooks this + * is likely the first scatterlist element. But for calls relying + * on the **sendpage** handler (e.g. **sendfile**\ ()) this will + * be the range (**0**, **0**) because the data is shared with + * user space and by default the objective is to avoid allowing + * user space to modify data while (or after) eBPF verdict is + * being decided. This helper can be used to pull in data and to + * set the start and end pointer to given values. Data will be + * copied if necessary (i.e. if data was not linear and if start + * and end pointers do not point to the same chunk). + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_msg_pull_data)(struct sk_msg_md *msg, __u32 start, __u32 end, __u64 flags) = (void *) 63; + +/* + * bpf_bind + * + * Bind the socket associated to *ctx* to the address pointed by + * *addr*, of length *addr_len*. This allows for making outgoing + * connection from the desired IP address, which can be useful for + * example when all processes inside a cgroup should use one + * single IP address on a host that has multiple IP configured. + * + * This helper works for IPv4 and IPv6, TCP and UDP sockets. The + * domain (*addr*\ **->sa_family**) must be **AF_INET** (or + * **AF_INET6**). It's advised to pass zero port (**sin_port** + * or **sin6_port**) which triggers IP_BIND_ADDRESS_NO_PORT-like + * behavior and lets the kernel efficiently pick up an unused + * port as long as 4-tuple is unique. Passing non-zero port might + * lead to degraded performance. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_bind)(struct bpf_sock_addr *ctx, struct sockaddr *addr, int addr_len) = (void *) 64; + +/* + * bpf_xdp_adjust_tail + * + * Adjust (move) *xdp_md*\ **->data_end** by *delta* bytes. It is + * possible to both shrink and grow the packet tail. + * Shrink done via *delta* being a negative integer. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_xdp_adjust_tail)(struct xdp_md *xdp_md, int delta) = (void *) 65; + +/* + * bpf_skb_get_xfrm_state + * + * Retrieve the XFRM state (IP transform framework, see also + * **ip-xfrm(8)**) at *index* in XFRM "security path" for *skb*. + * + * The retrieved value is stored in the **struct bpf_xfrm_state** + * pointed by *xfrm_state* and of length *size*. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * This helper is available only if the kernel was compiled with + * **CONFIG_XFRM** configuration option. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_get_xfrm_state)(struct __sk_buff *skb, __u32 index, struct bpf_xfrm_state *xfrm_state, __u32 size, __u64 flags) = (void *) 66; + +/* + * bpf_get_stack + * + * Return a user or a kernel stack in bpf program provided buffer. + * To achieve this, the helper needs *ctx*, which is a pointer + * to the context on which the tracing program is executed. + * To store the stacktrace, the bpf program provides *buf* with + * a nonnegative *size*. + * + * The last argument, *flags*, holds the number of stack frames to + * skip (from 0 to 255), masked with + * **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set + * the following flags: + * + * **BPF_F_USER_STACK** + * Collect a user space stack instead of a kernel stack. + * **BPF_F_USER_BUILD_ID** + * Collect buildid+offset instead of ips for user stack, + * only valid if **BPF_F_USER_STACK** is also specified. + * + * **bpf_get_stack**\ () can collect up to + * **PERF_MAX_STACK_DEPTH** both kernel and user frames, subject + * to sufficient large buffer size. Note that + * this limit can be controlled with the **sysctl** program, and + * that it should be manually increased in order to profile long + * user stacks (such as stacks for Java programs). To do so, use: + * + * :: + * + * # sysctl kernel.perf_event_max_stack= + * + * Returns + * A non-negative value equal to or less than *size* on success, + * or a negative error in case of failure. + */ +static long (*bpf_get_stack)(void *ctx, void *buf, __u32 size, __u64 flags) = (void *) 67; + +/* + * bpf_skb_load_bytes_relative + * + * This helper is similar to **bpf_skb_load_bytes**\ () in that + * it provides an easy way to load *len* bytes from *offset* + * from the packet associated to *skb*, into the buffer pointed + * by *to*. The difference to **bpf_skb_load_bytes**\ () is that + * a fifth argument *start_header* exists in order to select a + * base offset to start from. *start_header* can be one of: + * + * **BPF_HDR_START_MAC** + * Base offset to load data from is *skb*'s mac header. + * **BPF_HDR_START_NET** + * Base offset to load data from is *skb*'s network header. + * + * In general, "direct packet access" is the preferred method to + * access packet data, however, this helper is in particular useful + * in socket filters where *skb*\ **->data** does not always point + * to the start of the mac header and where "direct packet access" + * is not available. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_load_bytes_relative)(const void *skb, __u32 offset, void *to, __u32 len, __u32 start_header) = (void *) 68; + +/* + * bpf_fib_lookup + * + * Do FIB lookup in kernel tables using parameters in *params*. + * If lookup is successful and result shows packet is to be + * forwarded, the neighbor tables are searched for the nexthop. + * If successful (ie., FIB lookup shows forwarding and nexthop + * is resolved), the nexthop address is returned in ipv4_dst + * or ipv6_dst based on family, smac is set to mac address of + * egress device, dmac is set to nexthop mac address, rt_metric + * is set to metric from route (IPv4/IPv6 only), and ifindex + * is set to the device index of the nexthop from the FIB lookup. + * + * *plen* argument is the size of the passed in struct. + * *flags* argument can be a combination of one or more of the + * following values: + * + * **BPF_FIB_LOOKUP_DIRECT** + * Do a direct table lookup vs full lookup using FIB + * rules. + * **BPF_FIB_LOOKUP_OUTPUT** + * Perform lookup from an egress perspective (default is + * ingress). + * + * *ctx* is either **struct xdp_md** for XDP programs or + * **struct sk_buff** tc cls_act programs. + * + * Returns + * * < 0 if any input argument is invalid + * * 0 on success (packet is forwarded, nexthop neighbor exists) + * * > 0 one of **BPF_FIB_LKUP_RET_** codes explaining why the + * packet is not forwarded or needs assist from full stack + */ +static long (*bpf_fib_lookup)(void *ctx, struct bpf_fib_lookup *params, int plen, __u32 flags) = (void *) 69; + +/* + * bpf_sock_hash_update + * + * Add an entry to, or update a sockhash *map* referencing sockets. + * The *skops* is used as a new value for the entry associated to + * *key*. *flags* is one of: + * + * **BPF_NOEXIST** + * The entry for *key* must not exist in the map. + * **BPF_EXIST** + * The entry for *key* must already exist in the map. + * **BPF_ANY** + * No condition on the existence of the entry for *key*. + * + * If the *map* has eBPF programs (parser and verdict), those will + * be inherited by the socket being added. If the socket is + * already attached to eBPF programs, this results in an error. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_sock_hash_update)(struct bpf_sock_ops *skops, void *map, void *key, __u64 flags) = (void *) 70; + +/* + * bpf_msg_redirect_hash + * + * This helper is used in programs implementing policies at the + * socket level. If the message *msg* is allowed to pass (i.e. if + * the verdict eBPF program returns **SK_PASS**), redirect it to + * the socket referenced by *map* (of type + * **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and + * egress interfaces can be used for redirection. The + * **BPF_F_INGRESS** value in *flags* is used to make the + * distinction (ingress path is selected if the flag is present, + * egress path otherwise). This is the only flag supported for now. + * + * Returns + * **SK_PASS** on success, or **SK_DROP** on error. + */ +static long (*bpf_msg_redirect_hash)(struct sk_msg_md *msg, void *map, void *key, __u64 flags) = (void *) 71; + +/* + * bpf_sk_redirect_hash + * + * This helper is used in programs implementing policies at the + * skb socket level. If the sk_buff *skb* is allowed to pass (i.e. + * if the verdeict eBPF program returns **SK_PASS**), redirect it + * to the socket referenced by *map* (of type + * **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and + * egress interfaces can be used for redirection. The + * **BPF_F_INGRESS** value in *flags* is used to make the + * distinction (ingress path is selected if the flag is present, + * egress otherwise). This is the only flag supported for now. + * + * Returns + * **SK_PASS** on success, or **SK_DROP** on error. + */ +static long (*bpf_sk_redirect_hash)(struct __sk_buff *skb, void *map, void *key, __u64 flags) = (void *) 72; + +/* + * bpf_lwt_push_encap + * + * Encapsulate the packet associated to *skb* within a Layer 3 + * protocol header. This header is provided in the buffer at + * address *hdr*, with *len* its size in bytes. *type* indicates + * the protocol of the header and can be one of: + * + * **BPF_LWT_ENCAP_SEG6** + * IPv6 encapsulation with Segment Routing Header + * (**struct ipv6_sr_hdr**). *hdr* only contains the SRH, + * the IPv6 header is computed by the kernel. + * **BPF_LWT_ENCAP_SEG6_INLINE** + * Only works if *skb* contains an IPv6 packet. Insert a + * Segment Routing Header (**struct ipv6_sr_hdr**) inside + * the IPv6 header. + * **BPF_LWT_ENCAP_IP** + * IP encapsulation (GRE/GUE/IPIP/etc). The outer header + * must be IPv4 or IPv6, followed by zero or more + * additional headers, up to **LWT_BPF_MAX_HEADROOM** + * total bytes in all prepended headers. Please note that + * if **skb_is_gso**\ (*skb*) is true, no more than two + * headers can be prepended, and the inner header, if + * present, should be either GRE or UDP/GUE. + * + * **BPF_LWT_ENCAP_SEG6**\ \* types can be called by BPF programs + * of type **BPF_PROG_TYPE_LWT_IN**; **BPF_LWT_ENCAP_IP** type can + * be called by bpf programs of types **BPF_PROG_TYPE_LWT_IN** and + * **BPF_PROG_TYPE_LWT_XMIT**. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_lwt_push_encap)(struct __sk_buff *skb, __u32 type, void *hdr, __u32 len) = (void *) 73; + +/* + * bpf_lwt_seg6_store_bytes + * + * Store *len* bytes from address *from* into the packet + * associated to *skb*, at *offset*. Only the flags, tag and TLVs + * inside the outermost IPv6 Segment Routing Header can be + * modified through this helper. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_lwt_seg6_store_bytes)(struct __sk_buff *skb, __u32 offset, const void *from, __u32 len) = (void *) 74; + +/* + * bpf_lwt_seg6_adjust_srh + * + * Adjust the size allocated to TLVs in the outermost IPv6 + * Segment Routing Header contained in the packet associated to + * *skb*, at position *offset* by *delta* bytes. Only offsets + * after the segments are accepted. *delta* can be as well + * positive (growing) as negative (shrinking). + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_lwt_seg6_adjust_srh)(struct __sk_buff *skb, __u32 offset, __s32 delta) = (void *) 75; + +/* + * bpf_lwt_seg6_action + * + * Apply an IPv6 Segment Routing action of type *action* to the + * packet associated to *skb*. Each action takes a parameter + * contained at address *param*, and of length *param_len* bytes. + * *action* can be one of: + * + * **SEG6_LOCAL_ACTION_END_X** + * End.X action: Endpoint with Layer-3 cross-connect. + * Type of *param*: **struct in6_addr**. + * **SEG6_LOCAL_ACTION_END_T** + * End.T action: Endpoint with specific IPv6 table lookup. + * Type of *param*: **int**. + * **SEG6_LOCAL_ACTION_END_B6** + * End.B6 action: Endpoint bound to an SRv6 policy. + * Type of *param*: **struct ipv6_sr_hdr**. + * **SEG6_LOCAL_ACTION_END_B6_ENCAP** + * End.B6.Encap action: Endpoint bound to an SRv6 + * encapsulation policy. + * Type of *param*: **struct ipv6_sr_hdr**. + * + * A call to this helper is susceptible to change the underlying + * packet buffer. Therefore, at load time, all checks on pointers + * previously done by the verifier are invalidated and must be + * performed again, if the helper is used in combination with + * direct packet access. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_lwt_seg6_action)(struct __sk_buff *skb, __u32 action, void *param, __u32 param_len) = (void *) 76; + +/* + * bpf_rc_repeat + * + * This helper is used in programs implementing IR decoding, to + * report a successfully decoded repeat key message. This delays + * the generation of a key up event for previously generated + * key down event. + * + * Some IR protocols like NEC have a special IR message for + * repeating last button, for when a button is held down. + * + * The *ctx* should point to the lirc sample as passed into + * the program. + * + * This helper is only available is the kernel was compiled with + * the **CONFIG_BPF_LIRC_MODE2** configuration option set to + * "**y**". + * + * Returns + * 0 + */ +static long (*bpf_rc_repeat)(void *ctx) = (void *) 77; + +/* + * bpf_rc_keydown + * + * This helper is used in programs implementing IR decoding, to + * report a successfully decoded key press with *scancode*, + * *toggle* value in the given *protocol*. The scancode will be + * translated to a keycode using the rc keymap, and reported as + * an input key down event. After a period a key up event is + * generated. This period can be extended by calling either + * **bpf_rc_keydown**\ () again with the same values, or calling + * **bpf_rc_repeat**\ (). + * + * Some protocols include a toggle bit, in case the button was + * released and pressed again between consecutive scancodes. + * + * The *ctx* should point to the lirc sample as passed into + * the program. + * + * The *protocol* is the decoded protocol number (see + * **enum rc_proto** for some predefined values). + * + * This helper is only available is the kernel was compiled with + * the **CONFIG_BPF_LIRC_MODE2** configuration option set to + * "**y**". + * + * Returns + * 0 + */ +static long (*bpf_rc_keydown)(void *ctx, __u32 protocol, __u64 scancode, __u32 toggle) = (void *) 78; + +/* + * bpf_skb_cgroup_id + * + * Return the cgroup v2 id of the socket associated with the *skb*. + * This is roughly similar to the **bpf_get_cgroup_classid**\ () + * helper for cgroup v1 by providing a tag resp. identifier that + * can be matched on or used for map lookups e.g. to implement + * policy. The cgroup v2 id of a given path in the hierarchy is + * exposed in user space through the f_handle API in order to get + * to the same 64-bit id. + * + * This helper can be used on TC egress path, but not on ingress, + * and is available only if the kernel was compiled with the + * **CONFIG_SOCK_CGROUP_DATA** configuration option. + * + * Returns + * The id is returned or 0 in case the id could not be retrieved. + */ +static __u64 (*bpf_skb_cgroup_id)(struct __sk_buff *skb) = (void *) 79; + +/* + * bpf_get_current_cgroup_id + * + * + * Returns + * A 64-bit integer containing the current cgroup id based + * on the cgroup within which the current task is running. + */ +static __u64 (*bpf_get_current_cgroup_id)(void) = (void *) 80; + +/* + * bpf_get_local_storage + * + * Get the pointer to the local storage area. + * The type and the size of the local storage is defined + * by the *map* argument. + * The *flags* meaning is specific for each map type, + * and has to be 0 for cgroup local storage. + * + * Depending on the BPF program type, a local storage area + * can be shared between multiple instances of the BPF program, + * running simultaneously. + * + * A user should care about the synchronization by himself. + * For example, by using the **BPF_STX_XADD** instruction to alter + * the shared data. + * + * Returns + * A pointer to the local storage area. + */ +static void *(*bpf_get_local_storage)(void *map, __u64 flags) = (void *) 81; + +/* + * bpf_sk_select_reuseport + * + * Select a **SO_REUSEPORT** socket from a + * **BPF_MAP_TYPE_REUSEPORT_ARRAY** *map*. + * It checks the selected socket is matching the incoming + * request in the socket buffer. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_sk_select_reuseport)(struct sk_reuseport_md *reuse, void *map, void *key, __u64 flags) = (void *) 82; + +/* + * bpf_skb_ancestor_cgroup_id + * + * Return id of cgroup v2 that is ancestor of cgroup associated + * with the *skb* at the *ancestor_level*. The root cgroup is at + * *ancestor_level* zero and each step down the hierarchy + * increments the level. If *ancestor_level* == level of cgroup + * associated with *skb*, then return value will be same as that + * of **bpf_skb_cgroup_id**\ (). + * + * The helper is useful to implement policies based on cgroups + * that are upper in hierarchy than immediate cgroup associated + * with *skb*. + * + * The format of returned id and helper limitations are same as in + * **bpf_skb_cgroup_id**\ (). + * + * Returns + * The id is returned or 0 in case the id could not be retrieved. + */ +static __u64 (*bpf_skb_ancestor_cgroup_id)(struct __sk_buff *skb, int ancestor_level) = (void *) 83; + +/* + * bpf_sk_lookup_tcp + * + * Look for TCP socket matching *tuple*, optionally in a child + * network namespace *netns*. The return value must be checked, + * and if non-**NULL**, released via **bpf_sk_release**\ (). + * + * The *ctx* should point to the context of the program, such as + * the skb or socket (depending on the hook in use). This is used + * to determine the base network namespace for the lookup. + * + * *tuple_size* must be one of: + * + * **sizeof**\ (*tuple*\ **->ipv4**) + * Look for an IPv4 socket. + * **sizeof**\ (*tuple*\ **->ipv6**) + * Look for an IPv6 socket. + * + * If the *netns* is a negative signed 32-bit integer, then the + * socket lookup table in the netns associated with the *ctx* will + * will be used. For the TC hooks, this is the netns of the device + * in the skb. For socket hooks, this is the netns of the socket. + * If *netns* is any other signed 32-bit value greater than or + * equal to zero then it specifies the ID of the netns relative to + * the netns associated with the *ctx*. *netns* values beyond the + * range of 32-bit integers are reserved for future use. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * This helper is available only if the kernel was compiled with + * **CONFIG_NET** configuration option. + * + * Returns + * Pointer to **struct bpf_sock**, or **NULL** in case of failure. + * For sockets with reuseport option, the **struct bpf_sock** + * result is from *reuse*\ **->socks**\ [] using the hash of the + * tuple. + */ +static struct bpf_sock *(*bpf_sk_lookup_tcp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 84; + +/* + * bpf_sk_lookup_udp + * + * Look for UDP socket matching *tuple*, optionally in a child + * network namespace *netns*. The return value must be checked, + * and if non-**NULL**, released via **bpf_sk_release**\ (). + * + * The *ctx* should point to the context of the program, such as + * the skb or socket (depending on the hook in use). This is used + * to determine the base network namespace for the lookup. + * + * *tuple_size* must be one of: + * + * **sizeof**\ (*tuple*\ **->ipv4**) + * Look for an IPv4 socket. + * **sizeof**\ (*tuple*\ **->ipv6**) + * Look for an IPv6 socket. + * + * If the *netns* is a negative signed 32-bit integer, then the + * socket lookup table in the netns associated with the *ctx* will + * will be used. For the TC hooks, this is the netns of the device + * in the skb. For socket hooks, this is the netns of the socket. + * If *netns* is any other signed 32-bit value greater than or + * equal to zero then it specifies the ID of the netns relative to + * the netns associated with the *ctx*. *netns* values beyond the + * range of 32-bit integers are reserved for future use. + * + * All values for *flags* are reserved for future usage, and must + * be left at zero. + * + * This helper is available only if the kernel was compiled with + * **CONFIG_NET** configuration option. + * + * Returns + * Pointer to **struct bpf_sock**, or **NULL** in case of failure. + * For sockets with reuseport option, the **struct bpf_sock** + * result is from *reuse*\ **->socks**\ [] using the hash of the + * tuple. + */ +static struct bpf_sock *(*bpf_sk_lookup_udp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 85; + +/* + * bpf_sk_release + * + * Release the reference held by *sock*. *sock* must be a + * non-**NULL** pointer that was returned from + * **bpf_sk_lookup_xxx**\ (). + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_sk_release)(struct bpf_sock *sock) = (void *) 86; + +/* + * bpf_map_push_elem + * + * Push an element *value* in *map*. *flags* is one of: + * + * **BPF_EXIST** + * If the queue/stack is full, the oldest element is + * removed to make room for this. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_map_push_elem)(void *map, const void *value, __u64 flags) = (void *) 87; + +/* + * bpf_map_pop_elem + * + * Pop an element from *map*. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_map_pop_elem)(void *map, void *value) = (void *) 88; + +/* + * bpf_map_peek_elem + * + * Get an element from *map* without removing it. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_map_peek_elem)(void *map, void *value) = (void *) 89; + +/* + * bpf_msg_push_data + * + * For socket policies, insert *len* bytes into *msg* at offset + * *start*. + * + * If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a + * *msg* it may want to insert metadata or options into the *msg*. + * This can later be read and used by any of the lower layer BPF + * hooks. + * + * This helper may fail if under memory pressure (a malloc + * fails) in these cases BPF programs will get an appropriate + * error and BPF programs will need to handle them. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_msg_push_data)(struct sk_msg_md *msg, __u32 start, __u32 len, __u64 flags) = (void *) 90; + +/* + * bpf_msg_pop_data + * + * Will remove *len* bytes from a *msg* starting at byte *start*. + * This may result in **ENOMEM** errors under certain situations if + * an allocation and copy are required due to a full ring buffer. + * However, the helper will try to avoid doing the allocation + * if possible. Other errors can occur if input parameters are + * invalid either due to *start* byte not being valid part of *msg* + * payload and/or *pop* value being to large. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_msg_pop_data)(struct sk_msg_md *msg, __u32 start, __u32 len, __u64 flags) = (void *) 91; + +/* + * bpf_rc_pointer_rel + * + * This helper is used in programs implementing IR decoding, to + * report a successfully decoded pointer movement. + * + * The *ctx* should point to the lirc sample as passed into + * the program. + * + * This helper is only available is the kernel was compiled with + * the **CONFIG_BPF_LIRC_MODE2** configuration option set to + * "**y**". + * + * Returns + * 0 + */ +static long (*bpf_rc_pointer_rel)(void *ctx, __s32 rel_x, __s32 rel_y) = (void *) 92; + +/* + * bpf_spin_lock + * + * Acquire a spinlock represented by the pointer *lock*, which is + * stored as part of a value of a map. Taking the lock allows to + * safely update the rest of the fields in that value. The + * spinlock can (and must) later be released with a call to + * **bpf_spin_unlock**\ (\ *lock*\ ). + * + * Spinlocks in BPF programs come with a number of restrictions + * and constraints: + * + * * **bpf_spin_lock** objects are only allowed inside maps of + * types **BPF_MAP_TYPE_HASH** and **BPF_MAP_TYPE_ARRAY** (this + * list could be extended in the future). + * * BTF description of the map is mandatory. + * * The BPF program can take ONE lock at a time, since taking two + * or more could cause dead locks. + * * Only one **struct bpf_spin_lock** is allowed per map element. + * * When the lock is taken, calls (either BPF to BPF or helpers) + * are not allowed. + * * The **BPF_LD_ABS** and **BPF_LD_IND** instructions are not + * allowed inside a spinlock-ed region. + * * The BPF program MUST call **bpf_spin_unlock**\ () to release + * the lock, on all execution paths, before it returns. + * * The BPF program can access **struct bpf_spin_lock** only via + * the **bpf_spin_lock**\ () and **bpf_spin_unlock**\ () + * helpers. Loading or storing data into the **struct + * bpf_spin_lock** *lock*\ **;** field of a map is not allowed. + * * To use the **bpf_spin_lock**\ () helper, the BTF description + * of the map value must be a struct and have **struct + * bpf_spin_lock** *anyname*\ **;** field at the top level. + * Nested lock inside another struct is not allowed. + * * The **struct bpf_spin_lock** *lock* field in a map value must + * be aligned on a multiple of 4 bytes in that value. + * * Syscall with command **BPF_MAP_LOOKUP_ELEM** does not copy + * the **bpf_spin_lock** field to user space. + * * Syscall with command **BPF_MAP_UPDATE_ELEM**, or update from + * a BPF program, do not update the **bpf_spin_lock** field. + * * **bpf_spin_lock** cannot be on the stack or inside a + * networking packet (it can only be inside of a map values). + * * **bpf_spin_lock** is available to root only. + * * Tracing programs and socket filter programs cannot use + * **bpf_spin_lock**\ () due to insufficient preemption checks + * (but this may change in the future). + * * **bpf_spin_lock** is not allowed in inner maps of map-in-map. + * + * Returns + * 0 + */ +static long (*bpf_spin_lock)(struct bpf_spin_lock *lock) = (void *) 93; + +/* + * bpf_spin_unlock + * + * Release the *lock* previously locked by a call to + * **bpf_spin_lock**\ (\ *lock*\ ). + * + * Returns + * 0 + */ +static long (*bpf_spin_unlock)(struct bpf_spin_lock *lock) = (void *) 94; + +/* + * bpf_sk_fullsock + * + * This helper gets a **struct bpf_sock** pointer such + * that all the fields in this **bpf_sock** can be accessed. + * + * Returns + * A **struct bpf_sock** pointer on success, or **NULL** in + * case of failure. + */ +static struct bpf_sock *(*bpf_sk_fullsock)(struct bpf_sock *sk) = (void *) 95; + +/* + * bpf_tcp_sock + * + * This helper gets a **struct bpf_tcp_sock** pointer from a + * **struct bpf_sock** pointer. + * + * Returns + * A **struct bpf_tcp_sock** pointer on success, or **NULL** in + * case of failure. + */ +static struct bpf_tcp_sock *(*bpf_tcp_sock)(struct bpf_sock *sk) = (void *) 96; + +/* + * bpf_skb_ecn_set_ce + * + * Set ECN (Explicit Congestion Notification) field of IP header + * to **CE** (Congestion Encountered) if current value is **ECT** + * (ECN Capable Transport). Otherwise, do nothing. Works with IPv6 + * and IPv4. + * + * Returns + * 1 if the **CE** flag is set (either by the current helper call + * or because it was already present), 0 if it is not set. + */ +static long (*bpf_skb_ecn_set_ce)(struct __sk_buff *skb) = (void *) 97; + +/* + * bpf_get_listener_sock + * + * Return a **struct bpf_sock** pointer in **TCP_LISTEN** state. + * **bpf_sk_release**\ () is unnecessary and not allowed. + * + * Returns + * A **struct bpf_sock** pointer on success, or **NULL** in + * case of failure. + */ +static struct bpf_sock *(*bpf_get_listener_sock)(struct bpf_sock *sk) = (void *) 98; + +/* + * bpf_skc_lookup_tcp + * + * Look for TCP socket matching *tuple*, optionally in a child + * network namespace *netns*. The return value must be checked, + * and if non-**NULL**, released via **bpf_sk_release**\ (). + * + * This function is identical to **bpf_sk_lookup_tcp**\ (), except + * that it also returns timewait or request sockets. Use + * **bpf_sk_fullsock**\ () or **bpf_tcp_sock**\ () to access the + * full structure. + * + * This helper is available only if the kernel was compiled with + * **CONFIG_NET** configuration option. + * + * Returns + * Pointer to **struct bpf_sock**, or **NULL** in case of failure. + * For sockets with reuseport option, the **struct bpf_sock** + * result is from *reuse*\ **->socks**\ [] using the hash of the + * tuple. + */ +static struct bpf_sock *(*bpf_skc_lookup_tcp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 99; + +/* + * bpf_tcp_check_syncookie + * + * Check whether *iph* and *th* contain a valid SYN cookie ACK for + * the listening socket in *sk*. + * + * *iph* points to the start of the IPv4 or IPv6 header, while + * *iph_len* contains **sizeof**\ (**struct iphdr**) or + * **sizeof**\ (**struct ip6hdr**). + * + * *th* points to the start of the TCP header, while *th_len* + * contains **sizeof**\ (**struct tcphdr**). + * + * Returns + * 0 if *iph* and *th* are a valid SYN cookie ACK, or a negative + * error otherwise. + */ +static long (*bpf_tcp_check_syncookie)(struct bpf_sock *sk, void *iph, __u32 iph_len, struct tcphdr *th, __u32 th_len) = (void *) 100; + +/* + * bpf_sysctl_get_name + * + * Get name of sysctl in /proc/sys/ and copy it into provided by + * program buffer *buf* of size *buf_len*. + * + * The buffer is always NUL terminated, unless it's zero-sized. + * + * If *flags* is zero, full name (e.g. "net/ipv4/tcp_mem") is + * copied. Use **BPF_F_SYSCTL_BASE_NAME** flag to copy base name + * only (e.g. "tcp_mem"). + * + * Returns + * Number of character copied (not including the trailing NUL). + * + * **-E2BIG** if the buffer wasn't big enough (*buf* will contain + * truncated name in this case). + */ +static long (*bpf_sysctl_get_name)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len, __u64 flags) = (void *) 101; + +/* + * bpf_sysctl_get_current_value + * + * Get current value of sysctl as it is presented in /proc/sys + * (incl. newline, etc), and copy it as a string into provided + * by program buffer *buf* of size *buf_len*. + * + * The whole value is copied, no matter what file position user + * space issued e.g. sys_read at. + * + * The buffer is always NUL terminated, unless it's zero-sized. + * + * Returns + * Number of character copied (not including the trailing NUL). + * + * **-E2BIG** if the buffer wasn't big enough (*buf* will contain + * truncated name in this case). + * + * **-EINVAL** if current value was unavailable, e.g. because + * sysctl is uninitialized and read returns -EIO for it. + */ +static long (*bpf_sysctl_get_current_value)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len) = (void *) 102; + +/* + * bpf_sysctl_get_new_value + * + * Get new value being written by user space to sysctl (before + * the actual write happens) and copy it as a string into + * provided by program buffer *buf* of size *buf_len*. + * + * User space may write new value at file position > 0. + * + * The buffer is always NUL terminated, unless it's zero-sized. + * + * Returns + * Number of character copied (not including the trailing NUL). + * + * **-E2BIG** if the buffer wasn't big enough (*buf* will contain + * truncated name in this case). + * + * **-EINVAL** if sysctl is being read. + */ +static long (*bpf_sysctl_get_new_value)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len) = (void *) 103; + +/* + * bpf_sysctl_set_new_value + * + * Override new value being written by user space to sysctl with + * value provided by program in buffer *buf* of size *buf_len*. + * + * *buf* should contain a string in same form as provided by user + * space on sysctl write. + * + * User space may write new value at file position > 0. To override + * the whole sysctl value file position should be set to zero. + * + * Returns + * 0 on success. + * + * **-E2BIG** if the *buf_len* is too big. + * + * **-EINVAL** if sysctl is being read. + */ +static long (*bpf_sysctl_set_new_value)(struct bpf_sysctl *ctx, const char *buf, unsigned long buf_len) = (void *) 104; + +/* + * bpf_strtol + * + * Convert the initial part of the string from buffer *buf* of + * size *buf_len* to a long integer according to the given base + * and save the result in *res*. + * + * The string may begin with an arbitrary amount of white space + * (as determined by **isspace**\ (3)) followed by a single + * optional '**-**' sign. + * + * Five least significant bits of *flags* encode base, other bits + * are currently unused. + * + * Base must be either 8, 10, 16 or 0 to detect it automatically + * similar to user space **strtol**\ (3). + * + * Returns + * Number of characters consumed on success. Must be positive but + * no more than *buf_len*. + * + * **-EINVAL** if no valid digits were found or unsupported base + * was provided. + * + * **-ERANGE** if resulting value was out of range. + */ +static long (*bpf_strtol)(const char *buf, unsigned long buf_len, __u64 flags, long *res) = (void *) 105; + +/* + * bpf_strtoul + * + * Convert the initial part of the string from buffer *buf* of + * size *buf_len* to an unsigned long integer according to the + * given base and save the result in *res*. + * + * The string may begin with an arbitrary amount of white space + * (as determined by **isspace**\ (3)). + * + * Five least significant bits of *flags* encode base, other bits + * are currently unused. + * + * Base must be either 8, 10, 16 or 0 to detect it automatically + * similar to user space **strtoul**\ (3). + * + * Returns + * Number of characters consumed on success. Must be positive but + * no more than *buf_len*. + * + * **-EINVAL** if no valid digits were found or unsupported base + * was provided. + * + * **-ERANGE** if resulting value was out of range. + */ +static long (*bpf_strtoul)(const char *buf, unsigned long buf_len, __u64 flags, unsigned long *res) = (void *) 106; + +/* + * bpf_sk_storage_get + * + * Get a bpf-local-storage from a *sk*. + * + * Logically, it could be thought of getting the value from + * a *map* with *sk* as the **key**. From this + * perspective, the usage is not much different from + * **bpf_map_lookup_elem**\ (*map*, **&**\ *sk*) except this + * helper enforces the key must be a full socket and the map must + * be a **BPF_MAP_TYPE_SK_STORAGE** also. + * + * Underneath, the value is stored locally at *sk* instead of + * the *map*. The *map* is used as the bpf-local-storage + * "type". The bpf-local-storage "type" (i.e. the *map*) is + * searched against all bpf-local-storages residing at *sk*. + * + * An optional *flags* (**BPF_SK_STORAGE_GET_F_CREATE**) can be + * used such that a new bpf-local-storage will be + * created if one does not exist. *value* can be used + * together with **BPF_SK_STORAGE_GET_F_CREATE** to specify + * the initial value of a bpf-local-storage. If *value* is + * **NULL**, the new bpf-local-storage will be zero initialized. + * + * Returns + * A bpf-local-storage pointer is returned on success. + * + * **NULL** if not found or there was an error in adding + * a new bpf-local-storage. + */ +static void *(*bpf_sk_storage_get)(void *map, struct bpf_sock *sk, void *value, __u64 flags) = (void *) 107; + +/* + * bpf_sk_storage_delete + * + * Delete a bpf-local-storage from a *sk*. + * + * Returns + * 0 on success. + * + * **-ENOENT** if the bpf-local-storage cannot be found. + */ +static long (*bpf_sk_storage_delete)(void *map, struct bpf_sock *sk) = (void *) 108; + +/* + * bpf_send_signal + * + * Send signal *sig* to the process of the current task. + * The signal may be delivered to any of this process's threads. + * + * Returns + * 0 on success or successfully queued. + * + * **-EBUSY** if work queue under nmi is full. + * + * **-EINVAL** if *sig* is invalid. + * + * **-EPERM** if no permission to send the *sig*. + * + * **-EAGAIN** if bpf program can try again. + */ +static long (*bpf_send_signal)(__u32 sig) = (void *) 109; + +/* + * bpf_tcp_gen_syncookie + * + * Try to issue a SYN cookie for the packet with corresponding + * IP/TCP headers, *iph* and *th*, on the listening socket in *sk*. + * + * *iph* points to the start of the IPv4 or IPv6 header, while + * *iph_len* contains **sizeof**\ (**struct iphdr**) or + * **sizeof**\ (**struct ip6hdr**). + * + * *th* points to the start of the TCP header, while *th_len* + * contains the length of the TCP header. + * + * Returns + * On success, lower 32 bits hold the generated SYN cookie in + * followed by 16 bits which hold the MSS value for that cookie, + * and the top 16 bits are unused. + * + * On failure, the returned value is one of the following: + * + * **-EINVAL** SYN cookie cannot be issued due to error + * + * **-ENOENT** SYN cookie should not be issued (no SYN flood) + * + * **-EOPNOTSUPP** kernel configuration does not enable SYN cookies + * + * **-EPROTONOSUPPORT** IP packet version is not 4 or 6 + */ +static __s64 (*bpf_tcp_gen_syncookie)(struct bpf_sock *sk, void *iph, __u32 iph_len, struct tcphdr *th, __u32 th_len) = (void *) 110; + +/* + * bpf_skb_output + * + * Write raw *data* blob into a special BPF perf event held by + * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf + * event must have the following attributes: **PERF_SAMPLE_RAW** + * as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and + * **PERF_COUNT_SW_BPF_OUTPUT** as **config**. + * + * The *flags* are used to indicate the index in *map* for which + * the value must be put, masked with **BPF_F_INDEX_MASK**. + * Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU** + * to indicate that the index of the current CPU core should be + * used. + * + * The value to write, of *size*, is passed through eBPF stack and + * pointed by *data*. + * + * *ctx* is a pointer to in-kernel struct sk_buff. + * + * This helper is similar to **bpf_perf_event_output**\ () but + * restricted to raw_tracepoint bpf programs. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_skb_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 111; + +/* + * bpf_probe_read_user + * + * Safely attempt to read *size* bytes from user space address + * *unsafe_ptr* and store the data in *dst*. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_probe_read_user)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 112; + +/* + * bpf_probe_read_kernel + * + * Safely attempt to read *size* bytes from kernel space address + * *unsafe_ptr* and store the data in *dst*. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_probe_read_kernel)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 113; + +/* + * bpf_probe_read_user_str + * + * Copy a NUL terminated string from an unsafe user address + * *unsafe_ptr* to *dst*. The *size* should include the + * terminating NUL byte. In case the string length is smaller than + * *size*, the target is not padded with further NUL bytes. If the + * string length is larger than *size*, just *size*-1 bytes are + * copied and the last byte is set to NUL. + * + * On success, the length of the copied string is returned. This + * makes this helper useful in tracing programs for reading + * strings, and more importantly to get its length at runtime. See + * the following snippet: + * + * :: + * + * SEC("kprobe/sys_open") + * void bpf_sys_open(struct pt_regs *ctx) + * { + * char buf[PATHLEN]; // PATHLEN is defined to 256 + * int res = bpf_probe_read_user_str(buf, sizeof(buf), + * ctx->di); + * + * // Consume buf, for example push it to + * // userspace via bpf_perf_event_output(); we + * // can use res (the string length) as event + * // size, after checking its boundaries. + * } + * + * In comparison, using **bpf_probe_read_user**\ () helper here + * instead to read the string would require to estimate the length + * at compile time, and would often result in copying more memory + * than necessary. + * + * Another useful use case is when parsing individual process + * arguments or individual environment variables navigating + * *current*\ **->mm->arg_start** and *current*\ + * **->mm->env_start**: using this helper and the return value, + * one can quickly iterate at the right offset of the memory area. + * + * Returns + * On success, the strictly positive length of the string, + * including the trailing NUL character. On error, a negative + * value. + */ +static long (*bpf_probe_read_user_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 114; + +/* + * bpf_probe_read_kernel_str + * + * Copy a NUL terminated string from an unsafe kernel address *unsafe_ptr* + * to *dst*. Same semantics as with **bpf_probe_read_user_str**\ () apply. + * + * Returns + * On success, the strictly positive length of the string, including + * the trailing NUL character. On error, a negative value. + */ +static long (*bpf_probe_read_kernel_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 115; + +/* + * bpf_tcp_send_ack + * + * Send out a tcp-ack. *tp* is the in-kernel struct **tcp_sock**. + * *rcv_nxt* is the ack_seq to be sent out. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_tcp_send_ack)(void *tp, __u32 rcv_nxt) = (void *) 116; + +/* + * bpf_send_signal_thread + * + * Send signal *sig* to the thread corresponding to the current task. + * + * Returns + * 0 on success or successfully queued. + * + * **-EBUSY** if work queue under nmi is full. + * + * **-EINVAL** if *sig* is invalid. + * + * **-EPERM** if no permission to send the *sig*. + * + * **-EAGAIN** if bpf program can try again. + */ +static long (*bpf_send_signal_thread)(__u32 sig) = (void *) 117; + +/* + * bpf_jiffies64 + * + * Obtain the 64bit jiffies + * + * Returns + * The 64 bit jiffies + */ +static __u64 (*bpf_jiffies64)(void) = (void *) 118; + +/* + * bpf_read_branch_records + * + * For an eBPF program attached to a perf event, retrieve the + * branch records (**struct perf_branch_entry**) associated to *ctx* + * and store it in the buffer pointed by *buf* up to size + * *size* bytes. + * + * Returns + * On success, number of bytes written to *buf*. On error, a + * negative value. + * + * The *flags* can be set to **BPF_F_GET_BRANCH_RECORDS_SIZE** to + * instead return the number of bytes required to store all the + * branch entries. If this flag is set, *buf* may be NULL. + * + * **-EINVAL** if arguments invalid or **size** not a multiple + * of **sizeof**\ (**struct perf_branch_entry**\ ). + * + * **-ENOENT** if architecture does not support branch records. + */ +static long (*bpf_read_branch_records)(struct bpf_perf_event_data *ctx, void *buf, __u32 size, __u64 flags) = (void *) 119; + +/* + * bpf_get_ns_current_pid_tgid + * + * Returns 0 on success, values for *pid* and *tgid* as seen from the current + * *namespace* will be returned in *nsdata*. + * + * Returns + * 0 on success, or one of the following in case of failure: + * + * **-EINVAL** if dev and inum supplied don't match dev_t and inode number + * with nsfs of current task, or if dev conversion to dev_t lost high bits. + * + * **-ENOENT** if pidns does not exists for the current task. + */ +static long (*bpf_get_ns_current_pid_tgid)(__u64 dev, __u64 ino, struct bpf_pidns_info *nsdata, __u32 size) = (void *) 120; + +/* + * bpf_xdp_output + * + * Write raw *data* blob into a special BPF perf event held by + * *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf + * event must have the following attributes: **PERF_SAMPLE_RAW** + * as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and + * **PERF_COUNT_SW_BPF_OUTPUT** as **config**. + * + * The *flags* are used to indicate the index in *map* for which + * the value must be put, masked with **BPF_F_INDEX_MASK**. + * Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU** + * to indicate that the index of the current CPU core should be + * used. + * + * The value to write, of *size*, is passed through eBPF stack and + * pointed by *data*. + * + * *ctx* is a pointer to in-kernel struct xdp_buff. + * + * This helper is similar to **bpf_perf_eventoutput**\ () but + * restricted to raw_tracepoint bpf programs. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_xdp_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 121; + +/* + * bpf_get_netns_cookie + * + * Retrieve the cookie (generated by the kernel) of the network + * namespace the input *ctx* is associated with. The network + * namespace cookie remains stable for its lifetime and provides + * a global identifier that can be assumed unique. If *ctx* is + * NULL, then the helper returns the cookie for the initial + * network namespace. The cookie itself is very similar to that + * of **bpf_get_socket_cookie**\ () helper, but for network + * namespaces instead of sockets. + * + * Returns + * A 8-byte long opaque number. + */ +static __u64 (*bpf_get_netns_cookie)(void *ctx) = (void *) 122; + +/* + * bpf_get_current_ancestor_cgroup_id + * + * Return id of cgroup v2 that is ancestor of the cgroup associated + * with the current task at the *ancestor_level*. The root cgroup + * is at *ancestor_level* zero and each step down the hierarchy + * increments the level. If *ancestor_level* == level of cgroup + * associated with the current task, then return value will be the + * same as that of **bpf_get_current_cgroup_id**\ (). + * + * The helper is useful to implement policies based on cgroups + * that are upper in hierarchy than immediate cgroup associated + * with the current task. + * + * The format of returned id and helper limitations are same as in + * **bpf_get_current_cgroup_id**\ (). + * + * Returns + * The id is returned or 0 in case the id could not be retrieved. + */ +static __u64 (*bpf_get_current_ancestor_cgroup_id)(int ancestor_level) = (void *) 123; + +/* + * bpf_sk_assign + * + * Assign the *sk* to the *skb*. When combined with appropriate + * routing configuration to receive the packet towards the socket, + * will cause *skb* to be delivered to the specified socket. + * Subsequent redirection of *skb* via **bpf_redirect**\ (), + * **bpf_clone_redirect**\ () or other methods outside of BPF may + * interfere with successful delivery to the socket. + * + * This operation is only valid from TC ingress path. + * + * The *flags* argument must be zero. + * + * Returns + * 0 on success, or a negative error in case of failure: + * + * **-EINVAL** if specified *flags* are not supported. + * + * **-ENOENT** if the socket is unavailable for assignment. + * + * **-ENETUNREACH** if the socket is unreachable (wrong netns). + * + * **-EOPNOTSUPP** if the operation is not supported, for example + * a call from outside of TC ingress. + * + * **-ESOCKTNOSUPPORT** if the socket type is not supported + * (reuseport). + */ +static long (*bpf_sk_assign)(struct __sk_buff *skb, struct bpf_sock *sk, __u64 flags) = (void *) 124; + +/* + * bpf_ktime_get_boot_ns + * + * Return the time elapsed since system boot, in nanoseconds. + * Does include the time the system was suspended. + * See: **clock_gettime**\ (**CLOCK_BOOTTIME**) + * + * Returns + * Current *ktime*. + */ +static __u64 (*bpf_ktime_get_boot_ns)(void) = (void *) 125; + +/* + * bpf_seq_printf + * + * **bpf_seq_printf**\ () uses seq_file **seq_printf**\ () to print + * out the format string. + * The *m* represents the seq_file. The *fmt* and *fmt_size* are for + * the format string itself. The *data* and *data_len* are format string + * arguments. The *data* are a **u64** array and corresponding format string + * values are stored in the array. For strings and pointers where pointees + * are accessed, only the pointer values are stored in the *data* array. + * The *data_len* is the size of *data* in bytes. + * + * Formats **%s**, **%p{i,I}{4,6}** requires to read kernel memory. + * Reading kernel memory may fail due to either invalid address or + * valid address but requiring a major memory fault. If reading kernel memory + * fails, the string for **%s** will be an empty string, and the ip + * address for **%p{i,I}{4,6}** will be 0. Not returning error to + * bpf program is consistent with what **bpf_trace_printk**\ () does for now. + * + * Returns + * 0 on success, or a negative error in case of failure: + * + * **-EBUSY** if per-CPU memory copy buffer is busy, can try again + * by returning 1 from bpf program. + * + * **-EINVAL** if arguments are invalid, or if *fmt* is invalid/unsupported. + * + * **-E2BIG** if *fmt* contains too many format specifiers. + * + * **-EOVERFLOW** if an overflow happened: The same object will be tried again. + */ +static long (*bpf_seq_printf)(struct seq_file *m, const char *fmt, __u32 fmt_size, const void *data, __u32 data_len) = (void *) 126; + +/* + * bpf_seq_write + * + * **bpf_seq_write**\ () uses seq_file **seq_write**\ () to write the data. + * The *m* represents the seq_file. The *data* and *len* represent the + * data to write in bytes. + * + * Returns + * 0 on success, or a negative error in case of failure: + * + * **-EOVERFLOW** if an overflow happened: The same object will be tried again. + */ +static long (*bpf_seq_write)(struct seq_file *m, const void *data, __u32 len) = (void *) 127; + +/* + * bpf_sk_cgroup_id + * + * Return the cgroup v2 id of the socket *sk*. + * + * *sk* must be a non-**NULL** pointer to a full socket, e.g. one + * returned from **bpf_sk_lookup_xxx**\ (), + * **bpf_sk_fullsock**\ (), etc. The format of returned id is + * same as in **bpf_skb_cgroup_id**\ (). + * + * This helper is available only if the kernel was compiled with + * the **CONFIG_SOCK_CGROUP_DATA** configuration option. + * + * Returns + * The id is returned or 0 in case the id could not be retrieved. + */ +static __u64 (*bpf_sk_cgroup_id)(struct bpf_sock *sk) = (void *) 128; + +/* + * bpf_sk_ancestor_cgroup_id + * + * Return id of cgroup v2 that is ancestor of cgroup associated + * with the *sk* at the *ancestor_level*. The root cgroup is at + * *ancestor_level* zero and each step down the hierarchy + * increments the level. If *ancestor_level* == level of cgroup + * associated with *sk*, then return value will be same as that + * of **bpf_sk_cgroup_id**\ (). + * + * The helper is useful to implement policies based on cgroups + * that are upper in hierarchy than immediate cgroup associated + * with *sk*. + * + * The format of returned id and helper limitations are same as in + * **bpf_sk_cgroup_id**\ (). + * + * Returns + * The id is returned or 0 in case the id could not be retrieved. + */ +static __u64 (*bpf_sk_ancestor_cgroup_id)(struct bpf_sock *sk, int ancestor_level) = (void *) 129; + +/* + * bpf_ringbuf_output + * + * Copy *size* bytes from *data* into a ring buffer *ringbuf*. + * If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification + * of new data availability is sent. + * If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification + * of new data availability is sent unconditionally. + * + * Returns + * 0 on success, or a negative error in case of failure. + */ +static long (*bpf_ringbuf_output)(void *ringbuf, void *data, __u64 size, __u64 flags) = (void *) 130; + +/* + * bpf_ringbuf_reserve + * + * Reserve *size* bytes of payload in a ring buffer *ringbuf*. + * + * Returns + * Valid pointer with *size* bytes of memory available; NULL, + * otherwise. + */ +static void *(*bpf_ringbuf_reserve)(void *ringbuf, __u64 size, __u64 flags) = (void *) 131; + +/* + * bpf_ringbuf_submit + * + * Submit reserved ring buffer sample, pointed to by *data*. + * If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification + * of new data availability is sent. + * If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification + * of new data availability is sent unconditionally. + * + * Returns + * Nothing. Always succeeds. + */ +static void (*bpf_ringbuf_submit)(void *data, __u64 flags) = (void *) 132; + +/* + * bpf_ringbuf_discard + * + * Discard reserved ring buffer sample, pointed to by *data*. + * If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification + * of new data availability is sent. + * If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification + * of new data availability is sent unconditionally. + * + * Returns + * Nothing. Always succeeds. + */ +static void (*bpf_ringbuf_discard)(void *data, __u64 flags) = (void *) 133; + +/* + * bpf_ringbuf_query + * + * Query various characteristics of provided ring buffer. What + * exactly is queries is determined by *flags*: + * + * * **BPF_RB_AVAIL_DATA**: Amount of data not yet consumed. + * * **BPF_RB_RING_SIZE**: The size of ring buffer. + * * **BPF_RB_CONS_POS**: Consumer position (can wrap around). + * * **BPF_RB_PROD_POS**: Producer(s) position (can wrap around). + * + * Data returned is just a momentary snapshot of actual values + * and could be inaccurate, so this facility should be used to + * power heuristics and for reporting, not to make 100% correct + * calculation. + * + * Returns + * Requested value, or 0, if *flags* are not recognized. + */ +static __u64 (*bpf_ringbuf_query)(void *ringbuf, __u64 flags) = (void *) 134; + +/* + * bpf_csum_level + * + * Change the skbs checksum level by one layer up or down, or + * reset it entirely to none in order to have the stack perform + * checksum validation. The level is applicable to the following + * protocols: TCP, UDP, GRE, SCTP, FCOE. For example, a decap of + * | ETH | IP | UDP | GUE | IP | TCP | into | ETH | IP | TCP | + * through **bpf_skb_adjust_room**\ () helper with passing in + * **BPF_F_ADJ_ROOM_NO_CSUM_RESET** flag would require one call + * to **bpf_csum_level**\ () with **BPF_CSUM_LEVEL_DEC** since + * the UDP header is removed. Similarly, an encap of the latter + * into the former could be accompanied by a helper call to + * **bpf_csum_level**\ () with **BPF_CSUM_LEVEL_INC** if the + * skb is still intended to be processed in higher layers of the + * stack instead of just egressing at tc. + * + * There are three supported level settings at this time: + * + * * **BPF_CSUM_LEVEL_INC**: Increases skb->csum_level for skbs + * with CHECKSUM_UNNECESSARY. + * * **BPF_CSUM_LEVEL_DEC**: Decreases skb->csum_level for skbs + * with CHECKSUM_UNNECESSARY. + * * **BPF_CSUM_LEVEL_RESET**: Resets skb->csum_level to 0 and + * sets CHECKSUM_NONE to force checksum validation by the stack. + * * **BPF_CSUM_LEVEL_QUERY**: No-op, returns the current + * skb->csum_level. + * + * Returns + * 0 on success, or a negative error in case of failure. In the + * case of **BPF_CSUM_LEVEL_QUERY**, the current skb->csum_level + * is returned or the error code -EACCES in case the skb is not + * subject to CHECKSUM_UNNECESSARY. + */ +static long (*bpf_csum_level)(struct __sk_buff *skb, __u64 level) = (void *) 135; + +/* + * bpf_skc_to_tcp6_sock + * + * Dynamically cast a *sk* pointer to a *tcp6_sock* pointer. + * + * Returns + * *sk* if casting is valid, or NULL otherwise. + */ +static struct tcp6_sock *(*bpf_skc_to_tcp6_sock)(void *sk) = (void *) 136; + +/* + * bpf_skc_to_tcp_sock + * + * Dynamically cast a *sk* pointer to a *tcp_sock* pointer. + * + * Returns + * *sk* if casting is valid, or NULL otherwise. + */ +static struct tcp_sock *(*bpf_skc_to_tcp_sock)(void *sk) = (void *) 137; + +/* + * bpf_skc_to_tcp_timewait_sock + * + * Dynamically cast a *sk* pointer to a *tcp_timewait_sock* pointer. + * + * Returns + * *sk* if casting is valid, or NULL otherwise. + */ +static struct tcp_timewait_sock *(*bpf_skc_to_tcp_timewait_sock)(void *sk) = (void *) 138; + +/* + * bpf_skc_to_tcp_request_sock + * + * Dynamically cast a *sk* pointer to a *tcp_request_sock* pointer. + * + * Returns + * *sk* if casting is valid, or NULL otherwise. + */ +static struct tcp_request_sock *(*bpf_skc_to_tcp_request_sock)(void *sk) = (void *) 139; + +/* + * bpf_skc_to_udp6_sock + * + * Dynamically cast a *sk* pointer to a *udp6_sock* pointer. + * + * Returns + * *sk* if casting is valid, or NULL otherwise. + */ +static struct udp6_sock *(*bpf_skc_to_udp6_sock)(void *sk) = (void *) 140; + +/* + * bpf_get_task_stack + * + * Return a user or a kernel stack in bpf program provided buffer. + * To achieve this, the helper needs *task*, which is a valid + * pointer to struct task_struct. To store the stacktrace, the + * bpf program provides *buf* with a nonnegative *size*. + * + * The last argument, *flags*, holds the number of stack frames to + * skip (from 0 to 255), masked with + * **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set + * the following flags: + * + * **BPF_F_USER_STACK** + * Collect a user space stack instead of a kernel stack. + * **BPF_F_USER_BUILD_ID** + * Collect buildid+offset instead of ips for user stack, + * only valid if **BPF_F_USER_STACK** is also specified. + * + * **bpf_get_task_stack**\ () can collect up to + * **PERF_MAX_STACK_DEPTH** both kernel and user frames, subject + * to sufficient large buffer size. Note that + * this limit can be controlled with the **sysctl** program, and + * that it should be manually increased in order to profile long + * user stacks (such as stacks for Java programs). To do so, use: + * + * :: + * + * # sysctl kernel.perf_event_max_stack= + * + * Returns + * A non-negative value equal to or less than *size* on success, + * or a negative error in case of failure. + */ +static long (*bpf_get_task_stack)(struct task_struct *task, void *buf, __u32 size, __u64 flags) = (void *) 141; + + diff --git a/src/bpfswitch/include/bpf/bpf_helpers.h b/src/bpfswitch/include/bpf/bpf_helpers.h new file mode 100644 index 00000000..a510d8ed --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf_helpers.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BPF_HELPERS__ +#define __BPF_HELPERS__ + +/* + * Note that bpf programs need to include either + * vmlinux.h (auto-generated from BTF) or linux/types.h + * in advance since bpf_helper_defs.h uses such types + * as __u64. + */ +#include "bpf_helper_defs.h" + +#define __uint(name, val) int (*name)[val] +#define __type(name, val) typeof(val) *name +#define __array(name, val) typeof(val) *name[] + +/* Helper macro to print out debug messages */ +#define bpf_printk(fmt, ...) \ +({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ +}) + +/* + * Helper macro to place programs, maps, license in + * different sections in elf_bpf file. Section names + * are interpreted by elf_bpf loader + */ +#define SEC(NAME) __attribute__((section(NAME), used)) + +#ifndef __always_inline +#define __always_inline __attribute__((always_inline)) +#endif +#ifndef __weak +#define __weak __attribute__((weak)) +#endif + +/* + * Helper macro to manipulate data structures + */ +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) +#endif +#ifndef container_of +#define container_of(ptr, type, member) \ + ({ \ + void *__mptr = (void *)(ptr); \ + ((type *)(__mptr - offsetof(type, member))); \ + }) +#endif + +/* + * Helper structure used by eBPF C program + * to describe BPF map attributes to libbpf loader + */ +struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; +}; + +enum libbpf_pin_type { + LIBBPF_PIN_NONE, + /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */ + LIBBPF_PIN_BY_NAME, +}; + +enum libbpf_tristate { + TRI_NO = 0, + TRI_YES = 1, + TRI_MODULE = 2, +}; + +#define __kconfig __attribute__((section(".kconfig"))) +#define __ksym __attribute__((section(".ksyms"))) + +#endif diff --git a/src/bpfswitch/include/bpf/bpf_tracing.h b/src/bpfswitch/include/bpf/bpf_tracing.h new file mode 100644 index 00000000..58eceb88 --- /dev/null +++ b/src/bpfswitch/include/bpf/bpf_tracing.h @@ -0,0 +1,432 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +#ifndef __BPF_TRACING_H__ +#define __BPF_TRACING_H__ + +/* Scan the ARCH passed in from ARCH env variable (see Makefile) */ +#if defined(__TARGET_ARCH_x86) + #define bpf_target_x86 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_s390) + #define bpf_target_s390 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_arm) + #define bpf_target_arm + #define bpf_target_defined +#elif defined(__TARGET_ARCH_arm64) + #define bpf_target_arm64 + #define bpf_target_defined +#elif defined(__TARGET_ARCH_mips) + #define bpf_target_mips + #define bpf_target_defined +#elif defined(__TARGET_ARCH_powerpc) + #define bpf_target_powerpc + #define bpf_target_defined +#elif defined(__TARGET_ARCH_sparc) + #define bpf_target_sparc + #define bpf_target_defined +#else + #undef bpf_target_defined +#endif + +/* Fall back to what the compiler says */ +#ifndef bpf_target_defined +#if defined(__x86_64__) + #define bpf_target_x86 +#elif defined(__s390__) + #define bpf_target_s390 +#elif defined(__arm__) + #define bpf_target_arm +#elif defined(__aarch64__) + #define bpf_target_arm64 +#elif defined(__mips__) + #define bpf_target_mips +#elif defined(__powerpc__) + #define bpf_target_powerpc +#elif defined(__sparc__) + #define bpf_target_sparc +#endif +#endif + +#if defined(bpf_target_x86) + +#if defined(__KERNEL__) || defined(__VMLINUX_H__) + +#define PT_REGS_PARM1(x) ((x)->di) +#define PT_REGS_PARM2(x) ((x)->si) +#define PT_REGS_PARM3(x) ((x)->dx) +#define PT_REGS_PARM4(x) ((x)->cx) +#define PT_REGS_PARM5(x) ((x)->r8) +#define PT_REGS_RET(x) ((x)->sp) +#define PT_REGS_FP(x) ((x)->bp) +#define PT_REGS_RC(x) ((x)->ax) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->ip) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), di) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), si) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), dx) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), cx) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), r8) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), sp) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), bp) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), ax) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), sp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), ip) + +#else + +#ifdef __i386__ +/* i386 kernel is built with -mregparm=3 */ +#define PT_REGS_PARM1(x) ((x)->eax) +#define PT_REGS_PARM2(x) ((x)->edx) +#define PT_REGS_PARM3(x) ((x)->ecx) +#define PT_REGS_PARM4(x) 0 +#define PT_REGS_PARM5(x) 0 +#define PT_REGS_RET(x) ((x)->esp) +#define PT_REGS_FP(x) ((x)->ebp) +#define PT_REGS_RC(x) ((x)->eax) +#define PT_REGS_SP(x) ((x)->esp) +#define PT_REGS_IP(x) ((x)->eip) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), eax) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), edx) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), ecx) +#define PT_REGS_PARM4_CORE(x) 0 +#define PT_REGS_PARM5_CORE(x) 0 +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), esp) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), ebp) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), eax) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), esp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), eip) + +#else + +#define PT_REGS_PARM1(x) ((x)->rdi) +#define PT_REGS_PARM2(x) ((x)->rsi) +#define PT_REGS_PARM3(x) ((x)->rdx) +#define PT_REGS_PARM4(x) ((x)->rcx) +#define PT_REGS_PARM5(x) ((x)->r8) +#define PT_REGS_RET(x) ((x)->rsp) +#define PT_REGS_FP(x) ((x)->rbp) +#define PT_REGS_RC(x) ((x)->rax) +#define PT_REGS_SP(x) ((x)->rsp) +#define PT_REGS_IP(x) ((x)->rip) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), rdi) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), rsi) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), rdx) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), rcx) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), r8) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), rsp) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), rbp) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), rax) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), rsp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), rip) + +#endif +#endif + +#elif defined(bpf_target_s390) + +/* s390 provides user_pt_regs instead of struct pt_regs to userspace */ +struct pt_regs; +#define PT_REGS_S390 const volatile user_pt_regs +#define PT_REGS_PARM1(x) (((PT_REGS_S390 *)(x))->gprs[2]) +#define PT_REGS_PARM2(x) (((PT_REGS_S390 *)(x))->gprs[3]) +#define PT_REGS_PARM3(x) (((PT_REGS_S390 *)(x))->gprs[4]) +#define PT_REGS_PARM4(x) (((PT_REGS_S390 *)(x))->gprs[5]) +#define PT_REGS_PARM5(x) (((PT_REGS_S390 *)(x))->gprs[6]) +#define PT_REGS_RET(x) (((PT_REGS_S390 *)(x))->gprs[14]) +/* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_FP(x) (((PT_REGS_S390 *)(x))->gprs[11]) +#define PT_REGS_RC(x) (((PT_REGS_S390 *)(x))->gprs[2]) +#define PT_REGS_SP(x) (((PT_REGS_S390 *)(x))->gprs[15]) +#define PT_REGS_IP(x) (((PT_REGS_S390 *)(x))->psw.addr) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[2]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[3]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[4]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[5]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[6]) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[14]) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[11]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[2]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), gprs[15]) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((PT_REGS_S390 *)(x), psw.addr) + +#elif defined(bpf_target_arm) + +#define PT_REGS_PARM1(x) ((x)->uregs[0]) +#define PT_REGS_PARM2(x) ((x)->uregs[1]) +#define PT_REGS_PARM3(x) ((x)->uregs[2]) +#define PT_REGS_PARM4(x) ((x)->uregs[3]) +#define PT_REGS_PARM5(x) ((x)->uregs[4]) +#define PT_REGS_RET(x) ((x)->uregs[14]) +#define PT_REGS_FP(x) ((x)->uregs[11]) /* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_RC(x) ((x)->uregs[0]) +#define PT_REGS_SP(x) ((x)->uregs[13]) +#define PT_REGS_IP(x) ((x)->uregs[12]) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), uregs[0]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), uregs[1]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), uregs[2]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), uregs[3]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), uregs[4]) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), uregs[14]) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), uregs[11]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), uregs[0]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), uregs[13]) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), uregs[12]) + +#elif defined(bpf_target_arm64) + +/* arm64 provides struct user_pt_regs instead of struct pt_regs to userspace */ +struct pt_regs; +#define PT_REGS_ARM64 const volatile struct user_pt_regs +#define PT_REGS_PARM1(x) (((PT_REGS_ARM64 *)(x))->regs[0]) +#define PT_REGS_PARM2(x) (((PT_REGS_ARM64 *)(x))->regs[1]) +#define PT_REGS_PARM3(x) (((PT_REGS_ARM64 *)(x))->regs[2]) +#define PT_REGS_PARM4(x) (((PT_REGS_ARM64 *)(x))->regs[3]) +#define PT_REGS_PARM5(x) (((PT_REGS_ARM64 *)(x))->regs[4]) +#define PT_REGS_RET(x) (((PT_REGS_ARM64 *)(x))->regs[30]) +/* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_FP(x) (((PT_REGS_ARM64 *)(x))->regs[29]) +#define PT_REGS_RC(x) (((PT_REGS_ARM64 *)(x))->regs[0]) +#define PT_REGS_SP(x) (((PT_REGS_ARM64 *)(x))->sp) +#define PT_REGS_IP(x) (((PT_REGS_ARM64 *)(x))->pc) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[0]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[1]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[2]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[3]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[4]) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[30]) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[29]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), regs[0]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), sp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((PT_REGS_ARM64 *)(x), pc) + +#elif defined(bpf_target_mips) + +#define PT_REGS_PARM1(x) ((x)->regs[4]) +#define PT_REGS_PARM2(x) ((x)->regs[5]) +#define PT_REGS_PARM3(x) ((x)->regs[6]) +#define PT_REGS_PARM4(x) ((x)->regs[7]) +#define PT_REGS_PARM5(x) ((x)->regs[8]) +#define PT_REGS_RET(x) ((x)->regs[31]) +#define PT_REGS_FP(x) ((x)->regs[30]) /* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_RC(x) ((x)->regs[1]) +#define PT_REGS_SP(x) ((x)->regs[29]) +#define PT_REGS_IP(x) ((x)->cp0_epc) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), regs[4]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), regs[5]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), regs[6]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), regs[7]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), regs[8]) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), regs[31]) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((x), regs[30]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), regs[1]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), regs[29]) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), cp0_epc) + +#elif defined(bpf_target_powerpc) + +#define PT_REGS_PARM1(x) ((x)->gpr[3]) +#define PT_REGS_PARM2(x) ((x)->gpr[4]) +#define PT_REGS_PARM3(x) ((x)->gpr[5]) +#define PT_REGS_PARM4(x) ((x)->gpr[6]) +#define PT_REGS_PARM5(x) ((x)->gpr[7]) +#define PT_REGS_RC(x) ((x)->gpr[3]) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->nip) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), gpr[3]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), gpr[4]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), gpr[5]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), gpr[6]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), gpr[7]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), gpr[3]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), sp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), nip) + +#elif defined(bpf_target_sparc) + +#define PT_REGS_PARM1(x) ((x)->u_regs[UREG_I0]) +#define PT_REGS_PARM2(x) ((x)->u_regs[UREG_I1]) +#define PT_REGS_PARM3(x) ((x)->u_regs[UREG_I2]) +#define PT_REGS_PARM4(x) ((x)->u_regs[UREG_I3]) +#define PT_REGS_PARM5(x) ((x)->u_regs[UREG_I4]) +#define PT_REGS_RET(x) ((x)->u_regs[UREG_I7]) +#define PT_REGS_RC(x) ((x)->u_regs[UREG_I0]) +#define PT_REGS_SP(x) ((x)->u_regs[UREG_FP]) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I0]) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I1]) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I2]) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I3]) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I4]) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I7]) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((x), u_regs[UREG_I0]) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((x), u_regs[UREG_FP]) + +/* Should this also be a bpf_target check for the sparc case? */ +#if defined(__arch64__) +#define PT_REGS_IP(x) ((x)->tpc) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), tpc) +#else +#define PT_REGS_IP(x) ((x)->pc) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), pc) +#endif + +#endif + +#if defined(bpf_target_powerpc) +#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; }) +#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP +#elif defined(bpf_target_sparc) +#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = PT_REGS_RET(ctx); }) +#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP +#else +#define BPF_KPROBE_READ_RET_IP(ip, ctx) \ + ({ bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); }) +#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) \ + ({ bpf_probe_read(&(ip), sizeof(ip), \ + (void *)(PT_REGS_FP(ctx) + sizeof(ip))); }) +#endif + +#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 __attribute__((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 __attribute__((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 __attribute__((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 __attribute__((always_inline)) typeof(name(0)) \ +____##name(struct pt_regs *ctx, ##args) + +#define ___bpf_kretprobe_args0() ctx +#define ___bpf_kretprobe_args1(x) \ + ___bpf_kretprobe_args0(), (void *)PT_REGS_RC(ctx) +#define ___bpf_kretprobe_args(args...) \ + ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args) + +/* + * BPF_KRETPROBE is similar to BPF_KPROBE, except, it only provides optional + * return value (in addition to `struct pt_regs *ctx`), but no input + * arguments, because they will be clobbered by the time probed function + * returns. + */ +#define BPF_KRETPROBE(name, args...) \ +name(struct pt_regs *ctx); \ +static __attribute__((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) + +/* + * BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values + * in a structure. + */ +#define BPF_SEQ_PRINTF(seq, fmt, args...) \ + ({ \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ + static const char ___fmt[] = fmt; \ + unsigned long long ___param[] = { args }; \ + _Pragma("GCC diagnostic pop") \ + int ___ret = bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \ + ___param, sizeof(___param)); \ + ___ret; \ + }) + +#endif diff --git a/src/bpfswitch/include/bpf/btf.h b/src/bpfswitch/include/bpf/btf.h new file mode 100644 index 00000000..a3b7ef9b --- /dev/null +++ b/src/bpfswitch/include/bpf/btf.h @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2018 Facebook */ + +#ifndef __LIBBPF_BTF_H +#define __LIBBPF_BTF_H + +#include +#include +#include + +#include "libbpf_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BTF_ELF_SEC ".BTF" +#define BTF_EXT_ELF_SEC ".BTF.ext" +#define MAPS_ELF_SEC ".maps" + +struct btf; +struct btf_ext; +struct btf_type; + +struct bpf_object; + +/* + * The .BTF.ext ELF section layout defined as + * struct btf_ext_header + * func_info subsection + * + * The func_info subsection layout: + * record size for struct bpf_func_info in the func_info subsection + * struct btf_sec_func_info for section #1 + * a list of bpf_func_info records for section #1 + * where struct bpf_func_info mimics one in include/uapi/linux/bpf.h + * but may not be identical + * struct btf_sec_func_info for section #2 + * a list of bpf_func_info records for section #2 + * ...... + * + * Note that the bpf_func_info record size in .BTF.ext may not + * be the same as the one defined in include/uapi/linux/bpf.h. + * The loader should ensure that record_size meets minimum + * requirement and pass the record as is to the kernel. The + * kernel will handle the func_info properly based on its contents. + */ +struct btf_ext_header { + __u16 magic; + __u8 version; + __u8 flags; + __u32 hdr_len; + + /* All offsets are in bytes relative to the end of this header */ + __u32 func_info_off; + __u32 func_info_len; + __u32 line_info_off; + __u32 line_info_len; + + /* optional part of .BTF.ext header */ + __u32 field_reloc_off; + __u32 field_reloc_len; +}; + +LIBBPF_API void btf__free(struct btf *btf); +LIBBPF_API struct btf *btf__new(const void *data, __u32 size); +LIBBPF_API struct btf *btf__parse_elf(const char *path, + struct btf_ext **btf_ext); +LIBBPF_API int btf__finalize_data(struct bpf_object *obj, struct btf *btf); +LIBBPF_API int btf__load(struct btf *btf); +LIBBPF_API __s32 btf__find_by_name(const struct btf *btf, + const char *type_name); +LIBBPF_API __s32 btf__find_by_name_kind(const struct btf *btf, + const char *type_name, __u32 kind); +LIBBPF_API __u32 btf__get_nr_types(const struct btf *btf); +LIBBPF_API const struct btf_type *btf__type_by_id(const struct btf *btf, + __u32 id); +LIBBPF_API __s64 btf__resolve_size(const struct btf *btf, __u32 type_id); +LIBBPF_API int btf__resolve_type(const struct btf *btf, __u32 type_id); +LIBBPF_API int btf__align_of(const struct btf *btf, __u32 id); +LIBBPF_API int btf__fd(const struct btf *btf); +LIBBPF_API void btf__set_fd(struct btf *btf, int fd); +LIBBPF_API const void *btf__get_raw_data(const struct btf *btf, __u32 *size); +LIBBPF_API const char *btf__name_by_offset(const struct btf *btf, __u32 offset); +LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf); +LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name, + __u32 expected_key_size, + __u32 expected_value_size, + __u32 *key_type_id, __u32 *value_type_id); + +LIBBPF_API struct btf_ext *btf_ext__new(__u8 *data, __u32 size); +LIBBPF_API void btf_ext__free(struct btf_ext *btf_ext); +LIBBPF_API const void *btf_ext__get_raw_data(const struct btf_ext *btf_ext, + __u32 *size); +LIBBPF_API int btf_ext__reloc_func_info(const struct btf *btf, + const struct btf_ext *btf_ext, + const char *sec_name, __u32 insns_cnt, + void **func_info, __u32 *cnt); +LIBBPF_API int btf_ext__reloc_line_info(const struct btf *btf, + const struct btf_ext *btf_ext, + const char *sec_name, __u32 insns_cnt, + void **line_info, __u32 *cnt); +LIBBPF_API __u32 btf_ext__func_info_rec_size(const struct btf_ext *btf_ext); +LIBBPF_API __u32 btf_ext__line_info_rec_size(const struct btf_ext *btf_ext); + +LIBBPF_API struct btf *libbpf_find_kernel_btf(void); + +struct btf_dedup_opts { + unsigned int dedup_table_size; + bool dont_resolve_fwds; +}; + +LIBBPF_API int btf__dedup(struct btf *btf, struct btf_ext *btf_ext, + const struct btf_dedup_opts *opts); + +struct btf_dump; + +struct btf_dump_opts { + void *ctx; +}; + +typedef void (*btf_dump_printf_fn_t)(void *ctx, const char *fmt, va_list args); + +LIBBPF_API struct btf_dump *btf_dump__new(const struct btf *btf, + const struct btf_ext *btf_ext, + const struct btf_dump_opts *opts, + btf_dump_printf_fn_t printf_fn); +LIBBPF_API void btf_dump__free(struct btf_dump *d); + +LIBBPF_API int btf_dump__dump_type(struct btf_dump *d, __u32 id); + +struct btf_dump_emit_type_decl_opts { + /* size of this struct, for forward/backward compatiblity */ + size_t sz; + /* optional field name for type declaration, e.g.: + * - struct my_struct + * - void (*)(int) + * - char (*)[123] + */ + const char *field_name; + /* extra indentation level (in number of tabs) to emit for multi-line + * type declarations (e.g., anonymous struct); applies for lines + * starting from the second one (first line is assumed to have + * necessary indentation already + */ + int indent_level; +}; +#define btf_dump_emit_type_decl_opts__last_field indent_level + +LIBBPF_API int +btf_dump__emit_type_decl(struct btf_dump *d, __u32 id, + const struct btf_dump_emit_type_decl_opts *opts); + +/* + * A set of helpers for easier BTF types handling + */ +static inline __u16 btf_kind(const struct btf_type *t) +{ + return BTF_INFO_KIND(t->info); +} + +static inline __u16 btf_vlen(const struct btf_type *t) +{ + return BTF_INFO_VLEN(t->info); +} + +static inline bool btf_kflag(const struct btf_type *t) +{ + return BTF_INFO_KFLAG(t->info); +} + +static inline bool btf_is_void(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_UNKN; +} + +static inline bool btf_is_int(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_INT; +} + +static inline bool btf_is_ptr(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_PTR; +} + +static inline bool btf_is_array(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_ARRAY; +} + +static inline bool btf_is_struct(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_STRUCT; +} + +static inline bool btf_is_union(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_UNION; +} + +static inline bool btf_is_composite(const struct btf_type *t) +{ + __u16 kind = btf_kind(t); + + return kind == BTF_KIND_STRUCT || kind == BTF_KIND_UNION; +} + +static inline bool btf_is_enum(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_ENUM; +} + +static inline bool btf_is_fwd(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_FWD; +} + +static inline bool btf_is_typedef(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_TYPEDEF; +} + +static inline bool btf_is_volatile(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_VOLATILE; +} + +static inline bool btf_is_const(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_CONST; +} + +static inline bool btf_is_restrict(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_RESTRICT; +} + +static inline bool btf_is_mod(const struct btf_type *t) +{ + __u16 kind = btf_kind(t); + + return kind == BTF_KIND_VOLATILE || + kind == BTF_KIND_CONST || + kind == BTF_KIND_RESTRICT; +} + +static inline bool btf_is_func(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_FUNC; +} + +static inline bool btf_is_func_proto(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_FUNC_PROTO; +} + +static inline bool btf_is_var(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_VAR; +} + +static inline bool btf_is_datasec(const struct btf_type *t) +{ + return btf_kind(t) == BTF_KIND_DATASEC; +} + +static inline __u8 btf_int_encoding(const struct btf_type *t) +{ + return BTF_INT_ENCODING(*(__u32 *)(t + 1)); +} + +static inline __u8 btf_int_offset(const struct btf_type *t) +{ + return BTF_INT_OFFSET(*(__u32 *)(t + 1)); +} + +static inline __u8 btf_int_bits(const struct btf_type *t) +{ + return BTF_INT_BITS(*(__u32 *)(t + 1)); +} + +static inline struct btf_array *btf_array(const struct btf_type *t) +{ + return (struct btf_array *)(t + 1); +} + +static inline struct btf_enum *btf_enum(const struct btf_type *t) +{ + return (struct btf_enum *)(t + 1); +} + +static inline struct btf_member *btf_members(const struct btf_type *t) +{ + return (struct btf_member *)(t + 1); +} + +/* Get bit offset of a member with specified index. */ +static inline __u32 btf_member_bit_offset(const struct btf_type *t, + __u32 member_idx) +{ + const struct btf_member *m = btf_members(t) + member_idx; + bool kflag = btf_kflag(t); + + return kflag ? BTF_MEMBER_BIT_OFFSET(m->offset) : m->offset; +} +/* + * Get bitfield size of a member, assuming t is BTF_KIND_STRUCT or + * BTF_KIND_UNION. If member is not a bitfield, zero is returned. + */ +static inline __u32 btf_member_bitfield_size(const struct btf_type *t, + __u32 member_idx) +{ + const struct btf_member *m = btf_members(t) + member_idx; + bool kflag = btf_kflag(t); + + return kflag ? BTF_MEMBER_BITFIELD_SIZE(m->offset) : 0; +} + +static inline struct btf_param *btf_params(const struct btf_type *t) +{ + return (struct btf_param *)(t + 1); +} + +static inline struct btf_var *btf_var(const struct btf_type *t) +{ + return (struct btf_var *)(t + 1); +} + +static inline struct btf_var_secinfo * +btf_var_secinfos(const struct btf_type *t) +{ + return (struct btf_var_secinfo *)(t + 1); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __LIBBPF_BTF_H */ diff --git a/src/bpfswitch/include/bpf/libbpf.h b/src/bpfswitch/include/bpf/libbpf.h new file mode 100644 index 00000000..2335971e --- /dev/null +++ b/src/bpfswitch/include/bpf/libbpf.h @@ -0,0 +1,750 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* + * Common eBPF ELF object loading operations. + * + * Copyright (C) 2013-2015 Alexei Starovoitov + * Copyright (C) 2015 Wang Nan + * Copyright (C) 2015 Huawei Inc. + */ +#ifndef __LIBBPF_LIBBPF_H +#define __LIBBPF_LIBBPF_H + +#include +#include +#include +#include +#include // for size_t +#include + +#include "libbpf_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum libbpf_errno { + __LIBBPF_ERRNO__START = 4000, + + /* Something wrong in libelf */ + LIBBPF_ERRNO__LIBELF = __LIBBPF_ERRNO__START, + LIBBPF_ERRNO__FORMAT, /* BPF object format invalid */ + LIBBPF_ERRNO__KVERSION, /* Incorrect or no 'version' section */ + LIBBPF_ERRNO__ENDIAN, /* Endian mismatch */ + LIBBPF_ERRNO__INTERNAL, /* Internal error in libbpf */ + LIBBPF_ERRNO__RELOC, /* Relocation failed */ + LIBBPF_ERRNO__LOAD, /* Load program failure for unknown reason */ + LIBBPF_ERRNO__VERIFY, /* Kernel verifier blocks program loading */ + LIBBPF_ERRNO__PROG2BIG, /* Program too big */ + LIBBPF_ERRNO__KVER, /* Incorrect kernel version */ + LIBBPF_ERRNO__PROGTYPE, /* Kernel doesn't support this program type */ + LIBBPF_ERRNO__WRNGPID, /* Wrong pid in netlink message */ + LIBBPF_ERRNO__INVSEQ, /* Invalid netlink sequence */ + LIBBPF_ERRNO__NLPARSE, /* netlink parsing error */ + __LIBBPF_ERRNO__END, +}; + +LIBBPF_API int libbpf_strerror(int err, char *buf, size_t size); + +enum libbpf_print_level { + LIBBPF_WARN, + LIBBPF_INFO, + LIBBPF_DEBUG, +}; + +typedef int (*libbpf_print_fn_t)(enum libbpf_print_level level, + const char *, va_list ap); + +LIBBPF_API libbpf_print_fn_t libbpf_set_print(libbpf_print_fn_t fn); + +/* Hide internal to user */ +struct bpf_object; + +struct bpf_object_open_attr { + const char *file; + enum bpf_prog_type prog_type; +}; + +struct bpf_object_open_opts { + /* size of this struct, for forward/backward compatiblity */ + size_t sz; + /* object name override, if provided: + * - for object open from file, this will override setting object + * name from file path's base name; + * - for object open from memory buffer, this will specify an object + * name and will override default "-" name; + */ + const char *object_name; + /* parse map definitions non-strictly, allowing extra attributes/data */ + bool relaxed_maps; + /* DEPRECATED: handle CO-RE relocations non-strictly, allowing failures. + * Value is ignored. Relocations always are processed non-strictly. + * Non-relocatable instructions are replaced with invalid ones to + * prevent accidental errors. + * */ + bool relaxed_core_relocs; + /* maps that set the 'pinning' attribute in their definition will have + * their pin_path attribute set to a file in this directory, and be + * auto-pinned to that path on load; defaults to "/sys/fs/bpf". + */ + const char *pin_root_path; + __u32 attach_prog_fd; + /* Additional kernel config content that augments and overrides + * system Kconfig for CONFIG_xxx externs. + */ + const char *kconfig; +}; +#define bpf_object_open_opts__last_field kconfig + +LIBBPF_API struct bpf_object *bpf_object__open(const char *path); +LIBBPF_API struct bpf_object * +bpf_object__open_file(const char *path, const struct bpf_object_open_opts *opts); +LIBBPF_API struct bpf_object * +bpf_object__open_mem(const void *obj_buf, size_t obj_buf_sz, + const struct bpf_object_open_opts *opts); + +/* deprecated bpf_object__open variants */ +LIBBPF_API struct bpf_object * +bpf_object__open_buffer(const void *obj_buf, size_t obj_buf_sz, + const char *name); +LIBBPF_API struct bpf_object * +bpf_object__open_xattr(struct bpf_object_open_attr *attr); + +enum libbpf_pin_type { + LIBBPF_PIN_NONE, + /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */ + LIBBPF_PIN_BY_NAME, +}; + +/* pin_maps and unpin_maps can both be called with a NULL path, in which case + * they will use the pin_path attribute of each map (and ignore all maps that + * don't have a pin_path set). + */ +LIBBPF_API int bpf_object__pin_maps(struct bpf_object *obj, const char *path); +LIBBPF_API int bpf_object__unpin_maps(struct bpf_object *obj, + const char *path); +LIBBPF_API int bpf_object__pin_programs(struct bpf_object *obj, + const char *path); +LIBBPF_API int bpf_object__unpin_programs(struct bpf_object *obj, + const char *path); +LIBBPF_API int bpf_object__pin(struct bpf_object *object, const char *path); +LIBBPF_API void bpf_object__close(struct bpf_object *object); + +struct bpf_object_load_attr { + struct bpf_object *obj; + int log_level; + const char *target_btf_path; +}; + +/* Load/unload object into/from kernel */ +LIBBPF_API int bpf_object__load(struct bpf_object *obj); +LIBBPF_API int bpf_object__load_xattr(struct bpf_object_load_attr *attr); +LIBBPF_API int bpf_object__unload(struct bpf_object *obj); + +LIBBPF_API const char *bpf_object__name(const struct bpf_object *obj); +LIBBPF_API unsigned int bpf_object__kversion(const struct bpf_object *obj); + +struct btf; +LIBBPF_API struct btf *bpf_object__btf(const struct bpf_object *obj); +LIBBPF_API int bpf_object__btf_fd(const struct bpf_object *obj); + +LIBBPF_API struct bpf_program * +bpf_object__find_program_by_title(const struct bpf_object *obj, + const char *title); +LIBBPF_API struct bpf_program * +bpf_object__find_program_by_name(const struct bpf_object *obj, + const char *name); + +LIBBPF_API struct bpf_object *bpf_object__next(struct bpf_object *prev); +#define bpf_object__for_each_safe(pos, tmp) \ + for ((pos) = bpf_object__next(NULL), \ + (tmp) = bpf_object__next(pos); \ + (pos) != NULL; \ + (pos) = (tmp), (tmp) = bpf_object__next(tmp)) + +typedef void (*bpf_object_clear_priv_t)(struct bpf_object *, void *); +LIBBPF_API int bpf_object__set_priv(struct bpf_object *obj, void *priv, + bpf_object_clear_priv_t clear_priv); +LIBBPF_API void *bpf_object__priv(const struct bpf_object *prog); + +LIBBPF_API int +libbpf_prog_type_by_name(const char *name, enum bpf_prog_type *prog_type, + enum bpf_attach_type *expected_attach_type); +LIBBPF_API int libbpf_attach_type_by_name(const char *name, + enum bpf_attach_type *attach_type); +LIBBPF_API int libbpf_find_vmlinux_btf_id(const char *name, + enum bpf_attach_type attach_type); + +/* Accessors of bpf_program */ +struct bpf_program; +LIBBPF_API struct bpf_program *bpf_program__next(struct bpf_program *prog, + const struct bpf_object *obj); + +#define bpf_object__for_each_program(pos, obj) \ + for ((pos) = bpf_program__next(NULL, (obj)); \ + (pos) != NULL; \ + (pos) = bpf_program__next((pos), (obj))) + +LIBBPF_API struct bpf_program *bpf_program__prev(struct bpf_program *prog, + const struct bpf_object *obj); + +typedef void (*bpf_program_clear_priv_t)(struct bpf_program *, void *); + +LIBBPF_API int bpf_program__set_priv(struct bpf_program *prog, void *priv, + bpf_program_clear_priv_t clear_priv); + +LIBBPF_API void *bpf_program__priv(const struct bpf_program *prog); +LIBBPF_API void bpf_program__set_ifindex(struct bpf_program *prog, + __u32 ifindex); + +LIBBPF_API const char *bpf_program__name(const struct bpf_program *prog); +LIBBPF_API const char *bpf_program__title(const struct bpf_program *prog, + bool needs_copy); +LIBBPF_API bool bpf_program__autoload(const struct bpf_program *prog); +LIBBPF_API int bpf_program__set_autoload(struct bpf_program *prog, bool autoload); + +/* returns program size in bytes */ +LIBBPF_API size_t bpf_program__size(const struct bpf_program *prog); + +LIBBPF_API int bpf_program__load(struct bpf_program *prog, char *license, + __u32 kern_version); +LIBBPF_API int bpf_program__fd(const struct bpf_program *prog); +LIBBPF_API int bpf_program__pin_instance(struct bpf_program *prog, + const char *path, + int instance); +LIBBPF_API int bpf_program__unpin_instance(struct bpf_program *prog, + const char *path, + int instance); +LIBBPF_API int bpf_program__pin(struct bpf_program *prog, const char *path); +LIBBPF_API int bpf_program__unpin(struct bpf_program *prog, const char *path); +LIBBPF_API void bpf_program__unload(struct bpf_program *prog); + +struct bpf_link; + +LIBBPF_API struct bpf_link *bpf_link__open(const char *path); +LIBBPF_API int bpf_link__fd(const struct bpf_link *link); +LIBBPF_API const char *bpf_link__pin_path(const struct bpf_link *link); +LIBBPF_API int bpf_link__pin(struct bpf_link *link, const char *path); +LIBBPF_API int bpf_link__unpin(struct bpf_link *link); +LIBBPF_API int bpf_link__update_program(struct bpf_link *link, + struct bpf_program *prog); +LIBBPF_API void bpf_link__disconnect(struct bpf_link *link); +LIBBPF_API int bpf_link__destroy(struct bpf_link *link); + +LIBBPF_API struct bpf_link * +bpf_program__attach(struct bpf_program *prog); +LIBBPF_API struct bpf_link * +bpf_program__attach_perf_event(struct bpf_program *prog, int pfd); +LIBBPF_API struct bpf_link * +bpf_program__attach_kprobe(struct bpf_program *prog, bool retprobe, + const char *func_name); +LIBBPF_API struct bpf_link * +bpf_program__attach_uprobe(struct bpf_program *prog, bool retprobe, + pid_t pid, const char *binary_path, + size_t func_offset); +LIBBPF_API struct bpf_link * +bpf_program__attach_tracepoint(struct bpf_program *prog, + const char *tp_category, + const char *tp_name); +LIBBPF_API struct bpf_link * +bpf_program__attach_raw_tracepoint(struct bpf_program *prog, + const char *tp_name); +LIBBPF_API struct bpf_link * +bpf_program__attach_trace(struct bpf_program *prog); +LIBBPF_API struct bpf_link * +bpf_program__attach_lsm(struct bpf_program *prog); +LIBBPF_API struct bpf_link * +bpf_program__attach_cgroup(struct bpf_program *prog, int cgroup_fd); +LIBBPF_API struct bpf_link * +bpf_program__attach_netns(struct bpf_program *prog, int netns_fd); + +struct bpf_map; + +LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(struct bpf_map *map); + +struct bpf_iter_attach_opts { + size_t sz; /* size of this struct for forward/backward compatibility */ +}; +#define bpf_iter_attach_opts__last_field sz + +LIBBPF_API struct bpf_link * +bpf_program__attach_iter(struct bpf_program *prog, + const struct bpf_iter_attach_opts *opts); + +struct bpf_insn; + +/* + * Libbpf allows callers to adjust BPF programs before being loaded + * into kernel. One program in an object file can be transformed into + * multiple variants to be attached to different hooks. + * + * bpf_program_prep_t, bpf_program__set_prep and bpf_program__nth_fd + * form an API for this purpose. + * + * - bpf_program_prep_t: + * Defines a 'preprocessor', which is a caller defined function + * passed to libbpf through bpf_program__set_prep(), and will be + * called before program is loaded. The processor should adjust + * the program one time for each instance according to the instance id + * passed to it. + * + * - bpf_program__set_prep: + * Attaches a preprocessor to a BPF program. The number of instances + * that should be created is also passed through this function. + * + * - bpf_program__nth_fd: + * After the program is loaded, get resulting FD of a given instance + * of the BPF program. + * + * If bpf_program__set_prep() is not used, the program would be loaded + * without adjustment during bpf_object__load(). The program has only + * one instance. In this case bpf_program__fd(prog) is equal to + * bpf_program__nth_fd(prog, 0). + */ + +struct bpf_prog_prep_result { + /* + * If not NULL, load new instruction array. + * If set to NULL, don't load this instance. + */ + struct bpf_insn *new_insn_ptr; + int new_insn_cnt; + + /* If not NULL, result FD is written to it. */ + int *pfd; +}; + +/* + * Parameters of bpf_program_prep_t: + * - prog: The bpf_program being loaded. + * - n: Index of instance being generated. + * - insns: BPF instructions array. + * - insns_cnt:Number of instructions in insns. + * - res: Output parameter, result of transformation. + * + * Return value: + * - Zero: pre-processing success. + * - Non-zero: pre-processing error, stop loading. + */ +typedef int (*bpf_program_prep_t)(struct bpf_program *prog, int n, + struct bpf_insn *insns, int insns_cnt, + struct bpf_prog_prep_result *res); + +LIBBPF_API int bpf_program__set_prep(struct bpf_program *prog, int nr_instance, + bpf_program_prep_t prep); + +LIBBPF_API int bpf_program__nth_fd(const struct bpf_program *prog, int n); + +/* + * Adjust type of BPF program. Default is kprobe. + */ +LIBBPF_API int bpf_program__set_socket_filter(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_tracepoint(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_raw_tracepoint(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_kprobe(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_lsm(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_sched_cls(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_sched_act(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_xdp(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_perf_event(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_tracing(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_struct_ops(struct bpf_program *prog); +LIBBPF_API int bpf_program__set_extension(struct bpf_program *prog); + +LIBBPF_API enum bpf_prog_type bpf_program__get_type(struct bpf_program *prog); +LIBBPF_API void bpf_program__set_type(struct bpf_program *prog, + enum bpf_prog_type type); + +LIBBPF_API enum bpf_attach_type +bpf_program__get_expected_attach_type(struct bpf_program *prog); +LIBBPF_API void +bpf_program__set_expected_attach_type(struct bpf_program *prog, + enum bpf_attach_type type); + +LIBBPF_API int +bpf_program__set_attach_target(struct bpf_program *prog, int attach_prog_fd, + const char *attach_func_name); + +LIBBPF_API bool bpf_program__is_socket_filter(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_tracepoint(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_raw_tracepoint(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_kprobe(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_lsm(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_sched_cls(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_sched_act(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_xdp(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_perf_event(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_tracing(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_struct_ops(const struct bpf_program *prog); +LIBBPF_API bool bpf_program__is_extension(const struct bpf_program *prog); + +/* + * No need for __attribute__((packed)), all members of 'bpf_map_def' + * are all aligned. In addition, using __attribute__((packed)) + * would trigger a -Wpacked warning message, and lead to an error + * if -Werror is set. + */ +struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; +}; + +/* + * The 'struct bpf_map' in include/linux/bpf.h is internal to the kernel, + * so no need to worry about a name clash. + */ +LIBBPF_API struct bpf_map * +bpf_object__find_map_by_name(const struct bpf_object *obj, const char *name); + +LIBBPF_API int +bpf_object__find_map_fd_by_name(const struct bpf_object *obj, const char *name); + +/* + * Get bpf_map through the offset of corresponding struct bpf_map_def + * in the BPF object file. + */ +LIBBPF_API struct bpf_map * +bpf_object__find_map_by_offset(struct bpf_object *obj, size_t offset); + +LIBBPF_API struct bpf_map * +bpf_map__next(const struct bpf_map *map, const struct bpf_object *obj); +#define bpf_object__for_each_map(pos, obj) \ + for ((pos) = bpf_map__next(NULL, (obj)); \ + (pos) != NULL; \ + (pos) = bpf_map__next((pos), (obj))) +#define bpf_map__for_each bpf_object__for_each_map + +LIBBPF_API struct bpf_map * +bpf_map__prev(const struct bpf_map *map, const struct bpf_object *obj); + +/* get/set map FD */ +LIBBPF_API int bpf_map__fd(const struct bpf_map *map); +LIBBPF_API int bpf_map__reuse_fd(struct bpf_map *map, int fd); +/* get map definition */ +LIBBPF_API const struct bpf_map_def *bpf_map__def(const struct bpf_map *map); +/* get map name */ +LIBBPF_API const char *bpf_map__name(const struct bpf_map *map); +/* get/set map type */ +LIBBPF_API enum bpf_map_type bpf_map__type(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_type(struct bpf_map *map, enum bpf_map_type type); +/* get/set map size (max_entries) */ +LIBBPF_API __u32 bpf_map__max_entries(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_max_entries(struct bpf_map *map, __u32 max_entries); +LIBBPF_API int bpf_map__resize(struct bpf_map *map, __u32 max_entries); +/* get/set map flags */ +LIBBPF_API __u32 bpf_map__map_flags(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_map_flags(struct bpf_map *map, __u32 flags); +/* get/set map NUMA node */ +LIBBPF_API __u32 bpf_map__numa_node(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_numa_node(struct bpf_map *map, __u32 numa_node); +/* get/set map key size */ +LIBBPF_API __u32 bpf_map__key_size(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_key_size(struct bpf_map *map, __u32 size); +/* get/set map value size */ +LIBBPF_API __u32 bpf_map__value_size(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_value_size(struct bpf_map *map, __u32 size); +/* get map key/value BTF type IDs */ +LIBBPF_API __u32 bpf_map__btf_key_type_id(const struct bpf_map *map); +LIBBPF_API __u32 bpf_map__btf_value_type_id(const struct bpf_map *map); +/* get/set map if_index */ +LIBBPF_API __u32 bpf_map__ifindex(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_ifindex(struct bpf_map *map, __u32 ifindex); + +typedef void (*bpf_map_clear_priv_t)(struct bpf_map *, void *); +LIBBPF_API int bpf_map__set_priv(struct bpf_map *map, void *priv, + bpf_map_clear_priv_t clear_priv); +LIBBPF_API void *bpf_map__priv(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_initial_value(struct bpf_map *map, + const void *data, size_t size); +LIBBPF_API bool bpf_map__is_offload_neutral(const struct bpf_map *map); +LIBBPF_API bool bpf_map__is_internal(const struct bpf_map *map); +LIBBPF_API int bpf_map__set_pin_path(struct bpf_map *map, const char *path); +LIBBPF_API const char *bpf_map__get_pin_path(const struct bpf_map *map); +LIBBPF_API bool bpf_map__is_pinned(const struct bpf_map *map); +LIBBPF_API int bpf_map__pin(struct bpf_map *map, const char *path); +LIBBPF_API int bpf_map__unpin(struct bpf_map *map, const char *path); + +LIBBPF_API int bpf_map__set_inner_map_fd(struct bpf_map *map, int fd); + +LIBBPF_API long libbpf_get_error(const void *ptr); + +struct bpf_prog_load_attr { + const char *file; + enum bpf_prog_type prog_type; + enum bpf_attach_type expected_attach_type; + int ifindex; + int log_level; + int prog_flags; +}; + +LIBBPF_API int bpf_prog_load_xattr(const struct bpf_prog_load_attr *attr, + struct bpf_object **pobj, int *prog_fd); +LIBBPF_API int bpf_prog_load(const char *file, enum bpf_prog_type type, + struct bpf_object **pobj, int *prog_fd); + +struct xdp_link_info { + __u32 prog_id; + __u32 drv_prog_id; + __u32 hw_prog_id; + __u32 skb_prog_id; + __u8 attach_mode; +}; + +struct bpf_xdp_set_link_opts { + size_t sz; + int old_fd; +}; +#define bpf_xdp_set_link_opts__last_field old_fd + +LIBBPF_API int bpf_set_link_xdp_fd(int ifindex, int fd, __u32 flags); +LIBBPF_API int bpf_set_link_xdp_fd_opts(int ifindex, int fd, __u32 flags, + const struct bpf_xdp_set_link_opts *opts); +LIBBPF_API int bpf_get_link_xdp_id(int ifindex, __u32 *prog_id, __u32 flags); +LIBBPF_API int bpf_get_link_xdp_info(int ifindex, struct xdp_link_info *info, + size_t info_size, __u32 flags); + +/* Ring buffer APIs */ +struct ring_buffer; + +typedef int (*ring_buffer_sample_fn)(void *ctx, void *data, size_t size); + +struct ring_buffer_opts { + size_t sz; /* size of this struct, for forward/backward compatiblity */ +}; + +#define ring_buffer_opts__last_field sz + +LIBBPF_API struct ring_buffer * +ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, void *ctx, + const struct ring_buffer_opts *opts); +LIBBPF_API void ring_buffer__free(struct ring_buffer *rb); +LIBBPF_API int ring_buffer__add(struct ring_buffer *rb, int map_fd, + ring_buffer_sample_fn sample_cb, void *ctx); +LIBBPF_API int ring_buffer__poll(struct ring_buffer *rb, int timeout_ms); +LIBBPF_API int ring_buffer__consume(struct ring_buffer *rb); + +/* Perf buffer APIs */ +struct perf_buffer; + +typedef void (*perf_buffer_sample_fn)(void *ctx, int cpu, + void *data, __u32 size); +typedef void (*perf_buffer_lost_fn)(void *ctx, int cpu, __u64 cnt); + +/* common use perf buffer options */ +struct perf_buffer_opts { + /* if specified, sample_cb is called for each sample */ + perf_buffer_sample_fn sample_cb; + /* if specified, lost_cb is called for each batch of lost samples */ + perf_buffer_lost_fn lost_cb; + /* ctx is provided to sample_cb and lost_cb */ + void *ctx; +}; + +LIBBPF_API struct perf_buffer * +perf_buffer__new(int map_fd, size_t page_cnt, + const struct perf_buffer_opts *opts); + +enum bpf_perf_event_ret { + LIBBPF_PERF_EVENT_DONE = 0, + LIBBPF_PERF_EVENT_ERROR = -1, + LIBBPF_PERF_EVENT_CONT = -2, +}; + +struct perf_event_header; + +typedef enum bpf_perf_event_ret +(*perf_buffer_event_fn)(void *ctx, int cpu, struct perf_event_header *event); + +/* raw perf buffer options, giving most power and control */ +struct perf_buffer_raw_opts { + /* perf event attrs passed directly into perf_event_open() */ + struct perf_event_attr *attr; + /* raw event callback */ + perf_buffer_event_fn event_cb; + /* ctx is provided to event_cb */ + void *ctx; + /* if cpu_cnt == 0, open all on all possible CPUs (up to the number of + * max_entries of given PERF_EVENT_ARRAY map) + */ + int cpu_cnt; + /* if cpu_cnt > 0, cpus is an array of CPUs to open ring buffers on */ + int *cpus; + /* if cpu_cnt > 0, map_keys specify map keys to set per-CPU FDs for */ + int *map_keys; +}; + +LIBBPF_API struct perf_buffer * +perf_buffer__new_raw(int map_fd, size_t page_cnt, + const struct perf_buffer_raw_opts *opts); + +LIBBPF_API void perf_buffer__free(struct perf_buffer *pb); +LIBBPF_API int perf_buffer__poll(struct perf_buffer *pb, int timeout_ms); +LIBBPF_API int perf_buffer__consume(struct perf_buffer *pb); + +typedef enum bpf_perf_event_ret + (*bpf_perf_event_print_t)(struct perf_event_header *hdr, + void *private_data); +LIBBPF_API enum bpf_perf_event_ret +bpf_perf_event_read_simple(void *mmap_mem, size_t mmap_size, size_t page_size, + void **copy_mem, size_t *copy_size, + bpf_perf_event_print_t fn, void *private_data); + +struct bpf_prog_linfo; +struct bpf_prog_info; + +LIBBPF_API void bpf_prog_linfo__free(struct bpf_prog_linfo *prog_linfo); +LIBBPF_API struct bpf_prog_linfo * +bpf_prog_linfo__new(const struct bpf_prog_info *info); +LIBBPF_API const struct bpf_line_info * +bpf_prog_linfo__lfind_addr_func(const struct bpf_prog_linfo *prog_linfo, + __u64 addr, __u32 func_idx, __u32 nr_skip); +LIBBPF_API const struct bpf_line_info * +bpf_prog_linfo__lfind(const struct bpf_prog_linfo *prog_linfo, + __u32 insn_off, __u32 nr_skip); + +/* + * Probe for supported system features + * + * Note that running many of these probes in a short amount of time can cause + * the kernel to reach the maximal size of lockable memory allowed for the + * user, causing subsequent probes to fail. In this case, the caller may want + * to adjust that limit with setrlimit(). + */ +LIBBPF_API bool bpf_probe_prog_type(enum bpf_prog_type prog_type, + __u32 ifindex); +LIBBPF_API bool bpf_probe_map_type(enum bpf_map_type map_type, __u32 ifindex); +LIBBPF_API bool bpf_probe_helper(enum bpf_func_id id, + enum bpf_prog_type prog_type, __u32 ifindex); +LIBBPF_API bool bpf_probe_large_insn_limit(__u32 ifindex); + +/* + * Get bpf_prog_info in continuous memory + * + * struct bpf_prog_info has multiple arrays. The user has option to choose + * arrays to fetch from kernel. The following APIs provide an uniform way to + * fetch these data. All arrays in bpf_prog_info are stored in a single + * continuous memory region. This makes it easy to store the info in a + * file. + * + * Before writing bpf_prog_info_linear to files, it is necessary to + * translate pointers in bpf_prog_info to offsets. Helper functions + * bpf_program__bpil_addr_to_offs() and bpf_program__bpil_offs_to_addr() + * are introduced to switch between pointers and offsets. + * + * Examples: + * # To fetch map_ids and prog_tags: + * __u64 arrays = (1UL << BPF_PROG_INFO_MAP_IDS) | + * (1UL << BPF_PROG_INFO_PROG_TAGS); + * struct bpf_prog_info_linear *info_linear = + * bpf_program__get_prog_info_linear(fd, arrays); + * + * # To save data in file + * bpf_program__bpil_addr_to_offs(info_linear); + * write(f, info_linear, sizeof(*info_linear) + info_linear->data_len); + * + * # To read data from file + * read(f, info_linear, ); + * bpf_program__bpil_offs_to_addr(info_linear); + */ +enum bpf_prog_info_array { + BPF_PROG_INFO_FIRST_ARRAY = 0, + BPF_PROG_INFO_JITED_INSNS = 0, + BPF_PROG_INFO_XLATED_INSNS, + BPF_PROG_INFO_MAP_IDS, + BPF_PROG_INFO_JITED_KSYMS, + BPF_PROG_INFO_JITED_FUNC_LENS, + BPF_PROG_INFO_FUNC_INFO, + BPF_PROG_INFO_LINE_INFO, + BPF_PROG_INFO_JITED_LINE_INFO, + BPF_PROG_INFO_PROG_TAGS, + BPF_PROG_INFO_LAST_ARRAY, +}; + +struct bpf_prog_info_linear { + /* size of struct bpf_prog_info, when the tool is compiled */ + __u32 info_len; + /* total bytes allocated for data, round up to 8 bytes */ + __u32 data_len; + /* which arrays are included in data */ + __u64 arrays; + struct bpf_prog_info info; + __u8 data[]; +}; + +LIBBPF_API struct bpf_prog_info_linear * +bpf_program__get_prog_info_linear(int fd, __u64 arrays); + +LIBBPF_API void +bpf_program__bpil_addr_to_offs(struct bpf_prog_info_linear *info_linear); + +LIBBPF_API void +bpf_program__bpil_offs_to_addr(struct bpf_prog_info_linear *info_linear); + +/* + * A helper function to get the number of possible CPUs before looking up + * per-CPU maps. Negative errno is returned on failure. + * + * Example usage: + * + * int ncpus = libbpf_num_possible_cpus(); + * if (ncpus < 0) { + * // error handling + * } + * long values[ncpus]; + * bpf_map_lookup_elem(per_cpu_map_fd, key, values); + * + */ +LIBBPF_API int libbpf_num_possible_cpus(void); + +struct bpf_map_skeleton { + const char *name; + struct bpf_map **map; + void **mmaped; +}; + +struct bpf_prog_skeleton { + const char *name; + struct bpf_program **prog; + struct bpf_link **link; +}; + +struct bpf_object_skeleton { + size_t sz; /* size of this struct, for forward/backward compatibility */ + + const char *name; + void *data; + size_t data_sz; + + struct bpf_object **obj; + + int map_cnt; + int map_skel_sz; /* sizeof(struct bpf_skeleton_map) */ + struct bpf_map_skeleton *maps; + + int prog_cnt; + int prog_skel_sz; /* sizeof(struct bpf_skeleton_prog) */ + struct bpf_prog_skeleton *progs; +}; + +LIBBPF_API int +bpf_object__open_skeleton(struct bpf_object_skeleton *s, + const struct bpf_object_open_opts *opts); +LIBBPF_API int bpf_object__load_skeleton(struct bpf_object_skeleton *s); +LIBBPF_API int bpf_object__attach_skeleton(struct bpf_object_skeleton *s); +LIBBPF_API void bpf_object__detach_skeleton(struct bpf_object_skeleton *s); +LIBBPF_API void bpf_object__destroy_skeleton(struct bpf_object_skeleton *s); + +enum libbpf_tristate { + TRI_NO = 0, + TRI_YES = 1, + TRI_MODULE = 2, +}; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __LIBBPF_LIBBPF_H */ diff --git a/src/bpfswitch/include/bpf/libbpf_common.h b/src/bpfswitch/include/bpf/libbpf_common.h new file mode 100644 index 00000000..a23ae1ac --- /dev/null +++ b/src/bpfswitch/include/bpf/libbpf_common.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* + * Common user-facing libbpf helpers. + * + * Copyright (c) 2019 Facebook + */ + +#ifndef __LIBBPF_LIBBPF_COMMON_H +#define __LIBBPF_LIBBPF_COMMON_H + +#include + +#ifndef LIBBPF_API +#define LIBBPF_API __attribute__((visibility("default"))) +#endif + +/* Helper macro to declare and initialize libbpf options struct + * + * This dance with uninitialized declaration, followed by memset to zero, + * followed by assignment using compound literal syntax is done to preserve + * ability to use a nice struct field initialization syntax and **hopefully** + * have all the padding bytes initialized to zero. It's not guaranteed though, + * when copying literal, that compiler won't copy garbage in literal's padding + * bytes, but that's the best way I've found and it seems to work in practice. + * + * Macro declares opts struct of given type and name, zero-initializes, + * including any extra padding, it with memset() and then assigns initial + * values provided by users in struct initializer-syntax as varargs. + */ +#define DECLARE_LIBBPF_OPTS(TYPE, NAME, ...) \ + struct TYPE NAME = ({ \ + memset(&NAME, 0, sizeof(struct TYPE)); \ + (struct TYPE) { \ + .sz = sizeof(struct TYPE), \ + __VA_ARGS__ \ + }; \ + }) + +#endif /* __LIBBPF_LIBBPF_COMMON_H */ diff --git a/src/bpfswitch/include/bpf/libbpf_util.h b/src/bpfswitch/include/bpf/libbpf_util.h new file mode 100644 index 00000000..59c779c5 --- /dev/null +++ b/src/bpfswitch/include/bpf/libbpf_util.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* Copyright (c) 2019 Facebook */ + +#ifndef __LIBBPF_LIBBPF_UTIL_H +#define __LIBBPF_LIBBPF_UTIL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Use these barrier functions instead of smp_[rw]mb() when they are + * used in a libbpf header file. That way they can be built into the + * application that uses libbpf. + */ +#if defined(__i386__) || defined(__x86_64__) +# define libbpf_smp_rmb() asm volatile("" : : : "memory") +# define libbpf_smp_wmb() asm volatile("" : : : "memory") +# define libbpf_smp_mb() \ + asm volatile("lock; addl $0,-4(%%rsp)" : : : "memory", "cc") +/* Hinders stores to be observed before older loads. */ +# define libbpf_smp_rwmb() asm volatile("" : : : "memory") +#elif defined(__aarch64__) +# define libbpf_smp_rmb() asm volatile("dmb ishld" : : : "memory") +# define libbpf_smp_wmb() asm volatile("dmb ishst" : : : "memory") +# define libbpf_smp_mb() asm volatile("dmb ish" : : : "memory") +# define libbpf_smp_rwmb() libbpf_smp_mb() +#elif defined(__arm__) +/* These are only valid for armv7 and above */ +# define libbpf_smp_rmb() asm volatile("dmb ish" : : : "memory") +# define libbpf_smp_wmb() asm volatile("dmb ishst" : : : "memory") +# define libbpf_smp_mb() asm volatile("dmb ish" : : : "memory") +# define libbpf_smp_rwmb() libbpf_smp_mb() +#else +/* Architecture missing native barrier functions. */ +# define libbpf_smp_rmb() __sync_synchronize() +# define libbpf_smp_wmb() __sync_synchronize() +# define libbpf_smp_mb() __sync_synchronize() +# define libbpf_smp_rwmb() __sync_synchronize() +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/bpfswitch/include/bpf/xsk.h b/src/bpfswitch/include/bpf/xsk.h new file mode 100644 index 00000000..584f6820 --- /dev/null +++ b/src/bpfswitch/include/bpf/xsk.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ + +/* + * AF_XDP user-space access library. + * + * Copyright(c) 2018 - 2019 Intel Corporation. + * + * Author(s): Magnus Karlsson + */ + +#ifndef __LIBBPF_XSK_H +#define __LIBBPF_XSK_H + +#include +#include +#include + +#include "libbpf.h" +#include "libbpf_util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Do not access these members directly. Use the functions below. */ +#define DEFINE_XSK_RING(name) \ +struct name { \ + __u32 cached_prod; \ + __u32 cached_cons; \ + __u32 mask; \ + __u32 size; \ + __u32 *producer; \ + __u32 *consumer; \ + void *ring; \ + __u32 *flags; \ +} + +DEFINE_XSK_RING(xsk_ring_prod); +DEFINE_XSK_RING(xsk_ring_cons); + +/* For a detailed explanation on the memory barriers associated with the + * ring, please take a look at net/xdp/xsk_queue.h. + */ + +struct xsk_umem; +struct xsk_socket; + +static inline __u64 *xsk_ring_prod__fill_addr(struct xsk_ring_prod *fill, + __u32 idx) +{ + __u64 *addrs = (__u64 *)fill->ring; + + return &addrs[idx & fill->mask]; +} + +static inline const __u64 * +xsk_ring_cons__comp_addr(const struct xsk_ring_cons *comp, __u32 idx) +{ + const __u64 *addrs = (const __u64 *)comp->ring; + + return &addrs[idx & comp->mask]; +} + +static inline struct xdp_desc *xsk_ring_prod__tx_desc(struct xsk_ring_prod *tx, + __u32 idx) +{ + struct xdp_desc *descs = (struct xdp_desc *)tx->ring; + + return &descs[idx & tx->mask]; +} + +static inline const struct xdp_desc * +xsk_ring_cons__rx_desc(const struct xsk_ring_cons *rx, __u32 idx) +{ + const struct xdp_desc *descs = (const struct xdp_desc *)rx->ring; + + return &descs[idx & rx->mask]; +} + +static inline int xsk_ring_prod__needs_wakeup(const struct xsk_ring_prod *r) +{ + return *r->flags & XDP_RING_NEED_WAKEUP; +} + +static inline __u32 xsk_prod_nb_free(struct xsk_ring_prod *r, __u32 nb) +{ + __u32 free_entries = r->cached_cons - r->cached_prod; + + if (free_entries >= nb) + return free_entries; + + /* Refresh the local tail pointer. + * cached_cons is r->size bigger than the real consumer pointer so + * that this addition can be avoided in the more frequently + * executed code that computs free_entries in the beginning of + * this function. Without this optimization it whould have been + * free_entries = r->cached_prod - r->cached_cons + r->size. + */ + r->cached_cons = *r->consumer + r->size; + + return r->cached_cons - r->cached_prod; +} + +static inline __u32 xsk_cons_nb_avail(struct xsk_ring_cons *r, __u32 nb) +{ + __u32 entries = r->cached_prod - r->cached_cons; + + if (entries == 0) { + r->cached_prod = *r->producer; + entries = r->cached_prod - r->cached_cons; + } + + return (entries > nb) ? nb : entries; +} + +static inline size_t xsk_ring_prod__reserve(struct xsk_ring_prod *prod, + size_t nb, __u32 *idx) +{ + if (xsk_prod_nb_free(prod, nb) < nb) + return 0; + + *idx = prod->cached_prod; + prod->cached_prod += nb; + + return nb; +} + +static inline void xsk_ring_prod__submit(struct xsk_ring_prod *prod, size_t nb) +{ + /* Make sure everything has been written to the ring before indicating + * this to the kernel by writing the producer pointer. + */ + libbpf_smp_wmb(); + + *prod->producer += nb; +} + +static inline size_t xsk_ring_cons__peek(struct xsk_ring_cons *cons, + size_t nb, __u32 *idx) +{ + size_t entries = xsk_cons_nb_avail(cons, nb); + + if (entries > 0) { + /* Make sure we do not speculatively read the data before + * we have received the packet buffers from the ring. + */ + libbpf_smp_rmb(); + + *idx = cons->cached_cons; + cons->cached_cons += entries; + } + + return entries; +} + +static inline void xsk_ring_cons__release(struct xsk_ring_cons *cons, size_t nb) +{ + /* Make sure data has been read before indicating we are done + * with the entries by updating the consumer pointer. + */ + libbpf_smp_rwmb(); + + *cons->consumer += nb; +} + +static inline void *xsk_umem__get_data(void *umem_area, __u64 addr) +{ + return &((char *)umem_area)[addr]; +} + +static inline __u64 xsk_umem__extract_addr(__u64 addr) +{ + return addr & XSK_UNALIGNED_BUF_ADDR_MASK; +} + +static inline __u64 xsk_umem__extract_offset(__u64 addr) +{ + return addr >> XSK_UNALIGNED_BUF_OFFSET_SHIFT; +} + +static inline __u64 xsk_umem__add_offset_to_addr(__u64 addr) +{ + return xsk_umem__extract_addr(addr) + xsk_umem__extract_offset(addr); +} + +LIBBPF_API int xsk_umem__fd(const struct xsk_umem *umem); +LIBBPF_API int xsk_socket__fd(const struct xsk_socket *xsk); + +#define XSK_RING_CONS__DEFAULT_NUM_DESCS 2048 +#define XSK_RING_PROD__DEFAULT_NUM_DESCS 2048 +#define XSK_UMEM__DEFAULT_FRAME_SHIFT 12 /* 4096 bytes */ +#define XSK_UMEM__DEFAULT_FRAME_SIZE (1 << XSK_UMEM__DEFAULT_FRAME_SHIFT) +#define XSK_UMEM__DEFAULT_FRAME_HEADROOM 0 +#define XSK_UMEM__DEFAULT_FLAGS 0 + +struct xsk_umem_config { + __u32 fill_size; + __u32 comp_size; + __u32 frame_size; + __u32 frame_headroom; + __u32 flags; +}; + +/* Flags for the libbpf_flags field. */ +#define XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD (1 << 0) + +struct xsk_socket_config { + __u32 rx_size; + __u32 tx_size; + __u32 libbpf_flags; + __u32 xdp_flags; + __u16 bind_flags; +}; + +/* Set config to NULL to get the default configuration. */ +LIBBPF_API int xsk_umem__create(struct xsk_umem **umem, + void *umem_area, __u64 size, + struct xsk_ring_prod *fill, + struct xsk_ring_cons *comp, + const struct xsk_umem_config *config); +LIBBPF_API int xsk_umem__create_v0_0_2(struct xsk_umem **umem, + void *umem_area, __u64 size, + struct xsk_ring_prod *fill, + struct xsk_ring_cons *comp, + const struct xsk_umem_config *config); +LIBBPF_API int xsk_umem__create_v0_0_4(struct xsk_umem **umem, + void *umem_area, __u64 size, + struct xsk_ring_prod *fill, + struct xsk_ring_cons *comp, + const struct xsk_umem_config *config); +LIBBPF_API int xsk_socket__create(struct xsk_socket **xsk, + const char *ifname, __u32 queue_id, + struct xsk_umem *umem, + struct xsk_ring_cons *rx, + struct xsk_ring_prod *tx, + const struct xsk_socket_config *config); + +/* Returns 0 for success and -EBUSY if the umem is still in use. */ +LIBBPF_API int xsk_umem__delete(struct xsk_umem *umem); +LIBBPF_API void xsk_socket__delete(struct xsk_socket *xsk); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __LIBBPF_XSK_H */ diff --git a/src/bpfswitch/include/xdp_fdb.h b/src/bpfswitch/include/xdp_fdb.h new file mode 100644 index 00000000..3e2e593a --- /dev/null +++ b/src/bpfswitch/include/xdp_fdb.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _XDP_FDB_H_ +#define _XDP_FDB_H_ + +#include + +struct xdp_stats { + __u64 bytes_fwd; + __u64 pkts_fwd; + __u64 dropped; +}; + +struct fdb_key +{ + __u8 smac[ETH_ALEN]; + __u8 dmac[ETH_ALEN]; + //__u16 vlan; +}; + +#endif diff --git a/src/bpfswitch/ksrc/Makefile b/src/bpfswitch/ksrc/Makefile new file mode 100644 index 00000000..64bec2f8 --- /dev/null +++ b/src/bpfswitch/ksrc/Makefile @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0 + +include ../config.mk + +ifneq (,$(BUILDDIR)) +OBJDIR = $(BUILDDIR)/ksrc/obj/ +else +OBJDIR = obj/ +endif + +MODS += $(OBJDIR)xdp_l2fwd.o +MODS += $(OBJDIR)xdp_dummy.o + +VPATH := . + +# rule is based on samples/bpf/Makefile +DEFS = -D__KERNEL__ -D__BPF_TRACING__ -D__TARGET_ARCH_x86 $(EXTRA_DEFS) + +CFLAGS += -Wno-unused-value -Wno-pointer-sign +CFLAGS += -Wno-compare-distinct-pointer-types +CFLAGS += -Wno-gnu-variable-sized-type-not-at-end +CFLAGS += -Wno-address-of-packed-member +CFLAGS += -Wno-tautological-compare +CFLAGS += -Wno-unknown-warning-option +CFLAGS += -fno-stack-protector + +INCLUDES = -I../include +INCLUDES += -I$(KSRC)/arch/x86/include +INCLUDES += -I$(KBLD)/arch/x86/include/generated +INCLUDES += -I$(KBLD)/include +INCLUDES += -I$(KSRC)/include +INCLUDES += -I$(KSRC)/arch/x86/include/uapi +INCLUDES += -I$(KBLD)/arch/x86/include/generated/uapi +INCLUDES += -I$(KSRC)/include/uapi +INCLUDES += -I$(KBLD)/include/generated/uapi + +SINCLUDES = -include $(KSRC)/include/linux/kconfig.h +SINCLUDES += -include include/asm_goto_workaround.h + +# this is to find stdarg.h. Ubuntu has this under x86_64-linux-gnu +# and Fedora is under x86_64-redhat-linux. Let's try 'find'. +GCCVER=$(shell gcc -v 2>&1 | awk '{if ($$0 ~ /gcc version/) {ver=split($$3,n,"."); print n[1]}}') +GCC_INC=$(shell find /usr/lib/gcc/x86_64-*linux*/$(GCCVER) -name include) +NOSTDINC_FLAGS = -nostdinc -isystem $(GCC_INC) + +all: build $(MODS) + +build: + @mkdir -p $(OBJDIR) + +$(OBJDIR)%.o: %.c + $(QUIET_CLANG)$(CLANG) $(NOSTDINC_FLAGS) $(INCLUDES) \ + $(SINCLUDES) $(DEFS) $(CFLAGS) \ + -O2 -emit-llvm $(CLANG_FLAGS) -c $< -o $@.cl + $(QUIET_LLC)$(LLC) -march=bpf $(LLC_FLAGS) -filetype=obj -o $@ $@.cl + @rm $@.cl + +clean: + @rm -rf $(OBJDIR) diff --git a/src/bpfswitch/ksrc/include/asm_goto_workaround.h b/src/bpfswitch/ksrc/include/asm_goto_workaround.h new file mode 100644 index 00000000..7048bb35 --- /dev/null +++ b/src/bpfswitch/ksrc/include/asm_goto_workaround.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2019 Facebook */ +#ifndef __ASM_GOTO_WORKAROUND_H +#define __ASM_GOTO_WORKAROUND_H + +/* + * This will bring in asm_volatile_goto and asm_inline macro definitions + * if enabled by compiler and config options. + */ +#include + +#ifdef asm_volatile_goto +#undef asm_volatile_goto +#define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") +#endif + +/* + * asm_inline is defined as asm __inline in "include/linux/compiler_types.h" + * if supported by the kernel's CC (i.e CONFIG_CC_HAS_ASM_INLINE) which is not + * supported by CLANG. + */ +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif + +#define volatile(x...) volatile("") +#endif diff --git a/src/bpfswitch/ksrc/xdp_dummy.c b/src/bpfswitch/ksrc/xdp_dummy.c new file mode 100644 index 00000000..d60c1c07 --- /dev/null +++ b/src/bpfswitch/ksrc/xdp_dummy.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Dummy XDP program + * + * David Ahern + */ +#define KBUILD_MODNAME "xdp_dummy" +#include +#include +#include + +SEC("xdp_dummy") +int xdp_dummy_prog(struct xdp_md *ctx) +{ + //bpf_debug("ingress: device %u queue %u\n", + // ctx->ingress_ifindex, ctx->rx_queue_index); + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; +int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/src/bpfswitch/ksrc/xdp_l2fwd.c b/src/bpfswitch/ksrc/xdp_l2fwd.c new file mode 100644 index 00000000..750b580d --- /dev/null +++ b/src/bpfswitch/ksrc/xdp_l2fwd.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Example of L2 forwarding via XDP. FDB is a hash table + * returning device index to redirect packet. + * + * Copyright (c) 2019-2020 David Ahern + * The code is modified to use 2 mac addresses as key instead of + * mac address and vlan id as key + * + */ +#define KBUILD_MODNAME "xdp_l2fwd" +#include +#include +#include +#include +#include +#include +#include + +#include "xdp_fdb.h" + +/* For TX-traffic redirect requires net_device ifindex to be in this devmap */ +struct bpf_map_def SEC("maps") xdp_fwd_ports = { + .type = BPF_MAP_TYPE_DEVMAP_HASH, + .key_size = sizeof(u32), + .value_size = sizeof(struct bpf_devmap_val), + .max_entries = 512, +}; + +/* to device index map */ +struct bpf_map_def SEC("maps") fdb_map = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct fdb_key), + .value_size = sizeof(u32), + .max_entries = 512, +}; + +SEC("xdp_l2fwd") +int xdp_l2fwd_prog(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + struct bpf_devmap_val *entry; + struct vlan_hdr *vhdr = NULL; + struct ethhdr *eth; + struct fdb_key key; + u8 smac[ETH_ALEN]; + u16 h_proto = 0; + void *nh; + int rc; + + /* data in context points to ethernet header */ + eth = data; + + /* set pointer to header after ethernet header */ + nh = data + sizeof(*eth); + if (nh > data_end) + return XDP_DROP; // malformed packet + + __builtin_memset(&key, 0, sizeof(key)); + __builtin_memcpy(key.dmac, eth->h_dest, ETH_ALEN); + __builtin_memcpy(key.smac, eth->h_source, ETH_ALEN); + + if (eth->h_proto == htons(ETH_P_8021Q)) { + vhdr = nh; + if (vhdr + 1 > data_end) + return XDP_DROP; // malformed packet + + //key.vlan = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + } + + entry = bpf_map_lookup_elem(&fdb_map, &key); + if (!entry || entry->ifindex == 0) + return XDP_PASS; + + /* Verify redirect index exists in port map */ + if (!bpf_map_lookup_elem(&xdp_fwd_ports, &entry->ifindex)) + return XDP_PASS; + + if (vhdr) { + /* remove VLAN header before hand off to VM */ + h_proto = vhdr->h_vlan_encapsulated_proto; + __builtin_memcpy(smac, eth->h_source, ETH_ALEN); + + if (bpf_xdp_adjust_head(ctx, sizeof(*vhdr))) + return XDP_PASS; + + /* reset data pointers after adjust */ + data = (void *)(long)ctx->data; + data_end = (void *)(long)ctx->data_end; + eth = data; + if (eth + 1 > data_end) + return XDP_DROP; + + __builtin_memcpy(eth->h_dest, key.dmac, ETH_ALEN); + __builtin_memcpy(eth->h_source, smac, ETH_ALEN); + eth->h_proto = h_proto; + } + + return bpf_redirect_map(&xdp_fwd_ports, entry->ifindex, 0); +} + +char _license[] SEC("license") = "GPL"; +int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/src/bpfswitch/scripts/ifindex.py b/src/bpfswitch/scripts/ifindex.py new file mode 100755 index 00000000..aff9a3c5 --- /dev/null +++ b/src/bpfswitch/scripts/ifindex.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import socket +for if_nidx in socket.if_nameindex(): + print("Interface: {}, Index: {}".format(if_nidx[1], if_nidx[0])) diff --git a/src/bpfswitch/scripts/l2fwd.sh b/src/bpfswitch/scripts/l2fwd.sh new file mode 100755 index 00000000..173f93c1 --- /dev/null +++ b/src/bpfswitch/scripts/l2fwd.sh @@ -0,0 +1,137 @@ +#!/bin/bash + +# Copyright David Ashern and Sridhar K. N. Rao + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# L2FWD configuration + + +BPFFS=/sys/fs/bpf +BPFTOOL=/usr/sbin/bpftool + +# MAC ADDRESSES - Ex: Of Traffic-Generator Ports +TGENEASTMAC=12:41:da:80:1f:71 +TGENWESTMAC=12:30:6d:9f:6f:42 + +# Interfaces +EAST=ens0f0 +WEST=ens0f1 + +# Index of Interfaces +EASTINDEX=1 +EASTINDEX=2 + + +################################################################################ +# +pr_msg() +{ + echo -e "\e[34m$*\e[00m" +} + +run_cmd() +{ + local cmd="$*" + + echo + echo -e "\e[31m${cmd}\e[00m" + sudo $cmd +} + +show_maps() +{ + echo + echo -e "\e[31m${BPFTOOL} map sh\e[00m" + sudo ${BPFTOOL} map sh | \ + awk 'BEGIN { skip = 0 } { + if (skip) { + skip-- + } else if ($2 == "lpm_trie") { + skip = 1 + } else { + print + } + }' +} + +show_progs() +{ + echo + echo -e "\e[31m${BPFTOOL} prog sh\e[00m" + sudo ${BPFTOOL} prog sh | \ + awk 'BEGIN { skip = 0 } { + if (skip) { + skip-- + } else if ($2 == "cgroup_skb") { + skip = 2 + } else { + print + } + }' +} + +show_status() +{ + show_maps + show_progs + run_cmd ${BPFTOOL} net sh +} + +do_reset() +{ + sudo rm -rf ${BPFFS}/map + sudo rm -rf ${BPFFS}/prog + sudo mkdir ${BPFFS}/map + sudo mkdir ${BPFFS}/prog + + for d in eth0 eth1 + do + sudo ${BPFTOOL} net detach xdp dev ${d} + sudo ethtool -K ${d} hw-tc-offload on + sudo ethtool -K ${d} rxvlan off + done +} + +################################################################################ +# start + +do_reset >/dev/null 2>&1 + +echo +pr_msg "Create ports map" +pr_msg "- global map used for bulking redirected packets" + +run_cmd ${BPFTOOL} map create ${BPFFS}/map/xdp_fwd_ports \ + type devmap_hash key 4 value 8 entries 512 name xdp_fwd_ports + +echo +pr_msg "Add entries to the egress port map for EAST and WEST Interfaces" +run_cmd ${BPFTOOL} map update pinned ${BPFFS}/map/xdp_fwd_ports \ + key hex ${EASTINDEX} 0 0 0 value hex ${EASTINDEX} 0 0 0 0 0 0 0 +run_cmd ${BPFTOOL} map update pinned ${BPFFS}/map/xdp_fwd_ports \ + key hex ${WESTINDEX} 0 0 0 value hex ${WESTINDEX} 0 0 0 0 0 0 0 + +echo +pr_msg "load l2fwd program and attach to eth0 and eth1" + +run_cmd ${BPFTOOL} prog load ../ksrc/obj/xdp_l2fwd.o ${BPFFS}/prog/xdp_l2fwd \ + map name xdp_fwd_ports name xdp_fwd_ports +run_cmd ${BPFTOOL} net attach xdp pinned ${BPFFS}/prog/xdp_l2fwd dev ${EAST} +run_cmd ${BPFTOOL} net attach xdp pinned ${BPFFS}/prog/xdp_l2fwd dev ${WEST} + +echo +pr_msg "Add FDB and port map entries for this" +run_cmd ../usrc/bin/xdp_l2fwd -s ${TGENEASTMAC} -m ${TGENWESTMAC} -d eth1 +run_cmd ../usrc/bin/xdp_l2fwd -s ${TGENWESTMAC} -m ${TGENEASTMAC} -d eth1 +run_cmd ../usrc/bin/xdp_l2fwd -P diff --git a/src/bpfswitch/usrc/Makefile b/src/bpfswitch/usrc/Makefile new file mode 100644 index 00000000..fc15a6b0 --- /dev/null +++ b/src/bpfswitch/usrc/Makefile @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 + +include ../config.mk + +ifneq (,$(BUILDDIR)) +OBJDIR = $(BUILDDIR)/src/obj/ +else +OBJDIR = obj/ +endif + +ifneq (,$(BUILDDIR)) +BINDIR = $(BUILDDIR)/src/bin/ +else +BINDIR = bin/ +endif + +MODS += $(BINDIR)xdp_l2fwd +MODS += $(BINDIR)xdp_dummy + +VPATH := . + +CC = gcc +CFLAGS += -O2 -g -Wall + +INCLUDES = -I../include -I../include/uapi +INCLUDES += -I../include/tools + +ifneq (,$(LIBBPF_DIR)) +BPF_LINK_FEAT := $(shell egrep 'bpf_link_create' $(LIBBPF_PATH)/usr/include/bpf/libbpf.h) +ifneq (,$(BPF_LINK_FEAT)) +CFLAGS += -DHAVE_BPF_LINK_CREATE +endif +else +LIBBPF=-lbpf +endif + +#COMMON += $(OBJDIR)timestamps.o +COMMON += $(OBJDIR)libbpf_helpers.o +COMMON += $(OBJDIR)str_utils.o +#COMMON += $(OBJDIR)rbtree.o +#COMMON += $(OBJDIR)parse_pkt.o +#COMMON += $(OBJDIR)print_pkt.o +#COMMON += $(OBJDIR)ksyms.o + +all: build $(MODS) + +build: + @mkdir -p $(OBJDIR) $(BINDIR) + +$(BINDIR)%: $(OBJDIR)%.o $(COMMON) + $(QUIET_LINK)$(CC) $(INCLUDES) $(DEFS) $(CFLAGS) $^ -o $@ $(LDLIBS) + +$(BINDIR)xdp_%: $(OBJDIR)xdp_%_user.o $(COMMON) + $(QUIET_LINK)$(CC) $(INCLUDES) $(DEFS) $(CFLAGS) $^ -o $@ $(LDLIBS) + +$(OBJDIR)%.o: %.c + $(QUIET_CC)$(CC) $(INCLUDES) $(DEFS) $(CFLAGS) -c $^ -o $@ + +clean: + @rm -rf $(OBJDIR) $(BINDIR) diff --git a/src/bpfswitch/usrc/libbpf_helpers.c b/src/bpfswitch/usrc/libbpf_helpers.c new file mode 100644 index 00000000..522f5586 --- /dev/null +++ b/src/bpfswitch/usrc/libbpf_helpers.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * convenience wrappers around libbpf functions + * + * Copyright (c) 2019-2021 David Ahern + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libbpf_helpers.h" + +enum bpf_obj_type { + BPF_OBJ_UNKNOWN, + BPF_OBJ_PROG, + BPF_OBJ_MAP, + BPF_OBJ_LINK, + BPF_OBJ_BTF, +}; + +int load_obj_file(struct bpf_prog_load_attr *attr, + struct bpf_object **obj, + const char *objfile, bool user_set) +{ + static char *expected_paths[] = { + "bin", + "ksrc/obj", /* path in git tree */ + "bpf-obj", + ".", /* cwd */ + NULL, + }; + char path[PATH_MAX]; + int prog_fd, i = 0; + + if (user_set) { + attr->file = objfile; + return bpf_prog_load_xattr(attr, obj, &prog_fd); + } + + attr->file = path; + while (expected_paths[i]) { + struct stat sbuf; + + snprintf(path, sizeof(path), "%s/%s", + expected_paths[i], objfile); + + if (stat(path, &sbuf) == 0) { + if (!bpf_prog_load_xattr(attr, obj, &prog_fd)) + return 0; + + if (errno != ENOENT) + break; + } + ++i; + } + + fprintf(stderr, "Failed to find object file; nothing to load\n"); + return 1; +} + +int bpf_map_get_fd_by_name(const char *name) +{ + struct bpf_map_info info = {}; + __u32 len = sizeof(info); + __u32 id = 0; + int err, fd; + + while (1) { + err = bpf_map_get_next_id(id, &id); + if (err) + break; + + fd = bpf_map_get_fd_by_id(id); + if (fd < 0) + continue; + + err = bpf_obj_get_info_by_fd(fd, &info, &len); + if (!err && strcmp(info.name, name) == 0) + return fd; + + close(fd); + } + + return -1; +} + +/* from bpftool */ +static int get_fd_type(int fd) +{ + char path[PATH_MAX]; + char buf[512]; + ssize_t n; + + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + n = readlink(path, buf, sizeof(buf)); + if (n < 0) { + fprintf(stderr, "can't read link type: %s\n", strerror(errno)); + return -1; + } + if (n == sizeof(path)) { + fprintf(stderr, "can't read link type: path too long!\n"); + return -1; + } + + if (strstr(buf, "bpf-map")) + return BPF_OBJ_MAP; + + if (strstr(buf, "bpf-prog")) + return BPF_OBJ_PROG; + + if (strstr(buf, "bpf-link")) + return BPF_OBJ_LINK; + + return BPF_OBJ_UNKNOWN; +} + +int bpf_map_get_fd_by_path(const char *path) +{ + enum bpf_obj_type objtype; + int fd; + + fd = bpf_obj_get(path); + if (fd < 0) { + fprintf(stderr, "Failed to get bpf object (%s): %s\n", + path, strerror(errno)); + return -1; + } + + objtype = get_fd_type(fd); + if (objtype != BPF_OBJ_MAP) { + fprintf(stderr, "Path is not to a BPF map\n"); + close(fd); + return -1; + } + + return fd; +} + +int bpf_map_get_fd(__u32 id, const char *path, const char *name, + const char *desc) +{ + int fd = -1; + + if (id) { + fd = bpf_map_get_fd_by_id(id); + if (fd < 0 && errno != ENOENT) { + fprintf(stderr, + "Failed to get fd for %s by id: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } else if (path) { + fd = bpf_map_get_fd_by_path(path); + if (fd < 0) { + fprintf(stderr, + "Failed to get fd for %s by path: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } else if (name) { + fd = bpf_map_get_fd_by_name(name); + if (fd < 0 && errno != ENOENT) { + fprintf(stderr, + "Failed to get fd for %s by expected name: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } + + return fd; +} + +int bpf_prog_get_fd_by_path(const char *path) +{ + enum bpf_obj_type objtype; + int fd; + + fd = bpf_obj_get(path); + if (fd < 0) { + fprintf(stderr, "Failed to get bpf object (%s): %s\n", + path, strerror(errno)); + return -1; + } + + objtype = get_fd_type(fd); + if (objtype != BPF_OBJ_PROG) { + fprintf(stderr, "Path is not to a BPF program\n"); + close(fd); + return -1; + } + + return fd; +} + +int bpf_prog_get_fd_by_name(const char *name) +{ + struct bpf_prog_info info = {}; + __u32 len = sizeof(info); + __u32 id = 0; + int err, fd; + + while (1) { + err = bpf_prog_get_next_id(id, &id); + if (err) + break; + + fd = bpf_prog_get_fd_by_id(id); + if (fd < 0) + continue; + + err = bpf_obj_get_info_by_fd(fd, &info, &len); + if (!err && strcmp(info.name, name) == 0) + return fd; + + close(fd); + } + + return -1; +} + +int bpf_prog_get_fd(__u32 id, const char *path, const char *name, + const char *desc) +{ + int fd = -1; + + if (id) { + fd = bpf_prog_get_fd_by_id(id); + if (fd < 0 && errno != ENOENT) { + fprintf(stderr, + "Failed to get fd for %s by id: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } else if (path) { + fd = bpf_prog_get_fd_by_path(path); + if (fd < 0) { + fprintf(stderr, + "Failed to get fd for %s by path: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } else if (name) { + fd = bpf_prog_get_fd_by_name(name); + if (fd < 0 && errno != ENOENT) { + fprintf(stderr, + "Failed to get fd for %s by expected name: %s: %d\n", + desc, strerror(errno), errno); + return -1; + } + } + + return fd; +} + +int attach_to_dev_generic(int idx, int prog_fd, const char *dev) +{ + int err; + + err = bpf_set_link_xdp_fd(idx, prog_fd, XDP_FLAGS_SKB_MODE); + if (err < 0) { + printf("ERROR: failed to attach program to %s\n", dev); + return err; + } + + return 0; +} + +int detach_from_dev_generic(int idx, const char *dev) +{ + int err; + + err = bpf_set_link_xdp_fd(idx, -1, XDP_FLAGS_SKB_MODE); + if (err < 0) + printf("ERROR: failed to detach program from %s\n", dev); + + return 0; +} + + +int attach_to_dev(int idx, int prog_fd, const char *dev) +{ + int err; + + err = bpf_set_link_xdp_fd(idx, prog_fd, XDP_FLAGS_DRV_MODE); + if (err < 0) { + printf("ERROR: failed to attach program to %s\n", dev); + return err; + } + + return 0; +} + +int detach_from_dev(int idx, const char *dev) +{ + int err; + + err = bpf_set_link_xdp_fd(idx, -1, 0); + if (err < 0) + printf("ERROR: failed to detach program from %s\n", dev); + + return 0; +} diff --git a/src/bpfswitch/usrc/libbpf_helpers.h b/src/bpfswitch/usrc/libbpf_helpers.h new file mode 100644 index 00000000..9be0513c --- /dev/null +++ b/src/bpfswitch/usrc/libbpf_helpers.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LIBBPF_HELPERS_H +#define __LIBBPF_HELPERS_H + +#include + +int load_obj_file(struct bpf_prog_load_attr *attr, + struct bpf_object **obj, + const char *objfile, bool user_set); + +int bpf_map_get_fd_by_name(const char *name); +int bpf_map_get_fd_by_path(const char *path); +int bpf_map_get_fd(__u32 id, const char *path, const char *name, + const char *desc); + +int bpf_prog_get_fd_by_path(const char *path); +int bpf_prog_get_fd(__u32 id, const char *path, const char *name, + const char *desc); + +int attach_to_dev_generic(int idx, int prog_fd, const char *dev); +int detach_from_dev_generic(int idx, const char *dev); + +int attach_to_dev(int idx, int prog_fd, const char *dev); +int detach_from_dev(int idx, const char *dev); + +int attach_to_dev_tx(int idx, int prog_fd, const char *dev); +int detach_from_dev_tx(int idx, const char *dev); + +#endif diff --git a/src/bpfswitch/usrc/str_utils.c b/src/bpfswitch/usrc/str_utils.c new file mode 100644 index 00000000..8650cdfb --- /dev/null +++ b/src/bpfswitch/usrc/str_utils.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * String conversion and parsing functions. + * + * David Ahern + */ +#include +#include +#include +#include +#include +#include +#include + +#include "str_utils.h" + +static int str_to_int_base(const char *str, int min, int max, int *value, int base) +{ + int number; + char *end; + + errno = 0; + number = (int) strtol(str, &end, base); + + if ( ((*end == '\0') || (*end == '\n')) && (end != str) && + (errno != ERANGE) && (min <= number) && (number <= max)) { + *value = number; + return 0; + } + + return -1; +} + +int str_to_int(const char *str, int min, int max, int *value) +{ + return str_to_int_base(str, min, max, value, 0); +} + +int str_to_ushort(const char *str, unsigned short *us) +{ + int i; + + if (str_to_int(str, 0, 0xFFFF, &i) != 0) + return -1; + + *us = (unsigned short) (i); + + return 0; +} + +int str_to_ulong_base(const char *str, unsigned long *ul, int base) +{ + char *end; + + *ul= strtoul(str, &end, base); + if (*end != '\0') + return -1; + + return 0; +} + +int str_to_ulong(const char *str, unsigned long *ul) +{ + return str_to_ulong_base(str, ul, 0); +} + +int str_to_ullong(const char *str, unsigned long long *ul) +{ + char *end; + + *ul= strtoull(str, &end, 0); + if (*end != '\0') + return -1; + + return 0; +} + +int str_to_mac(const char *str, unsigned char *mac) +{ + int rc = -1, m, i; + char *s = strdup(str), *p, *d, tmp[3]; + + if (!s) + return -1; + + p = s; + tmp[2] = '\0'; + for (i = 0; i < ETH_ALEN; ++i) { + if (*p == '\0') + goto out; + + d = strchr(p, ':'); + if (d) { + *d = '\0'; + if (strlen(p) > 2) + goto out; + + strcpy(tmp, p); + p = d + 1; + } else { + strncpy(tmp, p, 2); + p += 2; + } + + if (str_to_int_base(tmp, 0, 0xFF, &m, 16) != 0) + goto out; + + mac[i] = m; + } + + if (*p == '\0') + rc = 0; +out: + free(s); + + return rc; +} + +int get_ifidx(const char *arg) +{ + int idx; + + idx = if_nametoindex(arg); + if (!idx) + idx = strtoul(arg, NULL, 0); + + return idx; +} + +/* find parameters in a string -- based on Harbison and Steele, p. 291 */ +int parsestr(char *str, char *delims, char *fields[], int nmax) +{ + int n; + + if (!str || (*str == '\0')) + return 0; + + n = 0; + fields[0] = strtok(str, delims); + while ((fields[n] != (char *) NULL) && (n < (nmax-1))) { + ++n; + fields[n] = strtok(NULL, delims); + } + + if ((n == (nmax - 1)) && (fields[n] != (char *) NULL)) + ++n; + + return n; +} + +void print_mac(const __u8 *mac, bool reverse) +{ + if (reverse) + printf("%.02x:%.02x:%.02x:%.02x:%.02x:%.02x", + mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]); + else + printf("%.02x:%.02x:%.02x:%.02x:%.02x:%.02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} diff --git a/src/bpfswitch/usrc/str_utils.h b/src/bpfswitch/usrc/str_utils.h new file mode 100644 index 00000000..817eb5f6 --- /dev/null +++ b/src/bpfswitch/usrc/str_utils.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __STR_UTILS_H +#define __STR_UTILS_H + +int str_to_int(const char *str, int min, int max, int *value); +int str_to_ushort(const char *str, unsigned short *us); +int str_to_ulong(const char *str, unsigned long *ul); +int str_to_ulong_base(const char *str, unsigned long *ul, int base); +int str_to_ullong(const char *str, unsigned long long *ul); +int str_to_mac(const char *str, unsigned char *mac); +int get_ifidx(const char *arg); + +int parsestr(char *str, char *delims, char *fields[], int nmax); +void print_mac(const __u8 *mac, bool reverse); +#endif diff --git a/src/bpfswitch/usrc/xdp_dummy_user.c b/src/bpfswitch/usrc/xdp_dummy_user.c new file mode 100644 index 00000000..7741f330 --- /dev/null +++ b/src/bpfswitch/usrc/xdp_dummy_user.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libbpf_helpers.h" + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s [OPTS] interface-list\n" + "\nOPTS:\n" + " -d detach program\n" + " -f bpf-file bpf filename to load\n" + " -g skb mode\n" + , prog); +} + +int main(int argc, char **argv) +{ + int (*attach_fn)(int idx, int prog_fd, const char *dev) = attach_to_dev; + int (*detach_fn)(int idx, const char *dev) = detach_from_dev; + struct bpf_prog_load_attr prog_load_attr = { }; + const char *objfile = "xdp_dummy_kern.o"; + const char *pname = "xdp_dummy"; + bool filename_set = false; + struct bpf_program *prog; + struct bpf_object *obj; + int opt, i, prog_fd; + bool attach = true; + int ret = 0; + + while ((opt = getopt(argc, argv, ":df:g")) != -1) { + switch (opt) { + case 'f': + objfile = optarg; + filename_set = true; + break; + case 'd': + attach = false; + break; + case 'g': + attach_fn = attach_to_dev_generic; + detach_fn = detach_from_dev_generic; + break; + default: + usage(basename(argv[0])); + return 1; + } + } + + if (optind == argc) { + usage(basename(argv[0])); + return 1; + } + + if (!attach) { + for (i = optind; i < argc; ++i) { + int idx, err; + + idx = if_nametoindex(argv[i]); + if (!idx) + idx = strtoul(argv[i], NULL, 0); + + if (!idx) { + fprintf(stderr, "Invalid device argument\n"); + return 1; + } + err = detach_fn(idx, argv[i]); + if (err) + ret = err; + } + return ret; + } + + if (load_obj_file(&prog_load_attr, &obj, objfile, filename_set)) + return 1; + + prog = bpf_object__find_program_by_title(obj, pname); + prog_fd = bpf_program__fd(prog); + if (prog_fd < 0) { + printf("program not found: %s\n", strerror(prog_fd)); + return 1; + } + + for (i = optind; i < argc; ++i) { + int idx, err; + + idx = if_nametoindex(argv[i]); + if (!idx) + idx = strtoul(argv[i], NULL, 0); + + if (!idx) { + fprintf(stderr, "Invalid device argument\n"); + return 1; + } + + err = attach_fn(idx, prog_fd, argv[i]); + if (err) + ret = err; + } + + return ret; +} diff --git a/src/bpfswitch/usrc/xdp_l2fwd_user.c b/src/bpfswitch/usrc/xdp_l2fwd_user.c new file mode 100644 index 00000000..13437621 --- /dev/null +++ b/src/bpfswitch/usrc/xdp_l2fwd_user.c @@ -0,0 +1,402 @@ +/* Copyright (c) 2019-22 David Ahern + * + * The code is modifed to use 2 mac addresses as key instead of + * mac-address and vland-id as key + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdp_fdb.h" +#include "str_utils.h" +#include "libbpf_helpers.h" + +static bool fdb_map_verify(int map_fd) +{ + struct bpf_map_info info = {}; + __u32 len = sizeof(info); + + if (bpf_obj_get_info_by_fd(map_fd, &info, &len)) { + fprintf(stderr, "Failed to get map info: %s: %d", + strerror(errno), errno); + return false; + } + + if (info.type != BPF_MAP_TYPE_HASH || + info.key_size != sizeof(struct fdb_key) || + info.value_size != sizeof(__u32)) { + fprintf(stderr, "Incompatible map\n"); + return false; + } + + return true; +} + +static bool ports_map_verify(int ports_fd, bool *with_prog) +{ + struct bpf_map_info info = {}; + __u32 len = sizeof(info); + + if (bpf_obj_get_info_by_fd(ports_fd, &info, &len)) { + fprintf(stderr, "Failed to get map info: %s: %d", + strerror(errno), errno); + return false; + } + + if (info.value_size == sizeof(struct bpf_devmap_val)) + *with_prog = true; + + if (info.type != BPF_MAP_TYPE_DEVMAP_HASH || + info.key_size != sizeof(__u32) || + (info.value_size != sizeof(__u32) && !with_prog)) { + fprintf(stderr, "Incompatible map\n"); + return false; + } + + return true; +} + +static int show_entries_cli(int fdb_fd, int ports_fd) +{ + struct fdb_key *key, *prev_key = NULL; + struct bpf_devmap_val pval; + bool with_prog = false; + char buf[IFNAMSIZ]; + int err, i; + __u32 fval; + + + if (!fdb_map_verify(fdb_fd) || + !ports_map_verify(ports_fd, &with_prog)) + return 1; + + key = calloc(1, sizeof(*key)); + if (!key) { + fprintf(stderr, "Failed to allocate memory for key\n"); + return 1; + } + + for (i = 0; ; ++i) { + err = bpf_map_get_next_key(fdb_fd, prev_key, key); + if (err) { + if (errno == ENOENT) + err = 0; + break; + } + + fval = 0; + memset(&pval, 0, sizeof(pval)); + if (bpf_map_lookup_elem(fdb_fd, key, &fval)) + goto next_key; + + if (if_indextoname(fval, buf) == NULL) { + fprintf(stderr, "WARNING: stale device index\n"); + snprintf(buf, IFNAMSIZ, "-"); + } + + //printf("-v %u -m ", key->vlan); + print_mac(key->smac, false); + print_mac(key->dmac, false); + printf(" -d %s", buf); + + if (bpf_map_lookup_elem(ports_fd, &fval, &pval)) { + fprintf(stderr, + "No ports entry for device %s/%d\n", buf, fval); + goto end_entry; + } + + if (with_prog) + printf(" -p %u", pval.bpf_prog.id); +end_entry: + printf("\n"); +next_key: + prev_key = key; + } + + free(key); + return err; +} + +static int show_ports_entries(int map_fd) +{ + __u32 *key, *prev_key = NULL; + struct bpf_devmap_val val; + bool with_prog = false; + int err; + + if (!ports_map_verify(map_fd, &with_prog)) + return 1; + + key = calloc(1, sizeof(*key)); + if (!key) { + fprintf(stderr, "Failed to allocate memory for key\n"); + return 1; + } + + printf("\nPorts map:\n"); + while(1) { + err = bpf_map_get_next_key(map_fd, prev_key, key); + if (err) { + if (errno == ENOENT) + err = 0; + break; + } + + memset(&val, 0, sizeof(val)); + if (!bpf_map_lookup_elem(map_fd, key, &val)) { + char buf[IFNAMSIZ]; + + if (if_indextoname(val.ifindex, buf) == NULL) { + fprintf(stderr, "WARNING: stale device index\n"); + snprintf(buf, IFNAMSIZ, "-"); + } + + printf("index %u -> device %s/%u", + *key, buf, val.ifindex); + if (with_prog && val.bpf_prog.id) + printf(", prog id %u", val.bpf_prog.id); + printf("\n"); + } + + prev_key = key; + } + + free(key); + return err; +} + +static int show_fdb_entries(int map_fd) +{ + struct fdb_key *key, *prev_key = NULL; + int err, i; + __u32 val; + + if (!fdb_map_verify(map_fd)) + return 1; + + key = calloc(1, sizeof(*key)); + if (!key) { + fprintf(stderr, "Failed to allocate memory for key\n"); + return 1; + } + + printf("FDB map:\n"); + for (i = 0; ; ++i) { + err = bpf_map_get_next_key(map_fd, prev_key, key); + if (err) { + if (errno == ENOENT) + err = 0; + break; + } + + val = 0; + if (!bpf_map_lookup_elem(map_fd, key, &val)) { + char buf[IFNAMSIZ]; + + if (if_indextoname(val, buf) == NULL) { + fprintf(stderr, "WARNING: stale device index\n"); + snprintf(buf, IFNAMSIZ, "-"); + } + + //printf("entry %d: < %u, ", i, key->vlan); + print_mac(key->smac, false); + print_mac(key->dmac, false); + printf(" > --> device %s/%u\n", buf, val); + } + + prev_key = key; + } + + free(key); + return err; +} + +/* remove from fdb then remove device */ +static int remove_entries(int fdb_fd, struct fdb_key *key, + int ports_fd, int idx) +{ + int rc; + + rc = bpf_map_delete_elem(fdb_fd, key); + if (rc) + fprintf(stderr, "Failed to delete fdb entry\n"); + + rc = bpf_map_delete_elem(ports_fd, &idx); + if (rc) + fprintf(stderr, "Failed to delete ports entry\n"); + return rc; +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s [OPTS]\n" + "\nOPTS:\n" + " -f id fdb map id or pinned path\n" + " -t id devmap id or pinned path for tx ports\n" + " -d device device to redirect\n" + " -m mac Destination mac address for entry\n" + " -s mac Source mac address for entry\n" + " -r remove entries\n" + " -p progid bpf program id or pinned path to attach to entry\n" + " -P print map entries\n" + " -C print map entries in command line format\n" + , prog); +} + +int main(int argc, char **argv) +{ + struct bpf_devmap_val pval = { .bpf_prog.fd = -1 }; + __u32 fdb_id = 0, ports_id = 0, bpf_prog_id = 0; + const char *ports_map_path = NULL; + const char *bpf_prog_path = NULL; + const char *fdb_map_path = NULL; + bool print_entries = false; + struct fdb_key key = {}; + int fdb_fd, ports_fd; + bool delete = false; + bool cli_arg = false; + unsigned long tmp; + int opt, ret; + + while ((opt = getopt(argc, argv, ":f:t:d:m:s:p:rPC")) != -1) { + switch (opt) { + case 'f': + if (str_to_ulong(optarg, &tmp) == 0) { + fdb_id = (__u32)tmp; + } else if (*optarg == '/') { + fdb_map_path = optarg; + } else { + fprintf(stderr, "Invalid fdb map id\n"); + return 1; + } + break; + case 't': + if (str_to_ulong(optarg, &tmp) == 0) { + ports_id = (__u32)tmp; + } else if (*optarg == '/') { + ports_map_path = optarg; + } else { + fprintf(stderr, "Invalid ports map id\n"); + return 1; + } + break; + case 'd': + pval.ifindex = if_nametoindex(optarg); + if (!pval.ifindex) { + if (str_to_int(optarg, 0, INT_MAX, &ret)) { + fprintf(stderr, "Invalid device\n"); + return 1; + } + pval.ifindex = (__u32)ret; + } + break; + case 'm': + if (str_to_mac(optarg, key.dmac)) { + fprintf(stderr, "Invalid Dest-mac address\n"); + return 1; + } + break; + case 's': + /* + if (str_to_int(optarg, 0, 4095, &ret)) { + fprintf(stderr, "Invalid vlan\n"); + return 1; + } + key.vlan = (__u16)ret; + */ + if (str_to_mac(optarg, key.smac)) { + fprintf(stderr, "Invalid Source-mac address\n"); + return 1; + } + break; + case 'p': + if (str_to_ulong(optarg, &tmp) == 0) { + bpf_prog_id = (__u32)tmp; + } else if (*optarg == '/') { + bpf_prog_path = optarg; + } else { + fprintf(stderr, "Invalid program id\n"); + return 1; + } + break; + case 'r': + delete = true; + break; + case 'C': + cli_arg = true; + break; + case 'P': + print_entries = true; + break; + default: + usage(basename(argv[0])); + return 1; + } + } + + fdb_fd = bpf_map_get_fd(fdb_id, fdb_map_path, "fdb_map", "fdb map"); + if (fdb_fd < 0) + return 1; + + ports_fd = bpf_map_get_fd(ports_id, ports_map_path, "xdp_fwd_ports", + "ports map"); + if (ports_fd < 0) + return 1; + + if (cli_arg) + return show_entries_cli(fdb_fd, ports_fd); + + if (print_entries) { + ret = show_fdb_entries(fdb_fd); + return show_ports_entries(ports_fd) ? : ret; + } + + pval.bpf_prog.fd = bpf_prog_get_fd(bpf_prog_id, bpf_prog_path, NULL, + "redirect program"); + if (pval.bpf_prog.fd < 0) + return 1; + + if (!pval.ifindex) { + fprintf(stderr, "Device index not given\n"); + return 1; + } + + if (delete) + return remove_entries(fdb_fd, &key, ports_fd, pval.ifindex); + + /* add device to port map and then add fdb entry */ + ret = bpf_map_update_elem(ports_fd, &pval.ifindex, &pval, 0); + if (ret) { + fprintf(stderr, "Failed to add ports entry: %s: %d\n", + strerror(errno), errno); + remove_entries(fdb_fd, &key, ports_fd, pval.ifindex); + return ret; + } + + ret = bpf_map_update_elem(fdb_fd, &key, &pval.ifindex, BPF_ANY); + if (ret) { + fprintf(stderr, "Failed to add fdb entry: %s: %d\n", + strerror(errno), errno); + remove_entries(fdb_fd, &key, ports_fd, pval.ifindex); + return ret; + } + + return 0; +} diff --git a/systems/README.md b/systems/README.md index d0812a81..c4148e41 100644 --- a/systems/README.md +++ b/systems/README.md @@ -39,4 +39,31 @@ May need following changes: DPDK_LIB = $(DPDK_DIR)/build/lib64 ``` - +eBPF +---- + +Adding eBPF support may require additional installations. The installation +script already includes tools like llvm, clang, libbpf-dev, etc. +For some programs, you may have to build kernel by downloading the source. +In the folder where you have kernel source, run: +``` +make oldconfig && make prepare && make headers_install && make +``` +While building any program, if you encounter an error with bpf.h in +include/linux/ folder, get the latest of bcc from iovisor project, +using following commands + +``` +apt purge bpfcc-tools libbpfcc python3-bpfcc +wget https://github.com/iovisor/bcc/releases/download/v0.25.0/bcc-src-with-submodule.tar.gz +tar xf bcc-src-with-submodule.tar.gz +cd bcc/ +apt install -y python-is-python3 +apt install -y bison build-essential cmake flex git libedit-dev libllvm11 llvm-11-dev libclang-11-dev zlib1g-dev libelf-dev libfl-dev python3-distutils +apt install -y checkinstall +mkdir build +cd build/ +cmake -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3 .. +make +checkinstall +``` diff --git a/systems/debian/build_base_machine.sh b/systems/debian/build_base_machine.sh index e41837a2..0c078140 100755 --- a/systems/debian/build_base_machine.sh +++ b/systems/debian/build_base_machine.sh @@ -30,6 +30,8 @@ apt-get -y install python3-venv apt-get -y install python3-pyelftools apt-get -y install meson apt-get -y install ninja-build +apt-get -y install clang +apt-get -y install llvm # Make and Compilers apt-get -y install make @@ -40,3 +42,7 @@ apt-get -y install libssl1.1 apt-get -y install libxml2 apt-get -y install zlib1g-dev apt-get -y install scapy +apt-get -y install libbpf-dev +apt-get -y install libelf-dev +apt-get -y install linux-tools-$(uname -r) +apt-get -y install linux-cloud-tools-$(uname -r) diff --git a/systems/fedora/33/build_base_machine.sh b/systems/fedora/33/build_base_machine.sh index f3a27644..a6a973ce 100644 --- a/systems/fedora/33/build_base_machine.sh +++ b/systems/fedora/33/build_base_machine.sh @@ -51,6 +51,10 @@ cifs-utils socat sysstat sshpass +llvm +clang +libbpf-devel + # install python packages python3-virtualenv diff --git a/systems/fedora/36/build_base_machine.sh b/systems/fedora/36/build_base_machine.sh index f3a27644..a9a7f63b 100755 --- a/systems/fedora/36/build_base_machine.sh +++ b/systems/fedora/36/build_base_machine.sh @@ -51,6 +51,9 @@ cifs-utils socat sysstat sshpass +llvm +clang +libbpf-devel # install python packages python3-virtualenv diff --git a/systems/ubuntu/build_base_machine.sh b/systems/ubuntu/build_base_machine.sh index 910cd173..c66fae43 100755 --- a/systems/ubuntu/build_base_machine.sh +++ b/systems/ubuntu/build_base_machine.sh @@ -34,6 +34,8 @@ apt-get -y install scapy apt-get -y install meson apt-get -y install ninja-build apt-get -y install python3-pyelftools +apt-get -y install clang +apt-get -y install llvm # Linux Kernel Source apt-get -y install linux-source @@ -50,6 +52,10 @@ apt-get -y install libfuse-dev apt-get -y install libnuma1 apt-get -y install libnuma-dev apt-get -y install sshpass +apt-get -y install libelf-dev +apt-get -y install libbpf-dev +apt-get -y install linux-tools-$(uname -r) +apt-get -y install linux-cloud-tools-$(uname -r) # Some useful tools you may optionally install #apt-get -y install ctags -- 2.16.6 From 9bb2fe4b0f99a0c7a8a675f3277c19ed304d852f Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Tue, 13 Dec 2022 22:05:48 +0530 Subject: [PATCH 15/16] TOOLS: Add eBPF-based solution support This patch adds automation support for eBPF based CNI. Add license headers Signed-off-by: Sridhar K. N. Rao Change-Id: I1ad7e6f785b5dd98a170c87a74f8b7ce02daa765 --- tools/ebpfautomation/afxdp-deploy/afxdp-nad.yaml | 68 +++++++++++ tools/ebpfautomation/afxdp-deploy/daemonset.yml | 135 +++++++++++++++++++++ tools/ebpfautomation/afxdp-deploy/deploy.sh | 42 +++++++ .../afxdp-podspec/afxdp-podspec.yaml | 47 +++++++ .../cnibinarybuilder/afxdp/Dockerfile | 30 +++++ 5 files changed, 322 insertions(+) create mode 100644 tools/ebpfautomation/afxdp-deploy/afxdp-nad.yaml create mode 100644 tools/ebpfautomation/afxdp-deploy/daemonset.yml create mode 100644 tools/ebpfautomation/afxdp-deploy/deploy.sh create mode 100644 tools/ebpfautomation/afxdp-podspec/afxdp-podspec.yaml create mode 100644 tools/ebpfautomation/cnibinarybuilder/afxdp/Dockerfile diff --git a/tools/ebpfautomation/afxdp-deploy/afxdp-nad.yaml b/tools/ebpfautomation/afxdp-deploy/afxdp-nad.yaml new file mode 100644 index 00000000..1174941c --- /dev/null +++ b/tools/ebpfautomation/afxdp-deploy/afxdp-nad.yaml @@ -0,0 +1,68 @@ +# Copyright 2022 The Linux Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# WARNING: This is an example definition only. Remove all comments before use. + +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: afxdp-east-network # Name of this network, pods will request this network by name + annotations: + k8s.v1.cni.cncf.io/resourceName: afxdp/eastPool # Needs to match the device plugin pool name / resource type +spec: + config: '{ + "cniVersion": "0.3.0", + "type": "afxdp", # CNI binary, leave as afxdp + "mode": "cdq", # CNI mode setting (required) + "logFile": "afxdp-cni-east.log", # CNI log file location (optional) + "logLevel": "debug", # CNI logging level (optional) + "ipam": { # CNI IPAM plugin and associated config (optional) + "type": "host-local", + "subnet": "192.168.1.0/24", + "rangeStart": "192.168.1.200", + "rangeEnd": "192.168.1.220", + "routes": [ + { "dst": "0.0.0.0/0" } + ], + "gateway": "192.168.1.1" + } + }' + +--- + +apiVersion: "k8s.cni.cncf.io/v1" +kind: NetworkAttachmentDefinition +metadata: + name: afxdp-west-network # Name of this network, pods will request this network by name + annotations: + k8s.v1.cni.cncf.io/resourceName: afxdp/westPool # Needs to match the device plugin pool name / resource type +spec: + config: '{ + "cniVersion": "0.3.0", + "type": "afxdp", + "mode": "cdq", + "logFile": "afxdp-cni-west.log", + "logLevel": "debug", + "ipam": { + "type": "host-local", + "subnet": "192.168.2.0/24", + "rangeStart": "192.168.2.200", + "rangeEnd": "192.168.2.220", + "routes": [ + { "dst": "0.0.0.0/0" } + ], + "gateway": "192.168.2.1" + } + }' + diff --git a/tools/ebpfautomation/afxdp-deploy/daemonset.yml b/tools/ebpfautomation/afxdp-deploy/daemonset.yml new file mode 100644 index 00000000..c2979703 --- /dev/null +++ b/tools/ebpfautomation/afxdp-deploy/daemonset.yml @@ -0,0 +1,135 @@ +# Copyright 2022 The Linux Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ConfigMap +metadata: + name: afxdp-dp-config + namespace: kube-system +data: + config.json: | + { + "logLevel":"debug", + "logFile":"afxdp-dp.log", + "pools":[ + { + "name":"eastPool", + "mode":"cdq", + "devices":[ + { + "name":"ens801f0", + "secondary":10 + } + ] + }, + { + "name":"westPool", + "mode":"cdq", + "devices":[ + { + "name":"ens801f1" + "secondary":10 + + }, + ] + } + ] + } +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: afxdp-device-plugin + namespace: kube-system +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: kube-afxdp-device-plugin + namespace: kube-system + labels: + tier: node + app: afxdp +spec: + selector: + matchLabels: + name: afxdp-device-plugin + template: + metadata: + labels: + name: afxdp-device-plugin + tier: node + app: afxdp + spec: + hostNetwork: true + nodeSelector: + kubernetes.io/arch: amd64 + tolerations: + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + serviceAccountName: afxdp-device-plugin + containers: + - name: kube-afxdp + image: afxdp-device-plugin:latest + imagePullPolicy: IfNotPresent + securityContext: + capabilities: + drop: + - all + add: + - SYS_ADMIN + - NET_ADMIN + resources: + requests: + cpu: "250m" + memory: "40Mi" + limits: + cpu: "1" + memory: "200Mi" + volumeMounts: + - name: unixsock + mountPath: /tmp/afxdp_dp/ + - name: devicesock + mountPath: /var/lib/kubelet/device-plugins/ + - name: resources + mountPath: /var/lib/kubelet/pod-resources/ + - name: config-volume + mountPath: /afxdp/config + - name: log + mountPath: /var/log/afxdp-k8s-plugins/ + - name: cnibin + mountPath: /opt/cni/bin/ + volumes: + - name: unixsock + hostPath: + path: /tmp/afxdp_dp/ + - name: devicesock + hostPath: + path: /var/lib/kubelet/device-plugins/ + - name: resources + hostPath: + path: /var/lib/kubelet/pod-resources/ + - name: config-volume + configMap: + name: afxdp-dp-config + items: + - key: config.json + path: config.json + - name: log + hostPath: + path: /var/log/afxdp-k8s-plugins/ + - name: cnibin + hostPath: + path: /opt/cni/bin/ diff --git a/tools/ebpfautomation/afxdp-deploy/deploy.sh b/tools/ebpfautomation/afxdp-deploy/deploy.sh new file mode 100644 index 00000000..4d6adb5f --- /dev/null +++ b/tools/ebpfautomation/afxdp-deploy/deploy.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Copyright 2022 The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script should be run from a node that has access to K8S Cluster + +ROOT_UID=0 +SUDO="" + +# check if root +if [ "$UID" -ne "$ROOT_UID" ] +then + # installation must be run via sudo + SUDO="sudo -E" +fi + +# clone afxdp plugins repository +echo "Cloning afxdp-plugins-for-kubernetes repository..." +[ -d afxdp-plugins-for-kubernetes ] && rm -rf afxdp-plugins-for-kubernetes +git clone https://github.com/intel/afxdp-plugins-for-kubernetes &> /dev/null + + +# Copy daemonset.yml to the appropriate folder +cp daemonset.yml afxdp-plugins-for-kubernetes/deployments + +# Build and deploy +cd afxdp-plugins-for-kubernetes && make deploy + +# Deploy the network attachment definition +kubectl create -f afxdp-nad.yaml diff --git a/tools/ebpfautomation/afxdp-podspec/afxdp-podspec.yaml b/tools/ebpfautomation/afxdp-podspec/afxdp-podspec.yaml new file mode 100644 index 00000000..75520631 --- /dev/null +++ b/tools/ebpfautomation/afxdp-podspec/afxdp-podspec.yaml @@ -0,0 +1,47 @@ +# Copyright 2022 The Linux Foundation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Pod +metadata: + name: afxdp-l2fwd # Pod name + annotations: + k8s.v1.cni.cncf.io/networks: afxdp-east-network, afxdp-west-network # List of networks to attach to this pod +spec: + containers: + - name: afxdp + image: dpdk-app-centos + imagePullPolicy: Never + command: ["sleep", "infinity"] + securityContext: + privileged: true + capabilities: + add: ["CAP_SYS_ADMIN"] + volumeMounts: + - mountPath: /dev/hugepages + name: hugepage + resources: + requests: + cpu: "6000m" + afxdp/eastPool: '1' + afxdp/westPool: '1' + limits: + cpu: "6000m" + hugepages-1Gi: 2Gi + afxdp/eastPool: '1' + afxdp/westPool: '1' + volumes: + - name: hugepage + emptyDir: + medium: HugePages diff --git a/tools/ebpfautomation/cnibinarybuilder/afxdp/Dockerfile b/tools/ebpfautomation/cnibinarybuilder/afxdp/Dockerfile new file mode 100644 index 00000000..7ad30053 --- /dev/null +++ b/tools/ebpfautomation/cnibinarybuilder/afxdp/Dockerfile @@ -0,0 +1,30 @@ +# Copyright 2022 The Linux Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build the container +# docker build -t afxdpbuilder . +# Copy the binary from container to local system (ex: replace ./ with /opt/cni/bin) +# docker run --name afxdp afxdpbuilder && docker cp afxdp:/usr/local/bin/afxdp ./ && docker stop afxdp && docker rm afxdp + +FROM golang:1.19 + +WORKDIR /usr/src/ + +RUN git clone https://github.com/intel/afxdp-plugins-for-kubernetes afxdp +RUN apt-get update +RUN apt-get install -y libbpf-dev + +WORKDIR /usr/src/afxdp + +RUN go build -o /usr/local/bin/afxdp ./cmd/cni -- 2.16.6 From e0b5af5ab0a36bb63c880b9137dd02e26e4c94cc Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Tue, 3 Jan 2023 18:24:40 +0530 Subject: [PATCH 16/16] RELNOTES: This patch adds Nile Release Notes. Signed-off-by: Sridhar K. N. Rao Change-Id: If5ea4a03e2a2db33d253814a53a216c0a50128a7 --- docs/release/release-notes/release-notes.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst index fd77a37a..6d13c7e5 100644 --- a/docs/release/release-notes/release-notes.rst +++ b/docs/release/release-notes/release-notes.rst @@ -3,6 +3,31 @@ .. (c) OPNFV, Intel Corporation, Spirent Communications, AT&T and others. +Anuket Nile Release +=================== + +* Supported Software Versions - DPDK:21.11, OVS:2.17.2, VPP:21.01, QEMU:3.1.1, Trex:2.86, PROX:Kali + +* Source + + * BPF-Switch: Based on https://github.com/dsahern/bpf-progs + +* Kubernetes + + * Support for XDP-based networking + + * Dockerfiles for af_xdp. + * Pod deployment files, network attachment definitions + * Example configurations - daemonset + * Detailed documentation. + +* Miscellaneous + + * Support for Ubuntu 22.04 is included + * Additional packages are included in the installation process - eBPF related packages. + * DPDK build based on meson is the default approach. + * Read the documentation in systems folder before initiating the build. + Anuket Moselle Release ====================== -- 2.16.6