This commit was generated by cvs2svn to compensate for changes in r786,
[libnl.git] / lib / route / sch / tbf.c
diff --git a/lib/route/sch/tbf.c b/lib/route/sch/tbf.c
new file mode 100644 (file)
index 0000000..f3d50a5
--- /dev/null
@@ -0,0 +1,540 @@
+/*
+ * 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-2006 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup qdisc
+ * @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_qdisc(q);
+       if (!tbf)
+               return nl_errno(ENOMEM);
+
+       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_build_no_hdr();
+       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 nl_errno(ENOMEM);
+
+       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 nl_errno(ENOMEM);
+
+       if (!(tbf->qt_mask & TBF_ATTR_RATE))
+               return nl_error(EINVAL, "The rate must be specified before "
+                               "limit can be calculated based on latency.");
+
+       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;
+       return
+               nl_errno(ENOENT);
+}
+
+/**
+ * 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 nl_errno(ENOMEM);
+
+       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;
+       return
+               nl_errno(ENOENT);
+}
+
+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 nl_errno(ENOMEM);
+
+       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 nl_errno(ENOMEM);
+
+       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);
+}
+
+/** @} */