All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dave Seddon <dave.seddon.ca@gmail.com>
To: netdev@vger.kernel.org
Cc: "David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Stanislav Fomichev <sdf@fomichev.me>,
	Tom Herbert <tom@herbertland.com>,
	Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
	linux-kernel@vger.kernel.org,
	Dave Seddon <dave.seddon.ca@gmail.com>
Subject: [PATCH net-next v1 05/11] net: flow_dissector: add fast-path for single MPLS label + IP
Date: Wed, 15 Jul 2026 17:43:51 -0700	[thread overview]
Message-ID: <20260716004357.3652679-6-dave.seddon.ca@gmail.com> (raw)
In-Reply-To: <20260716004357.3652679-1-dave.seddon.ca@gmail.com>

Adds flow_dissect_fast_mpls() — straight-line read of one MPLS label
entry (ETH_P_MPLS_UC 0x8847 / ETH_P_MPLS_MC 0x8848), extracting
label / TC / TTL / BoS and writing to FLOW_DISSECTOR_KEY_MPLS lse[0]
when the dissector requests it.

Byte-identical with the slow path's __skb_flow_dissect_mpls() called
with lse_index=0, bos=1: the slow path returns FLOW_DISSECT_RET_OUT_GOOD
when BoS is set without descending into the inner packet (MPLS flow
hashing is on the label stack, not the inner 5-tuple), so this
fast-path does the same — no IP tail-call.

Multi-label stacks (BoS=0 on the first label) defer to the slow
path. A depth-2+ variant can land later, mirroring the vlan ->
qinq staging used for the VLAN gates.

Why this matters: MPLS is the dominant SP backbone L2.5 and the
existing slow-path traverses an iteration loop per label even when
the consumer just wants the label-stack flow key. The fast-path
saves the iteration overhead for the common single-label case while
keeping the slow path as escape for multi-label and the
entropy-label case (which can only fire from label 2+ anyway).

Gated by:
  - new DEFINE_STATIC_KEY_FALSE(flow_dissector_mpls_key)
  - new sysctl /proc/sys/net/flow_dissector/mpls (default 0)
  - dispatcher case htons(ETH_P_MPLS_UC) / htons(ETH_P_MPLS_MC) with
    static_branch_unlikely(&flow_dissector_mpls_key) guard

When the sysctl is 0, dispatcher cost is one not-taken JMP per
matching packet — same per-shape pattern as the other fast-path
patches.

Documentation/admin-guide/sysctl/net.rst grows an `mpls` subsection
under /proc/sys/net/flow_dissector/.

The helper also mirrors the slow path's out_good terminal writes: for
the standard flow_keys dissectors (which request no MPLS key), the
slow path returns OUT_GOOD after the first LSE with nhoff advanced
past it, so thoff, n_proto (the MPLS ethertype) and ip_proto (0) are
still written at the exit label. Skipping those writes is observable
in struct flow_keys and would break the byte-identical contract; the
in-tree KUnit equivalence test added later in this series catches
exactly this.

Assisted-by: Claude:claude-fable-5 sparse smatch
Signed-off-by: Dave Seddon <dave.seddon.ca@gmail.com>
---
 Documentation/admin-guide/sysctl/net.rst | 19 ++++++
 include/net/flow_dissector.h             |  1 +
 net/core/flow_dissector.c                | 83 ++++++++++++++++++++++++
 3 files changed, 103 insertions(+)

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index 1096e6ddca39..1ee5419ffaeb 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -534,6 +534,25 @@ code != 0).
 
 Default: 0
 
