diff --git a/Makefile b/Makefile index 5b040415..e64c6599 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ WFLAGS += -Wmissing-declarations -Wold-style-definition -Wformat=2 CFLAGS := $(WFLAGS) $(CCOPTS) -I../include -I../include/uapi $(DEFINES) $(CFLAGS) YACCFLAGS = -d -t -v -SUBDIRS=lib ip tc bridge misc netem genl tipc devlink rdma man +SUBDIRS=lib ip tc bridge misc netem genl tipc devlink rdma dcb man LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a LDLIBS += $(LIBNETLINK) diff --git a/bridge/bridge.c b/bridge/bridge.c index 453d6897..8f691cfd 100644 --- a/bridge/bridge.c +++ b/bridge/bridge.c @@ -77,20 +77,14 @@ static int do_cmd(const char *argv0, int argc, char **argv) return -1; } +static int br_batch_cmd(int argc, char *argv[], void *data) +{ + return do_cmd(argv[0], argc, argv); +} + static int batch(const char *name) { - char *line = NULL; - size_t len = 0; - int ret = EXIT_SUCCESS; - - if (name && strcmp(name, "-") != 0) { - if (freopen(name, "r", stdin) == NULL) { - fprintf(stderr, - "Cannot open file \"%s\" for reading: %s\n", - name, strerror(errno)); - return EXIT_FAILURE; - } - } + int ret; if (rtnl_open(&rth, 0) < 0) { fprintf(stderr, "Cannot open rtnetlink\n"); @@ -99,25 +93,7 @@ static int batch(const char *name) rtnl_set_strict_dump(&rth); - cmdlineno = 0; - while (getcmdline(&line, &len, stdin) != -1) { - char *largv[100]; - int largc; - - largc = makeargs(line, largv, 100); - if (largc == 0) - continue; /* blank line */ - - if (do_cmd(largv[0], largc, largv)) { - fprintf(stderr, "Command failed %s:%d\n", - name, cmdlineno); - ret = EXIT_FAILURE; - if (!force) - break; - } - } - if (line) - free(line); + ret = do_batch(name, force, br_batch_cmd, NULL); rtnl_close(&rth); return ret; diff --git a/dcb/Makefile b/dcb/Makefile new file mode 100644 index 00000000..89581716 --- /dev/null +++ b/dcb/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../config.mk + +TARGETS := + +ifeq ($(HAVE_MNL),y) + +DCBOBJ = dcb.o dcb_ets.o +TARGETS += dcb + +endif + +all: $(TARGETS) $(LIBS) + +dcb: $(DCBOBJ) $(LIBNETLINK) + $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +install: all + for i in $(TARGETS); \ + do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \ + done + +clean: + rm -f $(DCBOBJ) $(TARGETS) diff --git a/dcb/dcb.c b/dcb/dcb.c new file mode 100644 index 00000000..dc1e9fe0 --- /dev/null +++ b/dcb/dcb.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include + +#include "dcb.h" +#include "mnl_utils.h" +#include "namespace.h" +#include "utils.h" +#include "version.h" + +static int dcb_init(struct dcb *dcb) +{ + dcb->buf = malloc(MNL_SOCKET_BUFFER_SIZE); + if (dcb->buf == NULL) { + perror("Netlink buffer allocation"); + return -1; + } + + dcb->nl = mnlu_socket_open(NETLINK_ROUTE); + if (dcb->nl == NULL) { + perror("Open netlink socket"); + goto err_socket_open; + } + + new_json_obj_plain(dcb->json_output); + return 0; + +err_socket_open: + free(dcb->buf); + return -1; +} + +static void dcb_fini(struct dcb *dcb) +{ + delete_json_obj_plain(); + mnl_socket_close(dcb->nl); +} + +static struct dcb *dcb_alloc(void) +{ + struct dcb *dcb; + + dcb = calloc(1, sizeof(*dcb)); + if (!dcb) + return NULL; + return dcb; +} + +static void dcb_free(struct dcb *dcb) +{ + free(dcb); +} + +struct dcb_get_attribute { + struct dcb *dcb; + int attr; + void *data; + size_t data_len; +}; + +static int dcb_get_attribute_attr_ieee_cb(const struct nlattr *attr, void *data) +{ + struct dcb_get_attribute *ga = data; + uint16_t len; + + if (mnl_attr_get_type(attr) != ga->attr) + return MNL_CB_OK; + + len = mnl_attr_get_payload_len(attr); + if (len != ga->data_len) { + fprintf(stderr, "Wrong len %d, expected %zd\n", len, ga->data_len); + return MNL_CB_ERROR; + } + + memcpy(ga->data, mnl_attr_get_payload(attr), ga->data_len); + return MNL_CB_STOP; +} + +static int dcb_get_attribute_attr_cb(const struct nlattr *attr, void *data) +{ + if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE) + return MNL_CB_OK; + + return mnl_attr_parse_nested(attr, dcb_get_attribute_attr_ieee_cb, data); +} + +static int dcb_get_attribute_cb(const struct nlmsghdr *nlh, void *data) +{ + return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_get_attribute_attr_cb, data); +} + +static int dcb_set_attribute_attr_cb(const struct nlattr *attr, void *data) +{ + uint16_t len; + uint8_t err; + + if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE) + return MNL_CB_OK; + + len = mnl_attr_get_payload_len(attr); + if (len != 1) { + fprintf(stderr, "Response attribute expected to have size 1, not %d\n", len); + return MNL_CB_ERROR; + } + + err = mnl_attr_get_u8(attr); + if (err) { + fprintf(stderr, "Error when attempting to set attribute: %s\n", + strerror(err)); + return MNL_CB_ERROR; + } + + return MNL_CB_STOP; +} + +static int dcb_set_attribute_cb(const struct nlmsghdr *nlh, void *data) +{ + return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_set_attribute_attr_cb, data); +} + +static int dcb_talk(struct dcb *dcb, struct nlmsghdr *nlh, mnl_cb_t cb, void *data) +{ + int ret; + + ret = mnl_socket_sendto(dcb->nl, nlh, nlh->nlmsg_len); + if (ret < 0) { + perror("mnl_socket_sendto"); + return -1; + } + + return mnlu_socket_recv_run(dcb->nl, nlh->nlmsg_seq, dcb->buf, MNL_SOCKET_BUFFER_SIZE, + cb, data); +} + +static struct nlmsghdr *dcb_prepare(struct dcb *dcb, const char *dev, + uint32_t nlmsg_type, uint8_t dcb_cmd) +{ + struct dcbmsg dcbm = { + .cmd = dcb_cmd, + }; + struct nlmsghdr *nlh; + + nlh = mnlu_msg_prepare(dcb->buf, nlmsg_type, NLM_F_REQUEST, &dcbm, sizeof(dcbm)); + mnl_attr_put_strz(nlh, DCB_ATTR_IFNAME, dev); + return nlh; +} + +int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, void *data, size_t data_len) +{ + struct dcb_get_attribute ga; + struct nlmsghdr *nlh; + int ret; + + nlh = dcb_prepare(dcb, dev, RTM_GETDCB, DCB_CMD_IEEE_GET); + + ga = (struct dcb_get_attribute) { + .dcb = dcb, + .attr = attr, + .data = data, + .data_len = data_len, + }; + ret = dcb_talk(dcb, nlh, dcb_get_attribute_cb, &ga); + if (ret) { + perror("Attribute read"); + return ret; + } + return 0; +} + +int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, const void *data, size_t data_len) +{ + struct nlmsghdr *nlh; + struct nlattr *nest; + int ret; + + nlh = dcb_prepare(dcb, dev, RTM_GETDCB, DCB_CMD_IEEE_SET); + + nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE); + mnl_attr_put(nlh, attr, data_len, data); + mnl_attr_nest_end(nlh, nest); + + ret = dcb_talk(dcb, nlh, dcb_set_attribute_cb, NULL); + if (ret) { + perror("Attribute write"); + return ret; + } + return 0; +} + +void dcb_print_array_u8(const __u8 *array, size_t size) +{ + SPRINT_BUF(b); + size_t i; + + for (i = 0; i < size; i++) { + snprintf(b, sizeof(b), "%zd:%%d ", i); + print_uint(PRINT_ANY, NULL, b, array[i]); + } +} + +void dcb_print_array_kw(const __u8 *array, size_t array_size, + const char *const kw[], size_t kw_size) +{ + SPRINT_BUF(b); + size_t i; + + for (i = 0; i < array_size; i++) { + __u8 emt = array[i]; + + snprintf(b, sizeof(b), "%zd:%%s ", i); + if (emt < kw_size && kw[emt]) + print_string(PRINT_ANY, NULL, b, kw[emt]); + else + print_string(PRINT_ANY, NULL, b, "???"); + } +} + +void dcb_print_named_array(const char *json_name, const char *fp_name, + const __u8 *array, size_t size, + void (*print_array)(const __u8 *, size_t)) +{ + open_json_array(PRINT_JSON, json_name); + print_string(PRINT_FP, NULL, "%s ", fp_name); + print_array(array, size); + close_json_array(PRINT_JSON, json_name); +} + +int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key, + const char *what_value, __u32 value, __u32 max_value, + void (*set_array)(__u32 index, __u32 value, void *data), + void *set_array_data) +{ + bool is_all = key == (__u32) -1; + + if (!is_all && key > max_key) { + fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%d\n", + what_key, what_value, what_key, max_key); + return -EINVAL; + } + + if (value > max_value) { + fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%d\n", + what_key, what_value, what_value, max_value); + return -EINVAL; + } + + if (is_all) { + for (key = 0; key <= max_key; key++) + set_array(key, value, set_array_data); + } else { + set_array(key, value, set_array_data); + } + + return 0; +} + +void dcb_set_u8(__u32 key, __u32 value, void *data) +{ + __u8 *array = data; + + array[key] = value; +} + +int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv, + int (*and_then)(struct dcb *dcb, const char *dev, + int argc, char **argv), + void (*help)(void)) +{ + const char *dev; + + if (!argc || matches(*argv, "help") == 0) { + help(); + return 0; + } else if (matches(*argv, "dev") == 0) { + NEXT_ARG(); + dev = *argv; + if (check_ifname(dev)) { + invarg("not a valid ifname", *argv); + return -EINVAL; + } + NEXT_ARG_FWD(); + return and_then(dcb, dev, argc, argv); + } else { + fprintf(stderr, "Expected `dev DEV', not `%s'", *argv); + help(); + return -EINVAL; + } +} + +static void dcb_help(void) +{ + fprintf(stderr, + "Usage: dcb [ OPTIONS ] OBJECT { COMMAND | help }\n" + " dcb [ -f | --force ] { -b | --batch } filename [ -N | --Netns ] netnsname\n" + "where OBJECT := ets\n" + " OPTIONS := [ -V | --Version | -j | --json | -p | --pretty | -v | --verbose ]\n"); +} + +static int dcb_cmd(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_help(); + return 0; + } else if (matches(*argv, "ets") == 0) { + return dcb_cmd_ets(dcb, argc - 1, argv + 1); + } + + fprintf(stderr, "Object \"%s\" is unknown\n", *argv); + return -ENOENT; +} + +static int dcb_batch_cmd(int argc, char *argv[], void *data) +{ + struct dcb *dcb = data; + + return dcb_cmd(dcb, argc, argv); +} + +static int dcb_batch(struct dcb *dcb, const char *name, bool force) +{ + return do_batch(name, force, dcb_batch_cmd, dcb); +} + +int main(int argc, char **argv) +{ + static const struct option long_options[] = { + { "Version", no_argument, NULL, 'V' }, + { "force", no_argument, NULL, 'f' }, + { "batch", required_argument, NULL, 'b' }, + { "json", no_argument, NULL, 'j' }, + { "pretty", no_argument, NULL, 'p' }, + { "Netns", required_argument, NULL, 'N' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + const char *batch_file = NULL; + bool force = false; + struct dcb *dcb; + int opt; + int err; + int ret; + + dcb = dcb_alloc(); + if (!dcb) { + fprintf(stderr, "Failed to allocate memory for dcb\n"); + return EXIT_FAILURE; + } + + while ((opt = getopt_long(argc, argv, "b:c::fhjnpvN:V", + long_options, NULL)) >= 0) { + + switch (opt) { + case 'V': + printf("dcb utility, iproute2-%s\n", version); + ret = EXIT_SUCCESS; + goto dcb_free; + case 'f': + force = true; + break; + case 'b': + batch_file = optarg; + break; + case 'j': + dcb->json_output = true; + break; + case 'p': + pretty = true; + break; + case 'N': + if (netns_switch(optarg)) { + ret = EXIT_FAILURE; + goto dcb_free; + } + break; + case 'h': + dcb_help(); + return 0; + default: + fprintf(stderr, "Unknown option.\n"); + dcb_help(); + ret = EXIT_FAILURE; + goto dcb_free; + } + } + + argc -= optind; + argv += optind; + + err = dcb_init(dcb); + if (err) { + ret = EXIT_FAILURE; + goto dcb_free; + } + + if (batch_file) + err = dcb_batch(dcb, batch_file, force); + else + err = dcb_cmd(dcb, argc, argv); + + if (err) { + ret = EXIT_FAILURE; + goto dcb_fini; + } + + ret = EXIT_SUCCESS; + +dcb_fini: + dcb_fini(dcb); +dcb_free: + dcb_free(dcb); + + return ret; +} diff --git a/dcb/dcb.h b/dcb/dcb.h new file mode 100644 index 00000000..6f135ed0 --- /dev/null +++ b/dcb/dcb.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DCB_H__ +#define __DCB_H__ 1 + +#include +#include + +/* dcb.c */ + +struct dcb { + char *buf; + struct mnl_socket *nl; + bool json_output; +}; + +int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key, + const char *what_value, __u32 value, __u32 max_value, + void (*set_array)(__u32 index, __u32 value, void *data), + void *set_array_data); +int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv, + int (*and_then)(struct dcb *dcb, const char *dev, + int argc, char **argv), + void (*help)(void)); + +void dcb_set_u8(__u32 key, __u32 value, void *data); + +int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, + void *data, size_t data_len); +int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, + const void *data, size_t data_len); + +void dcb_print_named_array(const char *json_name, const char *fp_name, + const __u8 *array, size_t size, + void (*print_array)(const __u8 *, size_t)); +void dcb_print_array_u8(const __u8 *array, size_t size); +void dcb_print_array_kw(const __u8 *array, size_t array_size, + const char *const kw[], size_t kw_size); + +/* dcb_ets.c */ + +int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv); + +#endif /* __DCB_H__ */ diff --git a/dcb/dcb_ets.c b/dcb/dcb_ets.c new file mode 100644 index 00000000..1735885a --- /dev/null +++ b/dcb/dcb_ets.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include + +#include "dcb.h" +#include "utils.h" + +static void dcb_ets_help_set(void) +{ + fprintf(stderr, + "Usage: dcb ets set dev STRING\n" + " [ willing { on | off } ]\n" + " [ { tc-tsa | reco-tc-tsa } TSA-MAP ]\n" + " [ { pg-bw | tc-bw | reco-tc-bw } BW-MAP ]\n" + " [ { prio-tc | reco-prio-tc } PRIO-MAP ]\n" + "\n" + " where TSA-MAP := [ TSA-MAP ] TSA-MAPPING\n" + " TSA-MAPPING := { all | TC }:{ strict | cbs | ets | vendor }\n" + " BW-MAP := [ BW-MAP ] BW-MAPPING\n" + " BW-MAPPING := { all | TC }:INTEGER\n" + " PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n" + " PRIO-MAPPING := { all | PRIO }:TC\n" + " TC := { 0 .. 7 }\n" + " PRIO := { 0 .. 7 }\n" + "\n" + ); +} + +static void dcb_ets_help_show(void) +{ + fprintf(stderr, + "Usage: dcb ets show dev STRING\n" + " [ willing | ets-cap | cbs | tc-tsa | reco-tc-tsa |\n" + " pg-bw | tc-bw | reco-tc-bw | prio-tc |\n" + " reco-prio-tc ]\n" + "\n" + ); +} + +static void dcb_ets_help(void) +{ + fprintf(stderr, + "Usage: dcb ets help\n" + "\n" + ); + dcb_ets_help_show(); + dcb_ets_help_set(); +} + +static const char *const tsa_names[] = { + [IEEE_8021QAZ_TSA_STRICT] = "strict", + [IEEE_8021QAZ_TSA_CB_SHAPER] = "cbs", + [IEEE_8021QAZ_TSA_ETS] = "ets", + [IEEE_8021QAZ_TSA_VENDOR] = "vendor", +}; + +static int dcb_ets_parse_mapping_tc_tsa(__u32 key, char *value, void *data) +{ + __u8 tsa; + int ret; + + tsa = parse_one_of("TSA", value, tsa_names, ARRAY_SIZE(tsa_names), &ret); + if (ret) + return ret; + + return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1, + "TSA", tsa, -1U, + dcb_set_u8, data); +} + +static int dcb_ets_parse_mapping_tc_bw(__u32 key, char *value, void *data) +{ + __u8 bw; + + if (get_u8(&bw, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1, + "BW", bw, 100, + dcb_set_u8, data); +} + +static int dcb_ets_parse_mapping_prio_tc(unsigned int key, char *value, void *data) +{ + __u8 tc; + + if (get_u8(&tc, value, 0)) + return -EINVAL; + + return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1, + "TC", tc, IEEE_8021QAZ_MAX_TCS - 1, + dcb_set_u8, data); +} + +static void dcb_print_array_tsa(const __u8 *array, size_t size) +{ + dcb_print_array_kw(array, size, tsa_names, ARRAY_SIZE(tsa_names)); +} + +static void dcb_ets_print_willing(const struct ieee_ets *ets) +{ + print_on_off(PRINT_ANY, "willing", "willing %s ", ets->willing); +} + +static void dcb_ets_print_ets_cap(const struct ieee_ets *ets) +{ + print_uint(PRINT_ANY, "ets_cap", "ets-cap %d ", ets->ets_cap); +} + +static void dcb_ets_print_cbs(const struct ieee_ets *ets) +{ + print_on_off(PRINT_ANY, "cbs", "cbs %s ", ets->cbs); +} + +static void dcb_ets_print_tc_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("tc_bw", "tc-bw", + ets->tc_tx_bw, ARRAY_SIZE(ets->tc_tx_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_pg_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("pg_bw", "pg-bw", + ets->tc_rx_bw, ARRAY_SIZE(ets->tc_rx_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_tc_tsa(const struct ieee_ets *ets) +{ + dcb_print_named_array("tc_tsa", "tc-tsa", + ets->tc_tsa, ARRAY_SIZE(ets->tc_tsa), + dcb_print_array_tsa); +} + +static void dcb_ets_print_prio_tc(const struct ieee_ets *ets) +{ + dcb_print_named_array("prio_tc", "prio-tc", + ets->prio_tc, ARRAY_SIZE(ets->prio_tc), + dcb_print_array_u8); +} + +static void dcb_ets_print_reco_tc_bw(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_tc_bw", "reco-tc-bw", + ets->tc_reco_bw, ARRAY_SIZE(ets->tc_reco_bw), + dcb_print_array_u8); +} + +static void dcb_ets_print_reco_tc_tsa(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_tc_tsa", "reco-tc-tsa", + ets->tc_reco_tsa, ARRAY_SIZE(ets->tc_reco_tsa), + dcb_print_array_tsa); +} + +static void dcb_ets_print_reco_prio_tc(const struct ieee_ets *ets) +{ + dcb_print_named_array("reco_prio_tc", "reco-prio-tc", + ets->reco_prio_tc, ARRAY_SIZE(ets->reco_prio_tc), + dcb_print_array_u8); +} + +static void dcb_ets_print(const struct ieee_ets *ets) +{ + dcb_ets_print_willing(ets); + dcb_ets_print_ets_cap(ets); + dcb_ets_print_cbs(ets); + print_nl(); + + dcb_ets_print_tc_bw(ets); + print_nl(); + + dcb_ets_print_pg_bw(ets); + print_nl(); + + dcb_ets_print_tc_tsa(ets); + print_nl(); + + dcb_ets_print_prio_tc(ets); + print_nl(); + + dcb_ets_print_reco_tc_bw(ets); + print_nl(); + + dcb_ets_print_reco_tc_tsa(ets); + print_nl(); + + dcb_ets_print_reco_prio_tc(ets); + print_nl(); +} + +static int dcb_ets_get(struct dcb *dcb, const char *dev, struct ieee_ets *ets) +{ + return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets)); +} + +static int dcb_ets_validate_bw(const __u8 bw[], const __u8 tsa[], const char *what) +{ + bool has_ets = false; + unsigned int total = 0; + unsigned int tc; + + for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) { + if (tsa[tc] == IEEE_8021QAZ_TSA_ETS) { + has_ets = true; + break; + } + } + + /* TC bandwidth is only intended for ETS, but 802.1Q-2018 only requires + * that the sum be 100, and individual entries 0..100. It explicitly + * notes that non-ETS TCs can have non-0 TC bandwidth during + * reconfiguration. + */ + for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) { + if (bw[tc] > 100) { + fprintf(stderr, "%d%% for TC %d of %s is not a valid bandwidth percentage, expected 0..100%%\n", + bw[tc], tc, what); + return -EINVAL; + } + total += bw[tc]; + } + + /* This is what 802.1Q-2018 requires. */ + if (total == 100) + return 0; + + /* But this requirement does not make sense for all-strict + * configurations. Anything else than 0 does not make sense: either BW + * has not been reconfigured for the all-strict allocation yet, at which + * point we expect sum of 100. Or it has already been reconfigured, at + * which point accept 0. + */ + if (!has_ets && total == 0) + return 0; + + fprintf(stderr, "Bandwidth percentages in %s sum to %d%%, expected %d%%\n", + what, total, has_ets ? 100 : 0); + return -EINVAL; +} + +static int dcb_ets_set(struct dcb *dcb, const char *dev, const struct ieee_ets *ets) +{ + /* Do not validate pg-bw, which is not standard and has unclear + * meaning. + */ + if (dcb_ets_validate_bw(ets->tc_tx_bw, ets->tc_tsa, "tc-bw") || + dcb_ets_validate_bw(ets->tc_reco_bw, ets->tc_reco_tsa, "reco-tc-bw")) + return -EINVAL; + + return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets)); +} + +static int dcb_cmd_ets_set(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_ets ets; + int ret; + + if (!argc) { + dcb_ets_help_set(); + return 1; + } + + ret = dcb_ets_get(dcb, dev, &ets); + if (ret) + return ret; + + do { + if (matches(*argv, "help") == 0) { + dcb_ets_help_set(); + return 0; + } else if (matches(*argv, "willing") == 0) { + NEXT_ARG(); + ets.willing = parse_on_off("willing", *argv, &ret); + if (ret) + return ret; + } else if (matches(*argv, "tc-tsa") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa, + ets.tc_tsa); + if (ret) { + fprintf(stderr, "Invalid tc-tsa mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-tc-tsa") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa, + ets.tc_reco_tsa); + if (ret) { + fprintf(stderr, "Invalid reco-tc-tsa mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "tc-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_tx_bw); + if (ret) { + fprintf(stderr, "Invalid tc-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "pg-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_rx_bw); + if (ret) { + fprintf(stderr, "Invalid pg-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-tc-bw") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw, + ets.tc_reco_bw); + if (ret) { + fprintf(stderr, "Invalid reco-tc-bw mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "prio-tc") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc, + ets.prio_tc); + if (ret) { + fprintf(stderr, "Invalid prio-tc mapping %s\n", *argv); + return ret; + } + continue; + } else if (matches(*argv, "reco-prio-tc") == 0) { + NEXT_ARG(); + ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc, + ets.reco_prio_tc); + if (ret) { + fprintf(stderr, "Invalid reco-prio-tc mapping %s\n", *argv); + return ret; + } + continue; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help_set(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + + return dcb_ets_set(dcb, dev, &ets); +} + +static int dcb_cmd_ets_show(struct dcb *dcb, const char *dev, int argc, char **argv) +{ + struct ieee_ets ets; + int ret; + + ret = dcb_ets_get(dcb, dev, &ets); + if (ret) + return ret; + + open_json_object(NULL); + + if (!argc) { + dcb_ets_print(&ets); + goto out; + } + + do { + if (matches(*argv, "help") == 0) { + dcb_ets_help(); + return 0; + } else if (matches(*argv, "willing") == 0) { + dcb_ets_print_willing(&ets); + print_nl(); + } else if (matches(*argv, "ets-cap") == 0) { + dcb_ets_print_ets_cap(&ets); + print_nl(); + } else if (matches(*argv, "cbs") == 0) { + dcb_ets_print_cbs(&ets); + print_nl(); + } else if (matches(*argv, "tc-tsa") == 0) { + dcb_ets_print_tc_tsa(&ets); + print_nl(); + } else if (matches(*argv, "reco-tc-tsa") == 0) { + dcb_ets_print_reco_tc_tsa(&ets); + print_nl(); + } else if (matches(*argv, "tc-bw") == 0) { + dcb_ets_print_tc_bw(&ets); + print_nl(); + } else if (matches(*argv, "pg-bw") == 0) { + dcb_ets_print_pg_bw(&ets); + print_nl(); + } else if (matches(*argv, "reco-tc-bw") == 0) { + dcb_ets_print_reco_tc_bw(&ets); + print_nl(); + } else if (matches(*argv, "prio-tc") == 0) { + dcb_ets_print_prio_tc(&ets); + print_nl(); + } else if (matches(*argv, "reco-prio-tc") == 0) { + dcb_ets_print_reco_prio_tc(&ets); + print_nl(); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help(); + return -EINVAL; + } + + NEXT_ARG_FWD(); + } while (argc > 0); + +out: + close_json_object(); + return 0; +} + +int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv) +{ + if (!argc || matches(*argv, "help") == 0) { + dcb_ets_help(); + return 0; + } else if (matches(*argv, "show") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_show, dcb_ets_help_show); + } else if (matches(*argv, "set") == 0) { + NEXT_ARG_FWD(); + return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_set, dcb_ets_help_set); + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + dcb_ets_help(); + return -EINVAL; + } +} diff --git a/devlink/Makefile b/devlink/Makefile index 7da7d1fa..d540feb3 100644 --- a/devlink/Makefile +++ b/devlink/Makefile @@ -12,7 +12,7 @@ endif all: $(TARGETS) $(LIBS) -devlink: $(DEVLINKOBJ) +devlink: $(DEVLINKOBJ) $(LIBNETLINK) $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ install: all diff --git a/devlink/devlink.c b/devlink/devlink.c index 1ff865bc..5bb00b3a 100644 --- a/devlink/devlink.c +++ b/devlink/devlink.c @@ -7912,43 +7912,16 @@ static void dl_free(struct dl *dl) free(dl); } +static int dl_batch_cmd(int argc, char *argv[], void *data) +{ + struct dl *dl = data; + + return dl_cmd(dl, argc, argv); +} + static int dl_batch(struct dl *dl, const char *name, bool force) { - char *line = NULL; - size_t len = 0; - int ret = EXIT_SUCCESS; - - if (name && strcmp(name, "-") != 0) { - if (freopen(name, "r", stdin) == NULL) { - fprintf(stderr, - "Cannot open file \"%s\" for reading: %s\n", - name, strerror(errno)); - return EXIT_FAILURE; - } - } - - cmdlineno = 0; - while (getcmdline(&line, &len, stdin) != -1) { - char *largv[100]; - int largc; - - largc = makeargs(line, largv, 100); - if (!largc) - continue; /* blank line */ - - if (dl_cmd(dl, largc, largv)) { - fprintf(stderr, "Command failed %s:%d\n", - name, cmdlineno); - ret = EXIT_FAILURE; - if (!force) - break; - } - } - - if (line) - free(line); - - return ret; + return do_batch(name, force, dl_batch_cmd, dl); } int main(int argc, char **argv) diff --git a/devlink/mnlg.c b/devlink/mnlg.c index c7d25e87..21b10c5a 100644 --- a/devlink/mnlg.c +++ b/devlink/mnlg.c @@ -14,11 +14,11 @@ #include #include #include -#include #include #include #include "libnetlink.h" +#include "mnl_utils.h" #include "utils.h" #include "mnlg.h" @@ -28,26 +28,20 @@ struct mnlg_socket { uint32_t id; uint8_t version; unsigned int seq; - unsigned int portid; }; static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, uint16_t flags, uint32_t id, uint8_t version) { + struct genlmsghdr genl = { + .cmd = cmd, + .version = version, + }; struct nlmsghdr *nlh; - struct genlmsghdr *genl; - - nlh = mnl_nlmsg_put_header(nlg->buf); - nlh->nlmsg_type = id; - nlh->nlmsg_flags = flags; - nlg->seq = time(NULL); - nlh->nlmsg_seq = nlg->seq; - - genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); - genl->cmd = cmd; - genl->version = version; + nlh = mnlu_msg_prepare(nlg->buf, id, flags, &genl, sizeof(genl)); + nlg->seq = nlh->nlmsg_seq; return nlh; } @@ -62,61 +56,10 @@ int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh) return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len); } -static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data) -{ - return MNL_CB_OK; -} - -static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data) -{ - const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); - - /* Netlink subsystems returns the errno value with different signess */ - if (err->error < 0) - errno = -err->error; - else - errno = err->error; - - if (nl_dump_ext_ack(nlh, NULL)) - return MNL_CB_ERROR; - - return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; -} - -static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data) -{ - int len = *(int *)NLMSG_DATA(nlh); - - if (len < 0) { - errno = -len; - nl_dump_ext_ack_done(nlh, len); - return MNL_CB_ERROR; - } - return MNL_CB_STOP; -} - -static mnl_cb_t mnlg_cb_array[NLMSG_MIN_TYPE] = { - [NLMSG_NOOP] = mnlg_cb_noop, - [NLMSG_ERROR] = mnlg_cb_error, - [NLMSG_DONE] = mnlg_cb_stop, - [NLMSG_OVERRUN] = mnlg_cb_noop, -}; - int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data) { - int err; - - do { - err = mnl_socket_recvfrom(nlg->nl, nlg->buf, - MNL_SOCKET_BUFFER_SIZE); - if (err <= 0) - break; - err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid, - data_cb, data, mnlg_cb_array, - ARRAY_SIZE(mnlg_cb_array)); - } while (err > 0); - - return err; + return mnlu_socket_recv_run(nlg->nl, nlg->seq, nlg->buf, MNL_SOCKET_BUFFER_SIZE, + data_cb, data); } struct group_info { @@ -263,7 +206,6 @@ struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version) { struct mnlg_socket *nlg; struct nlmsghdr *nlh; - int one = 1; int err; nlg = malloc(sizeof(*nlg)); @@ -274,19 +216,9 @@ struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version) if (!nlg->buf) goto err_buf_alloc; - nlg->nl = mnl_socket_open(NETLINK_GENERIC); + nlg->nl = mnlu_socket_open(NETLINK_GENERIC); if (!nlg->nl) - goto err_mnl_socket_open; - - /* Older kernels may no support capped/extended ACK reporting */ - mnl_socket_setsockopt(nlg->nl, NETLINK_CAP_ACK, &one, sizeof(one)); - mnl_socket_setsockopt(nlg->nl, NETLINK_EXT_ACK, &one, sizeof(one)); - - err = mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID); - if (err < 0) - goto err_mnl_socket_bind; - - nlg->portid = mnl_socket_get_portid(nlg->nl); + goto err_socket_open; nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY, NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1); @@ -305,9 +237,8 @@ struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version) err_mnlg_socket_recv_run: err_mnlg_socket_send: -err_mnl_socket_bind: mnl_socket_close(nlg->nl); -err_mnl_socket_open: +err_socket_open: free(nlg->buf); err_buf_alloc: free(nlg); diff --git a/include/json_print.h b/include/json_print.h index 50e71de4..096a999a 100644 --- a/include/json_print.h +++ b/include/json_print.h @@ -65,6 +65,7 @@ void print_nl(void); _PRINT_FUNC(int, int) _PRINT_FUNC(s64, int64_t) _PRINT_FUNC(bool, bool) +_PRINT_FUNC(on_off, bool) _PRINT_FUNC(null, const char*) _PRINT_FUNC(string, const char*) _PRINT_FUNC(uint, unsigned int) diff --git a/include/mnl_utils.h b/include/mnl_utils.h new file mode 100644 index 00000000..fa826ef1 --- /dev/null +++ b/include/mnl_utils.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MNL_UTILS_H__ +#define __MNL_UTILS_H__ 1 + +struct mnl_socket *mnlu_socket_open(int bus); +struct nlmsghdr *mnlu_msg_prepare(void *buf, uint32_t nlmsg_type, uint16_t flags, + void *extra_header, size_t extra_header_size); +int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size, + mnl_cb_t cb, void *data); + +#endif /* __MNL_UTILS_H__ */ diff --git a/include/utils.h b/include/utils.h index 7041c461..588fceb7 100644 --- a/include/utils.h +++ b/include/utils.h @@ -322,4 +322,15 @@ int get_time64(__s64 *time, const char *str); char *sprint_time(__u32 time, char *buf); char *sprint_time64(__s64 time, char *buf); +int do_batch(const char *name, bool force, + int (*cmd)(int argc, char *argv[], void *user), void *user); + +int parse_one_of(const char *msg, const char *realval, const char * const *list, + size_t len, int *p_err); +bool parse_on_off(const char *msg, const char *realval, int *p_err); + +int parse_mapping(int *argcp, char ***argvp, bool allow_all, + int (*mapping_cb)(__u32 key, char *value, void *data), + void *mapping_cb_data); + #endif /* __UTILS_H__ */ diff --git a/ip/ip.c b/ip/ip.c index ac445023..5e31957f 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -121,56 +121,30 @@ static int do_cmd(const char *argv0, int argc, char **argv) return EXIT_FAILURE; } +static int ip_batch_cmd(int argc, char *argv[], void *data) +{ + const int *orig_family = data; + + preferred_family = *orig_family; + return do_cmd(argv[0], argc, argv); +} + static int batch(const char *name) { - char *line = NULL; - size_t len = 0; - int ret = EXIT_SUCCESS; int orig_family = preferred_family; - - batch_mode = 1; - - if (name && strcmp(name, "-") != 0) { - if (freopen(name, "r", stdin) == NULL) { - fprintf(stderr, - "Cannot open file \"%s\" for reading: %s\n", - name, strerror(errno)); - return EXIT_FAILURE; - } - } + int ret; if (rtnl_open(&rth, 0) < 0) { fprintf(stderr, "Cannot open rtnetlink\n"); return EXIT_FAILURE; } - cmdlineno = 0; - while (getcmdline(&line, &len, stdin) != -1) { - char *largv[100]; - int largc; - - preferred_family = orig_family; - - largc = makeargs(line, largv, 100); - if (largc == 0) - continue; /* blank line */ - - if (do_cmd(largv[0], largc, largv)) { - fprintf(stderr, "Command failed %s:%d\n", - name, cmdlineno); - ret = EXIT_FAILURE; - if (!force) - break; - } - } - if (line) - free(line); + ret = do_batch(name, force, ip_batch_cmd, &orig_family); rtnl_close(&rth); return ret; } - int main(int argc, char **argv) { char *basename; diff --git a/ip/iplink_vlan.c b/ip/iplink_vlan.c index 1e6817f5..1426f2af 100644 --- a/ip/iplink_vlan.c +++ b/ip/iplink_vlan.c @@ -49,36 +49,30 @@ static int on_off(const char *msg, const char *arg) return -1; } +static int parse_qos_mapping(__u32 key, char *value, void *data) +{ + struct nlmsghdr *n = data; + struct ifla_vlan_qos_mapping m = { + .from = key, + }; + + if (get_u32(&m.to, value, 0)) + return 1; + + return addattr_l(n, 1024, IFLA_VLAN_QOS_MAPPING, &m, sizeof(m)); +} + static int vlan_parse_qos_map(int *argcp, char ***argvp, struct nlmsghdr *n, int attrtype) { - int argc = *argcp; - char **argv = *argvp; - struct ifla_vlan_qos_mapping m; struct rtattr *tail; tail = addattr_nest(n, 1024, attrtype); - while (argc > 0) { - char *colon = strchr(*argv, ':'); - - if (!colon) - break; - *colon = '\0'; - - if (get_u32(&m.from, *argv, 0)) - return 1; - if (get_u32(&m.to, colon + 1, 0)) - return 1; - argc--, argv++; - - addattr_l(n, 1024, IFLA_VLAN_QOS_MAPPING, &m, sizeof(m)); - } + if (parse_mapping(argcp, argvp, false, &parse_qos_mapping, n)) + return 1; addattr_nest_end(n, tail); - - *argcp = argc; - *argvp = argv; return 0; } diff --git a/ip/ipmacsec.c b/ip/ipmacsec.c index 18289ecd..bf48e8b5 100644 --- a/ip/ipmacsec.c +++ b/ip/ipmacsec.c @@ -23,8 +23,6 @@ #include "ll_map.h" #include "libgenl.h" -static const char * const values_on_off[] = { "off", "on" }; - static const char * const validate_str[] = { [MACSEC_VALIDATE_DISABLED] = "disabled", [MACSEC_VALIDATE_CHECK] = "check", @@ -108,25 +106,6 @@ static void ipmacsec_usage(void) exit(-1); } -static int one_of(const char *msg, const char *realval, const char * const *list, - size_t len, int *index) -{ - int i; - - for (i = 0; i < len; i++) { - if (matches(realval, list[i]) == 0) { - *index = i; - return 0; - } - } - - fprintf(stderr, "Error: argument of \"%s\" must be one of ", msg); - for (i = 0; i < len; i++) - fprintf(stderr, "\"%s\", ", list[i]); - fprintf(stderr, "not \"%s\"\n", realval); - return -1; -} - static int get_an(__u8 *val, const char *arg) { int ret = get_u8(val, arg, 0); @@ -559,8 +538,7 @@ static int do_offload(enum cmd c, int argc, char **argv) if (argc == 0) ipmacsec_usage(); - ret = one_of("offload", *argv, offload_str, ARRAY_SIZE(offload_str), - (int *)&offload); + offload = parse_one_of("offload", *argv, offload_str, ARRAY_SIZE(offload_str), &ret); if (ret) ipmacsec_usage(); @@ -1334,8 +1312,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("encrypt", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("encrypt", *argv, &ret); if (ret != 0) return ret; addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_ENCRYPT, i); @@ -1343,8 +1320,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("send_sci", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("send_sci", *argv, &ret); if (ret != 0) return ret; send_sci = i; @@ -1354,8 +1330,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("end_station", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("end_station", *argv, &ret); if (ret != 0) return ret; es = i; @@ -1364,8 +1339,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("scb", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("scb", *argv, &ret); if (ret != 0) return ret; scb = i; @@ -1374,8 +1348,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("protect", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("protect", *argv, &ret); if (ret != 0) return ret; addattr8(n, MACSEC_BUFLEN, IFLA_MACSEC_PROTECT, i); @@ -1383,8 +1356,7 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, NEXT_ARG(); int i; - ret = one_of("replay", *argv, values_on_off, - ARRAY_SIZE(values_on_off), &i); + i = parse_on_off("replay", *argv, &ret); if (ret != 0) return ret; replay_protect = !!i; @@ -1395,9 +1367,8 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, invarg("expected replay window size", *argv); } else if (strcmp(*argv, "validate") == 0) { NEXT_ARG(); - ret = one_of("validate", *argv, - validate_str, ARRAY_SIZE(validate_str), - (int *)&validate); + validate = parse_one_of("validate", *argv, validate_str, + ARRAY_SIZE(validate_str), &ret); if (ret != 0) return ret; addattr8(n, MACSEC_BUFLEN, @@ -1411,9 +1382,8 @@ static int macsec_parse_opt(struct link_util *lu, int argc, char **argv, invarg("expected an { 0..3 }", *argv); } else if (strcmp(*argv, "offload") == 0) { NEXT_ARG(); - ret = one_of("offload", *argv, - offload_str, ARRAY_SIZE(offload_str), - (int *)&offload); + offload = parse_one_of("offload", *argv, offload_str, + ARRAY_SIZE(offload_str), &ret); if (ret != 0) return ret; addattr8(n, MACSEC_BUFLEN, diff --git a/lib/Makefile b/lib/Makefile index 7cba1857..13f4ee15 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -7,7 +7,7 @@ UTILOBJ = utils.o rt_names.o ll_map.o ll_types.o ll_proto.o ll_addr.o \ inet_proto.o namespace.o json_writer.o json_print.o \ names.o color.o bpf.o exec.o fs.o cg_map.o -NLOBJ=libgenl.o libnetlink.o +NLOBJ=libgenl.o libnetlink.o mnl_utils.o all: libnetlink.a libutil.a diff --git a/lib/json_print.c b/lib/json_print.c index fe0705bf..62eeb1f1 100644 --- a/lib/json_print.c +++ b/lib/json_print.c @@ -191,11 +191,12 @@ int print_color_string(enum output_type type, * a value to it, you will need to use "is_json_context()" to have different * branch for json and regular output. grep -r "print_bool" for example */ -int print_color_bool(enum output_type type, - enum color_attr color, - const char *key, - const char *fmt, - bool value) +static int __print_color_bool(enum output_type type, + enum color_attr color, + const char *key, + const char *fmt, + bool value, + const char *str) { int ret = 0; @@ -205,13 +206,32 @@ int print_color_bool(enum output_type type, else jsonw_bool(_jw, value); } else if (_IS_FP_CONTEXT(type)) { - ret = color_fprintf(stdout, color, fmt, - value ? "true" : "false"); + ret = color_fprintf(stdout, color, fmt, str); } return ret; } +int print_color_bool(enum output_type type, + enum color_attr color, + const char *key, + const char *fmt, + bool value) +{ + return __print_color_bool(type, color, key, fmt, value, + value ? "true" : "false"); +} + +int print_color_on_off(enum output_type type, + enum color_attr color, + const char *key, + const char *fmt, + bool value) +{ + return __print_color_bool(type, color, key, fmt, value, + value ? "on" : "off"); +} + /* * In JSON context uses hardcode %#x format: 42 -> 0x2a */ diff --git a/lib/mnl_utils.c b/lib/mnl_utils.c new file mode 100644 index 00000000..46384ff8 --- /dev/null +++ b/lib/mnl_utils.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mnl_utils.c Helpers for working with libmnl. + */ + +#include +#include +#include +#include + +#include "libnetlink.h" +#include "mnl_utils.h" +#include "utils.h" + +struct mnl_socket *mnlu_socket_open(int bus) +{ + struct mnl_socket *nl; + int one = 1; + + nl = mnl_socket_open(bus); + if (nl == NULL) + return NULL; + + mnl_socket_setsockopt(nl, NETLINK_CAP_ACK, &one, sizeof(one)); + mnl_socket_setsockopt(nl, NETLINK_EXT_ACK, &one, sizeof(one)); + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) + goto err_bind; + + return nl; + +err_bind: + mnl_socket_close(nl); + return NULL; +} + +struct nlmsghdr *mnlu_msg_prepare(void *buf, uint32_t nlmsg_type, uint16_t flags, + void *extra_header, size_t extra_header_size) +{ + struct nlmsghdr *nlh; + void *eh; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = nlmsg_type; + nlh->nlmsg_flags = flags; + nlh->nlmsg_seq = time(NULL); + + eh = mnl_nlmsg_put_extra_header(nlh, extra_header_size); + memcpy(eh, extra_header, extra_header_size); + + return nlh; +} + +static int mnlu_cb_noop(const struct nlmsghdr *nlh, void *data) +{ + return MNL_CB_OK; +} + +static int mnlu_cb_error(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + + /* Netlink subsystems returns the errno value with different signess */ + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + if (nl_dump_ext_ack(nlh, NULL)) + return MNL_CB_ERROR; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnlu_cb_stop(const struct nlmsghdr *nlh, void *data) +{ + int len = *(int *)NLMSG_DATA(nlh); + + if (len < 0) { + errno = -len; + nl_dump_ext_ack_done(nlh, len); + return MNL_CB_ERROR; + } + return MNL_CB_STOP; +} + +static mnl_cb_t mnlu_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = mnlu_cb_noop, + [NLMSG_ERROR] = mnlu_cb_error, + [NLMSG_DONE] = mnlu_cb_stop, + [NLMSG_OVERRUN] = mnlu_cb_noop, +}; + +int mnlu_socket_recv_run(struct mnl_socket *nl, unsigned int seq, void *buf, size_t buf_size, + mnl_cb_t cb, void *data) +{ + unsigned int portid = mnl_socket_get_portid(nl); + int err; + + do { + err = mnl_socket_recvfrom(nl, buf, buf_size); + if (err <= 0) + break; + err = mnl_cb_run2(buf, err, seq, portid, + cb, data, mnlu_cb_array, + ARRAY_SIZE(mnlu_cb_array)); + } while (err > 0); + + return err; +} diff --git a/lib/utils.c b/lib/utils.c index c98021d6..a0ba5181 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -1695,3 +1695,106 @@ char *sprint_time64(__s64 time, char *buf) print_time64(buf, SPRINT_BSIZE-1, time); return buf; } + +int do_batch(const char *name, bool force, + int (*cmd)(int argc, char *argv[], void *data), void *data) +{ + char *line = NULL; + size_t len = 0; + int ret = EXIT_SUCCESS; + + if (name && strcmp(name, "-") != 0) { + if (freopen(name, "r", stdin) == NULL) { + fprintf(stderr, + "Cannot open file \"%s\" for reading: %s\n", + name, strerror(errno)); + return EXIT_FAILURE; + } + } + + cmdlineno = 0; + while (getcmdline(&line, &len, stdin) != -1) { + char *largv[100]; + int largc; + + largc = makeargs(line, largv, 100); + if (!largc) + continue; /* blank line */ + + if (cmd(largc, largv, data)) { + fprintf(stderr, "Command failed %s:%d\n", + name, cmdlineno); + ret = EXIT_FAILURE; + if (!force) + break; + } + } + + if (line) + free(line); + + return ret; +} + +int parse_one_of(const char *msg, const char *realval, const char * const *list, + size_t len, int *p_err) +{ + int i; + + for (i = 0; i < len; i++) { + if (list[i] && matches(realval, list[i]) == 0) { + *p_err = 0; + return i; + } + } + + fprintf(stderr, "Error: argument of \"%s\" must be one of ", msg); + for (i = 0; i < len; i++) + if (list[i]) + fprintf(stderr, "\"%s\", ", list[i]); + fprintf(stderr, "not \"%s\"\n", realval); + *p_err = -EINVAL; + return 0; +} + +bool parse_on_off(const char *msg, const char *realval, int *p_err) +{ + static const char * const values_on_off[] = { "off", "on" }; + + return parse_one_of(msg, realval, values_on_off, ARRAY_SIZE(values_on_off), p_err); +} + +int parse_mapping(int *argcp, char ***argvp, bool allow_all, + int (*mapping_cb)(__u32 key, char *value, void *data), + void *mapping_cb_data) +{ + int argc = *argcp; + char **argv = *argvp; + int ret = 0; + + while (argc > 0) { + char *colon = strchr(*argv, ':'); + __u32 key; + + if (!colon) + break; + *colon = '\0'; + + if (allow_all && matches(*argv, "all") == 0) { + key = (__u32) -1; + } else if (get_u32(&key, *argv, 0)) { + ret = 1; + break; + } + if (mapping_cb(key, colon + 1, mapping_cb_data)) { + ret = 1; + break; + } + + argc--, argv++; + } + + *argcp = argc; + *argvp = argv; + return ret; +} diff --git a/man/man8/dcb-ets.8 b/man/man8/dcb-ets.8 new file mode 100644 index 00000000..0ae3587c --- /dev/null +++ b/man/man8/dcb-ets.8 @@ -0,0 +1,188 @@ +.TH DCB-ETS 8 "19 October 2020" "iproute2" "Linux" +.SH NAME +dcb-ets \- show / manipulate ETS (Enhanced Transmission Selection) settings of +the DCB (Data Center Bridging) subsystem +.SH SYNOPSIS +.sp +.ad l +.in +8 + +.ti -8 +.B dcb +.RI "[ " OPTIONS " ] " +.B ets +.RI "{ " COMMAND " | " help " }" +.sp + +.ti -8 +.B dcb ets show dev +.RI DEV +.RB "[ { " willing " | " ets-cap " | " cbs " | " tc-tsa " | " reco-tc-tsa +.RB " | " pg-bw " | " tc-bw " | " reco-tc-bw " | " prio-tc +.RB " | " reco-prio-tc " } ]" + +.ti -8 +.B dcb ets set dev +.RI DEV +.RB "[ " willing " { " on " | " off " } ]" +.RB "[ { " tc-tsa " | " reco-tc-tsa " } " \fITSA-MAP\fB " ]" +.RB "[ { " pg-bw " | " tc-bw " | " reco-tc-bw " } " \fIBW-MAP\fB " ]" +.RB "[ { " prio-tc " | " reco-prio-tc " } " \fIPRIO-MAP\fB " ]" + +.ti -8 +.IR TSA-MAP " := [ " TSA-MAP " ] " TSA-MAPPING + +.ti -8 +.IR TSA-MAPPING " := { " TC " | " \fBall " }" \fB: "{ " \fBstrict\fR " | " +.IR \fBcbs\fR " | " \fBets\fR " | " \fBvendor\fR " }" + +.ti -8 +.IR BW-MAP " := [ " BW-MAP " ] " BW-MAPPING + +.ti -8 +.IR BW-MAPPING " := { " TC " | " \fBall " }" \fB:\fIINTEGER\fR + +.ti -8 +.IR PRIO-MAP " := [ " PRIO-MAP " ] " PRIO-MAPPING + +.ti -8 +.IR PRIO-MAPPING " := { " PRIO " | " \fBall " }" \fB:\fITC\fR + +.ti -8 +.IR TC " := { " \fB0\fR " .. " \fB7\fR " }" + +.ti -8 +.IR PRIO " := { " \fB0\fR " .. " \fB7\fR " }" + + +.SH DESCRIPTION + +.B dcb ets +is used to configure Enhanced Transmission Selection attributes through Linux +DCB (Data Center Bridging) interface. ETS permits configuration of mapping of +priorities to traffic classes, traffic selection algorithm to use per traffic +class, bandwidth allocation, etc. + +Two DCB TLVs are related to the ETS feature: a configuration and recommendation +values. Recommendation values are named with a prefix +.B reco-, +while the configuration ones have plain names. + +.SH PARAMETERS + +For read-write parameters, the following describes only the write direction, +i.e. as used with the \fBset\fR command. For the \fBshow\fR command, the +parameter name is to be used as a simple keyword without further arguments. This +instructs the tool to show the value of a given parameter. When no parameters +are given, the tool shows the complete ETS configuration. + +.TP +.B ets-cap +A read-only property that shows the number of supported ETS traffic classes. + +.TP +.B cbs +A read-only property that is enabled if the driver and the hardware support the +CBS Transmission Selection Algorithm. + +.TP +.B willing \fR{ \fBon\fR | \fBoff\fR } +Whether local host should accept configuration from peer TLVs. + +.TP +.B prio-tc \fIPRIO-MAP +.TQ +.B reco-prio-tc \fIPRIO-MAP +\fIPRIO-MAP\fR uses the array parameter syntax, see +.BR dcb (8) +for details. Keys are priorities, values are traffic classes. For each priority +sets a TC where traffic with that priority is directed to. + +.TP +.B tc-tsa \fITSA-MAP +.TQ +.B reco-tc-tsa \fITSA-MAP +\fITSA-MAP\fR uses the array parameter syntax, see +.BR dcb (8) +for details. Keys are TCs, values are Transmission Selection Algorithm (TSA) +keywords described below. For each TC sets an algorithm used for deciding how +traffic queued up at this TC is scheduled for transmission. Supported TSAs are: + +.B strict +- for strict priority, where traffic in higher-numbered TCs always takes +precedence over traffic in lower-numbered TCs. +.br +.B ets +- for Enhanced Traffic Selection, where available bandwidth is distributed among +the ETS-enabled TCs according to the weights set by +.B tc-bw +and +.B reco-tc-bw\fR, +respectively. +.br +.B cbs +- for Credit Based Shaper, where traffic is scheduled in a strict manner up to +the limit set by a shaper. +.br +.B vendor +- for vendor-specific traffic selection algorithm. + +.TP +.B tc-bw \fIBW-MAP +.TQ +.B reco-tc-bw \fIBW-MAP +\fIBW-MAP\fR uses the array parameter syntax, see +.BR dcb (8) +for details. Keys are TCs, values are integers representing percent of available +bandwidth given to the traffic class in question. The value should be 0 for TCs +whose TSA is not \fBets\fR, and the sum of all values shall be 100. As an +exception to the standard wording, a configuration with no \fBets\fR TCs is +permitted to sum up to 0 instead. +.br + +.TP +.B pg-bw \fIBW-MAP +The precise meaning of \fBpg-bw\fR is not standardized, but the assumption seems +to be that the same scheduling process as on the transmit side is applicable on +receive side as well, and configures receive bandwidth allocation for \fBets\fR +ingress traffic classes (priority groups). + +.SH EXAMPLE & USAGE + +Configure ETS priomap in a one-to-one fashion: + +.P +# dcb ets set dev eth0 prio-tc 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 + +Set TSA and transmit bandwidth configuration: + +.P +# dcb ets set dev eth0 tc-tsa all:strict 0:ets 1:ets 2:ets \\ +.br + tc-bw all:0 0:33 1:33 2:34 + +Show what was set: + +.P +# dcb ets show dev eth0 prio-tc tc-tsa tc-bw +.br +prio-tc 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 +.br +tc-tsa 0:ets 1:ets 2:ets 3:strict 4:strict 5:strict 6:strict 7:strict +.br +tc-bw 0:33 1:33 2:34 3:0 4:0 5:0 6:0 7:0 + +.SH EXIT STATUS +Exit status is 0 if command was successful or a positive integer upon failure. + +.SH SEE ALSO +.BR dcb (8) + +.SH REPORTING BUGS +Report any bugs to the Network Developers mailing list +.B +where the development and maintenance is primarily done. +You do not have to be subscribed to the list to send a message there. + +.SH AUTHOR +Petr Machata diff --git a/man/man8/dcb.8 b/man/man8/dcb.8 new file mode 100644 index 00000000..f318435c --- /dev/null +++ b/man/man8/dcb.8 @@ -0,0 +1,114 @@ +.TH DCB 8 "19 October 2020" "iproute2" "Linux" +.SH NAME +dcb \- show / manipulate DCB (Data Center Bridging) settings +.SH SYNOPSIS +.sp +.ad l +.in +8 + +.ti -8 +.B dcb +.RI "[ " OPTIONS " ] " +.B ets +.RI "{ " COMMAND " | " help " }" +.sp + +.ti -8 +.B dcb +.RB "[ " -force " ] " +.BI "-batch " filename +.sp + +.ti -8 +.B dcb +.RI "[ " OPTIONS " ] " +.B help +.sp + +.SH OPTIONS + +.TP +.BR "\-V" , " --Version" +Print the version of the +.B dcb +utility and exit. + +.TP +.BR "\-b", " --batch " +Read commands from provided file or standard input and invoke them. First +failure will cause termination of dcb. + +.TP +.BR "\-f", " --force" +Don't terminate dcb on errors in batch mode. If there were any errors during +execution of the commands, the application return code will be non zero. + +.TP +.BR "\-j" , " --json" +Generate JSON output. + +.TP +.BR "\-p" , " --pretty" +When combined with -j generate a pretty JSON output. + +.SH OBJECTS + +.TP +.B ets +- Configuration of ETS (Enhanced Transmission Selection) + +.SH COMMANDS + +A \fICOMMAND\fR specifies the action to perform on the object. The set of +possible actions depends on the object type. As a rule, it is possible to +.B show +objects and to invoke topical +.B help, +which prints a list of available commands and argument syntax conventions. + +.SH ARRAY PARAMETERS + +Like commands, specification of parameters is in the domain of individual +objects (and their commands) as well. However, much of the DCB interface +revolves around arrays of fixed size that specify one value per some key, such +as per traffic class or per priority. There is therefore a single syntax for +adjusting elements of these arrays. It consists of a series of +\fIKEY\fB:\fIVALUE\fR pairs, where the meaning of the individual keys and values +depends on the parameter. + +The elements are evaluated in order from left to right, and the latter ones +override the earlier ones. The elements that are not specified on the command +line are queried from the kernel and their current value is retained. + +As an example, take a made-up parameter tc-juju, which can be set to charm +traffic in a given TC with either good luck or bad luck. \fIKEY\fR can therefore +be 0..7 (as is usual for TC numbers in DCB), and \fIVALUE\fR either of +\fBnone\fR, \fBgood\fR, and \fBbad\fR. An example of changing a juju value of +TCs 0 and 7, while leaving all other intact, would then be: + +.P +# dcb foo set dev eth0 tc-juju 0:good 7:bad + +A special key, \fBall\fR, is recognized which sets the same value to all array +elements. This can be combined with the usual single-element syntax. E.g. in the +following, the juju of all keys is set to \fBnone\fR, except 0 and 7, which have +other values: + +.P +# dcb foo set dev eth0 tc-juju all:none 0:good 7:bad + +.SH EXIT STATUS +Exit status is 0 if command was successful or a positive integer upon failure. + +.SH SEE ALSO +.BR dcb-ets (8) +.br + +.SH REPORTING BUGS +Report any bugs to the Network Developers mailing list +.B +where the development and maintenance is primarily done. +You do not have to be subscribed to the list to send a message there. + +.SH AUTHOR +Petr Machata diff --git a/rdma/dev.c b/rdma/dev.c index a11081b8..c684dde4 100644 --- a/rdma/dev.c +++ b/rdma/dev.c @@ -159,7 +159,7 @@ static void dev_print_dim_setting(struct rd *rd, struct nlattr **tb) if (dim_setting > 1) return; - print_on_off(rd, "adaptive-moderation", dim_setting); + print_on_off(PRINT_ANY, "adaptive-moderation", "adaptive-moderation %s ", dim_setting); } diff --git a/rdma/rdma.c b/rdma/rdma.c index 9ea2d17f..8dc2d3e3 100644 --- a/rdma/rdma.c +++ b/rdma/rdma.c @@ -41,40 +41,16 @@ static int rd_cmd(struct rd *rd, int argc, char **argv) return rd_exec_cmd(rd, cmds, "object"); } +static int rd_batch_cmd(int argc, char *argv[], void *data) +{ + struct rd *rd = data; + + return rd_cmd(rd, argc, argv); +} + static int rd_batch(struct rd *rd, const char *name, bool force) { - char *line = NULL; - size_t len = 0; - int ret = 0; - - if (name && strcmp(name, "-") != 0) { - if (!freopen(name, "r", stdin)) { - pr_err("Cannot open file \"%s\" for reading: %s\n", - name, strerror(errno)); - return errno; - } - } - - cmdlineno = 0; - while (getcmdline(&line, &len, stdin) != -1) { - char *largv[512]; - int largc; - - largc = makeargs(line, largv, ARRAY_SIZE(largv)); - if (!largc) - continue; /* blank line */ - - ret = rd_cmd(rd, largc, largv); - if (ret) { - pr_err("Command failed %s:%d\n", name, cmdlineno); - if (!force) - break; - } - } - - free(line); - - return ret; + return do_batch(name, force, rd_batch_cmd, rd); } static int rd_init(struct rd *rd, char *filename) diff --git a/rdma/rdma.h b/rdma/rdma.h index a6c6bdea..8638b2a3 100644 --- a/rdma/rdma.h +++ b/rdma/rdma.h @@ -138,7 +138,6 @@ void print_driver_table(struct rd *rd, struct nlattr *tb); void print_raw_data(struct rd *rd, struct nlattr **nla_line); void newline(struct rd *rd); void newline_indent(struct rd *rd); -void print_on_off(struct rd *rd, const char *key_str, bool on); void print_raw_data(struct rd *rd, struct nlattr **nla_line); #define MAX_LINE_LENGTH 80 diff --git a/rdma/res-cq.c b/rdma/res-cq.c index 313f929a..9e7c4f51 100644 --- a/rdma/res-cq.c +++ b/rdma/res-cq.c @@ -36,7 +36,7 @@ static void print_cq_dim_setting(struct rd *rd, struct nlattr *attr) if (dim_setting > 1) return; - print_on_off(rd, "adaptive-moderation", dim_setting); + print_on_off(PRINT_ANY, "adaptive-moderation", "adaptive-moderation %s ", dim_setting); } static int res_cq_line_raw(struct rd *rd, const char *name, int idx, diff --git a/rdma/utils.c b/rdma/utils.c index 4d3de4fa..2a201aa4 100644 --- a/rdma/utils.c +++ b/rdma/utils.c @@ -781,11 +781,6 @@ static int print_driver_string(struct rd *rd, const char *key_str, return 0; } -void print_on_off(struct rd *rd, const char *key_str, bool on) -{ - print_driver_string(rd, key_str, (on) ? "on":"off"); -} - static int print_driver_s32(struct rd *rd, const char *key_str, int32_t val, enum rdma_nldev_print_type print_type) { diff --git a/tc/tc.c b/tc/tc.c index bdd5d4fa..af9b21da 100644 --- a/tc/tc.c +++ b/tc/tc.c @@ -233,22 +233,16 @@ static int do_cmd(int argc, char **argv) return -1; } +static int tc_batch_cmd(int argc, char *argv[], void *data) +{ + return do_cmd(argc, argv); +} + static int batch(const char *name) { - char *line = NULL; - size_t len = 0; - int ret = 0; + int ret; batch_mode = 1; - if (name && strcmp(name, "-") != 0) { - if (freopen(name, "r", stdin) == NULL) { - fprintf(stderr, - "Cannot open file \"%s\" for reading: %s\n", - name, strerror(errno)); - return -1; - } - } - tc_core_init(); if (rtnl_open(&rth, 0) < 0) { @@ -256,26 +250,8 @@ static int batch(const char *name) return -1; } - cmdlineno = 0; - while (getcmdline(&line, &len, stdin) != -1) { - char *largv[100]; - int largc; + ret = do_batch(name, force, tc_batch_cmd, NULL); - largc = makeargs(line, largv, 100); - if (largc == 0) - continue; /* blank line */ - - if (do_cmd(largc, largv)) { - fprintf(stderr, "Command failed %s:%d\n", - name, cmdlineno); - ret = 1; - if (!force) - break; - } - fflush(stdout); - } - - free(line); rtnl_close(&rth); return ret; }