From patchwork Tue May 31 00:46:13 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 1637175 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by bilbo.ozlabs.org (Postfix) with ESMTPS id 4LBtrk5gcZz9s1l for ; Tue, 31 May 2022 10:46:46 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id E4B9F82FCE; Tue, 31 May 2022 00:46:44 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id yzX-RQ2L4tpf; Tue, 31 May 2022 00:46:44 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp1.osuosl.org (Postfix) with ESMTPS id 191A482DE6; Tue, 31 May 2022 00:46:43 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id E0184C0032; Tue, 31 May 2022 00:46:42 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id AAF19C002D for ; Tue, 31 May 2022 00:46:41 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 5320D41C30 for ; Tue, 31 May 2022 00:46:32 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id SgCxoPvlN7m0 for ; Tue, 31 May 2022 00:46:31 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 Received: from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net [217.70.183.198]) by smtp4.osuosl.org (Postfix) with ESMTPS id E3F3341C2B for ; Tue, 31 May 2022 00:46:30 +0000 (UTC) Received: (Authenticated sender: numans@ovn.org) by mail.gandi.net (Postfix) with ESMTPSA id 1B648C0006; Tue, 31 May 2022 00:46:26 +0000 (UTC) From: numans@ovn.org To: dev@openvswitch.org Date: Mon, 30 May 2022 20:46:13 -0400 Message-Id: <20220531004613.3873477-1-numans@ovn.org> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20220531004237.3872754-1-numans@ovn.org> References: <20220531004237.3872754-1-numans@ovn.org> MIME-Version: 1.0 Subject: [ovs-dev] [RFC ovn 1/2] RFC: Add basic xdp/eBPF support in OVN. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Numan Siddique This patch just adds an xdp program and the support in the Makefile to compile it. A separate target is added in the Makefile. To compile, user has to run: 'make bpf'. Signed-off-by: Numan Siddique --- Makefile.am | 6 +++++- bpf/.gitignore | 5 +++++ bpf/automake.mk | 23 +++++++++++++++++++++++ bpf/ovn_xdp.c | 11 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 bpf/.gitignore create mode 100644 bpf/automake.mk create mode 100644 bpf/ovn_xdp.c diff --git a/Makefile.am b/Makefile.am index 3b0df83938..518be9c101 100644 --- a/Makefile.am +++ b/Makefile.am @@ -120,6 +120,7 @@ dist_pkgdata_SCRIPTS = dist_sbin_SCRIPTS = dist_scripts_SCRIPTS = dist_scripts_DATA = +dist_bpf_DATA = EXTRA_PROGRAMS = INSTALL_DATA_LOCAL = UNINSTALL_LOCAL = @@ -139,6 +140,7 @@ sbin_SCRIPTS = scripts_SCRIPTS = completion_SCRIPTS = scripts_DATA = +bpf_DATA = SUFFIXES = check_DATA = check_SCRIPTS = @@ -152,6 +154,7 @@ endif scriptsdir = $(pkgdatadir)/scripts completiondir = $(sysconfdir)/bash_completion.d pkgconfigdir = $(libdir)/pkgconfig +bpfdir = $(pkgdatadir)/bpf # This ensures that files added to EXTRA_DIST are always distributed, # even if they are inside an Automake if...endif conditional block that is @@ -255,7 +258,7 @@ config-h-check: @cd $(srcdir); \ if test -e .git && (git --version) >/dev/null 2>&1 && \ git --no-pager grep -L '#include ' `git ls-files | grep -v $(submodules) | grep '\.c$$' | \ - grep -vE '^python'`; \ + grep -vE '^bpf|^python'`; \ then \ echo "See above for list of violations of the rule that"; \ echo "every C source file must #include ."; \ @@ -493,6 +496,7 @@ include debian/automake.mk include lib/ovsdb_automake.mk include rhel/automake.mk include tutorial/automake.mk +include bpf/automake.mk include controller/automake.mk include controller-vtep/automake.mk include northd/automake.mk diff --git a/bpf/.gitignore b/bpf/.gitignore new file mode 100644 index 0000000000..b0f4f8eaff --- /dev/null +++ b/bpf/.gitignore @@ -0,0 +1,5 @@ +/Makefile +/Makefile.in +*.o +/distfiles + diff --git a/bpf/automake.mk b/bpf/automake.mk new file mode 100644 index 0000000000..7c44682335 --- /dev/null +++ b/bpf/automake.mk @@ -0,0 +1,23 @@ +bpf_sources = bpf/ovn_xdp.c +bpf_headers = +bpf_extra = + +dist_sources = $(bpf_sources) +dist_headers = $(bpf_headers) +build_sources = $(dist_sources) +build_headers = $(dist_headers) +build_objects = $(patsubst %.c,%.o,$(build_sources)) + +LLC ?= llc +CLANG ?= clang + +bpf: $(build_objects) +bpf/ovn_xdp.o: $(bpf_sources) $(bpf_headers) + $(MKDIR_P) $(dir $@) + @which $(CLANG) >/dev/null 2>&1 || \ + (echo "Unable to find clang, Install clang (>=3.7) package"; exit 1) + $(AM_V_CC) $(CLANG) -O3 -c $< -o - -emit-llvm | \ + $(LLC) -march=bpf - -filetype=obj -o $@ + + +EXTRA_DIST += $(dist_sources) $(dist_headers) $(bpf_extra) diff --git a/bpf/ovn_xdp.c b/bpf/ovn_xdp.c new file mode 100644 index 0000000000..9515a1d1bf --- /dev/null +++ b/bpf/ovn_xdp.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include + +SEC("xdp") +int xdp_ovn_vif(struct xdp_md *xdp) +{ + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; From patchwork Tue May 31 00:46:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 1637176 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=) Received: from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by bilbo.ozlabs.org (Postfix) with ESMTPS id 4LBts20TZCz9s1l for ; Tue, 31 May 2022 10:47:01 +1000 (AEST) Received: from localhost (localhost [127.0.0.1]) by smtp3.osuosl.org (Postfix) with ESMTP id 032D261259; Tue, 31 May 2022 00:47:00 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp3.osuosl.org ([127.0.0.1]) by localhost (smtp3.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id CB86wo2_5UL0; Tue, 31 May 2022 00:46:58 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [140.211.9.56]) by smtp3.osuosl.org (Postfix) with ESMTPS id 5AC946125A; Tue, 31 May 2022 00:46:57 +0000 (UTC) Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id 2FD27C002D; Tue, 31 May 2022 00:46:57 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) by lists.linuxfoundation.org (Postfix) with ESMTP id 302BCC0032 for ; Tue, 31 May 2022 00:46:55 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 0C8C48317B for ; Tue, 31 May 2022 00:46:52 +0000 (UTC) X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 42kvGNWu9Qv3 for ; Tue, 31 May 2022 00:46:50 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 Received: from relay4-d.mail.gandi.net (relay4-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::224]) by smtp1.osuosl.org (Postfix) with ESMTPS id EE1C883F12 for ; Tue, 31 May 2022 00:46:49 +0000 (UTC) Received: (Authenticated sender: numans@ovn.org) by mail.gandi.net (Postfix) with ESMTPSA id 3BB23E0002; Tue, 31 May 2022 00:46:46 +0000 (UTC) From: numans@ovn.org To: dev@openvswitch.org Date: Mon, 30 May 2022 20:46:36 -0400 Message-Id: <20220531004636.3873535-1-numans@ovn.org> X-Mailer: git-send-email 2.35.3 In-Reply-To: <20220531004237.3872754-1-numans@ovn.org> References: <20220531004237.3872754-1-numans@ovn.org> MIME-Version: 1.0 Subject: [ovs-dev] [RFC ovn 2/2] RFC: ovn-controller: Attach XDP progs to the VIFs of the logical ports. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Numan Siddique This patch attaches and detaches xdp programs by ovn-controller when it claims or releases a logical port. XDP program - ovn_xdp.c added in this patch implements basic port security and drops any packet if the port security check fails. There are still few TODOs in the port security checks. Like - Make ovn xdp configurable. - Removing the ingress Openflow rules from table 73 and 74 if ovn xdp is enabled. - Add IPv6 support. - Enhance the port security xdp program for ARP/IPv6 ND checks. This patch adds a basic XDP support in OVN and in future we can leverage eBPF/XDP features. In order to attach and detach xdp programs, libxdp [1] and libbpf is used. To test it out locally, please install libxdp-devel and libbpf-devel and the compile OVN first and then compile ovn_xdp by running "make bpf". Copy ovn_xdp.o to either /usr/share/ovn/ or /usr/local/share/ovn/ [1] - https://github.com/xdp-project/xdp-tools/tree/master/lib/libxdp Signed-off-by: Numan Siddique --- bpf/ovn_xdp.c | 145 ++++++++++++++ configure.ac | 2 + controller/automake.mk | 4 +- controller/binding.c | 45 +++-- controller/binding.h | 7 + controller/ovn-controller.c | 79 +++++++- controller/xdp.c | 389 ++++++++++++++++++++++++++++++++++++ controller/xdp.h | 41 ++++ m4/ovn.m4 | 20 ++ tests/automake.mk | 1 + 10 files changed, 709 insertions(+), 24 deletions(-) create mode 100644 controller/xdp.c create mode 100644 controller/xdp.h diff --git a/bpf/ovn_xdp.c b/bpf/ovn_xdp.c index 9515a1d1bf..d0c91602de 100644 --- a/bpf/ovn_xdp.c +++ b/bpf/ovn_xdp.c @@ -1,10 +1,155 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include #include +#include +#include +#include +#include +#include #include +#include +#include + +#define ETH_ALEN 6 +#define VIF_ADDR_FLAG_ETH_SET 0x01 +#define VIF_ADDR_FLAG_IP_SET 0x02 + +#define ETH_P_IP 0x0800 + +#define OVN_CHECK_PORT_SEC_MAC 0x00000001 +#define OVN_CHECK_PORT_SEC_MAC_IP 0x00000002 + + +struct bpf_map_def SEC("maps") ovn_vif_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u32), + .max_entries = 1, +}; + +/* Map for mac port security table*/ +struct bpf_map_def SEC("maps") port_sec_mac_table = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(__u64), + .value_size = sizeof(__u8), + .max_entries = 4, +}; + +struct lpm_mac_ip_key { + struct bpf_lpm_trie_key trie_key; + __u8 data[10]; /* 6 bytes for mac, 4 bytes for ip */ +}; + +/* Map for mac + ip4 port security table*/ +struct bpf_map_def SEC("maps") port_sec_mac_ip_table = { + .type = BPF_MAP_TYPE_LPM_TRIE, + .key_size = sizeof(struct lpm_mac_ip_key), + .value_size = sizeof(__u8), + .max_entries = 4, + .map_flags = BPF_F_NO_PREALLOC, +}; + +static inline __u64 +eth_addr_to_uint64(unsigned char ethaddr[ETH_ALEN]) { + return ((uint64_t) ethaddr[0] << 40) | + ((uint64_t) ethaddr[1] << 32) | + ((uint64_t) ethaddr[2] << 24) | + ((uint64_t) ethaddr[3] << 16) | + ((uint64_t) ethaddr[4] << 8) | + ((uint64_t) ethaddr[5] << 0); +} SEC("xdp") int xdp_ovn_vif(struct xdp_md *xdp) { + __u32 key = 0; + + void *data_end = (void *)(long)xdp->data_end; + void *data = (void *)(long)xdp->data; + struct ethhdr *eth = data; + + int ret = XDP_PASS; + + if (data + sizeof(*eth) > data_end) { + return XDP_DROP; + } + + __u32 *vif_flags = bpf_map_lookup_elem(&ovn_vif_map, &key); + if (!vif_flags || !(*vif_flags)) { + /* No checks to be done. */ + return XDP_PASS; + } + + if (eth->h_source[0] & 0x01) { + /* Multicast eth src. Drop it. */ + return XDP_DROP; + } + + if (eth->h_proto == htons(ETH_P_ARP)) { + __u8 ps_check_pass = 0; + __u64 src_mac; + + src_mac = eth_addr_to_uint64(eth->h_source); + if (bpf_map_lookup_elem(&port_sec_mac_table, &src_mac)) { + ps_check_pass = 1; + } + + if (!ps_check_pass) { + return XDP_DROP; + } + + /* TODO. Inspect the arp header and check if arp.sha is allowed or not. */ + return XDP_PASS; + } else if (eth->h_proto == htons(ETH_P_IP)) { + __u8 ps_check_pass = 0; + if (*vif_flags & OVN_CHECK_PORT_SEC_MAC_IP) { + struct iphdr *iph; + struct lpm_mac_ip_key key = { + .trie_key = { + .prefixlen = 80, + }, + }; + + iph = (struct iphdr *)(eth + 1); + if ((void *)(iph + 1) > data_end) { + return XDP_DROP; + } + + key.data[0] = eth->h_source[0]; + key.data[1] = eth->h_source[1]; + key.data[2] = eth->h_source[2]; + key.data[3] = eth->h_source[3]; + key.data[4] = eth->h_source[4]; + key.data[5] = eth->h_source[5]; + + key.data[6] = iph->saddr & 0xff; + key.data[7] = (iph->saddr >> 8) & 0xff; + key.data[8] = (iph->saddr >> 16) & 0xff; + key.data[9] = (iph->saddr >> 24) & 0xff; + + __u8 *v; + v = bpf_map_lookup_elem(&port_sec_mac_ip_table, &key); + if (v && *v) { + ps_check_pass = 1; + } + } + + if (!ps_check_pass && (*vif_flags & OVN_CHECK_PORT_SEC_MAC)) { + __u64 src_mac; + __u8 *v; + + src_mac = eth_addr_to_uint64(eth->h_source); + v = bpf_map_lookup_elem(&port_sec_mac_table, &src_mac); + if (v && *v) { + ps_check_pass = 1; + } + } + + if (!ps_check_pass) { + return XDP_DROP; + } + } + return XDP_PASS; } diff --git a/configure.ac b/configure.ac index 8665e71f41..8a77b3b31b 100644 --- a/configure.ac +++ b/configure.ac @@ -174,6 +174,8 @@ OVS_CHECK_PRAGMA_MESSAGE OVN_CHECK_OVS OVN_CHECK_VIF_PLUG_PROVIDER OVN_ENABLE_VIF_PLUG +OVN_CHECK_BPF +OVN_CHECK_XDP OVS_CTAGS_IDENTIFIERS AC_SUBST([OVS_CFLAGS]) AC_SUBST([OVS_LDFLAGS]) diff --git a/controller/automake.mk b/controller/automake.mk index c2ab1bbe6a..c5354ea0ab 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \ controller/ovsport.h \ controller/ovsport.c \ controller/vif-plug.h \ - controller/vif-plug.c + controller/vif-plug.c \ + controller/xdp.c \ + controller/xdp.h controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la man_MANS += controller/ovn-controller.8 diff --git a/controller/binding.c b/controller/binding.c index a900c9a509..870d09a3f1 100644 --- a/controller/binding.c +++ b/controller/binding.c @@ -36,6 +36,7 @@ #include "lport.h" #include "ovn-controller.h" #include "patch.h" +#include "xdp.h" VLOG_DEFINE_THIS_MODULE(binding); @@ -77,6 +78,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl) ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status); ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_status); + ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ifindex); ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos); ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type); @@ -539,8 +541,6 @@ static struct binding_lport *local_binding_add_lport( struct local_binding *, const struct sbrec_port_binding *, enum en_lport_type); -static struct binding_lport *local_binding_get_primary_lport( - struct local_binding *); static struct binding_lport *local_binding_get_first_lport( struct local_binding *lbinding); static struct binding_lport *local_binding_get_primary_or_localport_lport( @@ -807,6 +807,27 @@ binding_dump_local_bindings(struct local_binding_data *lbinding_data, free(nodes); } +/* Returns the primary binding lport if present in lbinding's + * binding lports list. A binding lport is considered primary + * if binding lport's type is LP_VIF and the name matches + * with the 'lbinding'. + */ +struct binding_lport * +local_binding_get_primary_lport(struct local_binding *lbinding) +{ + if (!lbinding) { + return NULL; + } + + struct binding_lport *b_lport = local_binding_get_first_lport(lbinding); + if (b_lport && b_lport->type == LP_VIF && + !strcmp(lbinding->name, b_lport->name)) { + return b_lport; + } + + return NULL; +} + static bool is_lport_vif(const struct sbrec_port_binding *pb) { @@ -2636,26 +2657,6 @@ local_binding_get_first_lport(struct local_binding *lbinding) return NULL; } -/* Returns the primary binding lport if present in lbinding's - * binding lports list. A binding lport is considered primary - * if binding lport's type is LP_VIF and the name matches - * with the 'lbinding'. - */ -static struct binding_lport * -local_binding_get_primary_lport(struct local_binding *lbinding) -{ - if (!lbinding) { - return NULL; - } - - struct binding_lport *b_lport = local_binding_get_first_lport(lbinding); - if (b_lport && b_lport->type == LP_VIF && - !strcmp(lbinding->name, b_lport->name)) { - return b_lport; - } - - return NULL; -} static struct binding_lport * local_binding_get_primary_or_localport_lport(struct local_binding *lbinding) diff --git a/controller/binding.h b/controller/binding.h index a3858d78bc..7eeed68a99 100644 --- a/controller/binding.h +++ b/controller/binding.h @@ -214,4 +214,11 @@ struct binding_lport { size_t n_port_security; }; +/* Returns the primary binding lport if present in lbinding's + * binding lports list. A binding lport is considered primary + * if binding lport's type is LP_VIF and the name matches + * with the 'lbinding'. + */ +struct binding_lport *local_binding_get_primary_lport(struct local_binding *); + #endif /* controller/binding.h */ diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index f0c2e23d85..a20d6a4701 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -76,6 +76,7 @@ #include "stopwatch.h" #include "lib/inc-proc-eng.h" #include "hmapx.h" +#include "xdp.h" VLOG_DEFINE_THIS_MODULE(main); @@ -1417,7 +1418,6 @@ en_runtime_data_run(struct engine_node *node, void *data) } binding_run(&b_ctx_in, &b_ctx_out); - engine_set_node_state(node, EN_UPDATED); } @@ -3207,6 +3207,77 @@ flow_output_lflow_output_handler(struct engine_node *node, return true; } +struct ed_type_xdp { + struct shash xdp_lports; +}; + +static void * +en_xdp_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ed_type_xdp *xdp = xzalloc(sizeof *xdp); + ovn_xdp_init(&xdp->xdp_lports); + return xdp; +} + +static void +en_xdp_cleanup(void *data) +{ + struct ed_type_xdp *xdp = data; + ovn_xdp_destroy(&xdp->xdp_lports); +} + +static void +en_xdp_run(struct engine_node *node, void *data) +{ + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + struct ed_type_xdp *xdp_data = data; + ovn_xdp_run(&rt_data->lbinding_data.bindings, &rt_data->local_lports, + &xdp_data->xdp_lports); + engine_set_node_state(node, EN_UPDATED); +} + +static bool +xdp_runtime_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + /* There is no tracked data. Fall back to full recompute of xdp. */ + if (!rt_data->tracked) { + return false; + } + + struct ed_type_xdp *xdp_data = data; + + struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings; + struct tracked_datapath *tdp; + HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) { + if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) { + /* Fall back to full recompute when a local datapath + * is added or deleted. */ + return false; + } + + struct shash_node *shash_node; + SHASH_FOR_EACH (shash_node, &tdp->lports) { + struct tracked_lport *lport = shash_node->data; + bool removed = + lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false; + if (!ovn_xdp_handle_lport( + lport->pb, removed, &rt_data->lbinding_data.bindings, + &xdp_data->xdp_lports)) { + return false; + } + } + } + + engine_set_node_state(node, EN_UPDATED); + return true; +} + struct ovn_controller_exit_args { bool *exiting; bool *restart; @@ -3459,6 +3530,7 @@ main(int argc, char *argv[]) ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets"); ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups"); ENGINE_NODE(northd_internal_version, "northd_internal_version"); + ENGINE_NODE(xdp, "xdp"); #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR); SB_NODES @@ -3601,6 +3673,11 @@ main(int argc, char *argv[]) flow_output_lflow_output_handler); engine_add_input(&en_flow_output, &en_pflow_output, flow_output_pflow_output_handler); + engine_add_input(&en_flow_output, &en_xdp, + engine_noop_handler); + + engine_add_input(&en_xdp, &en_runtime_data, + xdp_runtime_data_handler); struct engine_arg engine_arg = { .sb_idl = ovnsb_idl_loop.idl, diff --git a/controller/xdp.c b/controller/xdp.c new file mode 100644 index 0000000000..c8201cacd9 --- /dev/null +++ b/controller/xdp.c @@ -0,0 +1,389 @@ +/* Copyright (c) 2022 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +/* library headers */ +#include +#include +#include + +/* OVS includes. */ +#include "lib/vswitch-idl.h" +#include "openvswitch/vlog.h" + +/* OVN includes. */ +#include "binding.h" +#include "lib/ovn-dirs.h" +#include "lib/ovn-util.h" +#include "lib/ovn-sb-idl.h" +#include "xdp.h" + +VLOG_DEFINE_THIS_MODULE(xdp); + +#define MAX_ERRNO 4095 +#define STRERR_BUFSIZE 1024 + +#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO) + +struct lpm_mac_ip_key { + struct bpf_lpm_trie_key trie_key; + __u8 data[10]; /* 6 bytes for mac, 4 bytes for ip */ +}; + +struct xdp_lport { + int ifindex; + bool bpf_attached; + bool bpf_attach_failed; + + struct xdp_program *prog; + int vif_map_fd; + int port_sec_mac_map_fd; + int port_sec_mac_ip_map_fd; + + struct lport_addresses *ps_addrs; /* Port security addresses. */ + unsigned int n_ps_addrs; +}; + +static void xdp_handle_lport(const char *lport, struct shash *local_bindings, + struct shash *xdp_lports); +static struct xdp_lport *xdp_lport_find(struct shash *xdp_lports, + const char *lport); +static void xdp_lport_attach_prog(struct xdp_lport *); +static void xdp_lport_update_maps(struct xdp_lport *, bool allow); +static void xdp_lport_detach_prog(struct xdp_lport *); +static void xdp_lport_update_sb_bpf(struct xdp_lport *, + const struct sbrec_port_binding *); +static struct xdp_lport *xdp_lport_create( + struct shash *xdp_lports, const struct binding_lport *); +static void xdp_lport_destroy(struct xdp_lport *); + + +void +ovn_xdp_init(struct shash *xdp_lports) +{ + shash_init(xdp_lports); +} + +void +ovn_xdp_run(struct shash *local_bindings, struct sset *local_lports, + struct shash *xdp_lports) +{ + struct shash_node *node, *next; + SHASH_FOR_EACH_SAFE (node, next, xdp_lports) { + if (!sset_contains(local_lports, node->name)) { + struct xdp_lport *xdp_lport = node->data; + shash_delete(xdp_lports, node); + xdp_lport_destroy(xdp_lport); + } + } + + const char *lport; + SSET_FOR_EACH (lport, local_lports) { + xdp_handle_lport(lport, local_bindings, xdp_lports); + } +} + +bool +ovn_xdp_handle_lport(const struct sbrec_port_binding *pb, bool removed, + struct shash *local_bindings, struct shash *xdp_lports) +{ + if (removed) { + struct xdp_lport *xdp_lport = + shash_find_and_delete(xdp_lports, pb->logical_port); + if (xdp_lport) { + xdp_lport_destroy(xdp_lport); + } + } else { + xdp_handle_lport(pb->logical_port, local_bindings, xdp_lports); + } + + return true; +} + +void +ovn_xdp_destroy(struct shash *xdp_lports) +{ + struct shash_node *node, *next; + SHASH_FOR_EACH_SAFE (node, next, xdp_lports) { + struct xdp_lport *xdp_lport = node->data; + shash_delete(xdp_lports, node); + xdp_lport_destroy(xdp_lport); + } +} + +/* static functions. */ + +static void +xdp_handle_lport(const char *lport, struct shash *local_bindings, + struct shash *xdp_lports) +{ + struct local_binding *lbinding = local_binding_find(local_bindings, lport); + if (!lbinding) { + return; + } + + struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); + if (!b_lport) { + return; + } + + int ifindex = lbinding->iface->n_ifindex ? lbinding->iface->ifindex[0] + : -1; + struct xdp_lport *xdp_lport = xdp_lport_find(xdp_lports, lport); + if (xdp_lport && ifindex < 1) { + /* The interface has been deleted. */ + shash_find_and_delete(xdp_lports, lport); + xdp_lport_destroy(xdp_lport); + return; + } + + if (ifindex < 1) { + return; + } + + if (!xdp_lport) { + xdp_lport = xdp_lport_create(xdp_lports, b_lport); + if (!xdp_lport) { + return; + } + } else { + if (xdp_lport->bpf_attached) { + xdp_lport_update_maps(xdp_lport, false); + } + xdp_lport_update_sb_bpf(xdp_lport, b_lport->pb); + } + + xdp_lport_attach_prog(xdp_lport); + if (xdp_lport->bpf_attached) { + xdp_lport_update_maps(xdp_lport, true); + } +} + +static void +xdp_lport_attach_prog(struct xdp_lport *xdp_lport) +{ + if (xdp_lport->bpf_attached || xdp_lport->bpf_attach_failed) { + return; + } + + /* Detach any xdp program if it is already attached. */ + xdp_lport_detach_prog(xdp_lport); + + char *ovn_xdp_file = xasprintf("%s/ovn_xdp.o", ovn_pkgdatadir()); + struct xdp_program *prog = + xdp_program__open_file(ovn_xdp_file, "xdp", NULL); + free(ovn_xdp_file); + + int err = libxdp_get_error(prog); + char errmsg[STRERR_BUFSIZE]; + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + VLOG_ERR("ovn_xdp_attach: failed to open xdp program: %s", errmsg); + return; + } + + err = xdp_program__attach(prog, xdp_lport->ifindex, XDP_MODE_NATIVE, 0); + + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + VLOG_ERR("ovn_xdp_attach: failed to attach xdp program: %s", errmsg); + xdp_lport->bpf_attach_failed = true; + return; + } + + struct bpf_object *obj = xdp_program__bpf_obj(prog); + struct bpf_map *map = bpf_object__find_map_by_name(obj, "ovn_vif_map"); + + xdp_lport->vif_map_fd = map ? bpf_map__fd(map) : -1; + + map = bpf_object__find_map_by_name(obj, "port_sec_mac_table"); + xdp_lport->port_sec_mac_map_fd = map ? bpf_map__fd(map) : -1; + + map = bpf_object__find_map_by_name(obj, "port_sec_mac_ip_table"); + xdp_lport->port_sec_mac_ip_map_fd = map ? bpf_map__fd(map) : -1; + + xdp_lport->bpf_attached = true; + xdp_lport->bpf_attach_failed = false; + xdp_lport->prog = prog; +} + + +static void +xdp_lport_update_maps(struct xdp_lport *xdp_lport, bool allow) +{ + if (!xdp_lport->bpf_attached || xdp_lport->vif_map_fd < 1 || + xdp_lport->port_sec_mac_map_fd < 1 || + xdp_lport->port_sec_mac_ip_map_fd < 1) { + return; + } + + uint32_t ovn_xdp_checks = 0; + for (size_t i = 0 ; i < xdp_lport->n_ps_addrs; i++) { + uint8_t ps_only_l2 = 1; + struct lport_addresses *ps = &xdp_lport->ps_addrs[i]; + + if (ps->n_ipv4_addrs) { + struct lpm_mac_ip_key key; + key.data[0] = ps->ea.ea[0]; + key.data[1] = ps->ea.ea[1]; + key.data[2] = ps->ea.ea[2]; + key.data[3] = ps->ea.ea[3]; + key.data[4] = ps->ea.ea[4]; + key.data[5] = ps->ea.ea[5]; + + for (size_t j = 0; j < ps->n_ipv4_addrs; j++) { + key.trie_key.prefixlen = ps->ipv4_addrs[j].plen + 48; + + uint32_t addr = (OVS_FORCE uint32_t)ps->ipv4_addrs[j].addr; + key.data[6] = addr & 0xff; + key.data[7] = (addr >> 8) & 0xff; + key.data[8] = (addr >> 16) & 0xff; + key.data[9] = (addr >> 24) & 0xff; + + uint8_t v = allow ? 1 : 0; + if (bpf_map_update_elem(xdp_lport->port_sec_mac_ip_map_fd, + &key, &v, 0) < 0) { + VLOG_ERR("ovn_xdp_attach: failed to update port_sec_mac_ip_table"); + return; + } + ovn_xdp_checks |= OVN_CHECK_PORT_SEC_MAC_IP; + } + ps_only_l2 = 0; + } + + ovn_xdp_checks |= OVN_CHECK_PORT_SEC_MAC; + uint64_t mac = eth_addr_to_uint64(ps->ea); + + if (bpf_map_update_elem(xdp_lport->port_sec_mac_map_fd, &mac, + &ps_only_l2, 0) < 0) { + VLOG_ERR("ovn_xdp_attach: failed to update port_sec_mac_table"); + return; + } + } + + int key = 0; + if (bpf_map_update_elem(xdp_lport->vif_map_fd, &key, + &ovn_xdp_checks, 0) < 0) { + VLOG_ERR("ovn_xdp_attach: failed to update ovn_check_map"); + } +} + +static void +xdp_lport_detach_prog(struct xdp_lport *xdp_lport) +{ + if (xdp_lport->bpf_attached && xdp_lport->prog) { + if (xdp_lport->vif_map_fd > 0) { + close(xdp_lport->vif_map_fd); + xdp_lport->vif_map_fd = -1; + } + + if (xdp_lport->port_sec_mac_map_fd > 0) { + close(xdp_lport->port_sec_mac_map_fd); + xdp_lport->port_sec_mac_map_fd = -1; + } + + if (xdp_lport->port_sec_mac_ip_map_fd > 0) { + close(xdp_lport->port_sec_mac_ip_map_fd); + xdp_lport->port_sec_mac_ip_map_fd = -1; + } + + xdp_program__detach(xdp_lport->prog, xdp_lport->ifindex, XDP_MODE_NATIVE, 0); + xdp_program__close(xdp_lport->prog); + xdp_lport->bpf_attached = false; + xdp_lport->bpf_attach_failed = false; + xdp_lport->prog = NULL; + } else { + struct xdp_multiprog *mp = NULL; + mp = xdp_multiprog__get_from_ifindex(xdp_lport->ifindex); + + if (!mp || IS_ERR_VALUE((unsigned long) mp)) { + return; + } + + if (xdp_multiprog__detach(mp)) { + VLOG_ERR("ovn_xdp_detach: failed to detach xdp program"); + return; + } + + xdp_multiprog__close(mp); + } +} + +static struct xdp_lport * +xdp_lport_find(struct shash *xdp_lports, const char *lport) +{ + return shash_find_data(xdp_lports, lport); +} + +static void +xdp_lport_update_sb_bpf(struct xdp_lport *xdp_lp, + const struct sbrec_port_binding *sb_pb) +{ + if (xdp_lp->n_ps_addrs) { + for (size_t i = 0; i < xdp_lp->n_ps_addrs; i++) { + destroy_lport_addresses(&xdp_lp->ps_addrs[i]); + } + free(xdp_lp->ps_addrs); + xdp_lp->ps_addrs = NULL; + xdp_lp->n_ps_addrs = 0; + } + + xdp_lp->ps_addrs = + xmalloc(sizeof *xdp_lp->ps_addrs * sb_pb->n_port_security); + for (size_t i = 0; i < sb_pb->n_port_security; i++) { + if (!extract_lsp_addresses( + sb_pb->port_security[i], + &xdp_lp->ps_addrs[xdp_lp->n_ps_addrs])) { + static struct vlog_rate_limit rl + = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_INFO_RL(&rl, "invalid syntax '%s' in lport port security." + " No MAC address found", sb_pb->port_security[i]); + continue; + } + xdp_lp->n_ps_addrs++; + } +} + +static struct xdp_lport * +xdp_lport_create(struct shash *xdp_lports, const struct binding_lport *b_lport) +{ + struct xdp_lport *xdp_lport = xzalloc(sizeof(*xdp_lport)); + ovs_assert(b_lport->lbinding->iface->n_ifindex); + xdp_lport->ifindex = b_lport->lbinding->iface->ifindex[0]; + shash_add(xdp_lports, b_lport->lbinding->name, xdp_lport); + + if (b_lport->pb && b_lport->pb->n_port_security) { + xdp_lport_update_sb_bpf(xdp_lport, b_lport->pb); + } + + xdp_lport->vif_map_fd = -1; + xdp_lport->port_sec_mac_map_fd = -1; + xdp_lport->port_sec_mac_ip_map_fd = -1; + + return xdp_lport; +} + +static void +xdp_lport_destroy(struct xdp_lport *xdp_lport) +{ + xdp_lport_detach_prog(xdp_lport); + for (size_t i = 0; i < xdp_lport->n_ps_addrs; i++) { + destroy_lport_addresses(&xdp_lport->ps_addrs[i]); + } + free(xdp_lport->ps_addrs); + free(xdp_lport); +} diff --git a/controller/xdp.h b/controller/xdp.h new file mode 100644 index 0000000000..2034b70e36 --- /dev/null +++ b/controller/xdp.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2022 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVN_XDP_H +#define OVN_XDP_H 1 + +#define ETH_ALEN 6 + +struct vif_addr { + uint8_t flags; + uint8_t eth_addr[ETH_ALEN]; + ovs_be32 ip_addr; +}; + +#define OVN_CHECK_PORT_SEC_MAC 0x00000001 +#define OVN_CHECK_PORT_SEC_MAC_IP 0x00000002 + +struct sbrec_port_binding; +struct local_binding; +struct shash; + +void ovn_xdp_init(struct shash *); +void ovn_xdp_destroy(struct shash *); +void ovn_xdp_run(struct shash *local_bindings, struct sset *local_lports, + struct shash *xdp_lports); +bool ovn_xdp_handle_lport(const struct sbrec_port_binding *pb, bool removed, + struct shash *local_bindings, + struct shash *xdp_lports); +#endif diff --git a/m4/ovn.m4 b/m4/ovn.m4 index 2909914fb8..2c42fda97b 100644 --- a/m4/ovn.m4 +++ b/m4/ovn.m4 @@ -592,3 +592,23 @@ AC_DEFUN([OVS_CHECK_DDLOG_FAST_BUILD], if $ddlog_fast_build; then DDLOG_EXTRA_RUSTFLAGS="-C opt-level=z" fi]) + +dnl Checks for libbpf. +AC_DEFUN([OVN_CHECK_BPF], + [AC_CHECK_LIB(bpf, bpf_load_program, [HAVE_BPF=yes], [HAVE_BPF=no]) + if test "$HAVE_BPF" = yes; then + AC_DEFINE([HAVE_BPF], [1], [Define to 1 if bpf is detected.]) + LIBS="$LIBS -lbpf" + fi + AM_CONDITIONAL([HAVE_BPF], [test "$HAVE_BPF" = yes]) + AC_SUBST([HAVE_BPF])]) + +dnl Checks for libxdp. +AC_DEFUN([OVN_CHECK_XDP], + [AC_CHECK_LIB(xdp, xdp_program__attach, [HAVE_XDP=yes], [HAVE_XDP=no]) + if test "$HAVE_XDP" = yes; then + AC_DEFINE([HAVE_XDP], [1], [Define to 1 if xdp is detected.]) + LIBS="$LIBS -lxdp" + fi + AM_CONDITIONAL([HAVE_XDP], [test "$HAVE_XDP" = yes]) + AC_SUBST([HAVE_XDP])]) diff --git a/tests/automake.mk b/tests/automake.mk index 9733e8fa0f..6858c5a33c 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -265,6 +265,7 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ controller/ovsport.$(OBJEXT) \ controller/patch.$(OBJEXT) \ controller/vif-plug.$(OBJEXT) \ + controller/xdp.$(OBJEXT) \ northd/ipam.$(OBJEXT) # Python tests.