Logo Search packages:      
Sourcecode: libnl version File versions  Download package

tbf.c

/*
 * lib/route/sch/tbf.c        TBF Qdisc
 *
 *          This program is free software; you can redistribute it and/or
 *          modify it under the terms of the GNU General Public License
 *          as published by the Free Software Foundation; either version
 *          2 of the License, or (at your option) any later version.
 *
 * Copyright (c) 2003-2005 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 */

00038 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.
 */
00223 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.
 */
00266 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.
 */
00297 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.
 */
00314 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.
 */
00333 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.
 */
00369 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.
 */
00397 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.
 */
00413 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.
 */
00429 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.
 */
00448 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.
 */
00477 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.
 */
00493 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.
 */
00509 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);
}

/** @} */

Generated by  Doxygen 1.6.0   Back to index