* [DO_NOT_APPLY v3 0/3] xdp-forwarder
@ 2025-09-01 20:12 Yureka Lilian
2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Yureka Lilian @ 2025-09-01 20:12 UTC (permalink / raw)
To: devel; +Cc: Yureka Lilian
Same as v1, it doesn't make sense to apply this without the router.
Changes since v2:
- Switch xdp-forwarder build to meson
- Add guest build variant of spectrum-tools
Changes since v1:
- rebased
- apply new uncrustify config
- moved xdp-forwarder to tools/
- split integration into separate commit
- use linuxHeaders instead of vmlinux.h
- use original xdp-tutorial {parsing,rewrite}_helpers.h
- inlined the load scripts into /etc/iface/mdev, using /usr/lib/xdp as
fixed prefix for finding the XDP progs
- removed the README, added a paragraph to architecture doc instead
Yureka Lilian (3):
add xdp-forwarder
integrate xdp-forwarder into net-vm
docs/architecture: add paragraph about networking
Documentation/about/architecture.adoc | 20 ++
pkgs/default.nix | 5 +
tools/default.nix | 15 +-
tools/meson.build | 5 +
tools/meson_options.txt | 4 +
tools/xdp-forwarder/include/parsing_helpers.h | 273 ++++++++++++++++++
tools/xdp-forwarder/include/rewrite_helpers.h | 145 ++++++++++
tools/xdp-forwarder/prog_physical.c | 37 +++
tools/xdp-forwarder/prog_router.c | 43 +++
tools/xdp-forwarder/set_router_iface.c | 32 ++
vm/sys/net/Makefile | 8 +-
vm/sys/net/default.nix | 22 +-
vm/sys/net/etc/fstab | 2 +
vm/sys/net/etc/mdev/iface | 27 +-
vm/sys/net/etc/nftables.conf | 8 -
vm/sys/net/etc/s6-rc/connman/dependencies | 4 -
vm/sys/net/etc/s6-rc/connman/type | 1 -
vm/sys/net/etc/s6-rc/connman/type.license | 2 -
vm/sys/net/etc/s6-rc/nftables/type | 1 -
vm/sys/net/etc/s6-rc/nftables/type.license | 2 -
vm/sys/net/etc/s6-rc/nftables/up | 6 -
21 files changed, 599 insertions(+), 63 deletions(-)
create mode 100644 tools/xdp-forwarder/include/parsing_helpers.h
create mode 100644 tools/xdp-forwarder/include/rewrite_helpers.h
create mode 100644 tools/xdp-forwarder/prog_physical.c
create mode 100644 tools/xdp-forwarder/prog_router.c
create mode 100644 tools/xdp-forwarder/set_router_iface.c
delete mode 100644 vm/sys/net/etc/nftables.conf
delete mode 100644 vm/sys/net/etc/s6-rc/connman/dependencies
delete mode 100644 vm/sys/net/etc/s6-rc/connman/type
delete mode 100644 vm/sys/net/etc/s6-rc/connman/type.license
delete mode 100644 vm/sys/net/etc/s6-rc/nftables/type
delete mode 100644 vm/sys/net/etc/s6-rc/nftables/type.license
delete mode 100644 vm/sys/net/etc/s6-rc/nftables/up
--
2.50.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [DO_NOT_APPLY v3 1/3] add xdp-forwarder 2025-09-01 20:12 [DO_NOT_APPLY v3 0/3] xdp-forwarder Yureka Lilian @ 2025-09-01 20:12 ` Yureka Lilian 2025-09-06 12:05 ` Alyssa Ross 2025-09-06 17:07 ` [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm Demi Marie Obenour 2025-09-01 20:12 ` Yureka Lilian 2025-09-01 20:12 ` [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking Yureka Lilian 2 siblings, 2 replies; 10+ messages in thread From: Yureka Lilian @ 2025-09-01 20:12 UTC (permalink / raw) To: devel; +Cc: Yureka Lilian Signed-off-by: Yureka Lilian <yureka@cyberchaos.dev> --- pkgs/default.nix | 5 + tools/default.nix | 15 +- tools/meson.build | 5 + tools/meson_options.txt | 4 + tools/xdp-forwarder/include/parsing_helpers.h | 273 ++++++++++++++++++ tools/xdp-forwarder/include/rewrite_helpers.h | 145 ++++++++++ tools/xdp-forwarder/prog_physical.c | 37 +++ tools/xdp-forwarder/prog_router.c | 43 +++ tools/xdp-forwarder/set_router_iface.c | 32 ++ 9 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 tools/xdp-forwarder/include/parsing_helpers.h create mode 100644 tools/xdp-forwarder/include/rewrite_helpers.h create mode 100644 tools/xdp-forwarder/prog_physical.c create mode 100644 tools/xdp-forwarder/prog_router.c create mode 100644 tools/xdp-forwarder/set_router_iface.c diff --git a/pkgs/default.nix b/pkgs/default.nix index 3b81339..76b2a5c 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> # SPDX-License-Identifier: MIT { ... } @ args: @@ -42,6 +43,10 @@ let guestSupport = false; hostSupport = true; }; + spectrum-driver-tools = self.callSpectrumPackage ../tools { + guestSupport = false; + driverSupport = true; + }; xdg-desktop-portal-spectrum-host = self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-host {}; diff --git a/tools/default.nix b/tools/default.nix index 95d76a1..e664f47 100644 --- a/tools/default.nix +++ b/tools/default.nix @@ -1,13 +1,16 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2022-2025 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> import ../lib/call-package.nix ( { src, lib, stdenv, fetchCrate, fetchurl, runCommand, buildPackages , meson, ninja, pkg-config, rustc , clang-tools, clippy , dbus +, clang, libbpf , guestSupport ? true , hostSupport ? false +, driverSupport ? false }: let @@ -70,15 +73,18 @@ stdenv.mkDerivation (finalAttrs: { ./lsvm ./start-vmm ./subprojects + ] ++ lib.optionals driverSupport [ + ./xdp-forwarder ])); }; sourceRoot = "source/tools"; depsBuildBuild = lib.optionals hostSupport [ buildPackages.stdenv.cc ]; nativeBuildInputs = [ meson ninja ] - ++ lib.optionals guestSupport [ pkg-config ] - ++ lib.optionals hostSupport [ rustc ]; - buildInputs = lib.optionals guestSupport [ dbus ]; + ++ lib.optionals (guestSupport || driverSupport) [ pkg-config ] + ++ lib.optionals hostSupport [ rustc ] + ++ lib.optionals driverSupport [ clang ]; + buildInputs = lib.optionals guestSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf ]; postPatch = lib.optionals hostSupport (lib.concatMapStringsSep "\n" (crate: '' mkdir -p subprojects/packagecache @@ -88,12 +94,15 @@ stdenv.mkDerivation (finalAttrs: { mesonFlags = [ (lib.mesonBool "guest" guestSupport) (lib.mesonBool "host" hostSupport) + (lib.mesonBool "driver" driverSupport) "-Dhostfsrootdir=/run/virtiofs/virtiofs0" "-Dtests=false" "-Dunwind=false" "-Dwerror=true" ]; + hardeningDisable = lib.optionals driverSupport [ "zerocallusedregs" ]; + passthru.tests = { clang-tidy = finalAttrs.finalPackage.overrideAttrs ( { name, src, nativeBuildInputs ? [], ... }: diff --git a/tools/meson.build b/tools/meson.build index 9cebd03..e49f27c 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -1,5 +1,6 @@ # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2024 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> project('spectrum-tools', 'c', default_options : { @@ -26,3 +27,7 @@ endif if get_option('guest') subdir('xdg-desktop-portal-spectrum') endif + +if get_option('driver') + subdir('xdp-forwarder') +endif diff --git a/tools/meson_options.txt b/tools/meson_options.txt index 4af0031..887e388 100644 --- a/tools/meson_options.txt +++ b/tools/meson_options.txt @@ -1,5 +1,6 @@ # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2022-2024 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> option('host', type : 'boolean', value : false, description : 'Build tools for the Spectrum host') @@ -7,6 +8,9 @@ option('host', type : 'boolean', value : false, option('guest', type : 'boolean', description : 'Build tools for Spectrum guests') +option('driver', type : 'boolean', + description : 'Build tools for Spectrum driver VMs') + option('hostfsrootdir', type : 'string', value : '/run/host', description : 'Path where the virtio-fs provided by the host will be mounted') diff --git a/tools/xdp-forwarder/include/parsing_helpers.h b/tools/xdp-forwarder/include/parsing_helpers.h new file mode 100644 index 0000000..3d240cd --- /dev/null +++ b/tools/xdp-forwarder/include/parsing_helpers.h @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ +/* 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 <stddef.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/icmp.h> +#include <linux/icmpv6.h> +#include <linux/udp.h> +#include <linux/tcp.h> + +/* 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/include/rewrite_helpers.h b/tools/xdp-forwarder/include/rewrite_helpers.h new file mode 100644 index 0000000..2deae9a --- /dev/null +++ b/tools/xdp-forwarder/include/rewrite_helpers.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ +/* 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 <linux/bpf.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/if_ether.h> + +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +/* 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 */ diff --git a/tools/xdp-forwarder/prog_physical.c b/tools/xdp-forwarder/prog_physical.c new file mode 100644 index 0000000..04b5131 --- /dev/null +++ b/tools/xdp-forwarder/prog_physical.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> + +#define VLAN_MAX_DEPTH 1 + +#include <linux/bpf.h> +#include <bpf/bpf_endian.h> +#include "parsing_helpers.h" +#include "rewrite_helpers.h" + +struct { + __uint(type, BPF_MAP_TYPE_DEVMAP); + __type(key, int); + __type(value, int); + __uint(max_entries, 1); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} router_iface SEC(".maps"); + +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; + + if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > 4096) + return XDP_DROP; + + vlan_tag_push(ctx, eth, ctx->ingress_ifindex); + return bpf_redirect_map(&router_iface, 0, 0); +} diff --git a/tools/xdp-forwarder/prog_router.c b/tools/xdp-forwarder/prog_router.c new file mode 100644 index 0000000..fe6a6b5 --- /dev/null +++ b/tools/xdp-forwarder/prog_router.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> + +#define VLAN_MAX_DEPTH 1 + +#include <linux/bpf.h> +#include <bpf/bpf_endian.h> +#include "parsing_helpers.h" +#include "rewrite_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 +// are added. +struct { + __uint(type, BPF_MAP_TYPE_DEVMAP); + __type(key, int); + __type(value, int); + __uint(max_entries, 1); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} router_iface SEC(".maps"); + + +SEC("xdp") +int router(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; + int r; + if (r = parse_ethhdr(&nh, data_end, ð) < 0) + return XDP_DROP; + + int vlid = vlan_tag_pop(ctx, eth); + if (vlid < 0) { + return XDP_DROP; + } + + return bpf_redirect(vlid, 0); +} diff --git a/tools/xdp-forwarder/set_router_iface.c b/tools/xdp-forwarder/set_router_iface.c new file mode 100644 index 0000000..f1a2bac --- /dev/null +++ b/tools/xdp-forwarder/set_router_iface.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> + +#include <stdio.h> +#include <net/if.h> +#include <bpf/bpf.h> + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "missing interface name\n"); + return 1; + } + + int router_idx = if_nametoindex(argv[1]); + if (router_idx <= 0) { + perror("error getting router interface"); + return 1; + } + + int map_fd = bpf_obj_get("/sys/fs/bpf/router_iface"); + if (map_fd < 0) { + perror("failed to open bpf map"); + return 1; + } + + int id = 0; + if (bpf_map_update_elem(map_fd, &id, &router_idx, 0) < 0) { + perror("failed to update bpf map"); + return 1; + } +} -- 2.50.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [DO_NOT_APPLY v3 1/3] add xdp-forwarder 2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian @ 2025-09-06 12:05 ` Alyssa Ross [not found] ` <08193621-065b-466f-8703-5ae5ecf71788@yuka.dev> 2025-09-06 17:07 ` [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm Demi Marie Obenour 1 sibling, 1 reply; 10+ messages in thread From: Alyssa Ross @ 2025-09-06 12:05 UTC (permalink / raw) To: Yureka Lilian; +Cc: devel [-- Attachment #1: Type: text/plain, Size: 5629 bytes --] Yureka Lilian <yureka@cyberchaos.dev> writes: > Signed-off-by: Yureka Lilian <yureka@cyberchaos.dev> When you submit this for real, make sure to format the commit message like other Spectrum commits (e.g. "tools: add xdp-forwarder"), and maybe add a little background info, since it's not documented yet. > --- > pkgs/default.nix | 5 + > tools/default.nix | 15 +- > tools/meson.build | 5 + > tools/meson_options.txt | 4 + > tools/xdp-forwarder/include/parsing_helpers.h | 273 ++++++++++++++++++ > tools/xdp-forwarder/include/rewrite_helpers.h | 145 ++++++++++ > tools/xdp-forwarder/prog_physical.c | 37 +++ > tools/xdp-forwarder/prog_router.c | 43 +++ > tools/xdp-forwarder/set_router_iface.c | 32 ++ Shouldn't there be a tools/xdp-forwarder/meson.build? > 9 files changed, 556 insertions(+), 3 deletions(-) > create mode 100644 tools/xdp-forwarder/include/parsing_helpers.h > create mode 100644 tools/xdp-forwarder/include/rewrite_helpers.h > create mode 100644 tools/xdp-forwarder/prog_physical.c > create mode 100644 tools/xdp-forwarder/prog_router.c > create mode 100644 tools/xdp-forwarder/set_router_iface.c > > diff --git a/tools/default.nix b/tools/default.nix > index 95d76a1..e664f47 100644 > --- a/tools/default.nix > +++ b/tools/default.nix > @@ -1,13 +1,16 @@ > # SPDX-License-Identifier: MIT > # SPDX-FileCopyrightText: 2022-2025 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > import ../lib/call-package.nix ( > { src, lib, stdenv, fetchCrate, fetchurl, runCommand, buildPackages > , meson, ninja, pkg-config, rustc > , clang-tools, clippy > , dbus > +, clang, libbpf > , guestSupport ? true > , hostSupport ? false > +, driverSupport ? false > }: > > let > @@ -70,15 +73,18 @@ stdenv.mkDerivation (finalAttrs: { > ./lsvm > ./start-vmm > ./subprojects > + ] ++ lib.optionals driverSupport [ > + ./xdp-forwarder > ])); > }; > sourceRoot = "source/tools"; > > depsBuildBuild = lib.optionals hostSupport [ buildPackages.stdenv.cc ]; > nativeBuildInputs = [ meson ninja ] > - ++ lib.optionals guestSupport [ pkg-config ] > - ++ lib.optionals hostSupport [ rustc ]; > - buildInputs = lib.optionals guestSupport [ dbus ]; > + ++ lib.optionals (guestSupport || driverSupport) [ pkg-config ] > + ++ lib.optionals hostSupport [ rustc ] > + ++ lib.optionals driverSupport [ clang ]; > + buildInputs = lib.optionals guestSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf ]; > > postPatch = lib.optionals hostSupport (lib.concatMapStringsSep "\n" (crate: '' > mkdir -p subprojects/packagecache > @@ -88,12 +94,15 @@ stdenv.mkDerivation (finalAttrs: { > mesonFlags = [ > (lib.mesonBool "guest" guestSupport) > (lib.mesonBool "host" hostSupport) > + (lib.mesonBool "driver" driverSupport) > "-Dhostfsrootdir=/run/virtiofs/virtiofs0" > "-Dtests=false" > "-Dunwind=false" > "-Dwerror=true" > ]; > > + hardeningDisable = lib.optionals driverSupport [ "zerocallusedregs" ]; > + Should we use the unwrapped compiler? The wrapper doesn't give us anything useful for other targets, and makes us need things like this. > passthru.tests = { > clang-tidy = finalAttrs.finalPackage.overrideAttrs ( > { name, src, nativeBuildInputs ? [], ... }: > diff --git a/tools/meson_options.txt b/tools/meson_options.txt > index 4af0031..887e388 100644 > --- a/tools/meson_options.txt > +++ b/tools/meson_options.txt > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: EUPL-1.2+ > # SPDX-FileCopyrightText: 2022-2024 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > option('host', type : 'boolean', value : false, > description : 'Build tools for the Spectrum host') > @@ -7,6 +8,9 @@ option('host', type : 'boolean', value : false, > option('guest', type : 'boolean', > description : 'Build tools for Spectrum guests') > > +option('driver', type : 'boolean', > + description : 'Build tools for Spectrum driver VMs') > + Should be grouped with host and guest (no blank line between). > diff --git a/tools/xdp-forwarder/set_router_iface.c b/tools/xdp-forwarder/set_router_iface.c > new file mode 100644 > index 0000000..f1a2bac > --- /dev/null > +++ b/tools/xdp-forwarder/set_router_iface.c > @@ -0,0 +1,32 @@ > +// SPDX-License-Identifier: EUPL-1.2+ > +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > + > +#include <stdio.h> > +#include <net/if.h> > +#include <bpf/bpf.h> > + > +int main(int argc, char **argv) > +{ > + if (argc < 2) { > + fprintf(stderr, "missing interface name\n"); > + return 1; > + } > + > + int router_idx = if_nametoindex(argv[1]); > + if (router_idx <= 0) { > + perror("error getting router interface"); > + return 1; Can we use err(EXIT_FAILURE, "error getting router interface") like we do elsewhere in Spectrum? > + } > + > + int map_fd = bpf_obj_get("/sys/fs/bpf/router_iface"); Do we want to namespace this at all? Is there a convention? > + if (map_fd < 0) { > + perror("failed to open bpf map"); > + return 1; > + } > + > + int id = 0; > + if (bpf_map_update_elem(map_fd, &id, &router_idx, 0) < 0) { > + perror("failed to update bpf map"); > + return 1; > + } > +} > -- > 2.50.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 227 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <08193621-065b-466f-8703-5ae5ecf71788@yuka.dev>]
* Re: [DO_NOT_APPLY v3 1/3] add xdp-forwarder [not found] ` <08193621-065b-466f-8703-5ae5ecf71788@yuka.dev> @ 2025-09-06 16:13 ` Alyssa Ross 0 siblings, 0 replies; 10+ messages in thread From: Alyssa Ross @ 2025-09-06 16:13 UTC (permalink / raw) To: Yureka; +Cc: devel [-- Attachment #1: Type: text/plain, Size: 819 bytes --] Yureka <yuka@yuka.dev> writes: >>> @@ -7,6 +8,9 @@ option('host', type : 'boolean', value : false, >>> option('guest', type : 'boolean', >>> description : 'Build tools for Spectrum guests') >>> >>> +option('driver', type : 'boolean', >>> + description : 'Build tools for Spectrum driver VMs') >>> + >> Should be grouped with host and guest (no blank line between). > > There is a blank line in between host and guest, so I don't know what > you mean exactly. I think I was confused by the diff output, sorry. Let's default it to false though — the most likely way for this to be reused would be for people running custom application VMs, so that's what should be built by default. Should also rename "guest" to "app", I think — I can do that myself before applying, if you prefer. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 227 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm 2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian 2025-09-06 12:05 ` Alyssa Ross @ 2025-09-06 17:07 ` Demi Marie Obenour 2025-09-07 0:08 ` Yureka 1 sibling, 1 reply; 10+ messages in thread From: Demi Marie Obenour @ 2025-09-06 17:07 UTC (permalink / raw) To: Spectrum OS Development, Yureka Lilian [-- Attachment #1.1.1: Type: text/plain, Size: 26133 bytes --] On 9/1/25 16:12, Yureka Lilian wrote: > Signed-off-by: Yureka Lilian <yureka@cyberchaos.dev> > --- > pkgs/default.nix | 5 + > tools/default.nix | 15 +- > tools/meson.build | 5 + > tools/meson_options.txt | 4 + > tools/xdp-forwarder/include/parsing_helpers.h | 273 ++++++++++++++++++ > tools/xdp-forwarder/include/rewrite_helpers.h | 145 ++++++++++ > tools/xdp-forwarder/prog_physical.c | 37 +++ > tools/xdp-forwarder/prog_router.c | 43 +++ > tools/xdp-forwarder/set_router_iface.c | 32 ++ > 9 files changed, 556 insertions(+), 3 deletions(-) > create mode 100644 tools/xdp-forwarder/include/parsing_helpers.h > create mode 100644 tools/xdp-forwarder/include/rewrite_helpers.h > create mode 100644 tools/xdp-forwarder/prog_physical.c > create mode 100644 tools/xdp-forwarder/prog_router.c > create mode 100644 tools/xdp-forwarder/set_router_iface.c I have quite a few comments here, but they are almost all about the vendored helper code, not your code. It's not written the way I would write it, and the code for pushing and popping VLAN tags is very slightly suboptimal. Also, while it is correct in the kernel/BPF context it uses practices which would be bad, if not insecure, in userspace. > diff --git a/pkgs/default.nix b/pkgs/default.nix > index 3b81339..76b2a5c 100644 > --- a/pkgs/default.nix > +++ b/pkgs/default.nix > @@ -1,4 +1,5 @@ > # SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > # SPDX-License-Identifier: MIT > > { ... } @ args: > @@ -42,6 +43,10 @@ let > guestSupport = false; > hostSupport = true; > }; > + spectrum-driver-tools = self.callSpectrumPackage ../tools { > + guestSupport = false; > + driverSupport = true; > + }; > xdg-desktop-portal-spectrum-host = > self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-host {}; > > diff --git a/tools/default.nix b/tools/default.nix > index 95d76a1..e664f47 100644 > --- a/tools/default.nix > +++ b/tools/default.nix > @@ -1,13 +1,16 @@ > # SPDX-License-Identifier: MIT > # SPDX-FileCopyrightText: 2022-2025 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > import ../lib/call-package.nix ( > { src, lib, stdenv, fetchCrate, fetchurl, runCommand, buildPackages > , meson, ninja, pkg-config, rustc > , clang-tools, clippy > , dbus > +, clang, libbpf > , guestSupport ? true > , hostSupport ? false > +, driverSupport ? false > }: > > let > @@ -70,15 +73,18 @@ stdenv.mkDerivation (finalAttrs: { > ./lsvm > ./start-vmm > ./subprojects > + ] ++ lib.optionals driverSupport [ > + ./xdp-forwarder > ])); > }; > sourceRoot = "source/tools"; > > depsBuildBuild = lib.optionals hostSupport [ buildPackages.stdenv.cc ]; > nativeBuildInputs = [ meson ninja ] > - ++ lib.optionals guestSupport [ pkg-config ] > - ++ lib.optionals hostSupport [ rustc ]; > - buildInputs = lib.optionals guestSupport [ dbus ]; > + ++ lib.optionals (guestSupport || driverSupport) [ pkg-config ] > + ++ lib.optionals hostSupport [ rustc ] > + ++ lib.optionals driverSupport [ clang ]; > + buildInputs = lib.optionals guestSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf ]; > > postPatch = lib.optionals hostSupport (lib.concatMapStringsSep "\n" (crate: '' > mkdir -p subprojects/packagecache > @@ -88,12 +94,15 @@ stdenv.mkDerivation (finalAttrs: { > mesonFlags = [ > (lib.mesonBool "guest" guestSupport) > (lib.mesonBool "host" hostSupport) > + (lib.mesonBool "driver" driverSupport) > "-Dhostfsrootdir=/run/virtiofs/virtiofs0" > "-Dtests=false" > "-Dunwind=false" > "-Dwerror=true" > ]; > > + hardeningDisable = lib.optionals driverSupport [ "zerocallusedregs" ]; > + > passthru.tests = { > clang-tidy = finalAttrs.finalPackage.overrideAttrs ( > { name, src, nativeBuildInputs ? [], ... }: > diff --git a/tools/meson.build b/tools/meson.build > index 9cebd03..e49f27c 100644 > --- a/tools/meson.build > +++ b/tools/meson.build > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: EUPL-1.2+ > # SPDX-FileCopyrightText: 2024 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > project('spectrum-tools', 'c', > default_options : { > @@ -26,3 +27,7 @@ endif > if get_option('guest') > subdir('xdg-desktop-portal-spectrum') > endif > + > +if get_option('driver') > + subdir('xdp-forwarder') > +endif > diff --git a/tools/meson_options.txt b/tools/meson_options.txt > index 4af0031..887e388 100644 > --- a/tools/meson_options.txt > +++ b/tools/meson_options.txt > @@ -1,5 +1,6 @@ > # SPDX-License-Identifier: EUPL-1.2+ > # SPDX-FileCopyrightText: 2022-2024 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > option('host', type : 'boolean', value : false, > description : 'Build tools for the Spectrum host') > @@ -7,6 +8,9 @@ option('host', type : 'boolean', value : false, > option('guest', type : 'boolean', > description : 'Build tools for Spectrum guests') > > +option('driver', type : 'boolean', > + description : 'Build tools for Spectrum driver VMs') > + > option('hostfsrootdir', type : 'string', value : '/run/host', > description : 'Path where the virtio-fs provided by the host will be mounted') > > diff --git a/tools/xdp-forwarder/include/parsing_helpers.h b/tools/xdp-forwarder/include/parsing_helpers.h > new file mode 100644 > index 0000000..3d240cd > --- /dev/null > +++ b/tools/xdp-forwarder/include/parsing_helpers.h > @@ -0,0 +1,273 @@ > +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ > +/* 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 <stddef.h> > +#include <linux/if_ether.h> > +#include <linux/if_packet.h> > +#include <linux/ip.h> > +#include <linux/ipv6.h> > +#include <linux/icmp.h> > +#include <linux/icmpv6.h> > +#include <linux/udp.h> > +#include <linux/tcp.h> > + > +/* Header cursor to keep track of current parsing position */ > +struct hdr_cursor { > + void *pos; > +}; It's better to use `unsigned char *` or `uint8_t *` here. I believe that clang can be made to issue a warning about arithmetic on `void *`. > +/* > + * 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)); > +} Presumably the router only uses one of these tags, as using both would be pointless complexity. There is no need to interoperate with third-party code that assumes that only the innermost tag uses ETH_P_8021Q. Therefore, one of these can be removed. > +/* 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. > + */ This function is only ever called with VLAN_MAX_DEPTH == 1, so it is significant overkill for Spectrum's needs. > +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; In C, pointer arithmetic must produce a pointer that is either inside the bounds of an allocation, or one past the end. This means that this is undefined behavior if nh->pos points to less than hdrsize bytes of memory. To avoid this, it is better to write the code as follows *if* data_end is known to be at least nh->pos, or if (as here) hdrsize is signed and known to be non-negative: if (data_end - nh->pos < hdrsize) return -1; If hdrsize is unsigned and might exceed PTRDIFF_MAX, the following is needed: if ((data_end >= nh->pos) || (size_t)(data_end - nh->pos) < hdrsize) 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; Generally this is an antipattern, as it can cause (potentially exploitable) overflow or wraparound if vlh is an integer, or undefined behavior if vlh is a past-the-end pointer. Subtracting sizeof(*vlh) from data_end will also be undefined behavior if the frame is too short. I recommend if (((const char *)data_end - (const char *)vlh) <= (ptrdiff_t)sizeof(*vlh)) break; which is uglier, but safe. This can be wrapped in a macro if you wish, such as: #define INC_WOULD_BE_OOB(ptr, end) \ ((const char *)(end) - (const char *)(ptr) < (ptrdiff_t)sizeof(*(ptr))) if (INC_WOULD_BE_OOB(vlh, data_end)) break; In the Linux kernel (specifically), one can get away with this because: - Linux compiles with -fno-strict-overflow, so out-of-bounds pointer arithmetic is not undefined behavior. - Linux reserves the top 4096 bytes of the address space for error pointers, so there will never be a valid pointer to this memory. - sizeof(*vlh) < 4096 so if vlh is not an error pointer, vlh + 1 will not wrap around. The same arguments all apply to BPF provided one compiles with -fno-strict-overflow and (possibly) -fno-strict-aliasing. The Linux kernel version is much easier to read, which I suspect is why it gets used. In userspace, one (sadly) needs to use the uglier versions. > + h_proto = vlh->h_vlan_encapsulated_proto; > + if (vlans) /* collect VLAN ids */ > + vlans->id[i] = > + (bpf_ntohs(vlh->h_vlan_TCI) & VLAN_VID_MASK); Spectrum only uses parse_ethhdr(), which passes NULL for the `vlans` argument. Therefore, this branch is unreachable. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + hdrsize = iph->ihl * 4; > + /* Sanity check packet field is valid */ > + if(hdrsize < sizeof(*iph)) > + return -1; This is normally a very bad pattern because of integer overflow and/or wraparound, but in this case iph->ihl is a 4-bit bitfield and so iph->ihl * 4 is no more than 15 * 4 == 60. > + /* Variable-length IPv4 header, need to use byte-based arithmetic */ > + if (nh->pos + hdrsize > data_end) > + return -1; See above: this is UB in userspace but safe in the kernel/BPF with the correct compiler flags. If hdrsize could exceed 4095 it would not be, but it cannot. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + 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; Only safe in kernel/BPF. > + nh->pos += len; > + *tcphdr = h; > + > + return len; > +} > + > +#endif /* __PARSING_HELPERS_H */ > diff --git a/tools/xdp-forwarder/include/rewrite_helpers.h b/tools/xdp-forwarder/include/rewrite_helpers.h > new file mode 100644 > index 0000000..2deae9a > --- /dev/null > +++ b/tools/xdp-forwarder/include/rewrite_helpers.h > @@ -0,0 +1,145 @@ > +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ > +/* 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 <linux/bpf.h> > +#include <linux/ip.h> > +#include <linux/ipv6.h> > +#include <linux/if_ether.h> > + > +#include <bpf/bpf_helpers.h> > +#include <bpf/bpf_endian.h> > + > +/* 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; Unless the verifier requires otherwise, this only needs to be 12 bytes as the Ethertype is not restored, only the source and destination MAC addresses. > + struct vlan_hdr *vlh; > + __be16 h_proto; > + int vlid;> + > + if (!proto_is_vlan(eth->h_proto)) > + return -1; Only safe because the caller did a bounds check. > + /* Careful with the parenthesis here */ > + vlh = (void *)(eth + 1); > + > + /* Still need to do bounds checking */ > + if (vlh + 1 > data_end) > + return -1; Only safe in kernel/BPF. > + /* 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; Again, this only needs to be 12 bytes, not 14, as the old Ethertype is unchanged. The only exception is if not doing this causes the BPF verifier to complain. > + 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; Only safe in kernel/BPF. > + /* 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; Only safe in kernel/BPF. > + 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 */ > diff --git a/tools/xdp-forwarder/prog_physical.c b/tools/xdp-forwarder/prog_physical.c > new file mode 100644 > index 0000000..04b5131 > --- /dev/null > +++ b/tools/xdp-forwarder/prog_physical.c > @@ -0,0 +1,37 @@ > +// SPDX-License-Identifier: EUPL-1.2+ > +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > + > +#define VLAN_MAX_DEPTH 1 > + > +#include <linux/bpf.h> > +#include <bpf/bpf_endian.h> > +#include "parsing_helpers.h" > +#include "rewrite_helpers.h" > + > +struct { > + __uint(type, BPF_MAP_TYPE_DEVMAP); > + __type(key, int); > + __type(value, int); > + __uint(max_entries, 1); > + __uint(pinning, LIBBPF_PIN_BY_NAME); > +} router_iface SEC(".maps"); > + > +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; > + > + if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > 4096) > + return XDP_DROP;> + > + vlan_tag_push(ctx, eth, ctx->ingress_ifindex); > + return bpf_redirect_map(&router_iface, 0, 0); > +} > diff --git a/tools/xdp-forwarder/prog_router.c b/tools/xdp-forwarder/prog_router.c > new file mode 100644 > index 0000000..fe6a6b5 > --- /dev/null > +++ b/tools/xdp-forwarder/prog_router.c > @@ -0,0 +1,43 @@ > +// SPDX-License-Identifier: EUPL-1.2+ > +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > + > +#define VLAN_MAX_DEPTH 1 > + > +#include <linux/bpf.h> > +#include <bpf/bpf_endian.h> > +#include "parsing_helpers.h" > +#include "rewrite_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 > +// are added. > +struct { > + __uint(type, BPF_MAP_TYPE_DEVMAP); > + __type(key, int); > + __type(value, int); > + __uint(max_entries, 1); > + __uint(pinning, LIBBPF_PIN_BY_NAME); > +} router_iface SEC(".maps"); > + > + > +SEC("xdp") > +int router(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; > + int r; > + if (r = parse_ethhdr(&nh, data_end, ð) < 0) > + return XDP_DROP; > + > + int vlid = vlan_tag_pop(ctx, eth); > + if (vlid < 0) { > + return XDP_DROP; > + } > + > + return bpf_redirect(vlid, 0); > +} > diff --git a/tools/xdp-forwarder/set_router_iface.c b/tools/xdp-forwarder/set_router_iface.c > new file mode 100644 > index 0000000..f1a2bac > --- /dev/null > +++ b/tools/xdp-forwarder/set_router_iface.c > @@ -0,0 +1,32 @@ > +// SPDX-License-Identifier: EUPL-1.2+ > +// SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > + > +#include <stdio.h> > +#include <net/if.h> > +#include <bpf/bpf.h> > + > +int main(int argc, char **argv) > +{ > + if (argc < 2) { > + fprintf(stderr, "missing interface name\n"); > + return 1; > + } > + > + int router_idx = if_nametoindex(argv[1]); > + if (router_idx <= 0) { > + perror("error getting router interface"); > + return 1; > + } > + > + int map_fd = bpf_obj_get("/sys/fs/bpf/router_iface"); > + if (map_fd < 0) { > + perror("failed to open bpf map"); > + return 1; > + } > + > + int id = 0; > + if (bpf_map_update_elem(map_fd, &id, &router_idx, 0) < 0) { > + perror("failed to update bpf map"); > + return 1; > + } > +}-- Sincerely, Demi Marie Obenour (she/her/hers) [-- Attachment #1.1.2: OpenPGP public key --] [-- Type: application/pgp-keys, Size: 7255 bytes --] [-- Attachment #2: OpenPGP digital signature --] [-- Type: application/pgp-signature, Size: 833 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm 2025-09-06 17:07 ` [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm Demi Marie Obenour @ 2025-09-07 0:08 ` Yureka 0 siblings, 0 replies; 10+ messages in thread From: Yureka @ 2025-09-07 0:08 UTC (permalink / raw) To: devel [-- Attachment #1: Type: text/plain, Size: 26643 bytes --] (Sorry if you receive this multiple times, but I forgot the list again...) Thanks for the comments, Demi. However this header is meant to be only ever used in a BPF context and with the kernel headers. That's how it is used in this application too. Could you please narrow down the comments into upstreamable changes which affect spectrum's use of the code? On 9/6/25 19:07, Demi Marie Obenour wrote: > On 9/1/25 16:12, Yureka Lilian wrote: >> Signed-off-by: Yureka Lilian<yureka@cyberchaos.dev> >> --- >> pkgs/default.nix | 5 + >> tools/default.nix | 15 +- >> tools/meson.build | 5 + >> tools/meson_options.txt | 4 + >> tools/xdp-forwarder/include/parsing_helpers.h | 273 ++++++++++++++++++ >> tools/xdp-forwarder/include/rewrite_helpers.h | 145 ++++++++++ >> tools/xdp-forwarder/prog_physical.c | 37 +++ >> tools/xdp-forwarder/prog_router.c | 43 +++ >> tools/xdp-forwarder/set_router_iface.c | 32 ++ >> 9 files changed, 556 insertions(+), 3 deletions(-) >> create mode 100644 tools/xdp-forwarder/include/parsing_helpers.h >> create mode 100644 tools/xdp-forwarder/include/rewrite_helpers.h >> create mode 100644 tools/xdp-forwarder/prog_physical.c >> create mode 100644 tools/xdp-forwarder/prog_router.c >> create mode 100644 tools/xdp-forwarder/set_router_iface.c > I have quite a few comments here, but they are almost all about > the vendored helper code, not your code. It's not written the > way I would write it, and the code for pushing and popping VLAN > tags is very slightly suboptimal. Also, while it is correct in > the kernel/BPF context it uses practices which would be bad, if > not insecure, in userspace. > >> diff --git a/pkgs/default.nix b/pkgs/default.nix >> index 3b81339..76b2a5c 100644 >> --- a/pkgs/default.nix >> +++ b/pkgs/default.nix >> @@ -1,4 +1,5 @@ >> # SPDX-FileCopyrightText: 2023-2024 Alyssa Ross<hi@alyssa.is> >> +# SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> # SPDX-License-Identifier: MIT >> >> { ... } @ args: >> @@ -42,6 +43,10 @@ let >> guestSupport = false; >> hostSupport = true; >> }; >> + spectrum-driver-tools = self.callSpectrumPackage ../tools { >> + guestSupport = false; >> + driverSupport = true; >> + }; >> xdg-desktop-portal-spectrum-host = >> self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-host {}; >> >> diff --git a/tools/default.nix b/tools/default.nix >> index 95d76a1..e664f47 100644 >> --- a/tools/default.nix >> +++ b/tools/default.nix >> @@ -1,13 +1,16 @@ >> # SPDX-License-Identifier: MIT >> # SPDX-FileCopyrightText: 2022-2025 Alyssa Ross<hi@alyssa.is> >> +# SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> >> import ../lib/call-package.nix ( >> { src, lib, stdenv, fetchCrate, fetchurl, runCommand, buildPackages >> , meson, ninja, pkg-config, rustc >> , clang-tools, clippy >> , dbus >> +, clang, libbpf >> , guestSupport ? true >> , hostSupport ? false >> +, driverSupport ? false >> }: >> >> let >> @@ -70,15 +73,18 @@ stdenv.mkDerivation (finalAttrs: { >> ./lsvm >> ./start-vmm >> ./subprojects >> + ] ++ lib.optionals driverSupport [ >> + ./xdp-forwarder >> ])); >> }; >> sourceRoot = "source/tools"; >> >> depsBuildBuild = lib.optionals hostSupport [ buildPackages.stdenv.cc ]; >> nativeBuildInputs = [ meson ninja ] >> - ++ lib.optionals guestSupport [ pkg-config ] >> - ++ lib.optionals hostSupport [ rustc ]; >> - buildInputs = lib.optionals guestSupport [ dbus ]; >> + ++ lib.optionals (guestSupport || driverSupport) [ pkg-config ] >> + ++ lib.optionals hostSupport [ rustc ] >> + ++ lib.optionals driverSupport [ clang ]; >> + buildInputs = lib.optionals guestSupport [ dbus ] ++ lib.optionals driverSupport [ libbpf ]; >> >> postPatch = lib.optionals hostSupport (lib.concatMapStringsSep "\n" (crate: '' >> mkdir -p subprojects/packagecache >> @@ -88,12 +94,15 @@ stdenv.mkDerivation (finalAttrs: { >> mesonFlags = [ >> (lib.mesonBool "guest" guestSupport) >> (lib.mesonBool "host" hostSupport) >> + (lib.mesonBool "driver" driverSupport) >> "-Dhostfsrootdir=/run/virtiofs/virtiofs0" >> "-Dtests=false" >> "-Dunwind=false" >> "-Dwerror=true" >> ]; >> >> + hardeningDisable = lib.optionals driverSupport [ "zerocallusedregs" ]; >> + >> passthru.tests = { >> clang-tidy = finalAttrs.finalPackage.overrideAttrs ( >> { name, src, nativeBuildInputs ? [], ... }: >> diff --git a/tools/meson.build b/tools/meson.build >> index 9cebd03..e49f27c 100644 >> --- a/tools/meson.build >> +++ b/tools/meson.build >> @@ -1,5 +1,6 @@ >> # SPDX-License-Identifier: EUPL-1.2+ >> # SPDX-FileCopyrightText: 2024 Alyssa Ross<hi@alyssa.is> >> +# SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> >> project('spectrum-tools', 'c', >> default_options : { >> @@ -26,3 +27,7 @@ endif >> if get_option('guest') >> subdir('xdg-desktop-portal-spectrum') >> endif >> + >> +if get_option('driver') >> + subdir('xdp-forwarder') >> +endif >> diff --git a/tools/meson_options.txt b/tools/meson_options.txt >> index 4af0031..887e388 100644 >> --- a/tools/meson_options.txt >> +++ b/tools/meson_options.txt >> @@ -1,5 +1,6 @@ >> # SPDX-License-Identifier: EUPL-1.2+ >> # SPDX-FileCopyrightText: 2022-2024 Alyssa Ross<hi@alyssa.is> >> +# SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> >> option('host', type : 'boolean', value : false, >> description : 'Build tools for the Spectrum host') >> @@ -7,6 +8,9 @@ option('host', type : 'boolean', value : false, >> option('guest', type : 'boolean', >> description : 'Build tools for Spectrum guests') >> >> +option('driver', type : 'boolean', >> + description : 'Build tools for Spectrum driver VMs') >> + >> option('hostfsrootdir', type : 'string', value : '/run/host', >> description : 'Path where the virtio-fs provided by the host will be mounted') >> >> diff --git a/tools/xdp-forwarder/include/parsing_helpers.h b/tools/xdp-forwarder/include/parsing_helpers.h >> new file mode 100644 >> index 0000000..3d240cd >> --- /dev/null >> +++ b/tools/xdp-forwarder/include/parsing_helpers.h >> @@ -0,0 +1,273 @@ >> +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ >> +/* Vendored fromhttps://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 <stddef.h> >> +#include <linux/if_ether.h> >> +#include <linux/if_packet.h> >> +#include <linux/ip.h> >> +#include <linux/ipv6.h> >> +#include <linux/icmp.h> >> +#include <linux/icmpv6.h> >> +#include <linux/udp.h> >> +#include <linux/tcp.h> >> + >> +/* Header cursor to keep track of current parsing position */ >> +struct hdr_cursor { >> + void *pos; >> +}; > It's better to use `unsigned char *` or `uint8_t *` here. I believe that > clang can be made to issue a warning about arithmetic on `void *`. > >> +/* >> + * 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)); >> +} > Presumably the router only uses one of these tags, as using both would be > pointless complexity. There is no need to interoperate with third-party > code that assumes that only the innermost tag uses ETH_P_8021Q. Therefore, > one of these can be removed. > >> +/* 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. >> + */ > This function is only ever called with VLAN_MAX_DEPTH == 1, so it is significant > overkill for Spectrum's needs. > >> +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; > In C, pointer arithmetic must produce a pointer that is either inside the > bounds of an allocation, or one past the end. This means that this is > undefined behavior if nh->pos points to less than hdrsize bytes of memory. > To avoid this, it is better to write the code as follows *if* data_end is > known to be at least nh->pos, or if (as here) hdrsize is signed and known > to be non-negative: > > if (data_end - nh->pos < hdrsize) > return -1; > > If hdrsize is unsigned and might exceed PTRDIFF_MAX, the following is > needed: > > if ((data_end >= nh->pos) || > (size_t)(data_end - nh->pos) < hdrsize) > 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; > Generally this is an antipattern, as it can cause (potentially exploitable) > overflow or wraparound if vlh is an integer, or undefined behavior if vlh > is a past-the-end pointer. Subtracting sizeof(*vlh) from data_end will > also be undefined behavior if the frame is too short. I recommend > > if (((const char *)data_end - (const char *)vlh) > <= (ptrdiff_t)sizeof(*vlh)) > break; > > which is uglier, but safe. This can be wrapped in a macro if you wish, > such as: > > #define INC_WOULD_BE_OOB(ptr, end) \ > ((const char *)(end) - (const char *)(ptr) < (ptrdiff_t)sizeof(*(ptr))) > > if (INC_WOULD_BE_OOB(vlh, data_end)) > break; > > In the Linux kernel (specifically), one can get away with this because: > > - Linux compiles with -fno-strict-overflow, so out-of-bounds pointer arithmetic > is not undefined behavior. > - Linux reserves the top 4096 bytes of the address space for error pointers, > so there will never be a valid pointer to this memory. > - sizeof(*vlh) < 4096 so if vlh is not an error pointer, vlh + 1 will not > wrap around. > > The same arguments all apply to BPF provided one compiles with -fno-strict-overflow > and (possibly) -fno-strict-aliasing. The Linux kernel version is much easier to > read, which I suspect is why it gets used. In userspace, one (sadly) needs to > use the uglier versions. > >> + h_proto = vlh->h_vlan_encapsulated_proto; >> + if (vlans) /* collect VLAN ids */ >> + vlans->id[i] = >> + (bpf_ntohs(vlh->h_vlan_TCI) & VLAN_VID_MASK); > Spectrum only uses parse_ethhdr(), which passes NULL for the > `vlans` argument. Therefore, this branch is unreachable. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + hdrsize = iph->ihl * 4; >> + /* Sanity check packet field is valid */ >> + if(hdrsize < sizeof(*iph)) >> + return -1; > This is normally a very bad pattern because of integer overflow and/or > wraparound, but in this case iph->ihl is a 4-bit bitfield and so > iph->ihl * 4 is no more than 15 * 4 == 60. > >> + /* Variable-length IPv4 header, need to use byte-based arithmetic */ >> + if (nh->pos + hdrsize > data_end) >> + return -1; > See above: this is UB in userspace but safe in the kernel/BPF > with the correct compiler flags. If hdrsize could exceed 4095 > it would not be, but it cannot. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + 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; > Only safe in kernel/BPF. > >> + nh->pos += len; >> + *tcphdr = h; >> + >> + return len; >> +} >> + >> +#endif /* __PARSING_HELPERS_H */ >> diff --git a/tools/xdp-forwarder/include/rewrite_helpers.h b/tools/xdp-forwarder/include/rewrite_helpers.h >> new file mode 100644 >> index 0000000..2deae9a >> --- /dev/null >> +++ b/tools/xdp-forwarder/include/rewrite_helpers.h >> @@ -0,0 +1,145 @@ >> +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ >> +/* Vendored fromhttps://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 <linux/bpf.h> >> +#include <linux/ip.h> >> +#include <linux/ipv6.h> >> +#include <linux/if_ether.h> >> + >> +#include <bpf/bpf_helpers.h> >> +#include <bpf/bpf_endian.h> >> + >> +/* 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; > Unless the verifier requires otherwise, this only needs to be 12 > bytes as the Ethertype is not restored, only the source and > destination MAC addresses. > >> + struct vlan_hdr *vlh; >> + __be16 h_proto; >> + int vlid;> + >> + if (!proto_is_vlan(eth->h_proto)) >> + return -1; > Only safe because the caller did a bounds check. > >> + /* Careful with the parenthesis here */ >> + vlh = (void *)(eth + 1); >> + >> + /* Still need to do bounds checking */ >> + if (vlh + 1 > data_end) >> + return -1; > Only safe in kernel/BPF. > >> + /* 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; > Again, this only needs to be 12 bytes, not 14, as the old Ethertype > is unchanged. The only exception is if not doing this causes the BPF > verifier to complain. > >> + 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; > Only safe in kernel/BPF. > >> + /* 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; > Only safe in kernel/BPF. > >> + 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 */ >> diff --git a/tools/xdp-forwarder/prog_physical.c b/tools/xdp-forwarder/prog_physical.c >> new file mode 100644 >> index 0000000..04b5131 >> --- /dev/null >> +++ b/tools/xdp-forwarder/prog_physical.c >> @@ -0,0 +1,37 @@ >> +// SPDX-License-Identifier: EUPL-1.2+ >> +// SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> + >> +#define VLAN_MAX_DEPTH 1 >> + >> +#include <linux/bpf.h> >> +#include <bpf/bpf_endian.h> >> +#include "parsing_helpers.h" >> +#include "rewrite_helpers.h" >> + >> +struct { >> + __uint(type, BPF_MAP_TYPE_DEVMAP); >> + __type(key, int); >> + __type(value, int); >> + __uint(max_entries, 1); >> + __uint(pinning, LIBBPF_PIN_BY_NAME); >> +} router_iface SEC(".maps"); >> + >> +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; >> + >> + if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > 4096) >> + return XDP_DROP;> + >> + vlan_tag_push(ctx, eth, ctx->ingress_ifindex); >> + return bpf_redirect_map(&router_iface, 0, 0); >> +} >> diff --git a/tools/xdp-forwarder/prog_router.c b/tools/xdp-forwarder/prog_router.c >> new file mode 100644 >> index 0000000..fe6a6b5 >> --- /dev/null >> +++ b/tools/xdp-forwarder/prog_router.c >> @@ -0,0 +1,43 @@ >> +// SPDX-License-Identifier: EUPL-1.2+ >> +// SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> + >> +#define VLAN_MAX_DEPTH 1 >> + >> +#include <linux/bpf.h> >> +#include <bpf/bpf_endian.h> >> +#include "parsing_helpers.h" >> +#include "rewrite_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 >> +// are added. >> +struct { >> + __uint(type, BPF_MAP_TYPE_DEVMAP); >> + __type(key, int); >> + __type(value, int); >> + __uint(max_entries, 1); >> + __uint(pinning, LIBBPF_PIN_BY_NAME); >> +} router_iface SEC(".maps"); >> + >> + >> +SEC("xdp") >> +int router(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; >> + int r; >> + if (r = parse_ethhdr(&nh, data_end, ð) < 0) >> + return XDP_DROP; >> + >> + int vlid = vlan_tag_pop(ctx, eth); >> + if (vlid < 0) { >> + return XDP_DROP; >> + } >> + >> + return bpf_redirect(vlid, 0); >> +} >> diff --git a/tools/xdp-forwarder/set_router_iface.c b/tools/xdp-forwarder/set_router_iface.c >> new file mode 100644 >> index 0000000..f1a2bac >> --- /dev/null >> +++ b/tools/xdp-forwarder/set_router_iface.c >> @@ -0,0 +1,32 @@ >> +// SPDX-License-Identifier: EUPL-1.2+ >> +// SPDX-FileCopyrightText: 2025 Yureka Lilian<yureka@cyberchaos.dev> >> + >> +#include <stdio.h> >> +#include <net/if.h> >> +#include <bpf/bpf.h> >> + >> +int main(int argc, char **argv) >> +{ >> + if (argc < 2) { >> + fprintf(stderr, "missing interface name\n"); >> + return 1; >> + } >> + >> + int router_idx = if_nametoindex(argv[1]); >> + if (router_idx <= 0) { >> + perror("error getting router interface"); >> + return 1; >> + } >> + >> + int map_fd = bpf_obj_get("/sys/fs/bpf/router_iface"); >> + if (map_fd < 0) { >> + perror("failed to open bpf map"); >> + return 1; >> + } >> + >> + int id = 0; >> + if (bpf_map_update_elem(map_fd, &id, &router_idx, 0) < 0) { >> + perror("failed to update bpf map"); >> + return 1; >> + } >> +}-- > Sincerely, > Demi Marie Obenour (she/her/hers) [-- Attachment #2: Type: text/html, Size: 30594 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm 2025-09-01 20:12 [DO_NOT_APPLY v3 0/3] xdp-forwarder Yureka Lilian 2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian @ 2025-09-01 20:12 ` Yureka Lilian 2025-09-06 16:08 ` Alyssa Ross 2025-09-01 20:12 ` [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking Yureka Lilian 2 siblings, 1 reply; 10+ messages in thread From: Yureka Lilian @ 2025-09-01 20:12 UTC (permalink / raw) To: devel; +Cc: Yureka Lilian Signed-off-by: Yureka Lilian <yureka@cyberchaos.dev> --- vm/sys/net/Makefile | 8 +------ vm/sys/net/default.nix | 22 ++++++++++-------- vm/sys/net/etc/fstab | 2 ++ vm/sys/net/etc/mdev/iface | 27 +++++++--------------- vm/sys/net/etc/nftables.conf | 8 ------- vm/sys/net/etc/s6-rc/connman/dependencies | 4 ---- vm/sys/net/etc/s6-rc/connman/type | 1 - vm/sys/net/etc/s6-rc/connman/type.license | 2 -- vm/sys/net/etc/s6-rc/nftables/type | 1 - vm/sys/net/etc/s6-rc/nftables/type.license | 2 -- vm/sys/net/etc/s6-rc/nftables/up | 6 ----- 11 files changed, 23 insertions(+), 60 deletions(-) delete mode 100644 vm/sys/net/etc/nftables.conf delete mode 100644 vm/sys/net/etc/s6-rc/connman/dependencies delete mode 100644 vm/sys/net/etc/s6-rc/connman/type delete mode 100644 vm/sys/net/etc/s6-rc/connman/type.license delete mode 100644 vm/sys/net/etc/s6-rc/nftables/type delete mode 100644 vm/sys/net/etc/s6-rc/nftables/type.license delete mode 100644 vm/sys/net/etc/s6-rc/nftables/up diff --git a/vm/sys/net/Makefile b/vm/sys/net/Makefile index e681940..9576a92 100644 --- a/vm/sys/net/Makefile +++ b/vm/sys/net/Makefile @@ -34,12 +34,11 @@ VM_FILES = \ etc/init \ etc/mdev.conf \ etc/mdev/iface \ - etc/nftables.conf \ etc/passwd \ etc/s6-linux-init/run-image/service/getty-hvc0/run \ etc/s6-linux-init/scripts/rc.init \ etc/sysctl.conf -VM_DIRS = dev etc/s6-linux-init/env run proc sys var/lib/connman +VM_DIRS = dev etc/s6-linux-init/env run proc sys # These are separate because they need to be included, but putting # them as make dependencies would confuse make. @@ -59,9 +58,6 @@ build/rootfs.erofs: ../../../scripts/make-erofs.sh $(PACKAGES_FILE) $(VM_FILES) ) | ../../../scripts/make-erofs.sh $@ VM_S6_RC_FILES = \ - etc/s6-rc/connman/dependencies \ - etc/s6-rc/connman/run \ - etc/s6-rc/connman/type \ etc/s6-rc/dbus/notification-fd \ etc/s6-rc/dbus/run \ etc/s6-rc/dbus/type \ @@ -71,8 +67,6 @@ VM_S6_RC_FILES = \ etc/s6-rc/mdevd/notification-fd \ etc/s6-rc/mdevd/run \ etc/s6-rc/mdevd/type \ - etc/s6-rc/nftables/type \ - etc/s6-rc/nftables/up \ etc/s6-rc/ok-all/contents \ etc/s6-rc/ok-all/type \ etc/s6-rc/sysctl/type \ diff --git a/vm/sys/net/default.nix b/vm/sys/net/default.nix index b5873eb..10cb382 100644 --- a/vm/sys/net/default.nix +++ b/vm/sys/net/default.nix @@ -1,23 +1,22 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2021-2023 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> -import ../../../lib/call-package.nix ({ lseek, src, terminfo, pkgsStatic }: -pkgsStatic.callPackage ( +import ../../../lib/call-package.nix ({ lseek, spectrum-guest-tools, src, terminfo, pkgsMusl }: +pkgsMusl.callPackage ( { lib, stdenvNoCC, nixos, runCommand, writeClosure , erofs-utils, jq, s6-rc, util-linux, xorg -, busybox, connmanMinimal, dbus, execline, kmod, linux_latest, mdevd, nftables -, s6, s6-linux-init +, busybox, dbus, execline, kmod, linux_latest, mdevd +, s6, s6-linux-init, xdp-tools }: let inherit (lib) concatMapStringsSep; inherit (nixosAllHardware.config.hardware) firmware; - connman = connmanMinimal; - packages = [ - connman dbus execline kmod mdevd s6 s6-linux-init s6-rc + dbus execline kmod mdevd s6 s6-linux-init s6-rc xdp-tools (busybox.override { extraConfig = '' @@ -30,13 +29,16 @@ let CONFIG_RMMOD n ''; }) - - (nftables.override { withCli = false; }) ]; # Packages that should be fully linked into /usr, # (not just their bin/* files). - usrPackages = [ connman dbus firmware kernel terminfo ]; + usrPackages = [ + dbus firmware kernel terminfo + + # for xdp-forwarder + spectrum-guest-tools + ]; packagesSysroot = runCommand "packages-sysroot" { inherit packages; diff --git a/vm/sys/net/etc/fstab b/vm/sys/net/etc/fstab index 6a82ecc..5a1bbf4 100644 --- a/vm/sys/net/etc/fstab +++ b/vm/sys/net/etc/fstab @@ -1,6 +1,8 @@ # SPDX-License-Identifier: CC0-1.0 # SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> proc /proc proc defaults 0 0 devpts /dev/pts devpts defaults,gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 +bpffs /sys/fs/bpf bpf defaults 0 0 diff --git a/vm/sys/net/etc/mdev/iface b/vm/sys/net/etc/mdev/iface index 2306575..50deff9 100755 --- a/vm/sys/net/etc/mdev/iface +++ b/vm/sys/net/etc/mdev/iface @@ -1,36 +1,25 @@ #!/bin/execlineb -P # SPDX-License-Identifier: EUPL-1.2+ # SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> importas -Si INTERFACE ifte { - # This interface is connected to another VM. - - # The other VM's IP is encoded in the NIC-specific portion of the - # interface's MAC address. - backtick -E CLIENT_IP { - awk -F: "{printf \"100.64.%d.%d\\n\", \"0x\" $5, \"0x\" $6}" - /sys/class/net/${INTERFACE}/address - } - - if { ip address add 169.254.0.1/32 dev $INTERFACE } - if { ip link set $INTERFACE up } - ip route add $CLIENT_IP dev $INTERFACE + # This interface is connected to the router + if { xdp-loader load $INTERFACE /usr/lib/xdp/prog_router.o -m skb -p /sys/fs/bpf } + if { ip link set $INTERFACE promisc on } + if { set_router_iface $INTERFACE } + ip link set $INTERFACE up } { if { test $INTERFACE != lo } # This is a physical connection to a network device. - background { s6-rc -bu change connman } - if { s6-rc -bu change nftables } - if { - forx -pE module { nft_counter nft_masq } - modprobe $module - } - nft add rule ip nat postrouting oifname $INTERFACE counter masquerade + if { xdp-loader load $INTERFACE /usr/lib/xdp/prog_physical.o -m skb -p /sys/fs/bpf } + ip link set $INTERFACE up } grep -iq ^02:01: /sys/class/net/${INTERFACE}/address diff --git a/vm/sys/net/etc/nftables.conf b/vm/sys/net/etc/nftables.conf deleted file mode 100644 index 296d92c..0000000 --- a/vm/sys/net/etc/nftables.conf +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: EUPL-1.2+ -# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> - -table nat { - chain postrouting { - type nat hook postrouting priority 100; - } -} diff --git a/vm/sys/net/etc/s6-rc/connman/dependencies b/vm/sys/net/etc/s6-rc/connman/dependencies deleted file mode 100644 index 23bda19..0000000 --- a/vm/sys/net/etc/s6-rc/connman/dependencies +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 -# SPDX-FileCopyrightText: 2020 Alyssa Ross <hi@alyssa.is> -# -dbus diff --git a/vm/sys/net/etc/s6-rc/connman/type b/vm/sys/net/etc/s6-rc/connman/type deleted file mode 100644 index 5883cff..0000000 --- a/vm/sys/net/etc/s6-rc/connman/type +++ /dev/null @@ -1 +0,0 @@ -longrun diff --git a/vm/sys/net/etc/s6-rc/connman/type.license b/vm/sys/net/etc/s6-rc/connman/type.license deleted file mode 100644 index 2b3b032..0000000 --- a/vm/sys/net/etc/s6-rc/connman/type.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-License-Identifier: CC0-1.0 -SPDX-FileCopyrightText: 2020 Alyssa Ross <hi@alyssa.is> diff --git a/vm/sys/net/etc/s6-rc/nftables/type b/vm/sys/net/etc/s6-rc/nftables/type deleted file mode 100644 index bdd22a1..0000000 --- a/vm/sys/net/etc/s6-rc/nftables/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/vm/sys/net/etc/s6-rc/nftables/type.license b/vm/sys/net/etc/s6-rc/nftables/type.license deleted file mode 100644 index c49c11b..0000000 --- a/vm/sys/net/etc/s6-rc/nftables/type.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-License-Identifier: CC0-1.0 -SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> diff --git a/vm/sys/net/etc/s6-rc/nftables/up b/vm/sys/net/etc/s6-rc/nftables/up deleted file mode 100644 index 7d5f141..0000000 --- a/vm/sys/net/etc/s6-rc/nftables/up +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: EUPL-1.2+ -# SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is> - -if { modprobe nft_chain_nat } - -nft -f /etc/nftables.conf -- 2.50.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm 2025-09-01 20:12 ` Yureka Lilian @ 2025-09-06 16:08 ` Alyssa Ross 0 siblings, 0 replies; 10+ messages in thread From: Alyssa Ross @ 2025-09-06 16:08 UTC (permalink / raw) To: Yureka Lilian; +Cc: devel [-- Attachment #1: Type: text/plain, Size: 2000 bytes --] Yureka Lilian <yureka@cyberchaos.dev> writes: > diff --git a/vm/sys/net/default.nix b/vm/sys/net/default.nix > index b5873eb..10cb382 100644 > --- a/vm/sys/net/default.nix > +++ b/vm/sys/net/default.nix > @@ -1,23 +1,22 @@ > # SPDX-License-Identifier: MIT > # SPDX-FileCopyrightText: 2021-2023 Alyssa Ross <hi@alyssa.is> > +# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev> > > -import ../../../lib/call-package.nix ({ lseek, src, terminfo, pkgsStatic }: > -pkgsStatic.callPackage ( > +import ../../../lib/call-package.nix ({ lseek, spectrum-guest-tools, src, terminfo, pkgsMusl }: > +pkgsMusl.callPackage ( Switching to non-static really ought to be a separate patch, with rationale explained in the message. > > { lib, stdenvNoCC, nixos, runCommand, writeClosure > , erofs-utils, jq, s6-rc, util-linux, xorg > -, busybox, connmanMinimal, dbus, execline, kmod, linux_latest, mdevd, nftables > -, s6, s6-linux-init > +, busybox, dbus, execline, kmod, linux_latest, mdevd > +, s6, s6-linux-init, xdp-tools > }: > > let > inherit (lib) concatMapStringsSep; > inherit (nixosAllHardware.config.hardware) firmware; > > - connman = connmanMinimal; > - > packages = [ > - connman dbus execline kmod mdevd s6 s6-linux-init s6-rc > + dbus execline kmod mdevd s6 s6-linux-init s6-rc xdp-tools > > (busybox.override { > extraConfig = '' > @@ -30,13 +29,16 @@ let > CONFIG_RMMOD n > ''; > }) > - > - (nftables.override { withCli = false; }) > ]; > > # Packages that should be fully linked into /usr, > # (not just their bin/* files). > - usrPackages = [ connman dbus firmware kernel terminfo ]; > + usrPackages = [ > + dbus firmware kernel terminfo > + > + # for xdp-forwarder > + spectrum-guest-tools Shouldn't this be spectrum-driver-tools? > + ]; > > packagesSysroot = runCommand "packages-sysroot" { > inherit packages; [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 227 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking 2025-09-01 20:12 [DO_NOT_APPLY v3 0/3] xdp-forwarder Yureka Lilian 2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian 2025-09-01 20:12 ` Yureka Lilian @ 2025-09-01 20:12 ` Yureka Lilian 2025-09-08 10:05 ` Alyssa Ross 2 siblings, 1 reply; 10+ messages in thread From: Yureka Lilian @ 2025-09-01 20:12 UTC (permalink / raw) To: devel; +Cc: Yureka Lilian --- Documentation/about/architecture.adoc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/about/architecture.adoc b/Documentation/about/architecture.adoc index e32ab27..2b86616 100644 --- a/Documentation/about/architecture.adoc +++ b/Documentation/about/architecture.adoc @@ -68,3 +68,23 @@ nix-build img/live --no-out-link | xargs -o nix-tree See the https://diode.zone/w/8DBDQ6HQUe5UUdLkpDuL35[video] of Spectrum live image interactive analysis with nix-tree. + +== Networking + +The net-vm's purpose is running the Linux drivers for any physical +interfaces on the spectrum system. + +A net-vm (there could be multiple, one per IOMMU-group) will load the +xdp-forwarder XDP programs on the passed-through physical interfaces as well +as the downstream virtio interface going into the router (recognized by +its special MAC address) using mdev events. + +The net-vm needs to multiplex between the physical interfaces, as there +might be several interfaces in the same IOMMU-group. + +For this, the xdp-forwarder applies a +VLAN tag corresponding to the interface id, and redirects the packets to +the router interface (identified by the router_iface bpf map). +In the other direction the XDP program loaded on the router interface +removes one layer of VLAN tagging, and redirects the packets to the +interface read from the VLAN tag. -- 2.50.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking 2025-09-01 20:12 ` [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking Yureka Lilian @ 2025-09-08 10:05 ` Alyssa Ross 0 siblings, 0 replies; 10+ messages in thread From: Alyssa Ross @ 2025-09-08 10:05 UTC (permalink / raw) To: Yureka Lilian; +Cc: devel [-- Attachment #1: Type: text/plain, Size: 1996 bytes --] Yureka Lilian <yureka@cyberchaos.dev> writes: > --- > Documentation/about/architecture.adoc | 20 ++++++++++++++++++++ > 1 file changed, 20 insertions(+) Reviewed-by: Alyssa Ross <hi@alyssa.is> (I'll make some minor copyediting changes when it's ready to be applied, but won't trouble you with those.) When I get around to it, I'll probably rename netvm to "driver VM" or something, BTW, since given we don't control the IOMMU groups, we'll have to be prepared to drive multiple kinds of devices from one VM, assuming we ever manage to move anything aside from network devices out of the host. > diff --git a/Documentation/about/architecture.adoc b/Documentation/about/architecture.adoc > index e32ab27..2b86616 100644 > --- a/Documentation/about/architecture.adoc > +++ b/Documentation/about/architecture.adoc > @@ -68,3 +68,23 @@ nix-build img/live --no-out-link | xargs -o nix-tree > > See the https://diode.zone/w/8DBDQ6HQUe5UUdLkpDuL35[video] of Spectrum live > image interactive analysis with nix-tree. > + > +== Networking > + > +The net-vm's purpose is running the Linux drivers for any physical > +interfaces on the spectrum system. > + > +A net-vm (there could be multiple, one per IOMMU-group) will load the > +xdp-forwarder XDP programs on the passed-through physical interfaces as well > +as the downstream virtio interface going into the router (recognized by > +its special MAC address) using mdev events. > + > +The net-vm needs to multiplex between the physical interfaces, as there > +might be several interfaces in the same IOMMU-group. > + > +For this, the xdp-forwarder applies a > +VLAN tag corresponding to the interface id, and redirects the packets to > +the router interface (identified by the router_iface bpf map). > +In the other direction the XDP program loaded on the router interface > +removes one layer of VLAN tagging, and redirects the packets to the > +interface read from the VLAN tag. > -- > 2.50.1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 227 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-09-08 10:05 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-01 20:12 [DO_NOT_APPLY v3 0/3] xdp-forwarder Yureka Lilian
2025-09-01 20:12 ` [DO_NOT_APPLY v3 1/3] add xdp-forwarder Yureka Lilian
2025-09-06 12:05 ` Alyssa Ross
[not found] ` <08193621-065b-466f-8703-5ae5ecf71788@yuka.dev>
2025-09-06 16:13 ` Alyssa Ross
2025-09-06 17:07 ` [DO_NOT_APPLY v3 2/3] integrate xdp-forwarder into net-vm Demi Marie Obenour
2025-09-07 0:08 ` Yureka
2025-09-01 20:12 ` Yureka Lilian
2025-09-06 16:08 ` Alyssa Ross
2025-09-01 20:12 ` [DO_NOT_APPLY v3 3/3] docs/architecture: add paragraph about networking Yureka Lilian
2025-09-08 10:05 ` Alyssa Ross
Code repositories for project(s) associated with this public inbox https://spectrum-os.org/git/crosvm https://spectrum-os.org/git/doc https://spectrum-os.org/git/mktuntap https://spectrum-os.org/git/nixpkgs https://spectrum-os.org/git/spectrum https://spectrum-os.org/git/ucspi-vsock https://spectrum-os.org/git/www This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).