iproute2/tc/tc_class.c
Serhey Popovych fe99adbca4 utils: Introduce and use nodev() helper routine
There is a couple of places where we report error in case of no network
device is found. In all of them we output message in the same format to
stderr and either return -1 or 1 to the caller or exit with -1.

Introduce new helper function nodev() that takes name of the network
device caused error and returns -1 to it's caller. Either call exit()
or return to the caller to preserve behaviour before change.

Use -nodev() in traffic control (tc) code to return 1.

Simplify expression for checking for argument being 0/NULL in @if
statement.

Signed-off-by: Serhey Popovych <serhe.popovych@gmail.com>
2018-03-11 17:58:36 -07:00

490 lines
12 KiB
C

/*
* tc_class.c "tc class".
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <math.h>
#include "utils.h"
#include "tc_util.h"
#include "tc_common.h"
#include "list.h"
struct graph_node {
struct hlist_node hlist;
__u32 id;
__u32 parent_id;
struct graph_node *parent_node;
struct graph_node *right_node;
void *data;
int data_len;
int nodes_count;
};
static struct hlist_head cls_list = {};
static struct hlist_head root_cls_list = {};
static void usage(void);
static void usage(void)
{
fprintf(stderr, "Usage: tc class [ add | del | change | replace | show ] dev STRING\n");
fprintf(stderr, " [ classid CLASSID ] [ root | parent CLASSID ]\n");
fprintf(stderr, " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n");
fprintf(stderr, "\n");
fprintf(stderr, " tc class show [ dev STRING ] [ root | parent CLASSID ]\n");
fprintf(stderr, "Where:\n");
fprintf(stderr, "QDISC_KIND := { prio | cbq | etc. }\n");
fprintf(stderr, "OPTIONS := ... try tc class add <desired QDISC_KIND> help\n");
}
static int tc_class_modify(int cmd, unsigned int flags, int argc, char **argv)
{
struct {
struct nlmsghdr n;
struct tcmsg t;
char buf[4096];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
.n.nlmsg_flags = NLM_F_REQUEST | flags,
.n.nlmsg_type = cmd,
.t.tcm_family = AF_UNSPEC,
};
struct qdisc_util *q = NULL;
struct tc_estimator est = {};
char d[IFNAMSIZ] = {};
char k[FILTER_NAMESZ] = {};
while (argc > 0) {
if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (d[0])
duparg("dev", *argv);
strncpy(d, *argv, sizeof(d)-1);
} else if (strcmp(*argv, "classid") == 0) {
__u32 handle;
NEXT_ARG();
if (req.t.tcm_handle)
duparg("classid", *argv);
if (get_tc_classid(&handle, *argv))
invarg("invalid class ID", *argv);
req.t.tcm_handle = handle;
} else if (strcmp(*argv, "handle") == 0) {
fprintf(stderr, "Error: try \"classid\" instead of \"handle\"\n");
return -1;
} else if (strcmp(*argv, "root") == 0) {
if (req.t.tcm_parent) {
fprintf(stderr, "Error: \"root\" is duplicate parent ID.\n");
return -1;
}
req.t.tcm_parent = TC_H_ROOT;
} else if (strcmp(*argv, "parent") == 0) {
__u32 handle;
NEXT_ARG();
if (req.t.tcm_parent)
duparg("parent", *argv);
if (get_tc_classid(&handle, *argv))
invarg("invalid parent ID", *argv);
req.t.tcm_parent = handle;
} else if (matches(*argv, "estimator") == 0) {
if (parse_estimator(&argc, &argv, &est))
return -1;
} else if (matches(*argv, "help") == 0) {
usage();
} else {
strncpy(k, *argv, sizeof(k)-1);
q = get_qdisc_kind(k);
argc--; argv++;
break;
}
argc--; argv++;
}
if (k[0])
addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
if (est.ewma_log)
addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
if (q) {
if (q->parse_copt == NULL) {
fprintf(stderr, "Error: Qdisc \"%s\" is classless.\n", k);
return 1;
}
if (q->parse_copt(q, argc, argv, &req.n, d))
return 1;
} else {
if (argc) {
if (matches(*argv, "help") == 0)
usage();
fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc class help\".", *argv);
return -1;
}
}
if (d[0]) {
ll_init_map(&rth);
req.t.tcm_ifindex = ll_name_to_index(d);
if (!req.t.tcm_ifindex)
return -nodev(d);
}
if (rtnl_talk(&rth, &req.n, NULL) < 0)
return 2;
return 0;
}
int filter_ifindex;
__u32 filter_qdisc;
__u32 filter_classid;
static void graph_node_add(__u32 parent_id, __u32 id, void *data,
int len)
{
struct graph_node *node = calloc(1, sizeof(struct graph_node));
node->id = id;
node->parent_id = parent_id;
if (data && len) {
node->data = malloc(len);
node->data_len = len;
memcpy(node->data, data, len);
}
if (parent_id == TC_H_ROOT)
hlist_add_head(&node->hlist, &root_cls_list);
else
hlist_add_head(&node->hlist, &cls_list);
}
static void graph_indent(char *buf, struct graph_node *node, int is_newline,
int add_spaces)
{
char spaces[100] = {0};
while (node && node->parent_node) {
node->parent_node->right_node = node;
node = node->parent_node;
}
while (node && node->right_node) {
if (node->hlist.next)
strcat(buf, "| ");
else
strcat(buf, " ");
node = node->right_node;
}
if (is_newline) {
if (node->hlist.next && node->nodes_count)
strcat(buf, "| |");
else if (node->hlist.next)
strcat(buf, "| ");
else if (node->nodes_count)
strcat(buf, " |");
else if (!node->hlist.next)
strcat(buf, " ");
}
if (add_spaces > 0) {
sprintf(spaces, "%-*s", add_spaces, "");
strcat(buf, spaces);
}
}
static void graph_cls_show(FILE *fp, char *buf, struct hlist_head *root_list,
int level)
{
struct hlist_node *n, *tmp_cls;
char cls_id_str[256] = {};
struct rtattr *tb[TCA_MAX + 1];
struct qdisc_util *q;
char str[100] = {};
hlist_for_each_safe(n, tmp_cls, root_list) {
struct hlist_node *c, *tmp_chld;
struct hlist_head children = {};
struct graph_node *cls = container_of(n, struct graph_node,
hlist);
hlist_for_each_safe(c, tmp_chld, &cls_list) {
struct graph_node *child = container_of(c,
struct graph_node, hlist);
if (cls->id == child->parent_id) {
hlist_del(c);
hlist_add_head(c, &children);
cls->nodes_count++;
child->parent_node = cls;
}
}
graph_indent(buf, cls, 0, 0);
print_tc_classid(cls_id_str, sizeof(cls_id_str), cls->id);
sprintf(str, "+---(%s)", cls_id_str);
strcat(buf, str);
parse_rtattr(tb, TCA_MAX, (struct rtattr *)cls->data,
cls->data_len);
if (tb[TCA_KIND] == NULL) {
strcat(buf, " [unknown qdisc kind] ");
} else {
const char *kind = rta_getattr_str(tb[TCA_KIND]);
sprintf(str, " %s ", kind);
strcat(buf, str);
fprintf(fp, "%s", buf);
buf[0] = '\0';
q = get_qdisc_kind(kind);
if (q && q->print_copt) {
q->print_copt(q, fp, tb[TCA_OPTIONS]);
}
if (q && show_stats) {
int cls_indent = strlen(q->id) - 2 +
strlen(cls_id_str);
struct rtattr *stats = NULL;
graph_indent(buf, cls, 1, cls_indent);
if (tb[TCA_STATS] || tb[TCA_STATS2]) {
fprintf(fp, "\n");
print_tcstats_attr(fp, tb, buf, &stats);
buf[0] = '\0';
}
if (cls->hlist.next || cls->nodes_count) {
strcat(buf, "\n");
graph_indent(buf, cls, 1, 0);
}
}
}
free(cls->data);
fprintf(fp, "%s\n", buf);
buf[0] = '\0';
graph_cls_show(fp, buf, &children, level + 1);
if (!cls->hlist.next) {
graph_indent(buf, cls, 0, 0);
strcat(buf, "\n");
}
fprintf(fp, "%s", buf);
buf[0] = '\0';
free(cls);
}
}
int print_class(const struct sockaddr_nl *who,
struct nlmsghdr *n, void *arg)
{
FILE *fp = (FILE *)arg;
struct tcmsg *t = NLMSG_DATA(n);
int len = n->nlmsg_len;
struct rtattr *tb[TCA_MAX + 1];
struct qdisc_util *q;
char abuf[256];
if (n->nlmsg_type != RTM_NEWTCLASS && n->nlmsg_type != RTM_DELTCLASS) {
fprintf(stderr, "Not a class\n");
return 0;
}
len -= NLMSG_LENGTH(sizeof(*t));
if (len < 0) {
fprintf(stderr, "Wrong len %d\n", len);
return -1;
}
if (show_graph) {
graph_node_add(t->tcm_parent, t->tcm_handle, TCA_RTA(t), len);
return 0;
}
if (filter_qdisc && TC_H_MAJ(t->tcm_handle^filter_qdisc))
return 0;
if (filter_classid && t->tcm_handle != filter_classid)
return 0;
parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
if (tb[TCA_KIND] == NULL) {
fprintf(stderr, "print_class: NULL kind\n");
return -1;
}
if (n->nlmsg_type == RTM_DELTCLASS)
fprintf(fp, "deleted ");
abuf[0] = 0;
if (t->tcm_handle) {
if (filter_qdisc)
print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_handle));
else
print_tc_classid(abuf, sizeof(abuf), t->tcm_handle);
}
fprintf(fp, "class %s %s ", rta_getattr_str(tb[TCA_KIND]), abuf);
if (filter_ifindex == 0)
fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
if (t->tcm_parent == TC_H_ROOT)
fprintf(fp, "root ");
else {
if (filter_qdisc)
print_tc_classid(abuf, sizeof(abuf), TC_H_MIN(t->tcm_parent));
else
print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
fprintf(fp, "parent %s ", abuf);
}
if (t->tcm_info)
fprintf(fp, "leaf %x: ", t->tcm_info>>16);
q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
if (tb[TCA_OPTIONS]) {
if (q && q->print_copt)
q->print_copt(q, fp, tb[TCA_OPTIONS]);
else
fprintf(fp, "[cannot parse class parameters]");
}
fprintf(fp, "\n");
if (show_stats) {
struct rtattr *xstats = NULL;
if (tb[TCA_STATS] || tb[TCA_STATS2]) {
print_tcstats_attr(fp, tb, " ", &xstats);
fprintf(fp, "\n");
}
if (q && (xstats || tb[TCA_XSTATS]) && q->print_xstats) {
q->print_xstats(q, fp, xstats ? : tb[TCA_XSTATS]);
fprintf(fp, "\n");
}
}
fflush(fp);
return 0;
}
static int tc_class_list(int argc, char **argv)
{
struct tcmsg t = { .tcm_family = AF_UNSPEC };
char d[IFNAMSIZ] = {};
char buf[1024] = {0};
filter_qdisc = 0;
filter_classid = 0;
while (argc > 0) {
if (strcmp(*argv, "dev") == 0) {
NEXT_ARG();
if (d[0])
duparg("dev", *argv);
strncpy(d, *argv, sizeof(d)-1);
} else if (strcmp(*argv, "qdisc") == 0) {
NEXT_ARG();
if (filter_qdisc)
duparg("qdisc", *argv);
if (get_qdisc_handle(&filter_qdisc, *argv))
invarg("invalid qdisc ID", *argv);
} else if (strcmp(*argv, "classid") == 0) {
NEXT_ARG();
if (filter_classid)
duparg("classid", *argv);
if (get_tc_classid(&filter_classid, *argv))
invarg("invalid class ID", *argv);
} else if (strcmp(*argv, "root") == 0) {
if (t.tcm_parent) {
fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
return -1;
}
t.tcm_parent = TC_H_ROOT;
} else if (strcmp(*argv, "parent") == 0) {
__u32 handle;
if (t.tcm_parent)
duparg("parent", *argv);
NEXT_ARG();
if (get_tc_classid(&handle, *argv))
invarg("invalid parent ID", *argv);
t.tcm_parent = handle;
} else if (matches(*argv, "help") == 0) {
usage();
} else {
fprintf(stderr, "What is \"%s\"? Try \"tc class help\".\n", *argv);
return -1;
}
argc--; argv++;
}
ll_init_map(&rth);
if (d[0]) {
t.tcm_ifindex = ll_name_to_index(d);
if (!t.tcm_ifindex)
return -nodev(d);
filter_ifindex = t.tcm_ifindex;
}
if (rtnl_dump_request(&rth, RTM_GETTCLASS, &t, sizeof(t)) < 0) {
perror("Cannot send dump request");
return 1;
}
if (rtnl_dump_filter(&rth, print_class, stdout) < 0) {
fprintf(stderr, "Dump terminated\n");
return 1;
}
if (show_graph)
graph_cls_show(stdout, &buf[0], &root_cls_list, 0);
return 0;
}
int do_class(int argc, char **argv)
{
if (argc < 1)
return tc_class_list(0, NULL);
if (matches(*argv, "add") == 0)
return tc_class_modify(RTM_NEWTCLASS, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
if (matches(*argv, "change") == 0)
return tc_class_modify(RTM_NEWTCLASS, 0, argc-1, argv+1);
if (matches(*argv, "replace") == 0)
return tc_class_modify(RTM_NEWTCLASS, NLM_F_CREATE, argc-1, argv+1);
if (matches(*argv, "delete") == 0)
return tc_class_modify(RTM_DELTCLASS, 0, argc-1, argv+1);
#if 0
if (matches(*argv, "get") == 0)
return tc_class_get(RTM_GETTCLASS, 0, argc-1, argv+1);
#endif
if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
|| matches(*argv, "lst") == 0)
return tc_class_list(argc-1, argv+1);
if (matches(*argv, "help") == 0) {
usage();
return 0;
}
fprintf(stderr, "Command \"%s\" is unknown, try \"tc class help\".\n", *argv);
return -1;
}