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 04/11] net: flow_dissector: add fast-path for PPPoE session + IPv{4,6} + TCP/UDP
Date: Wed, 15 Jul 2026 17:43:50 -0700	[thread overview]
Message-ID: <20260716004357.3652679-5-dave.seddon.ca@gmail.com> (raw)
In-Reply-To: <20260716004357.3652679-1-dave.seddon.ca@gmail.com>

Adds flow_dissect_fast_pppoe() — a straight-line extractor for PPPoE
session frames (ETH_P_PPP_SES, EtherType 0x8864 per RFC 2516) carrying
IPv4 or IPv6 inside PPP. Strips the 6-byte pppoe_hdr + 2-byte PPP
protocol field, writes FLOW_DISSECTOR_KEY_PPPOE (session_id, ppp_proto,
type) when requested, and tail-calls into the eth_ip fast-path for
the inner IPv4 or IPv6 + TCP/UDP.

Byte-identical with the slow path's `case htons(ETH_P_PPP_SES)` block
for the eligible shape (RFC-2516-valid header + PPP_IP / PPP_IPV6 +
straight-line IP). On any miss — invalid header, PFC-compressed PPP
proto, PPP_MPLS_UC / PPP_MPLS_MC (defer until MPLS fast-path lands),
LCP / IPCP / other control frames — returns false so the dispatcher
falls through to the slow path. Output is exactly what the slow path
would produce.

Why this matters: PPPoE is the dominant L2 over millions of ISP last-
mile DSL/FTTH links, and the existing slow-path PPPoE block does a
__skb_header_pointer + per-field switch on every packet at the
graph-walk entry. The fast-path saves the walk overhead for the
common IPv4 / IPv6 inner case while keeping the slow path as the
escape valve for control / less-common inner protos.

Gated by:
  - new DEFINE_STATIC_KEY_FALSE(flow_dissector_pppoe_key)
  - new sysctl /proc/sys/net/flow_dissector/pppoe (default 0)
  - dispatcher case `htons(ETH_P_PPP_SES)` with
    `static_branch_unlikely(&flow_dissector_pppoe_key)` guard

When the sysctl is 0, the dispatcher hits a forward not-taken JMP —
same per-call cost as the vlan / qinq cases. The new dispatcher
case also doesn't widen the switch table (it adds one case to a
switch the compiler can already lower to a jump table on the
ETH_P_* constants).

Documentation/admin-guide/sysctl/net.rst grows a `pppoe` subsection
under the existing `/proc/sys/net/flow_dissector/` chapter, between
qinq and the next chapter, matching the existing per-shape layout.

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

diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index 824f4b735caa..1096e6ddca39 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -516,6 +516,24 @@ the outer entry is off).
 
 Default: 0
 
+pppoe
+~~~~~
+
+PPPoE session frames (RFC 2516, EtherType ``ETH_P_PPP_SES`` 0x8864)
+carrying IPv4 or IPv6 inside PPP. The fast-path strips the 6-byte
+PPPoE session header + 2-byte PPP protocol field, writes
+FLOW_DISSECTOR_KEY_PPPOE (session_id, ppp_proto, type) when the
+dissector requests it, and tail-calls into the eth_ip fast-path for
+the inner IPv4 / IPv6 + TCP/UDP. Byte-identical with the slow path
+for the eligible shape.
+
+Defers to slow path for: PPP_MPLS_UC / PPP_MPLS_MC (until an MPLS
+fast-path lands), PFC-compressed PPP protocol fields, LCP/IPCP/etc.
+control frames, and any malformed PPPoE header (ver != 1, type != 1,
+code != 0).
+
+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 4f36f3889a28..df85162959cc 100644
--- a/include/net/flow_dissector.h
+++ b/include/net/flow_dissector.h
@@ -431,6 +431,7 @@ extern struct flow_dissector flow_keys_basic_dissector;
 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;
 
 /* struct flow_keys_digest:
  *
diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index 826314e28db0..21a3f5e0ea05 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -46,6 +46,8 @@ DEFINE_STATIC_KEY_FALSE(flow_dissector_vlan_key);
 EXPORT_SYMBOL(flow_dissector_vlan_key);
 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);
 
 /* IPv4 version/IHL byte of an option-less header: version 4, IHL 5. */
 #define FLOW_DIS_IPV4_VIHL_NOOPT	0x45
