diff options
| author | Stephen Hemminger <shemminger@osdl.org> | 2005-10-30 16:47:34 -0500 |
|---|---|---|
| committer | Arnaldo Carvalho de Melo <acme@mandriva.com> | 2005-11-05 17:56:41 -0500 |
| commit | 300ce174ebc2fcf2b5111a50fa42f79d891927dd (patch) | |
| tree | ea7ac40eac2de90be9e5575759bab18029ae2fdf | |
| parent | 07aaa11540828f4482c09e1a936a1f63cdb9fc9d (diff) | |
[NETEM]: Support time based reordering
Change netem to support packets getting reordered because of variations in
delay. Introduce a special case version of FIFO that queues packets in order
based on the netem delay.
Since netem is classful, those users that don't want jitter based reordering
can just insert a pfifo instead of the default.
This required changes to generic skbuff code to allow finer grain manipulation
of sk_buff_head. Insertion into the middle and reverse walk.
Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@mandriva.com>
| -rw-r--r-- | include/linux/skbuff.h | 38 | ||||
| -rw-r--r-- | net/sched/sch_netem.c | 85 |
2 files changed, 114 insertions, 9 deletions
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 4286d832166f..fdfb8fe8c38c 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h | |||
| @@ -603,23 +603,23 @@ static inline void skb_queue_head_init(struct sk_buff_head *list) | |||
| 603 | */ | 603 | */ |
| 604 | 604 | ||
| 605 | /** | 605 | /** |
| 606 | * __skb_queue_head - queue a buffer at the list head | 606 | * __skb_queue_after - queue a buffer at the list head |
| 607 | * @list: list to use | 607 | * @list: list to use |
| 608 | * @prev: place after this buffer | ||
| 608 | * @newsk: buffer to queue | 609 | * @newsk: buffer to queue |
| 609 | * | 610 | * |
| 610 | * Queue a buffer at the start of a list. This function takes no locks | 611 | * Queue a buffer int the middle of a list. This function takes no locks |
| 611 | * and you must therefore hold required locks before calling it. | 612 | * and you must therefore hold required locks before calling it. |
| 612 | * | 613 | * |
| 613 | * A buffer cannot be placed on two lists at the same time. | 614 | * A buffer cannot be placed on two lists at the same time. |
| 614 | */ | 615 | */ |
| 615 | extern void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk); | 616 | static inline void __skb_queue_after(struct sk_buff_head *list, |
| 616 | static inline void __skb_queue_head(struct sk_buff_head *list, | 617 | struct sk_buff *prev, |
| 617 | struct sk_buff *newsk) | 618 | struct sk_buff *newsk) |
| 618 | { | 619 | { |
| 619 | struct sk_buff *prev, *next; | 620 | struct sk_buff *next; |
| 620 | |||
| 621 | list->qlen++; | 621 | list->qlen++; |
| 622 | prev = (struct sk_buff *)list; | 622 | |
| 623 | next = prev->next; | 623 | next = prev->next; |
| 624 | newsk->next = next; | 624 | newsk->next = next; |
| 625 | newsk->prev = prev; | 625 | newsk->prev = prev; |
| @@ -627,6 +627,23 @@ static inline void __skb_queue_head(struct sk_buff_head *list, | |||
| 627 | } | 627 | } |
| 628 | 628 | ||
| 629 | /** | 629 | /** |
| 630 | * __skb_queue_head - queue a buffer at the list head | ||
| 631 | * @list: list to use | ||
| 632 | * @newsk: buffer to queue | ||
| 633 | * | ||
| 634 | * Queue a buffer at the start of a list. This function takes no locks | ||
| 635 | * and you must therefore hold required locks before calling it. | ||
| 636 | * | ||
| 637 | * A buffer cannot be placed on two lists at the same time. | ||
| 638 | */ | ||
| 639 | extern void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk); | ||
| 640 | static inline void __skb_queue_head(struct sk_buff_head *list, | ||
| 641 | struct sk_buff *newsk) | ||
| 642 | { | ||
| 643 | __skb_queue_after(list, (struct sk_buff *)list, newsk); | ||
| 644 | } | ||
| 645 | |||
| 646 | /** | ||
| 630 | * __skb_queue_tail - queue a buffer at the list tail | 647 | * __skb_queue_tail - queue a buffer at the list tail |
| 631 | * @list: list to use | 648 | * @list: list to use |
| 632 | * @newsk: buffer to queue | 649 | * @newsk: buffer to queue |
| @@ -1203,6 +1220,11 @@ static inline void kunmap_skb_frag(void *vaddr) | |||
| 1203 | prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \ | 1220 | prefetch(skb->next), (skb != (struct sk_buff *)(queue)); \ |
| 1204 | skb = skb->next) | 1221 | skb = skb->next) |
| 1205 | 1222 | ||
| 1223 | #define skb_queue_reverse_walk(queue, skb) \ | ||
| 1224 | for (skb = (queue)->prev; \ | ||
| 1225 | prefetch(skb->prev), (skb != (struct sk_buff *)(queue)); \ | ||
| 1226 | skb = skb->prev) | ||
| 1227 | |||
| 1206 | 1228 | ||
| 1207 | extern struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, | 1229 | extern struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, |
| 1208 | int noblock, int *err); | 1230 | int noblock, int *err); |
diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index d871fe7f81a9..7c10ef3457d7 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c | |||
| @@ -300,11 +300,16 @@ static void netem_reset(struct Qdisc *sch) | |||
| 300 | del_timer_sync(&q->timer); | 300 | del_timer_sync(&q->timer); |
| 301 | } | 301 | } |
| 302 | 302 | ||
| 303 | /* Pass size change message down to embedded FIFO */ | ||
| 303 | static int set_fifo_limit(struct Qdisc *q, int limit) | 304 | static int set_fifo_limit(struct Qdisc *q, int limit) |
| 304 | { | 305 | { |
| 305 | struct rtattr *rta; | 306 | struct rtattr *rta; |
| 306 | int ret = -ENOMEM; | 307 | int ret = -ENOMEM; |
| 307 | 308 | ||
| 309 | /* Hack to avoid sending change message to non-FIFO */ | ||
| 310 | if (strncmp(q->ops->id + 1, "fifo", 4) != 0) | ||
| 311 | return 0; | ||
| 312 | |||
| 308 | rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL); | 313 | rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL); |
| 309 | if (rta) { | 314 | if (rta) { |
| 310 | rta->rta_type = RTM_NEWQDISC; | 315 | rta->rta_type = RTM_NEWQDISC; |
| @@ -436,6 +441,84 @@ static int netem_change(struct Qdisc *sch, struct rtattr *opt) | |||
| 436 | return 0; | 441 | return 0; |
| 437 | } | 442 | } |
| 438 | 443 | ||
| 444 | /* | ||
| 445 | * Special case version of FIFO queue for use by netem. | ||
| 446 | * It queues in order based on timestamps in skb's | ||
| 447 | */ | ||
| 448 | struct fifo_sched_data { | ||
| 449 | u32 limit; | ||
| 450 | }; | ||
| 451 | |||
| 452 | static int tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch) | ||
| 453 | { | ||
| 454 | struct fifo_sched_data *q = qdisc_priv(sch); | ||
| 455 | struct sk_buff_head *list = &sch->q; | ||
| 456 | const struct netem_skb_cb *ncb | ||
| 457 | = (const struct netem_skb_cb *)nskb->cb; | ||
| 458 | struct sk_buff *skb; | ||
| 459 | |||
| 460 | if (likely(skb_queue_len(list) < q->limit)) { | ||
| 461 | skb_queue_reverse_walk(list, skb) { | ||
| 462 | const struct netem_skb_cb *cb | ||
| 463 | = (const struct netem_skb_cb *)skb->cb; | ||
| 464 | |||
| 465 | if (PSCHED_TLESS(cb->time_to_send, ncb->time_to_send)) | ||
| 466 | break; | ||
| 467 | } | ||
| 468 | |||
| 469 | __skb_queue_after(list, skb, nskb); | ||
| 470 | |||
| 471 | sch->qstats.backlog += nskb->len; | ||
| 472 | sch->bstats.bytes += nskb->len; | ||
| 473 | sch->bstats.packets++; | ||
| 474 | |||
| 475 | return NET_XMIT_SUCCESS; | ||
| 476 | } | ||
| 477 | |||
| 478 | return qdisc_drop(nskb, sch); | ||
| 479 | } | ||
| 480 | |||
| 481 | static int tfifo_init(struct Qdisc *sch, struct rtattr *opt) | ||
| 482 | { | ||
| 483 | struct fifo_sched_data *q = qdisc_priv(sch); | ||
| 484 | |||
| 485 | if (opt) { | ||
| 486 | struct tc_fifo_qopt *ctl = RTA_DATA(opt); | ||
| 487 | if (RTA_PAYLOAD(opt) < sizeof(*ctl)) | ||
| 488 | return -EINVAL; | ||
| 489 | |||
| 490 | q->limit = ctl->limit; | ||
| 491 | } else | ||
| 492 | q->limit = max_t(u32, sch->dev->tx_queue_len, 1); | ||
| 493 | |||
| 494 | return 0; | ||
| 495 | } | ||
| 496 | |||
| 497 | static int tfifo_dump(struct Qdisc *sch, struct sk_buff *skb) | ||
| 498 | { | ||
| 499 | struct fifo_sched_data *q = qdisc_priv(sch); | ||
| 500 | struct tc_fifo_qopt opt = { .limit = q->limit }; | ||
| 501 | |||
| 502 | RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt); | ||
| 503 | return skb->len; | ||
| 504 | |||
| 505 | rtattr_failure: | ||
| 506 | return -1; | ||
| 507 | } | ||
| 508 | |||
| 509 | static struct Qdisc_ops tfifo_qdisc_ops = { | ||
| 510 | .id = "tfifo", | ||
| 511 | .priv_size = sizeof(struct fifo_sched_data), | ||
| 512 | .enqueue = tfifo_enqueue, | ||
| 513 | .dequeue = qdisc_dequeue_head, | ||
| 514 | .requeue = qdisc_requeue, | ||
| 515 | .drop = qdisc_queue_drop, | ||
| 516 | .init = tfifo_init, | ||
| 517 | .reset = qdisc_reset_queue, | ||
| 518 | .change = tfifo_init, | ||
| 519 | .dump = tfifo_dump, | ||
| 520 | }; | ||
| 521 | |||
| 439 | static int netem_init(struct Qdisc *sch, struct rtattr *opt) | 522 | static int netem_init(struct Qdisc *sch, struct rtattr *opt) |
| 440 | { | 523 | { |
| 441 | struct netem_sched_data *q = qdisc_priv(sch); | 524 | struct netem_sched_data *q = qdisc_priv(sch); |
| @@ -448,7 +531,7 @@ static int netem_init(struct Qdisc *sch, struct rtattr *opt) | |||
| 448 | q->timer.function = netem_watchdog; | 531 | q->timer.function = netem_watchdog; |
| 449 | q->timer.data = (unsigned long) sch; | 532 | q->timer.data = (unsigned long) sch; |
| 450 | 533 | ||
| 451 | q->qdisc = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops); | 534 | q->qdisc = qdisc_create_dflt(sch->dev, &tfifo_qdisc_ops); |
| 452 | if (!q->qdisc) { | 535 | if (!q->qdisc) { |
| 453 | pr_debug("netem: qdisc create failed\n"); | 536 | pr_debug("netem: qdisc create failed\n"); |
| 454 | return -ENOMEM; | 537 | return -ENOMEM; |
