summaryrefslogtreecommitdiffstats
path: root/lib/route/qdisc
diff options
context:
space:
mode:
authorThomas Graf <tgraf@suug.ch>2011-03-21 15:47:42 (GMT)
committerThomas Graf <tgraf@suug.ch>2011-03-21 15:47:42 (GMT)
commit45941f9d5f5f141e67cf2b6688c287aa5b52a4fd (patch)
tree21d321ab818f6ae46deeff949ae3613733deb39f /lib/route/qdisc
parent8eb5b5532eae985a5f0911dccf2db8cb4e0a5de4 (diff)
downloadlibnl-45941f9d5f5f141e67cf2b6688c287aa5b52a4fd.zip
libnl-45941f9d5f5f141e67cf2b6688c287aa5b52a4fd.tar.gz
libnl-45941f9d5f5f141e67cf2b6688c287aa5b52a4fd.tar.bz2
rename sch -> qdisc
Diffstat (limited to 'lib/route/qdisc')
-rw-r--r--lib/route/qdisc/blackhole.c37
-rw-r--r--lib/route/qdisc/cbq.c204
-rw-r--r--lib/route/qdisc/dsmark.c413
-rw-r--r--lib/route/qdisc/fifo.c169
-rw-r--r--lib/route/qdisc/htb.c433
-rw-r--r--lib/route/qdisc/netem.c905
-rw-r--r--lib/route/qdisc/prio.c294
-rw-r--r--lib/route/qdisc/red.c190
-rw-r--r--lib/route/qdisc/sfq.c256
-rw-r--r--lib/route/qdisc/tbf.c460
10 files changed, 3361 insertions, 0 deletions
diff --git a/lib/route/qdisc/blackhole.c b/lib/route/qdisc/blackhole.c
new file mode 100644
index 0000000..06f5380
--- /dev/null
+++ b/lib/route/qdisc/blackhole.c
@@ -0,0 +1,37 @@
+/*
+ * lib/route/qdisc/blackhole.c Blackhole Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_blackhole Blackhole
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink/netlink.h>
+#include <netlink/route/tc-api.h>
+
+static struct rtnl_tc_ops blackhole_ops = {
+ .to_kind = "blackhole",
+ .to_type = RTNL_TC_TYPE_QDISC,
+};
+
+static void __init blackhole_init(void)
+{
+ rtnl_tc_register(&blackhole_ops);
+}
+
+static void __exit blackhole_exit(void)
+{
+ rtnl_tc_unregister(&blackhole_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/cbq.c b/lib/route/qdisc/cbq.c
new file mode 100644
index 0000000..e791a10
--- /dev/null
+++ b/lib/route/qdisc/cbq.c
@@ -0,0 +1,204 @@
+/*
+ * lib/route/qdisc/cbq.c Class Based Queueing
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/cbq.h>
+#include <netlink/route/cls/police.h>
+
+/**
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_cbq Class Based Queueing (CBQ)
+ * @{
+ */
+
+static const struct trans_tbl ovl_strategies[] = {
+ __ADD(TC_CBQ_OVL_CLASSIC,classic)
+ __ADD(TC_CBQ_OVL_DELAY,delay)
+ __ADD(TC_CBQ_OVL_LOWPRIO,lowprio)
+ __ADD(TC_CBQ_OVL_DROP,drop)
+ __ADD(TC_CBQ_OVL_RCLASSIC,rclassic)
+};
+
+/**
+ * Convert a CBQ OVL strategy to a character string
+ * @arg type CBQ OVL strategy
+ * @arg buf destination buffer
+ * @arg len length of destination buffer
+ *
+ * Converts a CBQ OVL strategy to a character string and stores in the
+ * provided buffer. Returns the destination buffer or the type
+ * encoded in hex if no match was found.
+ */
+char *nl_ovl_strategy2str(int type, char *buf, size_t len)
+{
+ return __type2str(type, buf, len, ovl_strategies,
+ ARRAY_SIZE(ovl_strategies));
+}
+
+/**
+ * Convert a string to a CBQ OVL strategy
+ * @arg name CBQ OVL stragegy name
+ *
+ * Converts a CBQ OVL stragegy name to it's corresponding CBQ OVL strategy
+ * type. Returns the type or -1 if none was found.
+ */
+int nl_str2ovl_strategy(const char *name)
+{
+ return __str2type(name, ovl_strategies, ARRAY_SIZE(ovl_strategies));
+}
+
+static struct nla_policy cbq_policy[TCA_CBQ_MAX+1] = {
+ [TCA_CBQ_LSSOPT] = { .minlen = sizeof(struct tc_cbq_lssopt) },
+ [TCA_CBQ_RATE] = { .minlen = sizeof(struct tc_ratespec) },
+ [TCA_CBQ_WRROPT] = { .minlen = sizeof(struct tc_cbq_wrropt) },
+ [TCA_CBQ_OVL_STRATEGY] = { .minlen = sizeof(struct tc_cbq_ovl) },
+ [TCA_CBQ_FOPT] = { .minlen = sizeof(struct tc_cbq_fopt) },
+ [TCA_CBQ_POLICE] = { .minlen = sizeof(struct tc_cbq_police) },
+};
+
+static int cbq_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct nlattr *tb[TCA_CBQ_MAX + 1];
+ struct rtnl_cbq *cbq = data;
+ int err;
+
+ err = tca_parse(tb, TCA_CBQ_MAX, tc, cbq_policy);
+ if (err < 0)
+ return err;
+
+ nla_memcpy(&cbq->cbq_lss, tb[TCA_CBQ_LSSOPT], sizeof(cbq->cbq_lss));
+ nla_memcpy(&cbq->cbq_rate, tb[TCA_CBQ_RATE], sizeof(cbq->cbq_rate));
+ nla_memcpy(&cbq->cbq_wrr, tb[TCA_CBQ_WRROPT], sizeof(cbq->cbq_wrr));
+ nla_memcpy(&cbq->cbq_fopt, tb[TCA_CBQ_FOPT], sizeof(cbq->cbq_fopt));
+ nla_memcpy(&cbq->cbq_ovl, tb[TCA_CBQ_OVL_STRATEGY],
+ sizeof(cbq->cbq_ovl));
+ nla_memcpy(&cbq->cbq_police, tb[TCA_CBQ_POLICE],
+ sizeof(cbq->cbq_police));
+
+ return 0;
+}
+
+static void cbq_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_cbq *cbq = data;
+ double r, rbit;
+ char *ru, *rubit;
+
+ if (!cbq)
+ return;
+
+ r = nl_cancel_down_bytes(cbq->cbq_rate.rate, &ru);
+ rbit = nl_cancel_down_bits(cbq->cbq_rate.rate * 8, &rubit);
+
+ nl_dump(p, " rate %.2f%s/s (%.0f%s) prio %u",
+ r, ru, rbit, rubit, cbq->cbq_wrr.priority);
+}
+
+static void cbq_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_cbq *cbq = data;
+ char *unit, buf[32];
+ double w;
+ uint32_t el;
+
+ if (!cbq)
+ return;
+
+ w = nl_cancel_down_bits(cbq->cbq_wrr.weight * 8, &unit);
+
+ nl_dump(p, "avgpkt %u mpu %u cell %u allot %u weight %.0f%s\n",
+ cbq->cbq_lss.avpkt,
+ cbq->cbq_rate.mpu,
+ 1 << cbq->cbq_rate.cell_log,
+ cbq->cbq_wrr.allot, w, unit);
+
+ el = cbq->cbq_lss.ewma_log;
+ nl_dump_line(p, " minidle %uus maxidle %uus offtime "
+ "%uus level %u ewma_log %u\n",
+ nl_ticks2us(cbq->cbq_lss.minidle >> el),
+ nl_ticks2us(cbq->cbq_lss.maxidle >> el),
+ nl_ticks2us(cbq->cbq_lss.offtime >> el),
+ cbq->cbq_lss.level,
+ cbq->cbq_lss.ewma_log);
+
+ nl_dump_line(p, " penalty %uus strategy %s ",
+ nl_ticks2us(cbq->cbq_ovl.penalty),
+ nl_ovl_strategy2str(cbq->cbq_ovl.strategy, buf, sizeof(buf)));
+
+ nl_dump(p, "split %s defmap 0x%08x ",
+ rtnl_tc_handle2str(cbq->cbq_fopt.split, buf, sizeof(buf)),
+ cbq->cbq_fopt.defmap);
+
+ nl_dump(p, "police %s",
+ nl_police2str(cbq->cbq_police.police, buf, sizeof(buf)));
+}
+
+static void cbq_dump_stats(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct tc_cbq_xstats *x;
+
+ if (!(x = tca_xstats(tc)))
+ return;
+
+ nl_dump_line(p, " borrows overact "
+ " avgidle undertime\n");
+ nl_dump_line(p, " %10u %10u %10u %10u\n",
+ x->borrows, x->overactions, x->avgidle, x->undertime);
+}
+
+static struct rtnl_tc_ops cbq_qdisc_ops = {
+ .to_kind = "cbq",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_cbq),
+ .to_msg_parser = cbq_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = cbq_dump_line,
+ [NL_DUMP_DETAILS] = cbq_dump_details,
+ [NL_DUMP_STATS] = cbq_dump_stats,
+ },
+};
+
+static struct rtnl_tc_ops cbq_class_ops = {
+ .to_kind = "cbq",
+ .to_type = RTNL_TC_TYPE_CLASS,
+ .to_size = sizeof(struct rtnl_cbq),
+ .to_msg_parser = cbq_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = cbq_dump_line,
+ [NL_DUMP_DETAILS] = cbq_dump_details,
+ [NL_DUMP_STATS] = cbq_dump_stats,
+ },
+};
+
+static void __init cbq_init(void)
+{
+ rtnl_tc_register(&cbq_qdisc_ops);
+ rtnl_tc_register(&cbq_class_ops);
+}
+
+static void __exit cbq_exit(void)
+{
+ rtnl_tc_unregister(&cbq_qdisc_ops);
+ rtnl_tc_unregister(&cbq_class_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/dsmark.c b/lib/route/qdisc/dsmark.c
new file mode 100644
index 0000000..b5fd0d6
--- /dev/null
+++ b/lib/route/qdisc/dsmark.c
@@ -0,0 +1,413 @@
+/*
+ * lib/route/qdisc/dsmark.c DSMARK
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_dsmark Differentiated Services Marker (DSMARK)
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/class.h>
+#include <netlink/route/qdisc/dsmark.h>
+
+/** @cond SKIP */
+#define SCH_DSMARK_ATTR_INDICES 0x1
+#define SCH_DSMARK_ATTR_DEFAULT_INDEX 0x2
+#define SCH_DSMARK_ATTR_SET_TC_INDEX 0x4
+
+#define SCH_DSMARK_ATTR_MASK 0x1
+#define SCH_DSMARK_ATTR_VALUE 0x2
+/** @endcond */
+
+static struct nla_policy dsmark_policy[TCA_DSMARK_MAX+1] = {
+ [TCA_DSMARK_INDICES] = { .type = NLA_U16 },
+ [TCA_DSMARK_DEFAULT_INDEX] = { .type = NLA_U16 },
+ [TCA_DSMARK_SET_TC_INDEX] = { .type = NLA_FLAG },
+ [TCA_DSMARK_VALUE] = { .type = NLA_U8 },
+ [TCA_DSMARK_MASK] = { .type = NLA_U8 },
+};
+
+static int dsmark_qdisc_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_dsmark_qdisc *dsmark = data;
+ struct nlattr *tb[TCA_DSMARK_MAX + 1];
+ int err;
+
+ err = tca_parse(tb, TCA_DSMARK_MAX, tc, dsmark_policy);
+ if (err < 0)
+ return err;
+
+ if (tb[TCA_DSMARK_INDICES]) {
+ dsmark->qdm_indices = nla_get_u16(tb[TCA_DSMARK_INDICES]);
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_INDICES;
+ }
+
+ if (tb[TCA_DSMARK_DEFAULT_INDEX]) {
+ dsmark->qdm_default_index =
+ nla_get_u16(tb[TCA_DSMARK_DEFAULT_INDEX]);
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_DEFAULT_INDEX;
+ }
+
+ if (tb[TCA_DSMARK_SET_TC_INDEX]) {
+ dsmark->qdm_set_tc_index = 1;
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_SET_TC_INDEX;
+ }
+
+ return 0;
+}
+
+static int dsmark_class_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_dsmark_class *dsmark = data;
+ struct nlattr *tb[TCA_DSMARK_MAX + 1];
+ int err;
+
+ err = tca_parse(tb, TCA_DSMARK_MAX, tc, dsmark_policy);
+ if (err < 0)
+ return err;
+
+ if (tb[TCA_DSMARK_MASK]) {
+ dsmark->cdm_bmask = nla_get_u8(tb[TCA_DSMARK_MASK]);
+ dsmark->cdm_mask |= SCH_DSMARK_ATTR_MASK;
+ }
+
+ if (tb[TCA_DSMARK_VALUE]) {
+ dsmark->cdm_value = nla_get_u8(tb[TCA_DSMARK_VALUE]);
+ dsmark->cdm_mask |= SCH_DSMARK_ATTR_VALUE;
+ }
+
+ return 0;
+}
+
+static void dsmark_qdisc_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_dsmark_qdisc *dsmark = data;
+
+ if (dsmark && (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES))
+ nl_dump(p, " indices 0x%04x", dsmark->qdm_indices);
+}
+
+static void dsmark_qdisc_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_dsmark_qdisc *dsmark = data;
+
+ if (!dsmark)
+ return;
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_DEFAULT_INDEX)
+ nl_dump(p, " default index 0x%04x", dsmark->qdm_default_index);
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
+ nl_dump(p, " set-tc-index");
+}
+
+static void dsmark_class_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_dsmark_class *dsmark = data;
+
+ if (!dsmark)
+ return;
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
+ nl_dump(p, " value 0x%02x", dsmark->cdm_value);
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
+ nl_dump(p, " mask 0x%02x", dsmark->cdm_bmask);
+}
+
+static int dsmark_qdisc_msg_fill(struct rtnl_tc *tc, void *data,
+ struct nl_msg *msg)
+{
+ struct rtnl_dsmark_qdisc *dsmark = data;
+
+ if (!dsmark)
+ return 0;
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES)
+ NLA_PUT_U16(msg, TCA_DSMARK_INDICES, dsmark->qdm_indices);
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_DEFAULT_INDEX)
+ NLA_PUT_U16(msg, TCA_DSMARK_DEFAULT_INDEX,
+ dsmark->qdm_default_index);
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
+ NLA_PUT_FLAG(msg, TCA_DSMARK_SET_TC_INDEX);
+
+ return 0;
+
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
+static int dsmark_class_msg_fill(struct rtnl_tc *tc, void *data,
+ struct nl_msg *msg)
+{
+ struct rtnl_dsmark_class *dsmark = data;
+
+ if (!dsmark)
+ return 0;
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
+ NLA_PUT_U8(msg, TCA_DSMARK_MASK, dsmark->cdm_bmask);
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
+ NLA_PUT_U8(msg, TCA_DSMARK_VALUE, dsmark->cdm_value);
+
+ return 0;
+
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
+/**
+ * @name Class Attribute Access
+ * @{
+ */
+
+/**
+ * Set bitmask of DSMARK class.
+ * @arg class DSMARK class to be modified.
+ * @arg mask New bitmask.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_class_dsmark_set_bitmask(struct rtnl_class *class, uint8_t mask)
+{
+ struct rtnl_dsmark_class *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+ return -NLE_NOMEM;
+
+ dsmark->cdm_bmask = mask;
+ dsmark->cdm_mask |= SCH_DSMARK_ATTR_MASK;
+
+ return 0;
+}
+
+/**
+ * Get bitmask of DSMARK class.
+ * @arg class DSMARK class.
+ * @return Bitmask or a negative error code.
+ */
+int rtnl_class_dsmark_get_bitmask(struct rtnl_class *class)
+{
+ struct rtnl_dsmark_class *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+ return -NLE_NOMEM;
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_MASK)
+ return dsmark->cdm_bmask;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set value of DSMARK class.
+ * @arg class DSMARK class to be modified.
+ * @arg value New value.
+ * @return 0 on success or a negative errror code.
+ */
+int rtnl_class_dsmark_set_value(struct rtnl_class *class, uint8_t value)
+{
+ struct rtnl_dsmark_class *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+ return -NLE_NOMEM;
+
+ dsmark->cdm_value = value;
+ dsmark->cdm_mask |= SCH_DSMARK_ATTR_VALUE;
+
+ return 0;
+}
+
+/**
+ * Get value of DSMARK class.
+ * @arg class DSMARK class.
+ * @return Value or a negative error code.
+ */
+int rtnl_class_dsmark_get_value(struct rtnl_class *class)
+{
+ struct rtnl_dsmark_class *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(class))))
+ return -NLE_NOMEM;
+
+ if (dsmark->cdm_mask & SCH_DSMARK_ATTR_VALUE)
+ return dsmark->cdm_value;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Qdisc Attribute Access
+ * @{
+ */
+
+/**
+ * Set indices of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc to be modified.
+ * @arg indices New indices.
+ */
+int rtnl_qdisc_dsmark_set_indices(struct rtnl_qdisc *qdisc, uint16_t indices)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ dsmark->qdm_indices = indices;
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_INDICES;
+
+ return 0;
+}
+
+/**
+ * Get indices of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc.
+ * @return Indices or a negative error code.
+ */
+int rtnl_qdisc_dsmark_get_indices(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_INDICES)
+ return dsmark->qdm_indices;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set default index of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc to be modified.
+ * @arg default_index New default index.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_dsmark_set_default_index(struct rtnl_qdisc *qdisc,
+ uint16_t default_index)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ dsmark->qdm_default_index = default_index;
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_DEFAULT_INDEX;
+
+ return 0;
+}
+
+/**
+ * Get default index of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc.
+ * @return Default index or a negative error code.
+ */
+int rtnl_qdisc_dsmark_get_default_index(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_DEFAULT_INDEX)
+ return dsmark->qdm_default_index;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set set-tc-index flag of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc to be modified.
+ * @arg flag Flag indicating whether to enable or disable.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_dsmark_set_set_tc_index(struct rtnl_qdisc *qdisc, int flag)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ dsmark->qdm_set_tc_index = !!flag;
+ dsmark->qdm_mask |= SCH_DSMARK_ATTR_SET_TC_INDEX;
+
+ return 0;
+}
+
+/**
+ * Get set-tc-index flag of DSMARK qdisc.
+ * @arg qdisc DSMARK qdisc to be modified.
+ * @return 1 or 0 to indicate wehther the flag is enabled or a negative
+ * error code.
+ */
+int rtnl_qdisc_dsmark_get_set_tc_index(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_dsmark_qdisc *dsmark;
+
+ if (!(dsmark = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (dsmark->qdm_mask & SCH_DSMARK_ATTR_SET_TC_INDEX)
+ return dsmark->qdm_set_tc_index;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops dsmark_qdisc_ops = {
+ .to_kind = "dsmark",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_dsmark_qdisc),
+ .to_msg_parser = dsmark_qdisc_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = dsmark_qdisc_dump_line,
+ [NL_DUMP_DETAILS] = dsmark_qdisc_dump_details,
+ },
+ .to_msg_fill = dsmark_qdisc_msg_fill,
+};
+
+static struct rtnl_tc_ops dsmark_class_ops = {
+ .to_kind = "dsmark",
+ .to_type = RTNL_TC_TYPE_CLASS,
+ .to_size = sizeof(struct rtnl_dsmark_class),
+ .to_msg_parser = dsmark_class_msg_parser,
+ .to_dump[NL_DUMP_LINE] = dsmark_class_dump_line,
+ .to_msg_fill = dsmark_class_msg_fill,
+};
+
+static void __init dsmark_init(void)
+{
+ rtnl_tc_register(&dsmark_qdisc_ops);
+ rtnl_tc_register(&dsmark_class_ops);
+}
+
+static void __exit dsmark_exit(void)
+{
+ rtnl_tc_unregister(&dsmark_qdisc_ops);
+ rtnl_tc_unregister(&dsmark_class_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/fifo.c b/lib/route/qdisc/fifo.c
new file mode 100644
index 0000000..e87c79a
--- /dev/null
+++ b/lib/route/qdisc/fifo.c
@@ -0,0 +1,169 @@
+/*
+ * lib/route/qdisc/fifo.c (p|b)fifo
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_fifo Packet/Bytes FIFO (pfifo/bfifo)
+ * @brief
+ *
+ * The FIFO qdisc comes in two flavours:
+ * @par bfifo (Byte FIFO)
+ * Allows enqueuing until the currently queued volume in bytes exceeds
+ * the configured limit.backlog contains currently enqueued volume in bytes.
+ *
+ * @par pfifo (Packet FIFO)
+ * Allows enquueing until the currently queued number of packets
+ * exceeds the configured limit.
+ *
+ * The configuration is exactly the same, the decision which of
+ * the two variations is going to be used is made based on the
+ * kind of the qdisc (rtnl_tc_set_kind()).
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/fifo.h>
+#include <netlink/utils.h>
+
+/** @cond SKIP */
+#define SCH_FIFO_ATTR_LIMIT 1
+/** @endcond */
+
+static int fifo_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_fifo *fifo = data;
+ struct tc_fifo_qopt *opt;
+
+ if (tc->tc_opts->d_size < sizeof(struct tc_fifo_qopt))
+ return -NLE_INVAL;
+
+ opt = (struct tc_fifo_qopt *) tc->tc_opts->d_data;
+ fifo->qf_limit = opt->limit;
+ fifo->qf_mask = SCH_FIFO_ATTR_LIMIT;
+
+ return 0;
+}
+
+static void pfifo_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_fifo *fifo = data;
+
+ if (fifo)
+ nl_dump(p, " limit %u packets", fifo->qf_limit);
+}
+
+static void bfifo_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_fifo *fifo = data;
+ char *unit;
+ double r;
+
+ if (!fifo)
+ return;
+
+ r = nl_cancel_down_bytes(fifo->qf_limit, &unit);
+ nl_dump(p, " limit %.1f%s", r, unit);
+}
+
+static int fifo_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ struct rtnl_fifo *fifo = data;
+ struct tc_fifo_qopt opts = {0};
+
+ if (!fifo || !(fifo->qf_mask & SCH_FIFO_ATTR_LIMIT))
+ return -NLE_INVAL;
+
+ opts.limit = fifo->qf_limit;
+
+ return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Set limit of FIFO qdisc.
+ * @arg qdisc FIFO qdisc to be modified.
+ * @arg limit New limit.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_fifo_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+ struct rtnl_fifo *fifo;
+
+ if (!(fifo = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ fifo->qf_limit = limit;
+ fifo->qf_mask |= SCH_FIFO_ATTR_LIMIT;
+
+ return 0;
+}
+
+/**
+ * Get limit of a FIFO qdisc.
+ * @arg qdisc FIFO qdisc.
+ * @return Numeric limit or a negative error code.
+ */
+int rtnl_qdisc_fifo_get_limit(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_fifo *fifo;
+
+ if (!(fifo = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (fifo->qf_mask & SCH_FIFO_ATTR_LIMIT)
+ return fifo->qf_limit;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops pfifo_ops = {
+ .to_kind = "pfifo",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_fifo),
+ .to_msg_parser = fifo_msg_parser,
+ .to_dump[NL_DUMP_LINE] = pfifo_dump_line,
+ .to_msg_fill = fifo_msg_fill,
+};
+
+static struct rtnl_tc_ops bfifo_ops = {
+ .to_kind = "bfifo",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_fifo),
+ .to_msg_parser = fifo_msg_parser,
+ .to_dump[NL_DUMP_LINE] = bfifo_dump_line,
+ .to_msg_fill = fifo_msg_fill,
+};
+
+static void __init fifo_init(void)
+{
+ rtnl_tc_register(&pfifo_ops);
+ rtnl_tc_register(&bfifo_ops);
+}
+
+static void __exit fifo_exit(void)
+{
+ rtnl_tc_unregister(&pfifo_ops);
+ rtnl_tc_unregister(&bfifo_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/htb.c b/lib/route/qdisc/htb.c
new file mode 100644
index 0000000..94185de
--- /dev/null
+++ b/lib/route/qdisc/htb.c
@@ -0,0 +1,433 @@
+/*
+ * lib/route/qdisc/htb.c HTB Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com>
+ * Copyright (c) 2005-2006 Siemens AG Oesterreich
+ */
+
+/**
+ * @ingroup qdisc
+ * @ingroup class
+ * @defgroup qdisc_htb Hierachical Token Bucket (HTB)
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/htb.h>
+
+/** @cond SKIP */
+#define SCH_HTB_HAS_RATE2QUANTUM 0x01
+#define SCH_HTB_HAS_DEFCLS 0x02
+
+#define SCH_HTB_HAS_PRIO 0x001
+#define SCH_HTB_HAS_RATE 0x002
+#define SCH_HTB_HAS_CEIL 0x004
+#define SCH_HTB_HAS_RBUFFER 0x008
+#define SCH_HTB_HAS_CBUFFER 0x010
+#define SCH_HTB_HAS_QUANTUM 0x020
+/** @endcond */
+
+static struct nla_policy htb_policy[TCA_HTB_MAX+1] = {
+ [TCA_HTB_INIT] = { .minlen = sizeof(struct tc_htb_glob) },
+ [TCA_HTB_PARMS] = { .minlen = sizeof(struct tc_htb_opt) },
+};
+
+static int htb_qdisc_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct nlattr *tb[TCA_HTB_MAX + 1];
+ struct rtnl_htb_qdisc *htb = data;
+ int err;
+
+ if ((err = tca_parse(tb, TCA_HTB_MAX, tc, htb_policy)) < 0)
+ return err;
+
+ if (tb[TCA_HTB_INIT]) {
+ struct tc_htb_glob opts;
+
+ nla_memcpy(&opts, tb[TCA_HTB_INIT], sizeof(opts));
+ htb->qh_rate2quantum = opts.rate2quantum;
+ htb->qh_defcls = opts.defcls;
+
+ htb->qh_mask = (SCH_HTB_HAS_RATE2QUANTUM | SCH_HTB_HAS_DEFCLS);
+ }
+
+ return 0;
+}
+
+static int htb_class_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct nlattr *tb[TCA_HTB_MAX + 1];
+ struct rtnl_htb_class *htb = data;
+ int err;
+
+ if ((err = tca_parse(tb, TCA_HTB_MAX, tc, htb_policy)) < 0)
+ return err;
+
+ if (tb[TCA_HTB_PARMS]) {
+ struct tc_htb_opt opts;
+
+ nla_memcpy(&opts, tb[TCA_HTB_PARMS], sizeof(opts));
+ htb->ch_prio = opts.prio;
+ rtnl_copy_ratespec(&htb->ch_rate, &opts.rate);
+ rtnl_copy_ratespec(&htb->ch_ceil, &opts.ceil);
+ htb->ch_rbuffer = rtnl_tc_calc_bufsize(opts.buffer, opts.rate.rate);
+ htb->ch_cbuffer = rtnl_tc_calc_bufsize(opts.cbuffer, opts.ceil.rate);
+ htb->ch_quantum = opts.quantum;
+
+ rtnl_tc_set_mpu(tc, htb->ch_rate.rs_mpu);
+ rtnl_tc_set_overhead(tc, htb->ch_rate.rs_overhead);
+
+ htb->ch_mask = (SCH_HTB_HAS_PRIO | SCH_HTB_HAS_RATE |
+ SCH_HTB_HAS_CEIL | SCH_HTB_HAS_RBUFFER |
+ SCH_HTB_HAS_CBUFFER | SCH_HTB_HAS_QUANTUM);
+ }
+
+ return 0;
+}
+
+static void htb_qdisc_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_htb_qdisc *htb = data;
+
+ if (!htb)
+ return;
+
+ if (htb->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
+ nl_dump(p, " r2q %u", htb->qh_rate2quantum);
+
+ if (htb->qh_mask & SCH_HTB_HAS_DEFCLS) {
+ char buf[32];
+ nl_dump(p, " default %s",
+ rtnl_tc_handle2str(htb->qh_defcls, buf, sizeof(buf)));
+ }
+}
+
+static void htb_class_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_htb_class *htb = data;
+
+ if (!htb)
+ return;
+
+ if (htb->ch_mask & SCH_HTB_HAS_RATE) {
+ double r, rbit;
+ char *ru, *rubit;
+
+ r = nl_cancel_down_bytes(htb->ch_rate.rs_rate, &ru);
+ rbit = nl_cancel_down_bits(htb->ch_rate.rs_rate*8, &rubit);
+
+ nl_dump(p, " rate %.2f%s/s (%.0f%s) log %u",
+ r, ru, rbit, rubit, 1<<htb->ch_rate.rs_cell_log);
+ }
+}
+
+static void htb_class_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_htb_class *htb = data;
+
+ if (!htb)
+ return;
+
+ /* line 1 */
+ if (htb->ch_mask & SCH_HTB_HAS_CEIL) {
+ double r, rbit;
+ char *ru, *rubit;
+
+ r = nl_cancel_down_bytes(htb->ch_ceil.rs_rate, &ru);
+ rbit = nl_cancel_down_bits(htb->ch_ceil.rs_rate*8, &rubit);
+
+ nl_dump(p, " ceil %.2f%s/s (%.0f%s) log %u",
+ r, ru, rbit, rubit, 1<<htb->ch_ceil.rs_cell_log);
+ }
+
+ if (htb->ch_mask & SCH_HTB_HAS_PRIO)
+ nl_dump(p, " prio %u", htb->ch_prio);
+
+ if (htb->ch_mask & SCH_HTB_HAS_RBUFFER) {
+ double b;
+ char *bu;
+
+ b = nl_cancel_down_bytes(htb->ch_rbuffer, &bu);
+ nl_dump(p, " rbuffer %.2f%s", b, bu);
+ }
+
+ if (htb->ch_mask & SCH_HTB_HAS_CBUFFER) {
+ double b;
+ char *bu;
+
+ b = nl_cancel_down_bytes(htb->ch_cbuffer, &bu);
+ nl_dump(p, " cbuffer %.2f%s", b, bu);
+ }
+
+ if (htb->ch_mask & SCH_HTB_HAS_QUANTUM)
+ nl_dump(p, " quantum %u", htb->ch_quantum);
+}
+
+static int htb_qdisc_msg_fill(struct rtnl_tc *tc, void *data,
+ struct nl_msg *msg)
+{
+ struct rtnl_htb_qdisc *htb = data;
+ struct tc_htb_glob opts = {0};
+
+ opts.version = TC_HTB_PROTOVER;
+ opts.rate2quantum = 10;
+
+ if (htb) {
+ if (htb->qh_mask & SCH_HTB_HAS_RATE2QUANTUM)
+ opts.rate2quantum = htb->qh_rate2quantum;
+
+ if (htb->qh_mask & SCH_HTB_HAS_DEFCLS)
+ opts.defcls = htb->qh_defcls;
+ }
+
+ return nla_put(msg, TCA_HTB_INIT, sizeof(opts), &opts);
+}
+
+static int htb_class_msg_fill(struct rtnl_tc *tc, void *data,
+ struct nl_msg *msg)
+{
+ struct rtnl_htb_class *htb = data;
+ uint32_t mtu, rtable[RTNL_TC_RTABLE_SIZE], ctable[RTNL_TC_RTABLE_SIZE];
+ struct tc_htb_opt opts;
+ int buffer, cbuffer;
+
+ if (!htb || !(htb->ch_mask & SCH_HTB_HAS_RATE))
+ BUG();
+
+ /* if not set, zero (0) is used as priority */
+ if (htb->ch_mask & SCH_HTB_HAS_PRIO)
+ opts.prio = htb->ch_prio;
+
+ memset(&opts, 0, sizeof(opts));
+
+ mtu = rtnl_tc_get_mtu(tc);
+
+ rtnl_tc_build_rate_table(tc, &htb->ch_rate, rtable);
+ rtnl_rcopy_ratespec(&opts.rate, &htb->ch_rate);
+
+ if (htb->ch_mask & SCH_HTB_HAS_CEIL) {
+ rtnl_tc_build_rate_table(tc, &htb->ch_ceil, ctable);
+ rtnl_rcopy_ratespec(&opts.ceil, &htb->ch_ceil);
+ } else {
+ /*
+ * If not set, configured rate is used as ceil, which implies
+ * no borrowing.
+ */
+ memcpy(&opts.ceil, &opts.rate, sizeof(struct tc_ratespec));
+ }
+
+ if (htb->ch_mask & SCH_HTB_HAS_RBUFFER)
+ buffer = htb->ch_rbuffer;
+ else
+ buffer = opts.rate.rate / nl_get_user_hz() + mtu; /* XXX */
+
+ opts.buffer = rtnl_tc_calc_txtime(buffer, opts.rate.rate);
+
+ if (htb->ch_mask & SCH_HTB_HAS_CBUFFER)
+ cbuffer = htb->ch_cbuffer;
+ else
+ cbuffer = opts.ceil.rate / nl_get_user_hz() + mtu; /* XXX */
+
+ opts.cbuffer = rtnl_tc_calc_txtime(cbuffer, opts.ceil.rate);
+
+ if (htb->ch_mask & SCH_HTB_HAS_QUANTUM)
+ opts.quantum = htb->ch_quantum;
+
+ NLA_PUT(msg, TCA_HTB_PARMS, sizeof(opts), &opts);
+ NLA_PUT(msg, TCA_HTB_RTAB, sizeof(rtable), &rtable);
+ NLA_PUT(msg, TCA_HTB_CTAB, sizeof(ctable), &ctable);
+
+ return 0;
+
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
+/**
+ * @name Attribute Modifications
+ * @{
+ */
+
+void rtnl_htb_set_rate2quantum(struct rtnl_qdisc *qdisc, uint32_t rate2quantum)
+{
+ struct rtnl_htb_qdisc *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ htb->qh_rate2quantum = rate2quantum;
+ htb->qh_mask |= SCH_HTB_HAS_RATE2QUANTUM;
+}
+
+/**
+ * Set default class of the htb qdisc to the specified value
+ * @arg qdisc qdisc to change
+ * @arg defcls new default class
+ */
+void rtnl_htb_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls)
+{
+ struct rtnl_htb_qdisc *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ htb->qh_defcls = defcls;
+ htb->qh_mask |= SCH_HTB_HAS_DEFCLS;
+}
+
+void rtnl_htb_set_prio(struct rtnl_class *class, uint32_t prio)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_prio = prio;
+ htb->ch_mask |= SCH_HTB_HAS_PRIO;
+}
+
+/**
+ * Set rate of HTB class.
+ * @arg class HTB class to be modified.
+ * @arg rate New rate in bytes per second.
+ */
+void rtnl_htb_set_rate(struct rtnl_class *class, uint32_t rate)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_rate.rs_cell_log = UINT8_MAX; /* use default value */
+ htb->ch_rate.rs_rate = rate;
+ htb->ch_mask |= SCH_HTB_HAS_RATE;
+}
+
+uint32_t rtnl_htb_get_rate(struct rtnl_class *class)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ return 0;
+
+ return htb->ch_rate.rs_rate;
+}
+
+/**
+ * Set ceil of HTB class.
+ * @arg class HTB class to be modified.
+ * @arg ceil New ceil in bytes per second.
+ */
+void rtnl_htb_set_ceil(struct rtnl_class *class, uint32_t ceil)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_ceil.rs_cell_log = UINT8_MAX; /* use default value */
+ htb->ch_ceil.rs_rate = ceil;
+ htb->ch_mask |= SCH_HTB_HAS_CEIL;
+}
+
+/**
+ * Set size of the rate bucket of HTB class.
+ * @arg class HTB class to be modified.
+ * @arg rbuffer New size in bytes.
+ */
+void rtnl_htb_set_rbuffer(struct rtnl_class *class, uint32_t rbuffer)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_rbuffer = rbuffer;
+ htb->ch_mask |= SCH_HTB_HAS_RBUFFER;
+}
+
+/**
+ * Set size of the ceil bucket of HTB class.
+ * @arg class HTB class to be modified.
+ * @arg cbuffer New size in bytes.
+ */
+void rtnl_htb_set_cbuffer(struct rtnl_class *class, uint32_t cbuffer)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_cbuffer = cbuffer;
+ htb->ch_mask |= SCH_HTB_HAS_CBUFFER;
+}
+
+/**
+ * Set how much bytes to serve from leaf at once of HTB class {use r2q}.
+ * @arg class HTB class to be modified.
+ * @arg quantum New size in bytes.
+ */
+void rtnl_htb_set_quantum(struct rtnl_class *class, uint32_t quantum)
+{
+ struct rtnl_htb_class *htb;
+
+ if (!(htb = rtnl_tc_data(TC_CAST(class))))
+ BUG();
+
+ htb->ch_quantum = quantum;
+ htb->ch_mask |= SCH_HTB_HAS_QUANTUM;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops htb_qdisc_ops = {
+ .to_kind = "htb",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_htb_qdisc),
+ .to_msg_parser = htb_qdisc_msg_parser,
+ .to_dump[NL_DUMP_LINE] = htb_qdisc_dump_line,
+ .to_msg_fill = htb_qdisc_msg_fill,
+};
+
+static struct rtnl_tc_ops htb_class_ops = {
+ .to_kind = "htb",
+ .to_type = RTNL_TC_TYPE_CLASS,
+ .to_size = sizeof(struct rtnl_htb_class),
+ .to_msg_parser = htb_class_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = htb_class_dump_line,
+ [NL_DUMP_DETAILS] = htb_class_dump_details,
+ },
+ .to_msg_fill = htb_class_msg_fill,
+};
+
+static void __init htb_init(void)
+{
+ rtnl_tc_register(&htb_qdisc_ops);
+ rtnl_tc_register(&htb_class_ops);
+}
+
+static void __exit htb_exit(void)
+{
+ rtnl_tc_unregister(&htb_qdisc_ops);
+ rtnl_tc_unregister(&htb_class_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/netem.c b/lib/route/qdisc/netem.c
new file mode 100644
index 0000000..c86af56
--- /dev/null
+++ b/lib/route/qdisc/netem.c
@@ -0,0 +1,905 @@
+/*
+ * lib/route/qdisc/netem.c Network Emulator Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_netem Network Emulator
+ * @brief
+ *
+ * For further documentation see http://linux-net.osdl.org/index.php/Netem
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/netem.h>
+
+/** @cond SKIP */
+#define SCH_NETEM_ATTR_LATENCY 0x0001
+#define SCH_NETEM_ATTR_LIMIT 0x0002
+#define SCH_NETEM_ATTR_LOSS 0x0004
+#define SCH_NETEM_ATTR_GAP 0x0008
+#define SCH_NETEM_ATTR_DUPLICATE 0x0010
+#define SCH_NETEM_ATTR_JITTER 0x0020
+#define SCH_NETEM_ATTR_DELAY_CORR 0x0040
+#define SCH_NETEM_ATTR_LOSS_CORR 0x0080
+#define SCH_NETEM_ATTR_DUP_CORR 0x0100
+#define SCH_NETEM_ATTR_RO_PROB 0x0200
+#define SCH_NETEM_ATTR_RO_CORR 0x0400
+#define SCH_NETEM_ATTR_CORRUPT_PROB 0x0800
+#define SCH_NETEM_ATTR_CORRUPT_CORR 0x1000
+#define SCH_NETEM_ATTR_DIST 0x2000
+/** @endcond */
+
+static struct nla_policy netem_policy[TCA_NETEM_MAX+1] = {
+ [TCA_NETEM_CORR] = { .minlen = sizeof(struct tc_netem_corr) },
+ [TCA_NETEM_REORDER] = { .minlen = sizeof(struct tc_netem_reorder) },
+ [TCA_NETEM_CORRUPT] = { .minlen = sizeof(struct tc_netem_corrupt) },
+};
+
+static int netem_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_netem *netem = data;
+ struct tc_netem_qopt *opts;
+ int len, err = 0;
+
+ if (tc->tc_opts->d_size < sizeof(*opts))
+ return -NLE_INVAL;
+
+ opts = (struct tc_netem_qopt *) tc->tc_opts->d_data;
+ netem->qnm_latency = opts->latency;
+ netem->qnm_limit = opts->limit;
+ netem->qnm_loss = opts->loss;
+ netem->qnm_gap = opts->gap;
+ netem->qnm_duplicate = opts->duplicate;
+ netem->qnm_jitter = opts->jitter;
+
+ netem->qnm_mask = (SCH_NETEM_ATTR_LATENCY | SCH_NETEM_ATTR_LIMIT |
+ SCH_NETEM_ATTR_LOSS | SCH_NETEM_ATTR_GAP |
+ SCH_NETEM_ATTR_DUPLICATE | SCH_NETEM_ATTR_JITTER);
+
+ len = tc->tc_opts->d_size - sizeof(*opts);
+
+ if (len > 0) {
+ struct nlattr *tb[TCA_NETEM_MAX+1];
+
+ err = nla_parse(tb, TCA_NETEM_MAX, (struct nlattr *)
+ (tc->tc_opts->d_data + sizeof(*opts)),
+ len, netem_policy);
+ if (err < 0) {
+ free(netem);
+ return err;
+ }
+
+ if (tb[TCA_NETEM_CORR]) {
+ struct tc_netem_corr cor;
+
+ nla_memcpy(&cor, tb[TCA_NETEM_CORR], sizeof(cor));
+ netem->qnm_corr.nmc_delay = cor.delay_corr;
+ netem->qnm_corr.nmc_loss = cor.loss_corr;
+ netem->qnm_corr.nmc_duplicate = cor.dup_corr;
+
+ netem->qnm_mask |= (SCH_NETEM_ATTR_DELAY_CORR |
+ SCH_NETEM_ATTR_LOSS_CORR |
+ SCH_NETEM_ATTR_DUP_CORR);
+ }
+
+ if (tb[TCA_NETEM_REORDER]) {
+ struct tc_netem_reorder ro;
+
+ nla_memcpy(&ro, tb[TCA_NETEM_REORDER], sizeof(ro));
+ netem->qnm_ro.nmro_probability = ro.probability;
+ netem->qnm_ro.nmro_correlation = ro.correlation;
+
+ netem->qnm_mask |= (SCH_NETEM_ATTR_RO_PROB |
+ SCH_NETEM_ATTR_RO_CORR);
+ }
+
+ if (tb[TCA_NETEM_CORRUPT]) {
+ struct tc_netem_corrupt corrupt;
+
+ nla_memcpy(&corrupt, tb[TCA_NETEM_CORRUPT], sizeof(corrupt));
+ netem->qnm_crpt.nmcr_probability = corrupt.probability;
+ netem->qnm_crpt.nmcr_correlation = corrupt.correlation;
+
+ netem->qnm_mask |= (SCH_NETEM_ATTR_CORRUPT_PROB |
+ SCH_NETEM_ATTR_CORRUPT_CORR);
+ }
+
+ /* sch_netem does not currently dump TCA_NETEM_DELAY_DIST */
+ netem->qnm_dist.dist_data = NULL;
+ netem->qnm_dist.dist_size = 0;
+ }
+
+ return 0;
+}
+
+static void netem_free_data(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_netem *netem = data;
+
+ if (!netem)
+ return;
+
+ free(netem->qnm_dist.dist_data);
+}
+
+static void netem_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_netem *netem = data;
+
+ if (netem)
+ nl_dump(p, "limit %d", netem->qnm_limit);
+}
+
+int netem_msg_fill_raw(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ int err = 0;
+ struct tc_netem_qopt opts;
+ struct tc_netem_corr cor;
+ struct tc_netem_reorder reorder;
+ struct tc_netem_corrupt corrupt;
+ struct rtnl_netem *netem = data;
+
+ unsigned char set_correlation = 0, set_reorder = 0,
+ set_corrupt = 0, set_dist = 0;
+
+ if (!netem)
+ BUG();
+
+ memset(&opts, 0, sizeof(opts));
+ memset(&cor, 0, sizeof(cor));
+ memset(&reorder, 0, sizeof(reorder));
+ memset(&corrupt, 0, sizeof(corrupt));
+
+ msg->nm_nlh->nlmsg_flags |= NLM_F_REQUEST;
+
+ if ( netem->qnm_ro.nmro_probability != 0 ) {
+ if (netem->qnm_latency == 0) {
+ return -NLE_MISSING_ATTR;
+ }
+ if (netem->qnm_gap == 0) netem->qnm_gap = 1;
+ }
+ else if ( netem->qnm_gap ) {
+ return -NLE_MISSING_ATTR;
+ }
+
+ if ( netem->qnm_corr.nmc_delay != 0 ) {
+ if ( netem->qnm_latency == 0 || netem->qnm_jitter == 0) {
+ return -NLE_MISSING_ATTR;
+ }
+ set_correlation = 1;
+ }
+
+ if ( netem->qnm_corr.nmc_loss != 0 ) {
+ if ( netem->qnm_loss == 0 ) {
+ return -NLE_MISSING_ATTR;
+ }
+ set_correlation = 1;
+ }
+
+ if ( netem->qnm_corr.nmc_duplicate != 0 ) {
+ if ( netem->qnm_duplicate == 0 ) {
+ return -NLE_MISSING_ATTR;
+ }
+ set_correlation = 1;
+ }
+
+ if ( netem->qnm_ro.nmro_probability != 0 ) set_reorder = 1;
+ else if ( netem->qnm_ro.nmro_correlation != 0 ) {
+ return -NLE_MISSING_ATTR;
+ }
+
+ if ( netem->qnm_crpt.nmcr_probability != 0 ) set_corrupt = 1;
+ else if ( netem->qnm_crpt.nmcr_correlation != 0 ) {
+ return -NLE_MISSING_ATTR;
+ }
+
+ if ( netem->qnm_dist.dist_data && netem->qnm_dist.dist_size ) {
+ if (netem->qnm_latency == 0 || netem->qnm_jitter == 0) {
+ return -NLE_MISSING_ATTR;
+ }
+ else {
+ /* Resize to accomodate the large distribution table */
+ int new_msg_len = msg->nm_size + netem->qnm_dist.dist_size *
+ sizeof(netem->qnm_dist.dist_data[0]);
+
+ msg->nm_nlh = (struct nlmsghdr *) realloc(msg->nm_nlh, new_msg_len);
+ if ( msg->nm_nlh == NULL )
+ return -NLE_NOMEM;
+ msg->nm_size = new_msg_len;
+ set_dist = 1;
+ }
+ }
+
+ opts.latency = netem->qnm_latency;
+ opts.limit = netem->qnm_limit ? netem->qnm_limit : 1000;
+ opts.loss = netem->qnm_loss;
+ opts.gap = netem->qnm_gap;
+ opts.duplicate = netem->qnm_duplicate;
+ opts.jitter = netem->qnm_jitter;
+
+ NLA_PUT(msg, TCA_OPTIONS, sizeof(opts), &opts);
+
+ if ( set_correlation ) {
+ cor.delay_corr = netem->qnm_corr.nmc_delay;
+ cor.loss_corr = netem->qnm_corr.nmc_loss;
+ cor.dup_corr = netem->qnm_corr.nmc_duplicate;
+
+ NLA_PUT(msg, TCA_NETEM_CORR, sizeof(cor), &cor);
+ }
+
+ if ( set_reorder ) {
+ reorder.probability = netem->qnm_ro.nmro_probability;
+ reorder.correlation = netem->qnm_ro.nmro_correlation;
+
+ NLA_PUT(msg, TCA_NETEM_REORDER, sizeof(reorder), &reorder);
+ }
+
+ if ( set_corrupt ) {
+ corrupt.probability = netem->qnm_crpt.nmcr_probability;
+ corrupt.correlation = netem->qnm_crpt.nmcr_correlation;
+
+ NLA_PUT(msg, TCA_NETEM_CORRUPT, sizeof(corrupt), &corrupt);
+ }
+
+ if ( set_dist ) {
+ NLA_PUT(msg, TCA_NETEM_DELAY_DIST,
+ netem->qnm_dist.dist_size * sizeof(netem->qnm_dist.dist_data[0]),
+ netem->qnm_dist.dist_data);
+ }
+
+ /* Length specified in the TCA_OPTIONS section must span the entire
+ * remainder of the message. That's just the way that sch_netem expects it.
+ * Maybe there's a more succinct way to do this at a higher level.
+ */
+ struct nlattr* head = (struct nlattr *)(NLMSG_DATA(msg->nm_nlh) +
+ NLMSG_LENGTH(sizeof(struct tcmsg)) - NLMSG_ALIGNTO);
+
+ struct nlattr* tail = (struct nlattr *)(((void *) (msg->nm_nlh)) +
+ NLMSG_ALIGN(msg->nm_nlh->nlmsg_len));
+
+ int old_len = head->nla_len;
+ head->nla_len = (void *)tail - (void *)head;
+ msg->nm_nlh->nlmsg_len += (head->nla_len - old_len);
+
+ return err;
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
+/**
+ * @name Queue Limit
+ * @{
+ */
+
+/**
+ * Set limit of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg limit New limit in bytes.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_limit = limit;
+ netem->qnm_mask |= SCH_NETEM_ATTR_LIMIT;
+}
+
+/**
+ * Get limit of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Limit in bytes or a negative error code.
+ */
+int rtnl_netem_get_limit(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_LIMIT)
+ return netem->qnm_limit;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Packet Re-ordering
+ * @{
+ */
+
+/**
+ * Set re-ordering gap of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg gap New gap in number of packets.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_gap(struct rtnl_qdisc *qdisc, int gap)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_gap = gap;
+ netem->qnm_mask |= SCH_NETEM_ATTR_GAP;
+}
+
+/**
+ * Get re-ordering gap of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Re-ordering gap in packets or a negative error code.
+ */
+int rtnl_netem_get_gap(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_GAP)
+ return netem->qnm_gap;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set re-ordering probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New re-ordering probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_reorder_probability(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_ro.nmro_probability = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_RO_PROB;
+}
+
+/**
+ * Get re-ordering probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Re-ordering probability or a negative error code.
+ */
+int rtnl_netem_get_reorder_probability(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_RO_PROB)
+ return netem->qnm_ro.nmro_probability;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set re-order correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New re-ordering correlation probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_reorder_correlation(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_ro.nmro_correlation = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_RO_CORR;
+}
+
+/**
+ * Get re-ordering correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Re-ordering correlation probability or a negative error code.
+ */
+int rtnl_netem_get_reorder_correlation(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ return -NLE_NOMEM;
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_RO_CORR)
+ return netem->qnm_ro.nmro_correlation;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Corruption
+ * @{
+ */
+
+/**
+ * Set corruption probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New corruption probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_corruption_probability(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_crpt.nmcr_probability = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_PROB;
+}
+
+/**
+ * Get corruption probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Corruption probability or a negative error code.
+ */
+int rtnl_netem_get_corruption_probability(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_PROB)
+ return netem->qnm_crpt.nmcr_probability;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set corruption correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New corruption correlation probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_corruption_correlation(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_crpt.nmcr_correlation = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_CORRUPT_CORR;
+}
+
+/**
+ * Get corruption correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Corruption correlation probability or a negative error code.
+ */
+int rtnl_netem_get_corruption_correlation(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_CORRUPT_CORR)
+ return netem->qnm_crpt.nmcr_correlation;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Packet Loss
+ * @{
+ */
+
+/**
+ * Set packet loss probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New packet loss probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_loss(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_loss = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_LOSS;
+}
+
+/**
+ * Get packet loss probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet loss probability or a negative error code.
+ */
+int rtnl_netem_get_loss(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_LOSS)
+ return netem->qnm_loss;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set packet loss correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New packet loss correlation.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_loss_correlation(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_corr.nmc_loss = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_LOSS_CORR;
+}
+
+/**
+ * Get packet loss correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet loss correlation probability or a negative error code.
+ */
+int rtnl_netem_get_loss_correlation(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_LOSS_CORR)
+ return netem->qnm_corr.nmc_loss;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Packet Duplication
+ * @{
+ */
+
+/**
+ * Set packet duplication probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New packet duplication probability.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_duplicate(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_duplicate = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_DUPLICATE;
+}
+
+/**
+ * Get packet duplication probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet duplication probability or a negative error code.
+ */
+int rtnl_netem_get_duplicate(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_DUPLICATE)
+ return netem->qnm_duplicate;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set packet duplication correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New packet duplication correlation probability.
+ * @return 0 on sucess or a negative error code.
+ */
+void rtnl_netem_set_duplicate_correlation(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_corr.nmc_duplicate = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_DUP_CORR;
+}
+
+/**
+ * Get packet duplication correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet duplication correlation probability or a negative error code.
+ */
+int rtnl_netem_get_duplicate_correlation(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_DUP_CORR)
+ return netem->qnm_corr.nmc_duplicate;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+/**
+ * @name Packet Delay
+ * @{
+ */
+
+/**
+ * Set packet delay of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg delay New packet delay in micro seconds.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_delay(struct rtnl_qdisc *qdisc, int delay)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_latency = nl_us2ticks(delay);
+ netem->qnm_mask |= SCH_NETEM_ATTR_LATENCY;
+}
+
+/**
+ * Get packet delay of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet delay in micro seconds or a negative error code.
+ */
+int rtnl_netem_get_delay(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_LATENCY)
+ return nl_ticks2us(netem->qnm_latency);
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set packet delay jitter of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg jitter New packet delay jitter in micro seconds.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_netem_set_jitter(struct rtnl_qdisc *qdisc, int jitter)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_jitter = nl_us2ticks(jitter);
+ netem->qnm_mask |= SCH_NETEM_ATTR_JITTER;
+}
+
+/**
+ * Get packet delay jitter of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet delay jitter in micro seconds or a negative error code.
+ */
+int rtnl_netem_get_jitter(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_JITTER)
+ return nl_ticks2us(netem->qnm_jitter);
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set packet delay correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc to be modified.
+ * @arg prob New packet delay correlation probability.
+ */
+void rtnl_netem_set_delay_correlation(struct rtnl_qdisc *qdisc, int prob)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ netem->qnm_corr.nmc_delay = prob;
+ netem->qnm_mask |= SCH_NETEM_ATTR_DELAY_CORR;
+}
+
+/**
+ * Get packet delay correlation probability of netem qdisc.
+ * @arg qdisc Netem qdisc.
+ * @return Packet delay correlation probability or a negative error code.
+ */
+int rtnl_netem_get_delay_correlation(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_DELAY_CORR)
+ return netem->qnm_corr.nmc_delay;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Get the size of the distribution table.
+ * @arg qdisc Netem qdisc.
+ * @return Distribution table size or a negative error code.
+ */
+int rtnl_netem_get_delay_distribution_size(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_DIST)
+ return netem->qnm_dist.dist_size;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Get a pointer to the distribution table.
+ * @arg qdisc Netem qdisc.
+ * @arg dist_ptr The pointer to set.
+ * @return Negative error code on failure or 0 on success.
+ */
+int rtnl_netem_get_delay_distribution(struct rtnl_qdisc *qdisc, int16_t **dist_ptr)
+{
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (netem->qnm_mask & SCH_NETEM_ATTR_DIST) {
+ *dist_ptr = netem->qnm_dist.dist_data;
+ return 0;
+ } else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set the delay distribution. Latency/jitter must be set before applying.
+ * @arg qdisc Netem qdisc.
+ * @arg dist_type The name of the distribution (type, file, path/file).
+ * @return 0 on success, error code on failure.
+ */
+int rtnl_netem_set_delay_distribution(struct rtnl_qdisc *qdisc, const char *dist_type) {
+ struct rtnl_netem *netem;
+
+ if (!(netem = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ FILE *f = NULL;
+ int i, n = 0;
+ size_t len = 2048;
+ char *line;
+ char name[NAME_MAX];
+ char dist_suffix[] = ".dist";
+
+ /* If the given filename already ends in .dist, don't append it later */
+ char *test_suffix = strstr(dist_type, dist_suffix);
+ if (test_suffix != NULL && strlen(test_suffix) == 5)
+ strcpy(dist_suffix, "");
+
+ /* Check several locations for the dist file */
+ char *test_path[] = { "", "./", "/usr/lib/tc/", "/usr/local/lib/tc/" };
+
+ for (i = 0; i < sizeof(test_path) && f == NULL; i++) {
+ snprintf(name, NAME_MAX, "%s%s%s", test_path[i], dist_type, dist_suffix);
+ f = fopen(name, "r");
+ }
+
+ if ( f == NULL )
+ return -nl_syserr2nlerr(errno);
+
+ netem->qnm_dist.dist_data = (int16_t *) calloc (MAXDIST, sizeof(int16_t));
+
+ line = (char *) calloc (sizeof(char), len + 1);
+
+ while (getline(&line, &len, f) != -1) {
+ char *p, *endp;
+
+ if (*line == '\n' || *line == '#')
+ continue;
+
+ for (p = line; ; p = endp) {
+ long x = strtol(p, &endp, 0);
+ if (endp == p) break;
+
+ if (n >= MAXDIST) {
+ free(line);
+ fclose(f);
+ return -NLE_INVAL;
+ }
+ netem->qnm_dist.dist_data[n++] = x;
+ }
+ }
+
+ free(line);
+
+ netem->qnm_dist.dist_size = n;
+ netem->qnm_mask |= SCH_NETEM_ATTR_DIST;
+
+ fclose(f);
+ return 0;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops netem_ops = {
+ .to_kind = "netem",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_netem),
+ .to_msg_parser = netem_msg_parser,
+ .to_free_data = netem_free_data,
+ .to_dump[NL_DUMP_LINE] = netem_dump_line,
+ .to_msg_fill_raw = netem_msg_fill_raw,
+};
+
+static void __init netem_init(void)
+{
+ rtnl_tc_register(&netem_ops);
+}
+
+static void __exit netem_exit(void)
+{
+ rtnl_tc_unregister(&netem_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/prio.c b/lib/route/qdisc/prio.c
new file mode 100644
index 0000000..2433c61
--- /dev/null
+++ b/lib/route/qdisc/prio.c
@@ -0,0 +1,294 @@
+/*
+ * lib/route/qdisc/prio.c PRIO Qdisc/Class
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_prio (Fast) Prio
+ * @brief
+ *
+ * @par 1) Typical PRIO configuration
+ * @code
+ * // Specify the maximal number of bands to be used for this PRIO qdisc.
+ * rtnl_qdisc_prio_set_bands(qdisc, QDISC_PRIO_DEFAULT_BANDS);
+ *
+ * // Provide a map assigning each priority to a band number.
+ * uint8_t map[] = QDISC_PRIO_DEFAULT_PRIOMAP;
+ * rtnl_qdisc_prio_set_priomap(qdisc, map, sizeof(map));
+ * @endcode
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/prio.h>
+
+/** @cond SKIP */
+#define SCH_PRIO_ATTR_BANDS 1
+#define SCH_PRIO_ATTR_PRIOMAP 2
+/** @endcond */
+
+static int prio_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_prio *prio = data;
+ struct tc_prio_qopt *opt;
+
+ if (tc->tc_opts->d_size < sizeof(*opt))
+ return -NLE_INVAL;
+
+ opt = (struct tc_prio_qopt *) tc->tc_opts->d_data;
+ prio->qp_bands = opt->bands;
+ memcpy(prio->qp_priomap, opt->priomap, sizeof(prio->qp_priomap));
+ prio->qp_mask = (SCH_PRIO_ATTR_BANDS | SCH_PRIO_ATTR_PRIOMAP);
+
+ return 0;
+}
+
+static void prio_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_prio *prio = data;
+
+ if (prio)
+ nl_dump(p, " bands %u", prio->qp_bands);
+}
+
+static void prio_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_prio *prio = data;
+ int i, hp;
+
+ if (!prio)
+ return;
+
+ nl_dump(p, "priomap [");
+
+ for (i = 0; i <= TC_PRIO_MAX; i++)
+ nl_dump(p, "%u%s", prio->qp_priomap[i],
+ i < TC_PRIO_MAX ? " " : "");
+
+ nl_dump(p, "]\n");
+ nl_new_line(p);
+
+ hp = (((TC_PRIO_MAX/2) + 1) & ~1);
+
+ for (i = 0; i < hp; i++) {
+ char a[32];
+ nl_dump(p, " %18s => %u",
+ rtnl_prio2str(i, a, sizeof(a)),
+ prio->qp_priomap[i]);
+ if (hp+i <= TC_PRIO_MAX) {
+ nl_dump(p, " %18s => %u",
+ rtnl_prio2str(hp+i, a, sizeof(a)),
+ prio->qp_priomap[hp+i]);
+ if (i < (hp - 1)) {
+ nl_dump(p, "\n");
+ nl_new_line(p);
+ }
+ }
+ }
+}
+
+static int prio_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ struct rtnl_prio *prio = data;
+ struct tc_prio_qopt opts;
+
+ if (!prio || !(prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP))
+ BUG();
+
+ opts.bands = prio->qp_bands;
+ memcpy(opts.priomap, prio->qp_priomap, sizeof(opts.priomap));
+
+ return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Modification
+ * @{
+ */
+
+/**
+ * Set number of bands of PRIO qdisc.
+ * @arg qdisc PRIO qdisc to be modified.
+ * @arg bands New number of bands.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_qdisc_prio_set_bands(struct rtnl_qdisc *qdisc, int bands)
+{
+ struct rtnl_prio *prio;
+
+ if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ prio->qp_bands = bands;
+ prio->qp_mask |= SCH_PRIO_ATTR_BANDS;
+}
+
+/**
+ * Get number of bands of PRIO qdisc.
+ * @arg qdisc PRIO qdisc.
+ * @return Number of bands or a negative error code.
+ */
+int rtnl_qdisc_prio_get_bands(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_prio *prio;
+
+ if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (prio->qp_mask & SCH_PRIO_ATTR_BANDS)
+ return prio->qp_bands;
+ else
+ return -NLE_NOMEM;
+}
+
+/**
+ * Set priomap of the PRIO qdisc.
+ * @arg qdisc PRIO qdisc to be modified.
+ * @arg priomap New priority mapping.
+ * @arg len Length of priomap (# of elements).
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_prio_set_priomap(struct rtnl_qdisc *qdisc, uint8_t priomap[],
+ int len)
+{
+ struct rtnl_prio *prio;
+ int i;
+
+ if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (!(prio->qp_mask & SCH_PRIO_ATTR_BANDS))
+ return -NLE_MISSING_ATTR;
+
+ if ((len / sizeof(uint8_t)) > (TC_PRIO_MAX+1))
+ return -NLE_RANGE;
+
+ for (i = 0; i <= TC_PRIO_MAX; i++) {
+ if (priomap[i] > prio->qp_bands)
+ return -NLE_RANGE;
+ }
+
+ memcpy(prio->qp_priomap, priomap, len);
+ prio->qp_mask |= SCH_PRIO_ATTR_PRIOMAP;
+
+ return 0;
+}
+
+/**
+ * Get priomap of a PRIO qdisc.
+ * @arg qdisc PRIO qdisc.
+ * @return Priority mapping as array of size TC_PRIO_MAX+1
+ * or NULL if an error occured.
+ */
+uint8_t *rtnl_qdisc_prio_get_priomap(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_prio *prio;
+
+ if (!(prio = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (prio->qp_mask & SCH_PRIO_ATTR_PRIOMAP)
+ return prio->qp_priomap;
+ else
+ return NULL;
+}
+
+/** @} */
+
+/**
+ * @name Priority Band Translations
+ * @{
+ */
+
+static const struct trans_tbl prios[] = {
+ __ADD(TC_PRIO_BESTEFFORT,besteffort)
+ __ADD(TC_PRIO_FILLER,filler)
+ __ADD(TC_PRIO_BULK,bulk)
+ __ADD(TC_PRIO_INTERACTIVE_BULK,interactive_bulk)
+ __ADD(TC_PRIO_INTERACTIVE,interactive)
+ __ADD(TC_PRIO_CONTROL,control)
+};
+
+/**
+ * Convert priority to character string.
+ * @arg prio Priority.
+ * @arg buf Destination buffer
+ * @arg size Size of destination buffer.
+ *
+ * Converts a priority to a character string and stores the result in
+ * the specified destination buffer.
+ *
+ * @return Name of priority as character string.
+ */
+char * rtnl_prio2str(int prio, char *buf, size_t size)
+{
+ return __type2str(prio, buf, size, prios, ARRAY_SIZE(prios));
+}
+
+/**
+ * Convert character string to priority.
+ * @arg name Name of priority.
+ *
+ * Converts the provided character string specifying a priority
+ * to the corresponding numeric value.
+ *
+ * @return Numeric priority or a negative value if no match was found.
+ */
+int rtnl_str2prio(const char *name)
+{
+ return __str2type(name, prios, ARRAY_SIZE(prios));
+}
+
+/** @} */
+
+static struct rtnl_tc_ops prio_ops = {
+ .to_kind = "prio",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_prio),
+ .to_msg_parser = prio_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = prio_dump_line,
+ [NL_DUMP_DETAILS] = prio_dump_details,
+ },
+ .to_msg_fill = prio_msg_fill,
+};
+
+static struct rtnl_tc_ops pfifo_fast_ops = {
+ .to_kind = "pfifo_fast",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_prio),
+ .to_msg_parser = prio_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = prio_dump_line,
+ [NL_DUMP_DETAILS] = prio_dump_details,
+ },
+ .to_msg_fill = prio_msg_fill,
+};
+
+static void __init prio_init(void)
+{
+ rtnl_tc_register(&prio_ops);
+ rtnl_tc_register(&pfifo_fast_ops);
+}
+
+static void __exit prio_exit(void)
+{
+ rtnl_tc_unregister(&prio_ops);
+ rtnl_tc_unregister(&pfifo_fast_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/red.c b/lib/route/qdisc/red.c
new file mode 100644
index 0000000..0480282
--- /dev/null
+++ b/lib/route/qdisc/red.c
@@ -0,0 +1,190 @@
+/*
+ * lib/route/qdisc/red.c RED Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_red Random Early Detection (RED)
+ * @brief
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/red.h>
+
+/** @cond SKIP */
+#define RED_ATTR_LIMIT 0x01
+#define RED_ATTR_QTH_MIN 0x02
+#define RED_ATTR_QTH_MAX 0x04
+#define RED_ATTR_FLAGS 0x08
+#define RED_ATTR_WLOG 0x10
+#define RED_ATTR_PLOG 0x20
+#define RED_ATTR_SCELL_LOG 0x40
+/** @endcond */
+
+static struct nla_policy red_policy[TCA_RED_MAX+1] = {
+ [TCA_RED_PARMS] = { .minlen = sizeof(struct tc_red_qopt) },
+};
+
+static int red_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct nlattr *tb[TCA_RED_MAX+1];
+ struct rtnl_red *red = data;
+ struct tc_red_qopt *opts;
+ int err;
+
+ if (!(tc->ce_mask & TCA_ATTR_OPTS))
+ return 0;
+
+ err = tca_parse(tb, TCA_RED_MAX, tc, red_policy);
+ if (err < 0)
+ return err;
+
+ if (!tb[TCA_RED_PARMS])
+ return -NLE_MISSING_ATTR;
+
+ opts = nla_data(tb[TCA_RED_PARMS]);
+
+ red->qr_limit = opts->limit;
+ red->qr_qth_min = opts->qth_min;
+ red->qr_qth_max = opts->qth_max;
+ red->qr_flags = opts->flags;
+ red->qr_wlog = opts->Wlog;
+ red->qr_plog = opts->Plog;
+ red->qr_scell_log = opts->Scell_log;
+
+ red->qr_mask = (RED_ATTR_LIMIT | RED_ATTR_QTH_MIN | RED_ATTR_QTH_MAX |
+ RED_ATTR_FLAGS | RED_ATTR_WLOG | RED_ATTR_PLOG |
+ RED_ATTR_SCELL_LOG);
+
+ return 0;
+}
+
+static void red_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_red *red = data;
+
+ if (red) {
+ /* XXX: limit, min, max, flags */
+ }
+}
+
+static void red_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_red *red = data;
+
+ if (red) {
+ /* XXX: wlog, plog, scell_log */
+ }
+}
+
+static void red_dump_stats(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_red *red = data;
+
+ if (red) {
+ /* XXX: xstats */
+ }
+}
+
+static int red_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ struct rtnl_red *red = data;
+
+ if (!red)
+ BUG();
+
+#if 0
+ memset(&opts, 0, sizeof(opts));
+ opts.quantum = sfq->qs_quantum;
+ opts.perturb_period = sfq->qs_perturb;
+ opts.limit = sfq->qs_limit;
+
+ if (nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD) < 0)
+ goto errout;
+#endif
+
+ return -NLE_OPNOTSUPP;
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set limit of RED qdisc.
+ * @arg qdisc RED qdisc to be modified.
+ * @arg limit New limit in number of packets.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_red_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+ struct rtnl_red *red;
+
+ if (!(red = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ red->qr_limit = limit;
+ red->qr_mask |= RED_ATTR_LIMIT;
+}
+
+/**
+ * Get limit of RED qdisc.
+ * @arg qdisc RED qdisc.
+ * @return Limit or a negative error code.
+ */
+int rtnl_red_get_limit(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_red *red;
+
+ if (!(red = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (red->qr_mask & RED_ATTR_LIMIT)
+ return red->qr_limit;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops red_ops = {
+ .to_kind = "red",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_red),
+ .to_msg_parser = red_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = red_dump_line,
+ [NL_DUMP_DETAILS] = red_dump_details,
+ [NL_DUMP_STATS] = red_dump_stats,
+ },
+ .to_msg_fill = red_msg_fill,
+};
+
+static void __init red_init(void)
+{
+ rtnl_tc_register(&red_ops);
+}
+
+static void __exit red_exit(void)
+{
+ rtnl_tc_unregister(&red_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/sfq.c b/lib/route/qdisc/sfq.c
new file mode 100644
index 0000000..207140f
--- /dev/null
+++ b/lib/route/qdisc/sfq.c
@@ -0,0 +1,256 @@
+/*
+ * lib/route/qdisc/sfq.c SFQ Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_sfq Stochastic Fairness Queueing (SFQ)
+ * @brief
+ *
+ * @par Parameter Description
+ * - \b Quantum: Number of bytes to send out per slot and round.
+ * - \b Perturbation: Timer period between changing the hash function.
+ * - \b Limit: Upper limit of queue in number of packets before SFQ starts
+ * dropping packets.
+ * - \b Divisor: Hash table divisor, i.e. size of hash table.
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/sfq.h>
+
+/** @cond SKIP */
+#define SCH_SFQ_ATTR_QUANTUM 0x01
+#define SCH_SFQ_ATTR_PERTURB 0x02
+#define SCH_SFQ_ATTR_LIMIT 0x04
+#define SCH_SFQ_ATTR_DIVISOR 0x08
+#define SCH_SFQ_ATTR_FLOWS 0x10
+/** @endcond */
+
+static int sfq_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct rtnl_sfq *sfq = data;
+ struct tc_sfq_qopt *opts;
+
+ if (!(tc->ce_mask & TCA_ATTR_OPTS))
+ return 0;
+
+ if (tc->tc_opts->d_size < sizeof(*opts))
+ return -NLE_INVAL;
+
+ opts = (struct tc_sfq_qopt *) tc->tc_opts->d_data;
+
+ sfq->qs_quantum = opts->quantum;
+ sfq->qs_perturb = opts->perturb_period;
+ sfq->qs_limit = opts->limit;
+ sfq->qs_divisor = opts->divisor;
+ sfq->qs_flows = opts->flows;
+
+ sfq->qs_mask = (SCH_SFQ_ATTR_QUANTUM | SCH_SFQ_ATTR_PERTURB |
+ SCH_SFQ_ATTR_LIMIT | SCH_SFQ_ATTR_DIVISOR |
+ SCH_SFQ_ATTR_FLOWS);
+
+ return 0;
+}
+
+static void sfq_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_sfq *sfq = data;
+
+ if (sfq)
+ nl_dump(p, " quantum %u perturb %us", sfq->qs_quantum,
+ sfq->qs_perturb);
+}
+
+static void sfq_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_sfq *sfq = data;
+
+ if (sfq)
+ nl_dump(p, "limit %u divisor %u",
+ sfq->qs_limit, sfq->qs_divisor);
+}
+
+static int sfq_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ struct rtnl_sfq *sfq = data;
+ struct tc_sfq_qopt opts = {0};
+
+ if (!sfq)
+ BUG();
+
+ opts.quantum = sfq->qs_quantum;
+ opts.perturb_period = sfq->qs_perturb;
+ opts.limit = sfq->qs_limit;
+
+ return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set quantum of SFQ qdisc.
+ * @arg qdisc SFQ qdisc to be modified.
+ * @arg quantum New quantum in bytes.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_quantum(struct rtnl_qdisc *qdisc, int quantum)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ sfq->qs_quantum = quantum;
+ sfq->qs_mask |= SCH_SFQ_ATTR_QUANTUM;
+}
+
+/**
+ * Get quantum of SFQ qdisc.
+ * @arg qdisc SFQ qdisc.
+ * @return Quantum in bytes or a negative error code.
+ */
+int rtnl_sfq_get_quantum(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (sfq->qs_mask & SCH_SFQ_ATTR_QUANTUM)
+ return sfq->qs_quantum;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set limit of SFQ qdisc.
+ * @arg qdisc SFQ qdisc to be modified.
+ * @arg limit New limit in number of packets.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ sfq->qs_limit = limit;
+ sfq->qs_mask |= SCH_SFQ_ATTR_LIMIT;
+}
+
+/**
+ * Get limit of SFQ qdisc.
+ * @arg qdisc SFQ qdisc.
+ * @return Limit or a negative error code.
+ */
+int rtnl_sfq_get_limit(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (sfq->qs_mask & SCH_SFQ_ATTR_LIMIT)
+ return sfq->qs_limit;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Set perturbation interval of SFQ qdisc.
+ * @arg qdisc SFQ qdisc to be modified.
+ * @arg perturb New perturbation interval in seconds.
+ * @note A value of 0 disables perturbation altogether.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfq_set_perturb(struct rtnl_qdisc *qdisc, int perturb)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ sfq->qs_perturb = perturb;
+ sfq->qs_mask |= SCH_SFQ_ATTR_PERTURB;
+}
+
+/**
+ * Get perturbation interval of SFQ qdisc.
+ * @arg qdisc SFQ qdisc.
+ * @return Perturbation interval in seconds or a negative error code.
+ */
+int rtnl_sfq_get_perturb(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (sfq->qs_mask & SCH_SFQ_ATTR_PERTURB)
+ return sfq->qs_perturb;
+ else
+ return -NLE_NOATTR;
+}
+
+/**
+ * Get divisor of SFQ qdisc.
+ * @arg qdisc SFQ qdisc.
+ * @return Divisor in number of entries or a negative error code.
+ */
+int rtnl_sfq_get_divisor(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_sfq *sfq;
+
+ if (!(sfq = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (sfq->qs_mask & SCH_SFQ_ATTR_DIVISOR)
+ return sfq->qs_divisor;
+ else
+ return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops sfq_ops = {
+ .to_kind = "sfq",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_sfq),
+ .to_msg_parser = sfq_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = sfq_dump_line,
+ [NL_DUMP_DETAILS] = sfq_dump_details,
+ },
+ .to_msg_fill = sfq_msg_fill,
+};
+
+static void __init sfq_init(void)
+{
+ rtnl_tc_register(&sfq_ops);
+}
+
+static void __exit sfq_exit(void)
+{
+ rtnl_tc_unregister(&sfq_ops);
+}
+
+/** @} */
diff --git a/lib/route/qdisc/tbf.c b/lib/route/qdisc/tbf.c
new file mode 100644
index 0000000..8a6c400
--- /dev/null
+++ b/lib/route/qdisc/tbf.c
@@ -0,0 +1,460 @@
+/*
+ * lib/route/qdisc/tbf.c TBF Qdisc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_tbf Token Bucket Filter (TBF)
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink-tc.h>
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/utils.h>
+#include <netlink/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/tbf.h>
+
+/** @cond SKIP */
+#define TBF_ATTR_LIMIT 0x01
+#define TBF_ATTR_RATE 0x02
+#define TBF_ATTR_PEAKRATE 0x10
+/** @endcond */
+
+static struct nla_policy tbf_policy[TCA_TBF_MAX+1] = {
+ [TCA_TBF_PARMS] = { .minlen = sizeof(struct tc_tbf_qopt) },
+};
+
+static int tbf_msg_parser(struct rtnl_tc *tc, void *data)
+{
+ struct nlattr *tb[TCA_TBF_MAX + 1];
+ struct rtnl_tbf *tbf = data;
+ int err;
+
+ if ((err = tca_parse(tb, TCA_TBF_MAX, tc, tbf_policy)) < 0)
+ return err;
+
+ if (tb[TCA_TBF_PARMS]) {
+ struct tc_tbf_qopt opts;
+ int bufsize;
+
+ nla_memcpy(&opts, tb[TCA_TBF_PARMS], sizeof(opts));
+ tbf->qt_limit = opts.limit;
+
+ rtnl_copy_ratespec(&tbf->qt_rate, &opts.rate);
+ tbf->qt_rate_txtime = opts.buffer;
+ bufsize = rtnl_tc_calc_bufsize(nl_ticks2us(opts.buffer),
+ opts.rate.rate);
+ tbf->qt_rate_bucket = bufsize;
+
+ rtnl_copy_ratespec(&tbf->qt_peakrate, &opts.peakrate);
+ tbf->qt_peakrate_txtime = opts.mtu;
+ bufsize = rtnl_tc_calc_bufsize(nl_ticks2us(opts.mtu),
+ opts.peakrate.rate);
+ tbf->qt_peakrate_bucket = bufsize;
+
+ rtnl_tc_set_mpu(tc, tbf->qt_rate.rs_mpu);
+ rtnl_tc_set_overhead(tc, tbf->qt_rate.rs_overhead);
+
+ tbf->qt_mask = (TBF_ATTR_LIMIT | TBF_ATTR_RATE | TBF_ATTR_PEAKRATE);
+ }
+
+ return 0;
+}
+
+static void tbf_dump_line(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ double r, rbit, lim;
+ char *ru, *rubit, *limu;
+ struct rtnl_tbf *tbf = data;
+
+ if (!tbf)
+ return;
+
+ r = nl_cancel_down_bytes(tbf->qt_rate.rs_rate, &ru);
+ rbit = nl_cancel_down_bits(tbf->qt_rate.rs_rate*8, &rubit);
+ lim = nl_cancel_down_bytes(tbf->qt_limit, &limu);
+
+ nl_dump(p, " rate %.2f%s/s (%.0f%s) limit %.2f%s",
+ r, ru, rbit, rubit, lim, limu);
+}
+
+static void tbf_dump_details(struct rtnl_tc *tc, void *data,
+ struct nl_dump_params *p)
+{
+ struct rtnl_tbf *tbf = data;
+
+ if (!tbf)
+ return;
+
+ if (1) {
+ char *bu, *cu;
+ double bs = nl_cancel_down_bytes(tbf->qt_rate_bucket, &bu);
+ double cl = nl_cancel_down_bytes(1 << tbf->qt_rate.rs_cell_log,
+ &cu);
+
+ nl_dump(p, "rate-bucket-size %1.f%s "
+ "rate-cell-size %.1f%s\n",
+ bs, bu, cl, cu);
+
+ }
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
+ char *pru, *prbu, *bsu, *clu;
+ double pr, prb, bs, cl;
+
+ pr = nl_cancel_down_bytes(tbf->qt_peakrate.rs_rate, &pru);
+ prb = nl_cancel_down_bits(tbf->qt_peakrate.rs_rate * 8, &prbu);
+ bs = nl_cancel_down_bits(tbf->qt_peakrate_bucket, &bsu);
+ cl = nl_cancel_down_bits(1 << tbf->qt_peakrate.rs_cell_log,
+ &clu);
+
+ nl_dump_line(p, " peak-rate %.2f%s/s (%.0f%s) "
+ "bucket-size %.1f%s cell-size %.1f%s"
+ "latency %.1f%s",
+ pr, pru, prb, prbu, bs, bsu, cl, clu);
+ }
+}
+
+static int tbf_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+ uint32_t rtab[RTNL_TC_RTABLE_SIZE], ptab[RTNL_TC_RTABLE_SIZE];
+ struct tc_tbf_qopt opts;
+ struct rtnl_tbf *tbf = data;
+ int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
+
+ if (!(tbf->qt_mask & required) != required)
+ return -NLE_MISSING_ATTR;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.limit = tbf->qt_limit;
+ opts.buffer = tbf->qt_rate_txtime;
+
+ rtnl_tc_build_rate_table(tc, &tbf->qt_rate, rtab);
+ rtnl_rcopy_ratespec(&opts.rate, &tbf->qt_rate);
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
+ opts.mtu = tbf->qt_peakrate_txtime;
+ rtnl_tc_build_rate_table(tc, &tbf->qt_peakrate, ptab);
+ rtnl_rcopy_ratespec(&opts.peakrate, &tbf->qt_peakrate);
+
+ }
+
+ NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opts), &opts);
+ NLA_PUT(msg, TCA_TBF_RTAB, sizeof(rtab), rtab);
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
+ NLA_PUT(msg, TCA_TBF_PTAB, sizeof(ptab), ptab);
+
+ return 0;
+
+nla_put_failure:
+ return -NLE_MSGSIZE;
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set limit of TBF qdisc.
+ * @arg qdisc TBF qdisc to be modified.
+ * @arg limit New limit in bytes.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *qdisc, int limit)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ tbf->qt_limit = limit;
+ tbf->qt_mask |= TBF_ATTR_LIMIT;
+}
+
+static inline double calc_limit(struct rtnl_ratespec *spec, int latency,
+ int bucket)
+{
+ double limit;
+
+ limit = (double) spec->rs_rate * ((double) latency / 1000000.);
+ limit += bucket;
+
+ return limit;
+}
+
+/**
+ * Set limit of TBF qdisc by latency.
+ * @arg qdisc TBF qdisc to be modified.
+ * @arg latency Latency in micro seconds.
+ *
+ * Calculates and sets the limit based on the desired latency and the
+ * configured rate and peak rate. In order for this operation to succeed,
+ * the rate and if required the peak rate must have been set in advance.
+ *
+ * @f[
+ * limit_n = \frac{{rate_n} \times {latency}}{10^6}+{bucketsize}_n
+ * @f]
+ * @f[
+ * limit = min(limit_{rate},limit_{peak})
+ * @f]
+ *
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_tbf_set_limit_by_latency(struct rtnl_qdisc *qdisc, int latency)
+{
+ struct rtnl_tbf *tbf;
+ double limit, limit2;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (!(tbf->qt_mask & TBF_ATTR_RATE))
+ return -NLE_MISSING_ATTR;
+
+ limit = calc_limit(&tbf->qt_rate, latency, tbf->qt_rate_bucket);
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
+ limit2 = calc_limit(&tbf->qt_peakrate, latency,
+ tbf->qt_peakrate_bucket);
+
+ if (limit2 < limit)
+ limit = limit2;
+ }
+
+ rtnl_qdisc_tbf_set_limit(qdisc, (int) limit);
+
+ return 0;
+}
+
+/**
+ * Get limit of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Limit in bytes or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_limit(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_LIMIT)
+ return tbf->qt_limit;
+ else
+ return -NLE_NOATTR;
+}
+
+static inline int calc_cell_log(int cell, int bucket)
+{
+ cell = rtnl_tc_calc_cell_log(cell);
+ return cell;
+}
+
+/**
+ * Set rate of TBF qdisc.
+ * @arg qdisc TBF qdisc to be modified.
+ * @arg rate New rate in bytes per second.
+ * @arg bucket Size of bucket in bytes.
+ * @arg cell Size of a rate cell or 0 to get default value.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *qdisc, int rate, int bucket,
+ int cell)
+{
+ struct rtnl_tbf *tbf;
+ int cell_log;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (!cell)
+ cell_log = UINT8_MAX;
+ else
+ cell_log = rtnl_tc_calc_cell_log(cell);
+
+ tbf->qt_rate.rs_rate = rate;
+ tbf->qt_rate_bucket = bucket;
+ tbf->qt_rate.rs_cell_log = cell_log;
+ tbf->qt_rate_txtime = rtnl_tc_calc_txtime(bucket, rate);
+ tbf->qt_mask |= TBF_ATTR_RATE;
+}
+
+/**
+ * Get rate of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Rate in bytes per seconds or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_rate(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_RATE)
+ return tbf->qt_rate.rs_rate;
+ else
+ return -1;
+}
+
+/**
+ * Get rate bucket size of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Size of rate bucket or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_rate_bucket(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_RATE)
+ return tbf->qt_rate_bucket;
+ else
+ return -1;
+}
+
+/**
+ * Get rate cell size of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Size of rate cell in bytes or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_rate_cell(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_RATE)
+ return (1 << tbf->qt_rate.rs_cell_log);
+ else
+ return -1;
+}
+
+/**
+ * Set peak rate of TBF qdisc.
+ * @arg qdisc TBF qdisc to be modified.
+ * @arg rate New peak rate in bytes per second.
+ * @arg bucket Size of peakrate bucket.
+ * @arg cell Size of a peakrate cell or 0 to get default value.
+ * @return 0 on success or a negative error code.
+ */
+int rtnl_qdisc_tbf_set_peakrate(struct rtnl_qdisc *qdisc, int rate, int bucket,
+ int cell)
+{
+ struct rtnl_tbf *tbf;
+ int cell_log;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ cell_log = calc_cell_log(cell, bucket);
+ if (cell_log < 0)
+ return cell_log;
+
+ tbf->qt_peakrate.rs_rate = rate;
+ tbf->qt_peakrate_bucket = bucket;
+ tbf->qt_peakrate.rs_cell_log = cell_log;
+ tbf->qt_peakrate_txtime = rtnl_tc_calc_txtime(bucket, rate);
+
+ tbf->qt_mask |= TBF_ATTR_PEAKRATE;
+
+ return 0;
+}
+
+/**
+ * Get peak rate of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Peak rate in bytes per seconds or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_peakrate(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
+ return tbf->qt_peakrate.rs_rate;
+ else
+ return -1;
+}
+
+/**
+ * Get peak rate bucket size of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Size of peak rate bucket or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_peakrate_bucket(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
+ return tbf->qt_peakrate_bucket;
+ else
+ return -1;
+}
+
+/**
+ * Get peak rate cell size of TBF qdisc.
+ * @arg qdisc TBF qdisc.
+ * @return Size of peak rate cell in bytes or a negative error code.
+ */
+int rtnl_qdisc_tbf_get_peakrate_cell(struct rtnl_qdisc *qdisc)
+{
+ struct rtnl_tbf *tbf;
+
+ if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
+ BUG();
+
+ if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
+ return (1 << tbf->qt_peakrate.rs_cell_log);
+ else
+ return -1;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops tbf_tc_ops = {
+ .to_kind = "tbf",
+ .to_type = RTNL_TC_TYPE_QDISC,
+ .to_size = sizeof(struct rtnl_tbf),
+ .to_msg_parser = tbf_msg_parser,
+ .to_dump = {
+ [NL_DUMP_LINE] = tbf_dump_line,
+ [NL_DUMP_DETAILS] = tbf_dump_details,
+ },
+ .to_msg_fill = tbf_msg_fill,
+};
+
+static void __init tbf_init(void)
+{
+ rtnl_tc_register(&tbf_tc_ops);
+}
+
+static void __exit tbf_exit(void)
+{
+ rtnl_tc_unregister(&tbf_tc_ops);
+}
+
+/** @} */