+mpls
+~~~~
+
+Single-label MPLS frames (EtherType ``ETH_P_MPLS_UC`` 0x8847 or
+``ETH_P_MPLS_MC`` 0x8848 with bottom-of-stack bit set on the first
+label). The fast-path reads the 4-byte label entry, extracts
+label / TC / TTL / BoS, and writes them to FLOW_DISSECTOR_KEY_MPLS
+lse[0] when requested. Byte-identical with the slow path's
+__skb_flow_dissect_mpls() for lse_index=0, bos=1: the slow path
+returns OUT_GOOD on BoS without descending into the inner packet
+(MPLS hashing is on the label stack, not inner 5-tuple), so this
+fast-path does the same.
+
+Multi-label stacks (BoS=0 on the first label) defer to slow path —
+extending to depth-2 or more is a follow-up, mirroring the
+vlan -> qinq staging used earlier in this series.
+
+Default: 0
+
 3. /proc/sys/net/unix - Parameters for Unix domain sockets
 ----------------------------------------------------------
 
diff --git a/include/net/flow_dissector.h b/include/net/flow_dissector.h
index df85162959cc..8df7821ba769 100644
--- a/include/net/flow_dissector.h
+++ b/include/net/flow_dissector.h
@@ -432,6 +432,7 @@ extern struct static_key_false flow_dissector_eth_ip_key;
 extern struct static_key_false flow_dissector_vlan_key;
 extern struct static_key_false flow_dissector_qinq_key;
 extern struct static_key_false flow_dissector_pppoe_key;
+extern struct static_key_false flow_dissector_mpls_key;
 
 /* struct flow_keys_digest:
  *
diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index 21a3f5e0ea05..0ec1a6a1bb0e 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -48,6 +48,8 @@ DEFINE_STATIC_KEY_FALSE(flow_dissector_qinq_key);
 EXPORT_SYMBOL(flow_dissector_qinq_key);
 DEFINE_STATIC_KEY_FALSE(flow_dissector_pppoe_key);
 EXPORT_SYMBOL(flow_dissector_pppoe_key);
+DEFINE_STATIC_KEY_FALSE(flow_dissector_mpls_key);
+EXPORT_SYMBOL(flow_dissector_mpls_key);
 
 /* IPv4 version/IHL byte of an option-less header: version 4, IHL 5. */
 #define FLOW_DIS_IPV4_VIHL_NOOPT	0x45
@@ -1396,6 +1398,73 @@ static bool flow_dissect_fast_pppoe(const struct sk_buff *skb,
 				      nhoff, hlen);
 }
 
