mirror of
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git
synced 2024-11-15 22:15:13 +08:00
fe99adbca4
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>
490 lines
12 KiB
C
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;
|
|
}
|