Netdev List
 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 07/11] net: flow_dissector: add byte-identical fast-path for plain GRE inner
Date: Wed, 15 Jul 2026 17:43:53 -0700	[thread overview]
Message-ID: <20260716004357.3652679-8-dave.seddon.ca@gmail.com> (raw)
In-Reply-To: <20260716004357.3652679-1-dave.seddon.ca@gmail.com>

Adds flow_dissect_fast_gre_inner() — invoked from inside the
existing flow_dissect_fast_ipv4() / flow_dissect_fast_ipv6() helpers
when the outer IP's protocol/nexthdr is IPPROTO_GRE and the GRE gate
is on.

Byte-identical with the slow path's __skb_flow_dissect_gre() for the
subset where:

  - GRE version == 0 (v1 = PPTP defers to slow path)
  - All GRE flags clear (no CSUM, KEY, SEQ, ROUTING)
  - protocol == ETH_P_IP or ETH_P_IPV6 (no TEB, PPP, MPLS-over-GRE)

In that subset, the GRE base header is exactly 4 bytes; slow path also
descends to inner IP (sets *p_proto = hdr->protocol and returns
PROTO_AGAIN) and stamps FLOW_DIS_ENCAPSULATION on key_control->flags.
The fast-path produces the same output: skips the 4-byte header,
tail-calls into the inner IP fast-path, re-establishes ENCAP on
key_control after the inner overwrites it (same pattern as the
IPIP family fast-path from the prior patch).

Why this matters: plain GRE (no key, no checksum) is the dominant
shape for cloud backbone tunnels (AWS GRE between regions, GCP
inter-region overlays) and corporate site-to-site VPNs that don't
need per-tunnel keying. The existing slow-path GRE handler does a
__skb_header_pointer + flag-walk per packet; the fast-path saves
that for the common case while keeping the slow path as the escape
valve for GRE-with-flags, v1/PPTP, TEB, and PPP-over-GRE.

Gated by:
  - new DEFINE_STATIC_KEY_FALSE(flow_dissector_gre_key)
  - new sysctl /proc/sys/net/flow_dissector/gre (default 0)
  - static_branch_unlikely guard inside the v4/v6 helpers, on the
    same not-TCP/UDP fall-through path as the IPIP gate (so the
    eth_ip hot path remains unchanged when this is off)

Cost when off: the not-TCP/UDP fall-through grows by one
static_branch_unlikely check on top of the IPIP check. The TCP/UDP
hot path is untouched.

GRE-with-KEY (used in some MPLS-over-GRE and cloud-overlay
deployments) is a natural follow-up: same descent shape with the
optional 4-byte key field read and a write to
FLOW_DISSECTOR_KEY_GRE_KEYID.

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

Classification note: the slow path already descends through plain
GRE unconditionally, so this fast path is byte-identical with
today's behaviour — a pure CPU saving. That is unlike the UDP-tunnel
descents later in the series (VXLAN/Geneve/GTP-U/FOU/GUE), where the
slow path stops at the outer UDP header today and the descent itself
is the new, gated behaviour.

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

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index 15c8ace2c0a4..dbc819740db0 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -572,6 +572,31 @@ that the inner IP fast-path itself would defer.
 
 Default: 0
 
+gre
+~~~
+
+GRE-encapsulated inner IP (``IPPROTO_GRE`` 47). The fast-path
+mirrors the slow path's __skb_flow_dissect_gre() descent for the
+common subset:
+
+- GRE version 0 (the v1 PPTP variant defers to slow path)
+- All GRE flags clear (no GRE_CSUM, GRE_KEY, GRE_SEQ, GRE_ROUTING —
+  i.e. plain 4-byte GRE base header)
+- protocol field is ``ETH_P_IP`` 0x0800 or ``ETH_P_IPV6`` 0x86DD
+  (no Transparent Ethernet Bridging, no PPP-over-GRE, no MPLS-
+  over-GRE)
+
+In that subset, slow path also descends to inner IP and stamps
+``key_control->flags |= FLOW_DIS_ENCAPSULATION``; the fast-path
+produces the same output.
+
+GRE-with-KEY (common in MPLS-over-GRE deployments and some cloud
+overlays) is a follow-up patch — same descent shape with an
+additional 4-byte key field read and a write to
+``FLOW_DISSECTOR_KEY_GRE_KEYID``.
+
+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 d9b4b461cd05..ba7226a42d19 100644
--- a/include/net/flow_dissector.h
+++ b/include/net/flow_dissector.h
@@ -434,6 +434,7 @@ 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;
 extern struct static_key_false flow_dissector_ipip_key;
+extern struct static_key_false flow_dissector_gre_key;
 
 /* struct flow_keys_digest:
  *
diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index abf4fdb0100b..b79c80612dee 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -52,6 +52,8 @@ DEFINE_STATIC_KEY_FALSE(flow_dissector_mpls_key);
 EXPORT_SYMBOL(flow_dissector_mpls_key);
 DEFINE_STATIC_KEY_FALSE(flow_dissector_ipip_key);
 EXPORT_SYMBOL(flow_dissector_ipip_key);
+DEFINE_STATIC_KEY_FALSE(flow_dissector_gre_key);
+EXPORT_SYMBOL(flow_dissector_gre_key);
 
 /* IPv4 version/IHL byte of an option-less header: version 4, IHL 5. */
 #define FLOW_DIS_IPV4_VIHL_NOOPT	0x45