@@ -1333,6 +1335,67 @@ static bool flow_dissect_fast_vlan(const struct sk_buff *skb,
 	}
 }
 
+/* PPPoE session (RFC 2516) + 2-byte PPP protocol: write
+ * FLOW_DISSECTOR_KEY_PPPOE if requested, then tail-call the eth_ip
+ * fast-path for PPP_IP / PPP_IPV6. Anything else (PFC-compressed
+ * protocol, LCP, MPLS) defers.
+ */
+static bool flow_dissect_fast_pppoe(const struct sk_buff *skb,
+				    struct flow_dissector *flow_dissector,
+				    void *target_container,
+				    const void *data,
+				    int nhoff, int hlen)
+{
+	struct flow_dissector_key_pppoe *key_pppoe;
+	const struct {
+		struct pppoe_hdr hdr;
+		__be16 proto;
+	} *hdr;
+	__be16 inner_eth_proto;
+	u16 ppp_proto;
+
+	if (unlikely(hlen - nhoff < (int)sizeof(*hdr)))
+		return false;
+	hdr = (const void *)((const u8 *)data + nhoff);
+	if (!is_pppoe_ses_hdr_valid(&hdr->hdr))
+		return false;
+
+	ppp_proto = ntohs(hdr->proto);
+	switch (ppp_proto) {
+	case PPP_IP:
+		inner_eth_proto = htons(ETH_P_IP);
+		break;
+	case PPP_IPV6:
+		inner_eth_proto = htons(ETH_P_IPV6);
+		break;
+	default:
+		/* MPLS unicast/multicast + everything else (LCP, IPCP,
+		 * compressed PFC, ...) defers to the slow path.
+		 */
+		return false;
+	}
+
+	if (dissector_uses_key(flow_dissector,
+			       FLOW_DISSECTOR_KEY_PPPOE)) {
+		key_pppoe = skb_flow_dissector_target(flow_dissector,
+						      FLOW_DISSECTOR_KEY_PPPOE,
+						      target_container);
+		key_pppoe->session_id = hdr->hdr.sid;
+		key_pppoe->ppp_proto = htons(ppp_proto);
+		key_pppoe->type = htons(ETH_P_PPP_SES);
+	}
+
+	nhoff += PPPOE_SES_HLEN;
+
+	if (inner_eth_proto == htons(ETH_P_IP))
+		return flow_dissect_fast_ipv4(skb, flow_dissector,
+					      target_container, data,
+					      nhoff, hlen);
+	return flow_dissect_fast_ipv6(skb, flow_dissector,
+				      target_container, data,
+				      nhoff, hlen);
+}
+
 /* 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
@@ -1380,6 +1443,12 @@ static bool flow_dissect_fast(const struct sk_buff *skb,
 		return flow_dissect_fast_ipv6(skb, flow_dissector,
 					      target_container, data,
 					      nhoff, hlen);
+	case htons(ETH_P_PPP_SES):
+		if (!static_branch_unlikely(&flow_dissector_pppoe_key))
+			return false;
+		return flow_dissect_fast_pppoe(skb, flow_dissector,
+					       target_container, data,
+					       nhoff, hlen);
 	default:
 		return false;
 	}
@@ -2522,6 +2591,13 @@ static struct ctl_table flow_dissector_sysctl_table[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_set_qinq_key,
 	},
+	{
+		.procname	= "pppoe",
+		.data		= &flow_dissector_pppoe_key.key,
+		.maxlen		= sizeof(flow_dissector_pppoe_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 ` Dave Seddon [this message]
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 ` [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

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-5-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