
The HTB implementation in libnl uses units of microseconds in a number of places where it seems TC is expecting time in units of ticks, which causes actual rates much higher than requested. Additionally, libnl uses USER_HZ for calculating buffer and cbuffer sizes, which can result in much larger buffers than necessary on systems with high resolution timers. Note that the TBF qdisc uses microseconds incorrectly in two spots as well, I fixed this but did not test.
460 lines
10 KiB
C
460 lines
10 KiB
C
/*
|
|
* lib/route/qdisc/tbf.c TBF Qdisc
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* Copyright (c) 2003-2011 Thomas Graf <tgraf@suug.ch>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup qdisc
|
|
* @defgroup qdisc_tbf Token Bucket Filter (TBF)
|
|
* @{
|
|
*/
|
|
|
|
#include <netlink-local.h>
|
|
#include <netlink-tc.h>
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/cache.h>
|
|
#include <netlink/utils.h>
|
|
#include <netlink/route/tc-api.h>
|
|
#include <netlink/route/qdisc.h>
|
|
#include <netlink/route/class.h>
|
|
#include <netlink/route/link.h>
|
|
#include <netlink/route/qdisc/tbf.h>
|
|
|
|
/** @cond SKIP */
|
|
#define TBF_ATTR_LIMIT 0x01
|
|
#define TBF_ATTR_RATE 0x02
|
|
#define TBF_ATTR_PEAKRATE 0x10
|
|
/** @endcond */
|
|
|
|
static struct nla_policy tbf_policy[TCA_TBF_MAX+1] = {
|
|
[TCA_TBF_PARMS] = { .minlen = sizeof(struct tc_tbf_qopt) },
|
|
};
|
|
|
|
static int tbf_msg_parser(struct rtnl_tc *tc, void *data)
|
|
{
|
|
struct nlattr *tb[TCA_TBF_MAX + 1];
|
|
struct rtnl_tbf *tbf = data;
|
|
int err;
|
|
|
|
if ((err = tca_parse(tb, TCA_TBF_MAX, tc, tbf_policy)) < 0)
|
|
return err;
|
|
|
|
if (tb[TCA_TBF_PARMS]) {
|
|
struct tc_tbf_qopt opts;
|
|
int bufsize;
|
|
|
|
nla_memcpy(&opts, tb[TCA_TBF_PARMS], sizeof(opts));
|
|
tbf->qt_limit = opts.limit;
|
|
|
|
rtnl_copy_ratespec(&tbf->qt_rate, &opts.rate);
|
|
tbf->qt_rate_txtime = opts.buffer;
|
|
bufsize = rtnl_tc_calc_bufsize(nl_ticks2us(opts.buffer),
|
|
opts.rate.rate);
|
|
tbf->qt_rate_bucket = bufsize;
|
|
|
|
rtnl_copy_ratespec(&tbf->qt_peakrate, &opts.peakrate);
|
|
tbf->qt_peakrate_txtime = opts.mtu;
|
|
bufsize = rtnl_tc_calc_bufsize(nl_ticks2us(opts.mtu),
|
|
opts.peakrate.rate);
|
|
tbf->qt_peakrate_bucket = bufsize;
|
|
|
|
rtnl_tc_set_mpu(tc, tbf->qt_rate.rs_mpu);
|
|
rtnl_tc_set_overhead(tc, tbf->qt_rate.rs_overhead);
|
|
|
|
tbf->qt_mask = (TBF_ATTR_LIMIT | TBF_ATTR_RATE | TBF_ATTR_PEAKRATE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tbf_dump_line(struct rtnl_tc *tc, void *data,
|
|
struct nl_dump_params *p)
|
|
{
|
|
double r, rbit, lim;
|
|
char *ru, *rubit, *limu;
|
|
struct rtnl_tbf *tbf = data;
|
|
|
|
if (!tbf)
|
|
return;
|
|
|
|
r = nl_cancel_down_bytes(tbf->qt_rate.rs_rate, &ru);
|
|
rbit = nl_cancel_down_bits(tbf->qt_rate.rs_rate*8, &rubit);
|
|
lim = nl_cancel_down_bytes(tbf->qt_limit, &limu);
|
|
|
|
nl_dump(p, " rate %.2f%s/s (%.0f%s) limit %.2f%s",
|
|
r, ru, rbit, rubit, lim, limu);
|
|
}
|
|
|
|
static void tbf_dump_details(struct rtnl_tc *tc, void *data,
|
|
struct nl_dump_params *p)
|
|
{
|
|
struct rtnl_tbf *tbf = data;
|
|
|
|
if (!tbf)
|
|
return;
|
|
|
|
if (1) {
|
|
char *bu, *cu;
|
|
double bs = nl_cancel_down_bytes(tbf->qt_rate_bucket, &bu);
|
|
double cl = nl_cancel_down_bytes(1 << tbf->qt_rate.rs_cell_log,
|
|
&cu);
|
|
|
|
nl_dump(p, "rate-bucket-size %1.f%s "
|
|
"rate-cell-size %.1f%s\n",
|
|
bs, bu, cl, cu);
|
|
|
|
}
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
|
|
char *pru, *prbu, *bsu, *clu;
|
|
double pr, prb, bs, cl;
|
|
|
|
pr = nl_cancel_down_bytes(tbf->qt_peakrate.rs_rate, &pru);
|
|
prb = nl_cancel_down_bits(tbf->qt_peakrate.rs_rate * 8, &prbu);
|
|
bs = nl_cancel_down_bits(tbf->qt_peakrate_bucket, &bsu);
|
|
cl = nl_cancel_down_bits(1 << tbf->qt_peakrate.rs_cell_log,
|
|
&clu);
|
|
|
|
nl_dump_line(p, " peak-rate %.2f%s/s (%.0f%s) "
|
|
"bucket-size %.1f%s cell-size %.1f%s"
|
|
"latency %.1f%s",
|
|
pr, pru, prb, prbu, bs, bsu, cl, clu);
|
|
}
|
|
}
|
|
|
|
static int tbf_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
|
|
{
|
|
uint32_t rtab[RTNL_TC_RTABLE_SIZE], ptab[RTNL_TC_RTABLE_SIZE];
|
|
struct tc_tbf_qopt opts;
|
|
struct rtnl_tbf *tbf = data;
|
|
int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
|
|
|
|
if (!(tbf->qt_mask & required) != required)
|
|
return -NLE_MISSING_ATTR;
|
|
|
|
memset(&opts, 0, sizeof(opts));
|
|
opts.limit = tbf->qt_limit;
|
|
opts.buffer = tbf->qt_rate_txtime;
|
|
|
|
rtnl_tc_build_rate_table(tc, &tbf->qt_rate, rtab);
|
|
rtnl_rcopy_ratespec(&opts.rate, &tbf->qt_rate);
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
|
|
opts.mtu = tbf->qt_peakrate_txtime;
|
|
rtnl_tc_build_rate_table(tc, &tbf->qt_peakrate, ptab);
|
|
rtnl_rcopy_ratespec(&opts.peakrate, &tbf->qt_peakrate);
|
|
|
|
}
|
|
|
|
NLA_PUT(msg, TCA_TBF_PARMS, sizeof(opts), &opts);
|
|
NLA_PUT(msg, TCA_TBF_RTAB, sizeof(rtab), rtab);
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
|
|
NLA_PUT(msg, TCA_TBF_PTAB, sizeof(ptab), ptab);
|
|
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -NLE_MSGSIZE;
|
|
}
|
|
|
|
/**
|
|
* @name Attribute Access
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* Set limit of TBF qdisc.
|
|
* @arg qdisc TBF qdisc to be modified.
|
|
* @arg limit New limit in bytes.
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
void rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *qdisc, int limit)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
tbf->qt_limit = limit;
|
|
tbf->qt_mask |= TBF_ATTR_LIMIT;
|
|
}
|
|
|
|
static inline double calc_limit(struct rtnl_ratespec *spec, int latency,
|
|
int bucket)
|
|
{
|
|
double limit;
|
|
|
|
limit = (double) spec->rs_rate * ((double) latency / 1000000.);
|
|
limit += bucket;
|
|
|
|
return limit;
|
|
}
|
|
|
|
/**
|
|
* Set limit of TBF qdisc by latency.
|
|
* @arg qdisc TBF qdisc to be modified.
|
|
* @arg latency Latency in micro seconds.
|
|
*
|
|
* Calculates and sets the limit based on the desired latency and the
|
|
* configured rate and peak rate. In order for this operation to succeed,
|
|
* the rate and if required the peak rate must have been set in advance.
|
|
*
|
|
* @f[
|
|
* limit_n = \frac{{rate_n} \times {latency}}{10^6}+{bucketsize}_n
|
|
* @f]
|
|
* @f[
|
|
* limit = min(limit_{rate},limit_{peak})
|
|
* @f]
|
|
*
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_set_limit_by_latency(struct rtnl_qdisc *qdisc, int latency)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
double limit, limit2;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (!(tbf->qt_mask & TBF_ATTR_RATE))
|
|
return -NLE_MISSING_ATTR;
|
|
|
|
limit = calc_limit(&tbf->qt_rate, latency, tbf->qt_rate_bucket);
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
|
|
limit2 = calc_limit(&tbf->qt_peakrate, latency,
|
|
tbf->qt_peakrate_bucket);
|
|
|
|
if (limit2 < limit)
|
|
limit = limit2;
|
|
}
|
|
|
|
rtnl_qdisc_tbf_set_limit(qdisc, (int) limit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get limit of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Limit in bytes or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_limit(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_LIMIT)
|
|
return tbf->qt_limit;
|
|
else
|
|
return -NLE_NOATTR;
|
|
}
|
|
|
|
static inline int calc_cell_log(int cell, int bucket)
|
|
{
|
|
cell = rtnl_tc_calc_cell_log(cell);
|
|
return cell;
|
|
}
|
|
|
|
/**
|
|
* Set rate of TBF qdisc.
|
|
* @arg qdisc TBF qdisc to be modified.
|
|
* @arg rate New rate in bytes per second.
|
|
* @arg bucket Size of bucket in bytes.
|
|
* @arg cell Size of a rate cell or 0 to get default value.
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
void rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *qdisc, int rate, int bucket,
|
|
int cell)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
int cell_log;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (!cell)
|
|
cell_log = UINT8_MAX;
|
|
else
|
|
cell_log = rtnl_tc_calc_cell_log(cell);
|
|
|
|
tbf->qt_rate.rs_rate = rate;
|
|
tbf->qt_rate_bucket = bucket;
|
|
tbf->qt_rate.rs_cell_log = cell_log;
|
|
tbf->qt_rate_txtime = nl_us2ticks(rtnl_tc_calc_txtime(bucket, rate));
|
|
tbf->qt_mask |= TBF_ATTR_RATE;
|
|
}
|
|
|
|
/**
|
|
* Get rate of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Rate in bytes per seconds or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_rate(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_RATE)
|
|
return tbf->qt_rate.rs_rate;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get rate bucket size of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Size of rate bucket or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_rate_bucket(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_RATE)
|
|
return tbf->qt_rate_bucket;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get rate cell size of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Size of rate cell in bytes or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_rate_cell(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_RATE)
|
|
return (1 << tbf->qt_rate.rs_cell_log);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Set peak rate of TBF qdisc.
|
|
* @arg qdisc TBF qdisc to be modified.
|
|
* @arg rate New peak rate in bytes per second.
|
|
* @arg bucket Size of peakrate bucket.
|
|
* @arg cell Size of a peakrate cell or 0 to get default value.
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_set_peakrate(struct rtnl_qdisc *qdisc, int rate, int bucket,
|
|
int cell)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
int cell_log;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
cell_log = calc_cell_log(cell, bucket);
|
|
if (cell_log < 0)
|
|
return cell_log;
|
|
|
|
tbf->qt_peakrate.rs_rate = rate;
|
|
tbf->qt_peakrate_bucket = bucket;
|
|
tbf->qt_peakrate.rs_cell_log = cell_log;
|
|
tbf->qt_peakrate_txtime = nl_us2ticks(rtnl_tc_calc_txtime(bucket, rate));
|
|
|
|
tbf->qt_mask |= TBF_ATTR_PEAKRATE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get peak rate of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Peak rate in bytes per seconds or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_peakrate(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
|
|
return tbf->qt_peakrate.rs_rate;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get peak rate bucket size of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Size of peak rate bucket or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_peakrate_bucket(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
|
|
return tbf->qt_peakrate_bucket;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get peak rate cell size of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Size of peak rate cell in bytes or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_peakrate_cell(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
if (!(tbf = rtnl_tc_data(TC_CAST(qdisc))))
|
|
BUG();
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE)
|
|
return (1 << tbf->qt_peakrate.rs_cell_log);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
static struct rtnl_tc_ops tbf_tc_ops = {
|
|
.to_kind = "tbf",
|
|
.to_type = RTNL_TC_TYPE_QDISC,
|
|
.to_size = sizeof(struct rtnl_tbf),
|
|
.to_msg_parser = tbf_msg_parser,
|
|
.to_dump = {
|
|
[NL_DUMP_LINE] = tbf_dump_line,
|
|
[NL_DUMP_DETAILS] = tbf_dump_details,
|
|
},
|
|
.to_msg_fill = tbf_msg_fill,
|
|
};
|
|
|
|
static void __init tbf_init(void)
|
|
{
|
|
rtnl_tc_register(&tbf_tc_ops);
|
|
}
|
|
|
|
static void __exit tbf_exit(void)
|
|
{
|
|
rtnl_tc_unregister(&tbf_tc_ops);
|
|
}
|
|
|
|
/** @} */
|