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
next prev 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 ` [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
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-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 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.