diff --git a/include/Makefile.am b/include/Makefile.am index 79206fd..8df81eb 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -55,6 +55,7 @@ nobase_libnlinclude_HEADERS = \ netlink/route/qdisc/sfq.h \ netlink/route/qdisc/tbf.h \ netlink/route/qdisc/hfsc.h \ + netlink/route/qdisc/codel.h \ netlink/route/qdisc/fq_codel.h \ netlink/route/qdisc/plug.h \ netlink/route/addr.h \ diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h index 1c0727c..a38e1b9 100644 --- a/include/linux/pkt_sched.h +++ b/include/linux/pkt_sched.h @@ -624,6 +624,30 @@ struct tc_qfq_stats { __u32 lmax; }; +/* CODEL */ +enum { + TCA_CODEL_UNSPEC, + TCA_CODEL_TARGET, + TCA_CODEL_LIMIT, + TCA_CODEL_INTERVAL, + TCA_CODEL_ECN, + __TCA_CODEL_MAX +}; + +#define TCA_CODEL_MAX (__TCA_CODEL_MAX - 1) + +struct tc_codel_xstats { + __u32 maxpacket; /* largest packet we've seen so far */ + __u32 count; /* how many drops we've done since the last time we + * entered dropping state */ + __u32 lastcount; /* count at entry to dropping state */ + __u32 ldelay; /* delay seen by most recently dequeued packet */ + __s32 drop_next; /* time to drop next packet */ + __u32 drop_overlimit; /* instance count of max qdisc packet limit */ + __u32 ecn_mark; /* packet count we ECN marked instead of dropped */ + __u32 dropping; /* are we in dropping state ? */ +}; + /* FQ_CODEL */ enum { diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index ba21a7f..0fb48fd 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -590,6 +590,15 @@ struct rtnl_tbf uint32_t qt_mask; }; +struct rtnl_codel_qdisc +{ + uint32_t qc_target; + uint32_t qc_limit; + uint32_t qc_interval; + uint32_t qc_ecn; + uint32_t qc_mask; +}; + struct rtnl_fq_codel_qdisc { uint32_t qcq_target; diff --git a/include/netlink/route/qdisc/codel.h b/include/netlink/route/qdisc/codel.h new file mode 100644 index 0000000..a3653b8 --- /dev/null +++ b/include/netlink/route/qdisc/codel.h @@ -0,0 +1,34 @@ +/* + * netlink/route/sch/codel.h CODEL Qdisc + */ + +#ifndef NETLINK_CODEL_H_ +#define NETLINK_CODEL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Target Delay in usecs - Default 5 * 1000us */ +extern int rtnl_codel_qdisc_get_target_usecs(struct rtnl_qdisc *); +extern int rtnl_codel_qdisc_set_target_usecs(struct rtnl_qdisc *, + uint32_t); +/* Maximum number of enqueued packets before tail drop begins */ +extern int rtnl_codel_qdisc_get_packet_limit(struct rtnl_qdisc *); +extern int rtnl_codel_qdisc_set_packet_limit(struct rtnl_qdisc *, + uint32_t); +/* Moving window for calculating target delay in usecs - Default 100 * 1000us */ +extern int rtnl_codel_qdisc_get_interval(struct rtnl_qdisc *); +extern int rtnl_codel_qdisc_set_interval(struct rtnl_qdisc *, uint32_t); +/* Determines whether or not drops can be emulated via ECN - Default False*/ +extern int rtnl_codel_qdisc_get_ecn(struct rtnl_qdisc *); +extern int rtnl_codel_qdisc_set_ecn(struct rtnl_qdisc *, uint32_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Makefile.am b/lib/Makefile.am index e715ab7..ff85974 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -76,6 +76,7 @@ libnl_route_3_la_SOURCES = \ route/qdisc/prio.c route/qdisc/red.c route/qdisc/sfq.c \ route/qdisc/tbf.c route/qdisc/plug.c \ route/qdisc/fq_codel.c \ + route/qdisc/codel.c \ route/qdisc/hfsc.c \ \ fib_lookup/lookup.c fib_lookup/request.c \ diff --git a/lib/route/qdisc/codel.c b/lib/route/qdisc/codel.c new file mode 100644 index 0000000..3f952c2 --- /dev/null +++ b/lib/route/qdisc/codel.c @@ -0,0 +1,303 @@ +/* + * lib/route/qdisc/codel.c CODEL Qdisc + */ + +/** + * @ingroup qdisc + * @defgroup qdisc_codel Controlled Delay AQM + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_CODEL_ATTR_TARGET 0x1 +#define SCH_CODEL_ATTR_LIMIT 0x2 +#define SCH_CODEL_ATTR_INTERVAL 0x4 +#define SCH_CODEL_ATTR_ECN 0x8 +/** @endcond */ + +static struct nla_policy codel_policy[TCA_CODEL_MAX + 1] = { + [TCA_CODEL_TARGET] = { .type = NLA_U32 }, + [TCA_CODEL_LIMIT] = { .type = NLA_U32 }, + [TCA_CODEL_INTERVAL] = { .type = NLA_U32 }, + [TCA_CODEL_ECN] = { .type = NLA_U32 }, +}; + +static int codel_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_codel_qdisc *codel = data; + struct nlattr *tb[TCA_CODEL_MAX + 1]; + int err; + + err = tca_parse(tb, TCA_CODEL_MAX, tc, codel_policy); + if (err < 0) + return err; + + if (tb[TCA_CODEL_TARGET]) { + codel->qc_target = nla_get_u32(tb[TCA_CODEL_TARGET]); + codel->qc_mask |= SCH_CODEL_ATTR_TARGET; + } + + if (tb[TCA_CODEL_LIMIT]) { + codel->qc_limit = nla_get_u32(tb[TCA_CODEL_LIMIT]); + codel->qc_mask |= SCH_CODEL_ATTR_LIMIT; + } + + if (tb[TCA_CODEL_INTERVAL]) { + codel->qc_interval = nla_get_u32(tb[TCA_CODEL_INTERVAL]); + codel->qc_mask |= SCH_CODEL_ATTR_INTERVAL; + } + + if (tb[TCA_CODEL_ECN]) { + codel->qc_ecn = !!nla_get_u32(tb[TCA_CODEL_ECN]); + codel->qc_mask |= SCH_CODEL_ATTR_ECN; + } + + return 0; +} + +static void codel_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_codel_qdisc *codel = data; + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_TARGET)) + nl_dump(p, " target %u usecs", codel->qc_target); +} + +static void codel_qdisc_dump_details(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_codel_qdisc *codel = data; + + if (!codel) + return; + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_LIMIT)) + nl_dump(p, " limit %u packets", codel->qc_limit); + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_INTERVAL)) + nl_dump(p, " interval %u usecs", codel->qc_limit); + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_ECN)) + nl_dump(p, " ecn %s", codel->qc_ecn ? "enabled" : "disabled"); +} + +static int codel_qdisc_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_codel_qdisc *codel = data; + + if (!codel) + return 0; + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_TARGET)) + NLA_PUT_U32(msg, TCA_CODEL_TARGET, codel->qc_target); + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_LIMIT)) + NLA_PUT_U32(msg, TCA_CODEL_LIMIT, codel->qc_limit); + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_INTERVAL)) + NLA_PUT_U32(msg, TCA_CODEL_INTERVAL, codel->qc_interval); + + if (codel && (codel->qc_mask & SCH_CODEL_ATTR_ECN)) + NLA_PUT_U32(msg, TCA_CODEL_ECN, codel->qc_ecn); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +/** + * @name Qdisc Attribute Access + * @{ + */ + +/** + * Get target delay for CODEL qdisc. + * @arg qdisc CODEL qdisc. + * @return Target delay in microseconds, or a negative error code. + */ +int rtnl_codel_qdisc_get_target_usecs(struct rtnl_qdisc *qdisc) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (codel->qc_mask & SCH_CODEL_ATTR_TARGET) + return codel->qc_target; + else + return -NLE_NOATTR; +} + +/** + * Sets the target delay for CODEL qdisc. + * @arg qdisc CODEL qdisc to be modified. + * @arg target_usecs Target delay in miscroseconds. + * @return 0 on success, or a negative error code. + */ +int rtnl_codel_qdisc_set_target_usecs(struct rtnl_qdisc *qdisc, uint32_t target_usecs) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + codel->qc_target = target_usecs; + codel->qc_mask |= SCH_CODEL_ATTR_TARGET; + + return 0; +} + +/** + * Get maximum number of enqueued packets before tail drop occurs. + * @arg qdisc CODEL qdisc. + * @return Maximum number of enqueued packets, or a negative error code. + */ +int rtnl_codel_qdisc_get_packet_limit(struct rtnl_qdisc *qdisc) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (codel->qc_mask & SCH_CODEL_ATTR_LIMIT) + return codel->qc_limit; + else + return -NLE_NOATTR; +} + +/** + * Sets the maximum number of queued packets for CODEL qdisc. + * @arg qdisc CODEL qdisc to be modified. + * @arg limit Maximum number of queued packets before tail drop starts. + * @return 0 on success, or a negative error code. + */ +int rtnl_codel_qdisc_set_packet_limit(struct rtnl_qdisc *qdisc, uint32_t max_packets) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + codel->qc_limit = max_packets; + codel->qc_mask |= SCH_CODEL_ATTR_LIMIT; + + return 0; +} + + +/** + * Get width of the moving time window for calculating delay, in microseconds. + * @arg qdisc CODEL qdisc. + * @return Width of the moving window in microseconds, or a negative error code. + */ +int rtnl_codel_qdisc_get_interval(struct rtnl_qdisc *qdisc) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (codel->qc_mask & SCH_CODEL_ATTR_INTERVAL) + return codel->qc_interval; + else + return -NLE_NOATTR; +} + +/** + * Sets the width of the moving window over which the delay is calculated. + * @arg qdisc CODEL qdisc to be modified. + * @arg window_usecs Moving window width in microseconds + * @return 0 on success, or a negative error code. + */ +int rtnl_codel_qdisc_set_interval(struct rtnl_qdisc *qdisc, uint32_t window_usecs) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + codel->qc_interval = window_usecs; + codel->qc_mask |= SCH_CODEL_ATTR_INTERVAL; + + return 0; +} + + +/** + * Get ECN for CODEL qdisc. If this is set, CODEL will mark packets with ECN + * rather than dropping them, where it is possible. + * @arg qdisc CODEL qdisc. + * @return 1 if ECN marking instead of dropping is enabled, 0 if it is disabled, + * or a negative error code. + */ +int rtnl_codel_qdisc_get_ecn(struct rtnl_qdisc *qdisc) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (codel->qc_mask & SCH_CODEL_ATTR_ECN) + return !!(codel->qc_ecn); + else + return -NLE_NOATTR; +} + +/** + * Sets the target delay for CODEL qdisc. + * @arg qdisc CODEL qdisc to be modified. + * @arg ecn 0 indicates ECN marking instead of dropping is disabled, + * and 1 indicates that ECN marking instead of dropping is enabled. + * @return 0 on success, or a negative error code. + */ +int rtnl_codel_qdisc_set_ecn(struct rtnl_qdisc *qdisc, uint32_t ecn) +{ + struct rtnl_codel_qdisc *codel; + + if (!(codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + codel->qc_ecn = !!(ecn); + codel->qc_mask |= SCH_CODEL_ATTR_ECN; + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops codel_qdisc_ops = { + .to_kind = "codel", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_codel_qdisc), + .to_msg_parser = codel_qdisc_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = codel_qdisc_dump_line, + [NL_DUMP_DETAILS] = codel_qdisc_dump_details, + }, + .to_msg_fill = codel_qdisc_msg_fill, +}; + +static void __init codel_init(void) +{ + rtnl_tc_register(&codel_qdisc_ops); +} + +static void __exit codel_exit(void) +{ + rtnl_tc_unregister(&codel_qdisc_ops); +} + +/** @} */