 net/sched: Fix backlog accounting in qdisc_dequeue_internal · gregkh/linux@52bf272 · GitHub Skip to content Navigation Menu Toggle navigation Sign in Appearance settings Platform AI CODE CREATION GitHub Copilot Write better code with AI GitHub Spark Build and deploy intelligent apps GitHub Models Manage and compare prompts MCP Registry New Integrate external tools DEVELOPER WORKFLOWS Actions Automate any workflow Codespaces Instant dev environments Issues Plan and track work Code Review Manage code changes APPLICATION SECURITY GitHub Advanced Security Find and fix vulnerabilities Code security Secure your code as you build Secret protection  EXPLORE Why GitHub Documentation Blog Changelog Marketplace View all features Solutions BY COMPANY SIZE Enterprises Small and medium teams Startups Nonprofits BY USE CASE App Modernization DevSecOps DevOps CI/CD View all use cases BY INDUSTRY Healthcare Financial services Manufacturing Government View all industries View all solutions Resources EXPLORE BY TOPIC AI Software Development DevOps Security View all topics EXPLORE BY TYPE Customer stories Events &amp; webinars Ebooks &amp; reports Business insights GitHub Skills SUPPORT &amp; SERVICES Documentation Customer support Community forum Trust center Partners View all resources Open Source COMMUNITY GitHub Sponsors Fund open source developers PROGRAMS Security Lab Maintainer Community Accelerator GitHub Stars Archive Program REPOSITORIES Topics Trending Collections Enterprise ENTERPRISE SOLUTIONS Enterprise platform AI-powered developer platform AVAILABLE ADD-ONS GitHub Advanced Security Enterprise-grade security features Copilot for Business Enterprise-grade AI features Premium Support Enterprise-grade 24/7 support Pricing Search or jump to... Search code, repositories, users, issues, pull requests... --> Search Clear Search syntax tips Provide feedback --> We read every piece of feedback, and take your input very seriously.  Cancel Submit feedback Saved searches Use saved searches to filter your results more quickly --> Name Query To see all available qualifiers, see our documentation . Cancel Create saved search Sign in Sign up Appearance settings Resetting focus You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert {{ message }} gregkh / linux Public Notifications You must be signed in to change notification settings Fork 686 Star 644 Code Actions Security and quality 0 Insights Additional navigation options Code Actions Security and quality Insights Commit 52bf272 Browse files Browse files BitsByWill authored and kuba-moo committed net/sched: Fix backlog accounting in qdisc_dequeue_internal This issue applies for the following qdiscs: hhf, fq, fq_codel, and fq_pie, and occurs in their change handlers when adjusting to the new limit. The problem is the following in the values passed to the subsequent qdisc_tree_reduce_backlog call given a tbf parent: When the tbf parent runs out of tokens, skbs of these qdiscs will be placed in gso_skb. Their peek handlers are qdisc_peek_dequeued, which accounts for both qlen and backlog. However, in the case of qdisc_dequeue_internal, ONLY qlen is accounted for when pulling from gso_skb. This means that these qdiscs are missing a qdisc_qstats_backlog_dec when dropping packets to satisfy the new limit in their change handlers. One can observe this issue with the following (with tc patched to support a limit of 0): export TARGET=fq tc qdisc del dev lo root tc qdisc add dev lo root handle 1: tbf rate 8bit burst 100b latency 1ms tc qdisc replace dev lo handle 3: parent 1:1 $TARGET limit 1000 echo ''; echo 'add child'; tc -s -d qdisc show dev lo ping -I lo -f -c2 -s32 -W0.001 127.0.0.1 2&gt;&amp;1 &gt;/dev/null echo ''; echo 'after ping'; tc -s -d qdisc show dev lo tc qdisc change dev lo handle 3: parent 1:1 $TARGET limit 0 echo ''; echo 'after limit drop'; tc -s -d qdisc show dev lo tc qdisc replace dev lo handle 2: parent 1:1 sfq echo ''; echo 'post graft'; tc -s -d qdisc show dev lo The second to last show command shows 0 packets but a positive number (74) of backlog bytes. The problem becomes clearer in the last show command, where qdisc_purge_queue triggers qdisc_tree_reduce_backlog with the positive backlog and causes an underflow in the tbf parent's backlog (4096 Mb instead of 0). To fix this issue, the codepath for all clients of qdisc_dequeue_internal has been simplified: codel, pie, hhf, fq, fq_pie, and fq_codel. qdisc_dequeue_internal handles the backlog adjustments for all cases that do not directly use the dequeue handler. The old fq_codel_change limit adjustment loop accumulated the arguments to the subsequent qdisc_tree_reduce_backlog call through the cstats field. However, this is confusing and error prone as fq_codel_dequeue could also potentially mutate this field (which qdisc_dequeue_internal calls in the non gso_skb case), so we have unified the code here with other qdiscs. Fixes: 2d3cbfd ("net_sched: Flush gso_skb list too during -&gt;change()") Fixes: 4b549a2 ("fq_codel: Fair Queue Codel AQM") Fixes: 10239ed ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc") Signed-off-by: William Liu &lt;will@willsroot.io&gt; Reviewed-by: Savino Dicanosa &lt;savy@syst3mfailure.io&gt; Link: https://patch.msgid.link/20250812235725.45243-1-will@willsroot.io Signed-off-by: Jakub Kicinski &lt;kuba@kernel.org&gt; 1 parent d1547bf commit 52bf272 Copy full SHA for 52bf272 7 file s changed + 50 - 33 Lines changed: 50 additions &amp; 33 deletions File tree Expand file tree Collapse file tree Open diff view settings Filter options include/net sch_generic.h net/sched sch_codel.c sch_fq.c sch_fq_codel.c sch_fq_pie.c sch_hhf.c sch_pie.c Expand file tree Collapse file tree Open diff view settings Collapse file ‎ include/net/sch_generic.h ‎ Copy file name to clipboard Expand all lines: include/net/sch_generic.h + 8 - 3 Lines changed: 8 additions &amp; 3 deletions Original file line number Diff line number Diff line change @@ -1038,12 +1038,17 @@ static inline struct sk_buff *qdisc_dequeue_internal(struct Qdisc *sch, bool dir 1038 1038 skb = __skb_dequeue ( &amp; sch -&gt; gso_skb ); 1039 1039 if ( skb ) { 1040 1040 sch -&gt; q . qlen -- ; 1041 + qdisc_qstats_backlog_dec ( sch , skb ); 1041 1042 return skb ; 1042 1043 } 1043 - if ( direct ) 1044 - return __qdisc_dequeue_head ( &amp; sch -&gt; q ); 1045 - else 1044 + if ( direct ) { 1045 + skb = __qdisc_dequeue_head ( &amp; sch -&gt; q ); 1046 + if ( skb ) 1047 + qdisc_qstats_backlog_dec ( sch , skb ); 1048 + return skb ; 1049 + } else { 1046 1050 return sch -&gt; dequeue ( sch ); 1051 + } 1047 1052 } 1048 1053 1049 1054 static inline struct sk_buff * qdisc_dequeue_head ( struct Qdisc * sch ) Collapse file ‎ net/sched/sch_codel.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_codel.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -101,9 +101,9 @@ static const struct nla_policy codel_policy[TCA_CODEL_MAX + 1] = { 101 101 static int codel_change ( struct Qdisc * sch , struct nlattr * opt , 102 102 struct netlink_ext_ack * extack ) 103 103 { 104 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 104 105 struct codel_sched_data * q = qdisc_priv ( sch ); 105 106 struct nlattr * tb [ TCA_CODEL_MAX + 1 ]; 106 - unsigned int qlen , dropped = 0 ; 107 107 int err ; 108 108 109 109 err = nla_parse_nested_deprecated ( tb , TCA_CODEL_MAX , opt , @@ -142,15 +142,17 @@ static int codel_change(struct Qdisc *sch, struct nlattr *opt, 142 142 WRITE_ONCE ( q -&gt; params . ecn , 143 143 !! nla_get_u32 ( tb [ TCA_CODEL_ECN ])); 144 144 145 - qlen = sch -&gt; q . qlen ; 146 145 while ( sch -&gt; q . qlen &gt; sch -&gt; limit ) { 147 146 struct sk_buff * skb = qdisc_dequeue_internal ( sch , true); 148 147 149 - dropped += qdisc_pkt_len ( skb ); 150 - qdisc_qstats_backlog_dec ( sch , skb ); 148 + if (! skb ) 149 + break ; 150 + 151 + dropped_pkts ++ ; 152 + dropped_bytes += qdisc_pkt_len ( skb ); 151 153 rtnl_qdisc_drop ( skb , sch ); 152 154 } 153 - qdisc_tree_reduce_backlog ( sch , qlen - sch -&gt; q . qlen , dropped ); 155 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 154 156 155 157 sch_tree_unlock ( sch ); 156 158 return 0 ; Collapse file ‎ net/sched/sch_fq.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_fq.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -1013,11 +1013,11 @@ static int fq_load_priomap(struct fq_sched_data *q, 1013 1013 static int fq_change ( struct Qdisc * sch , struct nlattr * opt , 1014 1014 struct netlink_ext_ack * extack ) 1015 1015 { 1016 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 1016 1017 struct fq_sched_data * q = qdisc_priv ( sch ); 1017 1018 struct nlattr * tb [ TCA_FQ_MAX + 1 ]; 1018 - int err , drop_count = 0 ; 1019 - unsigned drop_len = 0 ; 1020 1019 u32 fq_log ; 1020 + int err ; 1021 1021 1022 1022 err = nla_parse_nested_deprecated ( tb , TCA_FQ_MAX , opt , fq_policy , 1023 1023 NULL ); @@ -1135,16 +1135,18 @@ static int fq_change(struct Qdisc *sch, struct nlattr *opt, 1135 1135 err = fq_resize ( sch , fq_log ); 1136 1136 sch_tree_lock ( sch ); 1137 1137 } 1138 + 1138 1139 while ( sch -&gt; q . qlen &gt; sch -&gt; limit ) { 1139 1140 struct sk_buff * skb = qdisc_dequeue_internal ( sch , false); 1140 1141 1141 1142 if (! skb ) 1142 1143 break ; 1143 - drop_len += qdisc_pkt_len ( skb ); 1144 + 1145 + dropped_pkts ++ ; 1146 + dropped_bytes += qdisc_pkt_len ( skb ); 1144 1147 rtnl_kfree_skbs ( skb , skb ); 1145 - drop_count ++ ; 1146 1148 } 1147 - qdisc_tree_reduce_backlog ( sch , drop_count , drop_len ); 1149 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 1148 1150 1149 1151 sch_tree_unlock ( sch ); 1150 1152 return err ; Collapse file ‎ net/sched/sch_fq_codel.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_fq_codel.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -366,6 +366,7 @@ static const struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = { 366 366 static int fq_codel_change ( struct Qdisc * sch , struct nlattr * opt , 367 367 struct netlink_ext_ack * extack ) 368 368 { 369 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 369 370 struct fq_codel_sched_data * q = qdisc_priv ( sch ); 370 371 struct nlattr * tb [ TCA_FQ_CODEL_MAX + 1 ]; 371 372 u32 quantum = 0 ; @@ -443,13 +444,14 @@ static int fq_codel_change(struct Qdisc *sch, struct nlattr *opt, 443 444 q -&gt; memory_usage &gt; q -&gt; memory_limit ) { 444 445 struct sk_buff * skb = qdisc_dequeue_internal ( sch , false); 445 446 446 - q -&gt; cstats . drop_len += qdisc_pkt_len ( skb ); 447 + if (! skb ) 448 + break ; 449 + 450 + dropped_pkts ++ ; 451 + dropped_bytes += qdisc_pkt_len ( skb ); 447 452 rtnl_kfree_skbs ( skb , skb ); 448 - q -&gt; cstats . drop_count ++ ; 449 453 } 450 - qdisc_tree_reduce_backlog ( sch , q -&gt; cstats . drop_count , q -&gt; cstats . drop_len ); 451 - q -&gt; cstats . drop_count = 0 ; 452 - q -&gt; cstats . drop_len = 0 ; 454 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 453 455 454 456 sch_tree_unlock ( sch ); 455 457 return 0 ; Collapse file ‎ net/sched/sch_fq_pie.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_fq_pie.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -287,10 +287,9 @@ static struct sk_buff *fq_pie_qdisc_dequeue(struct Qdisc *sch) 287 287 static int fq_pie_change ( struct Qdisc * sch , struct nlattr * opt , 288 288 struct netlink_ext_ack * extack ) 289 289 { 290 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 290 291 struct fq_pie_sched_data * q = qdisc_priv ( sch ); 291 292 struct nlattr * tb [ TCA_FQ_PIE_MAX + 1 ]; 292 - unsigned int len_dropped = 0 ; 293 - unsigned int num_dropped = 0 ; 294 293 int err ; 295 294 296 295 err = nla_parse_nested ( tb , TCA_FQ_PIE_MAX , opt , fq_pie_policy , extack ); @@ -368,11 +367,14 @@ static int fq_pie_change(struct Qdisc *sch, struct nlattr *opt, 368 367 while ( sch -&gt; q . qlen &gt; sch -&gt; limit ) { 369 368 struct sk_buff * skb = qdisc_dequeue_internal ( sch , false); 370 369 371 - len_dropped += qdisc_pkt_len ( skb ); 372 - num_dropped += 1 ; 370 + if (! skb ) 371 + break ; 372 + 373 + dropped_pkts ++ ; 374 + dropped_bytes += qdisc_pkt_len ( skb ); 373 375 rtnl_kfree_skbs ( skb , skb ); 374 376 } 375 - qdisc_tree_reduce_backlog ( sch , num_dropped , len_dropped ); 377 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 376 378 377 379 sch_tree_unlock ( sch ); 378 380 return 0 ; Collapse file ‎ net/sched/sch_hhf.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_hhf.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -508,9 +508,9 @@ static const struct nla_policy hhf_policy[TCA_HHF_MAX + 1] = { 508 508 static int hhf_change ( struct Qdisc * sch , struct nlattr * opt , 509 509 struct netlink_ext_ack * extack ) 510 510 { 511 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 511 512 struct hhf_sched_data * q = qdisc_priv ( sch ); 512 513 struct nlattr * tb [ TCA_HHF_MAX + 1 ]; 513 - unsigned int qlen , prev_backlog ; 514 514 int err ; 515 515 u64 non_hh_quantum ; 516 516 u32 new_quantum = q -&gt; quantum ; @@ -561,15 +561,17 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt, 561 561 usecs_to_jiffies ( us )); 562 562 } 563 563 564 - qlen = sch -&gt; q . qlen ; 565 - prev_backlog = sch -&gt; qstats . backlog ; 566 564 while ( sch -&gt; q . qlen &gt; sch -&gt; limit ) { 567 565 struct sk_buff * skb = qdisc_dequeue_internal ( sch , false); 568 566 567 + if (! skb ) 568 + break ; 569 + 570 + dropped_pkts ++ ; 571 + dropped_bytes += qdisc_pkt_len ( skb ); 569 572 rtnl_kfree_skbs ( skb , skb ); 570 573 } 571 - qdisc_tree_reduce_backlog ( sch , qlen - sch -&gt; q . qlen , 572 - prev_backlog - sch -&gt; qstats . backlog ); 574 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 573 575 574 576 sch_tree_unlock ( sch ); 575 577 return 0 ; Collapse file ‎ net/sched/sch_pie.c ‎ Copy file name to clipboard Expand all lines: net/sched/sch_pie.c + 7 - 5 Lines changed: 7 additions &amp; 5 deletions Original file line number Diff line number Diff line change @@ -141,9 +141,9 @@ static const struct nla_policy pie_policy[TCA_PIE_MAX + 1] = { 141 141 static int pie_change ( struct Qdisc * sch , struct nlattr * opt , 142 142 struct netlink_ext_ack * extack ) 143 143 { 144 + unsigned int dropped_pkts = 0 , dropped_bytes = 0 ; 144 145 struct pie_sched_data * q = qdisc_priv ( sch ); 145 146 struct nlattr * tb [ TCA_PIE_MAX + 1 ]; 146 - unsigned int qlen , dropped = 0 ; 147 147 int err ; 148 148 149 149 err = nla_parse_nested_deprecated ( tb , TCA_PIE_MAX , opt , pie_policy , @@ -193,15 +193,17 @@ static int pie_change(struct Qdisc *sch, struct nlattr *opt, 193 193 nla_get_u32 ( tb [ TCA_PIE_DQ_RATE_ESTIMATOR ])); 194 194 195 195 /* Drop excess packets if new limit is lower */ 196 - qlen = sch -&gt; q . qlen ; 197 196 while ( sch -&gt; q . qlen &gt; sch -&gt; limit ) { 198 197 struct sk_buff * skb = qdisc_dequeue_internal ( sch , true); 199 198 200 - dropped += qdisc_pkt_len ( skb ); 201 - qdisc_qstats_backlog_dec ( sch , skb ); 199 + if (! skb ) 200 + break ; 201 + 202 + dropped_pkts ++ ; 203 + dropped_bytes += qdisc_pkt_len ( skb ); 202 204 rtnl_qdisc_drop ( skb , sch ); 203 205 } 204 - qdisc_tree_reduce_backlog ( sch , qlen - sch -&gt; q . qlen , dropped ); 206 + qdisc_tree_reduce_backlog ( sch , dropped_pkts , dropped_bytes ); 205 207 206 208 sch_tree_unlock ( sch ); 207 209 return 0 ; 0 commit comments Comments 0 ( 0 ) Footer &copy; 2026 GitHub,&nbsp;Inc. Footer navigation Terms Privacy Security Status Community Docs Contact Manage cookies Do not share my personal information You can’t perform that action at this time. 