/* * lib/route/sch/htb.c HTB 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> * Copyright (c) 2005-2006 Petr Gotthard <petr.gotthard@siemens.com> * Copyright (c) 2005-2006 Siemens AG Oesterreich */ /** * @ingroup qdisc_api * @ingroup class_api * @defgroup htb Hierachical Token Bucket (HTB) * @{ */ #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/htb.h> /** @cond SKIP */ #define SCH_HTB_HAS_RATE2QUANTUM 0x01 #define SCH_HTB_HAS_DEFCLS 0x02 #define SCH_HTB_HAS_PRIO 0x001 #define SCH_HTB_HAS_MTU 0x002 #define SCH_HTB_HAS_RATE 0x004 #define SCH_HTB_HAS_CEIL 0x008 #define SCH_HTB_HAS_RBUFFER 0x010 #define SCH_HTB_HAS_CBUFFER 0x020 #define SCH_HTB_HAS_QUANTUM 0x040 #define SCH_HTB_HAS_OVERHEAD 0x080 #define SCH_HTB_HAS_MPU 0x100 /** @endcond */ static inline struct rtnl_htb_qdisc *htb_qdisc(struct rtnl_qdisc *qdisc) { if (qdisc->q_subdata == NULL) qdisc->q_subdata = calloc(1, sizeof(struct rtnl_htb_qdisc)); return (struct rtnl_htb_qdisc *) qdisc->q_subdata; } static struct nla_policy htb_policy[TCA_HTB_MAX+1] = { [TCA_HTB_INIT] = { .minlen = sizeof(struct tc_htb_glob) }, [TCA_HTB_PARMS] = { .minlen = sizeof(struct tc_htb_opt) }, }; static int htb_qdisc_msg_parser(struct rtnl_qdisc *qdisc) { int err; struct nlattr *tb[TCA_HTB_MAX + 1]; struct rtnl_htb_qdisc *d; err = tca_parse(tb, TCA_HTB_MAX, (struct rtnl_tca *) qdisc, htb_policy); if (err < 0) return err; d = htb_qdisc(qdisc); if (tb[TCA_HTB_INIT]) { struct tc_htb_glob opts; nla_memcpy(&opts, tb[TCA_HTB_INIT], sizeof(opts)); d->qh_rate2quantum = opts.rate2quantum; d->qh_defcls = opts.defcls; d->qh_mask = (SCH_HTB_HAS_RATE2QUANTUM | SCH_HTB_HAS_DEFCLS); } return 0; } static void htb_qdisc_free_data(struct rtnl_qdisc *qdisc) { free(qdisc->q_subdata); } static inline struct rtnl_htb_class *htb_class(struct rtnl_class *class) { if (class->c_subdata == NULL) class->c_subdata = calloc(1, sizeof(struct rtnl_htb_class)); return (struct rtnl_htb_class *) class->c_subdata; } static int htb_class_msg_parser(struct rtnl_class *class) { int err; struct nlattr *tb[TCA_HTB_MAX + 1]; struct rtnl_htb_class *d; err = tca_parse(tb, TCA_HTB_MAX, (struct rtnl_tca *) class, htb_policy); if (err < 0) return err; d = htb_class(class); if (tb[TCA_HTB_PARMS]) { struct tc_htb_opt opts; nla_memcpy(&opts, tb[TCA_HTB_PARMS], sizeof(opts)); d->ch_prio = opts.prio; rtnl_copy_ratespec(&d->ch_rate, &opts.rate); rtnl_copy_ratespec(&d->ch_ceil, &opts.ceil); d->ch_rbuffer = rtnl_tc_calc_bufsize(opts.buffer, opts.rate.rate); d->ch_cbuffer = rtnl_tc_calc_bufsize(opts.cbuffer, opts.ceil.rate); d->ch_quantum = opts.quantum; d->ch_overhead = (opts.rate.mpu >> 8) & 0xff; d->ch_mpu = opts.rate.mpu & 0xff; d->ch_mask = (SCH_HTB_HAS_PRIO | SCH_HTB_HAS_RATE | SCH_HTB_HAS_CEIL | SCH_HTB_HAS_RBUFFER | SCH_HTB_HAS_CBUFFER | SCH_HTB_HAS_QUANTUM | SCH_HTB_HAS_OVERHEAD | SCH_HTB_HAS_MPU); } return 0; } static void htb_class_free_data(struct rtnl_class *class) { free(class->c_subdata); } static void htb_qdisc_dump_line(struct rtnl_qdisc *qdisc, struct nl_dump_params *p) { struct rtnl_htb_qdisc *d = (struct rtnl_htb_qdisc *) qdisc->q_subdata; if (d == NULL) return; if (d->qh_mask & SCH_HTB_HAS_RATE2QUANTUM) nl_dump(p, " r2q %u", d->qh_rate2quantum); if (d->qh_mask & SCH_HTB_HAS_DEFCLS) { char buf[32]; nl_dump(p, " default %s", rtnl_tc_handle2str(d->qh_defcls, buf, sizeof(buf))); } } static void htb_class_dump_line(struct rtnl_class *class, struct nl_dump_params *p) { struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata; if (d == NULL) return; if (d->ch_mask & SCH_HTB_HAS_RATE) { double r, rbit; char *ru, *rubit; r = nl_cancel_down_bytes(d->ch_rate.rs_rate, &ru); rbit = nl_cancel_down_bits(d->ch_rate.rs_rate*8, &rubit); nl_dump(p, " rate %.2f%s/s (%.0f%s) log %u", r, ru, rbit, rubit, 1<<d->ch_rate.rs_cell_log); } } static void htb_class_dump_details(struct rtnl_class *class, struct nl_dump_params *p) { struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata; if (d == NULL) return; /* line 1 */ if (d->ch_mask & SCH_HTB_HAS_CEIL) { double r, rbit; char *ru, *rubit; r = nl_cancel_down_bytes(d->ch_ceil.rs_rate, &ru); rbit = nl_cancel_down_bits(d->ch_ceil.rs_rate*8, &rubit); nl_dump(p, " ceil %.2f%s/s (%.0f%s) log %u", r, ru, rbit, rubit, 1<<d->ch_ceil.rs_cell_log); } if (d->ch_mask & SCH_HTB_HAS_PRIO) nl_dump(p, " prio %u", d->ch_prio); if (d->ch_mask & SCH_HTB_HAS_MTU) nl_dump(p, " mtu %u", d->ch_mtu); if (d->ch_mask & SCH_HTB_HAS_RBUFFER) { double b; char *bu; b = nl_cancel_down_bytes(d->ch_rbuffer, &bu); nl_dump(p, " rbuffer %.2f%s", b, bu); } if (d->ch_mask & SCH_HTB_HAS_CBUFFER) { double b; char *bu; b = nl_cancel_down_bytes(d->ch_cbuffer, &bu); nl_dump(p, " cbuffer %.2f%s", b, bu); } if (d->ch_mask & SCH_HTB_HAS_QUANTUM) nl_dump(p, " quantum %u", d->ch_quantum); if (d->ch_mask & SCH_HTB_HAS_OVERHEAD) nl_dump(p, " overhead %u", d->ch_overhead); if (d->ch_mask & SCH_HTB_HAS_MPU) nl_dump(p, " mpu %u", d->ch_mpu); } static struct nl_msg *htb_qdisc_get_opts(struct rtnl_qdisc *qdisc) { struct rtnl_htb_qdisc *d = (struct rtnl_htb_qdisc *) qdisc->q_subdata; struct tc_htb_glob opts; struct nl_msg *msg; if (d == NULL) return NULL; msg = nlmsg_alloc(); if (msg == NULL) return NULL; memset(&opts, 0, sizeof(opts)); opts.version = TC_HTB_PROTOVER; if (d->qh_mask & SCH_HTB_HAS_RATE2QUANTUM) opts.rate2quantum = d->qh_rate2quantum; if (d->qh_mask & SCH_HTB_HAS_DEFCLS) opts.defcls = d->qh_defcls; nla_put(msg, TCA_HTB_INIT, sizeof(opts), &opts); return msg; } static uint8_t compute_cell(uint32_t rate, uint32_t mtu) { uint8_t cell_log = 0; while (mtu > 255) { mtu >>= 1; cell_log++; } return cell_log; } static struct nl_msg *htb_class_get_opts(struct rtnl_class *class) { struct rtnl_htb_class *d = (struct rtnl_htb_class *) class->c_subdata; uint32_t mtu, rtable[RTNL_TC_RTABLE_SIZE], ctable[RTNL_TC_RTABLE_SIZE]; struct tc_htb_opt opts; struct nl_msg *msg; int buffer, cbuffer; uint8_t overhead = 0, mpu = 0; if (d == NULL) return NULL; msg = nlmsg_alloc(); memset(&opts, 0, sizeof(opts)); /* if not set, zero (0) is used as priority */ if (d->ch_mask & SCH_HTB_HAS_PRIO) opts.prio = d->ch_prio; if (d->ch_mask & SCH_HTB_HAS_MTU) mtu = d->ch_mtu; else mtu = 1600; /* eth packet len */ if (!(d->ch_mask & SCH_HTB_HAS_RATE)) BUG(); rtnl_rcopy_ratespec(&opts.rate, &d->ch_rate); /* if cell_log not set, compute default value */ if (opts.rate.cell_log == UINT8_MAX) opts.rate.cell_log = compute_cell(opts.rate.rate, mtu); /* if not set, configured rate is used as ceil, which implies no borrowing */ if (d->ch_mask & SCH_HTB_HAS_CEIL) rtnl_rcopy_ratespec(&opts.ceil, &d->ch_ceil); else memcpy(&opts.ceil, &opts.rate, sizeof(struct tc_ratespec)); /* if cell_log not set, compute default value */ if (opts.ceil.cell_log == UINT8_MAX) opts.ceil.cell_log = compute_cell(opts.ceil.rate, mtu); if (d->ch_mask & SCH_HTB_HAS_RBUFFER) buffer = d->ch_rbuffer; else buffer = opts.rate.rate / nl_get_hz() + mtu; opts.buffer = rtnl_tc_calc_txtime(buffer, opts.rate.rate); if (d->ch_mask & SCH_HTB_HAS_CBUFFER) cbuffer = d->ch_cbuffer; else cbuffer = opts.ceil.rate / nl_get_hz() + mtu; opts.cbuffer = rtnl_tc_calc_txtime(cbuffer, opts.ceil.rate); if (d->ch_mask & SCH_HTB_HAS_QUANTUM) opts.quantum = d->ch_quantum; if (d->ch_mask & SCH_HTB_HAS_OVERHEAD) overhead = d->ch_overhead; if (d->ch_mask & SCH_HTB_HAS_MPU) mpu = d->ch_mpu; opts.rate.mpu = mpu | (overhead << 8); opts.ceil.mpu = mpu | (overhead << 8); nla_put(msg, TCA_HTB_PARMS, sizeof(opts), &opts); rtnl_tc_build_rate_table(rtable, mpu, overhead, 1 << opts.rate.cell_log, opts.rate.rate); nla_put(msg, TCA_HTB_RTAB, sizeof(rtable), &rtable); rtnl_tc_build_rate_table(ctable, mpu, overhead, 1 << opts.ceil.cell_log, opts.ceil.rate); nla_put(msg, TCA_HTB_CTAB, sizeof(ctable), &ctable); return msg; } /** * @name Attribute Modifications * @{ */ void rtnl_htb_set_rate2quantum(struct rtnl_qdisc *qdisc, uint32_t rate2quantum) { struct rtnl_htb_qdisc *d = htb_qdisc(qdisc); if (d == NULL) return; d->qh_rate2quantum = rate2quantum; d->qh_mask |= SCH_HTB_HAS_RATE2QUANTUM; } /** * Set default class of the htb qdisc to the specified value * @arg qdisc qdisc to change * @arg defcls new default class */ void rtnl_htb_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls) { struct rtnl_htb_qdisc *d = htb_qdisc(qdisc); if (d == NULL) return; d->qh_defcls = defcls; d->qh_mask |= SCH_HTB_HAS_DEFCLS; } void rtnl_htb_set_prio(struct rtnl_class *class, uint32_t prio) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_prio = prio; d->ch_mask |= SCH_HTB_HAS_PRIO; } /** * Set MTU of the data link. * @arg class HTB class to be modified. * @arg mtu New MTU in bytes. * * Sets MTU of the data link controlled by the HTB class. * If not set, the Ethernet MTU (1600) is used. */ void rtnl_htb_set_mtu(struct rtnl_class *class, uint32_t mtu) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_mtu = mtu; d->ch_mask |= SCH_HTB_HAS_MTU; } /** * Set rate of HTB class. * @arg class HTB class to be modified. * @arg rate New rate in bytes per second. */ void rtnl_htb_set_rate(struct rtnl_class *class, uint32_t rate) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_rate.rs_cell_log = UINT8_MAX; /* use default value */ d->ch_rate.rs_rate = rate; d->ch_mask |= SCH_HTB_HAS_RATE; } /** * Set ceil of HTB class. * @arg class HTB class to be modified. * @arg ceil New ceil in bytes per second. */ void rtnl_htb_set_ceil(struct rtnl_class *class, uint32_t ceil) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_ceil.rs_cell_log = UINT8_MAX; /* use default value */ d->ch_ceil.rs_rate = ceil; d->ch_mask |= SCH_HTB_HAS_CEIL; } /** * Set size of the rate bucket of HTB class. * @arg class HTB class to be modified. * @arg rbuffer New size in bytes. */ void rtnl_htb_set_rbuffer(struct rtnl_class *class, uint32_t rbuffer) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_rbuffer = rbuffer; d->ch_mask |= SCH_HTB_HAS_RBUFFER; } /** * Set size of the ceil bucket of HTB class. * @arg class HTB class to be modified. * @arg cbuffer New size in bytes. */ void rtnl_htb_set_cbuffer(struct rtnl_class *class, uint32_t cbuffer) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_cbuffer = cbuffer; d->ch_mask |= SCH_HTB_HAS_CBUFFER; } /** * Set how much bytes to serve from leaf at once of HTB class {use r2q}. * @arg class HTB class to be modified. * @arg quantum New size in bytes. */ void rtnl_htb_set_quantum(struct rtnl_class *class, uint32_t quantum) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_quantum = quantum; d->ch_mask |= SCH_HTB_HAS_QUANTUM; } /** * Set per-packet size overhead used in rate computations of HTB class. * @arg class HTB class to be modified. * @arg overhead Size in bytes. */ void rtnl_htb_set_overhead(struct rtnl_class *class, uint8_t overhead) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_overhead = overhead; d->ch_mask |= SCH_HTB_HAS_OVERHEAD; } /** * Set the minimum packet size used in rate computations of HTB class. * @arg class HTB class to be modified. * @arg mpu Size in bytes. */ void rtnl_htb_set_mpu(struct rtnl_class *class, uint8_t mpu) { struct rtnl_htb_class *d = htb_class(class); if (d == NULL) return; d->ch_mpu = mpu; d->ch_mask |= SCH_HTB_HAS_MPU; } /** @} */ static struct rtnl_qdisc_ops htb_qdisc_ops = { .qo_kind = "htb", .qo_msg_parser = htb_qdisc_msg_parser, .qo_free_data = htb_qdisc_free_data, .qo_dump[NL_DUMP_LINE] = htb_qdisc_dump_line, .qo_get_opts = htb_qdisc_get_opts, }; static struct rtnl_class_ops htb_class_ops = { .co_kind = "htb", .co_msg_parser = htb_class_msg_parser, .co_free_data = htb_class_free_data, .co_dump = { [NL_DUMP_LINE] = htb_class_dump_line, [NL_DUMP_DETAILS] = htb_class_dump_details, }, .co_get_opts = htb_class_get_opts, }; static void __init htb_init(void) { rtnl_qdisc_register(&htb_qdisc_ops); rtnl_class_register(&htb_class_ops); } static void __exit htb_exit(void) { rtnl_qdisc_unregister(&htb_qdisc_ops); rtnl_class_unregister(&htb_class_ops); } /** @} */