mirror of
https://git.proxmox.com/git/mirror_iproute2
synced 2025-10-18 23:50:12 +00:00
Merge branch 'dcb-tool' into next
Petr Machata says: ==================== 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. Currently a common libre tool for configuration of DCB is OpenLLDP. This suite contains a daemon that uses Linux DCB interface to configure HW according to the DCB TLVs exchanged over an interface. The daemon can also be controlled by a client, through which the user can adjust and view the configuration. The downside of using OpenLLDP is that it is somewhat heavyweight and difficult to use in scripts, and does not support extensions such as buffer and rate commands. For access to many HW features, one would be perfectly fine with a fire-and-forget tool along the lines of "ip" or "tc". For scripting in particular, this would be ideal. This author is aware of one such tool, mlnx_qos from Mellanox OFED scripts collection[1]. The downside here is that the tool is very verbose, the command line language is awkward to use, it is not packaged in Linux distros, and generally has the appearance of a very vendor-specific tool, despite not being one. This patchset addresses the above issues by providing a seed of a clean, well-documented, easily usable, extensible fire-and-forget tool for DCB configuration: # dcb ets set dev eni1np1 \ tc-tsa all:strict 0:ets 1:ets 2:ets \ tc-bw all:0 0:33 1:33 2:34 # dcb ets show dev eni1np1 tc-tsa tc-bw tc-tsa 0:ets 1:ets 2:ets 3:strict 4:strict 5:strict 6:strict 7:strict tc-bw 0:33 1:33 2:34 3:0 4:0 5:0 6:0 7:0 # dcb ets set dev eni1np1 tc-bw 1:30 2:37 # dcb -j ets show dev eni1np1 | jq '.tc_bw[2]' 37 The patchset proceeds as follows: - Many tools in iproute2 have an option to work in batch mode, where the commands to run are given in a file. The code to handle batching is largely the same independent of the tool in question. In patch #1, add a helper to handle the batching, and migrate individual tools to use it. - A number of configuration options come in a form of an on-off switch. This in turn can be considered a special case of parsing one of a given set of strings. In patch #2, extract helpers to parse one of a number of strings, on top of which build an on-off parser. Currently each tool open-codes the logic to parse the on-off toggle. A future patch set will migrate instances of this code over to the new helpers. - The on/off toggles from previous list item sometimes need to be dumped. While in the FP output, one typically wishes to maintain consistency with the command line and show actual strings, "on" and "off", in JSON output one would rather use booleans. This logic is somewhat annoying to have to open-code time and again. Therefore in patch #3, add a helper to do just that. - The DCB tool is built on top of libmnl. Several routines will be basically the same in DCB as they are currently in devlink. In patches #4-#6, extract them to a new module, mnl_utils, for easy reuse. - Much of DCB is built around arrays. A syntax similar to the iplink_vlan's ingress-qos-map / egress-qos-map is very handy for describing changes done to such arrays. Therefore in patch #7, extract a helper, parse_mapping(), which manages parsing of key-value arrays. In patch #8, fix a buglet in the helper, and in patch #9, extend it to allow setting of all array elements in one go. - In patch #10, add a skeleton of "dcb", which contains common helpers and dispatches to subtools for handling of individual objects. The skeleton is empty as of this patch. In patch #11, add "dcb_ets", a module for handling of specifically DCB ETS objects. The intention is to gradually add handlers for at least PFC, APP, peer configuration, buffers and rates. [1] https://github.com/Mellanox/mlnx-tools/tree/master/ofed_scripts ==================== Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
commit
1ed00380b0
2
Makefile
2
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)
|
||||
|
@ -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;
|
||||
|
24
dcb/Makefile
Normal file
24
dcb/Makefile
Normal file
@ -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)
|
416
dcb/dcb.c
Normal file
416
dcb/dcb.c
Normal file
@ -0,0 +1,416 @@
|
||||
// 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 := 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;
|
||||
}
|
43
dcb/dcb.h
Normal file
43
dcb/dcb.h
Normal file
@ -0,0 +1,43 @@
|
||||
/* 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);
|
||||
|
||||
/* dcb_ets.c */
|
||||
|
||||
int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv);
|
||||
|
||||
#endif /* __DCB_H__ */
|
435
dcb/dcb_ets.c
Normal file
435
dcb/dcb_ets.c
Normal file
@ -0,0 +1,435 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <linux/dcbnl.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ endif
|
||||
|
||||
all: $(TARGETS) $(LIBS)
|
||||
|
||||
devlink: $(DEVLINKOBJ)
|
||||
devlink: $(DEVLINKOBJ) $(LIBNETLINK)
|
||||
$(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
|
||||
|
||||
install: all
|
||||
|
@ -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)
|
||||
|
@ -14,11 +14,11 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <libmnl/libmnl.h>
|
||||
#include <linux/genetlink.h>
|
||||
|
||||
#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);
|
||||
|
@ -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)
|
||||
|
11
include/mnl_utils.h
Normal file
11
include/mnl_utils.h
Normal file
@ -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__ */
|
@ -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__ */
|
||||
|
46
ip/ip.c
46
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
110
lib/mnl_utils.c
Normal file
110
lib/mnl_utils.c
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* mnl_utils.c Helpers for working with libmnl.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <libmnl/libmnl.h>
|
||||
|
||||
#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;
|
||||
}
|
103
lib/utils.c
103
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;
|
||||
}
|
||||
|
188
man/man8/dcb-ets.8
Normal file
188
man/man8/dcb-ets.8
Normal file
@ -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 <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>
|
114
man/man8/dcb.8
Normal file
114
man/man8/dcb.8
Normal file
@ -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 " <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
|
||||
|
||||
.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 <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>
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
40
rdma/rdma.c
40
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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
{
|
||||
|
38
tc/tc.c
38
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user