From 5318b2c6674101795d6658d3feedc927789f2eec Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sun, 4 Aug 2013 11:43:02 -0700 Subject: [PATCH 1/4] Update kernel headers to net-next for 3.12 --- include/linux/fib_rules.h | 4 ++-- include/linux/if_bridge.h | 3 ++- include/linux/if_link.h | 1 + include/linux/if_tun.h | 3 +++ include/linux/tcp.h | 1 + 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/linux/fib_rules.h b/include/linux/fib_rules.h index 51da65b6..2b82d7e3 100644 --- a/include/linux/fib_rules.h +++ b/include/linux/fib_rules.h @@ -44,8 +44,8 @@ enum { FRA_FWMARK, /* mark */ FRA_FLOW, /* flow/class id */ FRA_UNUSED6, - FRA_UNUSED7, - FRA_UNUSED8, + FRA_SUPPRESS_IFGROUP, + FRA_SUPPRESS_PREFIXLEN, FRA_TABLE, /* Extended table id */ FRA_FWMASK, /* mask for netfilter mark */ FRA_OIFNAME, diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index d37e53c3..d2de4e67 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -14,6 +14,7 @@ #define _LINUX_IF_BRIDGE_H #include +#include #define SYSFS_BRIDGE_ATTR "bridge" #define SYSFS_BRIDGE_FDB "brforward" @@ -88,7 +89,7 @@ struct __port_info { }; struct __fdb_entry { - __u8 mac_addr[6]; + __u8 mac_addr[ETH_ALEN]; __u8 port_no; __u8 is_local; __u32 ageing_timer_value; diff --git a/include/linux/if_link.h b/include/linux/if_link.h index d07aecaa..f80fcea6 100644 --- a/include/linux/if_link.h +++ b/include/linux/if_link.h @@ -143,6 +143,7 @@ enum { IFLA_NUM_TX_QUEUES, IFLA_NUM_RX_QUEUES, IFLA_CARRIER, + IFLA_PHYS_PORT_ID, __IFLA_MAX }; diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index dc13de35..6a7fb11d 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -71,6 +71,9 @@ /* read-only flag */ #define IFF_PERSIST 0x0800 +/* Socket options */ +#define TUN_TX_TIMESTAMP 1 + /* Features for GSO (TUNSETOFFLOAD). */ #define TUN_F_CSUM 0x01 /* You can hand me unchecksummed packets. */ #define TUN_F_TSO4 0x02 /* I can handle TSO for IPv4 packets */ diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 1eb04d35..8df6bd7f 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -111,6 +111,7 @@ enum { #define TCP_REPAIR_OPTIONS 22 #define TCP_FASTOPEN 23 /* Enable FastOpen on listeners */ #define TCP_TIMESTAMP 24 +#define TCP_NOTSENT_LOWAT 25 /* limit number of unsent bytes in write queue */ struct tcp_repair_opt { __u32 opt_code; From b1d0525f9cc3ef6fa7670ff3769828404cea31d4 Mon Sep 17 00:00:00 2001 From: Stefan Tomanek Date: Sat, 3 Aug 2013 14:23:16 +0200 Subject: [PATCH 2/4] ip rule: add route suppression options When configuring a system with multiple network uplinks and default routes, it is often convenient to reference a routing table multiple times - but reject its routing decision if certain constraints are not met by it. Consider this setup: $ ip route add table secuplink default via 10.42.23.1 $ ip rule add pref 100 table main suppress_prefixlength 0 $ ip rule add pref 150 fwmark 0xA table secuplink With this setup, packets marked 0xA will be processed by the additional routing table "secuplink", but only if no suitable route in the main routing table can be found. By suppressing entries with a prefixlength of 0 (or less), the default route (/0) of the table "main" is hidden to packets processed by rule 100; packets traveling to destinations via more specific routes are processed as usual. It is also possible to suppress a routing entry if a device belonging to a specific interface group is to be used: $ ip rule add pref 150 table main suppress_group 1 Signed-off-by: Stefan Tomanek --- ip/iprule.c | 34 +++++++++++++++++++++++++++++++++- man/man8/ip-rule.8 | 17 +++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/ip/iprule.c b/ip/iprule.c index a5fcd432..d934f67d 100644 --- a/ip/iprule.c +++ b/ip/iprule.c @@ -39,6 +39,9 @@ static void usage(void) fprintf(stderr, " [ prohibit | reject | unreachable ]\n"); fprintf(stderr, " [ realms [SRCREALM/]DSTREALM ]\n"); fprintf(stderr, " [ goto NUMBER ]\n"); + fprintf(stderr, " SUPPRESSOR\n"); + fprintf(stderr, "SUPPRESSOR := [ suppress_prefixlength NUMBER ]\n"); + fprintf(stderr, " [ suppress_ifgroup DEVGROUP ]\n"); fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n"); exit(-1); } @@ -153,9 +156,24 @@ int print_rule(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) } table = rtm_get_table(r, tb); - if (table) + if (table) { fprintf(fp, "lookup %s ", rtnl_rttable_n2a(table, b1, sizeof(b1))); + if (tb[FRA_SUPPRESS_PREFIXLEN]) { + int pl = rta_getattr_u32(tb[FRA_SUPPRESS_PREFIXLEN]); + if (pl != -1) { + fprintf(fp, "suppress_prefixlength %d ", pl); + } + } + if (tb[FRA_SUPPRESS_IFGROUP]) { + int group = rta_getattr_u32(tb[FRA_SUPPRESS_IFGROUP]); + if (group != -1) { + SPRINT_BUF(b1); + fprintf(fp, "suppress_ifgroup %s ", rtnl_group_n2a(group, b1, sizeof(b1))); + } + } + } + if (tb[FRA_FLOW]) { __u32 to = rta_getattr_u32(tb[FRA_FLOW]); __u32 from = to>>16; @@ -310,6 +328,20 @@ static int iprule_modify(int cmd, int argc, char **argv) addattr32(&req.n, sizeof(req), FRA_TABLE, tid); } table_ok = 1; + } else if (matches(*argv, "suppress_prefixlength") == 0 || + strcmp(*argv, "sup_pl") == 0) { + int pl; + NEXT_ARG(); + if (get_s32(&pl, *argv, 0) || pl < 0) + invarg("suppress_prefixlength value is invalid\n", *argv); + addattr32(&req.n, sizeof(req), FRA_SUPPRESS_PREFIXLEN, pl); + } else if (matches(*argv, "suppress_ifgroup") == 0 || + strcmp(*argv, "sup_group") == 0) { + NEXT_ARG(); + int group; + if (rtnl_group_a2n(&group, *argv)) + invarg("Invalid \"suppress_ifgroup\" value\n", *argv); + addattr32(&req.n, sizeof(req), FRA_SUPPRESS_IFGROUP, group); } else if (strcmp(*argv, "dev") == 0 || strcmp(*argv, "iif") == 0) { NEXT_ARG(); diff --git a/man/man8/ip-rule.8 b/man/man8/ip-rule.8 index 36e46f1b..62df3b0b 100644 --- a/man/man8/ip-rule.8 +++ b/man/man8/ip-rule.8 @@ -43,6 +43,14 @@ ip-rule \- routing policy database management .IR ADDRESS " ] [ " .BR prohibit " | " reject " | " unreachable " ] [ " realms .RI "[" SRCREALM "/]" DSTREALM " ]" +.I SUPPRESSOR + +.ti -8 +.IR SUPPRESSOR " := [ " +.B suppress_prefixlength +.IR NUMBER " ] [ " +.B suppress_ifgroup +.IR GROUP " ]" .ti -8 .IR TABLE_ID " := [ " @@ -216,6 +224,15 @@ The options preference and order are synonyms with priority. the routing table identifier to lookup if the rule selector matches. It is also possible to use lookup instead of table. +.TP +.BI suppress_prefixlength " NUMBER" +reject routing decisions that have a prefix length of NUMBER or less. + +.TP +.BI suppress_ifgroup " GROUP" +reject routing decisions that use a device belonging to the interface +group GROUP. + .TP .BI realms " FROM/TO" Realms to select if the rule matched and the routing table lookup From c92716d1edc4fad0223a138123dbdcc05f06615c Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Tue, 3 Sep 2013 08:46:40 -0700 Subject: [PATCH 3/4] Update to 3.11 net-next kernel headers --- include/linux/if_link.h | 2 ++ include/linux/if_tun.h | 3 +++ include/linux/pkt_sched.h | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/include/linux/if_link.h b/include/linux/if_link.h index f80fcea6..ee4f2ba9 100644 --- a/include/linux/if_link.h +++ b/include/linux/if_link.h @@ -312,6 +312,8 @@ enum { IFLA_VXLAN_L2MISS, IFLA_VXLAN_L3MISS, IFLA_VXLAN_PORT, /* destination port */ + IFLA_VXLAN_GROUP6, + IFLA_VXLAN_LOCAL6, __IFLA_VXLAN_MAX }; #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 6a7fb11d..75cc8ace 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -56,6 +56,8 @@ #define TUNGETVNETHDRSZ _IOR('T', 215, int) #define TUNSETVNETHDRSZ _IOW('T', 216, int) #define TUNSETQUEUE _IOW('T', 217, int) +#define TUNSETIFINDEX _IOW('T', 218, unsigned int) +#define TUNGETFILTER _IOR('T', 219, struct sock_fprog) /* TUNSETIFF ifr flags */ #define IFF_TUN 0x0001 @@ -70,6 +72,7 @@ #define IFF_DETACH_QUEUE 0x0400 /* read-only flag */ #define IFF_PERSIST 0x0800 +#define IFF_NOFILTER 0x1000 /* Socket options */ #define TUN_TX_TIMESTAMP 1 diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h index 09d62b92..9b829134 100644 --- a/include/linux/pkt_sched.h +++ b/include/linux/pkt_sched.h @@ -744,4 +744,45 @@ struct tc_fq_codel_xstats { }; }; +/* FQ */ + +enum { + TCA_FQ_UNSPEC, + + TCA_FQ_PLIMIT, /* limit of total number of packets in queue */ + + TCA_FQ_FLOW_PLIMIT, /* limit of packets per flow */ + + TCA_FQ_QUANTUM, /* RR quantum */ + + TCA_FQ_INITIAL_QUANTUM, /* RR quantum for new flow */ + + TCA_FQ_RATE_ENABLE, /* enable/disable rate limiting */ + + TCA_FQ_FLOW_DEFAULT_RATE,/* for sockets with unspecified sk_rate, + * use the following rate + */ + + TCA_FQ_FLOW_MAX_RATE, /* per flow max rate */ + + TCA_FQ_BUCKETS_LOG, /* log2(number of buckets) */ + __TCA_FQ_MAX +}; + +#define TCA_FQ_MAX (__TCA_FQ_MAX - 1) + +struct tc_fq_qd_stats { + __u64 gc_flows; + __u64 highprio_packets; + __u64 tcp_retrans; + __u64 throttled; + __u64 flows_plimit; + __u64 pkts_too_long; + __u64 allocation_errors; + __s64 time_next_delayed_flow; + __u32 flows; + __u32 inactive_flows; + __u32 throttled_flows; + __u32 pad; +}; #endif From bc113e46a3cd06973b37b13c06db3f5f459502f8 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 29 Aug 2013 19:30:36 -0700 Subject: [PATCH 4/4] pkt_sched: fq: Fair Queue packet scheduler Support for FQ packet scheduler $ tc qd add dev eth0 root fq help Usage: ... fq [ limit PACKETS ] [ flow_limit PACKETS ] [ quantum BYTES ] [ initial_quantum BYTES ] [ maxrate RATE ] [ buckets NUMBER ] [ [no]pacing ] $ tc -s -d qd qdisc fq 8002: dev eth0 root refcnt 32 limit 10000p flow_limit 100p buckets 256 quantum 3028 initial_quantum 15140 Sent 216532416 bytes 148395 pkt (dropped 0, overlimits 0 requeues 14) backlog 0b 0p requeues 14 511 flows (511 inactive, 0 throttled) 110 gc, 0 highprio, 0 retrans, 1143 throttled, 0 flows_plimit limit : max number of packets on whole Qdisc (default 10000) flow_limit : max number of packets per flow (default 100) quantum : the max deficit per RR round (default is 2 MTU) initial_quantum : initial credit for new flows (default is 10 MTU) maxrate : max per flow rate (default : unlimited) buckets : number of RB trees (default : 1024) in hash table. (consumes 8 bytes per bucket) [no]pacing : disable/enable pacing (default is enable) Usage : tc qdisc add dev $ETH root fq tc qdisc del dev $ETH root 2>/dev/null tc qdisc add dev $ETH root handle 1: mq for i in `seq 1 4` do tc qdisc add dev $ETH parent 1:$i est 1sec 4sec fq done Signed-off-by: Eric Dumazet --- tc/Makefile | 1 + tc/q_fq.c | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 tc/q_fq.c diff --git a/tc/Makefile b/tc/Makefile index f26e7646..1eeabd8c 100644 --- a/tc/Makefile +++ b/tc/Makefile @@ -50,6 +50,7 @@ TCMODULES += em_meta.o TCMODULES += q_mqprio.o TCMODULES += q_codel.o TCMODULES += q_fq_codel.o +TCMODULES += q_fq.o ifeq ($(TC_CONFIG_IPSET), y) ifeq ($(TC_CONFIG_XT), y) diff --git a/tc/q_fq.c b/tc/q_fq.c new file mode 100644 index 00000000..c1f658e0 --- /dev/null +++ b/tc/q_fq.c @@ -0,0 +1,279 @@ +/* + * Fair Queue + * + * Copyright (C) 2013 Eric Dumazet + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, provided that this notice is retained in full, this + * software may be distributed under the terms of the GNU General + * Public License ("GPL") version 2, in which case the provisions of the + * GPL apply INSTEAD OF those given above. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... fq [ limit PACKETS ] [ flow_limit PACKETS ]\n"); + fprintf(stderr, " [ quantum BYTES ] [ initial_quantum BYTES ]\n"); + fprintf(stderr, " [ maxrate RATE ] [ buckets NUMBER ]\n"); + fprintf(stderr, " [ [no]pacing ]\n"); +} + +static unsigned int ilog2(unsigned int val) +{ + unsigned int res = 0; + + val--; + while (val) { + res++; + val >>= 1; + } + return res; +} + +static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n) +{ + unsigned int plimit = ~0U; + unsigned int flow_plimit = ~0U; + unsigned int quantum = ~0U; + unsigned int initial_quantum = ~0U; + unsigned int buckets = 0; + unsigned int maxrate = ~0U; + unsigned int defrate = ~0U; + int pacing = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&plimit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "flow_limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&flow_plimit, *argv, 0)) { + fprintf(stderr, "Illegal \"flow_limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "buckets") == 0) { + NEXT_ARG(); + if (get_unsigned(&buckets, *argv, 0)) { + fprintf(stderr, "Illegal \"buckets\"\n"); + return -1; + } + } else if (strcmp(*argv, "maxrate") == 0) { + NEXT_ARG(); + if (get_rate(&maxrate, *argv)) { + fprintf(stderr, "Illegal \"maxrate\"\n"); + return -1; + } + } else if (strcmp(*argv, "defrate") == 0) { + NEXT_ARG(); + if (get_rate(&defrate, *argv)) { + fprintf(stderr, "Illegal \"defrate\"\n"); + return -1; + } + } else if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + } else if (strcmp(*argv, "initial_quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&initial_quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"initial_quantum\"\n"); + return -1; + } + } else if (strcmp(*argv, "pacing") == 0) { + pacing = 1; + } else if (strcmp(*argv, "nopacing") == 0) { + pacing = 0; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = NLMSG_TAIL(n); + addattr_l(n, 1024, TCA_OPTIONS, NULL, 0); + if (buckets) { + unsigned int log = ilog2(buckets); + + addattr_l(n, 1024, TCA_FQ_BUCKETS_LOG, + &log, sizeof(log)); + } + if (plimit != ~0U) + addattr_l(n, 1024, TCA_FQ_PLIMIT, + &plimit, sizeof(plimit)); + if (flow_plimit != ~0U) + addattr_l(n, 1024, TCA_FQ_FLOW_PLIMIT, + &flow_plimit, sizeof(flow_plimit)); + if (quantum != ~0U) + addattr_l(n, 1024, TCA_FQ_QUANTUM, &quantum, sizeof(quantum)); + if (initial_quantum != ~0U) + addattr_l(n, 1024, TCA_FQ_INITIAL_QUANTUM, + &initial_quantum, sizeof(initial_quantum)); + if (pacing != -1) + addattr_l(n, 1024, TCA_FQ_RATE_ENABLE, + &pacing, sizeof(pacing)); + if (maxrate != ~0U) + addattr_l(n, 1024, TCA_FQ_FLOW_MAX_RATE, + &maxrate, sizeof(maxrate)); + if (defrate != ~0U) + addattr_l(n, 1024, TCA_FQ_FLOW_DEFAULT_RATE, + &defrate, sizeof(defrate)); + tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; + return 0; +} + +static int fq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_FQ_MAX + 1]; + unsigned int plimit, flow_plimit; + unsigned int buckets_log; + int pacing; + unsigned int rate, quantum; + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_FQ_MAX, opt); + + if (tb[TCA_FQ_PLIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_PLIMIT]) >= sizeof(__u32)) { + plimit = rta_getattr_u32(tb[TCA_FQ_PLIMIT]); + fprintf(f, "limit %up ", plimit); + } + if (tb[TCA_FQ_FLOW_PLIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_PLIMIT]) >= sizeof(__u32)) { + flow_plimit = rta_getattr_u32(tb[TCA_FQ_FLOW_PLIMIT]); + fprintf(f, "flow_limit %up ", flow_plimit); + } + if (tb[TCA_FQ_BUCKETS_LOG] && + RTA_PAYLOAD(tb[TCA_FQ_BUCKETS_LOG]) >= sizeof(__u32)) { + buckets_log = rta_getattr_u32(tb[TCA_FQ_BUCKETS_LOG]); + fprintf(f, "buckets %u ", 1U << buckets_log); + } + if (tb[TCA_FQ_RATE_ENABLE] && + RTA_PAYLOAD(tb[TCA_FQ_RATE_ENABLE]) >= sizeof(int)) { + pacing = rta_getattr_u32(tb[TCA_FQ_RATE_ENABLE]); + if (pacing == 0) + fprintf(f, "nopacing "); + } + if (tb[TCA_FQ_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_QUANTUM]); + fprintf(f, "quantum %u ", quantum); + } + if (tb[TCA_FQ_INITIAL_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_INITIAL_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_INITIAL_QUANTUM]); + fprintf(f, "initial_quantum %u ", quantum); + } + if (tb[TCA_FQ_FLOW_MAX_RATE] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_MAX_RATE]) >= sizeof(__u32)) { + rate = rta_getattr_u32(tb[TCA_FQ_FLOW_MAX_RATE]); + + if (rate != ~0U) + fprintf(f, "maxrate %s ", sprint_rate(rate, b1)); + } + if (tb[TCA_FQ_FLOW_DEFAULT_RATE] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_DEFAULT_RATE]) >= sizeof(__u32)) { + rate = rta_getattr_u32(tb[TCA_FQ_FLOW_DEFAULT_RATE]); + + if (rate != 0) + fprintf(f, "defrate %s ", sprint_rate(rate, b1)); + } + + return 0; +} + +static int fq_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_fq_qd_stats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + + fprintf(f, " %u flows (%u inactive, %u throttled)", + st->flows, st->inactive_flows, st->throttled_flows); + + if (st->time_next_delayed_flow > 0) + fprintf(f, ", next packet delay %llu ns", st->time_next_delayed_flow); + + fprintf(f, "\n %llu gc, %llu highprio", + st->gc_flows, st->highprio_packets); + + if (st->tcp_retrans) + fprintf(f, ", %llu retrans", st->tcp_retrans); + + fprintf(f, ", %llu throttled", st->throttled); + + if (st->flows_plimit) + fprintf(f, ", %llu flows_plimit", st->flows_plimit); + + if (st->pkts_too_long || st->allocation_errors) + fprintf(f, "\n %llu too long pkts, %llu alloc errors\n", + st->pkts_too_long, st->allocation_errors); + + return 0; +} + +struct qdisc_util fq_qdisc_util = { + .id = "fq", + .parse_qopt = fq_parse_opt, + .print_qopt = fq_print_opt, + .print_xstats = fq_print_xstats, +};