+/* Single MPLS label (BoS=1): write FLOW_DISSECTOR_KEY_MPLS lse[0]
+ * when requested and stop -- the slow path returns OUT_GOOD on BoS
+ * without descending into the inner packet, so the fast path must
+ * not tail-call either. Multi-label stacks defer.
+ */
+static bool flow_dissect_fast_mpls(const struct sk_buff *skb,
+				   struct flow_dissector *flow_dissector,
+				   void *target_container,
+				   const void *data,
+				   __be16 proto, int nhoff, int hlen)
+{
+	struct flow_dissector_key_control *key_control;
+	struct flow_dissector_key_basic *key_basic;
+	struct flow_dissector_key_mpls *key_mpls;
+	struct flow_dissector_mpls_lse *lse;
+	const struct mpls_label *hdr;
+	u32 entry, label, bos;
+
+	if (unlikely(hlen - nhoff < (int)sizeof(*hdr)))
+		return false;
+	hdr = (const struct mpls_label *)((const u8 *)data + nhoff);
+	entry = ntohl(hdr->entry);
+	bos = (entry & MPLS_LS_S_MASK) >> MPLS_LS_S_SHIFT;
+
+	/* Multi-label stack: defer. */
+	if (!bos)
+		return false;
+
+	/* Neither MPLS key requested: the slow path returns OUT_GOOD without
+	 * writing the key; skipping the write block matches it.
+	 */
+	label = (entry & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT;
+
+	if (dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_MPLS)) {
+		key_mpls = skb_flow_dissector_target(flow_dissector,
+						     FLOW_DISSECTOR_KEY_MPLS,
+						     target_container);
+		lse = &key_mpls->ls[0];
+		lse->mpls_ttl = (entry & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT;
+		lse->mpls_bos = bos;
+		lse->mpls_tc = (entry & MPLS_LS_TC_MASK) >> MPLS_LS_TC_SHIFT;
+		lse->mpls_label = label;
+		dissector_set_mpls_lse(key_mpls, 0);
+	}
+
+	/* Mirror the slow path's out_good terminal: nhoff advances past the
+	 * LSE, then thoff/basic are written from the final proto/ip_proto.
+	 */
+	nhoff += (int)sizeof(*hdr);
+	if (dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_CONTROL)) {
+		key_control = skb_flow_dissector_target(flow_dissector,
+							FLOW_DISSECTOR_KEY_CONTROL,
+							target_container);
+		key_control->thoff = min_t(u16, nhoff,
+					   skb ? skb->len : hlen);
+	}
+	if (dissector_uses_key(flow_dissector, FLOW_DISSECTOR_KEY_BASIC)) {
+		key_basic = skb_flow_dissector_target(flow_dissector,
+						      FLOW_DISSECTOR_KEY_BASIC,
+						      target_container);
+		key_basic->n_proto = proto;
+		key_basic->ip_proto = 0;
+	}
+
+	return true;
+}
+
 /* Top-level dispatcher: eligibility check (only the two standard
  * dissectors and flag subset) + per-proto switch with per-shape
  * static_branch gating. Each case's branch is a forward not-taken JMP
@@ -1449,6 +1518,13 @@ static bool flow_dissect_fast(const struct sk_buff *skb,
 		return flow_dissect_fast_pppoe(skb, flow_dissector,
 					       target_container, data,
 					       nhoff, hlen);
+	case htons(ETH_P_MPLS_UC):
+	case htons(ETH_P_MPLS_MC):
+		if (!static_branch_unlikely(&flow_dissector_mpls_key))
+			return false;
+		return flow_dissect_fast_mpls(skb, flow_dissector,
+					      target_container, data,
+					      proto, nhoff, hlen);
 	default:
 		return false;
 	}
@@ -2598,6 +2674,13 @@ static struct ctl_table flow_dissector_sysctl_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_do_static_key,
 	},
+	{
+		.procname	= "mpls",
+		.data		= &flow_dissector_mpls_key.key,
+		.maxlen		= sizeof(flow_dissector_mpls_key),
+		.mode		= 0644,
+		.proc_handler	= proc_do_static_key,
+	},
 };
 
 static int __init flow_dissector_sysctl_init(void)
-- 
2.54.0


  parent reply	other threads:[~2026-07-16  0:44 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-16  0:43 [PATCH net-next v1 00/11] net: flow_dissector: opt-in byte-identical fast paths for common shapes Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 01/11] net: flow_dissector: gate BPF program lookup behind a static key Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 02/11] net: flow_dissector: opt-in fast-path for eth + IPv{4,6} + {TCP,UDP} Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 03/11] net: flow_dissector: add fast-path for VLAN and QinQ + IP + TCP/UDP Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 04/11] net: flow_dissector: add fast-path for PPPoE session + IPv{4,6} " Dave Seddon
2026-07-16  0:43 ` Dave Seddon [this message]
2026-07-16  0:43 ` [PATCH net-next v1 06/11] net: flow_dissector: add fast-path for IP-in-IP family (IPIP / 4in6 / 6in4) Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 07/11] net: flow_dissector: add byte-identical fast-path for plain GRE inner Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 08/11] net: flow_dissector: per-shape counters + /proc/net/flow_dissector_stats Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 09/11] net: flow_dissector: bound fast-path tunnel recursion Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 10/11] net: flow_dissector: add KUnit fast/slow path equivalence tests Dave Seddon
2026-07-16  0:43 ` [PATCH net-next v1 11/11] Documentation: networking: add flow_dissector overview and fast-path guide Dave Seddon
2026-07-16  9:50 ` [PATCH net-next v1 00/11] net: flow_dissector: opt-in byte-identical fast paths for common shapes Willem de Bruijn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260716004357.3652679-6-dave.seddon.ca@gmail.com \
    --to=dave.seddon.ca@gmail.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sdf@fomichev.me \
    --cc=tom@herbertland.com \
    --cc=willemdebruijn.kernel@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.