Skip to content

Commit e676426

Browse files
martin-ottensNipaLocal
authored and
NipaLocal
committed
net/sched: netem: account for backlog updates from child qdisc
In general, 'qlen' of any classful qdisc should keep track of the number of packets that the qdisc itself and all of its children holds. In case of netem, 'qlen' only accounts for the packets in its internal tfifo. When netem is used with a child qdisc, the child qdisc can use 'qdisc_tree_reduce_backlog' to inform its parent, netem, about created or dropped SKBs. This function updates 'qlen' and the backlog statistics of netem, but netem does not account for changes made by a child qdisc. 'qlen' then indicates the wrong number of packets in the tfifo. If a child qdisc creates new SKBs during enqueue and informs its parent about this, netem's 'qlen' value is increased. When netem dequeues the newly created SKBs from the child, the 'qlen' in netem is not updated. If 'qlen' reaches the configured sch->limit, the enqueue function stops working, even though the tfifo is not full. Reproduce the bug: Ensure that the sender machine has GSO enabled. Configure netem as root qdisc and tbf as its child on the outgoing interface of the machine as follows: $ tc qdisc add dev <oif> root handle 1: netem delay 100ms limit 100 $ tc qdisc add dev <oif> parent 1:0 tbf rate 50Mbit burst 1542 latency 50ms Send bulk TCP traffic out via this interface, e.g., by running an iPerf3 client on the machine. Check the qdisc statistics: $ tc -s qdisc show dev <oif> tbf segments the GSO SKBs (tbf_segment) and updates the netem's 'qlen'. The interface fully stops transferring packets and "locks". In this case, the child qdisc and tfifo are empty, but 'qlen' indicates the tfifo is at its limit and no more packets are accepted. This patch adds a counter for the entries in the tfifo. Netem's 'qlen' is only decreased when a packet is returned by its dequeue function, and not during enqueuing into the child qdisc. External updates to 'qlen' are thus accounted for and only the behavior of the backlog statistics changes. As in other qdiscs, 'qlen' then keeps track of how many packets are held in netem and all of its children. As before, sch->limit remains as the maximum number of packets in the tfifo. The same applies to netem's backlog statistics. (Note: the 'backlog' byte-statistic of netem is incorrect in the example above even after the patch is applied due to a bug in tbf. See my previous patch ([PATCH] net/sched: tbf: correct backlog statistic for GSO packets)). Signed-off-by: Martin Ottens <[email protected]> Signed-off-by: NipaLocal <nipa@local>
1 parent 795540b commit e676426

File tree

1 file changed

+16
-6
lines changed

1 file changed

+16
-6
lines changed

net/sched/sch_netem.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ struct netem_sched_data {
7979
struct sk_buff *t_head;
8080
struct sk_buff *t_tail;
8181

82+
u32 t_len;
83+
8284
/* optional qdisc for classful handling (NULL at netem init) */
8385
struct Qdisc *qdisc;
8486

@@ -383,6 +385,7 @@ static void tfifo_reset(struct Qdisc *sch)
383385
rtnl_kfree_skbs(q->t_head, q->t_tail);
384386
q->t_head = NULL;
385387
q->t_tail = NULL;
388+
q->t_len = 0;
386389
}
387390

388391
static void tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch)
@@ -412,6 +415,7 @@ static void tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch)
412415
rb_link_node(&nskb->rbnode, parent, p);
413416
rb_insert_color(&nskb->rbnode, &q->t_root);
414417
}
418+
q->t_len++;
415419
sch->q.qlen++;
416420
}
417421

@@ -518,7 +522,7 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch,
518522
1<<get_random_u32_below(8);
519523
}
520524

521-
if (unlikely(sch->q.qlen >= sch->limit)) {
525+
if (unlikely(q->t_len >= sch->limit)) {
522526
/* re-link segs, so that qdisc_drop_all() frees them all */
523527
skb->next = segs;
524528
qdisc_drop_all(skb, sch, to_free);
@@ -702,8 +706,8 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch)
702706
tfifo_dequeue:
703707
skb = __qdisc_dequeue_head(&sch->q);
704708
if (skb) {
705-
qdisc_qstats_backlog_dec(sch, skb);
706709
deliver:
710+
qdisc_qstats_backlog_dec(sch, skb);
707711
qdisc_bstats_update(sch, skb);
708712
return skb;
709713
}
@@ -719,8 +723,7 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch)
719723

720724
if (time_to_send <= now && q->slot.slot_next <= now) {
721725
netem_erase_head(q, skb);
722-
sch->q.qlen--;
723-
qdisc_qstats_backlog_dec(sch, skb);
726+
q->t_len--;
724727
skb->next = NULL;
725728
skb->prev = NULL;
726729
/* skb->dev shares skb->rbnode area,
@@ -747,16 +750,21 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch)
747750
if (net_xmit_drop_count(err))
748751
qdisc_qstats_drop(sch);
749752
qdisc_tree_reduce_backlog(sch, 1, pkt_len);
753+
sch->qstats.backlog -= pkt_len;
754+
sch->q.qlen--;
750755
}
751756
goto tfifo_dequeue;
752757
}
758+
sch->q.qlen--;
753759
goto deliver;
754760
}
755761

756762
if (q->qdisc) {
757763
skb = q->qdisc->ops->dequeue(q->qdisc);
758-
if (skb)
764+
if (skb) {
765+
sch->q.qlen--;
759766
goto deliver;
767+
}
760768
}
761769

762770
qdisc_watchdog_schedule_ns(&q->watchdog,
@@ -766,8 +774,10 @@ static struct sk_buff *netem_dequeue(struct Qdisc *sch)
766774

767775
if (q->qdisc) {
768776
skb = q->qdisc->ops->dequeue(q->qdisc);
769-
if (skb)
777+
if (skb) {
778+
sch->q.qlen--;
770779
goto deliver;
780+
}
771781
}
772782
return NULL;
773783
}

0 commit comments

Comments
 (0)