From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: mail.toke.dk; dkim=pass header.d=mojatatu.com header.i=@mojatatu.com header.a=rsa-sha256 header.s=google header.b=oabgF9ES; arc=none (Message is not ARC signed); dmarc=none Received: from mail-qk1-x72c.google.com (mail-qk1-x72c.google.com [IPv6:2607:f8b0:4864:20::72c]) by mail.toke.dk (Postfix) with ESMTPS id C0A7D17E7BD7 for ; Wed, 14 Oct 2026 11:55:46 +0200 (CEST) Received: by mail-qk1-x72c.google.com with SMTP id af79cd13be357-93e7457349eso18096485a.1 for ; Wed, 14 Oct 2026 02:55:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mojatatu.com; s=google; t=1791971745; x=1792576545; darn=lists.bufferbloat.net; h=content-transfer-encoding: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=8amoaRo5x4tOQa/4NouaEJxL8CWcjy+23IoAkOoXYhw=; b=oabgF9ESukzO/cAk+f59Ima6IPZpHkfVfEeEZjw//YiPBGFgLYJRUiZA1531wQashU kr9QnPSpiCVC48A8kD09UggKBqsXKKADYKI1qfya/eN4WLNkLEp+bLCN97fF529DO24X mf3nptnTZBcTCIM5KNBDQpnJHsKu1rGosYr+Q= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791971745; x=1792576545; h=content-transfer-encoding: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=8amoaRo5x4tOQa/4NouaEJxL8CWcjy+23IoAkOoXYhw=; b=mqyzirDU9HHfQxuclJ6ZHdXKycu3df38m9TYl7fjpn8B2TzPlQXI5+GbNatOuH+mh3 uRYOoQNVF2OaQcqW4WLEoHbDI4o/rmoEdAnuzZ+4wG3BgAZdvCZRIyT4iYLw+Qa9Og89 YUGD0xn/94Zqurq4b3jPPmydt3Z1DmNtyz5HAD7wt11v3RQeURfcS8QVdvnmRcpex+jV ar0FBRo52YaUH/JLsdghG3p2XEgypqHSGYRq4SXUchfRn0TnTFbwpVIYMpZTAxhGzPPQ hYQWl43vaB0dhIOOHULj/u9YSsjjqHxh1xnNee468W6weQ/vbWkBIGhtRdoxw6xgIIFi cfQw== X-Forwarded-Encrypted: i=1; AKwUvBzF8gCsrJis1VLfV2yz+oKzk0pr3jqqsN9c/32MHFyJALPGHuS8bs2lmMG5oAT72F7KquS7@lists.bufferbloat.net X-Gm-Message-State: AFq9FYIDamq2vs8jKup9Yyvr5Q44yMUGyjNNWZVIRRNFEXRDdMsrSQ4P PjfUr/LBQDrLiPqSK9DrBX9I1vXQvE/QkFm05ShpEo1y0JHHJFbrky/9ObocThV4ig== X-Gm-Gg: AYBFou3D7ly5xMQIOs8hogFo0uXN+3hkcMAbTaIfzw4++a9g5sYau9K2CBS58z5UKbE v2T0toT0/qsqJ6aYZ71gZyHqdIiNQOiKwY36YB3Mg3EajZRvdFBvBz+Yf5/JlM3QUK7non9KN5A 59G1G6aeUl9mgzWn1nrVn0qpgs+yeCL3cYbgYH7h0Z1Uq2/BKRC9W/x3EaZl8yWCDKFCdjYOwLe RozmiquixqOozeY8/ZpartpjD9kv8oVHEsf2mflKihUy1zVcJUTKCwTlxSuAxoiQ/mHbJMarNxK KOfh3DwrRJqJpmMdHSnfaQqWhPdxE/hjVsP/7bMMjSzqbWlATuI2ThKZofnPZLjBKkmZJwN8UcR AvZ2Ptgb7/7xjz808QIFHhdwH4X1e900B+Yk21+DwHky9G8cejPwCgT0HfUsGPjtimDpuOxduQO 6BFOPqSY9S5lML3S4/WrlwX3ZDv1SvhrojfNzgM69BKev5zek8dHCV980X58BTaG3/yI6OqBw7J Vn0bOdjWhyH/zIfSIl6wscOaxF0xj0BpYqstXQxZlzScY94gOrTAdDNYPTT X-Received: by 2002:a05:620a:2612:b0:93c:9eb3:9f04 with SMTP id af79cd13be357-93ebd0e41c3mr2334903485a.9.1791971745267; Wed, 14 Oct 2026 02:55:45 -0700 (PDT) Received: from mbili.tail33bf8.ts.net ([64.203.83.2]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93ef5055eb3sm453074985a.33.2026.10.14.02.55.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 02:55:44 -0700 (PDT) From: Jamal Hadi Salim To: netdev@vger.kernel.org Cc: Jamal Hadi Salim , Jiri Pirko , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Victor Nogueira , =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= , cake@lists.bufferbloat.net, Sashiko Date: Wed, 14 Oct 2026 05:55:32 -0400 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: AKMZWZY47VUFUUPHPEWIFMH3YHBB53TO X-Message-ID-Hash: AKMZWZY47VUFUUPHPEWIFMH3YHBB53TO X-MailFrom: jhs@mojatatu.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list Subject: [Cake] [PATCH net-next v6 4/4] net/sched: cake: hash before segmenting and reject an overlong split List-Id: Cake - FQ_codel the next generation Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The Fix: with CAKE_FLAG_SPLIT_GSO, cake computes the flow/host hash keys, then splits a GSO skb into segments it accounts individually. Key extraction must run on the unsegmented skb -- skb_gso_segment() can rewrite an encapsulated packet's headers to the inner ones -- and the resolution must wait until the packet is admitted, since it commits the set-associative tags, host indices and way counters that a later rejection would leave mutated (a GSO skb whose segmentation fails was dropped with that state already committed). Split cake_hash() into a side-effect-free cake_hash_keys(), run on the unsegmented skb, and cake_hash_resolve(), run only once the packet is admitted. Move the split-GSO path ahead of the resolution: sum the segment list, test the total against the ceiling before linking any segment, and drop the whole list on rejection so no persistent state is committed. The list is deferred to to_free so the tail segment's sock_wfree() never runs under the qdisc lock; a fraglist GSO, where skb_segment_list() returns the original skb as the head, drops the caller's reference and queues the list once instead of self-linking it. Reviewed-by: Victor Nogueira Signed-off-by: Jamal Hadi Salim --- v5 -> v6: split out of the single v5 patch; the hash logic is unchanged. --- net/sched/sch_cake.c | 126 ++++++++++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 31 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index adfe11737c66..c67db1c61a90 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -706,19 +706,28 @@ static u16 cake_get_flow_quantum(struct cake_tin_data *q, get_random_u16()) >> 16; } -static u32 cake_hash(struct cake_tin_data *q, const struct sk_buff *skb, - int flow_mode, u16 flow_override, u16 host_override) +struct cake_hash_keys { + u32 srchost_hash; + u32 dsthost_hash; + u32 flow_hash; +}; + +static void cake_hash_keys(const struct sk_buff *skb, int flow_mode, + u16 flow_override, u16 host_override, + struct cake_hash_keys *out) { bool hash_flows = (!flow_override && !!(flow_mode & CAKE_FLOW_FLOWS)); bool hash_hosts = (!host_override && !!(flow_mode & CAKE_FLOW_HOSTS)); bool nat_enabled = !!(flow_mode & CAKE_FLOW_NAT_FLAG); - u32 flow_hash = 0, srchost_hash = 0, dsthost_hash = 0; - u16 reduced_hash, srchost_idx, dsthost_idx; struct flow_keys keys, host_keys; bool use_skbhash = skb->l4_hash; + out->flow_hash = 0; + out->srchost_hash = 0; + out->dsthost_hash = 0; + if (unlikely(flow_mode == CAKE_FLOW_NONE)) - return 0; + return; /* If both overrides are set, or we can use the SKB hash and nat mode is * disabled, we can skip packet dissection entirely. If nat mode is @@ -753,50 +762,65 @@ static u32 cake_hash(struct cake_tin_data *q, const struct sk_buff *skb, switch (host_keys.control.addr_type) { case FLOW_DISSECTOR_KEY_IPV4_ADDRS: host_keys.addrs.v4addrs.src = 0; - dsthost_hash = flow_hash_from_keys(&host_keys); + out->dsthost_hash = flow_hash_from_keys(&host_keys); host_keys.addrs.v4addrs.src = keys.addrs.v4addrs.src; host_keys.addrs.v4addrs.dst = 0; - srchost_hash = flow_hash_from_keys(&host_keys); + out->srchost_hash = flow_hash_from_keys(&host_keys); break; case FLOW_DISSECTOR_KEY_IPV6_ADDRS: memset(&host_keys.addrs.v6addrs.src, 0, sizeof(host_keys.addrs.v6addrs.src)); - dsthost_hash = flow_hash_from_keys(&host_keys); + out->dsthost_hash = flow_hash_from_keys(&host_keys); host_keys.addrs.v6addrs.src = keys.addrs.v6addrs.src; memset(&host_keys.addrs.v6addrs.dst, 0, sizeof(host_keys.addrs.v6addrs.dst)); - srchost_hash = flow_hash_from_keys(&host_keys); + out->srchost_hash = flow_hash_from_keys(&host_keys); break; default: - dsthost_hash = 0; - srchost_hash = 0; + out->dsthost_hash = 0; + out->srchost_hash = 0; } /* This *must* be after the above switch, since as a * side-effect it sorts the src and dst addresses. */ if (hash_flows && !use_skbhash) - flow_hash = flow_hash_from_keys(&keys); + out->flow_hash = flow_hash_from_keys(&keys); skip_hash: if (flow_override) - flow_hash = flow_override - 1; + out->flow_hash = flow_override - 1; else if (use_skbhash && (flow_mode & CAKE_FLOW_FLOWS)) - flow_hash = skb->hash; + out->flow_hash = skb->hash; if (host_override) { - dsthost_hash = host_override - 1; - srchost_hash = host_override - 1; + out->dsthost_hash = host_override - 1; + out->srchost_hash = host_override - 1; } if (!(flow_mode & CAKE_FLOW_FLOWS)) { if (flow_mode & CAKE_FLOW_SRC_IP) - flow_hash ^= srchost_hash; + out->flow_hash ^= out->srchost_hash; if (flow_mode & CAKE_FLOW_DST_IP) - flow_hash ^= dsthost_hash; + out->flow_hash ^= out->dsthost_hash; } +} + +/* Commit the resolved flow into the set-associative table. Mutates + * q->tags[], the flow host indices, the way counters and the host + * bulk-flow counts, so run only after admission. + */ +static u32 cake_hash_resolve(struct cake_tin_data *q, + const struct cake_hash_keys *keys, int flow_mode) +{ + u32 flow_hash = keys->flow_hash, srchost_hash = keys->srchost_hash, + dsthost_hash = keys->dsthost_hash; + u16 reduced_hash, srchost_idx, dsthost_idx; + + if (unlikely(flow_mode == CAKE_FLOW_NONE)) + return 0; reduced_hash = flow_hash % CAKE_QUEUES; @@ -1763,11 +1787,14 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct cake_sched_data *q = qdisc_priv(sch); int len = qdisc_pkt_len(skb), ret; u16 flow_override, host_override; + struct sk_buff *segs = NULL; + struct cake_hash_keys keys; struct sk_buff *ack = NULL; ktime_t now = ktime_get(); struct cake_tin_data *b; struct cake_flow *flow; bool same_flow = false; + u64 slen = 0; /* Classify first: a terminal TC action consumes the packet. */ if (cake_tcf_classify(sch, skb, &flow_override, &host_override, &ret)) { @@ -1787,12 +1814,55 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, QDISC_DROP_OVERLIMIT); } - /* choose flow to insert into */ + /* Tin selection and key extraction run on the unsegmented skb: + * skb_gso_segment() rewrites an encapsulated GSO packet's headers to + * the inner ones, changing the keys. Neither commits state. + */ b = cake_select_tin(sch, skb); - idx = cake_hash(b, skb, q->config->flow_mode, flow_override, - host_override) + 1; + cake_hash_keys(skb, q->config->flow_mode, flow_override, + host_override, &keys); + + if (qdisc_pkt_segs(skb) > 1 && q->config->rate_flags & CAKE_FLAG_SPLIT_GSO) { + struct sk_buff *nskb, *seg; + netdev_features_t features = netif_skb_features(skb); + + segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK); + if (IS_ERR_OR_NULL(segs)) + return qdisc_drop(skb, sch, to_free); + + /* The split path accounts the segment sum, not the + * stab-adjusted qdisc_pkt_len(), so the top check does not + * bound it. Sum the list and reject it whole. + */ + skb_list_walk_safe(segs, seg, nskb) + slen += seg->len; + + if (unlikely((u64)sch->qstats.backlog + slen > QDISC_MAX_BACKLOG)) { + qdisc_qstats_overlimit(sch); + /* skb_gso_segment() moved the sock_wfree destructor to + * the tail segment; defer the whole list to to_free, + * never free it under the qdisc lock. + * + * Fraglist GSO is the exception: skb_segment_list() + * returns the original skb as the head with an extra + * ref (segs == skb), so drop the caller's ref and + * queue the list once, or the two drops self-link it. + */ + if (unlikely(segs == skb)) { + tcf_set_qdisc_drop_reason(skb, + QDISC_DROP_OVERLIMIT); + consume_skb(skb); + return qdisc_drop_all(segs, sch, to_free); + } + __qdisc_drop_all(segs, to_free); + return qdisc_drop_reason(skb, sch, to_free, + QDISC_DROP_OVERLIMIT); + } + } + + /* Admitted: resolve the keys and commit flow/shaper state. */ + idx = cake_hash_resolve(b, &keys, q->config->flow_mode); tin = (u32)(b - q->tins); - idx--; flow = &b->flows[idx]; /* ensure shaper state isn't stale */ @@ -1819,14 +1889,9 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (unlikely(len > b->max_skblen)) WRITE_ONCE(b->max_skblen, len); - if (qdisc_pkt_segs(skb) > 1 && q->config->rate_flags & CAKE_FLAG_SPLIT_GSO) { - struct sk_buff *segs, *nskb, *seg; - netdev_features_t features = netif_skb_features(skb); - unsigned int slen = 0, numsegs = 0; - - segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK); - if (IS_ERR_OR_NULL(segs)) - return qdisc_drop(skb, sch, to_free); + if (segs) { + struct sk_buff *nskb, *seg; + unsigned int numsegs = 0; skb_list_walk_safe(segs, seg, nskb) { skb_mark_not_on_list(seg); @@ -1840,7 +1905,6 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, qdisc_qlen_inc(sch); numsegs++; q->buffer_used += seg->truesize; - slen += seg->len; WRITE_ONCE(b->packets, b->packets + 1); } -- 2.43.0