]> git.proxmox.com Git - mirror_iproute2.git/commitdiff
Add skeleton of a new tool, dcb
authorPetr Machata <me@pmachata.org>
Thu, 12 Nov 2020 22:24:47 +0000 (23:24 +0100)
committerDavid Ahern <dsahern@gmail.com>
Sat, 14 Nov 2020 02:43:19 +0000 (19:43 -0700)
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>
Makefile
dcb/Makefile [new file with mode: 0644]
dcb/dcb.c [new file with mode: 0644]
dcb/dcb.h [new file with mode: 0644]
man/man8/dcb.8 [new file with mode: 0644]

index 5b040415a12bbf124a1998806bf8c69a749f164e..e64c65992585a657291318e85e779229bffe67b6 100644 (file)
--- 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/dcb/Makefile b/dcb/Makefile
new file mode 100644 (file)
index 0000000..9966c8f
--- /dev/null
@@ -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 (file)
index 0000000..1ec8c24
--- /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 (file)
index 0000000..a09d102
--- /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__ */
diff --git a/man/man8/dcb.8 b/man/man8/dcb.8
new file mode 100644 (file)
index 0000000..19433bf
--- /dev/null
@@ -0,0 +1,103 @@
+.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
+.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 " <FILENAME>
+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
+
+.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 <netdev@vger.kernel.org>
+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 <me@pmachata.org>