This is a rough PoC for an idea to offload TC flower to XDP.
* Motivation
The purpose is to speed up software TC flower by using XDP.
I chose TC flower because my current interest is in OVS. OVS uses TC to
offload flow tables to hardware, so if TC can offload flows to XDP, OVS
also can be offloaded to XDP.
When TC flower filter is offloaded to XDP, the received packets are
handled by XDP first, and if their protocol or something is not
supported by the eBPF program, the program returns XDP_PASS and packets
are passed to upper layer TC.
The packet processing flow will be like this when this mechanism,
xdp_flow, is used with OVS.
+-------------+
| openvswitch |
| kmod |
+-------------+
^
| if not match in filters (flow key or action not supported by TC)
+-------------+
| TC flower |
+-------------+
^
| if not match in flow tables (flow key or action not supported by XDP)
+-------------+
| XDP prog |
+-------------+
^
| incoming packets
Of course we can directly use TC flower without OVS to speed up TC.
This is useful especially when the device does not support HW-offload.
Such interfaces include virtual interfaces like veth.
* How to use
It only supports ingress (clsact) flower filter at this point.
Enable the feature via ethtool before adding ingress/clsact qdisc.
$ ethtool -K eth0 tc-offload-xdp on
Then add qdisc/filters as normal.
$ tc qdisc add dev eth0 clsact
$ tc filter add dev eth0 ingress protocol ip flower skip_sw ...
Alternatively, when using OVS, adding qdisc and filters will be
automatically done by setting hw-offload.
$ ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
$ systemctl stop openvswitch
$ tc qdisc del dev eth0 ingress # or reboot
$ ethtool -K eth0 tc-offload-xdp on
$ systemctl start openvswitch
* Performance
I measured drop rate at veth interface with redirect action from physical
interface (i40e 25G NIC, XXV 710) to veth. The CPU is Xeon Silver 4114
(2.20 GHz).
XDP_DROP
+------+ +-------+ +-------+
pktgen -- wire --> | eth0 | -- TC/OVS redirect --> | veth0 |----| veth1 |
+------+ (offloaded to XDP) +-------+ +-------+
The setup for redirect is done by OVS like this.
$ ovs-vsctl add-br ovsbr0
$ ovs-vsctl add-port ovsbr0 eth0
$ ovs-vsctl add-port ovsbr0 veth0
$ ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
$ systemctl stop openvswitch
$ tc qdisc del dev eth0 ingress
$ tc qdisc del dev veth0 ingress
$ ethtool -K eth0 tc-offload-xdp on
$ ethtool -K veth0 tc-offload-xdp on
$ systemctl start openvswitch
Tested single core/single flow with 3 configurations.
- xdp_flow: hw-offload=true, tc-offload-xdp on
- TC: hw-offload=true, tc-offload-xdp off (software TC)
- ovs kmod: hw-offload=false
xdp_flow TC ovs kmod
-------- -------- --------
4.0 Mpps 1.1 Mpps 1.1 Mpps
So xdp_flow drop rate is roughly 4x faster than software TC or ovs kmod.
OTOH the time to add a flow increases with xdp_flow.
ping latency of first packet when veth1 does XDP_PASS instead of DROP:
xdp_flow TC ovs kmod
-------- -------- --------
25ms 12ms 0.6ms
xdp_flow does a lot of work to emulate TC behavior including UMH
transaction and multiple bpf map update from UMH which I think increases
the latency.
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
+----------------------+
| xdp_flow_umh | load eBPF prog for XDP
| (eBPF prog embedded) | update maps for flow tables
+----------------------+
^ |
request | v eBPF prog id
+-----------+ offload +-----------------------+
| TC flower | --------> | xdp_flow kmod | attach the prog to XDP
+-----------+ | (flow offload driver) |
+-----------------------+
- When ingress/clsact qdisc is created, i.e. a device is bound to a flow
block, xdp_flow kmod requests xdp_flow_umh to load eBPF prog.
xdp_flow_umh returns prog id and xdp_flow kmod attach the prog to XDP
(the reason of attaching XDP from kmod is that rtnl_lock is held here).
- When flower filter is added, xdp_flow kmod requests xdp_flow_umh to
update maps for flow tables.
* Patches
- patch 1
Basic framework for xdp_flow kmod and UMH.
- patch 2
Add prebuilt eBPF program embedded in UMH.
- patch 3, 4
Attach the prog to XDP in kmod after using the prog id returned from
UMH.
- patch 5, 6
Add maps for flow tables and flow table manipulation logic in UMH.
- patch 7
Implement flow lookup and basic actions in eBPF prog.
- patch 8
Implement flow manipulation logic, serialize flow key and actions from
TC flower and make requests to UMH in kmod.
- patch 9
Add tc-offload-xdp netdev feature and hooks to call xdp_flow kmod in
TC flower offload code.
- patch 10, 11
Add example actions, redirect and vlan_push.
- patch 12
Add testcase for xdp_flow.
- patch 13, 14
These are unrelated patches. They just improves XDP program's
performance. They are included to demonstrate to what extent xdp_flow
performance can increase. Without them, drop rate goes down from 4Mpps
to 3Mpps.
* About OVS AF_XDP netdev
Recently OVS has added AF_XDP netdev type support. This also makes use
of XDP, but in some ways different from this patch set.
- AF_XDP work originally started in order to bring BPF's flexibility to
OVS, which enables us to upgrade datapath without updating kernel.
AF_XDP solution uses userland datapath so it achieved its goal.
xdp_flow will not replace OVS datapath completely, but offload it
partially just for speed up.
- OVS AF_XDP requires PMD for the best performance so consumes 100% CPU.
- OVS AF_XDP needs packet copy when forwarding packets.
- xdp_flow can be used not only for OVS. It works for direct use of TC
flower. nftables also can be offloaded by the same mechanism in the
future.
* About alternative userland (ovs-vswitchd etc.) implementation
Maybe a similar logic can be implemented in ovs-vswitchd offload
mechanism, instead of adding code to kernel. I just thought offloading
TC is more generic and allows wider usage with direct TC command.
For example, considering that OVS inserts a flow to kernel only when
flow miss happens in kernel, we can in advance add offloaded flows via
tc filter to avoid flow insertion latency for certain sensitive flows.
TC flower usage without using OVS is also possible.
Also as written above nftables can be offloaded to XDP with this
mechanism as well.
* Note
This patch set is based on top of commit a664a834579a ("tools: bpftool:
fix reading from /proc/config.gz").
Any feedback is welcome.
Thanks!
Signed-off-by: Toshiaki Makita <redacted>
Toshiaki Makita (14):
xdp_flow: Add skeleton of XDP based TC offload driver
xdp_flow: Add skeleton bpf program for XDP
bpf: Add API to get program from id
xdp_flow: Attach bpf prog to XDP in kernel after UMH loaded program
xdp_flow: Prepare flow tables in bpf
xdp_flow: Add flow entry insertion/deletion logic in UMH
xdp_flow: Add flow handling and basic actions in bpf prog
xdp_flow: Implement flow replacement/deletion logic in xdp_flow kmod
xdp_flow: Add netdev feature for enabling TC flower offload to XDP
xdp_flow: Implement redirect action
xdp_flow: Implement vlan_push action
bpf, selftest: Add test for xdp_flow
i40e: prefetch xdp->data before running XDP prog
bpf, hashtab: Compare keys in long
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 1 +
include/linux/bpf.h | 6 +
include/linux/netdev_features.h | 2 +
include/linux/netdevice.h | 4 +
include/net/flow_offload_xdp.h | 33 +
include/net/pkt_cls.h | 5 +
include/net/sch_generic.h | 1 +
kernel/bpf/hashtab.c | 27 +-
kernel/bpf/syscall.c | 26 +-
net/Kconfig | 1 +
net/Makefile | 1 +
net/core/dev.c | 13 +-
net/core/ethtool.c | 1 +
net/sched/cls_api.c | 67 +-
net/xdp_flow/.gitignore | 1 +
net/xdp_flow/Kconfig | 16 +
net/xdp_flow/Makefile | 112 +++
net/xdp_flow/msgfmt.h | 102 +++
net/xdp_flow/umh_bpf.h | 34 +
net/xdp_flow/xdp_flow_core.c | 126 ++++
net/xdp_flow/xdp_flow_kern_bpf.c | 358 +++++++++
net/xdp_flow/xdp_flow_kern_bpf_blob.S | 7 +
net/xdp_flow/xdp_flow_kern_mod.c | 645 ++++++++++++++++
net/xdp_flow/xdp_flow_umh.c | 1034 ++++++++++++++++++++++++++
net/xdp_flow/xdp_flow_umh_blob.S | 7 +
tools/testing/selftests/bpf/Makefile | 1 +
tools/testing/selftests/bpf/test_xdp_flow.sh | 103 +++
27 files changed, 2716 insertions(+), 18 deletions(-)
create mode 100644 include/net/flow_offload_xdp.h
create mode 100644 net/xdp_flow/.gitignore
create mode 100644 net/xdp_flow/Kconfig
create mode 100644 net/xdp_flow/Makefile
create mode 100644 net/xdp_flow/msgfmt.h
create mode 100644 net/xdp_flow/umh_bpf.h
create mode 100644 net/xdp_flow/xdp_flow_core.c
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf.c
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf_blob.S
create mode 100644 net/xdp_flow/xdp_flow_kern_mod.c
create mode 100644 net/xdp_flow/xdp_flow_umh.c
create mode 100644 net/xdp_flow/xdp_flow_umh_blob.S
create mode 100755 tools/testing/selftests/bpf/test_xdp_flow.sh
--
1.8.3.1
Add TC offload driver, xdp_flow_core.c, and skeleton of UMH handling
mechanism. The driver is not called from anywhere yet.
xdp_flow_setup_block() in xdp_flow_core.c is meant to be called when
ingress qdisc is added. It loads xdp_flow kernel module and the kmod
provides some callbacks for setup phase and flow insertion phase.
xdp_flow_setup() in the kmod will be called from xdp_flow_setup_block()
when ingress qdisc is added, and xdp_flow_setup_block_cb() will be
called when a tc flower filter is added.
The former will request the UMH to load the eBPF program and the latter
will request the UMH to populate maps for flow tables. In this patch
no actual processing is implemented and the following commits implement
them.
The overall mechanism of UMH handling is written referring to bpfilter.
Signed-off-by: Toshiaki Makita <redacted>
---
include/net/flow_offload_xdp.h | 33 ++++++
net/Kconfig | 1 +
net/Makefile | 1 +
net/xdp_flow/.gitignore | 1 +
net/xdp_flow/Kconfig | 16 +++
net/xdp_flow/Makefile | 31 +++++
net/xdp_flow/msgfmt.h | 102 ++++++++++++++++
net/xdp_flow/xdp_flow_core.c | 126 ++++++++++++++++++++
net/xdp_flow/xdp_flow_kern_mod.c | 250 +++++++++++++++++++++++++++++++++++++++
net/xdp_flow/xdp_flow_umh.c | 109 +++++++++++++++++
net/xdp_flow/xdp_flow_umh_blob.S | 7 ++
11 files changed, 677 insertions(+)
create mode 100644 include/net/flow_offload_xdp.h
create mode 100644 net/xdp_flow/.gitignore
create mode 100644 net/xdp_flow/Kconfig
create mode 100644 net/xdp_flow/Makefile
create mode 100644 net/xdp_flow/msgfmt.h
create mode 100644 net/xdp_flow/xdp_flow_core.c
create mode 100644 net/xdp_flow/xdp_flow_kern_mod.c
create mode 100644 net/xdp_flow/xdp_flow_umh.c
create mode 100644 net/xdp_flow/xdp_flow_umh_blob.S
@@ -0,0 +1,31 @@+# SPDX-License-Identifier: GPL-2.0++obj-$(CONFIG_XDP_FLOW)+=xdp_flow_core.o++ifeq ($(CONFIG_XDP_FLOW_UMH), y)+# builtin xdp_flow_umh should be compiled with -static+# since rootfs isn't mounted at the time of __init+# function is called and do_execv won't find elf interpreter+STATIC:=-static+endif++quiet_cmd_cc_user=CC$@+cmd_cc_user=$(CC)-Wall-Wmissing-prototypes-O2-std=gnu89\+-I$(srctree)/tools/include/\+-c-o$@$<++quiet_cmd_ld_user=LD$@+cmd_ld_user=$(CC)$(STATIC)-o$@$^++$(obj)/xdp_flow_umh.o:$(src)/xdp_flow_umh.cFORCE+$(callif_changed,cc_user)++$(obj)/xdp_flow_umh:$(obj)/xdp_flow_umh.o+$(callif_changed,ld_user)++clean-files:=xdp_flow_umh++$(obj)/xdp_flow_umh_blob.o:$(obj)/xdp_flow_umh++obj-$(CONFIG_XDP_FLOW_UMH)+=xdp_flow.o+xdp_flow-objs+=xdp_flow_kern_mod.oxdp_flow_umh_blob.o
@@ -0,0 +1,250 @@+// SPDX-License-Identifier: GPL-2.0+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt+#include<linux/module.h>+#include<linux/umh.h>+#include<linux/sched/signal.h>+#include<net/pkt_cls.h>+#include<net/flow_offload_xdp.h>+#include"msgfmt.h"++externcharxdp_flow_umh_start;+externcharxdp_flow_umh_end;++staticvoidshutdown_umh(void)+{+structtask_struct*tsk;++if(xdp_flow_ops.stop)+return;++tsk=get_pid_task(find_vpid(xdp_flow_ops.info.pid),PIDTYPE_PID);+if(tsk){+send_sig(SIGKILL,tsk,1);+put_task_struct(tsk);+}+}++staticinttransact_umh(structmbox_request*req,u32*id)+{+structmbox_replyreply;+intret=-EFAULT;+loff_tpos;+ssize_tn;++if(!xdp_flow_ops.info.pid)+gotoout;++n=__kernel_write(xdp_flow_ops.info.pipe_to_umh,req,sizeof(*req),+&pos);+if(n!=sizeof(*req)){+pr_err("write fail %zd\n",n);+shutdown_umh();+gotoout;+}++pos=0;+n=kernel_read(xdp_flow_ops.info.pipe_from_umh,&reply,+sizeof(reply),&pos);+if(n!=sizeof(reply)){+pr_err("read fail %zd\n",n);+shutdown_umh();+gotoout;+}++ret=reply.status;+if(id)+*id=reply.id;+out:+returnret;+}++staticintxdp_flow_replace(structnet_device*dev,structflow_cls_offload*f)+{+return-EOPNOTSUPP;+}++intxdp_flow_destroy(structnet_device*dev,structflow_cls_offload*f)+{+return-EOPNOTSUPP;+}++staticintxdp_flow_setup_flower(structnet_device*dev,+structflow_cls_offload*f)+{+switch(f->command){+caseFLOW_CLS_REPLACE:+returnxdp_flow_replace(dev,f);+caseFLOW_CLS_DESTROY:+returnxdp_flow_destroy(dev,f);+caseFLOW_CLS_STATS:+caseFLOW_CLS_TMPLT_CREATE:+caseFLOW_CLS_TMPLT_DESTROY:+default:+return-EOPNOTSUPP;+}+}++staticintxdp_flow_setup_block_cb(enumtc_setup_typetype,void*type_data,+void*cb_priv)+{+structflow_cls_common_offload*common=type_data;+structnet_device*dev=cb_priv;+interr=0;++if(common->chain_index){+NL_SET_ERR_MSG(common->extack,+"xdp_flow supports only offload of chain 0");+return-EOPNOTSUPP;+}++if(type!=TC_SETUP_CLSFLOWER)+return-EOPNOTSUPP;++mutex_lock(&xdp_flow_ops.lock);+if(xdp_flow_ops.stop){+err=xdp_flow_ops.start();+if(err)+gotoout;+}++err=xdp_flow_setup_flower(dev,type_data);+out:+mutex_unlock(&xdp_flow_ops.lock);+returnerr;+}++staticintxdp_flow_setup_bind(structnet_device*dev,+structnetlink_ext_ack*extack)+{+structmbox_request*req;+u32id=0;+interr;++req=kzalloc(sizeof(*req),GFP_KERNEL);+if(!req)+return-ENOMEM;++req->cmd=XDP_FLOW_CMD_LOAD;+req->ifindex=dev->ifindex;++/* Load bpf in UMH and get prog id */+err=transact_umh(req,&id);++/* TODO: id will be used to attach bpf prog to XDP+*Aswehavertnl_lock,UMHcannotattachprogtoXDP+*/++kfree(req);++returnerr;+}++staticintxdp_flow_setup_unbind(structnet_device*dev,+structnetlink_ext_ack*extack)+{+structmbox_request*req;+interr;++req=kzalloc(sizeof(*req),GFP_KERNEL);+if(!req)+return-ENOMEM;++req->cmd=XDP_FLOW_CMD_UNLOAD;+req->ifindex=dev->ifindex;++err=transact_umh(req,NULL);++kfree(req);++returnerr;+}++staticintxdp_flow_setup(structnet_device*dev,booldo_bind,+structnetlink_ext_ack*extack)+{+ASSERT_RTNL();++if(!net_eq(dev_net(dev),&init_net))+return-EINVAL;++returndo_bind?+xdp_flow_setup_bind(dev,extack):+xdp_flow_setup_unbind(dev,extack);+}++staticintxdp_flow_test(void)+{+structmbox_request*req;+interr;++req=kzalloc(sizeof(*req),GFP_KERNEL);+if(!req)+return-ENOMEM;++req->cmd=XDP_FLOW_CMD_NOOP;+err=transact_umh(req,NULL);++kfree(req);++returnerr;+}++staticintstart_umh(void)+{+interr;++/* fork usermode process */+err=fork_usermode_blob(&xdp_flow_umh_start,+&xdp_flow_umh_end-&xdp_flow_umh_start,+&xdp_flow_ops.info);+if(err)+returnerr;++xdp_flow_ops.stop=false;+pr_info("Loaded xdp_flow_umh pid %d\n",xdp_flow_ops.info.pid);++/* health check that usermode process started correctly */+if(xdp_flow_test()){+shutdown_umh();+return-EFAULT;+}++return0;+}++staticint__initload_umh(void)+{+interr=0;++mutex_lock(&xdp_flow_ops.lock);+if(!xdp_flow_ops.stop){+err=-EFAULT;+gotoerr;+}++err=start_umh();+if(err)+gotoerr;++xdp_flow_ops.setup_cb=&xdp_flow_setup_block_cb;+xdp_flow_ops.setup=&xdp_flow_setup;+xdp_flow_ops.start=&start_umh;+xdp_flow_ops.module=THIS_MODULE;+err:+mutex_unlock(&xdp_flow_ops.lock);+returnerr;+}++staticvoid__exitfini_umh(void)+{+mutex_lock(&xdp_flow_ops.lock);+shutdown_umh();+xdp_flow_ops.module=NULL;+xdp_flow_ops.start=NULL;+xdp_flow_ops.setup=NULL;+xdp_flow_ops.setup_cb=NULL;+mutex_unlock(&xdp_flow_ops.lock);+}+module_init(load_umh);+module_exit(fini_umh);+MODULE_LICENSE("GPL");
The program is meant to be loaded when a device is bound to an ingress
TC block and should be attached to XDP on the device.
Typically it should be loaded when TC ingress or clsact qdisc is added.
The program is prebuilt and embedded in the UMH, instead of generated
dynamically. This is because TC filter is frequently changed when it is
used by OVS, and the latency of TC filter change will affect the latency
of datapath.
Signed-off-by: Toshiaki Makita <redacted>
---
net/xdp_flow/Makefile | 87 +++++++++++-
net/xdp_flow/xdp_flow_kern_bpf.c | 12 ++
net/xdp_flow/xdp_flow_kern_bpf_blob.S | 7 +
net/xdp_flow/xdp_flow_umh.c | 241 +++++++++++++++++++++++++++++++++-
4 files changed, 343 insertions(+), 4 deletions(-)
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf.c
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf_blob.S
@@ -2,25 +2,106 @@obj-$(CONFIG_XDP_FLOW)+=xdp_flow_core.o+XDP_FLOW_PATH?=$(abspath$(srctree)/$(src))+TOOLS_PATH:=$(XDP_FLOW_PATH)/../../tools++# Libbpf dependencies+LIBBPF=$(TOOLS_PATH)/lib/bpf/libbpf.a++LLC?=llc+CLANG?=clang+LLVM_OBJCOPY?=llvm-objcopy+BTF_PAHOLE?=pahole++ifdef CROSS_COMPILE+CLANG_ARCH_ARGS=-target$(ARCH)+endif++BTF_LLC_PROBE:=$(shell$(LLC)-march=bpf-mattr=help2>&1|grepdwarfris)+BTF_PAHOLE_PROBE:=$(shell$(BTF_PAHOLE)--help2>&1|grepBTF)+BTF_OBJCOPY_PROBE:=$(shell$(LLVM_OBJCOPY)--help2>&1|grep-i'usage.*llvm')+BTF_LLVM_PROBE:=$(shellecho"int main() { return 0; }"|\+$(CLANG)-targetbpf-O2-g-c-xc--o./llvm_btf_verify.o;\+readelf-S./llvm_btf_verify.o|grepBTF;\+/bin/rm-f./llvm_btf_verify.o)++ifneq ($(BTF_LLVM_PROBE),)+EXTRA_CFLAGS+=-g+else+ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),)+EXTRA_CFLAGS+=-g+LLC_FLAGS+=-mattr=dwarfris+DWARF2BTF=y+endif+endif++$(LIBBPF):FORCE+# Fix up variables inherited from Kbuild that tools/ build system won't like+$(MAKE)-C$(dir$@)RM='rm -rf'LDFLAGS=srctree=$(XDP_FLOW_PATH)/../../O=++# Verify LLVM compiler tools are available and bpf target is supported by llc+.PHONY:verify_cmdsverify_target_bpf$(CLANG)$(LLC)++verify_cmds:$(CLANG)$(LLC)+@forTOOLin$^;do\+ if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \+echo"*** ERROR: Cannot find LLVM tool $${TOOL}";\+exit1;\+ else true; fi; \+done++verify_target_bpf:verify_cmds+@if!(${LLC}-march=bpf-mattr=help>/dev/null2>&1);then\+echo"*** ERROR: LLVM (${LLC}) does not support 'bpf' target";\+echo" NOTICE: LLVM version >= 3.7.1 required";\+exit2;\+ else true; fi++$(src)/xdp_flow_kern_bpf.c:verify_target_bpf++$(obj)/xdp_flow_kern_bpf.o:$(src)/xdp_flow_kern_bpf.cFORCE+@echo" CLANG-bpf "$@+$(Q)$(CLANG)$(NOSTDINC_FLAGS)$(LINUXINCLUDE)$(EXTRA_CFLAGS)-I$(obj)\+-I$(srctree)/tools/testing/selftests/bpf/\+-D__KERNEL__-D__BPF_TRACING__-Wno-unused-value-Wno-pointer-sign\+-D__TARGET_ARCH_$(SRCARCH)-Wno-compare-distinct-pointer-types\+-Wno-gnu-variable-sized-type-not-at-end\+-Wno-address-of-packed-member-Wno-tautological-compare\+-Wno-unknown-warning-option$(CLANG_ARCH_ARGS)\+-I$(srctree)/samples/bpf/-includeasm_goto_workaround.h\+-O2-emit-llvm-c$<-o-|$(LLC)-march=bpf$(LLC_FLAGS)-filetype=obj-o$@+ifeq ($(DWARF2BTF),y)+$(BTF_PAHOLE)-J$@+endif+ifeq ($(CONFIG_XDP_FLOW_UMH), y)# builtin xdp_flow_umh should be compiled with -static# since rootfs isn't mounted at the time of __init# function is called and do_execv won't find elf interpreterSTATIC:=-static+STATICLDLIBS:=-lzendif+quiet_cmd_as_user=AS$@+cmd_as_user=$(AS)-c-o$@$<+quiet_cmd_cc_user=CC$@cmd_cc_user=$(CC)-Wall-Wmissing-prototypes-O2-std=gnu89\--I$(srctree)/tools/include/\+-I$(srctree)/tools/lib/-I$(srctree)/tools/include/\-c-o$@$<quiet_cmd_ld_user=LD$@-cmd_ld_user=$(CC)$(STATIC)-o$@$^+cmd_ld_user=$(CC)$(STATIC)-o$@$^$(LIBBPF)-lelf$(STATICLDLIBS)++$(obj)/xdp_flow_kern_bpf_blob.o:$(src)/xdp_flow_kern_bpf_blob.S \+$(obj)/xdp_flow_kern_bpf.o+$(callif_changed,as_user)$(obj)/xdp_flow_umh.o:$(src)/xdp_flow_umh.cFORCE$(callif_changed,cc_user)-$(obj)/xdp_flow_umh:$(obj)/xdp_flow_umh.o+$(obj)/xdp_flow_umh:$(obj)/xdp_flow_umh.o$(LIBBPF) \+$(obj)/xdp_flow_kern_bpf_blob.o$(callif_changed,ld_user)clean-files:=xdp_flow_umh
@@ -6,9 +6,19 @@#include<fcntl.h>#include<unistd.h>#include<syslog.h>+#include<bpf/libbpf.h>+#include<bpf/bpf.h>+#include<sys/mman.h>#include<sys/types.h>+#include<sys/resource.h>+#include<linux/hashtable.h>+#include<linux/err.h>#include"msgfmt.h"+externcharxdp_flow_bpf_start;+externcharxdp_flow_bpf_end;+intprogfile_fd;+/* FIXME: syslog is used for easy debugging. As writing /dev/log can be stuck*duetoreaderside,shoulduseanotherlogmechanismlikekmsg.*/
@@ -17,15 +27,241 @@#define pr_warn(fmt, ...) syslog(LOG_DAEMON | LOG_WARNING, fmt, ##__VA_ARGS__)#define pr_err(fmt, ...) syslog(LOG_DAEMON | LOG_ERR, fmt, ##__VA_ARGS__)+#define ERRBUF_SIZE 64++/* This key represents a net device */+structnetdev_info_key{+intifindex;+};++structnetdev_info{+structnetdev_info_keykey;+structhlist_nodenode;+structbpf_object*obj;+};++DEFINE_HASHTABLE(netdev_info_table,16);++staticintlibbpf_err(interr,char*errbuf)+{+libbpf_strerror(err,errbuf,ERRBUF_SIZE);++if(-err<__LIBBPF_ERRNO__START)+returnerr;++return-EINVAL;+}++staticintsetup(void)+{+size_tsize=&xdp_flow_bpf_end-&xdp_flow_bpf_start;+structrlimitr={RLIM_INFINITY,RLIM_INFINITY};+ssize_tlen;+interr;++if(setrlimit(RLIMIT_MEMLOCK,&r)){+err=-errno;+pr_err("setrlimit MEMLOCK failed: %s\n",strerror(errno));+returnerr;+}++progfile_fd=memfd_create("xdp_flow_kern_bpf.o",0);+if(progfile_fd<0){+err=-errno;+pr_err("memfd_create failed: %s\n",strerror(errno));+returnerr;+}++len=write(progfile_fd,&xdp_flow_bpf_start,size);+if(len<0){+err=-errno;+pr_err("Failed to write bpf prog: %s\n",strerror(errno));+gotoerr;+}++if(len<size){+pr_err("bpf prog written too short: expected %ld, actual %ld\n",+size,len);+err=-EIO;+gotoerr;+}++return0;+err:+close(progfile_fd);++returnerr;+}++staticintload_bpf(intifindex,structbpf_object**objp)+{+structbpf_object_open_attrattr={};+charpath[256],errbuf[ERRBUF_SIZE];+structbpf_program*prog;+structbpf_object*obj;+intprog_fd,err;+ssize_tlen;++len=snprintf(path,256,"/proc/self/fd/%d",progfile_fd);+if(len<0){+err=-errno;+pr_err("Failed to setup prog fd path string: %s\n",+strerror(errno));+returnerr;+}++attr.file=path;+attr.prog_type=BPF_PROG_TYPE_XDP;+obj=bpf_object__open_xattr(&attr);+if(IS_ERR_OR_NULL(obj)){+if(IS_ERR(obj)){+err=libbpf_err((int)PTR_ERR(obj),errbuf);+}else{+err=-ENOENT;+strerror_r(-err,errbuf,sizeof(errbuf));+}+pr_err("Cannot open bpf prog: %s\n",errbuf);+returnerr;+}++bpf_object__for_each_program(prog,obj)+bpf_program__set_type(prog,attr.prog_type);++err=bpf_object__load(obj);+if(err){+err=libbpf_err(err,errbuf);+pr_err("Failed to load bpf prog: %s\n",errbuf);+gotoerr;+}++prog=bpf_object__find_program_by_title(obj,"xdp_flow");+if(!prog){+pr_err("Cannot find xdp_flow program\n");+err=-ENOENT;+gotoerr;+}++prog_fd=bpf_program__fd(prog);+if(prog_fd<0){+err=libbpf_err(prog_fd,errbuf);+pr_err("Invalid program fd: %s\n",errbuf);+gotoerr;+}++*objp=obj;++returnprog_fd;+err:+bpf_object__close(obj);+returnerr;+}++staticintget_netdev_info_keyval(conststructnetdev_info_key*key)+{+returnkey->ifindex;+}++staticstructnetdev_info*find_netdev_info(conststructnetdev_info_key*key)+{+intkeyval=get_netdev_info_keyval(key);+structnetdev_info*netdev_info;++hash_for_each_possible(netdev_info_table,netdev_info,node,keyval){+if(netdev_info->key.ifindex==key->ifindex)+returnnetdev_info;+}++returnNULL;+}++staticintget_netdev_info_key(conststructmbox_request*req,+structnetdev_info_key*key)+{+key->ifindex=req->ifindex;++return0;+}++staticstructnetdev_info*get_netdev_info(conststructmbox_request*req)+{+structnetdev_info*netdev_info;+structnetdev_info_keykey;+interr;++err=get_netdev_info_key(req,&key);+if(err)+returnERR_PTR(err);++netdev_info=find_netdev_info(&key);+if(!netdev_info){+pr_err("BUG: netdev_info for if %d not found.\n",+key.ifindex);+returnERR_PTR(-ENOENT);+}++returnnetdev_info;+}+staticinthandle_load(conststructmbox_request*req,__u32*prog_id){-*prog_id=0;+structnetdev_info*netdev_info;+structbpf_prog_infoinfo={};+structnetdev_info_keykey;+__u32len=sizeof(info);+interr,prog_fd;++err=get_netdev_info_key(req,&key);+if(err)+returnerr;++netdev_info=find_netdev_info(&key);+if(netdev_info)+return0;++netdev_info=malloc(sizeof(*netdev_info));+if(!netdev_info){+pr_err("malloc for netdev_info failed.\n");+return-ENOMEM;+}+netdev_info->key.ifindex=key.ifindex;++prog_fd=load_bpf(req->ifindex,&netdev_info->obj);+if(prog_fd<0){+err=prog_fd;+gotoerr_netdev_info;+}++err=bpf_obj_get_info_by_fd(prog_fd,&info,&len);+if(err)+gotoerr_obj;++*prog_id=info.id;+hash_add(netdev_info_table,&netdev_info->node,+get_netdev_info_keyval(&netdev_info->key));+pr_debug("XDP program for if %d was loaded\n",req->ifindex);return0;+err_obj:+bpf_object__close(netdev_info->obj);+err_netdev_info:+free(netdev_info);++returnerr;}staticinthandle_unload(conststructmbox_request*req){+structnetdev_info*netdev_info;++netdev_info=get_netdev_info(req);+if(IS_ERR(netdev_info))+returnPTR_ERR(netdev_info);++hash_del(&netdev_info->node);+bpf_object__close(netdev_info->obj);+free(netdev_info);+pr_debug("XDP program for if %d was closed\n",req->ifindex);+return0;}
Factor out the logic in bpf_prog_get_fd_by_id() and add
bpf_prog_get_by_id(). Also export bpf_prog_get_ok().
They are used by the next commit to get bpf prog from its id.
Signed-off-by: Toshiaki Makita <redacted>
---
include/linux/bpf.h | 6 ++++++
kernel/bpf/syscall.c | 26 ++++++++++++++++++--------
2 files changed, 24 insertions(+), 8 deletions(-)
@@ -2122,6 +2123,22 @@ static int bpf_obj_get_next_id(const union bpf_attr *attr,returnerr;}+structbpf_prog*bpf_prog_get_by_id(u32id)+{+structbpf_prog*prog;++spin_lock_bh(&prog_idr_lock);+prog=idr_find(&prog_idr,id);+if(prog)+prog=bpf_prog_inc_not_zero(prog);+else+prog=ERR_PTR(-ENOENT);+spin_unlock_bh(&prog_idr_lock);++returnprog;+}+EXPORT_SYMBOL_GPL(bpf_prog_get_by_id);+#define BPF_PROG_GET_FD_BY_ID_LAST_FIELD prog_idstaticintbpf_prog_get_fd_by_id(constunionbpf_attr*attr)
@@ -2136,14 +2153,7 @@ static int bpf_prog_get_fd_by_id(const union bpf_attr *attr)if(!capable(CAP_SYS_ADMIN))return-EPERM;-spin_lock_bh(&prog_idr_lock);-prog=idr_find(&prog_idr,id);-if(prog)-prog=bpf_prog_inc_not_zero(prog);-else-prog=ERR_PTR(-ENOENT);-spin_unlock_bh(&prog_idr_lock);-+prog=bpf_prog_get_by_id(id);if(IS_ERR(prog))returnPTR_ERR(prog);
As UMH runs under RTNL, it cannot attach XDP from userspace. Thus the
kernel, xdp_flow module, installs the XDP program.
NOTE: As an RFC, XDP-related logic is emulating dev_change_xdp_fd().
I'm thinking I should factor out the logic from dev_change_xdp_fd() and
export it instead.
Signed-off-by: Toshiaki Makita <redacted>
---
include/linux/netdevice.h | 4 +++
net/core/dev.c | 11 ++++---
net/xdp_flow/xdp_flow_kern_mod.c | 63 ++++++++++++++++++++++++++++++++++++----
3 files changed, 69 insertions(+), 9 deletions(-)
@@ -116,10 +116,26 @@ static int xdp_flow_setup_block_cb(enum tc_setup_type type, void *type_data,staticintxdp_flow_setup_bind(structnet_device*dev,structnetlink_ext_ack*extack){+enumbpf_prog_typeattach_type=BPF_PROG_TYPE_XDP;structmbox_request*req;+bpf_op_tbpf_op,bpf_chk;+structbpf_prog*prog;u32id=0;interr;+bpf_op=bpf_chk=dev->netdev_ops->ndo_bpf;+if(!bpf_op)+bpf_op=generic_xdp_install;+else+bpf_chk=generic_xdp_install;++/* TODO: These checks should be unified with net core */+if(__dev_xdp_query(dev,bpf_chk,XDP_QUERY_PROG))+return-EEXIST;++if(__dev_xdp_query(dev,bpf_op,XDP_QUERY_PROG))+return-EBUSY;+req=kzalloc(sizeof(*req),GFP_KERNEL);if(!req)return-ENOMEM;
@@ -129,21 +145,56 @@ static int xdp_flow_setup_bind(struct net_device *dev,/* Load bpf in UMH and get prog id */err=transact_umh(req,&id);+if(err)+gotoout;++prog=bpf_prog_get_by_id(id);+if(IS_ERR(prog)){+err=PTR_ERR(prog);+gotoerr_umh;+}++if(!bpf_prog_get_ok(prog,&attach_type,false)){+err=-EINVAL;+gotoerr_prog;+}-/* TODO: id will be used to attach bpf prog to XDP-*Aswehavertnl_lock,UMHcannotattachprogtoXDP-*/+/* As we have rtnl_lock, install XDP in kernel */+err=dev_xdp_install(dev,bpf_op,extack,0,prog);+if(err)+gotoerr_prog;+/* TODO: Should get prog once more and save it for later check */+out:kfree(req);returnerr;+err_prog:+bpf_prog_put(prog);+err_umh:+req->cmd=XDP_FLOW_CMD_UNLOAD;+transact_umh(req,NULL);++gotoout;}staticintxdp_flow_setup_unbind(structnet_device*dev,structnetlink_ext_ack*extack){structmbox_request*req;-interr;+interr,ret=0;+bpf_op_tbpf_op;++bpf_op=dev->netdev_ops->ndo_bpf;+if(!bpf_op)+bpf_op=generic_xdp_install;++/* TODO: Should check if prog is not changed */+err=dev_xdp_install(dev,bpf_op,extack,0,NULL);+if(err){+pr_warn("Failed to uninstall XDP prog: %d\n",err);+ret=err;+}req=kzalloc(sizeof(*req),GFP_KERNEL);if(!req)
@@ -153,10 +204,12 @@ static int xdp_flow_setup_unbind(struct net_device *dev,req->ifindex=dev->ifindex;err=transact_umh(req,NULL);+if(err)+ret=err;kfree(req);-returnerr;+returnret;}staticintxdp_flow_setup(structnet_device*dev,booldo_bind,
Add maps for flow tables in bpf. TC flower has hash tables for each flow
mask ordered by priority. To do the same thing, prepare
hashmap-in-arraymap. As bpf does not provide ordered list, we emulate it
by an array. Each array entry has one-byte next index field to implement
a list. Also prepare a one-element array to point to the head index of
the list.
Because of the limitation of bpf maps, the outer array is implemented
using two array maps. "flow_masks" is the array to emulate the list and
its entries have the priority and mask of each flow table. For each
priority/mask, the same index entry of another map "flow_tables", which
is the hashmap-in-arraymap, points to the actual flow table.
The flow insertion logic in UMH and lookup logic in BPF will be
implemented in the following commits.
NOTE: This list emulation by array may be able to be realized by adding
ordered-list type map. In that case we also need map iteration API for
bpf progs.
Signed-off-by: Toshiaki Makita <redacted>
---
net/xdp_flow/umh_bpf.h | 18 +++++++++++
net/xdp_flow/xdp_flow_kern_bpf.c | 22 +++++++++++++
net/xdp_flow/xdp_flow_umh.c | 70 ++++++++++++++++++++++++++++++++++++++--
3 files changed, 108 insertions(+), 2 deletions(-)
create mode 100644 net/xdp_flow/umh_bpf.h
This logic will be used when xdp_flow kmod requests flow
insertion/deleteion.
On insertion, find a free entry and populate it, then update next index
pointer of its previous entry. On deletion, set the next index pointer
of the prev entry to the next index of the entry to be deleted.
Signed-off-by: Toshiaki Makita <redacted>
---
net/xdp_flow/umh_bpf.h | 15 ++
net/xdp_flow/xdp_flow_umh.c | 470 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 483 insertions(+), 2 deletions(-)
@@ -19,6 +19,8 @@externcharxdp_flow_bpf_end;intprogfile_fd;+#define zalloc(size) calloc(1, (size))+/* FIXME: syslog is used for easy debugging. As writing /dev/log can be stuck*duetoreaderside,shoulduseanotherlogmechanismlikekmsg.*/
BPF prog for XDP parses the packet and extracts the flow key. Then find
an entry from flow tables.
Only "accept" and "drop" actions are implemented at this point.
Signed-off-by: Toshiaki Makita <redacted>
---
net/xdp_flow/xdp_flow_kern_bpf.c | 297 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 296 insertions(+), 1 deletion(-)
@@ -1,9 +1,27 @@// SPDX-License-Identifier: GPL-2.0#define KBUILD_MODNAME "foo"#include<uapi/linux/bpf.h>+#include<linux/in.h>+#include<linux/if_ether.h>+#include<linux/if_packet.h>+#include<linux/if_vlan.h>+#include<linux/ip.h>+#include<linux/ipv6.h>+#include<net/ipv6.h>+#include<net/dsfield.h>#include<bpf_helpers.h>#include"umh_bpf.h"+/* Used when the action only modifies the packet */+#define _XDP_CONTINUE -1++structbpf_map_defSEC("maps")debug_stats={+.type=BPF_MAP_TYPE_PERCPU_ARRAY,+.key_size=sizeof(u32),+.value_size=sizeof(long),+.max_entries=256,+};+structbpf_map_defSEC("maps")flow_masks_head={.type=BPF_MAP_TYPE_ARRAY,.key_size=sizeof(u32),
As struct flow_rule has descrete storages for flow_dissector and
key/mask containers, we need to serialize them in some way to pass them
to UMH.
Convert flow_rule into flow key form used in xdp_flow bpf prog and
pass it.
Signed-off-by: Toshiaki Makita <redacted>
---
net/xdp_flow/xdp_flow_kern_mod.c | 334 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 331 insertions(+), 3 deletions(-)
@@ -3,13 +3,266 @@#include<linux/module.h>#include<linux/umh.h>#include<linux/sched/signal.h>+#include<linux/rhashtable.h>#include<net/pkt_cls.h>#include<net/flow_offload_xdp.h>#include"msgfmt.h"+structxdp_flow_rule{+structrhash_headht_node;+unsignedlongcookie;+structxdp_flow_keykey;+structxdp_flow_keymask;+};++staticconststructrhashtable_paramsrules_params={+.key_len=sizeof(unsignedlong),+.key_offset=offsetof(structxdp_flow_rule,cookie),+.head_offset=offsetof(structxdp_flow_rule,ht_node),+.automatic_shrinking=true,+};++staticstructrhashtablerules;+externcharxdp_flow_umh_start;externcharxdp_flow_umh_end;+staticintxdp_flow_parse_actions(structxdp_flow_actions*actions,+structflow_action*flow_action,+structnetlink_ext_ack*extack)+{+conststructflow_action_entry*act;+inti;++if(!flow_action_has_entries(flow_action))+return0;++if(flow_action->num_entries>MAX_XDP_FLOW_ACTIONS)+return-ENOBUFS;++flow_action_for_each(i,act,flow_action){+structxdp_flow_action*action=&actions->actions[i];++switch(act->id){+caseFLOW_ACTION_ACCEPT:+action->id=XDP_FLOW_ACTION_ACCEPT;+break;+caseFLOW_ACTION_DROP:+action->id=XDP_FLOW_ACTION_DROP;+break;+caseFLOW_ACTION_REDIRECT:+caseFLOW_ACTION_VLAN_PUSH:+caseFLOW_ACTION_VLAN_POP:+caseFLOW_ACTION_VLAN_MANGLE:+caseFLOW_ACTION_MANGLE:+caseFLOW_ACTION_CSUM:+/* TODO: implement these */+/* fall through */+default:+NL_SET_ERR_MSG_MOD(extack,"Unsupported action");+return-EOPNOTSUPP;+}+}+actions->num_actions=flow_action->num_entries;++return0;+}++staticintxdp_flow_parse_ports(structxdp_flow_key*key,+structxdp_flow_key*mask,+structflow_cls_offload*f,u8ip_proto)+{+conststructflow_rule*rule=flow_cls_offload_flow_rule(f);+structflow_match_portsmatch;++if(!flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_PORTS))+return0;++if(ip_proto!=IPPROTO_TCP&&ip_proto!=IPPROTO_UDP){+NL_SET_ERR_MSG_MOD(f->common.extack,+"Only UDP and TCP keys are supported");+return-EINVAL;+}++flow_rule_match_ports(rule,&match);++key->l4port.src=match.key->src;+mask->l4port.src=match.mask->src;+key->l4port.dst=match.key->dst;+mask->l4port.dst=match.mask->dst;++return0;+}++staticintxdp_flow_parse_tcp(structxdp_flow_key*key,+structxdp_flow_key*mask,+structflow_cls_offload*f,u8ip_proto)+{+conststructflow_rule*rule=flow_cls_offload_flow_rule(f);+structflow_match_tcpmatch;++if(!flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_TCP))+return0;++if(ip_proto!=IPPROTO_TCP){+NL_SET_ERR_MSG_MOD(f->common.extack,+"TCP keys supported only for TCP");+return-EINVAL;+}++flow_rule_match_tcp(rule,&match);++key->tcp.flags=match.key->flags;+mask->tcp.flags=match.mask->flags;++return0;+}++staticintxdp_flow_parse_ip(structxdp_flow_key*key,+structxdp_flow_key*mask,+structflow_cls_offload*f,__be16n_proto)+{+conststructflow_rule*rule=flow_cls_offload_flow_rule(f);+structflow_match_ipmatch;++if(!flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_IP))+return0;++if(n_proto!=htons(ETH_P_IP)&&n_proto!=htons(ETH_P_IPV6)){+NL_SET_ERR_MSG_MOD(f->common.extack,+"IP keys supported only for IPv4/6");+return-EINVAL;+}++flow_rule_match_ip(rule,&match);++key->ip.ttl=match.key->ttl;+mask->ip.ttl=match.mask->ttl;+key->ip.tos=match.key->tos;+mask->ip.tos=match.mask->tos;++return0;+}++staticintxdp_flow_parse(structxdp_flow_key*key,structxdp_flow_key*mask,+structxdp_flow_actions*actions,+structflow_cls_offload*f)+{+structflow_rule*rule=flow_cls_offload_flow_rule(f);+structflow_dissector*dissector=rule->match.dissector;+__be16n_proto=0,n_proto_mask=0;+u16addr_type=0;+u8ip_proto=0;+interr;++if(dissector->used_keys&+~(BIT(FLOW_DISSECTOR_KEY_CONTROL)|+BIT(FLOW_DISSECTOR_KEY_BASIC)|+BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS)|+BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS)|+BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS)|+BIT(FLOW_DISSECTOR_KEY_PORTS)|+BIT(FLOW_DISSECTOR_KEY_TCP)|+BIT(FLOW_DISSECTOR_KEY_IP)|+BIT(FLOW_DISSECTOR_KEY_VLAN))){+NL_SET_ERR_MSG_MOD(f->common.extack,"Unsupported key");+return-EOPNOTSUPP;+}++if(flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_CONTROL)){+structflow_match_controlmatch;++flow_rule_match_control(rule,&match);+addr_type=match.key->addr_type;+}++if(flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_BASIC)){+structflow_match_basicmatch;++flow_rule_match_basic(rule,&match);++n_proto=match.key->n_proto;+n_proto_mask=match.mask->n_proto;+if(n_proto==htons(ETH_P_ALL)){+n_proto=0;+n_proto_mask=0;+}++key->eth.type=n_proto;+mask->eth.type=n_proto_mask;++if(match.mask->ip_proto){+ip_proto=match.key->ip_proto;+key->ip.proto=ip_proto;+mask->ip.proto=match.mask->ip_proto;+}+}++if(flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_ETH_ADDRS)){+structflow_match_eth_addrsmatch;++flow_rule_match_eth_addrs(rule,&match);++ether_addr_copy(key->eth.dst,match.key->dst);+ether_addr_copy(mask->eth.dst,match.mask->dst);+ether_addr_copy(key->eth.src,match.key->src);+ether_addr_copy(mask->eth.src,match.mask->src);+}++if(flow_rule_match_key(rule,FLOW_DISSECTOR_KEY_VLAN)){+structflow_match_vlanmatch;++flow_rule_match_vlan(rule,&match);++key->vlan.tpid=match.key->vlan_tpid;+mask->vlan.tpid=match.mask->vlan_tpid;+key->vlan.tci=htons(match.key->vlan_id|+(match.key->vlan_priority<<+VLAN_PRIO_SHIFT));+mask->vlan.tci=htons(match.mask->vlan_id|+(match.mask->vlan_priority<<+VLAN_PRIO_SHIFT));+}++if(addr_type==FLOW_DISSECTOR_KEY_IPV4_ADDRS){+structflow_match_ipv4_addrsmatch;++flow_rule_match_ipv4_addrs(rule,&match);++key->ipv4.src=match.key->src;+mask->ipv4.src=match.mask->src;+key->ipv4.dst=match.key->dst;+mask->ipv4.dst=match.mask->dst;+}++if(addr_type==FLOW_DISSECTOR_KEY_IPV6_ADDRS){+structflow_match_ipv6_addrsmatch;++flow_rule_match_ipv6_addrs(rule,&match);++key->ipv6.src=match.key->src;+mask->ipv6.src=match.mask->src;+key->ipv6.dst=match.key->dst;+mask->ipv6.dst=match.mask->dst;+}++err=xdp_flow_parse_ports(key,mask,f,ip_proto);+if(err)+returnerr;+err=xdp_flow_parse_tcp(key,mask,f,ip_proto);+if(err)+returnerr;++err=xdp_flow_parse_ip(key,mask,f,n_proto);+if(err)+returnerr;++// TODO: encapsulation related tasks++returnxdp_flow_parse_actions(actions,&rule->action,+f->common.extack);+}+staticvoidshutdown_umh(void){structtask_struct*tsk;
The usage would be like this:
$ ethtool -K eth0 tc-offload-xdp on
$ tc qdisc add dev eth0 clsact
$ tc filter add dev eth0 ingress protocol ip flower skip_sw ...
Then the filters offloaded to XDP are marked as "in_hw".
If the tc flow block is created when tc-offload-xdp is enabled on the
device, the block is internally marked as xdp and only can be offloaded
to XDP.
The reason not to allow HW-offload and XDP-offload at the same time is
to avoid the situation where offloading to only one of them succeeds.
If we allow offloading to both, users cannot know which offload
succeeded.
NOTE: This makes flows offloaded to XDP look as if they are HW
offloaded, since they will be marked as "in_hw". This could be confusing.
Maybe we can add another status "in_xdp"? Then we can allow both of HW-
and XDP-offload at the same time.
Signed-off-by: Toshiaki Makita <redacted>
---
include/linux/netdev_features.h | 2 ++
include/net/pkt_cls.h | 5 +++
include/net/sch_generic.h | 1 +
net/core/dev.c | 2 ++
net/core/ethtool.c | 1 +
net/sched/cls_api.c | 67 +++++++++++++++++++++++++++++++++++++---
net/xdp_flow/xdp_flow_kern_mod.c | 6 ++++
7 files changed, 80 insertions(+), 4 deletions(-)
@@ -80,6 +80,7 @@ enum {NETIF_F_GRO_HW_BIT,/* Hardware Generic receive offload */NETIF_F_HW_TLS_RECORD_BIT,/* Offload TLS record */+NETIF_F_XDP_TC_BIT,/* Offload TC to XDP *//**Addyourfreshnewfeatureaboveandremembertoupdate
@@ -150,6 +151,7 @@ enum {#define NETIF_F_GSO_UDP_L4 __NETIF_F(GSO_UDP_L4)#define NETIF_F_HW_TLS_TX __NETIF_F(HW_TLS_TX)#define NETIF_F_HW_TLS_RX __NETIF_F(HW_TLS_RX)+#define NETIF_F_XDP_TC __NETIF_F(XDP_TC)/* Finds the next feature with the highest number of the range of start till 0.*/
@@ -402,6 +402,7 @@ struct tcf_block {structflow_blockflow_block;structlist_headowner_list;boolkeep_dst;+boolxdp;unsignedintoffloadcnt;/* Number of oddloaded filters */unsignedintnooffloaddevcnt;/* Number of devs unable to do offload */struct{
@@ -806,7 +807,7 @@ static int tcf_block_offload_cmd(struct tcf_block *block,structnet_device*dev,structtcf_block_ext_info*ei,enumflow_block_commandcommand,-structnetlink_ext_ack*extack)+boolxdp,structnetlink_ext_ack*extack){structflow_block_offloadbo={};interr;
@@ -819,13 +820,39 @@ static int tcf_block_offload_cmd(struct tcf_block *block,bo.extack=extack;INIT_LIST_HEAD(&bo.cb_list);-err=dev->netdev_ops->ndo_setup_tc(dev,TC_SETUP_BLOCK,&bo);+if(xdp)+err=xdp_flow_setup_block(dev,&bo);+else+err=dev->netdev_ops->ndo_setup_tc(dev,TC_SETUP_BLOCK,&bo);if(err<0)returnerr;returntcf_block_setup(block,&bo);}+staticinttcf_block_offload_bind_xdp(structtcf_block*block,structQdisc*q,+structtcf_block_ext_info*ei,+structnetlink_ext_ack*extack)+{+structnet_device*dev=q->dev_queue->dev;+interr;++if(!tc_xdp_offload_enabled(dev)&&tcf_block_offload_in_use(block)){+NL_SET_ERR_MSG(extack,+"Bind to offloaded block failed as dev has tc-offload-xdp disabled");+return-EOPNOTSUPP;+}++err=tcf_block_offload_cmd(block,dev,ei,FLOW_BLOCK_BIND,true,+extack);+if(err==-EOPNOTSUPP){+block->nooffloaddevcnt++;+err=0;+}++returnerr;+}+staticinttcf_block_offload_bind(structtcf_block*block,structQdisc*q,structtcf_block_ext_info*ei,structnetlink_ext_ack*extack)
@@ -833,6 +860,15 @@ static int tcf_block_offload_bind(struct tcf_block *block, struct Qdisc *q,structnet_device*dev=q->dev_queue->dev;interr;+if(block->xdp)+returntcf_block_offload_bind_xdp(block,q,ei,extack);++if(tc_xdp_offload_enabled(dev)){+NL_SET_ERR_MSG(extack,+"Cannot bind to block created with tc-offload-xdp disabled");+return-EOPNOTSUPP;+}+if(!dev->netdev_ops->ndo_setup_tc)gotono_offload_dev_inc;
@@ -1004,6 +1059,10 @@ static struct tcf_block *tcf_block_create(struct net *net, struct Qdisc *q,/* Don't store q pointer for blocks which are shared */if(!tcf_block_shared(block))block->q=q;++if(tc_xdp_offload_enabled(q->dev_queue->dev))+block->xdp=true;+returnblock;}
@@ -410,6 +410,12 @@ static int xdp_flow_setup_block_cb(enum tc_setup_type type, void *type_data,structnet_device*dev=cb_priv;interr=0;+if(!tc_xdp_offload_enabled(dev)){+NL_SET_ERR_MSG(common->extack,+"tc-offload-xdp is disabled on net device");+return-EOPNOTSUPP;+}+if(common->chain_index){NL_SET_ERR_MSG(common->extack,"xdp_flow supports only offload of chain 0");
@@ -90,6 +101,15 @@ static int setup(void)gotoerr;}+output_map_fd=bpf_create_map(BPF_MAP_TYPE_DEVMAP,sizeof(int),+sizeof(int),MAX_PORTS,0);+if(output_map_fd<0){+err=-errno;+pr_err("map creation for output_map failed: %s\n",+strerror(errno));+gotoerr;+}+return0;err:close(progfile_fd);
@@ -97,10 +117,23 @@ static int setup(void)returnerr;}-staticintload_bpf(intifindex,structbpf_object**objp)+staticvoiddelete_output_map_elem(intidx)+{+charerrbuf[ERRBUF_SIZE];+interr;++err=bpf_map_delete_elem(output_map_fd,&idx);+if(err){+libbpf_err(err,errbuf);+pr_warn("Failed to delete idx %d from output_map: %s\n",+idx,errbuf);+}+}++staticintload_bpf(intifindex,intdevmap_idx,structbpf_object**objp){intprog_fd,flow_tables_fd,flow_meta_fd,flow_masks_head_fd,err;-structbpf_map*flow_tables,*flow_masks_head;+structbpf_map*output_map,*flow_tables,*flow_masks_head;intzero=0,flow_masks_tail=FLOW_MASKS_TAIL;structbpf_object_open_attrattr={};charpath[256],errbuf[ERRBUF_SIZE];
@@ -133,6 +166,27 @@ static int load_bpf(int ifindex, struct bpf_object **objp)bpf_object__for_each_program(prog,obj)bpf_program__set_type(prog,attr.prog_type);+output_map=bpf_object__find_map_by_name(obj,"output_map");+if(!output_map){+pr_err("Cannot find output_map\n");+err=-ENOENT;+gotoerr_obj;+}++err=bpf_map__reuse_fd(output_map,output_map_fd);+if(err){+err=libbpf_err(err,errbuf);+pr_err("Failed to reuse output_map fd: %s\n",errbuf);+gotoerr_obj;+}++if(bpf_map_update_elem(output_map_fd,&devmap_idx,&ifindex,0)){+err=-errno;+pr_err("Failed to insert idx %d if %d into output_map: %s\n",+devmap_idx,ifindex,strerror(errno));+gotoerr_obj;+}+flow_meta_fd=bpf_create_map(BPF_MAP_TYPE_HASH,sizeof(structxdp_flow_key),sizeof(structxdp_flow_actions),
@@ -382,12 +497,45 @@ static int handle_unload(const struct mbox_request *req)hash_del(&netdev_info->node);bpf_object__close(netdev_info->obj);+delete_output_map_elem(netdev_info->devmap_idx);+delete_devmap_idx(netdev_info->devmap_idx);free(netdev_info);pr_debug("XDP program for if %d was closed\n",req->ifindex);return0;}+staticintconvert_ifindex_to_devmap_idx(structmbox_request*req)+{+inti;++for(i=0;i<req->flow.actions.num_actions;i++){+structxdp_flow_action*action=&req->flow.actions.actions[i];++if(action->id==XDP_FLOW_ACTION_REDIRECT){+structnetdev_info*netdev_info;+structnetdev_info_keykey;+interr;++err=get_netdev_info_key(req,&key);+if(err)+returnerr;+key.ifindex=action->ifindex;++netdev_info=find_netdev_info(&key);+if(!netdev_info){+pr_err("Cannot redirect to ifindex %d. Please setup xdp_flow on ifindex %d in advance.\n",+key.ifindex,key.ifindex);+return-ENOENT;+}++action->ifindex=netdev_info->devmap_idx;+}+}++return0;+}+staticintget_table_fd(conststructnetdev_info*netdev_info,constchar*table_name){
@@ -784,6 +932,11 @@ static int handle_replace(struct mbox_request *req)if(IS_ERR(netdev_info))returnPTR_ERR(netdev_info);+/* TODO: Use XDP_TX for redirect action when possible */+err=convert_ifindex_to_devmap_idx(req);+if(err)+returnerr;+err=flow_table_insert_elem(netdev_info,&req->flow);if(err)returnerr;
@@ -875,6 +1028,7 @@ int main(void)return-1;loop();close(progfile_fd);+close(output_map_fd);return0;}
@@ -0,0 +1,103 @@+#!/bin/sh+# SPDX-License-Identifier: GPL-2.0+#+# Create 2 namespaces with 2 veth peers, and+# forward packets in-between using xdp_flow+#+# NS1(veth11) NS2(veth22)+# | |+# | |+# (veth1) (veth2)+# ^ ^+# | xdp_flow |+# --------------------++# Kselftest framework requirement - SKIP code is 4.+ksft_skip=4++TESTNAME=xdp_flow++_cleanup()+{+set+e+iplinkdelveth12>/dev/null+iplinkdelveth22>/dev/null+ipnetnsdelns12>/dev/null+ipnetnsdelns22>/dev/null+}++cleanup_skip()+{+echo"selftests: $TESTNAME [SKIP]"+_cleanup++exit$ksft_skip+}++cleanup()+{+if["$?"=0];then+echo"selftests: $TESTNAME [PASS]"+else+echo"selftests: $TESTNAME [FAILED]"+fi+_cleanup+}++if[$(id-u)-ne0];then+echo"selftests: $TESTNAME [SKIP] Need root privileges"+exit$ksft_skip+fi++if!iplinksetdevloxdpoff>/dev/null2>&1;then+echo"selftests: $TESTNAME [SKIP] Could not run test without the ip xdp support"+exit$ksft_skip+fi++set-e++trapcleanup_skipEXIT++ipnetnsaddns1+ipnetnsaddns2++iplinkaddveth1typevethpeernameveth11netnsns1+iplinkaddveth2typevethpeernameveth22netnsns2++iplinksetveth1up+iplinksetveth2up++ip-nns1addradd10.1.1.11/24devveth11+ip-nns2addradd10.1.1.22/24devveth22++ip-nns1linksetdevveth11up+ip-nns2linksetdevveth22up++ip-nns1linksetdevveth11xdpobjxdp_dummy.osecxdp_dummy+ip-nns2linksetdevveth22xdpobjxdp_dummy.osecxdp_dummy++ethtool-Kveth1tc-offload-xdpon+ethtool-Kveth2tc-offload-xdpon++trapcleanupEXIT++# Adding clsact or ingress will trigger loading bpf prog in UMH+tcqdiscadddevveth1clsact+tcqdiscadddevveth2clsact++# Adding filter will have UMH populate flow table map+# 'skip_sw' can be accepted only when 'tc-offload-xdp' is enabled on veth+tcfilteradddevveth1ingressprotocolipflowerskip_sw\+dst_ip10.1.1.0/24actionmirredegressredirectdevveth2+tcfilteradddevveth2ingressprotocolipflowerskip_sw\+dst_ip10.1.1.0/24actionmirredegressredirectdevveth1++# ARP is not supported so don't add 'skip_sw'+tcfilteradddevveth1ingressprotocolarpflower\+arp_tip10.1.1.0/24actionmirredegressredirectdevveth2+tcfilteradddevveth2ingressprotocolarpflower\+arp_sip10.1.1.0/24actionmirredegressredirectdevveth1++ipnetnsexecns1ping-c1-W110.1.1.22++exit0
XDP progs are likely to read/write xdp->data.
This improves the performance of xdp_flow.
This is included in this series just to demonstrate to what extent
xdp_flow performance can increase.
Signed-off-by: Toshiaki Makita <redacted>
---
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 1 +
1 file changed, 1 insertion(+)
memcmp() is generally slow. Compare keys in long if possible.
This improves xdp_flow performance.
This is included in this series just to demonstrate to what extent
xdp_flow performance can increase.
Signed-off-by: Toshiaki Makita <redacted>
---
kernel/bpf/hashtab.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
@@ -417,6 +417,29 @@ static inline struct hlist_nulls_head *select_bucket(struct bpf_htab *htab, u32return&__select_bucket(htab,hash)->head;}+/* key1 must be aligned to sizeof long */+staticboolkey_equal(void*key1,void*key2,u32size)+{+/* Check for key1 */+BUILD_BUG_ON(!IS_ALIGNED(offsetof(structhtab_elem,key),+sizeof(long)));++if(IS_ALIGNED((unsignedlong)key2|(unsignedlong)size,+sizeof(long))){+unsignedlong*lkey1,*lkey2;++for(lkey1=key1,lkey2=key2;size>0;+lkey1++,lkey2++,size-=sizeof(long)){+if(*lkey1!=*lkey2)+returnfalse;+}++returntrue;+}++return!memcmp(key1,key2,size);+}+/* this lookup function can only be called with bucket lock taken */staticstructhtab_elem*lookup_elem_raw(structhlist_nulls_head*head,u32hash,void*key,u32key_size)
Is xdp_flow limited to 4 Mpps due to veth or something else?
So xdp_flow drop rate is roughly 4x faster than software TC or ovs kmod.
OTOH the time to add a flow increases with xdp_flow.
ping latency of first packet when veth1 does XDP_PASS instead of DROP:
xdp_flow TC ovs kmod
-------- -------- --------
25ms 12ms 0.6ms
xdp_flow does a lot of work to emulate TC behavior including UMH
transaction and multiple bpf map update from UMH which I think increases
the latency.
make sense, but why vanilla TC is so slow ?
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
I think UMH approach is a good fit for this.
Clearly the same algorithm can be done as kernel code or kernel module, but
bpfilter-like UMH is a safer approach.
- patch 9
Add tc-offload-xdp netdev feature and hooks to call xdp_flow kmod in
TC flower offload code.
The hook into UMH from TC looks simple. Do you expect the same interface to be
reused from OVS ?
* About alternative userland (ovs-vswitchd etc.) implementation
Maybe a similar logic can be implemented in ovs-vswitchd offload
mechanism, instead of adding code to kernel. I just thought offloading
TC is more generic and allows wider usage with direct TC command.
For example, considering that OVS inserts a flow to kernel only when
flow miss happens in kernel, we can in advance add offloaded flows via
tc filter to avoid flow insertion latency for certain sensitive flows.
TC flower usage without using OVS is also possible.
Also as written above nftables can be offloaded to XDP with this
mechanism as well.
Makes sense to me.
bpf, hashtab: Compare keys in long
3Mpps vs 4Mpps just from this patch ?
or combined with i40 prefech patch ?
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 1 +
Could you share "perf report" for just hash tab optimization
and for i40 ?
I haven't seen memcmp to be bottle neck in hash tab.
What is the the of the key?
Is xdp_flow limited to 4 Mpps due to veth or something else?
Looking at perf, accumulation of each layer's overhead resulted in the number.
With XDP prog which only redirects packets and does not touch the data,
the drop rate is 10 Mpps. In this case the main overhead is XDP's redirect processing
and handling of 2 XDP progs (in veth and i40e).
In the xdp_flow test the overhead additionally includes flow key parse in XDP prog
and hash table lookup (including jhash calculation) which resulted in 4 Mpps.
quoted
So xdp_flow drop rate is roughly 4x faster than software TC or ovs kmod.
OTOH the time to add a flow increases with xdp_flow.
ping latency of first packet when veth1 does XDP_PASS instead of DROP:
xdp_flow TC ovs kmod
-------- -------- --------
25ms 12ms 0.6ms
xdp_flow does a lot of work to emulate TC behavior including UMH
transaction and multiple bpf map update from UMH which I think increases
the latency.
make sense, but why vanilla TC is so slow ?
No ideas. At least TC requires additional syscall to insert a flow compared to ovs kmod,
but 12 ms looks too long for that.
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
I think UMH approach is a good fit for this.
Clearly the same algorithm can be done as kernel code or kernel module, but
bpfilter-like UMH is a safer approach.
quoted
- patch 9
Add tc-offload-xdp netdev feature and hooks to call xdp_flow kmod in
TC flower offload code.
The hook into UMH from TC looks simple. Do you expect the same interface to be
reused from OVS ?
Do you mean openvswitch kernel module by OVS?
If so, no, at this point. TC hook is simple because I reused flow offload mechanism.
OVS kmod does not have offload interface and ovs-vswitchd is using TC for offload.
I wanted to reuse this mechanism for offloading to XDP, so using TC.
quoted
* About alternative userland (ovs-vswitchd etc.) implementation
Maybe a similar logic can be implemented in ovs-vswitchd offload
mechanism, instead of adding code to kernel. I just thought offloading
TC is more generic and allows wider usage with direct TC command.
For example, considering that OVS inserts a flow to kernel only when
flow miss happens in kernel, we can in advance add offloaded flows via
tc filter to avoid flow insertion latency for certain sensitive flows.
TC flower usage without using OVS is also possible.
Also as written above nftables can be offloaded to XDP with this
mechanism as well.
Makes sense to me.
quoted
bpf, hashtab: Compare keys in long
3Mpps vs 4Mpps just from this patch ?
or combined with i40 prefech patch ?
Combined.
quoted
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 1 +
Could you share "perf report" for just hash tab optimization
and for i40 ?
Sure, I'll get some more data and post them.
I haven't seen memcmp to be bottle neck in hash tab.
What is the the of the key?
typo of "size of the key"? IIRC 64 bytes.
Toshiaki Makita
From: Stanislav Fomichev <sdf@fomichev.me> Date: 2019-08-14 17:07:19
On 08/13, Toshiaki Makita wrote:
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
I also thought about that. There are two phases so let's think about them separately.
1) TC block (qdisc) creation / eBPF load
I saw eBPF maintainers repeatedly saying eBPF program loading needs to be
done from userland, not from kernel, to run the verifier for safety.
However xdp_flow eBPF program is prebuilt and embedded in kernel so we may
allow such programs to be loaded from kernel? I currently don't have the will
to make such an API as loading can be done with current UMH mechanism.
2) flow insertion / eBPF map update
Not sure if this needs to be done from userland. One concern is that eBPF maps can
be modified by unrelated processes and we need to handle all unexpected state of maps.
Such handling tends to be difficult and may cause unexpected kernel behavior.
OTOH updating maps from kmod may reduces the latency of flow insertion drastically.
Alexei, Daniel, what do you think?
Toshiaki Makita
From: Stanislav Fomichev <sdf@fomichev.me> Date: 2019-08-15 15:21:04
On 08/15, Toshiaki Makita wrote:
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
But, I haven't looked at the series deeply, so I might be missing
something :-)
I also thought about that. There are two phases so let's think about them separately.
1) TC block (qdisc) creation / eBPF load
I saw eBPF maintainers repeatedly saying eBPF program loading needs to be
done from userland, not from kernel, to run the verifier for safety.
However xdp_flow eBPF program is prebuilt and embedded in kernel so we may
allow such programs to be loaded from kernel? I currently don't have the will
to make such an API as loading can be done with current UMH mechanism.
2) flow insertion / eBPF map update
Not sure if this needs to be done from userland. One concern is that eBPF maps can
be modified by unrelated processes and we need to handle all unexpected state of maps.
Such handling tends to be difficult and may cause unexpected kernel behavior.
OTOH updating maps from kmod may reduces the latency of flow insertion drastically.
Latency from the moment I type 'tc filter add ...' to the moment the rule
is installed into the maps? Does it really matter?
Do I understand correctly that both of those events (qdisc creation and
flow insertion) are triggered from tcf_block_offload_cmd (or similar)?
Alexei, Daniel, what do you think?
Toshiaki Makita
From: William Tu <hidden> Date: 2019-08-15 15:47:21
On Tue, Aug 13, 2019 at 5:07 AM Toshiaki Makita
[off-list ref] wrote:
This is a rough PoC for an idea to offload TC flower to XDP.
* Motivation
The purpose is to speed up software TC flower by using XDP.
I chose TC flower because my current interest is in OVS. OVS uses TC to
offload flow tables to hardware, so if TC can offload flows to XDP, OVS
also can be offloaded to XDP.
When TC flower filter is offloaded to XDP, the received packets are
handled by XDP first, and if their protocol or something is not
supported by the eBPF program, the program returns XDP_PASS and packets
are passed to upper layer TC.
The packet processing flow will be like this when this mechanism,
xdp_flow, is used with OVS.
+-------------+
| openvswitch |
| kmod |
+-------------+
^
| if not match in filters (flow key or action not supported by TC)
+-------------+
| TC flower |
+-------------+
^
| if not match in flow tables (flow key or action not supported by XDP)
+-------------+
| XDP prog |
+-------------+
^
| incoming packets
I like this idea, some comments about the OVS AF_XDP work.
Another way when using OVS AF_XDP is to serve as slow path of TC flow
HW offload.
For example:
Userspace OVS datapath (The one used by OVS-DPDK)
^
|
+------------------------------+
| OVS AF_XDP netdev |
+------------------------------+
^
| if not supported or not match in flow tables
+---------------------+
| TC HW flower |
+---------------------+
^
| incoming packets
So in this case it's either TC HW flower offload, or the userspace PMD OVS.
Both cases should be pretty fast.
I think xdp_flow can also be used by OVS AF_XDP netdev, sitting between
TC HW flower and OVS AF_XDP netdev.
Before the XDP program sending packet to AF_XDP socket, the
xdp_flow can execute first, and if not match, then send to AF_XDP.
So in your patch set, implement s.t like
bpf_redirect_map(&xsks_map, index, 0);
Another thing is that at each layer we are doing its own packet parsing.
From your graph, first parse at XDP program, then at TC flow, then at
openvswitch kmod.
I wonder if we can reuse some parsing result.
Regards,
William
This is useful especially when the device does not support HW-offload.
Such interfaces include virtual interfaces like veth.
* How to use
It only supports ingress (clsact) flower filter at this point.
Enable the feature via ethtool before adding ingress/clsact qdisc.
$ ethtool -K eth0 tc-offload-xdp on
Then add qdisc/filters as normal.
$ tc qdisc add dev eth0 clsact
$ tc filter add dev eth0 ingress protocol ip flower skip_sw ...
Alternatively, when using OVS, adding qdisc and filters will be
automatically done by setting hw-offload.
$ ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
$ systemctl stop openvswitch
$ tc qdisc del dev eth0 ingress # or reboot
$ ethtool -K eth0 tc-offload-xdp on
$ systemctl start openvswitch
* Performance
I measured drop rate at veth interface with redirect action from physical
interface (i40e 25G NIC, XXV 710) to veth. The CPU is Xeon Silver 4114
(2.20 GHz).
XDP_DROP
+------+ +-------+ +-------+
pktgen -- wire --> | eth0 | -- TC/OVS redirect --> | veth0 |----| veth1 |
+------+ (offloaded to XDP) +-------+ +-------+
The setup for redirect is done by OVS like this.
$ ovs-vsctl add-br ovsbr0
$ ovs-vsctl add-port ovsbr0 eth0
$ ovs-vsctl add-port ovsbr0 veth0
$ ovs-vsctl set Open_vSwitch . other_config:hw-offload=true
$ systemctl stop openvswitch
$ tc qdisc del dev eth0 ingress
$ tc qdisc del dev veth0 ingress
$ ethtool -K eth0 tc-offload-xdp on
$ ethtool -K veth0 tc-offload-xdp on
$ systemctl start openvswitch
Tested single core/single flow with 3 configurations.
- xdp_flow: hw-offload=true, tc-offload-xdp on
- TC: hw-offload=true, tc-offload-xdp off (software TC)
- ovs kmod: hw-offload=false
xdp_flow TC ovs kmod
-------- -------- --------
4.0 Mpps 1.1 Mpps 1.1 Mpps
So xdp_flow drop rate is roughly 4x faster than software TC or ovs kmod.
OTOH the time to add a flow increases with xdp_flow.
ping latency of first packet when veth1 does XDP_PASS instead of DROP:
xdp_flow TC ovs kmod
-------- -------- --------
25ms 12ms 0.6ms
xdp_flow does a lot of work to emulate TC behavior including UMH
transaction and multiple bpf map update from UMH which I think increases
the latency.
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
+----------------------+
| xdp_flow_umh | load eBPF prog for XDP
| (eBPF prog embedded) | update maps for flow tables
+----------------------+
^ |
request | v eBPF prog id
+-----------+ offload +-----------------------+
| TC flower | --------> | xdp_flow kmod | attach the prog to XDP
+-----------+ | (flow offload driver) |
+-----------------------+
- When ingress/clsact qdisc is created, i.e. a device is bound to a flow
block, xdp_flow kmod requests xdp_flow_umh to load eBPF prog.
xdp_flow_umh returns prog id and xdp_flow kmod attach the prog to XDP
(the reason of attaching XDP from kmod is that rtnl_lock is held here).
- When flower filter is added, xdp_flow kmod requests xdp_flow_umh to
update maps for flow tables.
* Patches
- patch 1
Basic framework for xdp_flow kmod and UMH.
- patch 2
Add prebuilt eBPF program embedded in UMH.
- patch 3, 4
Attach the prog to XDP in kmod after using the prog id returned from
UMH.
- patch 5, 6
Add maps for flow tables and flow table manipulation logic in UMH.
- patch 7
Implement flow lookup and basic actions in eBPF prog.
- patch 8
Implement flow manipulation logic, serialize flow key and actions from
TC flower and make requests to UMH in kmod.
- patch 9
Add tc-offload-xdp netdev feature and hooks to call xdp_flow kmod in
TC flower offload code.
- patch 10, 11
Add example actions, redirect and vlan_push.
- patch 12
Add testcase for xdp_flow.
- patch 13, 14
These are unrelated patches. They just improves XDP program's
performance. They are included to demonstrate to what extent xdp_flow
performance can increase. Without them, drop rate goes down from 4Mpps
to 3Mpps.
* About OVS AF_XDP netdev
Recently OVS has added AF_XDP netdev type support. This also makes use
of XDP, but in some ways different from this patch set.
- AF_XDP work originally started in order to bring BPF's flexibility to
OVS, which enables us to upgrade datapath without updating kernel.
AF_XDP solution uses userland datapath so it achieved its goal.
xdp_flow will not replace OVS datapath completely, but offload it
partially just for speed up.
- OVS AF_XDP requires PMD for the best performance so consumes 100% CPU.
- OVS AF_XDP needs packet copy when forwarding packets.
- xdp_flow can be used not only for OVS. It works for direct use of TC
flower. nftables also can be offloaded by the same mechanism in the
future.
* About alternative userland (ovs-vswitchd etc.) implementation
Maybe a similar logic can be implemented in ovs-vswitchd offload
mechanism, instead of adding code to kernel. I just thought offloading
TC is more generic and allows wider usage with direct TC command.
For example, considering that OVS inserts a flow to kernel only when
flow miss happens in kernel, we can in advance add offloaded flows via
tc filter to avoid flow insertion latency for certain sensitive flows.
TC flower usage without using OVS is also possible.
Also as written above nftables can be offloaded to XDP with this
mechanism as well.
* Note
This patch set is based on top of commit a664a834579a ("tools: bpftool:
fix reading from /proc/config.gz").
Any feedback is welcome.
Thanks!
Signed-off-by: Toshiaki Makita <redacted>
Toshiaki Makita (14):
xdp_flow: Add skeleton of XDP based TC offload driver
xdp_flow: Add skeleton bpf program for XDP
bpf: Add API to get program from id
xdp_flow: Attach bpf prog to XDP in kernel after UMH loaded program
xdp_flow: Prepare flow tables in bpf
xdp_flow: Add flow entry insertion/deletion logic in UMH
xdp_flow: Add flow handling and basic actions in bpf prog
xdp_flow: Implement flow replacement/deletion logic in xdp_flow kmod
xdp_flow: Add netdev feature for enabling TC flower offload to XDP
xdp_flow: Implement redirect action
xdp_flow: Implement vlan_push action
bpf, selftest: Add test for xdp_flow
i40e: prefetch xdp->data before running XDP prog
bpf, hashtab: Compare keys in long
drivers/net/ethernet/intel/i40e/i40e_txrx.c | 1 +
include/linux/bpf.h | 6 +
include/linux/netdev_features.h | 2 +
include/linux/netdevice.h | 4 +
include/net/flow_offload_xdp.h | 33 +
include/net/pkt_cls.h | 5 +
include/net/sch_generic.h | 1 +
kernel/bpf/hashtab.c | 27 +-
kernel/bpf/syscall.c | 26 +-
net/Kconfig | 1 +
net/Makefile | 1 +
net/core/dev.c | 13 +-
net/core/ethtool.c | 1 +
net/sched/cls_api.c | 67 +-
net/xdp_flow/.gitignore | 1 +
net/xdp_flow/Kconfig | 16 +
net/xdp_flow/Makefile | 112 +++
net/xdp_flow/msgfmt.h | 102 +++
net/xdp_flow/umh_bpf.h | 34 +
net/xdp_flow/xdp_flow_core.c | 126 ++++
net/xdp_flow/xdp_flow_kern_bpf.c | 358 +++++++++
net/xdp_flow/xdp_flow_kern_bpf_blob.S | 7 +
net/xdp_flow/xdp_flow_kern_mod.c | 645 ++++++++++++++++
net/xdp_flow/xdp_flow_umh.c | 1034 ++++++++++++++++++++++++++
net/xdp_flow/xdp_flow_umh_blob.S | 7 +
tools/testing/selftests/bpf/Makefile | 1 +
tools/testing/selftests/bpf/test_xdp_flow.sh | 103 +++
27 files changed, 2716 insertions(+), 18 deletions(-)
create mode 100644 include/net/flow_offload_xdp.h
create mode 100644 net/xdp_flow/.gitignore
create mode 100644 net/xdp_flow/Kconfig
create mode 100644 net/xdp_flow/Makefile
create mode 100644 net/xdp_flow/msgfmt.h
create mode 100644 net/xdp_flow/umh_bpf.h
create mode 100644 net/xdp_flow/xdp_flow_core.c
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf.c
create mode 100644 net/xdp_flow/xdp_flow_kern_bpf_blob.S
create mode 100644 net/xdp_flow/xdp_flow_kern_mod.c
create mode 100644 net/xdp_flow/xdp_flow_umh.c
create mode 100644 net/xdp_flow/xdp_flow_umh_blob.S
create mode 100755 tools/testing/selftests/bpf/test_xdp_flow.sh
--
1.8.3.1
From: Jakub Kicinski <hidden> Date: 2019-08-15 19:22:54
On Thu, 15 Aug 2019 08:21:00 -0700, Stanislav Fomichev wrote:
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
FWIW Quentin spent some time working on a universal flow rule to BPF
translation library:
https://github.com/Netronome/libkefir
A lot remains to be done there, but flower front end is one of the
targets. A library can be tuned for any application, without a
dependency on flower uAPI.
But, I haven't looked at the series deeply, so I might be missing
something :-)
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
My intention is to make more people who want high speed network easily use XDP,
so making transparent XDP offload with current TC interface.
What userspace program would monitor TC events with your suggestion?
ovs-vswitchd? If so, it even does not need to monitor TC. It can
implement XDP offload directly.
(However I prefer kernel solution. Please refer to "About alternative
userland (ovs-vswitchd etc.) implementation" section in the cover letter.)
Also such a TC monitoring solution easily can be out-of-sync with real TC
behavior as TC filter/flower is being heavily developed and changed,
e.g. introduction of TC block, support multiple masks with the same pref, etc.
I'm not sure such an unreliable solution have much value.
But, I haven't looked at the series deeply, so I might be missing
something :-)
quoted
I also thought about that. There are two phases so let's think about them separately.
1) TC block (qdisc) creation / eBPF load
I saw eBPF maintainers repeatedly saying eBPF program loading needs to be
done from userland, not from kernel, to run the verifier for safety.
However xdp_flow eBPF program is prebuilt and embedded in kernel so we may
allow such programs to be loaded from kernel? I currently don't have the will
to make such an API as loading can be done with current UMH mechanism.
2) flow insertion / eBPF map update
Not sure if this needs to be done from userland. One concern is that eBPF maps can
be modified by unrelated processes and we need to handle all unexpected state of maps.
Such handling tends to be difficult and may cause unexpected kernel behavior.
OTOH updating maps from kmod may reduces the latency of flow insertion drastically.
Latency from the moment I type 'tc filter add ...' to the moment the rule
is installed into the maps? Does it really matter?
Yes it matters. Flow insertion is kind of data path in OVS.
Please see how ping latency is affected in the cover letter.
Do I understand correctly that both of those events (qdisc creation and
flow insertion) are triggered from tcf_block_offload_cmd (or similar)?
Both of eBPF load and map update are triggered from tcf_block_offload_cmd.
I think you understand it correctly.
Toshiaki Makita
On Thu, 15 Aug 2019 08:21:00 -0700, Stanislav Fomichev wrote:
quoted
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
I don't think it's loosely coupled. Emulating TC behavior in userspace
is not so easy.
Think about recent multi-mask support in flower. Previously userspace could
assume there is one mask and hash table for each preference in TC. After the
change TC accepts different masks with the same pref. Such a change tends to
break userspace emulation. It may ignore masks passed from flow insertion
and use the mask remembered when the first flow of the pref is inserted. It
may override the mask of all existing flows with the pref. It may fail to
insert such flows. Any of them would result in unexpected wrong datapath
handling which is critical.
I think such an emulation layer needs to be updated in sync with TC.
Toshiaki Makita
On Tue, Aug 13, 2019 at 5:07 AM Toshiaki Makita
[off-list ref] wrote:
quoted
This is a rough PoC for an idea to offload TC flower to XDP.
* Motivation
The purpose is to speed up software TC flower by using XDP.
I chose TC flower because my current interest is in OVS. OVS uses TC to
offload flow tables to hardware, so if TC can offload flows to XDP, OVS
also can be offloaded to XDP.
When TC flower filter is offloaded to XDP, the received packets are
handled by XDP first, and if their protocol or something is not
supported by the eBPF program, the program returns XDP_PASS and packets
are passed to upper layer TC.
The packet processing flow will be like this when this mechanism,
xdp_flow, is used with OVS.
+-------------+
| openvswitch |
| kmod |
+-------------+
^
| if not match in filters (flow key or action not supported by TC)
+-------------+
| TC flower |
+-------------+
^
| if not match in flow tables (flow key or action not supported by XDP)
+-------------+
| XDP prog |
+-------------+
^
| incoming packets
I like this idea, some comments about the OVS AF_XDP work.
Another way when using OVS AF_XDP is to serve as slow path of TC flow
HW offload.
For example:
Userspace OVS datapath (The one used by OVS-DPDK)
^
|
+------------------------------+
| OVS AF_XDP netdev |
+------------------------------+
^
| if not supported or not match in flow tables
+---------------------+
| TC HW flower |
+---------------------+
^
| incoming packets
So in this case it's either TC HW flower offload, or the userspace PMD OVS.
Both cases should be pretty fast.
I think xdp_flow can also be used by OVS AF_XDP netdev, sitting between
TC HW flower and OVS AF_XDP netdev.
Before the XDP program sending packet to AF_XDP socket, the
xdp_flow can execute first, and if not match, then send to AF_XDP.
So in your patch set, implement s.t like
bpf_redirect_map(&xsks_map, index, 0);
Thanks, the concept sounds good but this is probably difficult as long as
this is a TC offload, which is emulating TC.
If I changed the direction and implement offload in ovs-vswitchd, it would
be possible. I'll remember this optimization.
Another thing is that at each layer we are doing its own packet parsing.
From your graph, first parse at XDP program, then at TC flow, then at
openvswitch kmod.
I wonder if we can reuse some parsing result.
That would be nice if possible...
Currently I don't have any ideas to do that. Someday XDP may support more
metadata for this or HW-offload like checksum. Then we can store the information
and upper layers may be able to use that.
Toshiaki Makita
From: Stanislav Fomichev <sdf@fomichev.me> Date: 2019-08-16 15:35:54
On 08/16, Toshiaki Makita wrote:
On 2019/08/16 0:21, Stanislav Fomichev wrote:
quoted
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
My intention is to make more people who want high speed network easily use XDP,
so making transparent XDP offload with current TC interface.
What userspace program would monitor TC events with your suggestion?
Have a new system daemon (xdpflowerd) that is independently
packaged/shipped/installed. Anybody who wants accelerated TC can
download/install it. OVS can be completely unaware of this.
ovs-vswitchd? If so, it even does not need to monitor TC. It can
implement XDP offload directly.
(However I prefer kernel solution. Please refer to "About alternative
userland (ovs-vswitchd etc.) implementation" section in the cover letter.)
Also such a TC monitoring solution easily can be out-of-sync with real TC
behavior as TC filter/flower is being heavily developed and changed,
e.g. introduction of TC block, support multiple masks with the same pref, etc.
I'm not sure such an unreliable solution have much value.
This same issue applies to the in-kernel implementation, isn't it?
What happens if somebody sends patches for a new flower feature but
doesn't add appropriate xdp support? Do we reject them?
That's why I'm suggesting to move this problem to the userspace :-)
quoted
But, I haven't looked at the series deeply, so I might be missing
something :-)
quoted
I also thought about that. There are two phases so let's think about them separately.
1) TC block (qdisc) creation / eBPF load
I saw eBPF maintainers repeatedly saying eBPF program loading needs to be
done from userland, not from kernel, to run the verifier for safety.
However xdp_flow eBPF program is prebuilt and embedded in kernel so we may
allow such programs to be loaded from kernel? I currently don't have the will
to make such an API as loading can be done with current UMH mechanism.
2) flow insertion / eBPF map update
Not sure if this needs to be done from userland. One concern is that eBPF maps can
be modified by unrelated processes and we need to handle all unexpected state of maps.
Such handling tends to be difficult and may cause unexpected kernel behavior.
OTOH updating maps from kmod may reduces the latency of flow insertion drastically.
Latency from the moment I type 'tc filter add ...' to the moment the rule
is installed into the maps? Does it really matter?
Yes it matters. Flow insertion is kind of data path in OVS.
Please see how ping latency is affected in the cover letter.
Ok, but what I'm suggesting shouldn't be less performant.
We are talking about UMH writing into a pipe vs writing TC events into
a netlink.
quoted
Do I understand correctly that both of those events (qdisc creation and
flow insertion) are triggered from tcf_block_offload_cmd (or similar)?
Both of eBPF load and map update are triggered from tcf_block_offload_cmd.
I think you understand it correctly.
Toshiaki Makita
From: Stanislav Fomichev <sdf@fomichev.me> Date: 2019-08-16 15:59:15
On 08/15, Jakub Kicinski wrote:
On Thu, 15 Aug 2019 08:21:00 -0700, Stanislav Fomichev wrote:
quoted
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
Even for bpfilter I would've solved it using something similar:
iptables bypass + redirect iptables netlink requests to some
userspace helper that was registered to be iptables compatibility
manager. And then, again, it becomes a purely userspace problem.
The issue with UMH is that the helper has to be statically compiled
from the kernel tree, which means we can't bring in any dependencies
(stuff like libkefir you mentioned below).
But I digress :-)
FWIW Quentin spent some time working on a universal flow rule to BPF
translation library:
https://github.com/Netronome/libkefir
A lot remains to be done there, but flower front end is one of the
targets. A library can be tuned for any application, without a
dependency on flower uAPI.
quoted
But, I haven't looked at the series deeply, so I might be missing
something :-)
From: Stanislav Fomichev <sdf@fomichev.me> Date: 2019-08-16 16:20:32
On 08/16, Stanislav Fomichev wrote:
On 08/15, Jakub Kicinski wrote:
quoted
On Thu, 15 Aug 2019 08:21:00 -0700, Stanislav Fomichev wrote:
quoted
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
Even for bpfilter I would've solved it using something similar:
iptables bypass + redirect iptables netlink requests to some
userspace helper that was registered to be iptables compatibility
manager. And then, again, it becomes a purely userspace problem.
Oh, wait, isn't iptables kernel api is setsockopt/getsockopt?
With the new cgroup hooks you can now try to do bpfilter completely
in BPF 🤯
The issue with UMH is that the helper has to be statically compiled
from the kernel tree, which means we can't bring in any dependencies
(stuff like libkefir you mentioned below).
But I digress :-)
quoted
FWIW Quentin spent some time working on a universal flow rule to BPF
translation library:
https://github.com/Netronome/libkefir
A lot remains to be done there, but flower front end is one of the
targets. A library can be tuned for any application, without a
dependency on flower uAPI.
quoted
But, I haven't looked at the series deeply, so I might be missing
something :-)
From: Jakub Kicinski <hidden> Date: 2019-08-16 18:52:43
On Fri, 16 Aug 2019 10:28:10 +0900, Toshiaki Makita wrote:
On 2019/08/16 4:22, Jakub Kicinski wrote:
quoted
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
I don't think it's loosely coupled. Emulating TC behavior in userspace
is not so easy.
Think about recent multi-mask support in flower. Previously userspace could
assume there is one mask and hash table for each preference in TC. After the
change TC accepts different masks with the same pref. Such a change tends to
break userspace emulation. It may ignore masks passed from flow insertion
and use the mask remembered when the first flow of the pref is inserted. It
may override the mask of all existing flows with the pref. It may fail to
insert such flows. Any of them would result in unexpected wrong datapath
handling which is critical.
I think such an emulation layer needs to be updated in sync with TC.
Oh, so you're saying that if xdp_flow is merged all patches to
cls_flower and netfilter which affect flow offload will be required
to update xdp_flow as well?
That's a question of policy. Technically the implementation in user
space is equivalent.
The advantage of user space implementation is that you can add more
to it and explore use cases which do not fit in the flow offload API,
but are trivial for BPF. Not to mention the obvious advantage of
decoupling the upgrade path.
Personally I'm not happy with the way this patch set messes with the
flow infrastructure. You should use the indirect callback
infrastructure instead, and that way you can build the whole thing
touching none of the flow offload core.
On Fri, 16 Aug 2019 10:28:10 +0900, Toshiaki Makita wrote:
quoted
On 2019/08/16 4:22, Jakub Kicinski wrote:
quoted
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
I don't think it's loosely coupled. Emulating TC behavior in userspace
is not so easy.
Think about recent multi-mask support in flower. Previously userspace could
assume there is one mask and hash table for each preference in TC. After the
change TC accepts different masks with the same pref. Such a change tends to
break userspace emulation. It may ignore masks passed from flow insertion
and use the mask remembered when the first flow of the pref is inserted. It
may override the mask of all existing flows with the pref. It may fail to
insert such flows. Any of them would result in unexpected wrong datapath
handling which is critical.
I think such an emulation layer needs to be updated in sync with TC.
Oh, so you're saying that if xdp_flow is merged all patches to
cls_flower and netfilter which affect flow offload will be required
to update xdp_flow as well?
Hmm... you are saying that we are allowed to break other in-kernel
subsystem by some change? Sounds strange...
That's a question of policy. Technically the implementation in user
space is equivalent.
>
The advantage of user space implementation is that you can add more
to it and explore use cases which do not fit in the flow offload API,
but are trivial for BPF. Not to mention the obvious advantage of
decoupling the upgrade path.
I understand the advantage, but I can't trust such a third-party kernel
emulation solution for this kind of thing which handles critical data path.
Personally I'm not happy with the way this patch set messes with the
flow infrastructure. You should use the indirect callback
infrastructure instead, and that way you can build the whole thing
touching none of the flow offload core.
I don't want to mess up the core flow infrastructure either. I'm all
ears about less invasive ways. Using indirect callback sounds like a
good idea. Will give it a try. Many thanks.
Toshiaki Makita
On 19/08/17 (土) 0:35:50, Stanislav Fomichev wrote:
On 08/16, Toshiaki Makita wrote:
quoted
On 2019/08/16 0:21, Stanislav Fomichev wrote:
quoted
On 08/15, Toshiaki Makita wrote:
quoted
On 2019/08/15 2:07, Stanislav Fomichev wrote:
quoted
On 08/13, Toshiaki Makita wrote:
quoted
* Implementation
xdp_flow makes use of UMH to load an eBPF program for XDP, similar to
bpfilter. The difference is that xdp_flow does not generate the eBPF
program dynamically but a prebuilt program is embedded in UMH. This is
mainly because flow insertion is considerably frequent. If we generate
and load an eBPF program on each insertion of a flow, the latency of the
first packet of ping in above test will incease, which I want to avoid.
Can this be instead implemented with a new hook that will be called
for TC events? This hook can write to perf event buffer and control
plane will insert/remove/modify flow tables in the BPF maps (contol
plane will also install xdp program).
Why do we need UMH? What am I missing?
So you suggest doing everything in xdp_flow kmod?
You probably don't even need xdp_flow kmod. Add new tc "offload" mode
(bypass) that dumps every command via netlink (or calls the BPF hook
where you can dump it into perf event buffer) and then read that info
from userspace and install xdp programs and modify flow tables.
I don't think you need any kernel changes besides that stream
of data from the kernel about qdisc/tc flow creation/removal/etc.
My intention is to make more people who want high speed network easily use XDP,
so making transparent XDP offload with current TC interface.
What userspace program would monitor TC events with your suggestion?
Have a new system daemon (xdpflowerd) that is independently
packaged/shipped/installed. Anybody who wants accelerated TC can
download/install it. OVS can be completely unaware of this.
Thanks, but that's what I called an unreliable solution...
quoted
ovs-vswitchd? If so, it even does not need to monitor TC. It can
implement XDP offload directly.
(However I prefer kernel solution. Please refer to "About alternative
userland (ovs-vswitchd etc.) implementation" section in the cover letter.)
Also such a TC monitoring solution easily can be out-of-sync with real TC
behavior as TC filter/flower is being heavily developed and changed,
e.g. introduction of TC block, support multiple masks with the same pref, etc.
I'm not sure such an unreliable solution have much value.
This same issue applies to the in-kernel implementation, isn't it?
What happens if somebody sends patches for a new flower feature but
doesn't add appropriate xdp support? Do we reject them?
Why can we accept a patch which breaks other in-kernel subsystem...
Such patches can be applied accidentally but we are supposed to fix such
problems in -rc phase, aren't we?
Toshiaki Makita
From: Jakub Kicinski <hidden> Date: 2019-08-19 18:15:57
On Sat, 17 Aug 2019 23:01:59 +0900, Toshiaki Makita wrote:
On 19/08/17 (土) 3:52:24, Jakub Kicinski wrote:
quoted
On Fri, 16 Aug 2019 10:28:10 +0900, Toshiaki Makita wrote:
quoted
On 2019/08/16 4:22, Jakub Kicinski wrote:
quoted
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
I don't think it's loosely coupled. Emulating TC behavior in userspace
is not so easy.
Think about recent multi-mask support in flower. Previously userspace could
assume there is one mask and hash table for each preference in TC. After the
change TC accepts different masks with the same pref. Such a change tends to
break userspace emulation. It may ignore masks passed from flow insertion
and use the mask remembered when the first flow of the pref is inserted. It
may override the mask of all existing flows with the pref. It may fail to
insert such flows. Any of them would result in unexpected wrong datapath
handling which is critical.
I think such an emulation layer needs to be updated in sync with TC.
Oh, so you're saying that if xdp_flow is merged all patches to
cls_flower and netfilter which affect flow offload will be required
to update xdp_flow as well?
Hmm... you are saying that we are allowed to break other in-kernel
subsystem by some change? Sounds strange...
No I'm not saying that, please don't put words in my mouth.
I'm asking you if that's your intention.
Having an implementation nor support a feature of another implementation
and degrade gracefully to the slower one is not necessarily breakage.
We need to make a concious decision here, hence the clarifying question.
quoted
That's a question of policy. Technically the implementation in user
space is equivalent.
The advantage of user space implementation is that you can add more
to it and explore use cases which do not fit in the flow offload API,
but are trivial for BPF. Not to mention the obvious advantage of
decoupling the upgrade path.
I understand the advantage, but I can't trust such a third-party kernel
emulation solution for this kind of thing which handles critical data path.
That's a strange argument to make. All production data path BPF today
comes from user space.
quoted
Personally I'm not happy with the way this patch set messes with the
flow infrastructure. You should use the indirect callback
infrastructure instead, and that way you can build the whole thing
touching none of the flow offload core.
I don't want to mess up the core flow infrastructure either. I'm all
ears about less invasive ways. Using indirect callback sounds like a
good idea. Will give it a try. Many thanks.
On 19/08/20 (火) 3:15:46, Jakub Kicinski wrote:
I'm on vacation and replying slowly. Sorry for any inconvenience.
On Sat, 17 Aug 2019 23:01:59 +0900, Toshiaki Makita wrote:
quoted
On 19/08/17 (土) 3:52:24, Jakub Kicinski wrote:
quoted
On Fri, 16 Aug 2019 10:28:10 +0900, Toshiaki Makita wrote:
quoted
On 2019/08/16 4:22, Jakub Kicinski wrote:
quoted
There's a certain allure in bringing the in-kernel BPF translation
infrastructure forward. OTOH from system architecture perspective IMHO
it does seem like a task best handed in user space. bpfilter can replace
iptables completely, here we're looking at an acceleration relatively
loosely coupled with flower.
I don't think it's loosely coupled. Emulating TC behavior in userspace
is not so easy.
Think about recent multi-mask support in flower. Previously userspace could
assume there is one mask and hash table for each preference in TC. After the
change TC accepts different masks with the same pref. Such a change tends to
break userspace emulation. It may ignore masks passed from flow insertion
and use the mask remembered when the first flow of the pref is inserted. It
may override the mask of all existing flows with the pref. It may fail to
insert such flows. Any of them would result in unexpected wrong datapath
handling which is critical.
I think such an emulation layer needs to be updated in sync with TC.
Oh, so you're saying that if xdp_flow is merged all patches to
cls_flower and netfilter which affect flow offload will be required
to update xdp_flow as well?
Hmm... you are saying that we are allowed to break other in-kernel
subsystem by some change? Sounds strange...
No I'm not saying that, please don't put words in my mouth.
If we ignore xdp_flow when modifying something which affects flow
offload, that may cause breakage. I showed such an example using
multi-mask support. So I just wondered what you mean and guessed you
think we can break other subsystem in some situation.
I admit I should not have used the wording "you are saying...?". If it
was not unpleasant to you I'm sorry about that. But I think you should
not use it as well. I did not say "cls_flower and netfilter which affect
flow offload will be required to update xdp_flow". I guess most patches
which affect flow offload core will not break xdp_flow. In some cases
breakage may happen. In that case we need to fix xdp_flow as well.
I'm asking you if that's your intention.
Having an implementation nor support a feature of another implementation
and degrade gracefully to the slower one is not necessarily breakage.
We need to make a concious decision here, hence the clarifying question.
As I described above, breakage can happen in some case, and if the patch
breaks xdp_flow I think we need to fix xdp_flow at the same time. If
xdp_flow does not support newly added features but it works for existing
ones, it is OK. In the first place not all features can be offloaded to
xdp_flow. I think this is the same as HW-offload.
quoted
quoted
That's a question of policy. Technically the implementation in user
space is equivalent.
The advantage of user space implementation is that you can add more
to it and explore use cases which do not fit in the flow offload API,
but are trivial for BPF. Not to mention the obvious advantage of
decoupling the upgrade path.
I understand the advantage, but I can't trust such a third-party kernel
emulation solution for this kind of thing which handles critical data path.
That's a strange argument to make. All production data path BPF today
comes from user space.
Probably my explanation was not sufficient. What I'm concerned about is
that this needs to emulate kernel behavior, and it is difficult.
I don't think userspace-defined datapath itself is not reliable, nor
eBPF ecosystem.
Toshiaki Makita
From: Jakub Kicinski <hidden> Date: 2019-08-21 18:38:26
On Wed, 21 Aug 2019 17:49:33 +0900, Toshiaki Makita wrote:
quoted
Having an implementation nor support a feature of another implementation
and degrade gracefully to the slower one is not necessarily breakage.
We need to make a concious decision here, hence the clarifying question.
As I described above, breakage can happen in some case, and if the patch
breaks xdp_flow I think we need to fix xdp_flow at the same time. If
xdp_flow does not support newly added features but it works for existing
ones, it is OK. In the first place not all features can be offloaded to
xdp_flow. I think this is the same as HW-offload.
I see, that sounds reasonable, yes. Thanks for clarifying.