From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 4C3C116821; Sun, 07 Sep 2025 21:30:31 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id 6A1F816883; Sun, 07 Sep 2025 21:30:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-yb1-xb34.google.com (mail-yb1-xb34.google.com [IPv6:2607:f8b0:4864:20::b34]) by atuin.qyliss.net (Postfix) with ESMTPS id 8CF81167FF for ; Sun, 07 Sep 2025 21:30:26 +0000 (UTC) Received: by mail-yb1-xb34.google.com with SMTP id 3f1490d57ef6-e97021a3695so2964547276.3 for ; Sun, 07 Sep 2025 14:30:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757280625; x=1757885425; darn=spectrum-os.org; h=in-reply-to:autocrypt:from:content-language:references:cc:to :subject:user-agent:mime-version:date:message-id:from:to:cc:subject :date:message-id:reply-to; bh=pZIXAsWwlTWDEK7Z27kL8Zw1RxmCDByRPQJaThJCOXE=; b=OygDe707bhkbPQT6Q66CKwXt8b5y0Ge8WOSq8AAG9edHrZ1hH3rR+fQPMtKtYSeKUm Vb9UVkCwcdTI+lAfjEY8kVvclZKMB4jHp51GRpEiBRoMTPlJG4LFd2nGQU0w172dV7sB XWnaqnIMLXcLYzkYfBdevLrABXdLfNWCZ+URCbjX4B7zmwxn9X2W6GaVOvtp/3v6hkx9 4ri12FPPr1n2357PuISEMszWQde3TUn7K1/sBvDFWlBpd5xR00AL9dSVqIBvtn7T0JYs jNWLRAqReqqRIW8+snUt69KrTdZOg8w4NyWuDME5oj03A9YtqNgBnGhXdph8+WHLsAj/ rO0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757280625; x=1757885425; h=in-reply-to:autocrypt:from:content-language:references:cc:to :subject:user-agent:mime-version:date:message-id:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=pZIXAsWwlTWDEK7Z27kL8Zw1RxmCDByRPQJaThJCOXE=; b=TGWRTuTWI7NHpQvtYnCkrL+/LGxhiYfQAPo2ZzLtCZqnBcbyiojiiKTxDkPtJJ1N91 lRbEAXSXk8fghr8MQYX6LeHcl4ICBu/E/STFvRzkkkhlwSLUPEJSl+bJn0O3y9ec5kWi Y9Jx53dPtJAFqzsPpZyAzlSrio20h6Mu3QdCGXGzIgMr8tjkM8mZy0tQjHAsDq0bUY9z jNoowdrTx86Umkhg4rwocXZhL5aySDxmGu1lHWTznG0UjJIPUaD91N4zyqrDxGt9EVRV IAeT4Xm8/tujOwk/yssQ92b2hRGmcE+xUBxT0ArNigP072pPD12GcKRtERhizxN8jvTG j/Xg== X-Gm-Message-State: AOJu0YwI+icX5vJSAiz2/nciZ9d81en9Z+4mhg6eBuLa2Z5YV1SNV5YH 4K8qPTiayoRr7S7EBRSwQiWTbvq04G8aIg6ZSDpon/H85FyyNpVasuG9BmU1Qg== X-Gm-Gg: ASbGncvfl4s+7PVeGFm3lPOB8a/EMKAdso/is51gWu4XuADmvtFZ0NF31WlMfuJdk2j 253vssBod1NoleuxjSJ7EM0dTE8OtVSwaj87jy/UZ2vBdEWygzM91JkhVJg10RWmPpEK7OfoLBT CyPZhMt7ttgWnK3BbbI+lrYNMh9Gt6GKnukAb239weEgwpeXfhiG0uashNaN8vR6/J09A0kpNON WGq8j5hrqoH3WH6fiz1/aZlPKGo1qknHgX84tS8lspRieyxyKOPC4dI59ETvIJNNB7QtgNDFdVF 64G9oBW4etcr3ajEq5kSVQurwst97+kwMcv5KcECqh3EdUglNu+VSomOFyVhv0byf7/Pa9i0wBx FZ/Od3pmFbLKJ3FNi0Qs1kRWKvDb7Osgs4A+wEChiL0InCXa/bL6dNzVmIrU0HoXDGqGh4w1ksp AFf9sGW988mme094+c/avvSsGBM5K2Ki2uHtA= X-Google-Smtp-Source: AGHT+IEypWKS+AmRSd8RJoB5M0GrQW6KDnCrHRztJUdlK5apm/7T3ERvzU/476/48wNDAFBsPMccKw== X-Received: by 2002:a05:690c:6c85:b0:71f:c5f0:339c with SMTP id 00721157ae682-727f593e79cmr47906947b3.43.1757280624567; Sun, 07 Sep 2025 14:30:24 -0700 (PDT) Received: from [10.138.34.110] (h96-60-249-169.cncrtn.broadband.dynamic.tds.net. [96.60.249.169]) by smtp.gmail.com with ESMTPSA id 00721157ae682-723a8356bcfsm47263477b3.29.2025.09.07.14.30.23 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Sun, 07 Sep 2025 14:30:23 -0700 (PDT) Message-ID: Date: Sun, 7 Sep 2025 17:30:19 -0400 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH] tools: add xdp-forwarder To: Yureka References: <20250906141228.2357630-1-yureka@cyberchaos.dev> <9e9e071f-d999-476d-895a-9128dd36a0a5@yuka.dev> Content-Language: en-US From: Demi Marie Obenour Autocrypt: addr=demiobenour@gmail.com; keydata= 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 +MYSfkEjBz0E8CLOcAw7JIwAaeBT In-Reply-To: <9e9e071f-d999-476d-895a-9128dd36a0a5@yuka.dev> Content-Type: multipart/signed; micalg=pgp-sha256; protocol="application/pgp-signature"; boundary="------------MYuBYc8kbtlw0DtMYJ8JylnB" Message-ID-Hash: BZ7OHCIX4XDWQAW7QBN3FZWM7IKUAHVB X-Message-ID-Hash: BZ7OHCIX4XDWQAW7QBN3FZWM7IKUAHVB X-MailFrom: demiobenour@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: devel@spectrum-os.org X-Mailman-Version: 3.3.9 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This is an OpenPGP/MIME signed message (RFC 4880 and 3156) --------------MYuBYc8kbtlw0DtMYJ8JylnB Content-Type: multipart/mixed; boundary="------------3SFC0FzjdR6OzL6O0fdfWRPh"; protected-headers="v1" From: Demi Marie Obenour To: Yureka Cc: devel@spectrum-os.org Message-ID: Subject: Re: [PATCH] tools: add xdp-forwarder References: <20250906141228.2357630-1-yureka@cyberchaos.dev> <9e9e071f-d999-476d-895a-9128dd36a0a5@yuka.dev> In-Reply-To: <9e9e071f-d999-476d-895a-9128dd36a0a5@yuka.dev> --------------3SFC0FzjdR6OzL6O0fdfWRPh Content-Type: multipart/mixed; boundary="------------IanMCrGENTOkww0j1HskhEKc" --------------IanMCrGENTOkww0j1HskhEKc Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable On 9/7/25 16:28, Yureka wrote: >=20 > On 9/7/25 21:25, Demi Marie Obenour wrote: >> On 9/6/25 10:12, Yureka Lilian wrote: >>> The xdp-forwarder's purpose is implementing the functionality needed >>> within the net-vm (a VM running the Linux drivers for any physical >>> interfaces on the spectrum system). >>> >>> In the future, the net-vm will load the included XDP programs on the >>> passed-through physical interfaces as well as the downstream virtio >>> interface going into the router (recognized by its special MAC addres= s). >>> >>> The net-vm needs to multiplex between the physical interfaces, as the= re >>> might be several interfaces in the same IOMMU-group. >>> >>> For this, the XDP program loaded on the physical interfaces >>> (`prog_physical.o`) 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 loade= d on >>> the router interface (`prog_router.o`) removes one layer of VLAN tagg= ing >>> and redirects the packets to the interface read from the VLAN tag. >>> >>> The helper program `set_router_iface` is used to update the=20 >>> `router_iface` >>> bpf map to point to the interface passed as argument to the program. >>> >>> Signed-off-by: Yureka Lilian >>> --- >>> 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/meson.build | 38 +++ >>> tools/xdp-forwarder/prog_physical.c | 37 +++ >>> tools/xdp-forwarder/prog_router.c | 43 +++ >>> tools/xdp-forwarder/set_router_iface.c | 29 ++ >>> 10 files changed, 591 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/meson.build >>> 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/xdp-forwarder/include/parsing_helpers.h=20 >>> 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/d3= d3eed6ea9a63d1302bfa8b5a8e93862bfe11f0/common/parsing_helpers.h */ It looks like your mail client wrapped the lines. I demangled them in th= e C code. >>> [...] >>> +/* >>> + * 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; >>> +}; I think this (and all the subsequent structs) should be __attribute__((packed)) but am not sure. [...] >>> diff --git a/tools/xdp-forwarder/include/rewrite_helpers.h=20 >>> 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=20 >>> https://github.com/xdp-project/xdp-tutorial/blob/d3d3eed6ea9a63d1302b= fa8b5a8e93862bfe11f0/common/rewrite_helpers.h=20 >>> */ >>> +/* >>> + * This file contains functions that are used in the packetXX XDP pr= ograms 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 progr= am. >>> + */ >>> + >>> +#ifndef __REWRITE_HELPERS_H >>> +#define __REWRITE_HELPERS_H >>> + >>> +#include >>> +#include >>> +#include >>> +#include >>> + >>> +#include >>> +#include >>> + >>> +/* Pops the outermost VLAN tag off the packet. Returns the popped VL= AN ID on >>> + * success or negative errno on failure. >>> + */ >>> +static __always_inline int vlan_tag_pop(struct xdp_md *ctx, struct e= thhdr *eth) >>> +{ >>> + void *data_end =3D (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 =3D (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 =3D bpf_ntohs(vlh->h_vlan_TCI); >>> + h_proto =3D 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 check= ing >>> + * after adjusting head >>> + */ >>> + eth =3D (void *)(long)ctx->data; >>> + data_end =3D (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 =3D h_proto; >>> + >>> + return vlid; >>> +} >> In Spectrum, eth is always the packet data, so this should be able to = be >> simplified to: [...] >=20 > Not upstreamable I agree. > [...] >> In the Spectrum use-case (where the Ethernet header is always the one = in >> the packet) this should be able to be simplified to (not tested): [...= ] >=20 > Not upstreamable That makes sense. >> This also avoids needing the 'eth' parameter, as it is now assigned in= the >> function body before being read from. I'm not sure this would be > you're not sure this would be what? Should have added "upstreamable". > [...] >>> +#endif /* __REWRITE_HELPERS_H */ >>> diff --git a/tools/xdp-forwarder/meson.build=20 >>> b/tools/xdp-forwarder/meson.build >>> new file mode 100644 >>> index 0000000..7e60c11 >>> --- /dev/null >>> +++ b/tools/xdp-forwarder/meson.build >>> @@ -0,0 +1,38 @@ >>> +# SPDX-License-Identifier: EUPL-1.2+ >>> +# SPDX-FileCopyrightText: 2025 Yureka Lilian = >>> + >>> +libbpf =3D dependency('libbpf', version : '1.6.2') >>> + >>> +executable('set_router_iface', 'set_router_iface.c', >>> + dependencies : libbpf, >>> + install : true) >>> + >>> +clang =3D find_program('clang') >>> + >>> +bpf_o_cmd =3D [ >>> + clang.full_path(), >>> + '-fno-stack-protector', >>> + '-O2', >>> + '-target', 'bpf', >>> + '-I', meson.current_source_dir() + '/include', >>> + '-g', >>> + '-c', >>> + '@INPUT@', >>> + '-o', >>> + '@OUTPUT@', >>> +] >> I recommend adding -fno-strict-overflow and -fno-strict-aliasing >> here. I don't know if those are implied by -target bpf, but better >> safe than sorry. > Will do. >=20 >>> +prog_router_o =3D custom_target( >>> + input : 'prog_router.c', >>> + output : 'prog_router.o', >>> + command : bpf_o_cmd, >>> + install: true, >>> + install_dir: 'lib/xdp') >>> + >>> +prog_physical_o =3D custom_target( >>> + input : 'prog_physical.c', >>> + output : 'prog_physical.o', >>> + command : bpf_o_cmd, >>> + install: true, >>> + install_dir: 'lib/xdp') >> Meson allows providing a dependency file so that the >> target is remade if any of the headers are changed. >> Clang is able to produce such a file (-MD -MP -MF file). >=20 > I mostly got inspiration from the systemd meson files.=C2=A0I'm not ver= y=20 > familiar with meson, but I will try to add this. I think bpf_o_cmd =3D [ clang.full_path(), '-fno-stack-protector', '-fno-strict-aliasing', '-fno-strict-overflow', '-Wall', '-Wextra', '-O2', '-target', 'bpf', '-I', meson.current_source_dir() + '/include', '-g', '-c', '-o', '@OUTPUT@', '-MD', '-MP', '-MF', '@DEPFILE@', '--', '@INPUT@', ] prog_physical_o =3D custom_target( input: 'prog_physical.c', output: 'prog_physical.o', depfile: 'prog_physical.o.dep', command: bpf_o_cmd, install: true, install_dir: 'lib/xdp') should work. '--' is optional, of course; I am in the habit of adding it because it does matter in other contexts. This is also untested, but it is consistent with the Meson documentation (https://mesonbuild.com/Reference-manual_functions.html#custom_target). The above command includes the extra command-line options I mentioned (-Wall -Wextra -fno-strict-overflow -fno-strict-aliasing). > [...] >>> +SEC("xdp") >>> +int physical(struct xdp_md *ctx) >>> +{ >>> + void *data_end =3D (void *)(long)ctx->data_end; >>> + void *data =3D (void *)(long)ctx->data; >> Would it make sense to use char * here? I know that Linux uses >> arithmetic on void *, but it makes the > it makes the [... what]? Whoops, forgot to finish the sentence. It makes the code easier to understand for those familiar with userspace programming. >>> + struct hdr_cursor nh; >>> + nh.pos =3D 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; I think this is off-by-1 and should be: if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > VLAN_VID_MASK) return XDP_DROP; >>> + vlan_tag_push(ctx, eth, ctx->ingress_ifindex); >> Looks like a missing check for the return value. > Thanks, will fix. You're welcome. > [...] >>> + int vlid =3D vlan_tag_pop(ctx, eth); >>> + if (vlid < 0) { >>> + return XDP_DROP; >>> + } >> I don=E2=80=99t think Spectrum uses bits other than the VLAN ID, >> and VLAN 0 is reserved, so perhaps: >> >> if (vlid < 1 || vlid > 4096) { >> return XDP_DROP; >> } > The "< 0" check is strictly an error check. In success cases,=20 > vlan_tag_pop returns exactly the VLAN ID. Makes sense. A check for the VLAN ID being valid could be done but would be separate (I think `(vlid & VLAN_VID_MASK) !=3D 0`). > [...] >> I simplified the code into the following (only compile-tested): >> >> /* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-clause) */ >> /* >> * This file contains parsing functions that can be used in eXDP progr= ams. 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 pa= yload >> * (h_proto for Ethernet, nexthdr for IPv6), for ICMP it is the ICMP t= ype field. >> * All return values are in host byte order. >> */ >> >> #include >> #include >> #include >> #include >> #include >> #include >> >> /* >> * 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; >> } __attribute__((__packed__)); >> >> struct eth_vlan_hdr { >> struct ethhdr eth; >> struct vlan_hdr vlan; >> } __attribute__((__packed__)); >> >> static __always_inline int proto_is_vlan(__u16 h_proto) >> { >> return !!(h_proto =3D=3D bpf_htons(ETH_P_8021Q) || >> h_proto =3D=3D bpf_htons(ETH_P_8021AD)); >> } >> >> /* Pushes a new VLAN tag after the Ethernet header. Returns 0 on succe= ss, >> * -1 on failure. >> */ >> static __always_inline int vlan_tag_push(struct xdp_md *ctx, int vlid)= >> { >> struct eth_vlan_hdr *hdr; >> char *data_end; >> char *data; >> >> /* Check for valid Ethernet frame */ >> if ((unsigned long)ctx->data + ETH_ZLEN > (unsigned long)ctx->data_en= d) >> return -1; >> >> /* Add space in front of the packet */ >> if (bpf_xdp_adjust_head(ctx, -(int)sizeof(struct vlan_hdr))) >> return -1; >> >> /* Must re-fetch header pointers */ >> data_end =3D (char *)(long)ctx->data_end; >> data =3D (char *)(long)ctx->data; >> >> /* Verifier requires this (redundant) bounds check */ >> if (data + ETH_ZLEN + sizeof(struct vlan_hdr) > data_end) >> return -1; >> >> /* Move source and destination MAC addresses, populate VLAN tag >> * with ID and proto, and set outer Ethernet header to VLAN type. >> */ >> __builtin_memmove(data, data + sizeof(struct vlan_hdr), >> offsetof(struct ethhdr, h_proto)); >> hdr =3D (struct eth_vlan_hdr *)data; >> /* TODO: should this be ETH_P_8021AD? */ >> hdr->eth.h_proto =3D bpf_htons(ETH_P_8021Q); >> hdr->vlan.h_vlan_TCI =3D bpf_htons(vlid); >> return 0; >> } >> >> /* 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) >> { >> >> char *data_end =3D (void *)(long)ctx->data_end; >> struct ethhdr *eth =3D (void *)(long)ctx->data; >> struct vlan_hdr *vlh; >> int vlid; >> >> /* Check that the produced Ethernet frame will be long enough */ >> if ((unsigned long)eth + ETH_ZLEN + sizeof(*vlh) > >> (unsigned long)data_end) >> return -1; >> >> /* Careful with the parenthesis here */ >> vlh =3D (void *)(eth + 1); >> >> if (!proto_is_vlan(eth->h_proto)) >> return -1; >> >> /* Save vlan ID for returning */ >> vlid =3D bpf_ntohs(vlh->h_vlan_TCI); >> >> /* Move the source and destination MAC addresses. >> * This moves the Ethernet header by 4 bytes, so >> * be sure to cast to char * before doing pointer >> * arithmetic. >> */ >> __builtin_memmove((char *)eth + sizeof(*vlh), eth, >> offsetof(struct ethhdr, h_proto)); >> >> /* Actually adjust the head pointer */ >> if (bpf_xdp_adjust_head(ctx, (int)sizeof(*vlh))) >> return -1; >> >> return vlid; >> } >> >> // The maps are actually not used by this program, but just included >> // to keep the reference-counted pin alive before any physical interfa= ces >> // 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); >> } physical_iface SEC(".maps"); >> >> 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) >> { >> if (ctx->ingress_ifindex < 1 || ctx->ingress_ifindex > 4096) >> return XDP_DROP; >> >> if (vlan_tag_push(ctx, ctx->ingress_ifindex)) >> return XDP_DROP; >> >> return bpf_redirect_map(&physical_iface, 0, 0); >> } >> >> SEC("xdp") >> int router(struct xdp_md *ctx) >> { >> int vlid =3D vlan_tag_pop(ctx); >> >> /* Negative VLAN IDs indicate errors, and 0 is reserved and invalid. >> * Values above 4096 indicate that the priority field is used, which >> * isn't suppported out of simplicity. >> */ >> if (vlid < 1 || vlid > 4096) >> return XDP_DROP; >> >> return bpf_redirect(vlid, 0); >> } >=20 > I'm not sure what you intend with this. If there is no significant flaw= s=20 > in the upstream code, why should we write our own version? Valid point. I do think it is safe to set VLAN_MAX_DEPTH to 0, as Spectrum doesn't parse VLAN tags. --=20 Sincerely, Demi Marie Obenour (she/her/hers) --------------IanMCrGENTOkww0j1HskhEKc Content-Type: application/pgp-keys; name="OpenPGP_0xB288B55FFF9C22C1.asc" Content-Disposition: attachment; filename="OpenPGP_0xB288B55FFF9C22C1.asc" Content-Description: OpenPGP public key Content-Transfer-Encoding: quoted-printable -----BEGIN PGP PUBLIC KEY BLOCK----- xsFNBFp+A0oBEADffj6anl9/BHhUSxGTICeVl2tob7hPDdhHNgPR4C8xlYt5q49y B+l2nipdaq+4Gk6FZfqC825TKl7eRpUjMriwle4r3R0ydSIGcy4M6eb0IcxmuPYf bWpr/si88QKgyGSVZ7GeNW1UnzTdhYHuFlk8dBSmB1fzhEYEk0RcJqg4AKoq6/3/ UorR+FaSuVwT7rqzGrTlscnTDlPWgRzrQ3jssesI7sZLm82E3pJSgaUoCdCOlL7M MPCJwI8JpPlBedRpe9tfVyfu3euTPLPxwcV3L/cfWPGSL4PofBtB8NUU6QwYiQ9H zx4xOyn67zW73/G0Q2vPPRst8LBDqlxLjbtx/WLR6h3nBc3eyuZ+q62HS1pJ5EvU T1vjyJ1ySrqtUXWQ4XlZyoEFUfpJxJoN0A9HCxmHGVckzTRl5FMWo8TCniHynNXs BtDQbabt7aNEOaAJdE7to0AH3T/Bvwzcp0ZJtBk0EM6YeMLtotUut7h2Bkg1b//r 6bTBswMBXVJ5H44Qf0+eKeUg7whSC9qpYOzzrm7+0r9F5u3qF8ZTx55TJc2g656C 9a1P1MYVysLvkLvS4H+crmxA/i08Tc1h+x9RRvqba4lSzZ6/Tmt60DPM5Sc4R0nS m9BBff0Nm0bSNRS8InXdO1Aq3362QKX2NOwcL5YaStwODNyZUqF7izjK4QARAQAB zTxEZW1pIE9iZW5vdXIgKElUTCBFbWFpbCBLZXkpIDxhdGhlbmFAaW52aXNpYmxl dGhpbmdzbGFiLmNvbT7CwY4EEwEIADgWIQR2h02fEza6IlkHHHGyiLVf/5wiwQUC X6YJvQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCyiLVf/5wiwWRhD/0Y R+YYC5Kduv/2LBgQJIygMsFiRHbR4+tWXuTFqgrxxFSlMktZ6gQrQCWe38WnOXkB oY6n/5lSJdfnuGd2UagZ/9dkaGMUkqt+5WshLFly4BnP7pSsWReKgMP7etRTwn3S zk1OwFx2lzY1EnnconPLfPBc6rWG2moA6l0WX+3WNR1B1ndqpl2hPSjT2jUCBWDV rGOUSX7r5f1WgtBeNYnEXPBCUUM51pFGESmfHIXQrqFDA7nBNiIVFDJTmQzuEqIy Jl67pKNgooij5mKzRhFKHfjLRAH4mmWZlB9UjDStAfFBAoDFHwd1HL5VQCNQdqEc /9lZDApqWuCPadZN+pGouqLysesIYsNxUhJ7dtWOWHl0vs7/3qkWmWun/2uOJMQh ra2u8nA9g91FbOobWqjrDd6x3ZJoGQf4zLqjmn/P514gb697788e573WN/MpQ5XI Fl7aM2d6/GJiq6LC9T2gSUW4rbPBiqOCeiUx7Kd/sVm41p9TOA7fEG4bYddCfDsN xaQJH6VRK3NOuBUGeL+iQEVF5Xs6Yp+U+jwvv2M5Lel3EqAYo5xXTx4ls0xaxDCu fudcAh8CMMqx3fguSb7Mi31WlnZpk0fDuWQVNKyDP7lYpwc4nCCGNKCj622ZSocH AcQmX28L8pJdLYacv9pU3jPy4fHcQYvmTavTqowGnM08RGVtaSBNYXJpZSBPYmVu b3VyIChsb3ZlciBvZiBjb2RpbmcpIDxkZW1pb2Jlbm91ckBnbWFpbC5jb20+wsF4 BBMBAgAiBQJafgNKAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCyiLVf /5wiwYa/EACv8a2+MMou9cSCNoZBQaU+fTmyzft9hUE+0d5W2UY1RY3OsjFIzm9R /4SVccfsqOYLEo+S0vQMIIIqFEq3FCpXXwPzyimotps05VA8U3Bd7yseojFygOgK sAMOAee2RCaDDOnoJue01dfZMzzHPO/TVdp3OvnpWipfv5G1Xg96rwbhMLE3tg6N xwAHa31Bv4/Xq8CJOoIWvx6fcmZQpz01/lSvsYn0KrfEbTKkuUf0vM9JrCTCP2oz VNN5BYzqaq2M4r+jmSyeXLim922VOWqGkUEQ85BSEemqrRS06IU6NtEMsF8EWt/b hWjk/9GDKTcnpdJHTrMxTspExBiNrvpI2t+YPU5B/dJJAUxvmhFrbSIbdB8umBZs I3AMYrEmpAbh5x7jEjoskUC7uN3o9vpg1oCLS2ePDLtAtyBtbHnkA4xGD7ar8mem xpH9lY/i+sC6CyyIUWcUDnnagKyJP0m9ks0GLsTeOCA0bft2XA6rD6aaCnMUsndT ctrab42CV5XypjmC4U1rPJ8JQJUh1/3P48/8sMH+3krxpJ06KNWNFaUbaMTGiltZ 7x9DngklSYrX0T+2G4kVXNmjaljwkoLahwLla2gUWwBSyofXdqyhQdwZsp01KXNQ UCyT/Pg+aDcm/E7OMV3d4lf7g/CSxiX2GSEe6BlhSz+Lmd7ZJ3g32M1ARGVtaSBN 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 EtJuZYM5blWncBOJCoWMnBEcTEo/viU3GgcVRw=3D=3D =3Dx94R -----END PGP PUBLIC KEY BLOCK----- --------------IanMCrGENTOkww0j1HskhEKc-- --------------3SFC0FzjdR6OzL6O0fdfWRPh-- --------------MYuBYc8kbtlw0DtMYJ8JylnB Content-Type: application/pgp-signature; name="OpenPGP_signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="OpenPGP_signature.asc" -----BEGIN PGP SIGNATURE----- iQIyBAEBCgAdFiEEopQtqVJW1aeuo9/sszaHOrMp8lMFAmi9+WwACgkQszaHOrMp 8lMRuw/45AlOp9+Pn9/HodNMNlo0CXsZThAw41L0ROz9WjIW7SvdJLIGuWV11wRa Cvd7gHyayX7S0Iv3vkY1X3EViUxI4uQtwm5FGrxLfysKda+gagzEmmtngIhcDqn3 1O9Ns2BDZL8yQY8+vK2qep3VFYxXt0VDDZ4R2UbuWiFCszYh8a2nc4IyVQNm5uuQ JpnVm3KKyqGbxK5EfZhOdE/cu0g9dnn1bTwNnx5+DDXFSkfgm8YyD1n6HpZPGk56 mjLxrH6BoTBXGAc9X0Cg1cdHUA7ZzV0g6oDZ5BNqwdOLhGz+0d7uD7JnWTT9r8gk Y01w83ceKyCZpx+z4DKbZcHM3ax56eeNaxMSH+DaBzjqLQ+9M7xO6cjh+yhUA9fw 76EQt95HVjXQ+PXTULrTRck3bqilF70LDarIUwpZoTERHh8VTb00hXXhK2qE9QJV 7Yb4zq5CUaSCcnH6q073X+Iz1FKERN+Ktjt5wiFXMHDDCFCU8vrc0mbE6gxEE7SP /pE7xUlUrZMD0lM9C2g4Pv1s8baG2fWewdSaNXX/WldgaUDbXK+F57dvhMW0799E +NLA1elNeE34/zvIdD3rgmfF04BIcUeGeT38Ik+/32UWUA1vENKCIEvB2tztYuI2 vl0W8Fz+tNwnfPKxQw7NlHCBpUiaOaJdGyBd/IMdS9to6Jbyww== =hZ0u -----END PGP SIGNATURE----- --------------MYuBYc8kbtlw0DtMYJ8JylnB--