diff --git a/include/Makefile.am b/include/Makefile.am index 1e07fdb..3f96690 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -54,6 +54,7 @@ nobase_libnlinclude_HEADERS = \ netlink/route/qdisc/red.h \ netlink/route/qdisc/sfq.h \ netlink/route/qdisc/tbf.h \ + netlink/route/qdisc/fq_codel.h \ netlink/route/qdisc/plug.h \ netlink/route/addr.h \ netlink/route/class.h \ diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h index 7ccc1fd..1c0727c 100644 --- a/include/linux/pkt_sched.h +++ b/include/linux/pkt_sched.h @@ -624,4 +624,57 @@ struct tc_qfq_stats { __u32 lmax; }; +/* FQ_CODEL */ + +enum { + TCA_FQ_CODEL_UNSPEC, + TCA_FQ_CODEL_TARGET, + TCA_FQ_CODEL_LIMIT, + TCA_FQ_CODEL_INTERVAL, + TCA_FQ_CODEL_ECN, + TCA_FQ_CODEL_FLOWS, + TCA_FQ_CODEL_QUANTUM, + __TCA_FQ_CODEL_MAX +}; + +#define TCA_FQ_CODEL_MAX (__TCA_FQ_CODEL_MAX - 1) + +enum { + TCA_FQ_CODEL_XSTATS_QDISC, + TCA_FQ_CODEL_XSTATS_CLASS, +}; + +struct tc_fq_codel_qd_stats { + __u32 maxpacket; /* largest packet we've seen so far */ + __u32 drop_overlimit; /* number of time max qdisc + * packet limit was hit + */ + __u32 ecn_mark; /* number of packets we ECN marked + * instead of being dropped + */ + __u32 new_flow_count; /* number of time packets + * created a 'new flow' + */ + __u32 new_flows_len; /* count of flows in new list */ + __u32 old_flows_len; /* count of flows in old list */ +}; + +struct tc_fq_codel_cl_stats { + __s32 deficit; + __u32 ldelay; /* in-queue delay seen by most recently + * dequeued packet + */ + __u32 count; + __u32 lastcount; + __u32 dropping; + __s32 drop_next; +}; + +struct tc_fq_codel_xstats { + __u32 type; + union { + struct tc_fq_codel_qd_stats qdisc_stats; + struct tc_fq_codel_cl_stats class_stats; + }; +}; #endif diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index 60a3bce..d6d056d 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -590,6 +590,17 @@ struct rtnl_tbf uint32_t qt_mask; }; +struct rtnl_fq_codel_qdisc +{ + uint32_t qcq_target; + uint32_t qcq_limit; + uint32_t qcq_interval; + uint32_t qcq_ecn; + uint32_t qcq_flows; + uint32_t qcq_quantum; + uint32_t qcq_mask; +}; + struct rtnl_sfq { uint32_t qs_quantum; diff --git a/include/netlink/route/qdisc/fq_codel.h b/include/netlink/route/qdisc/fq_codel.h new file mode 100644 index 0000000..da7c637 --- /dev/null +++ b/include/netlink/route/qdisc/fq_codel.h @@ -0,0 +1,41 @@ +/* + * netlink/route/sch/fq_codel.h Fair Queue CODEL Qdisc + */ + +#ifndef NETLINK_FQ_CODEL_H_ +#define NETLINK_FQ_CODEL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Target Delay in usecs - Default 5 * 1000us */ +extern int rtnl_fq_codel_qdisc_get_target_usecs(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_target_usecs(struct rtnl_qdisc *, + uint32_t); +/* Maximum number of enqueued packets before tail drop begins */ +extern int rtnl_fq_codel_qdisc_get_packet_limit(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_packet_limit(struct rtnl_qdisc *, + uint32_t); +/* Moving window for calculating target delay in usecs - Default 100 * 1000us */ +extern int rtnl_fq_codel_qdisc_get_interval(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_interval(struct rtnl_qdisc *, uint32_t); +/* Determines whether or not drops can be emulated via ECN - Default False */ +extern int rtnl_fq_codel_qdisc_get_ecn(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_ecn(struct rtnl_qdisc *, uint32_t); +/* The maximum number of flows into which connections may be classified */ +extern int rtnl_fq_codel_qdisc_get_max_flow_count(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_max_flow_count(struct rtnl_qdisc *, + uint32_t); +/* The quantum value for byte round robin between flows */ +extern int rtnl_fq_codel_qdisc_get_quantum(struct rtnl_qdisc *); +extern int rtnl_fq_codel_qdisc_set_quantum(struct rtnl_qdisc *, uint32_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Makefile.am b/lib/Makefile.am index 677a89c..1dce098 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -75,6 +75,7 @@ libnl_route_3_la_SOURCES = \ route/qdisc/fifo.c route/qdisc/htb.c route/qdisc/netem.c \ 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 \ \ fib_lookup/lookup.c fib_lookup/request.c \ \ diff --git a/lib/route/qdisc/fq_codel.c b/lib/route/qdisc/fq_codel.c new file mode 100644 index 0000000..3c99887 --- /dev/null +++ b/lib/route/qdisc/fq_codel.c @@ -0,0 +1,413 @@ +/* + * lib/route/qdisc/fq_codel.c Fair Queue CODEL Qdisc + */ + +/** + * @ingroup qdisc + * @defgroup qdisc_fq_codel Fair Queue with Per-Flow Controlled Delay AQM + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_FQ_CODEL_ATTR_TARGET 0x01 +#define SCH_FQ_CODEL_ATTR_LIMIT 0x02 +#define SCH_FQ_CODEL_ATTR_INTERVAL 0x04 +#define SCH_FQ_CODEL_ATTR_ECN 0x08 +#define SCH_FQ_CODEL_ATTR_FLOWS 0x10 +#define SCH_FQ_CODEL_ATTR_QUANTUM 0x20 +/** @endcond */ + +static struct nla_policy fq_codel_policy[TCA_FQ_CODEL_MAX + 1] = { + [TCA_FQ_CODEL_TARGET] = { .type = NLA_U32 }, + [TCA_FQ_CODEL_LIMIT] = { .type = NLA_U32 }, + [TCA_FQ_CODEL_INTERVAL] = { .type = NLA_U32 }, + [TCA_FQ_CODEL_ECN] = { .type = NLA_U32 }, + [TCA_FQ_CODEL_FLOWS] = { .type = NLA_U32 }, + [TCA_FQ_CODEL_QUANTUM] = { .type = NLA_U32 }, +}; + +static int fq_codel_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_fq_codel_qdisc *fq_codel = data; + struct nlattr *tb[TCA_FQ_CODEL_MAX + 1]; + int err; + + err = tca_parse(tb, TCA_FQ_CODEL_MAX, tc, fq_codel_policy); + if (err < 0) + return err; + + if (tb[TCA_FQ_CODEL_TARGET]) { + fq_codel->qcq_target = nla_get_u32(tb[TCA_FQ_CODEL_TARGET]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_TARGET; + } + + if (tb[TCA_FQ_CODEL_LIMIT]) { + fq_codel->qcq_limit = nla_get_u32(tb[TCA_FQ_CODEL_LIMIT]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_LIMIT; + } + + if (tb[TCA_FQ_CODEL_INTERVAL]) { + fq_codel->qcq_interval = nla_get_u32(tb[TCA_FQ_CODEL_INTERVAL]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_INTERVAL; + } + + if (tb[TCA_FQ_CODEL_ECN]) { + fq_codel->qcq_ecn = !!nla_get_u32(tb[TCA_FQ_CODEL_ECN]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_ECN; + } + + if (tb[TCA_FQ_CODEL_FLOWS]) { + fq_codel->qcq_flows = nla_get_u32(tb[TCA_FQ_CODEL_FLOWS]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_FLOWS; + } + + if (tb[TCA_FQ_CODEL_QUANTUM]) { + fq_codel->qcq_quantum = nla_get_u32(tb[TCA_FQ_CODEL_QUANTUM]); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_QUANTUM; + } + + return 0; +} + +static void fq_codel_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_fq_codel_qdisc *fq_codel = data; + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_TARGET)) + nl_dump(p, " target %u usecs", fq_codel->qcq_target); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_FLOWS)) + nl_dump(p, " max_flows %u", fq_codel->qcq_flows); +} + +static void fq_codel_qdisc_dump_details(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_fq_codel_qdisc *fq_codel = data; + + if (!fq_codel) + return; + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_LIMIT)) + nl_dump(p, " limit %u packets", fq_codel->qcq_limit); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_INTERVAL)) + nl_dump(p, " interval %u usecs", fq_codel->qcq_limit); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_ECN)) + nl_dump(p, " ecn %s", fq_codel->qcq_ecn ? "enable" : "disable"); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_QUANTUM)) + nl_dump(p, " quantum %u bytes", fq_codel->qcq_quantum); +} + +static int fq_codel_qdisc_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_fq_codel_qdisc *fq_codel = data; + + if (!fq_codel) + return 0; + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_TARGET)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_TARGET, fq_codel->qcq_target); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_LIMIT)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_LIMIT, fq_codel->qcq_limit); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_INTERVAL)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_INTERVAL, fq_codel->qcq_interval); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_ECN)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_ECN, fq_codel->qcq_ecn); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_FLOWS)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_FLOWS, fq_codel->qcq_flows); + + if (fq_codel && (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_QUANTUM)) + NLA_PUT_U32(msg, TCA_FQ_CODEL_QUANTUM, fq_codel->qcq_quantum); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +/** + * @name Qdisc Attribute Access + * @{ + */ + +/** + * Get target delay for FQ_CODEL qdisc. + * @arg qdisc FQ_CODEL qdisc. + * @return Target delay in microseconds, or a negative error code. + */ +int rtnl_fq_codel_qdisc_get_target_usecs(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_TARGET) + return fq_codel->qcq_target; + else + return -NLE_NOATTR; +} + +/** + * Sets the target delay for FQ_CODEL qdisc. + * @arg qdisc FQ_CODEL qdisc to be modified. + * @arg target_usecs Target delay in miscroseconds. + * @return 0 on success, or a negative error code. + */ +int rtnl_fq_codel_qdisc_set_target_usecs(struct rtnl_qdisc *qdisc, + uint32_t target_usecs) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_target = target_usecs; + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_TARGET; + + return 0; +} + +/** + * Get maximum number of enqueued packets before tail drop occurs. + * @arg qdisc FQ_CODEL qdisc. + * @return Maximum number of enqueued packets, or a negative error code. + */ +int rtnl_fq_codel_qdisc_get_packet_limit(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_LIMIT) + return fq_codel->qcq_limit; + else + return -NLE_NOATTR; +} + +/** + * Sets the maximum number of queued packets for FQ_CODEL qdisc. + * @arg qdisc FQ_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_fq_codel_qdisc_set_packet_limit(struct rtnl_qdisc *qdisc, + uint32_t max_packets) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_limit = max_packets; + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_LIMIT; + + return 0; +} + + +/** + * Get width of the moving time window for calculating delay, in microseconds. + * @arg qdisc FQ_CODEL qdisc. + * @return Width of the moving window in microseconds, or a negative error code. + */ +int rtnl_fq_codel_qdisc_get_interval(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_INTERVAL) + return fq_codel->qcq_interval; + else + return -NLE_NOATTR; +} + +/** + * Sets the width of the moving window over which the delay is calculated. + * @arg qdisc FQ_CODEL qdisc to be modified. + * @arg window_usecs Moving window width in microseconds + * @return 0 on success, or a negative error code. + */ +int rtnl_fq_codel_qdisc_set_interval(struct rtnl_qdisc *qdisc, + uint32_t window_usecs) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_interval = window_usecs; + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_INTERVAL; + + return 0; +} + + +/** + * Get ECN for FQ_CODEL qdisc. If this is set, FQ_CODEL will mark packets with + * ECN rather than dropping them, where it is possible. + * @arg qdisc FQ_CODEL qdisc. + * @return 1 if ECN marking instead of dropping is enabled, 0 if it is disabled, + * or a negative error code. + */ +int rtnl_fq_codel_qdisc_get_ecn(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_ECN) + return !!(fq_codel->qcq_ecn); + else + return -NLE_NOATTR; +} + +/** + * Sets the target delay for FQ_CODEL qdisc. + * @arg qdisc FQ_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_fq_codel_qdisc_set_ecn(struct rtnl_qdisc *qdisc, uint32_t ecn) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_ecn = !!(ecn); + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_ECN; + + return 0; +} + +/** + * Get maximum number of flows for FQ_CODEL qdisc. This is the number of + * flows into which connections may be classified. + * @arg qdisc FQ_CODEL qdisc. + * @return Maximum number of flows for qdisc, or a negative error code. + */ +int rtnl_fq_codel_qdisc_get_max_flow_count(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_FLOWS) + return fq_codel->qcq_flows; + else + return -NLE_NOATTR; +} + +/** + * Set the maximum number of flows for FQ_CODEL qdisc. This is the number of + * flows into which connections may be classified. + * @arg qdisc FQ_CODEL qdisc to be modified. + * @arg ecn New maximum number of flows. This must be greater than + * 0, and less than 65536. + * @return 0 on success, or a negative error code. + */ +int rtnl_fq_codel_qdisc_set_max_flow_count(struct rtnl_qdisc *qdisc, + uint32_t max_flows) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_flows = max_flows; + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_FLOWS; + + return 0; +} + +/** + * Get the quantum for round robin between the active flows. + * @arg qdisc FQ_CODEL qdisc. + * @return The current quantum in bytes each flow receives during round robin. + */ +int rtnl_fq_codel_qdisc_get_quantum(struct rtnl_qdisc *qdisc) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + if (fq_codel->qcq_mask & SCH_FQ_CODEL_ATTR_QUANTUM) + return fq_codel->qcq_quantum; + else + return -NLE_NOATTR; +} + +/** + * Set the quantum for round robin between the active flows. + * @arg qdisc FQ_CODEL qdisc to be modified. + * @arg quantum Set the number of bytes each flow may transmit before + * allowing the next flow to transmit during round robin between active flows. + * If this number is < 256, it will be set to 256. + * @return 0 on success, or a negative error code. + */ +int rtnl_fq_codel_qdisc_set_quantum(struct rtnl_qdisc *qdisc, + uint32_t quantum) +{ + struct rtnl_fq_codel_qdisc *fq_codel; + + if (!(fq_codel = rtnl_tc_data(TC_CAST(qdisc)))) + return -NLE_NOMEM; + + fq_codel->qcq_quantum = quantum; + fq_codel->qcq_mask |= SCH_FQ_CODEL_ATTR_QUANTUM; + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops fq_codel_qdisc_ops = { + .to_kind = "fq_codel", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_fq_codel_qdisc), + .to_msg_parser = fq_codel_qdisc_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = fq_codel_qdisc_dump_line, + [NL_DUMP_DETAILS] = fq_codel_qdisc_dump_details, + }, + .to_msg_fill = fq_codel_qdisc_msg_fill, +}; + +static void __init fq_codel_init(void) +{ + rtnl_tc_register(&fq_codel_qdisc_ops); +} + +static void __exit fq_codel_exit(void) +{ + rtnl_tc_unregister(&fq_codel_qdisc_ops); +} + +/** @} */