/* * 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 */ /** * @ingroup qdisc_api * @defgroup tbf Token Bucket Filter (TBF) * @{ */ #include #include #include #include #include #include #include #include #include #include #include #include /** @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 void tbf_free_data(struct rtnl_qdisc *qdisc) { free(qdisc->q_subdata); } static void tbf_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p) { double r, rbit, lim; char *ru, *rubit, *limu; struct rtnl_tbf *tbf = tbf_qdisc(qdisc); 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_qdisc *qdisc, struct nl_dump_params *p) { struct rtnl_tbf *tbf = tbf_qdisc(qdisc); 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, "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); 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 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_LINE] = tbf_dump_line, [NL_DUMP_DETAILS] = tbf_dump_details, }, .qo_free_data = tbf_free_data, .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); } /** @} */