diff --git a/.gitignore b/.gitignore index 85ddb3c..8709bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.lo *.o *.swp +tags Makefile Makefile.in defs.h.in @@ -27,4 +28,4 @@ cscope.* /install-sh /missing /*.pc - +TAGS diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..c305cba --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,3 @@ +Overall Streamboost libnl Changes Maintainer: + Ben Menchaca qca.qualcomm.com> + ssh://crusher.qualcomm.com/git/krouter/libnl-bf diff --git a/include/Makefile.am b/include/Makefile.am index 2ba0ece..79ade11 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -54,6 +54,10 @@ nobase_libnlinclude_HEADERS = \ netlink/route/qdisc/red.h \ netlink/route/qdisc/sfq.h \ netlink/route/qdisc/tbf.h \ + netlink/route/qdisc/bf.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 \ netlink/route/class.h \ diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h index 7ccc1fd..a614d99 100644 --- a/include/linux/pkt_sched.h +++ b/include/linux/pkt_sched.h @@ -129,23 +129,23 @@ struct tc_multiq_qopt { /* PLUG section */ -#define TCQ_PLUG_BUFFER 0 -#define TCQ_PLUG_RELEASE_ONE 1 +#define TCQ_PLUG_BUFFER 0 +#define TCQ_PLUG_RELEASE_ONE 1 #define TCQ_PLUG_RELEASE_INDEFINITE 2 -#define TCQ_PLUG_LIMIT 3 +#define TCQ_PLUG_LIMIT 3 struct tc_plug_qopt { /* TCQ_PLUG_BUFFER: Inset a plug into the queue and - * buffer any incoming packets - * TCQ_PLUG_RELEASE_ONE: Dequeue packets from queue head - * to beginning of the next plug. - * TCQ_PLUG_RELEASE_INDEFINITE: Dequeue all packets from queue. - * Stop buffering packets until the next TCQ_PLUG_BUFFER - * command is received (just act as a pass-thru queue). - * TCQ_PLUG_LIMIT: Increase/decrease queue size - */ - int action; - __u32 limit; + * buffer any incoming packets + * TCQ_PLUG_RELEASE_ONE: Dequeue packets from queue head + * to beginning of the next plug. + * TCQ_PLUG_RELEASE_INDEFINITE: Dequeue all packets from queue. + * Stop buffering packets until the next TCQ_PLUG_BUFFER + * command is received (just act as a pass-thru queue). + * TCQ_PLUG_LIMIT: Increase/decrease queue size + */ + int action; + __u32 limit; }; /* TBF section */ @@ -168,7 +168,6 @@ enum { #define TCA_TBF_MAX (__TCA_TBF_MAX - 1) - /* TEQL section */ /* TEQL does not require any parameters */ @@ -220,10 +219,10 @@ struct tc_red_qopt { }; struct tc_red_xstats { - __u32 early; /* Early drops */ - __u32 pdrop; /* Drops due to queue limits */ - __u32 other; /* Drops due to drop() calls */ - __u32 marked; /* Marked packets */ + __u32 early; /* Early drops */ + __u32 pdrop; /* Drops due to queue limits */ + __u32 other; /* Drops due to drop() calls */ + __u32 marked; /* Marked packets */ }; /* GRED section */ @@ -244,17 +243,17 @@ struct tc_gred_qopt { __u32 limit; /* HARD maximal queue length (bytes) */ __u32 qth_min; /* Min average length threshold (bytes) */ __u32 qth_max; /* Max average length threshold (bytes) */ - __u32 DP; /* up to 2^32 DPs */ + __u32 DP; /* up to 2^32 DPs */ __u32 backlog; __u32 qave; __u32 forced; __u32 early; __u32 other; __u32 pdrop; - __u8 Wlog; /* log(W) */ - __u8 Plog; /* log(P_max/(qth_max-qth_min)) */ + __u8 Wlog; /* log(W) */ + __u8 Plog; /* log(P_max/(qth_max-qth_min)) */ __u8 Scell_log; /* cell size for idle damping */ - __u8 prio; /* prio of this VQ */ + __u8 prio; /* prio of this VQ */ __u32 packets; __u32 bytesin; }; @@ -290,10 +289,10 @@ struct tc_choke_qopt { }; struct tc_choke_xstats { - __u32 early; /* Early drops */ - __u32 pdrop; /* Drops due to queue limits */ - __u32 other; /* Drops due to drop() calls */ - __u32 marked; /* Marked packets */ + __u32 early; /* Early drops */ + __u32 pdrop; /* Drops due to queue limits */ + __u32 other; /* Drops due to drop() calls */ + __u32 marked; /* Marked packets */ __u32 matched; /* Drops due to flow match */ }; @@ -624,4 +623,136 @@ 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 { + 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; + }; +}; + + +/* BF section */ + +enum { + TCA_BF_UNSPEC = 0, + TCA_BF_PARAMS, + TCA_BF_INIT, + __TCA_BF_MAX, +}; + +enum TC_BF_STRATA { + TC_BF_STRATUM_RT = 0, + TC_BF_STRATUM_NOMINAL, + TC_BF_STRATUM_OPTIMAL, + TC_BF_STRATUM_BULK, + __TC_BF_STRATA_COUNT, +}; +#define TC_BF_STRATUM_NONE TC_BF_STRATUM_BULK + +#define TC_BF_MAX_PRIORITY_POW2 8 + +enum BF_PRIORITY_CALC { + BF_PRIORITY_CALC_DEFAULT = 0, + BF_PRIORITY_CALC_FLOW_NODE, + BF_PRIORITY_CALC_NODE_FLOW, + BF_PRIORITY_CALC_FLOW_ONLY, + BF_PRIORITY_CALC_NODE_ONLY, + __BF_PRIORITY_CALC_COUNT, +}; + +struct tc_bf_glob { + __u32 defcls; /* default class number */ + __u32 flow_priorities_pow2; /* Range of flow priorities log2 */ + __u32 node_priorities_pow2; /* Range of node priorities log2 */ + enum BF_PRIORITY_CALC calc; + __u32 total_bw; + __u32 direct_pkts; +}; + + +struct tc_bf_opt { + __u32 flow_priority; + __u32 node_priority; + __u32 bytes_per_sec_limits[__TC_BF_STRATA_COUNT]; +}; + +struct tc_bf_xstats { + enum TC_BF_STRATA oversub_strata; +}; + + +#define TCA_BF_MAX (__TCA_BF_MAX - 1) + + #endif diff --git a/include/netlink-local.h b/include/netlink-local.h index 01c611a..0c062a1 100644 --- a/include/netlink-local.h +++ b/include/netlink-local.h @@ -126,7 +126,9 @@ static inline int nl_cb_call(struct nl_cb *cb, int type, struct nl_msg *msg) return cb->cb_set[type](msg, cb->cb_args[type]); } +#ifndef ARRAY_SIZE #define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) +#endif /* This is also defined in stddef.h */ #ifndef offsetof diff --git a/include/netlink-types.h b/include/netlink-types.h index 5ced836..ea38faf 100644 --- a/include/netlink-types.h +++ b/include/netlink-types.h @@ -35,7 +35,7 @@ struct nl_cb { nl_recvmsg_msg_cb_t cb_set[NL_CB_TYPE_MAX+1]; void * cb_args[NL_CB_TYPE_MAX+1]; - + nl_recvmsg_err_cb_t cb_err; void * cb_err_arg; @@ -157,7 +157,7 @@ struct rtnl_link uint32_t l_txqlen; uint32_t l_weight; uint32_t l_master; - struct nl_addr * l_addr; + struct nl_addr * l_addr; struct nl_addr * l_bcast; char l_qdisc[IFQDISCSIZ]; struct rtnl_link_map l_map; @@ -190,9 +190,9 @@ struct rtnl_neigh uint32_t n_ifindex; uint16_t n_state; uint8_t n_flags; - uint8_t n_type; + uint8_t n_type; struct nl_addr *n_lladdr; - struct nl_addr *n_dst; + struct nl_addr *n_dst; uint32_t n_probes; struct rtnl_ncacheinfo n_cacheinfo; uint32_t n_state_mask; @@ -225,14 +225,14 @@ struct rtnl_addr uint8_t a_scope; uint32_t a_ifindex; - struct nl_addr *a_peer; + struct nl_addr *a_peer; struct nl_addr *a_local; struct nl_addr *a_bcast; struct nl_addr *a_anycast; struct nl_addr *a_multicast; struct rtnl_addr_cacheinfo a_cacheinfo; - + char a_label[IFNAMSIZ]; uint32_t a_flag_mask; struct rtnl_link *a_link; @@ -384,7 +384,7 @@ struct rtnl_neightbl_parms * Queue length for the delayed proxy arp requests. */ uint32_t ntp_proxy_qlen; - + /** * Mask of available parameter attributes */ @@ -552,6 +552,12 @@ struct rtnl_dsmark_class uint32_t cdm_mask; }; +struct rtnl_drr_class +{ + uint32_t cd_quantum; + uint32_t cd_mask; +}; + struct rtnl_fifo { uint32_t qf_limit; @@ -577,6 +583,45 @@ 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; + 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_bf_qdisc +{ + uint32_t qb_defcls; + uint32_t qb_flow_priorities_pow2; + uint32_t qb_node_priorities_pow2; + enum BF_PRIORITY_CALC qb_calc_method; + uint32_t qb_total_bw; + uint32_t qb_direct_pkts; + uint32_t qb_mask; +}; + +struct rtnl_bf_class +{ + uint32_t cb_flow_prio; + uint32_t cb_node_prio; + uint32_t cb_limits[__TC_BF_STRATA_COUNT]; + uint32_t cb_mask; +}; + struct rtnl_sfq { uint32_t qs_quantum; @@ -587,6 +632,20 @@ struct rtnl_sfq uint32_t qs_mask; }; +struct rtnl_sfb +{ + uint32_t qsb_rehash_interval; + uint32_t qsb_warmup_time; + uint32_t qsb_limit; + uint32_t qsb_target; + uint32_t qsb_max; + uint32_t qsb_increment; + uint32_t qsb_decrement; + uint32_t qsb_penalty_rate; + uint32_t qsb_penalty_burst; + uint32_t qsb_mask; +}; + struct rtnl_netem_corr { uint32_t nmc_delay; @@ -627,6 +686,27 @@ struct rtnl_netem struct rtnl_netem_dist qnm_dist; }; +struct rtnl_hfsc_qdisc +{ + uint32_t qsc_defcls; + uint32_t qsc_mask; +}; + +struct rtnl_curve +{ + uint32_t sc_m1; + uint32_t sc_d; + uint32_t sc_m2; +}; + +struct rtnl_hfsc_class +{ + struct rtnl_curve csc_rt_sc; + struct rtnl_curve csc_fair_sc; + struct rtnl_curve csc_upper_sc; + uint32_t csc_mask; +}; + struct rtnl_htb_qdisc { uint32_t qh_rate2quantum; diff --git a/include/netlink/route/qdisc/bf.h b/include/netlink/route/qdisc/bf.h new file mode 100644 index 0000000..3cf2acf --- /dev/null +++ b/include/netlink/route/qdisc/bf.h @@ -0,0 +1,47 @@ +/* + * netlink/route/sch/bf.h 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 + * Copyright (c) 2005 Petr Gotthard + * Copyright (c) 2005 Siemens AG Oesterreich + */ + +#ifndef NETLINK_BF_H_ +#define NETLINK_BF_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint32_t rtnl_bf_get_defcls(struct rtnl_qdisc *); +extern int rtnl_bf_set_defcls(struct rtnl_qdisc *, uint32_t); +extern uint32_t rtnl_bf_get_flow_priorities(struct rtnl_qdisc *); +extern uint32_t rtnl_bf_get_node_priorities(struct rtnl_qdisc *); +extern int rtnl_bf_set_priorities(struct rtnl_qdisc *, uint32_t flow_prios, + uint32_t node_prios); +extern enum BF_PRIORITY_CALC rtnl_bf_get_prio_calc_method(struct rtnl_qdisc *); +extern int rtnl_bf_set_prio_calc_method(struct rtnl_qdisc *, + enum BF_PRIORITY_CALC); +extern uint32_t rtnl_bf_get_total_bandwidth(struct rtnl_qdisc *); +extern int rtnl_bf_set_total_bandwidth(struct rtnl_qdisc *, uint32_t); + +extern uint32_t rtnl_bf_get_flow_prio(struct rtnl_class *); +extern int rtnl_bf_set_flow_prio(struct rtnl_class *, uint32_t); +extern uint32_t rtnl_bf_get_node_prio(struct rtnl_class *); +extern int rtnl_bf_set_node_prio(struct rtnl_class *, uint32_t); +extern uint32_t rtnl_bf_get_rate(struct rtnl_class *, uint32_t); +extern int rtnl_bf_set_rates(struct rtnl_class *, uint32_t *); + +#ifdef __cplusplus +} +#endif + +#endif 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/include/netlink/route/qdisc/drr.h b/include/netlink/route/qdisc/drr.h new file mode 100644 index 0000000..2d46e9d --- /dev/null +++ b/include/netlink/route/qdisc/drr.h @@ -0,0 +1,21 @@ +/* + * netlink/route/sch/drr.c Deficit Round Robin Qdisc + */ + +#ifndef NETLINK_DRR_H_ +#define NETLINK_DRR_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int rtnl_class_drr_set_quantum(struct rtnl_qdisc *, uint32_t); +extern uint32_t rtnl_class_drr_get_quantum(struct rtnl_qdisc *); + +#ifdef __cplusplus +} +#endif + +#endif 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/include/netlink/route/qdisc/hfsc.h b/include/netlink/route/qdisc/hfsc.h new file mode 100644 index 0000000..d47b29d --- /dev/null +++ b/include/netlink/route/qdisc/hfsc.h @@ -0,0 +1,46 @@ +/* + * netlink/route/sch/hfsc.h HFSC Qdisc + */ + +#ifndef NETLINK_HFSC_H_ +#define NETLINK_HFSC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct hfsc_spec { + unsigned int rate; + unsigned int delay_max; + unsigned int work_max; +}; + +extern uint32_t rtnl_hfsc_get_defcls(struct rtnl_qdisc *); +extern int rtnl_hfsc_set_defcls(struct rtnl_qdisc *, uint32_t); + +/* m1-d-m2: Direct Method of Specifying service curve slopes */ +extern void rtnl_hfsc_get_realtime_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_realtime_sc(struct rtnl_class *, + struct tc_service_curve *); +extern void rtnl_hfsc_get_fair_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_fair_sc(struct rtnl_class *, + struct tc_service_curve *); +extern void rtnl_hfsc_get_upperlimit_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_upperlimit_sc(struct rtnl_class *, + struct tc_service_curve *); + +/* umax-dmax-rate Method of specifying service curve translations */ +extern int rtnl_hfsc_spec_to_sc(struct hfsc_spec *, + struct tc_service_curve *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/netlink/route/qdisc/sfb.h b/include/netlink/route/qdisc/sfb.h new file mode 100644 index 0000000..29366d7 --- /dev/null +++ b/include/netlink/route/qdisc/sfb.h @@ -0,0 +1,47 @@ +/* + * netlink/route/sch/sfb.c SFB Qdisc + * + * Copyright (c) 2012 Ben Menchaca + */ + +#ifndef NETLINK_SFB_H_ +#define NETLINK_SFB_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern void rtnl_sfb_set_rehash_interval(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_rehash_interval(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_warmup_time(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_warmup_time(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_max(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_max(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_bin_size(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_bin_size(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_increment(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_increment(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_decrement(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_decrement(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_limit(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_limit(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_penalty_rate(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_penalty_rate(struct rtnl_qdisc *); + +extern void rtnl_sfb_set_penalty_burst(struct rtnl_qdisc *, int); +extern int rtnl_sfq_get_penalty_burst(struct rtnl_qdisc *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Makefile.am b/lib/Makefile.am index aee8d0f..cc88227 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -72,7 +72,10 @@ libnl_route_3_la_SOURCES = \ route/qdisc/blackhole.c route/qdisc/cbq.c route/qdisc/dsmark.c \ 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/tbf.c route/qdisc/plug.c route/qdisc/sfb.c \ + route/qdisc/hfsc.c route/qdisc/codel.c route/qdisc/fq_codel.c \ + route/qdisc/drr.c \ + route/qdisc/bf.c \ \ fib_lookup/lookup.c fib_lookup/request.c \ \ @@ -98,6 +101,8 @@ EXTRA_DIST = \ if ENABLE_CLI nobase_pkglib_LTLIBRARIES = \ + cli/qdisc/bf.la \ + cli/qdisc/hfsc.la \ cli/qdisc/htb.la \ cli/qdisc/blackhole.la \ cli/qdisc/pfifo.la \ @@ -107,6 +112,8 @@ nobase_pkglib_LTLIBRARIES = \ cli/cls/cgroup.la cli_qdisc_htb_la_LDFLAGS = -module -avoid-version +cli_qdisc_bf_la_LDFLAGS = -module -avoid-version +cli_qdisc_hfsc_la_LDFLAGS = -module -avoid-version cli_qdisc_blackhole_la_LDFLAGS = -module -avoid-version cli_qdisc_pfifo_la_LDFLAGS = -module -avoid-version cli_qdisc_plug_la_LDFLAGS = -module -avoid-version diff --git a/lib/cli/qdisc/bf.c b/lib/cli/qdisc/bf.c new file mode 100644 index 0000000..c7a4373 --- /dev/null +++ b/lib/cli/qdisc/bf.c @@ -0,0 +1,210 @@ +/* + * src/lib/bf.c Bigfoot module for CLI lib + * + * 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) 2010-2011 Thomas Graf + */ + +#include +#include +#include + +static void print_qdisc_usage(void) +{ + printf( +"Usage: nl-qdisc-add [...] bf [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --default=ID Default class for unclassified traffic.\n" +" --flow-prios=FPRIOS Range from 1 to NPRIOS available for flows.\n" +" --node-prios=NPRIOS Range from 1 to FPRIOS available for nodes.\n" +" --calc-method=METHOD ID of calc method for combining node/flow prios.\n" +" --total-bw=BYTES/SEC Total available bandwidth in bytes per second.\n" +"\n" +"CALC METHOD\n" +" The available methods are:\n" +" 0 Default This is the FLOW/NODE method\n" +" 1 Flow/Node Strict priority with flow priority precedence.\n" +" 2 Node/Flow Strict priority with node priority precedence.\n" +" 3 Flow Only Strict priority, flow only, node not considered.\n" +" 4 Node Only Strict priority, node only, flow not considered.\n" +"NOTE\n" +" Due to internal limitations, 0 is not a valid value for either FPRIOS or\n" +" NPRIOS. " +"EXAMPLE\n" +" # Create bf root qdisc 1: and direct unclassified traffic to class 1:10\n" +" nl-qdisc-add --dev=eth1 --parent=root --handle=1: bf --default=10 --flow-prios=4 --node-prios=16 --calc-method=0 --total-bw=1000000\n"); +} + +static void bf_parse_qdisc_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc; + unsigned int flow_prios = 0; + unsigned int node_prios = 0; + long rate; + + for (;;) { + int c, optidx = 0; + enum { + ARG_DEFAULT = 257, + ARG_FLOW_PRIOS = 258, + ARG_NODE_PRIOS = 259, + ARG_CALC_METHOD = 260, + ARG_TOTAL_BW = 261, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "default", 1, 0, ARG_DEFAULT }, + { "flow-prios", 1, 0, ARG_FLOW_PRIOS }, + { "node-prios", 1, 0, ARG_NODE_PRIOS }, + { "calc-method", 1, 0, ARG_CALC_METHOD }, + { "total-bw", 1, 0, ARG_TOTAL_BW }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hv", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_qdisc_usage(); + return; + case ARG_DEFAULT: + rtnl_bf_set_defcls(qdisc, nl_cli_parse_u32(optarg)); + break; + case ARG_FLOW_PRIOS: + flow_prios = nl_cli_parse_u32(optarg); + break; + case ARG_NODE_PRIOS: + node_prios = nl_cli_parse_u32(optarg); + break; + case ARG_CALC_METHOD: + rtnl_bf_set_prio_calc_method(qdisc, + nl_cli_parse_u32(optarg)); + break; + case ARG_TOTAL_BW: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse total-bw " + "\"%s\": Invalid format.", optarg); + } + rtnl_bf_set_total_bandwidth(qdisc, rate); + break; + + } + } + + if ((flow_prios != 0) || (node_prios != 0)) + rtnl_bf_set_priorities(qdisc, flow_prios, node_prios); +} + +static void print_class_usage(void) +{ + printf( +"Usage: nl-class-add [...] bf [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --realtime=RATE Realtime rate limit (default: 0).\n" +" --nominal=RATE Nominal rate limit (default: rt rate).\n" +" --optimal=RATE Optimal rate limit (default: nom rate).\n" +" --flow-prio=FPRIO App Priority, lower is served first (default: 1).\n" +" --node-prio=NPRIO Device Priority, lower is first (default: 1).\n" +" --total-bw=BW Total BW available for the interface (default: 0)\n" +"\n" +"EXAMPLE" +" # Attach class 1:1 to bf qdisc 1: and nominal rate limit it to 20mbit\n" +" nl-class-add --dev=eth1 --parent=1: --classid=1:1 bf --nom=20mbit\n"); +} + +static void bf_parse_class_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_class *cls = (struct rtnl_class *) tc; + unsigned int rates[__TC_BF_STRATA_COUNT] = {0}; + + for (;;) { + int c, optidx = 0; + enum { + ARG_RT = 257, + ARG_NOM = 258, + ARG_OPT = 259, + ARG_FPRIO = 260, + ARG_NPRIO = 261, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "realtime", 1, 0, ARG_RT }, + { "nominal", 1, 0, ARG_NOM }, + { "optimal", 1, 0, ARG_OPT }, + { "flow-prio", 1, 0, ARG_FPRIO }, + { "node-prio", 1, 0, ARG_NPRIO }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "h", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_class_usage(); + return; + case ARG_RT: + rates[TC_BF_STRATUM_RT] = nl_cli_parse_u32(optarg); + break; + case ARG_NOM: + rates[TC_BF_STRATUM_NOMINAL] = nl_cli_parse_u32(optarg); + break; + case ARG_OPT: + rates[TC_BF_STRATUM_OPTIMAL] = nl_cli_parse_u32(optarg); + break; + case ARG_FPRIO: + rtnl_bf_set_flow_prio(cls, nl_cli_parse_u32(optarg)); + break; + case ARG_NPRIO: + rtnl_bf_set_node_prio(cls, nl_cli_parse_u32(optarg)); + break; + + } + } + + if (rates[TC_BF_STRATUM_NOMINAL] == 0) + rates[TC_BF_STRATUM_NOMINAL] = rates[TC_BF_STRATUM_RT]; + + if (rates[TC_BF_STRATUM_OPTIMAL] == 0) + rates[TC_BF_STRATUM_OPTIMAL] = rates[TC_BF_STRATUM_NOMINAL]; + + rtnl_bf_set_rates(cls, rates); +} + +static struct nl_cli_tc_module bf_qdisc_module = +{ + .tm_name = "bf", + .tm_type = RTNL_TC_TYPE_QDISC, + .tm_parse_argv = bf_parse_qdisc_argv, +}; + +static struct nl_cli_tc_module bf_class_module = +{ + .tm_name = "bf", + .tm_type = RTNL_TC_TYPE_CLASS, + .tm_parse_argv = bf_parse_class_argv, +}; + +static void __init bf_init(void) +{ + nl_cli_tc_register(&bf_qdisc_module); + nl_cli_tc_register(&bf_class_module); +} + +static void __exit bf_exit(void) +{ + nl_cli_tc_unregister(&bf_class_module); + nl_cli_tc_unregister(&bf_qdisc_module); +} diff --git a/lib/cli/qdisc/hfsc.c b/lib/cli/qdisc/hfsc.c new file mode 100644 index 0000000..e452f56 --- /dev/null +++ b/lib/cli/qdisc/hfsc.c @@ -0,0 +1,211 @@ +/* + * src/lib/hfsc.c HFSC module for CLI lib + * + * 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. + */ + +#include +#include +#include + +static void print_qdisc_usage(void) +{ + printf( +"Usage: nl-qdisc-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --default=ID Default class for unclassified traffic.\n" +"\n" +"EXAMPLE" +" # Create hfsc root qdisc 1: and direct unclassified traffic to class 1:3\n" +" nl-qdisc-add --dev=eth1 --parent=root --handle=1: hfsc --default=3\n"); +} + +static void hfsc_parse_qdisc_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc; + + for (;;) { + int c, optidx = 0; + enum { + ARG_DEFAULT = 257, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "default", 1, 0, ARG_DEFAULT }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hv", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_qdisc_usage(); + return; + case ARG_DEFAULT: + rtnl_hfsc_set_defcls(qdisc, nl_cli_parse_u32(optarg)); + break; + } + } +} + +#if 0 +static void print_class_usage(void) +{ + printf( +"Usage: nl-class-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" Usage: ... hfsc [ [ --rt \"SC\" ] [ --ls \"SC\" ] | [--sc \"SC\" ] ] [--ul\"SC\" ]\n" +" --help Show this help text.\n" +" --realtime=\"SC\" Realtime service curve. Cannot be used with sc.\n" +" --linkshare=\"SC\" Link sharing service curve. Cannot be used with ls\n" +" --servicecurve=\"SC\" Combined service curve; Cannot be used with rt or ls.\n" +" --upperlimit=\"SC\" Upper limit service curve.\n" +" Slope Format\n" +" SC := [ [ m1 BPS ] [ d SEC ] m2 BPS\n" +" m1 : slope of first segment\n" +" d : x-coordinate of intersection\n" +" m2 : slope of second segment\n" +" Alternative Format:\n" +" SC := [ [ umax BYTE ] dmax SEC ] rate BPS\n" +" umax : maximum unit of work\n" +" dmax : maximum Delay\n" +" rate : rate\n" +"\n" +" Note: Service curve definitions must be in quotes on the command line.\n" +" Note: Must provide at least one service curve.\n" +" Note: If Upper limit is provided, linkshare or combined service curve must be given.\n" +"EXAMPLE" +" # Attach class 1:1 to hfsc qdisc 1: and rate limit it to 20mbit\n" +" nl-class-add --dev=eth1 --parent=1: --classid=1:1 hfsc --sc=\"rate 20mbit\"\n"); +} + + +static int hfsc_get_sc_slope(char *optarg, struct service_curve *sc_out) +{ + return 0; +} + +static int hfsc_get_sc_alt(char *optarg, struct service_curve *sc_out) +{ + return 0; +} + +static int hfsc_get_sc(char *optarg, struct service_curve *sc_out) +{ + memset(sc_out, 0, sizeof(*sc_out)); + + if ((hfsc_get_sc_slope(optarg, sc) < 0) && + (hfsc_get_sc_alt(optarg, sc) < 0)) + return -EINVAL; + + if ((sc->m1 == 0) && (sc->m2 == 0)) { + return -ENOENT; + } + +} + +static void hfsc_parse_class_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_class *class = (struct rtnl_class *) tc; + struct tc_service_curve sc; + long rate; + + for (;;) { + int c, optidx = 0; + enum { + ARG_RT = 257, + ARG_LS = 258, + ARG_SC = 259, + ARG_UL = 260, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "realtime", 1, 0, ARG_RT }, + { "linkshare", 1, 0, ARG_LS }, + { "servicecurve", 1, 0, ARG_SC }, + { "upperlimit", 1, 0, ARG_UL }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "h", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_class_usage(); + return; + + case ARG_RT: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse htb rate " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_rate(class, rate); + break; + + case ARG_LS: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse htb ceil rate " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_ceil(class, rate); + break; + + case ARG_SC: + rtnl_htb_set_prio(class, nl_cli_parse_u32(optarg)); + break; + + case ARG_UL: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse quantum " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_quantum(class, rate); + break; + } +} + +#endif + +static struct nl_cli_tc_module hfsc_qdisc_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_QDISC, + .tm_parse_argv = hfsc_parse_qdisc_argv, +}; + +#if 0 +static struct nl_cli_tc_module hfsc_class_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_CLASS, + .tm_parse_argv = hfsc_parse_class_argv, +}; +#endif + +static void __init hfsc_init(void) +{ + nl_cli_tc_register(&hfsc_qdisc_module); +// nl_cli_tc_register(&hfsc_class_module); +} + +static void __exit hfsc_exit(void) +{ +// nl_cli_tc_unregister(&hfsc_class_module); + nl_cli_tc_unregister(&hfsc_qdisc_module); +} diff --git a/lib/route/classid.c b/lib/route/classid.c index abed244..a6cdcb3 100644 --- a/lib/route/classid.c +++ b/lib/route/classid.c @@ -438,4 +438,13 @@ static void __init classid_init(void) fprintf(stderr, "Failed to read classid file: %s\n", nl_geterror(err)); } +static void __exit classid_exit(void) +{ + void free_map(void *map) { + free(((struct classid_map *)map)->name); + free(map); + }; + tdestroy(id_root, free_map); +} + /** @} */ diff --git a/lib/route/qdisc/bf.c b/lib/route/qdisc/bf.c new file mode 100644 index 0000000..1158e7b --- /dev/null +++ b/lib/route/qdisc/bf.c @@ -0,0 +1,535 @@ +/* + * lib/route/qdisc/bf.c BF 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 + */ + +/** + * @ingroup qdisc + * @ingroup class + * @defgroup qdisc_bf Bigfoot Scheduler (BF) + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_BF_HAS_DEFCLS 0x001 +#define SCH_BF_HAS_FLOW_PRIO_LIMIT 0x002 +#define SCH_BF_HAS_NODE_PRIO_LIMIT 0x004 +#define SCH_BF_HAS_PRIO_CALC_METHOD 0x008 +#define SCH_BF_HAS_TOTAL_BW 0x010 +#define SCH_BF_HAS_FLOW_PRIO 0x020 +#define SCH_BF_HAS_NODE_PRIO 0x040 +#define SCH_BF_HAS_RATES 0x080 +/** @endcond */ + +static char *strata_names[__TC_BF_STRATA_COUNT] = { + "realtime", "nominal", "optimal", "bulk", +}; + +static char *prio_calc_method_names[__BF_PRIORITY_CALC_COUNT] = { + "default", "flow-node", "node-flow", "flow-only", "node-only", +}; + +static struct nla_policy bf_policy[TCA_BF_MAX+1] = { + [TCA_BF_INIT] = { .minlen = sizeof(struct tc_bf_glob) }, + [TCA_BF_PARAMS] = { .minlen = sizeof(struct tc_bf_opt) }, +}; + +static int bf_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct nlattr *tb[TCA_BF_MAX + 1]; + struct rtnl_bf_qdisc *bf = data; + int err; + + if ((err = tca_parse(tb, TCA_BF_MAX, tc, bf_policy)) < 0) + return err; + + if (tb[TCA_BF_INIT]) { + struct tc_bf_glob opts; + + bf->qb_mask = 0; + nla_memcpy(&opts, tb[TCA_BF_INIT], sizeof(opts)); + + bf->qb_defcls = opts.defcls; + bf->qb_mask |= SCH_BF_HAS_DEFCLS; + + bf->qb_flow_priorities_pow2 = opts.flow_priorities_pow2; + bf->qb_mask |= SCH_BF_HAS_FLOW_PRIO_LIMIT; + + bf->qb_node_priorities_pow2 = opts.node_priorities_pow2; + bf->qb_mask |= SCH_BF_HAS_NODE_PRIO_LIMIT; + + bf->qb_calc_method = opts.calc; + bf->qb_mask |= SCH_BF_HAS_PRIO_CALC_METHOD; + + bf->qb_total_bw = opts.total_bw; + bf->qb_mask |= SCH_BF_HAS_TOTAL_BW; + } + + return 0; +} + +static int bf_class_msg_parser(struct rtnl_tc *tc, void *data) +{ + int err; + struct nlattr *tb[TCA_BF_MAX + 1]; + struct rtnl_bf_class *bf = data; + + if ((err = tca_parse(tb, TCA_BF_MAX, tc, bf_policy)) < 0) + return err; + + if (tb[TCA_BF_PARAMS]) { + unsigned int i; + struct tc_bf_opt opts; + + bf->cb_mask = 0; + nla_memcpy(&opts, tb[TCA_BF_PARAMS], sizeof(opts)); + bf->cb_flow_prio = opts.flow_priority; + bf->cb_mask |= SCH_BF_HAS_FLOW_PRIO; + + bf->cb_node_prio = opts.node_priority; + bf->cb_mask |= SCH_BF_HAS_NODE_PRIO; + + for (i = 0; i < __TC_BF_STRATA_COUNT; i++) { + bf->cb_limits[i] = opts.bytes_per_sec_limits[i]; + } + bf->cb_mask |= SCH_BF_HAS_RATES; + } + + return 0; +} + +static void bf_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_bf_qdisc *bf = data; + + if (!bf) + return; + + if (bf->qb_mask & SCH_BF_HAS_DEFCLS) { + char buf[64]; + nl_dump(p, " default-class %s", + rtnl_tc_handle2str(bf->qb_defcls, + buf, sizeof(buf))); + } + + if (bf->qb_mask & SCH_BF_HAS_FLOW_PRIO_LIMIT) { + nl_dump(p, " flow-priorities %u", + (1 << bf->qb_flow_priorities_pow2)); + } + + if (bf->qb_mask & SCH_BF_HAS_NODE_PRIO_LIMIT) { + nl_dump(p, " node-priorities %u", + (1 << bf->qb_node_priorities_pow2)); + } + + if (bf->qb_mask & SCH_BF_HAS_PRIO_CALC_METHOD) { + nl_dump(p, " priority-calculation %s", + prio_calc_method_names[bf->qb_calc_method]); + } + + if (bf->qb_mask & SCH_BF_HAS_TOTAL_BW) { + nl_dump(p, " total-bw %u", bf->qb_total_bw); + } +} + +static void bf_class_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_bf_class *bf = data; + + if (!bf) + return; + + if (bf->cb_mask & SCH_BF_HAS_FLOW_PRIO) { + nl_dump(p, " flow_prio %u", bf->cb_node_prio); + } + + if (bf->cb_mask & SCH_BF_HAS_NODE_PRIO) { + nl_dump(p, " node_prio %u", bf->cb_node_prio); + } + + if (bf->cb_mask & SCH_BF_HAS_RATES) { + unsigned int i; + + for (i = 0; i < __TC_BF_STRATA_COUNT; i++) { + nl_dump(p, " %s_limit %u", strata_names[i], + bf->cb_limits[i]); + } + } +} + +static void bf_class_dump_stats(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + enum TC_BF_STRATA stratum; + struct tc_bf_xstats *x; + + if (!(x = tca_xstats(tc))) + return; + + stratum = x->oversub_strata; + if (stratum == TC_BF_STRATUM_BULK) { + nl_dump(p, " No strata"); + } else { + while(stratum < TC_BF_STRATUM_BULK) { + nl_dump(p, " %s", strata_names[stratum]); + stratum++; + } + } + nl_dump_line(p, " limited (oversubscription)\n"); +} + +static int bf_qdisc_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg) +{ + struct rtnl_bf_qdisc *bf = data; + struct tc_bf_glob opts = {0}; + + if (bf) { + if (bf->qb_mask & SCH_BF_HAS_DEFCLS) + opts.defcls = bf->qb_defcls; + + if (bf->qb_mask & SCH_BF_HAS_FLOW_PRIO_LIMIT) + opts.flow_priorities_pow2 = bf->qb_flow_priorities_pow2; + + if (bf->qb_mask & SCH_BF_HAS_NODE_PRIO_LIMIT) + opts.node_priorities_pow2 = bf->qb_node_priorities_pow2; + + if (bf->qb_mask & SCH_BF_HAS_PRIO_CALC_METHOD) + opts.calc = bf->qb_calc_method; + + if (bf->qb_mask & SCH_BF_HAS_TOTAL_BW) + opts.total_bw = bf->qb_total_bw; + } + + return nla_put(msg, TCA_BF_INIT, sizeof(opts), &opts); +} + + +static int bf_class_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg) +{ + unsigned int i; + struct rtnl_bf_class *bf = data; + struct tc_bf_opt opts = {0}; + + if ((!bf) || !(bf->cb_mask & (SCH_BF_HAS_RATES))) + BUG(); + + if (bf->cb_mask & SCH_BF_HAS_FLOW_PRIO) { + opts.flow_priority = bf->cb_flow_prio; + } + + if (bf->cb_mask & SCH_BF_HAS_NODE_PRIO) { + opts.node_priority = bf->cb_node_prio; + } + + for (i = 0; i < __TC_BF_STRATA_COUNT; i++) { + opts.bytes_per_sec_limits[i] = bf->cb_limits[i]; + } + + NLA_PUT(msg, TCA_BF_PARAMS, sizeof(opts), &opts); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static struct rtnl_tc_ops bf_qdisc_ops; +static struct rtnl_tc_ops bf_class_ops; + +static struct rtnl_bf_qdisc *bf_qdisc_data(struct rtnl_qdisc *qdisc) +{ + return rtnl_tc_data_check(TC_CAST(qdisc), &bf_qdisc_ops); +} + +static struct rtnl_bf_class *bf_class_data(struct rtnl_class *class) +{ + return rtnl_tc_data_check(TC_CAST(class), &bf_class_ops); +} + +/* + * Calculate the next highest power of 2. 0 is an illegal value to pass to + * this function. + */ +static unsigned int next_pow2(unsigned int value) +{ + unsigned int retval; + unsigned int bits; + + if (value == 0) + return 0; + + bits = 0; + retval = 0; + do { + retval++; + bits += value & 1; + value >>= 1; + } while (value != 0); + + return (bits > 1) ? retval : retval - 1; +} + + + +/** + * @name Attribute Modifications + * @{ + */ +uint32_t rtnl_bf_get_defcls(struct rtnl_qdisc *qdisc) +{ + struct rtnl_bf_qdisc *bf; + + if ((bf = bf_qdisc_data(qdisc)) && bf->qb_mask & SCH_BF_HAS_DEFCLS) + return bf->qb_defcls; + + return TC_H_UNSPEC; +} + + +int rtnl_bf_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls) +{ + struct rtnl_bf_qdisc *bf; + + if (!(bf = bf_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + bf->qb_defcls = defcls; + bf->qb_mask |= SCH_BF_HAS_DEFCLS; + + return 0; +} + +uint32_t rtnl_bf_get_flow_priorities(struct rtnl_qdisc *qdisc) +{ + struct rtnl_bf_qdisc *bf; + + if ((bf = bf_qdisc_data(qdisc)) && + (bf->qb_mask & SCH_BF_HAS_FLOW_PRIO_LIMIT)) + return (1 << bf->qb_flow_priorities_pow2); + + return TC_H_UNSPEC; +} + +uint32_t rtnl_bf_get_node_priorities(struct rtnl_qdisc *qdisc) +{ + struct rtnl_bf_qdisc *bf; + + if ((bf = bf_qdisc_data(qdisc)) && + (bf->qb_mask & SCH_BF_HAS_NODE_PRIO_LIMIT)) + return (1 << bf->qb_node_priorities_pow2); + + return TC_H_UNSPEC; +} + +int rtnl_bf_set_priorities(struct rtnl_qdisc *qdisc, uint32_t flow_prios, + uint32_t node_prios) +{ + unsigned int flow_pow2, node_pow2; + struct rtnl_bf_qdisc *bf; + + if (!(bf = bf_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + if ((flow_prios == 0) || (node_prios == 0)) + return -NLE_RANGE; + + flow_pow2 = next_pow2(flow_prios); + node_pow2 = next_pow2(node_prios); + + if ((flow_pow2 + node_pow2) > TC_BF_MAX_PRIORITY_POW2) + return -NLE_RANGE; + + bf->qb_node_priorities_pow2 = node_pow2; + bf->qb_flow_priorities_pow2 = flow_pow2; + + bf->qb_mask |= SCH_BF_HAS_NODE_PRIO_LIMIT; + bf->qb_mask |= SCH_BF_HAS_FLOW_PRIO_LIMIT; + + return 0; +} + +enum BF_PRIORITY_CALC rtnl_bf_get_prio_calc_method(struct rtnl_qdisc *qdisc) +{ + struct rtnl_bf_qdisc *bf; + + if ((bf = bf_qdisc_data(qdisc)) && + (bf->qb_mask & SCH_BF_HAS_PRIO_CALC_METHOD)) + return bf->qb_calc_method; + + return TC_H_UNSPEC; +} + +int rtnl_bf_set_prio_calc_method(struct rtnl_qdisc *qdisc, + enum BF_PRIORITY_CALC calc) +{ + struct rtnl_bf_qdisc *bf; + + if (!(bf = bf_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + bf->qb_calc_method = calc; + bf->qb_mask |= SCH_BF_HAS_PRIO_CALC_METHOD; + + return 0; +} + +uint32_t rtnl_bf_get_total_bandwidth(struct rtnl_qdisc *qdisc) +{ + struct rtnl_bf_qdisc *bf; + + if ((bf = bf_qdisc_data(qdisc)) && + (bf->qb_mask & SCH_BF_HAS_TOTAL_BW)) + return bf->qb_total_bw; + + return TC_H_UNSPEC; +} + +int rtnl_bf_set_total_bandwidth(struct rtnl_qdisc *qdisc, uint32_t total_bw) +{ + struct rtnl_bf_qdisc *bf; + + if (!(bf = bf_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + bf->qb_total_bw = total_bw; + bf->qb_mask |= SCH_BF_HAS_TOTAL_BW; + + return 0; +} + + +uint32_t rtnl_bf_get_flow_prio(struct rtnl_class *cls) +{ + struct rtnl_bf_class *bf; + + if ((bf = bf_class_data(cls)) && bf->cb_mask & SCH_BF_HAS_FLOW_PRIO) + return bf->cb_flow_prio; + + return 0; +} + +int rtnl_bf_set_flow_prio(struct rtnl_class *class, uint32_t flow_prio) +{ + struct rtnl_bf_class *bf; + + if (!(bf = bf_class_data(class))) + return -NLE_OPNOTSUPP; + + bf->cb_flow_prio = flow_prio; + bf->cb_mask |= SCH_BF_HAS_FLOW_PRIO; + + return 0; +} + +uint32_t rtnl_bf_get_node_prio(struct rtnl_class *class) +{ + struct rtnl_bf_class *bf; + + if ((bf = bf_class_data(class)) && bf->cb_mask & SCH_BF_HAS_NODE_PRIO) + return bf->cb_node_prio; + + return 0; +} + +int rtnl_bf_set_node_prio(struct rtnl_class *class, uint32_t node_prio) +{ + struct rtnl_bf_class *bf; + + if (!(bf = bf_class_data(class))) + return -NLE_OPNOTSUPP; + + bf->cb_node_prio = node_prio; + bf->cb_mask |= SCH_BF_HAS_NODE_PRIO; + + return 0; +} + + +uint32_t rtnl_bf_get_rate(struct rtnl_class *class, uint32_t strata) +{ + struct rtnl_bf_class *bf; + + if ((bf = bf_class_data(class)) && bf->cb_mask & SCH_BF_HAS_RATES) + return bf->cb_limits[strata]; + + return 0; +} + +int rtnl_bf_set_rates(struct rtnl_class *class, uint32_t *rates) +{ + unsigned int i; + struct rtnl_bf_class *bf; + + if (!(bf = bf_class_data(class))) + return -NLE_OPNOTSUPP; + + if (!rates) + return -ENOENT; + + for (i = 0; i < __TC_BF_STRATA_COUNT; i++) { + bf->cb_limits[i] = rates[i]; + } + bf->cb_mask |= SCH_BF_HAS_RATES; + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops bf_qdisc_ops = { + .to_kind = "bf", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_bf_qdisc), + .to_msg_parser = bf_qdisc_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = bf_qdisc_dump_line, + [NL_DUMP_DETAILS] = NULL, + }, + .to_msg_fill = bf_qdisc_msg_fill, +}; + +static struct rtnl_tc_ops bf_class_ops = { + .to_kind = "bf", + .to_type = RTNL_TC_TYPE_CLASS, + .to_size = sizeof(struct rtnl_bf_class), + .to_msg_parser = bf_class_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = bf_class_dump_line, + [NL_DUMP_DETAILS] = NULL, + [NL_DUMP_STATS] = bf_class_dump_stats, + }, + .to_msg_fill = bf_class_msg_fill, +}; + +static void __init bf_init(void) +{ + rtnl_tc_register(&bf_qdisc_ops); + rtnl_tc_register(&bf_class_ops); +} + +static void __exit bf_exit(void) +{ + rtnl_tc_unregister(&bf_class_ops); + rtnl_tc_unregister(&bf_qdisc_ops); +} + +/** @} */ diff --git a/lib/route/qdisc/codel.c b/lib/route/qdisc/codel.c new file mode 100644 index 0000000..701788b --- /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); +} + +/** @} */ diff --git a/lib/route/qdisc/drr.c b/lib/route/qdisc/drr.c new file mode 100644 index 0000000..c7b2a29 --- /dev/null +++ b/lib/route/qdisc/drr.c @@ -0,0 +1,150 @@ +/* + * lib/route/qdisc/drr.c DRR Qdisc + */ + +/** + * @ingroup qdisc + * @ingroup class + * @defgroup qdisc_drr Deficit Round Robin + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_DRR_HAS_QUANTUM 0x1 +/** @endcond */ + +static struct nla_policy drr_policy[TCA_DRR_MAX + 1] = { + [TCA_DRR_QUANTUM] = { .type = NLA_U32 }, +}; + +static int drr_class_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_drr_class *drr = data; + struct nlattr *tb[TCA_DRR_MAX + 1]; + int err; + + err = tca_parse(tb, TCA_DRR_MAX, tc, drr_policy); + if (err < 0) + return err; + + if (tb[TCA_DRR_QUANTUM]) { + drr->cd_quantum = nla_get_u32(tb[TCA_DRR_QUANTUM]); + drr->cd_mask |= SCH_DRR_HAS_QUANTUM; + } + + return 0; +} + +static void drr_class_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_drr_class *drr = data; + + if (drr && (drr->cd_mask & SCH_DRR_HAS_QUANTUM)) + nl_dump(p, " quantum %u", drr->cd_quantum); +} + +static int drr_class_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_drr_class *drr = data; + + if (drr && (drr->cd_mask & SCH_DRR_HAS_QUANTUM)) + NLA_PUT_U32(msg, TCA_DRR_QUANTUM, drr->cd_quantum); + +nla_put_failure: + return -NLE_MSGSIZE; +} + +/** + * @name Class Attribute Access + * @{ + */ + +/** + * Get quantum for DRR class. + * @arg cls DRR class. + * @return Quantum per DRR round in bytes, or 0 for error. + */ +int rtnl_drr_class_get_quantum(struct rtnl_class *cls) +{ + struct rtnl_drr_class *drr; + + if (!(drr = rtnl_tc_data(TC_CAST(cls)))) + return -NLE_NOMEM; + + if (drr->cd_mask & SCH_DRR_HAS_QUANTUM) + return drr->cd_quantum; + else + return 0; +} + +/** + * Sets the quantum for a DRR class. + * @arg cls DRR class. + * @arg quantum Quantum per DRR round in bytes. + * @return 0 on success, or a negative error code. + */ +int rtnl_drr_qdisc_set_quantum(struct rtnl_class *cls, uint32_t quantum) +{ + struct rtnl_drr_class *drr; + + if (!(drr = rtnl_tc_data(TC_CAST(cls)))) + return -NLE_NOMEM; + + drr->cd_quantum = quantum; + drr->cd_mask |= SCH_DRR_HAS_QUANTUM; + + return 0; +} + + +/** @} */ + +static struct rtnl_tc_ops drr_qdisc_ops = { + .to_kind = "drr", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = 0, + .to_msg_parser = NULL, + .to_dump = { + [NL_DUMP_LINE] = NULL, + [NL_DUMP_DETAILS] = NULL, + }, + .to_msg_fill = NULL, +}; + +static struct rtnl_tc_ops drr_class_ops = { + .to_kind = "drr", + .to_type = RTNL_TC_TYPE_CLASS, + .to_size = sizeof(struct rtnl_drr_class), + .to_msg_parser = drr_class_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = drr_class_dump_line, + [NL_DUMP_DETAILS] = NULL, + }, + .to_msg_fill = drr_class_msg_fill, +}; + +static void __init drr_init(void) +{ + rtnl_tc_register(&drr_qdisc_ops); + rtnl_tc_register(&drr_class_ops); +} + +static void __exit drr_exit(void) +{ + rtnl_tc_unregister(&drr_class_ops); + rtnl_tc_unregister(&drr_qdisc_ops); +} + +/** @} */ diff --git a/lib/route/qdisc/fq_codel.c b/lib/route/qdisc/fq_codel.c new file mode 100644 index 0000000..7303b63 --- /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); +} + +/** @} */ diff --git a/lib/route/qdisc/hfsc.c b/lib/route/qdisc/hfsc.c new file mode 100644 index 0000000..a19e5be --- /dev/null +++ b/lib/route/qdisc/hfsc.c @@ -0,0 +1,404 @@ +/* + * lib/route/qdisc/hfsc.c HFSC Qdisc + */ + +/** + * @ingroup qdisc + * @ingroup class + * @defgroup qdisc_hfsc Hierachical Fair Service Curve (HFSC) + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USEC_PER_SEC 1000000 + +/** @cond SKIP */ +#define SCH_HFSC_HAS_DEFCLS 0x02 + +#define SCH_HFSC_HAS_RT_SC 0x001 +#define SCH_HFSC_HAS_FAIR_SC 0x002 +#define SCH_HFSC_HAS_UPPER_SC 0x004 +/** @endcond */ + +static struct nla_policy hfsc_policy[TCA_HFSC_MAX + 1] = { + [TCA_HFSC_RSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_FSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_USC] = { .minlen = sizeof(struct tc_service_curve) }, +}; + +static int hfsc_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt *opt; + + if (tc->tc_opts->d_size < sizeof(struct tc_hfsc_qopt)) + return -NLE_INVAL; + + opt = (struct tc_hfsc_qopt *) tc->tc_opts->d_data; + hfsc->qsc_defcls = opt->defcls; + hfsc->qsc_mask = SCH_HFSC_HAS_DEFCLS; + + return 0; +} + +static int hfsc_class_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct nlattr *tb[TCA_HFSC_MAX + 1]; + struct rtnl_hfsc_class *hfsc = data; + int err; + + if ((err = tca_parse(tb, TCA_HFSC_MAX, tc, hfsc_policy)) < 0) + return err; + + hfsc->csc_mask = 0; + if (tb[TCA_HFSC_RSC]) { + nla_memcpy(&hfsc->csc_rt_sc, tb[TCA_HFSC_RSC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_RT_SC; + } + + if (tb[TCA_HFSC_FSC]) { + nla_memcpy(&hfsc->csc_fair_sc, tb[TCA_HFSC_FSC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_FAIR_SC; + } + + if (tb[TCA_HFSC_USC]) { + nla_memcpy(&hfsc->csc_upper_sc, tb[TCA_HFSC_USC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_UPPER_SC; + } + + return 0; +} + +static void hfsc_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + + if (!hfsc) + return; + + if (hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS) { + char buf[64]; + nl_dump(p, " default-class %s", + rtnl_tc_handle2str(hfsc->qsc_defcls, buf, sizeof(buf))); + } +} + + +static void hfsc_print_sc(struct nl_dump_params *p, char *name, + struct rtnl_curve *sc) +{ + double val; + char *unit; + + nl_dump(p, "%s ", name); + val = nl_cancel_down_bits(sc->sc_m1, &unit); + nl_dump(p, " m1 %.2f%s/s", val, unit); + + val = nl_cancel_down_us(sc->sc_d, &unit); + nl_dump(p, " d %.2f%s", val, unit); + + val = nl_cancel_down_bits(sc->sc_m2, &unit); + nl_dump(p, " m2 %.2f%s/s", val, unit); +} + + +static void hfsc_class_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_class *hfsc = data; + + if (!hfsc) + return; + + if ((hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) && + (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) && + (memcmp(&hfsc->csc_rt_sc, + &hfsc->csc_fair_sc, + sizeof(hfsc->csc_rt_sc)) == 0)) { + hfsc_print_sc(p, "sc", &hfsc->csc_rt_sc); + } else { + if (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) + hfsc_print_sc(p, "rt", &hfsc->csc_rt_sc); + if (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) + hfsc_print_sc(p, "ls", &hfsc->csc_fair_sc); + } + + if (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC) + hfsc_print_sc(p, "ul", &hfsc->csc_upper_sc); +} + + +static int hfsc_qdisc_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt opts = {0}; + + if (!hfsc || !(hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS)) + return -NLE_INVAL; + + opts.defcls = hfsc->qsc_defcls; + + return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD); +} + +/* FIXME: Verify we have at least one curve, and if upper, must have fair */ +static int hfsc_class_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_class *hfsc = data; + struct tc_service_curve sc; + + if (!hfsc) + BUG(); + + if (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) { + memcpy(&sc, &hfsc->csc_rt_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_RSC, sizeof(sc), &sc); + + if (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) { + memcpy(&sc, &hfsc->csc_fair_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_FSC, sizeof(sc), &sc); + + if (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC) { + memcpy(&sc, &hfsc->csc_upper_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_USC, sizeof(sc), &sc); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static struct rtnl_tc_ops hfsc_qdisc_ops; +static struct rtnl_tc_ops hfsc_class_ops; + +static struct rtnl_hfsc_qdisc *hfsc_qdisc_data(struct rtnl_qdisc *qdisc) +{ + return rtnl_tc_data_check(TC_CAST(qdisc), &hfsc_qdisc_ops); +} + +static struct rtnl_hfsc_class *hfsc_class_data(struct rtnl_class *class) +{ + return rtnl_tc_data_check(TC_CAST(class), &hfsc_class_ops); +} + +/** + * @name Attribute Modifications + * @{ + */ + +/** + * Return default class of HFSC qdisc + * @arg qdisc hfsc qdisc object + * + * Returns the classid of the class where all unclassified traffic + * goes to. + * + * @return classid or TC_H_UNSPEC if unspecified. + */ +uint32_t rtnl_hfsc_get_defcls(struct rtnl_qdisc *qdisc) +{ + struct rtnl_hfsc_qdisc *hfsc; + + if ((hfsc = hfsc_qdisc_data(qdisc)) && + hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS) + return hfsc->qsc_defcls; + + return TC_H_UNSPEC; +} + +/** + * Set default class of the hfsc qdisc to the specified value + * @arg qdisc qdisc to change + * @arg defcls new default class + */ +int rtnl_hfsc_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls) +{ + struct rtnl_hfsc_qdisc *hfsc; + + if (!(hfsc = hfsc_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + hfsc->qsc_defcls = defcls; + hfsc->qsc_mask |= SCH_HFSC_HAS_DEFCLS; + + return 0; +} + +/* m1-d-m2: Direct Method of Specifying service curve slopes */ +void rtnl_hfsc_get_realtime_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC)) { + memcpy(curve_out, &hfsc->csc_rt_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_realtime_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_rt_sc, curve_in, sizeof(hfsc->csc_rt_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_RT_SC; + + return 0; +} + +void rtnl_hfsc_get_fair_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC)) { + memcpy(curve_out, &hfsc->csc_fair_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_fair_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_fair_sc, curve_in, sizeof(hfsc->csc_fair_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_FAIR_SC; + + return 0; +} + +void rtnl_hfsc_get_upperlimit_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC)) { + memcpy(curve_out, &hfsc->csc_upper_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_upperlimit_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_upper_sc, curve_in, sizeof(hfsc->csc_upper_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_UPPER_SC; + + return 0; +} + +/* umax-dmax-rate Method of specifying service curve translations */ +int rtnl_hfsc_spec_to_sc(struct hfsc_spec *spec_in, + struct tc_service_curve *curve_out) +{ + unsigned int umax, dmax, rate; + + umax = spec_in->work_max; + dmax = spec_in->delay_max; + rate = spec_in->rate; + + if (umax != 0 && dmax == 0) { + return -EINVAL; + } + + if (rate == 0) { + curve_out->m1 = 0; + curve_out->d = 0; + curve_out->m2 = 0; + return 0; + } + + /* concave: slope of first segment is umax/dmax, intersect at dmax */ + if ((dmax != 0) && (ceil(1.0 * umax * USEC_PER_SEC / dmax) > rate)) { + curve_out->m1 = ceil(1.0 * umax * USEC_PER_SEC / dmax); + curve_out->d = dmax; + curve_out->m2 = rate; + } else { + /* convex: slope of first segment = 0, intersect (dmax - umax / rate */ + /* FIXME: is that calc correct? Maybe it's (dmax - umax) / rate? */ + curve_out->m1 = 0; + curve_out->d = ceil(dmax - umax * USEC_PER_SEC / rate); + curve_out->m2 = rate; + } + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops hfsc_qdisc_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_hfsc_qdisc), + .to_msg_parser = hfsc_qdisc_msg_parser, + .to_dump[NL_DUMP_LINE] = hfsc_qdisc_dump_line, + .to_msg_fill = hfsc_qdisc_msg_fill, +}; + +static struct rtnl_tc_ops hfsc_class_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_CLASS, + .to_size = sizeof(struct rtnl_hfsc_class), + .to_msg_parser = hfsc_class_msg_parser, + .to_dump[NL_DUMP_LINE] = hfsc_class_dump_line, + .to_msg_fill = hfsc_class_msg_fill, +}; + +static void __init hfsc_init(void) +{ + rtnl_tc_register(&hfsc_qdisc_ops); + rtnl_tc_register(&hfsc_class_ops); +} + +static void __exit hfsc_exit(void) +{ + rtnl_tc_unregister(&hfsc_qdisc_ops); + rtnl_tc_unregister(&hfsc_class_ops); +} diff --git a/lib/route/qdisc/sfb.c b/lib/route/qdisc/sfb.c new file mode 100644 index 0000000..a7cb808 --- /dev/null +++ b/lib/route/qdisc/sfb.c @@ -0,0 +1,513 @@ +/* + * lib/route/qdisc/sfb.c Stochastic Fair Blue Qdisc + * + * Copyright (c) 2012 Ben Menchaca + */ + +/** + * @ingroup qdisc + * @defgroup qdisc_sfb Stochastic Fair Blue (SFB) + * @brief + * Sch_sfb is an implementation of the Stochastic Fair Blue (SFB) queue + * management algorithm for Linux. SFB is described in + * + * Stochastic Fair Blue: A Queue Management Algorithm for Enforcing + * Fairness. Wu-chang Feng, Dilip D. Kandlur, Debanjan Saha and + * Kang G. Shin. Proc. INFOCOM'01. 2001. + * + * SFB aims to keep your queues short while avoiding packet drops, + * maximising link utilisation and preserving fairness between flows. + * SFB will detect flows that do not respond to congestion indications, + * and limit them to a constant share of the link's capacity. + * + * SFB only deals with marking and/or droping packets. Reordering of + * packets is handled by sfb's child qdisc; by default, this is pfifo, + * meaning that no reordering will happen. You may want to use something + * like prio or sfq as sfb's child. + * + * Unlike most other fair queueing policies, SFB doesn't actually keep + * per-flow state; it manages flow information using a Bloom filter, + * which means that hash collisions are reduced to a minimum while using + * fairly little memory. + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_SFB_ATTR_REHASH 0x001 +#define SCH_SFB_ATTR_WARMUP 0x002 +#define SCH_SFB_ATTR_LIMIT 0x004 +#define SCH_SFB_ATTR_TARGET 0x008 +#define SCH_SFB_ATTR_MAX 0x010 +#define SCH_SFB_ATTR_INCREMENT 0x020 +#define SCH_SFB_ATTR_DECREMENT 0x040 +#define SCH_SFB_ATTR_PEN_RATE 0x080 +#define SCH_SFB_ATTR_PEN_BURST 0x100 +/** @endcond */ + +static int sfb_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_sfb *sfb = data; + struct tc_sfb_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_sfb_qopt *) tc->tc_opts->d_data; + + sfb->qsb_rehash_interval = opts->rehash_interval; + sfb->qsb_warmup_time = opts->warmup_time; + sfb->qsb_limit = opts->limit; + sfb->qsb_target = opts->bin_size; + sfb->qsb_max = opts->max; + sfb->qsb_increment = opts->increment; + sfb->qsb_decrement = opts->decrement; + sfb->qsb_penalty_rate = opts->penalty_rate; + sfb->qsb_penalty_burst = opts->penalty_burst; + + sfb->qsb_mask = (SCH_SFB_ATTR_REHASH | SCH_SFB_ATTR_WARMUP | + SCH_SFB_ATTR_LIMIT | SCH_SFB_ATTR_MAX | + SCH_SFB_ATTR_TARGET | + SCH_SFB_ATTR_INCREMENT | SCH_SFB_ATTR_DECREMENT | + SCH_SFB_ATTR_PEN_RATE | SCH_SFB_ATTR_PEN_BURST); + + return 0; +} + +static void sfb_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_sfb *sfb = data; + + if (sfb) + nl_dump(p, " limit %u target %u max %u" , + sfb->qsb_limit, sfb->qsb_target, sfb->qsb_max); +} + +static void sfb_dump_details(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_sfb *sfb = data; + + if (sfb) + nl_dump(p, "rehash_interval %us warmup_time %us " + "increment %u decrement %u " + "penalty_rate %up/s penalty_burst %up", + sfb->qsb_rehash_interval, sfb->qsb_warmup_time, + sfb->qsb_increment, sfb->qsb_decrement, + sfb->qsb_penalty_rate, sfb->qsb_penalty_burst); +} + +static int sfb_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg) +{ + struct rtnl_sfb *sfb = data; + struct tc_sfb_qopt opts = {0}; + + if (!sfb) + BUG(); + + opts.rehash_interval = sfb->qsb_rehash_interval; + opts.warmup_time = sfb->qsb_warmup_time; + opts.limit = sfb->qsb_limit; + opts.bin_size = sfb->qsb_target; + opts.max = sfb->qsb_max; + opts.increment = sfb->qsb_increment; + opts.decrement = sfb->qsb_decrement; + opts.penalty_rate = sfb->qsb_penalty_rate; + opts.penalty_burst = sfb->qsb_penalty_burst; + + return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD); +} + +/** + * @name Attribute Access + * @{ + */ + +/** + * Set the rehash interval of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg interval_secs Specifies how often, in seconds, before we will switch + * to a fresh Bloom filter and a different set of hash functions. Since Bloom + * filters are more resistent to hash collisions that hash tables, this may be + * set to a fairly large value. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_rehash_interval(struct rtnl_qdisc *qdisc, int interval_secs) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_rehash_interval = interval_secs; + sfb->qsb_mask |= SCH_SFB_ATTR_REHASH; +} + +/** + * Get rehash interval of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Rehash interval in seconds, or a negative error code. + */ +int rtnl_sfb_get_rehash_interval(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_REHASH) + return sfb->qsb_rehash_interval; + else + return -NLE_NOATTR; +} + +/** + * Set the warmup time of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg warmup_secs In seconds, how long we will perform double buffering + * before switching to a new bloom filter. This should be long enough to make + * sure that the new filter is ``primed'' before it is used, a few tens of + * seconds should be enough. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_warmup_time(struct rtnl_qdisc *qdisc, int warmup_secs) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_warmup_time = warmup_secs; + sfb->qsb_mask |= SCH_SFB_ATTR_WARMUP; +} + +/** + * Get warmup time of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Warmup time in seconds, or a negative error code. + */ +int rtnl_sfb_get_warmup_time(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_WARMUP) + return sfb->qsb_warmup_time; + else + return -NLE_NOATTR; +} + +/** + * Set the total packet limit of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg interval The hard limit on the number of packets in all of sfb's + * aggregates (total qdisc limit). Set it to some large value, it should never + * be reached anyway. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_limit(struct rtnl_qdisc *qdisc, int interval) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_limit = interval; + sfb->qsb_mask |= SCH_SFB_ATTR_REHASH; +} + +/** + * Get rehash interval of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Limit in packets, or a negative error code. + */ +int rtnl_sfb_get_limit(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_REHASH) + return sfb->qsb_limit; + else + return -NLE_NOATTR; +} + +/** + * Set the target number of packets per aggregate of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg target_packets the target per-flow queue size in packets. sfb will + * try to keep each per-aggregate queue between 0 and target. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_target_packets(struct rtnl_qdisc *qdisc, int target_packets) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_target = target_packets; + sfb->qsb_mask |= SCH_SFB_ATTR_TARGET; +} + +/** + * Get rehash interval of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Target packets per aggregate, or a negative error code. + */ +int rtnl_sfb_get_target_packets(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_TARGET) + return sfb->qsb_target; + else + return -NLE_NOATTR; +} + +/** + * Set the max packets per aggregate of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg max_packets The maximum number of packets queued for a given + * aggregate. It should be very slightly larger than target, in order to + * absorb transient bursts. Setting this to more than roughly 1.5 times target + * will cause instabilities with which Blue is not designed to cope. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_max_packets(struct rtnl_qdisc *qdisc, int max_packets) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_max = max_packets; + sfb->qsb_mask |= SCH_SFB_ATTR_MAX; +} + +/** + * Get max packets per aggregate of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Max packets, or a negative error code. + */ +int rtnl_sfb_get_max_packets(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_MAX) + return sfb->qsb_max; + else + return -NLE_NOATTR; +} + +/** + * Set the underflow increment of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg increment 16-bit fixed point integer that represents the value by + * which per-flow dropping probability is decreased on queue underflow. This + * should be a small fraction of a percent, and increment should be a few times + * smaller than decrement. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_underflow_increment(struct rtnl_qdisc *qdisc, int increment) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_increment = increment; + sfb->qsb_mask |= SCH_SFB_ATTR_INCREMENT; +} + +/** + * Get underflow increment of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Underflow increment in 16-bit fixed point, or a negative error code. + */ +int rtnl_sfb_get_underflow_increment(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_INCREMENT) + return sfb->qsb_increment; + else + return -NLE_NOATTR; +} + +/** + * Set the overflow decrement of SFB qdisc. + * + * @arg qdisc SFB qdisc to be modified. + * @arg decrement 16-bit fixed point integer that represents the value by + * which per-flow dropping probability is increased on queue overflow. This + * should be a small fraction of a percent, and decrement should be a few times + * larger than increment. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_overflow_decrement(struct rtnl_qdisc *qdisc, int decrement) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_decrement = decrement; + sfb->qsb_mask |= SCH_SFB_ATTR_DECREMENT; +} + +/** + * Get overflow decrement of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Overflow decrement in 16-bit fixed point, or a negative error code. + */ +int rtnl_sfb_get_overflow_decrement(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_DECREMENT) + return sfb->qsb_decrement; + else + return -NLE_NOATTR; +} + +/** + * Set the penalty rate of SFB qdisc. + * + * When a flow doesn't back off in response to congestion indication, sfb will + * categorise it as ``non-reactive'' and will rate-limit it. + * + * @arg qdisc SFB qdisc to be modified. + * @arg packets_per_sec Total throughput that non-reactive flows are allowed to + * use in packets per second. You should set penalty_rate to some reasonable + * fraction of your up-link throughput (the default values are probably too + * small). + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_penalty_rate(struct rtnl_qdisc *qdisc, int packets_per_sec) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_penalty_rate = packets_per_sec; + sfb->qsb_mask |= SCH_SFB_ATTR_PEN_RATE; +} + +/** + * Get the penalty rate of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Penalty rate in packets per second, or a negative error code. + */ +int rtnl_sfb_get_penalty_rate(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_PEN_RATE) + return sfb->qsb_penalty_rate; + else + return -NLE_NOATTR; +} + +/** + * Set the penalty burst of SFB qdisc. + * + * When a flow doesn't back off in response to congestion indication, sfb will + * categorise it as ``non-reactive'' and will rate-limit it. + * + * @arg qdisc SFB qdisc to be modified. + * @arg burst_packets Number of packets in the token bucket for penalty rate + * limiting. + * @return 0 on success or a negative error code. + */ +void rtnl_sfb_set_penalty_burst(struct rtnl_qdisc *qdisc, int burst_packets) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + sfb->qsb_penalty_burst = burst_packets; + sfb->qsb_mask |= SCH_SFB_ATTR_PEN_BURST; +} + +/** + * Get the penalty burst of SFB qdisc. + * @arg qdisc SFB qdisc. + * @return Penalty burst size in packets, or a negative error code. + */ +int rtnl_sfb_get_penalty_burst(struct rtnl_qdisc *qdisc) +{ + struct rtnl_sfb *sfb; + + if (!(sfb = rtnl_tc_data(TC_CAST(qdisc)))) + BUG(); + + if (sfb->qsb_mask & SCH_SFB_ATTR_PEN_RATE) + return sfb->qsb_penalty_burst; + else + return -NLE_NOATTR; +} + +/** @} */ + +static struct rtnl_tc_ops sfb_ops = { + .to_kind = "sfb", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_sfb), + .to_msg_parser = sfb_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = sfb_dump_line, + [NL_DUMP_DETAILS] = sfb_dump_details, + }, + .to_msg_fill = sfb_msg_fill, +}; + +static void __init sfb_init(void) +{ + rtnl_tc_register(&sfb_ops); +} + +static void __exit sfb_exit(void) +{ + rtnl_tc_unregister(&sfb_ops); +} + +/** @} */ diff --git a/lib/route/qdisc/tbf.c b/lib/route/qdisc/tbf.c index 8a6c400..7c33382 100644 --- a/lib/route/qdisc/tbf.c +++ b/lib/route/qdisc/tbf.c @@ -54,13 +54,13 @@ static int tbf_msg_parser(struct rtnl_tc *tc, void *data) rtnl_copy_ratespec(&tbf->qt_rate, &opts.rate); tbf->qt_rate_txtime = opts.buffer; - bufsize = rtnl_tc_calc_bufsize(nl_ticks2us(opts.buffer), + bufsize = rtnl_tc_calc_bufsize(tbf->qt_rate_txtime, 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), + bufsize = rtnl_tc_calc_bufsize(tbf->qt_peakrate_txtime, opts.peakrate.rate); tbf->qt_peakrate_bucket = bufsize; @@ -135,7 +135,7 @@ static int tbf_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg) struct rtnl_tbf *tbf = data; int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT; - if (!(tbf->qt_mask & required) != required) + if ((tbf->qt_mask & required) != required) return -NLE_MISSING_ATTR; memset(&opts, 0, sizeof(opts)); diff --git a/lib/route/tc.c b/lib/route/tc.c index 6826a05..0d48c6b 100644 --- a/lib/route/tc.c +++ b/lib/route/tc.c @@ -578,7 +578,7 @@ int rtnl_tc_calc_txtime(int bufsize, int rate) tx_time_secs = (double) bufsize / (double) rate; - return tx_time_secs * 1000000.; + return nl_us2ticks(tx_time_secs * 1000000.); } /** @@ -601,7 +601,7 @@ int rtnl_tc_calc_bufsize(int txtime, int rate) bufsize = (double) txtime * (double) rate; - return bufsize / 1000000.; + return nl_ticks2us(bufsize / 1000000.); } /**