Repository navigation
Conversation
Contributor
|
lgtm |
tohojo
requested changes
Aug 13, 2026
tohojo
left a comment
Member
There was a problem hiding this comment.
A couple of high-level comments:
- I don't think it makes sense to hide this behind a compile-time flag. Most people use xdp-tools from distro packages, so recompiling to get the feature is not feasible. I think we should rather just make it always-available, but disable the bpf-side code if no VLAN interfaces are relevant to the configured interface set (via a
volatile constglobal variable so the verifier can just remove the code if it's not used). - It seems odd that the cost of rewriting an existing tag is way higher than pushing/popping tags. Makes me suspect that the measurement methodology is dodgy?
- Some of the comment blocks just reiterate what the codes does; please trim those.
- Let's merge the initial bugfix separately; please open a separate PR for that (you can keep the commit in this PR as well as a dependency - rebase-then-merge should do the right thing with this).
Strykar
force-pushed
the
vlans-userspace
branch
from
August 15, 2026 06:54
1981f07 to
1f23b0c
Compare
Contributor
Author
Strykar
force-pushed
the
vlans-userspace
branch
from
September 3, 2026 17:52
1f23b0c to
29ca5c8
Compare
The kernel FIB resolves a route through a VLAN upper device to that device's ifindex, which XDP cannot redirect through, so xdp-forward cannot serve VLAN topologies on stock kernels. At load time, enumerate the VLAN uppers of the configured interfaces over netlink into a BPF hash map keyed by VLAN ifindex. On a FIB result that is not a TX-port, the XDP program consults the map, redirects through the underlying physical device, and rewrites the packet's VLAN encapsulation to match the egress (tag push, pop or rewrite via bpf_xdp_adjust_head()). The untagged fast path is unchanged: the map is only consulted after a TX-port miss. The feature is always built and gates itself at runtime: the loader counts the usable VLAN uppers before the object loads and freezes the count into rodata (volatile const). With zero configured, the verifier removes every VLAN branch and the map is not created; the measured residue against a build without VLAN support is 13 instructions per packet (the gate read plus code layout), not zero. With VLANs configured, the untagged path pays 54 instructions over a stock build (around 20 of them code-layout ripple relative to a compile-gated variant of this feature); boundary-crossing packets pay 73 (pop) to 242 (push). The rodata prune relies on the verifier's frozen-rodata scalar tracking (v5.5 and later); on older kernels the skipped map creation would fail the load, and always creating the map is the graceful alternative if that floor matters. Only untagged and single-tagged 802.1Q frames are reshaped. 802.1ad and stacked tags bail back to the stack before the frame is touched. The map only holds devices the program can actually transmit through: 802.1ad uppers, VLAN devices stacked on other VLAN devices, links whose parent lives in another netns (their IFLA_LINK is meaningless locally and free to collide with a TX-port ifindex) and links with a master device are all excluded. The prune is hygiene rather than the safety property: a stacked entry's parent is itself a VLAN ifindex, which can never pass the TX-port re-check after translation. The netlink dump runs before the object loads, so only the map-update calls remain between attach and populate; a packet arriving in that window takes a miss path that returns XDP_PASS before the TTL decrement and reaches the stack unmodified. Three tests cover it, running unconditionally: test_vlan_userspace forwards through a VLAN egress (tag push out, pop back), test_vlan_rewrite forwards between two VLANs (tag rewrite both directions), both asserting the router's ForwDatagrams count stays flat, and test_vlan_qinq asserts stacked-VLAN traffic still flows and does traverse the stack. Also verified by hand on a two-port ConnectX-6 Dx with native XDP over a 25G wire, where the mlx5 rx_xdp_redirect and tx_xdp_xmit counters attribute both paths to XDP and the pushed tag is visible in wire captures. Suggested-by: Samuel Dobron <samuel@dobron.sk> Signed-off-by: Avinash Duduskar <avinash.duduskar@gmail.com>
tohojo
force-pushed
the
vlans-userspace
branch
from
October 20, 2026 10:32
a4dfed3 to
2fcc54e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


This picks up #504 as agreed there: the userspace half now, the
kernel-assisted half once the kernel support reaches a released kernel.
@enhaut proposed the approach. The implementation is fresh rather than a
rebase: the parsing-helpers refactor (c5ebaea, 006e306) conflicts with the
original patches, and rewriting on top of the VLAN-aware parse_ethhdr()
was simpler than porting.
The first commit fixes a TTL bug found on the way, #586.
How the feature works: the kernel FIB resolves a route through a VLAN
upper to that device's ifindex, which XDP cannot redirect through. At
load time, xdp-forward enumerates the VLAN uppers of the configured
interfaces over netlink into a BPF map (VLAN ifindex -> vid + physical
ifindex). When the FIB returns an ifindex that is not a TX-port, the
program translates it through the map and rewrites the frame to match
the egress (tag push, pop or rewrite via bpf_xdp_adjust_head()).
The feature is always built and arms itself at runtime: the loader
freezes the usable-VLAN count into rodata before the object loads, so
with no VLANs configured the verifier removes the VLAN code (only a
cheap runtime check remains) and the map is not created. The rodata
prune relies on frozen-rodata scalar tracking (kernel v5.5+); the
commit message states the trade if that floor matters.
takes exactly the current code path.
ingress goes back to the stack, and the map only holds devices the
program can actually transmit through (no 802.1ad uppers,
VLAN-on-VLAN, cross-netns parents, or devices with a master).
Limitations are documented in the README: the VLAN list is a load-time
snapshot, single-tagged 802.1Q only, VLAN offload must be off on
attached interfaces, and policy routing keyed on the VLAN iif does not
match.
Throughput on veth (measured on the compile-gated build this respin
replaces; the armed datapath is unchanged):
Three harness tests cover push/pop, rewrite, and QinQ fail-closed,
running unconditionally on every matrix cell. The datapath is also
wire-verified on a two-port ConnectX-6 Dx with native XDP (mlx5
counters attribute both paths to XDP; the pushed tag is visible in
wire captures).
Per-packet costs, the measurement methodology, and the corrected
numbers replacing the earlier cycles table are in the review
discussion below.
The kernel-assisted mode follows as its own PR once BPF_FIB_LOOKUP_VLAN
(bpf-next commits 35dac1daeb3c and 217828aad80d) reaches a released
kernel; it also removes the policy-routing limitation.