From cacc24ea66dd268ee6470cc794481d2ccf65041c Mon Sep 17 00:00:00 2001 From: Cong Wang Date: Mon, 28 Jul 2014 15:21:01 -0700 Subject: qdisc: add hfsc qdisc support Cc: Thomas Graf Cc: Thomas Haller Signed-off-by: Cong Wang Acked-by: Thomas Graf Signed-off-by: Thomas Haller --- include/Makefile.am | 1 + include/netlink-private/types.h | 14 ++ include/netlink/route/qdisc/hfsc.h | 37 ++++ lib/Makefile.am | 4 +- lib/cli/qdisc/hfsc.c | 250 ++++++++++++++++++++++++++ lib/route/qdisc/hfsc.c | 351 +++++++++++++++++++++++++++++++++++++ 6 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 include/netlink/route/qdisc/hfsc.h create mode 100644 lib/cli/qdisc/hfsc.c create mode 100644 lib/route/qdisc/hfsc.c diff --git a/include/Makefile.am b/include/Makefile.am index 90f647c..74bbd5a 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -70,6 +70,7 @@ nobase_libnlinclude_HEADERS = \ netlink/route/qdisc/tbf.h \ netlink/route/qdisc/plug.h \ netlink/route/qdisc/fq_codel.h \ + netlink/route/qdisc/hfsc.h \ netlink/route/addr.h \ netlink/route/class.h \ netlink/route/classifier.h \ diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index 10bdaaa..6f3243b 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -721,6 +721,20 @@ struct rtnl_fq_codel uint32_t fq_mask; }; +struct rtnl_hfsc_qdisc +{ + uint32_t qh_defcls; + uint32_t qh_mask; +}; + +struct rtnl_hfsc_class +{ + struct tc_service_curve ch_rsc; + struct tc_service_curve ch_fsc; + struct tc_service_curve ch_usc; + uint32_t ch_mask; +}; + struct flnl_request { NLHDR_COMMON diff --git a/include/netlink/route/qdisc/hfsc.h b/include/netlink/route/qdisc/hfsc.h new file mode 100644 index 0000000..8d34fe5 --- /dev/null +++ b/include/netlink/route/qdisc/hfsc.h @@ -0,0 +1,37 @@ +/* + * netlink/route/sch/hfsc.h HFSC 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) 2014 Cong Wang + */ + +#ifndef NETLINK_HFSC_H_ +#define NETLINK_HFSC_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern uint32_t rtnl_qdisc_hfsc_get_defcls(const struct rtnl_qdisc *); +extern int rtnl_qdisc_hfsc_set_defcls(struct rtnl_qdisc *, uint32_t); + +extern int rtnl_class_hfsc_get_rsc(const struct rtnl_class *class, struct tc_service_curve *tsc); +extern int rtnl_class_hfsc_set_rsc(struct rtnl_class *class, const struct tc_service_curve *tsc); +extern int rtnl_class_hfsc_get_fsc(const struct rtnl_class *class, struct tc_service_curve *tsc); +extern int rtnl_class_hfsc_set_fsc(struct rtnl_class *class, const struct tc_service_curve *tsc); +extern int rtnl_class_hfsc_get_usc(const struct rtnl_class *class, struct tc_service_curve *tsc); +extern int rtnl_class_hfsc_set_usc(struct rtnl_class *class, const struct tc_service_curve *tsc); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Makefile.am b/lib/Makefile.am index ee9c00d..2567323 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -86,7 +86,7 @@ libnl_route_3_la_SOURCES = \ route/qdisc/fifo.c route/qdisc/htb.c route/qdisc/netem.c \ route/qdisc/prio.c route/qdisc/red.c route/qdisc/sfq.c \ route/qdisc/tbf.c route/qdisc/plug.c route/qdisc/ingress.c \ - route/qdisc/fq_codel.c \ + route/qdisc/fq_codel.c route/qdisc/hfsc.c \ \ fib_lookup/lookup.c fib_lookup/request.c \ \ @@ -128,6 +128,7 @@ nobase_pkglib_LTLIBRARIES = \ cli/qdisc/bfifo.la \ cli/qdisc/ingress.la \ cli/qdisc/fq_codel.la \ + cli/qdisc/hfsc.la \ cli/cls/basic.la \ cli/cls/cgroup.la @@ -138,6 +139,7 @@ cli_qdisc_plug_la_LDFLAGS = -module -avoid-version cli_qdisc_bfifo_la_LDFLAGS = -module -avoid-version cli_qdisc_ingress_la_LDFLAGS = -module -avoid-version cli_qdisc_fq_codel_la_LDFLAGS = -module -avoid-version +cli_qdisc_hfsc_la_LDFLAGS = -module -avoid-version cli_cls_basic_la_LDFLAGS = -module -avoid-version cli_cls_cgroup_la_LDFLAGS = -module -avoid-version endif diff --git a/lib/cli/qdisc/hfsc.c b/lib/cli/qdisc/hfsc.c new file mode 100644 index 0000000..1e6878a --- /dev/null +++ b/lib/cli/qdisc/hfsc.c @@ -0,0 +1,250 @@ +/* + * lib/cli/qdisc/hfsc.c HFSC module for CLI lib + * + * 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) 2014 Cong Wang + */ + +#include +#include +#include + +static void print_qdisc_usage(void) +{ + printf( +"Usage: nl-qdisc-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --default=ID Default class for unclassified traffic.\n" +"\n" +"EXAMPLE" +" # Create hfsc root qdisc 1: and direct unclassified traffic to class 1:10\n" +" nl-qdisc-add --dev=eth1 --parent=root --handle=1: hfsc --default=10\n"); +} + +static void hfsc_parse_qdisc_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc; + + for (;;) { + int c, optidx = 0; + enum { + ARG_DEFAULT = 257, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "default", 1, 0, ARG_DEFAULT }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hv", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_qdisc_usage(); + return; + + case ARG_DEFAULT: + rtnl_qdisc_hfsc_set_defcls(qdisc, nl_cli_parse_u32(optarg)); + break; + } + } +} + +static void print_class_usage(void) +{ + printf( +"Usage: nl-class-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --ls=SC Link-sharing service curve\n" +" --rt=SC Real-time service curve\n" +" --sc=SC Specifiy both of the above\n" +" --ul=SC Upper limit\n" +" where SC := [ [ m1 bits ] d usec ] m2 bits\n" +"\n" +"EXAMPLE" +" # Attach class 1:1 to hfsc qdisc 1: and use rt and ls curve\n" +" nl-class-add --dev=eth1 --parent=1: --classid=1:1 hfsc --sc=m1:250,d:8,m2:100\n"); +} + +static int +hfsc_get_sc(char *optarg, struct tc_service_curve *sc) +{ + unsigned int m1 = 0, d = 0, m2 = 0; + char *tmp = strdup(optarg); + char *p = tmp, *endptr; + + if (!tmp) + return -ENOMEM; + + p = strstr(p, "m1:"); + if (p) { + char *q; + p += 3; + if (*p == 0) + goto err; + q = strchr(p, ','); + if (!q) + goto err; + *q = 0; + m1 = strtoul(p, &endptr, 10); + if (endptr == p) + goto err; + p = q + 1; + } + + p = strstr(p, "d:"); + if (p) { + char *q; + p += 2; + if (*p == 0) + goto err; + q = strchr(p, ','); + if (!q) + goto err; + *q = 0; + d = strtoul(p, &endptr, 10); + if (endptr == p) + goto err; + p = q + 1; + } + + p = strstr(p, "m2:"); + if (p) { + p += 3; + if (*p == 0) + goto err; + m2 = strtoul(p, &endptr, 10); + if (endptr == p) + goto err; + } else + goto err; + + free(tmp); + sc->m1 = m1; + sc->d = d; + sc->m2 = m2; + return 0; + +err: + free(tmp); + return -EINVAL; +} + +static void hfsc_parse_class_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_class *class = (struct rtnl_class *) tc; + int arg_ok = 0, ret = -EINVAL; + + for (;;) { + int c, optidx = 0; + enum { + ARG_RT = 257, + ARG_LS = 258, + ARG_SC, + ARG_UL, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "rt", 1, 0, ARG_RT }, + { "ls", 1, 0, ARG_LS }, + { "sc", 1, 0, ARG_SC }, + { "ul", 1, 0, ARG_UL }, + { 0, 0, 0, 0 } + }; + struct tc_service_curve tsc; + + c = getopt_long(argc, argv, "h", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_class_usage(); + return; + + case ARG_RT: + ret = hfsc_get_sc(optarg, &tsc); + if (ret < 0) { + nl_cli_fatal(ret, "Unable to parse sc " + "\"%s\": Invalid format.", optarg); + } + + rtnl_class_hfsc_set_rsc(class, &tsc); + arg_ok++; + break; + + case ARG_LS: + ret = hfsc_get_sc(optarg, &tsc); + if (ret < 0) { + nl_cli_fatal(ret, "Unable to parse sc " + "\"%s\": Invalid format.", optarg); + } + + rtnl_class_hfsc_set_fsc(class, &tsc); + arg_ok++; + break; + + case ARG_SC: + ret = hfsc_get_sc(optarg, &tsc); + if (ret < 0) { + nl_cli_fatal(ret, "Unable to parse sc " + "\"%s\": Invalid format.", optarg); + } + + rtnl_class_hfsc_set_rsc(class, &tsc); + rtnl_class_hfsc_set_fsc(class, &tsc); + arg_ok++; + break; + + case ARG_UL: + ret = hfsc_get_sc(optarg, &tsc); + if (ret < 0) { + nl_cli_fatal(ret, "Unable to parse sc " + "\"%s\": Invalid format.", optarg); + } + + rtnl_class_hfsc_set_usc(class, &tsc); + arg_ok++; + break; + } + } + + if (!arg_ok) + nl_cli_fatal(ret, "Invalid arguments"); +} + +static struct nl_cli_tc_module hfsc_qdisc_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_QDISC, + .tm_parse_argv = hfsc_parse_qdisc_argv, +}; + +static struct nl_cli_tc_module hfsc_class_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_CLASS, + .tm_parse_argv = hfsc_parse_class_argv, +}; + +static void __init hfsc_init(void) +{ + nl_cli_tc_register(&hfsc_qdisc_module); + nl_cli_tc_register(&hfsc_class_module); +} + +static void __exit hfsc_exit(void) +{ + nl_cli_tc_unregister(&hfsc_class_module); + nl_cli_tc_unregister(&hfsc_qdisc_module); +} diff --git a/lib/route/qdisc/hfsc.c b/lib/route/qdisc/hfsc.c new file mode 100644 index 0000000..ddd1242 --- /dev/null +++ b/lib/route/qdisc/hfsc.c @@ -0,0 +1,351 @@ +/* + * lib/route/qdisc/hfsc.c HFSC 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) 2014 Cong Wang + */ + +/** + * @ingroup qdisc + * @ingroup class + * @defgroup qdisc_hfsc Hierarchical Fair Service Curve (HFSC) + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @cond SKIP */ +#define SCH_HFSC_CLS_HAS_RSC 0x001 +#define SCH_HFSC_CLS_HAS_FSC 0x002 +#define SCH_HFSC_CLS_HAS_USC 0x004 + +#define SCH_HFSC_QD_HAS_DEFCLS 0x01 +/** @endcond */ + +static struct nla_policy hfsc_policy[TCA_HFSC_MAX + 1] = { + [TCA_HFSC_RSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_FSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_USC] = { .minlen = sizeof(struct tc_service_curve) }, +}; + +static int hfsc_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt *opts; + + opts = (struct tc_hfsc_qopt *) tc->tc_opts->d_data; + hfsc->qh_defcls = opts->defcls; + hfsc->qh_mask |= SCH_HFSC_QD_HAS_DEFCLS; + return 0; +} + +static int hfsc_class_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct nlattr *tb[TCA_HFSC_MAX + 1]; + struct rtnl_hfsc_class *hfsc = data; + int err; + + if ((err = tca_parse(tb, TCA_HFSC_MAX, tc, hfsc_policy)) < 0) + return err; + + if (tb[TCA_HFSC_RSC]) { + struct tc_service_curve tsc; + + nla_memcpy(&tsc, tb[TCA_HFSC_RSC], sizeof(tsc)); + hfsc->ch_rsc = tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_RSC; + } + + if (tb[TCA_HFSC_FSC]) { + struct tc_service_curve tsc; + + nla_memcpy(&tsc, tb[TCA_HFSC_FSC], sizeof(tsc)); + hfsc->ch_fsc = tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_FSC; + } + + if (tb[TCA_HFSC_USC]) { + struct tc_service_curve tsc; + + nla_memcpy(&tsc, tb[TCA_HFSC_USC], sizeof(tsc)); + hfsc->ch_usc = tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_USC; + } + + return 0; +} + +static void hfsc_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + + if (!hfsc) + return; + + if (hfsc->qh_mask & SCH_HFSC_QD_HAS_DEFCLS) { + char buf[64]; + nl_dump(p, " default-class %s", + rtnl_tc_handle2str(hfsc->qh_defcls, buf, sizeof(buf))); + } +} + +static void hfsc_dump_tsc(struct nl_dump_params *p, struct tc_service_curve *tsc) +{ + nl_dump(p, " m1 %u d %u m2 %u\n", tsc->m1, tsc->d, tsc->m2); +} + +static void hfsc_class_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_class *hfsc = data; + + if (!hfsc) + return; + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_RSC) + hfsc_dump_tsc(p, &hfsc->ch_rsc); + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_FSC) + hfsc_dump_tsc(p, &hfsc->ch_fsc); + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_USC) + hfsc_dump_tsc(p, &hfsc->ch_usc); +} + +static void hfsc_class_dump_details(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + return; +} + +static int hfsc_qdisc_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt opts = {0}; + + if (!hfsc) + BUG(); + + opts.defcls = hfsc->qh_defcls; + return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD); +} + +static int hfsc_class_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_class *hfsc = data; + struct tc_service_curve tsc; + + if (!hfsc) + BUG(); + + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_RSC) { + tsc = hfsc->ch_rsc; + NLA_PUT(msg, TCA_HFSC_RSC, sizeof(tsc), &tsc); + } + + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_FSC) { + tsc = hfsc->ch_fsc; + NLA_PUT(msg, TCA_HFSC_FSC, sizeof(tsc), &tsc); + } + + if (hfsc->ch_mask & SCH_HFSC_CLS_HAS_USC) { + tsc = hfsc->ch_usc; + NLA_PUT(msg, TCA_HFSC_USC, sizeof(tsc), &tsc); + } + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static struct rtnl_tc_ops hfsc_qdisc_ops; +static struct rtnl_tc_ops hfsc_class_ops; + +static struct rtnl_hfsc_qdisc *hfsc_qdisc_data(const struct rtnl_qdisc *qdisc, int *err) +{ + return rtnl_tc_data_check(TC_CAST(qdisc), &hfsc_qdisc_ops, err); +} + +static struct rtnl_hfsc_class *hfsc_class_data(const struct rtnl_class *class, int *err) +{ + return rtnl_tc_data_check(TC_CAST(class), &hfsc_class_ops, err); +} + +/** + * @name Attribute Modifications + * @{ + */ + +/** + * Return default class of HFSC qdisc + * @arg qdisc hfsc qdisc object + * + * Returns the classid of the class where all unclassified traffic + * goes to. + * + * @return classid or TC_H_UNSPEC if unspecified. + */ +uint32_t rtnl_qdisc_hfsc_get_defcls(const struct rtnl_qdisc *qdisc) +{ + struct rtnl_hfsc_qdisc *hfsc; + + if ((hfsc = hfsc_qdisc_data(qdisc, NULL)) && + (hfsc->qh_mask & SCH_HFSC_QD_HAS_DEFCLS)) + return hfsc->qh_defcls; + + return TC_H_UNSPEC; +} + +/** + * Set default class of the hfsc qdisc to the specified value + * @arg qdisc qdisc to change + * @arg defcls new default class + */ +int rtnl_qdisc_hfsc_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls) +{ + struct rtnl_hfsc_qdisc *hfsc; + int err; + + if (!(hfsc = hfsc_qdisc_data(qdisc, &err))) + return err; + + hfsc->qh_defcls = defcls; + hfsc->qh_mask |= SCH_HFSC_QD_HAS_DEFCLS; + + return 0; +} + +int rtnl_class_hfsc_get_rsc(const struct rtnl_class *class, struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err = -NLE_OPNOTSUPP; + + if ((hfsc = hfsc_class_data(class, &err)) && + (hfsc->ch_mask & SCH_HFSC_CLS_HAS_RSC)) { + *tsc = hfsc->ch_rsc; + return 0; + } + + return err; +} + +int rtnl_class_hfsc_set_rsc(struct rtnl_class *class, const struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err; + + if (!(hfsc = hfsc_class_data(class, &err))) + return err; + + hfsc->ch_rsc = *tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_RSC; + + return 0; +} + +int rtnl_class_hfsc_get_fsc(const struct rtnl_class *class, struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err = -NLE_OPNOTSUPP; + + if ((hfsc = hfsc_class_data(class, &err)) && + (hfsc->ch_mask & SCH_HFSC_CLS_HAS_FSC)) { + *tsc = hfsc->ch_fsc; + return 0; + } + + return err; +} + +int rtnl_class_hfsc_set_fsc(struct rtnl_class *class, const struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err; + + if (!(hfsc = hfsc_class_data(class, &err))) + return err; + + hfsc->ch_fsc = *tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_FSC; + + return 0; +} + +int rtnl_class_hfsc_get_usc(const struct rtnl_class *class, struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err = -NLE_OPNOTSUPP; + + if ((hfsc = hfsc_class_data(class, &err)) && + (hfsc->ch_mask & SCH_HFSC_CLS_HAS_USC)) { + *tsc = hfsc->ch_usc; + return 0; + } + + return err; +} + +int rtnl_class_hfsc_set_usc(struct rtnl_class *class, const struct tc_service_curve *tsc) +{ + struct rtnl_hfsc_class *hfsc; + int err; + + if (!(hfsc = hfsc_class_data(class, &err))) + return err; + + hfsc->ch_usc = *tsc; + hfsc->ch_mask |= SCH_HFSC_CLS_HAS_USC; + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops hfsc_qdisc_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_hfsc_qdisc), + .to_msg_parser = hfsc_qdisc_msg_parser, + .to_dump[NL_DUMP_LINE] = hfsc_qdisc_dump_line, + .to_msg_fill = hfsc_qdisc_msg_fill, +}; + +static struct rtnl_tc_ops hfsc_class_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_CLASS, + .to_size = sizeof(struct rtnl_hfsc_class), + .to_msg_parser = hfsc_class_msg_parser, + .to_dump = { + [NL_DUMP_LINE] = hfsc_class_dump_line, + [NL_DUMP_DETAILS] = hfsc_class_dump_details, + }, + .to_msg_fill = hfsc_class_msg_fill, +}; + +static void __init hfsc_init(void) +{ + rtnl_tc_register(&hfsc_qdisc_ops); + rtnl_tc_register(&hfsc_class_ops); +} + +static void __exit hfsc_exit(void) +{ + rtnl_tc_unregister(&hfsc_qdisc_ops); + rtnl_tc_unregister(&hfsc_class_ops); +} + +/** @} */ -- cgit v0.12