mirror of
				https://git.proxmox.com/git/mirror_iproute2
				synced 2025-11-04 02:56:43 +00:00 
			
		
		
		
	ETS, for "Enhanced Transmission Selection", is a set of configurations that
permit configuration of mapping of priorities to traffic classes, traffic
selection algorithm to use per traffic class, bandwidth allocation, etc.
Add a dcb subtool to allow showing and tweaking of individual ETS
configuration options. For example:
    # dcb ets show dev eni1np1
    willing on ets_cap 8 cbs off
    tc-bw 0:0 1:0 2:0 3:0 4:100 5:0 6:0 7:0
    pg-bw 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
    tc-tsa 0:strict 1:strict 2:strict 3:strict 4:ets 5:strict 6:strict 7:strict
    prio-tc 0:1 1:3 2:5 3:0 4:0 5:0 6:0 7:0
    reco-tc-bw 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
    reco-tc-tsa 0:strict 1:strict 2:strict 3:strict 4:strict 5:strict 6:strict 7:strict
    reco-prio-tc 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
Signed-off-by: Petr Machata <me@pmachata.org>
Signed-off-by: David Ahern <dsahern@gmail.com>
		
	
			
		
			
				
	
	
		
			417 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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;
 | 
						|
}
 |