summaryrefslogtreecommitdiff
path: root/dcb
diff options
context:
space:
mode:
authorPetr Machata <me@pmachata.org>2020-11-12 23:24:47 +0100
committerDavid Ahern <dsahern@gmail.com>2020-11-13 19:43:19 -0700
commit67033d1c1c8a5466c99c782e6cc71d8b7e0a57d2 (patch)
tree395ab896df431d1a921e9177b07abe7446225d63 /dcb
parent66a2d7148713d4e168b8a596d1eef03d4e9d4826 (diff)
downloadiproute2-67033d1c1c8a5466c99c782e6cc71d8b7e0a57d2.tar.gz
Add skeleton of a new tool, dcb
The Linux DCB interface allows configuration of a broad range of hardware-specific attributes, such as TC scheduling, flow control, per-port buffer configuration, TC rate, etc. Add a new tool to show that configuration and tweak it. DCB allows configuration of several objects, and possibly could expand to pre-standard CEE interfaces. Therefore the tool itself is a lean shell that dispatches to subtools each dedicated to one of the objects. Signed-off-by: Petr Machata <me@pmachata.org> Signed-off-by: David Ahern <dsahern@gmail.com>
Diffstat (limited to 'dcb')
-rw-r--r--dcb/Makefile24
-rw-r--r--dcb/dcb.c414
-rw-r--r--dcb/dcb.h39
3 files changed, 477 insertions, 0 deletions
diff --git a/dcb/Makefile b/dcb/Makefile
new file mode 100644
index 00000000..9966c8f0
--- /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
+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..1ec8c24e
--- /dev/null
+++ b/dcb/dcb.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <stdio.h>
+#include <linux/dcbnl.h>
+#include <libmnl/libmnl.h>
+#include <getopt.h>
+
+#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 :=\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;
+ }
+
+ 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..a09d102a
--- /dev/null
+++ b/dcb/dcb.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DCB_H__
+#define __DCB_H__ 1
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* 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);
+
+#endif /* __DCB_H__ */