
In order for the interface to become more thread safe, the error handling was revised to no longer depend on a static errno and error string buffer. This patch converts all error paths to return a libnl specific error code which can be translated to a error message using nl_geterror(int error). The functions nl_error() and nl_get_errno() are therefore obsolete. This change required various sets of function prototypes to be changed in order to return an error code, the most prominent are: struct nl_cache *foo_alloc_cache(...); changed to: int foo_alloc_cache(..., struct nl_cache **); struct nl_msg *foo_build_request(...); changed to: int foo_build_request(..., struct nl_msg **); struct foo *foo_parse(...); changed to: int foo_parse(..., struct foo **); This pretty much only leaves trivial allocation functions to still return a pointer object which can still return NULL to signal out of memory. This change is a serious API and ABI breaker, sorry!
539 lines
12 KiB
C
539 lines
12 KiB
C
/*
|
|
* lib/route/sch/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-2008 Thomas Graf <tgraf@suug.ch>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup qdisc_api
|
|
* @defgroup 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.h>
|
|
#include <netlink/route/qdisc.h>
|
|
#include <netlink/route/qdisc-modules.h>
|
|
#include <netlink/route/class.h>
|
|
#include <netlink/route/class-modules.h>
|
|
#include <netlink/route/link.h>
|
|
#include <netlink/route/sch/tbf.h>
|
|
|
|
/** @cond SKIP */
|
|
#define TBF_ATTR_LIMIT 0x01
|
|
#define TBF_ATTR_RATE 0x02
|
|
#define TBF_ATTR_PEAKRATE 0x10
|
|
#define TBF_ATTR_MPU 0x80
|
|
/** @endcond */
|
|
|
|
static inline struct rtnl_tbf *tbf_qdisc(struct rtnl_qdisc *qdisc)
|
|
{
|
|
return (struct rtnl_tbf *) qdisc->q_subdata;
|
|
}
|
|
|
|
static inline struct rtnl_tbf *tbf_alloc(struct rtnl_qdisc *qdisc)
|
|
{
|
|
if (!qdisc->q_subdata)
|
|
qdisc->q_subdata = calloc(1, sizeof(struct rtnl_tbf));
|
|
|
|
return tbf_qdisc(qdisc);
|
|
}
|
|
|
|
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_qdisc *q)
|
|
{
|
|
int err;
|
|
struct nlattr *tb[TCA_TBF_MAX + 1];
|
|
struct rtnl_tbf *tbf;
|
|
|
|
err = tca_parse(tb, TCA_TBF_MAX, (struct rtnl_tca *) q, tbf_policy);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
tbf = tbf_alloc(q);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
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;
|
|
tbf->qt_mpu = opts.rate.mpu;
|
|
|
|
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;
|
|
|
|
tbf->qt_mask = (TBF_ATTR_LIMIT | TBF_ATTR_MPU | TBF_ATTR_RATE |
|
|
TBF_ATTR_PEAKRATE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tbf_dump_brief(struct rtnl_qdisc *qdisc, struct nl_dump_params *p,
|
|
int line)
|
|
{
|
|
double r, rbit, lim;
|
|
char *ru, *rubit, *limu;
|
|
struct rtnl_tbf *tbf = tbf_qdisc(qdisc);
|
|
|
|
if (!tbf)
|
|
goto ignore;
|
|
|
|
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);
|
|
|
|
dp_dump(p, " rate %.2f%s/s (%.0f%s) limit %.2f%s",
|
|
r, ru, rbit, rubit, lim, limu);
|
|
|
|
ignore:
|
|
return line;
|
|
}
|
|
|
|
static int tbf_dump_full(struct rtnl_qdisc *qdisc, struct nl_dump_params *p,
|
|
int line)
|
|
{
|
|
struct rtnl_tbf *tbf = tbf_qdisc(qdisc);
|
|
|
|
if (!tbf)
|
|
goto ignore;
|
|
|
|
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);
|
|
|
|
dp_dump(p, "mpu %u rate-bucket-size %1.f%s "
|
|
"rate-cell-size %.1f%s\n",
|
|
tbf->qt_mpu, 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);
|
|
|
|
dp_dump_line(p, line++, " 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);
|
|
}
|
|
|
|
ignore:
|
|
return line;
|
|
}
|
|
|
|
static struct nl_msg *tbf_get_opts(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct tc_tbf_qopt opts;
|
|
struct rtnl_tbf *tbf;
|
|
struct nl_msg *msg;
|
|
uint32_t rtab[RTNL_TC_RTABLE_SIZE];
|
|
uint32_t ptab[RTNL_TC_RTABLE_SIZE];
|
|
int required = TBF_ATTR_RATE | TBF_ATTR_LIMIT;
|
|
|
|
memset(&opts, 0, sizeof(opts));
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (!tbf)
|
|
return NULL;
|
|
|
|
if (!(tbf->qt_mask & required) != required)
|
|
return NULL;
|
|
|
|
opts.limit = tbf->qt_limit;
|
|
opts.buffer = tbf->qt_rate_txtime;
|
|
tbf->qt_rate.rs_mpu = tbf->qt_mpu;
|
|
rtnl_rcopy_ratespec(&opts.rate, &tbf->qt_rate);
|
|
|
|
rtnl_tc_build_rate_table(rtab, tbf->qt_mpu & 0xff, tbf->qt_mpu >> 8,
|
|
1 << tbf->qt_rate.rs_cell_log,
|
|
tbf->qt_rate.rs_rate);
|
|
|
|
if (tbf->qt_mask & TBF_ATTR_PEAKRATE) {
|
|
opts.mtu = tbf->qt_peakrate_txtime;
|
|
tbf->qt_peakrate.rs_mpu = tbf->qt_mpu;
|
|
rtnl_rcopy_ratespec(&opts.peakrate, &tbf->qt_peakrate);
|
|
|
|
rtnl_tc_build_rate_table(ptab, tbf->qt_mpu & 0xff,
|
|
tbf->qt_mpu >> 8,
|
|
1 << tbf->qt_peakrate.rs_cell_log,
|
|
tbf->qt_peakrate.rs_rate);
|
|
}
|
|
|
|
msg = nlmsg_alloc();
|
|
if (!msg)
|
|
goto nla_put_failure;
|
|
|
|
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 msg;
|
|
|
|
nla_put_failure:
|
|
nlmsg_free(msg);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
int rtnl_qdisc_tbf_set_limit(struct rtnl_qdisc *qdisc, int limit)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
tbf = tbf_alloc(qdisc);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
tbf->qt_limit = limit;
|
|
tbf->qt_mask |= TBF_ATTR_LIMIT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
tbf = tbf_alloc(qdisc);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
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;
|
|
}
|
|
|
|
return rtnl_qdisc_tbf_set_limit(qdisc, (int) limit);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (tbf->qt_mask & TBF_ATTR_LIMIT))
|
|
return tbf->qt_limit;
|
|
else
|
|
return -NLE_NOATTR;
|
|
}
|
|
|
|
/**
|
|
* Set MPU of TBF qdisc.
|
|
* @arg qdisc TBF qdisc to be modified.
|
|
* @arg mpu New MPU in bytes.
|
|
* @return 0 on success or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_set_mpu(struct rtnl_qdisc *qdisc, int mpu)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
tbf = tbf_alloc(qdisc);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
tbf->qt_mpu = mpu;
|
|
tbf->qt_mask |= TBF_ATTR_MPU;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get MPU of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return MPU in bytes or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_mpu(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (tbf->qt_mask & TBF_ATTR_MPU))
|
|
return tbf->qt_mpu;
|
|
else
|
|
return -NLE_NOATTR;
|
|
}
|
|
|
|
static inline int calc_cell_log(int cell, int bucket)
|
|
{
|
|
if (cell > 0)
|
|
cell = rtnl_tc_calc_cell_log(cell);
|
|
else {
|
|
cell = 0;
|
|
|
|
if (!bucket)
|
|
bucket = 2047; /* defaults to cell_log=3 */
|
|
|
|
while ((bucket >> cell) > 255)
|
|
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.
|
|
*/
|
|
int rtnl_qdisc_tbf_set_rate(struct rtnl_qdisc *qdisc, int rate, int bucket,
|
|
int cell)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
int cell_log;
|
|
|
|
tbf = tbf_alloc(qdisc);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
cell_log = calc_cell_log(cell, bucket);
|
|
if (cell_log < 0)
|
|
return cell_log;
|
|
|
|
tbf->qt_rate.rs_rate = rate;
|
|
tbf->qt_rate_bucket = bucket;
|
|
tbf->qt_rate.rs_cell_log = cell_log;
|
|
tbf->qt_rate_txtime = rtnl_tc_calc_txtime(bucket, rate);
|
|
tbf->qt_mask |= TBF_ATTR_RATE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (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;
|
|
|
|
tbf = tbf_alloc(qdisc);
|
|
if (!tbf)
|
|
return -NLE_NOMEM;
|
|
|
|
cell_log = calc_cell_log(cell, bucket);
|
|
if (cell_log < 0)
|
|
return cell_log;
|
|
|
|
tbf->qt_peakrate.rs_rate = rate;
|
|
tbf->qt_peakrate_bucket = bucket;
|
|
tbf->qt_peakrate.rs_cell_log = cell_log;
|
|
tbf->qt_peakrate_txtime = rtnl_tc_calc_txtime(bucket, rate);
|
|
|
|
tbf->qt_mask |= TBF_ATTR_PEAKRATE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get peak rate of TBF qdisc.
|
|
* @arg qdisc TBF qdisc.
|
|
* @return Peak rate in bytes per seconds or a negative error code.
|
|
*/
|
|
int rtnl_qdisc_tbf_get_peakrate(struct rtnl_qdisc *qdisc)
|
|
{
|
|
struct rtnl_tbf *tbf;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (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;
|
|
|
|
tbf = tbf_qdisc(qdisc);
|
|
if (tbf && (tbf->qt_mask & TBF_ATTR_PEAKRATE))
|
|
return (1 << tbf->qt_peakrate.rs_cell_log);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/** @} */
|
|
|
|
static struct rtnl_qdisc_ops tbf_qdisc_ops = {
|
|
.qo_kind = "tbf",
|
|
.qo_msg_parser = tbf_msg_parser,
|
|
.qo_dump[NL_DUMP_BRIEF] = tbf_dump_brief,
|
|
.qo_dump[NL_DUMP_FULL] = tbf_dump_full,
|
|
.qo_get_opts = tbf_get_opts,
|
|
};
|
|
|
|
static void __init tbf_init(void)
|
|
{
|
|
rtnl_qdisc_register(&tbf_qdisc_ops);
|
|
}
|
|
|
|
static void __exit tbf_exit(void)
|
|
{
|
|
rtnl_qdisc_unregister(&tbf_qdisc_ops);
|
|
}
|
|
|
|
/** @} */
|