From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 8907711FD3; Wed, 08 Oct 2025 17:23:52 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id A2E1F11FC2; Wed, 08 Oct 2025 17:23:49 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,FREEMAIL_FROM,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-yx1-xb12b.google.com (mail-yx1-xb12b.google.com [IPv6:2607:f8b0:4864:20::b12b]) by atuin.qyliss.net (Postfix) with ESMTPS id 2EB8511FC0 for ; Wed, 08 Oct 2025 17:23:44 +0000 (UTC) Received: by mail-yx1-xb12b.google.com with SMTP id 956f58d0204a3-637dc2292cfso135793d50.3 for ; Wed, 08 Oct 2025 10:23:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1759944222; x=1760549022; darn=spectrum-os.org; h=cc:to:references:in-reply-to:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Siqjf4f9uKtE7pjPnvw/5Y+i1R0VfpXS7wlLt+Q2JFA=; b=aJxZDrfcIO9BVXH3meg6rRNgyxljlF8zbTzQ8iW8SYpiR7zzBfA/EN2pt0CWlrCdfY BOEzcZ5F52EMzwZOM5HMBNIg8yVcvCibbsYcpg+B3jlYLB+vjRB18G0SEHhG2mykW+Dz DoCPny5/xfrNqtyhP8sbOt63S/JBUdOB9wdb+K+vVtl1ynstNs4gTEuFr3Dode5HNGUb z5O6lUMtCgDOI/C3smUkwUzdduqDRLF3Q6Krpenzsvzc2xvcQftNMa0RzO1E0GYEbebo 51oEBR7f80L9nu5+Wf7Mp9ePM+Z7KWzHN3RO+upDkNYhCseKpZPhpBafzAX91C/co42P oEjg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1759944222; x=1760549022; h=cc:to:references:in-reply-to:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Siqjf4f9uKtE7pjPnvw/5Y+i1R0VfpXS7wlLt+Q2JFA=; b=P9jI76HQWZtTgZtzRpfYzjxXA0t42Dzp6WCPuS3YbvvHp3ypzvyacK9qWZYP81bk6r UY0jjWwDqMjSi5dxR1nzRWHt1h2COCQr/46N9MeNZIU92D8bp2yHu2OAbOJCeWA8nU2j yr4Abl4pFOEdDyfVGSJLstCzH/1ScJY3uTWBxqUERc9YZZ+HSUp5vZikUoEWrXKjI3ot jEDj41vhNabZBHfnl06XohuriwZyKC6OP6dJInG9C+171JCfatBcbeAKRCFht29xmoAu 0+n6rpxmnB+1Zr6H1ZSTgE28l6Hoj+9bSfvyGxlRzrVlW9aE5yZS3Bl/W64zEeXrZp0m E1PA== X-Gm-Message-State: AOJu0Yx+xs+E/8p4SIYub4Hd7F2zh10Nt5EvR19AA+PGQW8b1WCcoTpA BfzakKIa5Ifi6f7sd2Kk5AxCzXjaM7+GrvVTzJP+AGZAdbN7If+RD8zraW18GA== X-Gm-Gg: ASbGnctveAtQLUKT2fmpmQofL1z065Oz8Tr4oOM9hYY/wl31N2cqgJZ3CXAw+EeMNlJ oTVVSbUnpm4YCtJ8pNwD1oBIBjSo0M3zQJiQv1Y579ImHpTDmjw4tkUWu/DSIguToKON/gy56RP kKT0xyJXsDzCphUtCj+LNApXcHGRCRg5KsKvtcBWqTfStr5V0ACRcWNxW/WCdwSgC+KmF6Mhgk/ +oT0apG3OKVccdvglnT9OY7M+2rXlFk/jrqVZuJ8M77zxdRVLzR0a91pwAYIoMKT2Ub6r1JLb5O Yx503/TEfHdFYHq7TeTDTM2y/YZAIox8pRVoe2v8b+HpwsV0hNfmgobh1z3iUstSPue43tMBKAK wUK5qFyMT0lAGlIWqcMwOFWNwBLkWKwZzgftlZe1kipYCRjP7LH4di2qNLyKUyVXv4+2NnBHCOm 7PYb3FLjxL/c/7qJcs3dd4o7FebXsgV8hjIOBAIF9hVIj9Ez0M58UIkYLRQG9oSW9/MIc= X-Google-Smtp-Source: AGHT+IHogoGXp1UIJPVJ4uwCEZke5hVnKBMLKc4ucO1cJzAczZee8P7v/RbpVDkHf66hdX244TWJTg== X-Received: by 2002:a05:690c:d06:b0:77f:772e:6229 with SMTP id 00721157ae682-780e1521e9cmr84014887b3.1.1759944221190; Wed, 08 Oct 2025 10:23:41 -0700 (PDT) Received: from localhost.localdomain (h96-60-249-169.cncrtn.broadband.dynamic.tds.net. [96.60.249.169]) by smtp.gmail.com with UTF8SMTPSA id 00721157ae682-77fb2899174sm40077287b3.17.2025.10.08.10.23.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 08 Oct 2025 10:23:40 -0700 (PDT) From: Demi Marie Obenour Date: Wed, 08 Oct 2025 13:22:41 -0400 Subject: [PATCH v3] tools/xdp-forwarder: Do not include libc headers in eBPF programs MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20251008-fix-forwarder-build-v3-1-a93e5156fb6a@gmail.com> X-B4-Tracking: v=1; b=H4sIAOCd5mgC/33NwQ7CIAyA4VdZOIsBJox58j2MBwbd1mQbBhQ1y 95dttMOxuPftF9nEiEgRHIuZhIgYUQ/5SgPBbG9mTqg6HITwYTkjJW0xTdtfXiZ4CDQ5omDo0L rGkxtwNac5Mt7gLy2qddb7h7jw4fP9iTxdfrfS5xyqqVqKm1AKqku3WhwOFo/ktVLYm+o34bIh mqdq4BJ2/DT3liW5QvMRAGd+gAAAA== X-Change-ID: 20251003-fix-forwarder-build-2889ea9aec91 In-Reply-To: <20251006-fix-forwarder-build-v2-1-6fdd7e05cb14@gmail.com> References: <20251006-fix-forwarder-build-v2-1-6fdd7e05cb14@gmail.com> To: Spectrum OS Development X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1759944160; l=24854; i=demiobenour@gmail.com; s=20250729; h=from:subject:message-id; bh=IQ3tnY5KBEbBurzHp9T24wzrM08Sw6hl+Y94BYolO70=; b=IlYV2iKHd2TXklAcugRKCqP1RpSLx0lVLOiUckgYP6uLiGfe+HpwyRb23a1EEmx4/kniZFsqm ZnqnAyvuYY5BOkSFMcbuDhfH91Ivxu0eXN5ZcICqXA2CwfPrDKc/SPj X-Developer-Key: i=demiobenour@gmail.com; a=ed25519; pk=X57Q4/YQDj9t4SBeKaDwvXYKB6quZJVx/DE2Ly2out0= Message-ID-Hash: PVXHEGD2CRQELB4NWJLKSJ4KNJ32O2UF X-Message-ID-Hash: PVXHEGD2CRQELB4NWJLKSJ4KNJ32O2UF X-MailFrom: demiobenour@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Alyssa Ross , Demi Marie Obenour X-Mailman-Version: 3.3.9 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The build happened to work on arm64 because the glibc arm64 headers don't support multilib. On x86_64, glibc headers assume that BPF is a 32-bit platform (because __x86_64__ isn't defined) and fail to find the 32-bit headers. This is not a glibc bug. Rather, BPF programs should not be including glibc headers. Most Linux headers are not trivial to include in BPF programs. The version of the headers meant for userspace use do include glibc headers, and that isn't supported in BPF. The version meant for building kernel modules does not, but using it requires much more complicated build system. Solve this problem by only including headers intended for use in BPF programs. These headers include declarations explicitly intended for use in BPF programs, so if they do pull in libc headers that is a bug. This also allows using an unwrapped clang. Nix's wrapped clang is not suitable when a target is explicitly specified, so this is another a bug fix. This prevents vendoring the example headers, so replace them with a header that only includes common functionality common to both eBPF programs. A single struct including both Ethernet II and 802.1Q headers is used, massively simplifying the code. Signed-off-by: Demi Marie Obenour --- Changes in v3: - Fix build. - Clean up XDP programs. - Link to v2: https://lore.kernel.org/r/20251006-fix-forwarder-build-v2-1-6fdd7e05cb14@gmail.com Changes in v2: - Rewrite the programs to not use the helpers. - This doesn't build. - Link to v1: https://lore.kernel.org/r/20251003-fix-forwarder-build-v1-1-856b78ae5656@gmail.com --- To check that the program does not include libc headers, one can add #include (or any other libc header) and check that one gets an appropriate error. --- tools/default.nix | 10 +- tools/meson.options | 7 + tools/xdp-forwarder/helpers.h | 54 +++++++ tools/xdp-forwarder/meson.build | 16 +- tools/xdp-forwarder/parsing_helpers.h | 274 ---------------------------------- tools/xdp-forwarder/prog_physical.c | 49 +++--- tools/xdp-forwarder/prog_router.c | 47 ++++-- tools/xdp-forwarder/rewrite_helpers.h | 146 ------------------ 8 files changed, 142 insertions(+), 461 deletions(-) diff --git a/tools/default.nix b/tools/default.nix index 2c6846c80073e7b64fb7a19488103f6cf97a4420..8fd3491010303f981dac80cd6581614d594f993c 100644 --- a/tools/default.nix +++ b/tools/default.nix @@ -6,7 +6,7 @@ import ../lib/call-package.nix ( { src, lib, stdenv, fetchCrate, fetchurl, runCommand, buildPackages , meson, ninja, pkg-config, rustc , clang-tools, clippy, jq -, dbus +, dbus, linuxHeaders # clang 19 (current nixpkgs default) is too old to support -fwrapv-pointer , clang_21, libbpf , buildSupport ? false @@ -88,7 +88,7 @@ stdenv.mkDerivation (finalAttrs: { ++ lib.optionals (appSupport || driverSupport) [ pkg-config ] ++ lib.optionals hostSupport [ rustc ] ++ lib.optionals driverSupport [ clang_21 ]; - buildInputs = lib.optionals appSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf ]; + buildInputs = lib.optionals appSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf linuxHeaders ]; postPatch = lib.optionals hostSupport (lib.concatMapStringsSep "\n" (crate: '' mkdir -p subprojects/packagecache @@ -104,11 +104,11 @@ stdenv.mkDerivation (finalAttrs: { "-Dtests=false" "-Dunwind=false" "-Dwerror=true" + ] ++ lib.optionals driverSupport [ + "-Dclang=${clang_21.cc}/bin/clang" + "-Dlinux-headers=${linuxHeaders}/include" ]; - # Not supported for target bpf - hardeningDisable = lib.optionals driverSupport [ "zerocallusedregs" ]; - passthru.tests = { clang-tidy = finalAttrs.finalPackage.overrideAttrs ( { name, src, nativeBuildInputs ? [], ... }: diff --git a/tools/meson.options b/tools/meson.options index 301efb9f677fdec57c8491fd6a6868f2d35cb076..2aa82a3188f2912487d075c8f320a63c14878f91 100644 --- a/tools/meson.options +++ b/tools/meson.options @@ -13,6 +13,13 @@ option('driver', type : 'boolean', value : false, option('hostfsrootdir', type : 'string', value : '/run/host', description : 'Path where the virtio-fs provided by the host will be mounted') +option('clang', type : 'string', + description : 'Path to clang') + +option('linux-headers', + type : 'string', + description : 'Path to Linux kernel headers') + option('tests', type : 'boolean', description : 'Build tests') diff --git a/tools/xdp-forwarder/helpers.h b/tools/xdp-forwarder/helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..f9bcbf48a230bd374d1035a0f9da8d04dc301505 --- /dev/null +++ b/tools/xdp-forwarder/helpers.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +/* SPDX-FileCopyrightText: 2021 The xdp-tutorial Authors */ +// SPDX-FileCopyrightText: 2025 Yureka Lilian +// SPDX-FileCopyrightText: 2025 Demi Marie Obenour +/* + * This file contains a helper for validating a VLAN tag, + * as well as structs used by both BPF programs. + */ + +#ifndef HELPERS_H +#define HELPERS_H + +#include +#include +#include + +#define VLAN_ETHTYPE 0x8100 /* Ethertype for tagged frames */ + +struct ethhdr { + struct { + __u8 destination_mac[6]; + __u8 source_mac[6]; + } mac_addresses; + __be16 h_proto; +}; + +struct vlan_hdr { + __be16 h_vlan_TCI; + __be16 h_vlan_encapsulated_proto; +}; + +struct maybe_tagged_ethhdr { + union { + struct { + struct ethhdr eth; + struct vlan_hdr vlan; + } tagged; + struct { + struct vlan_hdr pad; + struct ethhdr eth; + } untagged; + }; +}; + +// The router doesn't support the PCP and DEI bits +// and they are not part of the VLAN tag. +// Therefore, ensure they are unset. +// Also reject VLAN 0, which is reserved. +static __always_inline bool vlan_tag_is_valid(__u32 tag) +{ + return tag >= 1 && tag <= 0x0FFF; +} + +#endif /* HELPERS_H */ diff --git a/tools/xdp-forwarder/meson.build b/tools/xdp-forwarder/meson.build index b73130eb27b8000a102b0a8847ecb06b93a955d2..dec75c55884c4317af747b9a12e8f791d09d0a19 100644 --- a/tools/xdp-forwarder/meson.build +++ b/tools/xdp-forwarder/meson.build @@ -9,10 +9,18 @@ executable('set-router-iface', 'set_router_iface.c', dependencies : libbpf, install : true) -clang = find_program('clang', native : true) +clang_path = get_option('clang') +if clang_path == '' + error('clang must be provided to build XDP forwarder') +endif + +linux_headers_path = get_option('linux-headers') +if linux_headers_path == '' + error('Linux header path must be provided to build XDP forwarder') +endif bpf_o_cmd = [ - clang.full_path(), + clang_path, '-fno-stack-protector', '-fno-strict-aliasing', '-fwrapv', '-fwrapv-pointer', @@ -22,10 +30,12 @@ bpf_o_cmd = [ '-Wno-sign-compare', '-O2', '-target', 'bpf', - '-I', meson.current_source_dir() + '/include', '-g', '-c', + '-std=gnu23', '-o', '@OUTPUT@', + '-I', libbpf.get_variable('includedir'), + '-I', linux_headers_path, '-MD', '-MQ', '@OUTPUT', '-MF', '@DEPFILE@', diff --git a/tools/xdp-forwarder/parsing_helpers.h b/tools/xdp-forwarder/parsing_helpers.h deleted file mode 100644 index da099346008bd58485af8308feb4d3391ceef8f5..0000000000000000000000000000000000000000 --- a/tools/xdp-forwarder/parsing_helpers.h +++ /dev/null @@ -1,274 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -/* SPDX-FileCopyrightText: 2021 The xdp-tutorial Authors */ -/* Vendored from https://github.com/xdp-project/xdp-tutorial/blob/d3d3eed6ea9a63d1302bfa8b5a8e93862bfe11f0/common/parsing_helpers.h */ -/* - * This file contains parsing functions that are used in the packetXX XDP - * programs. The functions are marked as __always_inline, and fully defined in - * this header file to be included in the BPF program. - * - * Each helper parses a packet header, including doing bounds checking, and - * returns the type of its contents if successful, and -1 otherwise. - * - * For Ethernet and IP headers, the content type is the type of the payload - * (h_proto for Ethernet, nexthdr for IPv6), for ICMP it is the ICMP type field. - * All return values are in host byte order. - * - * The versions of the functions included here are slightly expanded versions of - * the functions in the packet01 lesson. For instance, the Ethernet header - * parsing has support for parsing VLAN tags. - */ - -#ifndef __PARSING_HELPERS_H -#define __PARSING_HELPERS_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Header cursor to keep track of current parsing position */ -struct hdr_cursor { - void *pos; -}; - -/* - * struct vlan_hdr - vlan header - * @h_vlan_TCI: priority and VLAN ID - * @h_vlan_encapsulated_proto: packet type ID or len - */ -struct vlan_hdr { - __be16 h_vlan_TCI; - __be16 h_vlan_encapsulated_proto; -}; - -/* - * Struct icmphdr_common represents the common part of the icmphdr and icmp6hdr - * structures. - */ -struct icmphdr_common { - __u8 type; - __u8 code; - __sum16 cksum; -}; - -/* Allow users of header file to redefine VLAN max depth */ -#ifndef VLAN_MAX_DEPTH -#define VLAN_MAX_DEPTH 2 -#endif - -#define VLAN_VID_MASK 0x0fff /* VLAN Identifier */ -/* Struct for collecting VLANs after parsing via parse_ethhdr_vlan */ -struct collect_vlans { - __u16 id[VLAN_MAX_DEPTH]; -}; - -static __always_inline int proto_is_vlan(__u16 h_proto) -{ - return !!(h_proto == bpf_htons(ETH_P_8021Q) || - h_proto == bpf_htons(ETH_P_8021AD)); -} - -/* Notice, parse_ethhdr() will skip VLAN tags, by advancing nh->pos and returns - * next header EtherType, BUT the ethhdr pointer supplied still points to the - * Ethernet header. Thus, caller can look at eth->h_proto to see if this was a - * VLAN tagged packet. - */ -static __always_inline int parse_ethhdr_vlan(struct hdr_cursor *nh, - void *data_end, - struct ethhdr **ethhdr, - struct collect_vlans *vlans) -{ - struct ethhdr *eth = nh->pos; - int hdrsize = sizeof(*eth); - struct vlan_hdr *vlh; - __u16 h_proto; - int i; - - /* Byte-count bounds check; check if current pointer + size of header - * is after data_end. - */ - if (nh->pos + hdrsize > data_end) - return -1; - - nh->pos += hdrsize; - *ethhdr = eth; - vlh = nh->pos; - h_proto = eth->h_proto; - - /* Use loop unrolling to avoid the verifier restriction on loops; - * support up to VLAN_MAX_DEPTH layers of VLAN encapsulation. - */ - #pragma unroll - for (i = 0; i < VLAN_MAX_DEPTH; i++) { - if (!proto_is_vlan(h_proto)) - break; - - if (vlh + 1 > data_end) - break; - - h_proto = vlh->h_vlan_encapsulated_proto; - if (vlans) /* collect VLAN ids */ - vlans->id[i] = - (bpf_ntohs(vlh->h_vlan_TCI) & VLAN_VID_MASK); - - vlh++; - } - - nh->pos = vlh; - return h_proto; /* network-byte-order */ -} - -static __always_inline int parse_ethhdr(struct hdr_cursor *nh, - void *data_end, - struct ethhdr **ethhdr) -{ - /* Expect compiler removes the code that collects VLAN ids */ - return parse_ethhdr_vlan(nh, data_end, ethhdr, NULL); -} - -static __always_inline int parse_ip6hdr(struct hdr_cursor *nh, - void *data_end, - struct ipv6hdr **ip6hdr) -{ - struct ipv6hdr *ip6h = nh->pos; - - /* Pointer-arithmetic bounds check; pointer +1 points to after end of - * thing being pointed to. We will be using this style in the remainder - * of the tutorial. - */ - if (ip6h + 1 > data_end) - return -1; - - nh->pos = ip6h + 1; - *ip6hdr = ip6h; - - return ip6h->nexthdr; -} - -static __always_inline int parse_iphdr(struct hdr_cursor *nh, - void *data_end, - struct iphdr **iphdr) -{ - struct iphdr *iph = nh->pos; - int hdrsize; - - if (iph + 1 > data_end) - return -1; - - hdrsize = iph->ihl * 4; - /* Sanity check packet field is valid */ - if(hdrsize < sizeof(*iph)) - return -1; - - /* Variable-length IPv4 header, need to use byte-based arithmetic */ - if (nh->pos + hdrsize > data_end) - return -1; - - nh->pos += hdrsize; - *iphdr = iph; - - return iph->protocol; -} - -static __always_inline int parse_icmp6hdr(struct hdr_cursor *nh, - void *data_end, - struct icmp6hdr **icmp6hdr) -{ - struct icmp6hdr *icmp6h = nh->pos; - - if (icmp6h + 1 > data_end) - return -1; - - nh->pos = icmp6h + 1; - *icmp6hdr = icmp6h; - - return icmp6h->icmp6_type; -} - -static __always_inline int parse_icmphdr(struct hdr_cursor *nh, - void *data_end, - struct icmphdr **icmphdr) -{ - struct icmphdr *icmph = nh->pos; - - if (icmph + 1 > data_end) - return -1; - - nh->pos = icmph + 1; - *icmphdr = icmph; - - return icmph->type; -} - -static __always_inline int parse_icmphdr_common(struct hdr_cursor *nh, - void *data_end, - struct icmphdr_common **icmphdr) -{ - struct icmphdr_common *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - nh->pos = h + 1; - *icmphdr = h; - - return h->type; -} - -/* - * parse_udphdr: parse the udp header and return the length of the udp payload - */ -static __always_inline int parse_udphdr(struct hdr_cursor *nh, - void *data_end, - struct udphdr **udphdr) -{ - int len; - struct udphdr *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - nh->pos = h + 1; - *udphdr = h; - - len = bpf_ntohs(h->len) - sizeof(struct udphdr); - if (len < 0) - return -1; - - return len; -} - -/* - * parse_tcphdr: parse and return the length of the tcp header - */ -static __always_inline int parse_tcphdr(struct hdr_cursor *nh, - void *data_end, - struct tcphdr **tcphdr) -{ - int len; - struct tcphdr *h = nh->pos; - - if (h + 1 > data_end) - return -1; - - len = h->doff * 4; - /* Sanity check packet field is valid */ - if(len < sizeof(*h)) - return -1; - - /* Variable-length TCP header, need to use byte-based arithmetic */ - if (nh->pos + len > data_end) - return -1; - - nh->pos += len; - *tcphdr = h; - - return len; -} - -#endif /* __PARSING_HELPERS_H */ diff --git a/tools/xdp-forwarder/prog_physical.c b/tools/xdp-forwarder/prog_physical.c index 2b70654ebe21cb2504e6d26ca3b93cb4b62efb0f..4c6744e905132d3cc2fcc21ce936f7893fab29fa 100644 --- a/tools/xdp-forwarder/prog_physical.c +++ b/tools/xdp-forwarder/prog_physical.c @@ -1,12 +1,8 @@ // SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2025 Yureka Lilian +// SPDX-FileCopyrightText: 2025 Demi Marie Obenour -#define VLAN_MAX_DEPTH 1 - -#include -#include -#include "parsing_helpers.h" -#include "rewrite_helpers.h" +#include "helpers.h" struct { __uint(type, BPF_MAP_TYPE_DEVMAP); @@ -16,24 +12,43 @@ struct { __uint(pinning, LIBBPF_PIN_BY_NAME); } router_iface SEC(".maps"); +static __always_inline bool push_vlan_tag(struct xdp_md *ctx, __u16 tag) +{ + struct maybe_tagged_ethhdr *hdr; + + // Add extra space at the front of the packet. + // Doing this first avoids reloading pointers and + // extra bounds checks later. + if (bpf_xdp_adjust_head(ctx, -(int)sizeof(hdr->untagged.pad))) + return false; + + hdr = (void *)(long)ctx->data; + if (hdr + 1 > (void *)(long)ctx->data_end) + return false; + + // Move the MAC addresses. + // Ethertype is already in the correct position. + __builtin_memmove(&hdr->tagged.eth.mac_addresses, + &hdr->untagged.eth.mac_addresses, + sizeof(hdr->tagged.eth.mac_addresses)); + + // Set the VLAN ID and the Ethertype of the frame. + hdr->tagged.vlan.h_vlan_TCI = bpf_htons((__u16)tag); + hdr->tagged.eth.h_proto = bpf_htons(VLAN_ETHTYPE); + return true; +} + SEC("xdp") int physical(struct xdp_md *ctx) { - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; - - struct hdr_cursor nh; - nh.pos = data; - - struct ethhdr *eth; - if (parse_ethhdr(&nh, data_end, ð) < 0) - return XDP_DROP; + __u32 ingress_ifindex = ctx->ingress_ifindex; - if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > VLAN_VID_MASK) + if (!vlan_tag_is_valid(ingress_ifindex)) return XDP_DROP; - if (vlan_tag_push(ctx, eth, ctx->ingress_ifindex) < 0) + if (!push_vlan_tag(ctx, (__u16)ingress_ifindex)) return XDP_DROP; + // Redirect to the router interface. return bpf_redirect_map(&router_iface, 0, 0); } diff --git a/tools/xdp-forwarder/prog_router.c b/tools/xdp-forwarder/prog_router.c index 5b0c8fa5c55e365c08fdb1be8f0613bdc67d8649..21595d3c66e596d085d6be7e2057a0aeaaf1c55a 100644 --- a/tools/xdp-forwarder/prog_router.c +++ b/tools/xdp-forwarder/prog_router.c @@ -1,12 +1,8 @@ // SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2025 Yureka Lilian +// SPDX-FileCopyrightText: 2025 Demi Marie Obenour -#define VLAN_MAX_DEPTH 1 - -#include -#include -#include "parsing_helpers.h" -#include "rewrite_helpers.h" +#include "helpers.h" // The map is actually not used by this program, but just included // to keep the reference-counted pin alive before any physical interfaces @@ -19,23 +15,42 @@ struct { __uint(pinning, LIBBPF_PIN_BY_NAME); } router_iface SEC(".maps"); +static __always_inline bool pop_vlan_tag(struct xdp_md *ctx, __u16 *tag) +{ + struct maybe_tagged_ethhdr *hdr = (void *)(long)ctx->data; + if (hdr + 1 > (void *)(long)ctx->data_end) + return false; + + // 0x8A88 is also valid but the router does not + // use it. It's meant for service provider VLANs. + if (hdr->tagged.eth.h_proto != bpf_htons(VLAN_ETHTYPE)) + return false; + + *tag = bpf_ntohs(hdr->tagged.vlan.h_vlan_TCI); + + // Move the MAC addresses. + // Ethertype is already in correct position. + __builtin_memmove(&hdr->untagged.eth.mac_addresses, + &hdr->tagged.eth.mac_addresses, + sizeof(hdr->tagged.eth.mac_addresses)); + + // Move the head pointer to the new Ethernet header. + // Doing this last avoids needing to reload pointers + // and add extra bounds checks earlier. + return !bpf_xdp_adjust_head(ctx, (int)sizeof(hdr->untagged.pad)); +} SEC("xdp") int router(struct xdp_md *ctx) { - void *data_end = (void *)(long)ctx->data_end; - void *data = (void *)(long)ctx->data; + __u16 vlid; - struct hdr_cursor nh; - nh.pos = data; - - struct ethhdr *eth; - if (parse_ethhdr(&nh, data_end, ð) < 0) - return XDP_DROP; + if (!pop_vlan_tag(ctx, &vlid)) + return false; - int vlid = vlan_tag_pop(ctx, eth); - if (vlid < 0) + if (!vlan_tag_is_valid(vlid)) return XDP_DROP; + // Redirect to the correct physical interface. return bpf_redirect(vlid, 0); } diff --git a/tools/xdp-forwarder/rewrite_helpers.h b/tools/xdp-forwarder/rewrite_helpers.h deleted file mode 100644 index 71aa23e038cc450087c1db20c00cbdbe1616b0a1..0000000000000000000000000000000000000000 --- a/tools/xdp-forwarder/rewrite_helpers.h +++ /dev/null @@ -1,146 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -/* SPDX-FileCopyrightText: 2019 The xdp-tutorial Authors */ -/* Vendored from https://github.com/xdp-project/xdp-tutorial/blob/d3d3eed6ea9a63d1302bfa8b5a8e93862bfe11f0/common/rewrite_helpers.h */ -/* - * This file contains functions that are used in the packetXX XDP programs to - * manipulate on packets data. The functions are marked as __always_inline, and - * fully defined in this header file to be included in the BPF program. - */ - -#ifndef __REWRITE_HELPERS_H -#define __REWRITE_HELPERS_H - -#include -#include -#include -#include - -#include -#include - -/* Pops the outermost VLAN tag off the packet. Returns the popped VLAN ID on - * success or negative errno on failure. - */ -static __always_inline int vlan_tag_pop(struct xdp_md *ctx, struct ethhdr *eth) -{ - void *data_end = (void *)(long)ctx->data_end; - struct ethhdr eth_cpy; - struct vlan_hdr *vlh; - __be16 h_proto; - int vlid; - - if (!proto_is_vlan(eth->h_proto)) - return -1; - - /* Careful with the parenthesis here */ - vlh = (void *)(eth + 1); - - /* Still need to do bounds checking */ - if (vlh + 1 > data_end) - return -1; - - /* Save vlan ID for returning, h_proto for updating Ethernet header */ - vlid = bpf_ntohs(vlh->h_vlan_TCI); - h_proto = vlh->h_vlan_encapsulated_proto; - - /* Make a copy of the outer Ethernet header before we cut it off */ - __builtin_memcpy(ð_cpy, eth, sizeof(eth_cpy)); - - /* Actually adjust the head pointer */ - if (bpf_xdp_adjust_head(ctx, (int)sizeof(*vlh))) - return -1; - - /* Need to re-evaluate data *and* data_end and do new bounds checking - * after adjusting head - */ - eth = (void *)(long)ctx->data; - data_end = (void *)(long)ctx->data_end; - if (eth + 1 > data_end) - return -1; - - /* Copy back the old Ethernet header and update the proto type */ - __builtin_memcpy(eth, ð_cpy, sizeof(*eth)); - eth->h_proto = h_proto; - - return vlid; -} - -/* Pushes a new VLAN tag after the Ethernet header. Returns 0 on success, - * -1 on failure. - */ -static __always_inline int vlan_tag_push(struct xdp_md *ctx, - struct ethhdr *eth, int vlid) -{ - void *data_end = (void *)(long)ctx->data_end; - struct ethhdr eth_cpy; - struct vlan_hdr *vlh; - - /* First copy the original Ethernet header */ - __builtin_memcpy(ð_cpy, eth, sizeof(eth_cpy)); - - /* Then add space in front of the packet */ - if (bpf_xdp_adjust_head(ctx, 0 - (int)sizeof(*vlh))) - return -1; - - /* Need to re-evaluate data_end and data after head adjustment, and - * bounds check, even though we know there is enough space (as we - * increased it). - */ - data_end = (void *)(long)ctx->data_end; - eth = (void *)(long)ctx->data; - - if (eth + 1 > data_end) - return -1; - - /* Copy back Ethernet header in the right place, populate VLAN tag with - * ID and proto, and set outer Ethernet header to VLAN type. - */ - __builtin_memcpy(eth, ð_cpy, sizeof(*eth)); - - vlh = (void *)(eth + 1); - - if (vlh + 1 > data_end) - return -1; - - vlh->h_vlan_TCI = bpf_htons(vlid); - vlh->h_vlan_encapsulated_proto = eth->h_proto; - - eth->h_proto = bpf_htons(ETH_P_8021Q); - return 0; -} - -/* - * Swaps destination and source MAC addresses inside an Ethernet header - */ -static __always_inline void swap_src_dst_mac(struct ethhdr *eth) -{ - __u8 h_tmp[ETH_ALEN]; - - __builtin_memcpy(h_tmp, eth->h_source, ETH_ALEN); - __builtin_memcpy(eth->h_source, eth->h_dest, ETH_ALEN); - __builtin_memcpy(eth->h_dest, h_tmp, ETH_ALEN); -} - -/* - * Swaps destination and source IPv6 addresses inside an IPv6 header - */ -static __always_inline void swap_src_dst_ipv6(struct ipv6hdr *ipv6) -{ - struct in6_addr tmp = ipv6->saddr; - - ipv6->saddr = ipv6->daddr; - ipv6->daddr = tmp; -} - -/* - * Swaps destination and source IPv4 addresses inside an IPv4 header - */ -static __always_inline void swap_src_dst_ipv4(struct iphdr *iphdr) -{ - __be32 tmp = iphdr->saddr; - - iphdr->saddr = iphdr->daddr; - iphdr->daddr = tmp; -} - -#endif /* __REWRITE_HELPERS_H */ --- base-commit: c5d5786d3dc938af0b279c542d1e43bce381b4b9 change-id: 20251003-fix-forwarder-build-2889ea9aec91 -- Sincerely, Demi Marie Obenour (she/her/hers)