From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f181.google.com (mail-pg1-f181.google.com [209.85.215.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C82A52C21F0 for ; Thu, 16 Jul 2026 00:44:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.181 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784162654; cv=none; b=JYLgwbgNLhCOBNT3Va+lxNzly9zdS2YtIQsy5GRxiZJmZLCHxPYwhpWu/jrSbJFUFmVvaB96j5YfVf7cS1Ipc6/4MdQAfh2xV0rKaImYTPEoeriwm7aCENltMyI0QEPhhNTUTCK7Yo7niJrKZJkT72mJfdi0Ikme8yFkUBCWyc8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784162654; c=relaxed/simple; bh=8z2dU6C3TYRJ022H9N3Ej1VRtoM6gFjZ5RUegcBnuak=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=KjXswl6AZCq9soTI0LnvV/j20LfeWwIHVaJ8VGqMZssJcN8w3/LxsRqpeDir61R3anf/7DmOB2mCaPXj4U9DNQ+aQ0UR2qni4iicaGzl06m9mBS1iqCweBsoCsjaiVl70tRlYRlP4+odpJTl/JWiOOlTSJLTjXBKdCfJxO3lhG8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Ag07XFKm; arc=none smtp.client-ip=209.85.215.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Ag07XFKm" Received: by mail-pg1-f181.google.com with SMTP id 41be03b00d2f7-ca766c1c9ccso3516686a12.0 for ; Wed, 15 Jul 2026 17:44:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784162648; x=1784767448; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=vobfv6qQPpjyzGOl008TBJ35Kdt4Q/Z2GOdMKmOELa0=; b=Ag07XFKm1byI2WJ9DgEnCYDBrgGXnCZ81pHFMN2wCPo5ZLlu6qhy6P8ioWmb7lv4PP UteJ/2MHhrWnvgnKxol8Gp+2rrcrp9b8IW214H1UvnS9ginqGXRHmYebRc0+H1VR8D9Y ZbBcbas5hs3Tfygv0OwMWn2vI9AwZWMehU3JNXWQs8CRoV+4bPdU/Uoucv3IgDXqgJv7 yzarAlqWGhR5yaIfHK43Zuz8r6VYoPHTpi0voUlD30snlr1JL604VnzOUWC1GVgTSDGF WHZmbbC5qlLmpaAWASA3ge0dM+G7X/URzd5fQtNcYpzx7DxstkwyhN3XcjweR5innD6z rIVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784162648; x=1784767448; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=vobfv6qQPpjyzGOl008TBJ35Kdt4Q/Z2GOdMKmOELa0=; b=fQSwr194YG12EEeA/WzXUBX5q0EQk7H9CPbKLPg1hglvEOyG7O8fJ/Mn2PxcRWUEmm +9SY+TaDfRCwkSXdMesN8g4eNSRmU8l2NFz4C6YjC7LdLpessB821ia9JPhoxipcRDdW PVTzGM+T+4C7UhrI1GAnusSsCzTQbDXqttbeMuf1u7aKW2RzE+fKajUt1kewaRbZUxbH uhp5XSl7Dwx5rP2kX4jPxceijLBcSdv7OLIWJrojgvf4lPOphrjetpG4q3pVPIJsw/Lz biR6uXMC4c7icH6WjszWxNkxZDJtfbT1Mxs6NtSdVZRO6ezzkGIDAW8zEajR6lmZJeTp jn6g== X-Forwarded-Encrypted: i=1; AHgh+RovBMtyWDye7GGSRQrJG4EjWNfIYTYHDoHf6PgsYvqIWxhZ4dkKDo4l0wJjERDUJY/740oCrtfjSSmD5/A=@vger.kernel.org X-Gm-Message-State: AOJu0YyTPbx4aUZdwgqtEZLtzJGolRPocB3SDTQw3CKG/aSUGUazXRYr X5Yv4W1B5Yx7DyLb0mlucFYQAvEOcFNrchYnx/E4c73+iY/oxug9x8pR X-Gm-Gg: AfdE7cljiJIaKQOqMZ+g7qUEXMB3w5oRNwohH7CxzCS1KrJKoRhuzatKsQ3rpgKwSj0 /hviUB+E7pq9cK1R0nhF7QxUdM3LlyGJ5Bqn/3MGr36NOprHQBtB1GKBcChBce74mamzzgAE4Wv YhkpBYgXK4qZP3vBcPDMo6/zsVkgtRCmXOJbiVe1z156NdIW1wgJ9gBPUQay+yNkdPjYKsFH29P d9oChIBp6jMkVk1W6y9U5F6PD/Z+ggfjrtLMNa1kZvs8k4bHb+iUDTo6pYByKTGc00c4fylu9bC ME39dzsUtdOA9wTc8LpcrF/KFxRvG69m5+bOhVjVuxPnxLQby9BAQLUNlwyioTTtSAZ4t32jFnI ndCTdvmEP1euhIDDQHoPMsYhAv+LB7Irzlo4vNfbmAiJfBY5ayeMnWPY/BhNg2EG8cbtrvghJxr 7uZmVH6Iz6F2+4u1UfcTJheg+L+aC4GN1NUW6rD7jLAGdas37tchyVdgJN4+sFARfm0co7Cra6o 7I/py5TTA== X-Received: by 2002:a05:6a20:4310:b0:3bf:e2dc:bc7d with SMTP id adf61e73a8af0-3c36c3b1de3mr5402079637.57.1784162648082; Wed, 15 Jul 2026 17:44:08 -0700 (PDT) Received: from l.localdomain ([76.166.220.66]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13cd4284a92sm4107620c88.4.2026.07.15.17.44.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 15 Jul 2026 17:44:07 -0700 (PDT) From: Dave Seddon To: netdev@vger.kernel.org Cc: "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Stanislav Fomichev , Tom Herbert , Willem de Bruijn , linux-kernel@vger.kernel.org, Dave Seddon Subject: [PATCH net-next v1 06/11] net: flow_dissector: add fast-path for IP-in-IP family (IPIP / 4in6 / 6in4) Date: Wed, 15 Jul 2026 17:43:52 -0700 Message-ID: <20260716004357.3652679-7-dave.seddon.ca@gmail.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260716004357.3652679-1-dave.seddon.ca@gmail.com> References: <20260716004357.3652679-1-dave.seddon.ca@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds flow_dissect_fast_ipip_inner() — a small recursion helper invoked from the existing flow_dissect_fast_ipv4() and flow_dissect_fast_ipv6() helpers when the outer IP's protocol (IPv4) or nexthdr (IPv6) is IPPROTO_IPIP or IPPROTO_IPV6 and the ipip gate is on. Tail-calls into the matching inner IP fast-path then stamps FLOW_DIS_ENCAPSULATION on key_control->flags. This is the series' first tunnel descent: the fast path parses through the tunnel header and dissects the inner flow, the pattern the GRE and UDP-tunnel patches later in the series reuse. Byte-identical with the slow path's IPPROTO_IPIP and IPPROTO_IPV6 switch cases in __skb_flow_dissect(): the slow path also lets the inner IP overwrite outer addrs/basic and OR's ENCAP into the control flags. The ENCAP write must happen *after* the inner returns because the existing fast-path helpers unconditionally set key_control->flags = 0 on entry (correct for the top-level call but clobbers the encap flag during recursion); the ipip_inner helper re-establishes ENCAP after the inner returns. Coverage: outer IPv4, protocol == IPIP -> inner IPv4 outer IPv4, protocol == IPV6 -> inner IPv6 (6in4) outer IPv6, nexthdr == IPIP -> inner IPv4 (4in6) outer IPv6, nexthdr == IPV6 -> inner IPv6 FLOW_DISSECTOR_F_STOP_BEFORE_ENCAP and STOP_AT_ENCAP don't apply because the top-level dispatcher already rejects anything beyond F_PARSE_1ST_FRAG, so the fast-path entry never sees the encap-stop flags - packets that request them go to the slow path unchanged. Gated by: - new DEFINE_STATIC_KEY_FALSE(flow_dissector_ipip_key) - new sysctl /proc/sys/net/flow_dissector/ipip (default 0) - static_branch_unlikely guard inside the v4/v6 helpers, hit only when the outer protocol/nexthdr is not TCP/UDP (i.e. the TCP/UDP fast-path was about to return false anyway) Cost when off: the not-TCP/UDP fall-through grows by one static_branch_unlikely check. The TCP/UDP hot path is unchanged — the new branch is below the existing TCP/UDP fast-out. The IPv6 helper's standalone not-TCP/UDP guard becomes unreachable once the new block handles that case in full, so it is removed here. Documentation/admin-guide/sysctl/net.rst grows an `ipip` subsection under /proc/sys/net/flow_dissector/. The IPv6-outer descent mirrors the slow path's outer writes (v6addrs, addr_type) before recursing into the inner header. The slow path fills these for the outer header and the inner pass then overwrites only what it uses: an inner IPv4 overwrites just the first 8 bytes of the addrs union, leaving outer-v6 bytes in the tail. Byte-identical output means reproducing exactly that, residue included — the KUnit equivalence test's 4in6 cases fail otherwise. (A non-zero outer flow label defers before any descent — see the eth_ip patch's output contract — so no label residue exists. The IPv4-outer descent needs no mirroring: an inner IPv4 or IPv6 write always fully covers the outer's 8 address bytes.) Assisted-by: Claude:claude-fable-5 sparse smatch Signed-off-by: Dave Seddon --- Documentation/admin-guide/sysctl/net.rst | 19 ++++ include/net/flow_dissector.h | 1 + net/core/flow_dissector.c | 117 ++++++++++++++++++++++- 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst index 1ee5419ffaeb..15c8ace2c0a4 100644 --- a/Documentation/admin-guide/sysctl/net.rst +++ b/Documentation/admin-guide/sysctl/net.rst @@ -553,6 +553,25 @@ vlan -> qinq staging used earlier in this series. Default: 0 +ipip +~~~~ + +IPv4-in-IPv4 (``IPPROTO_IPIP`` 4), IPv6-in-IPv4 (``IPPROTO_IPV6`` 41 +inside an IPv4 outer), IPv4-in-IPv6 and IPv6-in-IPv6 tunnels. When +the outer IP fast-path notices the outer protocol/nexthdr is one of +these and this gate is on, it skips the outer header and tail-calls +into the inner IPv4 / IPv6 fast-path. The flow_keys ends up with the +inner 5-tuple (matching the slow path's behaviour, where the inner +parse overwrites the outer addrs/basic), plus +FLOW_DIS_ENCAPSULATION set on key_control->flags. + +Byte-identical with the slow path's IPPROTO_IPIP / IPPROTO_IPV6 +switch cases for the eligible shape. Defers on any miss — including +fragmented outer, unusual outer IHL, or unsupported inner shapes +that the inner IP fast-path itself would defer. + +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 8df7821ba769..d9b4b461cd05 100644 --- a/include/net/flow_dissector.h +++ b/include/net/flow_dissector.h @@ -433,6 +433,7 @@ 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; +extern struct static_key_false flow_dissector_ipip_key; /* struct flow_keys_digest: * diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c index 0ec1a6a1bb0e..abf4fdb0100b 100644 --- a/net/core/flow_dissector.c +++ b/net/core/flow_dissector.c @@ -50,6 +50,8 @@ 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); +DEFINE_STATIC_KEY_FALSE(flow_dissector_ipip_key); +EXPORT_SYMBOL(flow_dissector_ipip_key); /* IPv4 version/IHL byte of an option-less header: version 4, IHL 5. */ #define FLOW_DIS_IPV4_VIHL_NOOPT 0x45 @@ -65,6 +67,12 @@ static bool flow_dissect_fast_ipv6(const struct sk_buff *skb, void *target_container, const void *data, int nhoff, int hlen); +static bool flow_dissect_fast_ipip_inner(const struct sk_buff *skb, + struct flow_dissector *flow_dissector, + void *target_container, + const void *data, + __be16 inner_eth_proto, + int inner_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 @@ -1114,8 +1122,25 @@ static bool flow_dissect_fast_ipv4(const struct sk_buff *skb, return false; if (unlikely(iph->protocol != IPPROTO_TCP && - iph->protocol != IPPROTO_UDP)) + iph->protocol != IPPROTO_UDP)) { + /* IPIP / IPv6-in-IPv4 outer: defer to the inner fast-path and + * stamp ENCAP. Outer addrs are not written -- the inner pass + * overwrites them, as in the slow path. + */ + if (static_branch_unlikely(&flow_dissector_ipip_key)) { + if (iph->protocol == IPPROTO_IPIP) + return flow_dissect_fast_ipip_inner(skb, + flow_dissector, target_container, + data, htons(ETH_P_IP), + nhoff + (int)sizeof(*iph), hlen); + if (iph->protocol == IPPROTO_IPV6) + return flow_dissect_fast_ipip_inner(skb, + flow_dissector, target_container, + data, htons(ETH_P_IPV6), + nhoff + (int)sizeof(*iph), hlen); + } return false; + } thoff = nhoff + (int)sizeof(*iph); @@ -1203,8 +1228,49 @@ static bool flow_dissect_fast_ipv6(const struct sk_buff *skb, return false; if (unlikely(iph->nexthdr != IPPROTO_TCP && - iph->nexthdr != IPPROTO_UDP)) - return false; + iph->nexthdr != IPPROTO_UDP)) { + /* IPIP / IPV6-in-IPv6 tunnel outer: same pattern as the + * IPv4 helper. The nexthdr_IPIP / nexthdr_IPV6 cases + * defer to the inner IP fast-path and stamp ENCAP. + */ + bool ipip = static_branch_unlikely(&flow_dissector_ipip_key) && + (iph->nexthdr == IPPROTO_IPIP || + iph->nexthdr == IPPROTO_IPV6); + + if (!ipip) + return false; + + /* Mirror the slow path's outer-IPv6 writes before the + * descent. The slow path fills v6addrs for the outer + * header and the inner pass then overwrites only what + * it uses — an inner IPv4 leaves the tail of the addrs + * union holding outer-v6 bytes. Byte-identical means + * reproducing exactly that, residue included. (The + * outer flow label is always zero here — a non-zero + * label deferred above — so there is no label residue.) + */ + if (dissector_uses_key(flow_dissector, + FLOW_DISSECTOR_KEY_IPV6_ADDRS)) { + key_addrs = skb_flow_dissector_target(flow_dissector, + FLOW_DISSECTOR_KEY_IPV6_ADDRS, + target_container); + memcpy(&key_addrs->v6addrs.src, &iph->saddr, + sizeof(key_addrs->v6addrs.src)); + memcpy(&key_addrs->v6addrs.dst, &iph->daddr, + sizeof(key_addrs->v6addrs.dst)); + key_control = skb_flow_dissector_target(flow_dissector, + FLOW_DISSECTOR_KEY_CONTROL, + target_container); + 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), + nhoff + (int)sizeof(*iph), hlen); + } thoff = nhoff + (int)sizeof(*iph); @@ -1465,6 +1531,44 @@ static bool flow_dissect_fast_mpls(const struct sk_buff *skb, return true; } +/* IPIP / 4in6 / 6in4 inner descent: recurse into the inner IP + * fast-path, then stamp FLOW_DIS_ENCAPSULATION -- the inner helper + * zeros key_control->flags, so the ENCAP write must come after. + */ +static bool flow_dissect_fast_ipip_inner(const struct sk_buff *skb, + struct flow_dissector *flow_dissector, + void *target_container, + const void *data, + __be16 inner_eth_proto, + int inner_nhoff, int hlen) +{ + struct flow_dissector_key_control *key_control; + bool ok; + + if (inner_eth_proto == htons(ETH_P_IP)) + ok = flow_dissect_fast_ipv4(skb, flow_dissector, + target_container, data, + inner_nhoff, hlen); + else if (inner_eth_proto == htons(ETH_P_IPV6)) + ok = flow_dissect_fast_ipv6(skb, flow_dissector, + target_container, data, + inner_nhoff, hlen); + else + return false; + + if (!ok) + return false; + + 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 @@ -2681,6 +2785,13 @@ static struct ctl_table flow_dissector_sysctl_table[] = { .mode = 0644, .proc_handler = proc_do_static_key, }, + { + .procname = "ipip", + .data = &flow_dissector_ipip_key.key, + .maxlen = sizeof(flow_dissector_ipip_key), + .mode = 0644, + .proc_handler = proc_do_static_key, + }, }; static int __init flow_dissector_sysctl_init(void) -- 2.54.0