@@ -73,6 +75,11 @@ static bool flow_dissect_fast_ipip_inner(const struct sk_buff *skb,
 					 const void *data,
 					 __be16 inner_eth_proto,
 					 int inner_nhoff, int hlen);
+static bool flow_dissect_fast_gre_inner(const struct sk_buff *skb,
+					struct flow_dissector *flow_dissector,
+					void *target_container,
+					const void *data,
+					int nhoff, int hlen);
 
 /* One of the two dissectors the fast-path eligibility check admits;
  * defined here so flow_dissect_fast() below can reference it (its keys
@@ -1139,6 +1146,11 @@ static bool flow_dissect_fast_ipv4(const struct sk_buff *skb,
 						data, htons(ETH_P_IPV6),
 						nhoff + (int)sizeof(*iph), hlen);
 		}
+		if (static_branch_unlikely(&flow_dissector_gre_key) &&
+		    iph->protocol == IPPROTO_GRE)
+			return flow_dissect_fast_gre_inner(skb, flow_dissector,
+					target_container, data,
+					nhoff + (int)sizeof(*iph), hlen);
 		return false;
 	}
 
@@ -1236,8 +1248,10 @@ static bool flow_dissect_fast_ipv6(const struct sk_buff *skb,
 		bool ipip = static_branch_unlikely(&flow_dissector_ipip_key) &&
 			    (iph->nexthdr == IPPROTO_IPIP ||
 			     iph->nexthdr == IPPROTO_IPV6);
+		bool gre = static_branch_unlikely(&flow_dissector_gre_key) &&
+			   iph->nexthdr == IPPROTO_GRE;
 
-		if (!ipip)
+		if (!ipip && !gre)
 			return false;
 
 		/* Mirror the slow path's outer-IPv6 writes before the
@@ -1264,11 +1278,15 @@ static bool flow_dissect_fast_ipv6(const struct sk_buff *skb,
 			key_control->addr_type = FLOW_DISSECTOR_KEY_IPV6_ADDRS;
 		}
 
-		return flow_dissect_fast_ipip_inner(skb,
-				flow_dissector, target_container,
-				data,
-				iph->nexthdr == IPPROTO_IPIP ?
-				htons(ETH_P_IP) : htons(ETH_P_IPV6),
+		if (ipip)
+			return flow_dissect_fast_ipip_inner(skb,
+					flow_dissector, target_container,
+					data,
+					iph->nexthdr == IPPROTO_IPIP ?
+					htons(ETH_P_IP) : htons(ETH_P_IPV6),
+					nhoff + (int)sizeof(*iph), hlen);
+		return flow_dissect_fast_gre_inner(skb, flow_dissector,
+				target_container, data,
 				nhoff + (int)sizeof(*iph), hlen);
 	}
 
@@ -1569,6 +1587,70 @@ static bool flow_dissect_fast_ipip_inner(const struct sk_buff *skb,
 	return true;
 }
 
+/* Plain-GRE inner descent (version 0, no flags, inner IP): a 4-byte
+ * base header the slow path steps over with PROTO_AGAIN. Any flags,
+ * version 1 (PPTP) or a non-IP inner defers. ENCAP is stamped after
+ * the inner pass, which zeros key_control->flags.
+ */
+static bool flow_dissect_fast_gre_inner(const struct sk_buff *skb,
+					struct flow_dissector *flow_dissector,
+					void *target_container,
+					const void *data,
+					int nhoff, int hlen)
+{
+	struct flow_dissector_key_control *key_control;
+	const struct gre_base_hdr *hdr;
+	__be16 inner_proto;
+	int inner_nhoff;
+	bool ok;
+
+	if (unlikely(hlen - nhoff < (int)sizeof(*hdr)))
+		return false;
+	hdr = (const struct gre_base_hdr *)((const u8 *)data + nhoff);
+
+	/* flags field carries the GRE control flags *and* the 3-bit
+	 * version in its low bits. A zero word means "no flags set
+	 * AND version 0" — the byte-identical fast subset.
+	 */
+	if (hdr->flags != 0)
+		return false;
+
+	switch (hdr->protocol) {
+	case htons(ETH_P_IP):
+		inner_proto = htons(ETH_P_IP);
+		break;
+	case htons(ETH_P_IPV6):
+		inner_proto = htons(ETH_P_IPV6);
+		break;
+	default:
+		return false;
+	}
+
+	inner_nhoff = nhoff + (int)sizeof(*hdr);
+
+	if (inner_proto == htons(ETH_P_IP))
+		ok = flow_dissect_fast_ipv4(skb, flow_dissector,
+					    target_container, data,
+					    inner_nhoff, hlen);
+	else
+		ok = flow_dissect_fast_ipv6(skb, flow_dissector,
+					    target_container, data,
+					    inner_nhoff, hlen);
+
+	if (!ok)
+		return false;
+
+	/* Re-establish ENCAP after the inner pass zeroed key_control->flags. */
+	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->flags |= FLOW_DIS_ENCAPSULATION;
+	}
+	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
@@ -2792,6 +2874,13 @@ static struct ctl_table flow_dissector_sysctl_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_do_static_key,
 	},
+	{
+		.procname	= "gre",
+		.data		= &flow_dissector_gre_key.key,
+		.maxlen		= sizeof(flow_dissector_gre_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: 12+ 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 ` [PATCH net-next v1 05/11] net: flow_dissector: add fast-path for single MPLS label + IP Dave Seddon
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 ` Dave Seddon [this message]
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

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-8-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox