iproute2/tc/m_action.c
Baowen Zheng a93c90c7f2 tc: separate action print for filter and action dump
We need to separate action print for filter and action dump since
in action dump, we need to print hardware status and flags. But in
filter dump, we do not need to print action hardware status and
hardware related flags.

In filter dump, actions hardware status should be same with filter.
so we will not print action hardware status in this case.

Action print for action dump:
  action order 0:  police 0xff000100 rate 0bit burst 0b mtu 64Kb pkts_rate 50000 pkts_burst 10000 action drop/pipe overhead 0b linklayer unspec
  ref 4 bind 3  installed 666 sec used 0 sec firstused 106 sec
  Action statistics:
  Sent 7634140154 bytes 5109889 pkt (dropped 0, overlimits 0 requeues 0)
  Sent software 84 bytes 3 pkt
  Sent hardware 7634140070 bytes 5109886 pkt
  backlog 0b 0p requeues 0
  in_hw in_hw_count 1
  used_hw_stats delayed

Action print for filter dump:
  action order 1:  police 0xff000100 rate 0bit burst 0b mtu 64Kb pkts_rate 50000 pkts_burst 10000 action drop/pipe overhead 0b linklayer unspec
  ref 4 bind 3  installed 680 sec used 0 sec firstused 119 sec
  Action statistics:
  Sent 8627975846 bytes 5775107 pkt (dropped 0, overlimits 0 requeues 0)
  Sent software 84 bytes 3 pkt
  Sent hardware 8627975762 bytes 5775104 pkt
  backlog 0b 0p requeues 0
  used_hw_stats delayed

Signed-off-by: Baowen Zheng <baowen.zheng@corigine.com>
Signed-off-by: Simon Horman <simon.horman@corigine.com>
Reviewed-by: Roi Dayan <roid@nvidia.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
2022-03-07 15:45:41 -07:00

912 lines
20 KiB
C

/*
* m_action.c Action Management
*
* This program is free software; you can distribute 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: J Hadi Salim (hadi@cyberus.ca)
*
* TODO:
* - parse to be passed a filedescriptor for logging purposes
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <dlfcn.h>
#include "utils.h"
#include "tc_common.h"
#include "tc_util.h"
static struct action_util *action_list;
#ifdef CONFIG_GACT
static int gact_ld; /* f*ckin backward compatibility */
#endif
static int tab_flush;
static void act_usage(void)
{
/*XXX: In the near future add a action->print_help to improve
* usability
* This would mean new tc will not be backward compatible
* with any action .so from the old days. But if someone really
* does that, they would know how to fix this ..
*
*/
fprintf(stderr,
"usage: tc actions <ACTSPECOP>*\n"
"Where: ACTSPECOP := ACR | GD | FL\n"
" ACR := add | change | replace <ACTSPEC>*\n"
" GD := get | delete | <ACTISPEC>*\n"
" FL := ls | list | flush | <ACTNAMESPEC>\n"
" ACTNAMESPEC := action <ACTNAME>\n"
" ACTISPEC := <ACTNAMESPEC> <INDEXSPEC>\n"
" ACTSPEC := action <ACTDETAIL> [INDEXSPEC] [HWSTATSSPEC] [SKIPSPEC]\n"
" INDEXSPEC := index <32 bit indexvalue>\n"
" HWSTATSSPEC := hw_stats [ immediate | delayed | disabled ]\n"
" SKIPSPEC := [ skip_sw | skip_hw ]\n"
" ACTDETAIL := <ACTNAME> <ACTPARAMS>\n"
" Example ACTNAME is gact, mirred, bpf, etc\n"
" Each action has its own parameters (ACTPARAMS)\n"
"\n");
exit(-1);
}
static int print_noaopt(struct action_util *au, FILE *f, struct rtattr *opt)
{
if (opt && RTA_PAYLOAD(opt))
fprintf(f, "[Unknown action, optlen=%u] ",
(unsigned int) RTA_PAYLOAD(opt));
return 0;
}
static int parse_noaopt(struct action_util *au, int *argc_p,
char ***argv_p, int code, struct nlmsghdr *n)
{
int argc = *argc_p;
char **argv = *argv_p;
if (argc)
fprintf(stderr,
"Unknown action \"%s\", hence option \"%s\" is unparsable\n",
au->id, *argv);
else
fprintf(stderr, "Unknown action \"%s\"\n", au->id);
return -1;
}
static struct action_util *get_action_kind(char *str)
{
static void *aBODY;
void *dlh;
char buf[256];
struct action_util *a;
#ifdef CONFIG_GACT
int looked4gact = 0;
restart_s:
#endif
for (a = action_list; a; a = a->next) {
if (strcmp(a->id, str) == 0)
return a;
}
snprintf(buf, sizeof(buf), "%s/m_%s.so", get_tc_lib(), str);
dlh = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);
if (dlh == NULL) {
dlh = aBODY;
if (dlh == NULL) {
dlh = aBODY = dlopen(NULL, RTLD_LAZY);
if (dlh == NULL)
goto noexist;
}
}
snprintf(buf, sizeof(buf), "%s_action_util", str);
a = dlsym(dlh, buf);
if (a == NULL)
goto noexist;
reg:
a->next = action_list;
action_list = a;
return a;
noexist:
#ifdef CONFIG_GACT
if (!looked4gact) {
looked4gact = 1;
strcpy(str, "gact");
goto restart_s;
}
#endif
a = calloc(1, sizeof(*a));
if (a) {
strncpy(a->id, "noact", 15);
a->parse_aopt = parse_noaopt;
a->print_aopt = print_noaopt;
goto reg;
}
return a;
}
static bool
new_cmd(char **argv)
{
return (matches(*argv, "change") == 0) ||
(matches(*argv, "replace") == 0) ||
(matches(*argv, "delete") == 0) ||
(matches(*argv, "get") == 0) ||
(matches(*argv, "add") == 0);
}
static const struct hw_stats_item {
const char *str;
__u8 type;
} hw_stats_items[] = {
{ "immediate", TCA_ACT_HW_STATS_IMMEDIATE },
{ "delayed", TCA_ACT_HW_STATS_DELAYED },
{ "disabled", 0 }, /* no bit set */
};
static void print_hw_stats(const struct rtattr *arg, bool print_used)
{
struct nla_bitfield32 *hw_stats_bf = RTA_DATA(arg);
__u8 hw_stats;
int i;
hw_stats = hw_stats_bf->value & hw_stats_bf->selector;
print_string(PRINT_FP, NULL, "\t", NULL);
open_json_array(PRINT_ANY, print_used ? "used_hw_stats" : "hw_stats");
for (i = 0; i < ARRAY_SIZE(hw_stats_items); i++) {
const struct hw_stats_item *item;
item = &hw_stats_items[i];
if ((!hw_stats && !item->type) || hw_stats & item->type)
print_string(PRINT_ANY, NULL, " %s", item->str);
}
close_json_array(PRINT_JSON, NULL);
print_nl();
}
static int parse_hw_stats(const char *str, struct nlmsghdr *n)
{
int i;
for (i = 0; i < ARRAY_SIZE(hw_stats_items); i++) {
const struct hw_stats_item *item;
item = &hw_stats_items[i];
if (matches(str, item->str) == 0) {
struct nla_bitfield32 hw_stats_bf = {
.value = item->type,
.selector = item->type
};
addattr_l(n, MAX_MSG, TCA_ACT_HW_STATS,
&hw_stats_bf, sizeof(hw_stats_bf));
return 0;
}
}
return -1;
}
int parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n)
{
int argc = *argc_p;
char **argv = *argv_p;
struct rtattr *tail, *tail2;
char k[FILTER_NAMESZ];
int act_ck_len = 0;
int ok = 0;
int eap = 0; /* expect action parameters */
int ret = 0;
int prio = 0;
unsigned char act_ck[TC_COOKIE_MAX_SIZE];
if (argc <= 0)
return -1;
tail2 = addattr_nest(n, MAX_MSG, tca_id);
while (argc > 0) {
memset(k, 0, sizeof(k));
if (strcmp(*argv, "action") == 0) {
argc--;
argv++;
eap = 1;
#ifdef CONFIG_GACT
if (!gact_ld)
get_action_kind("gact");
#endif
continue;
} else if (strcmp(*argv, "flowid") == 0) {
break;
} else if (strcmp(*argv, "classid") == 0) {
break;
} else if (strcmp(*argv, "help") == 0) {
return -1;
} else if (new_cmd(argv)) {
goto done0;
} else {
struct action_util *a = NULL;
int skip_loop = 2;
__u32 flag = 0;
if (!action_a2n(*argv, NULL, false))
strncpy(k, "gact", sizeof(k) - 1);
else
strncpy(k, *argv, sizeof(k) - 1);
eap = 0;
if (argc > 0) {
a = get_action_kind(k);
} else {
done0:
if (ok)
break;
else
goto done;
}
if (a == NULL)
goto bad_val;
tail = addattr_nest(n, MAX_MSG, ++prio);
addattr_l(n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
ret = a->parse_aopt(a, &argc, &argv,
TCA_ACT_OPTIONS | NLA_F_NESTED,
n);
if (ret < 0) {
fprintf(stderr, "bad action parsing\n");
goto bad_val;
}
if (*argv && strcmp(*argv, "cookie") == 0) {
size_t slen;
NEXT_ARG();
slen = strlen(*argv);
if (slen > TC_COOKIE_MAX_SIZE * 2) {
char cookie_err_m[128];
snprintf(cookie_err_m, 128,
"%zd Max allowed size %d",
slen, TC_COOKIE_MAX_SIZE*2);
invarg(cookie_err_m, *argv);
}
if (slen % 2 ||
hex2mem(*argv, act_ck, slen / 2) < 0)
invarg("cookie must be a hex string\n",
*argv);
act_ck_len = slen / 2;
argc--;
argv++;
}
if (act_ck_len)
addattr_l(n, MAX_MSG, TCA_ACT_COOKIE,
&act_ck, act_ck_len);
if (*argv && matches(*argv, "hw_stats") == 0) {
NEXT_ARG();
ret = parse_hw_stats(*argv, n);
if (ret < 0)
invarg("value is invalid\n", *argv);
NEXT_ARG_FWD();
}
if (*argv && strcmp(*argv, "no_percpu") == 0) {
flag |= TCA_ACT_FLAGS_NO_PERCPU_STATS;
NEXT_ARG_FWD();
}
/* we need to parse twice to fix skip flag out of order */
while (skip_loop--) {
if (*argv && strcmp(*argv, "skip_sw") == 0) {
flag |= TCA_ACT_FLAGS_SKIP_SW;
NEXT_ARG_FWD();
} else if (*argv && strcmp(*argv, "skip_hw") == 0) {
flag |= TCA_ACT_FLAGS_SKIP_HW;
NEXT_ARG_FWD();
}
}
if (flag) {
struct nla_bitfield32 flags =
{ flag, flag };
addattr_l(n, MAX_MSG, TCA_ACT_FLAGS, &flags,
sizeof(struct nla_bitfield32));
}
addattr_nest_end(n, tail);
ok++;
}
}
if (eap > 0) {
fprintf(stderr, "bad action empty %d\n", eap);
goto bad_val;
}
addattr_nest_end(n, tail2);
done:
*argc_p = argc;
*argv_p = argv;
return 0;
bad_val:
/* no need to undo things, returning from here should
* cause enough pain
*/
fprintf(stderr, "parse_action: bad value (%d:%s)!\n", argc, *argv);
return -1;
}
static int tc_print_one_action(FILE *f, struct rtattr *arg, bool bind)
{
struct rtattr *tb[TCA_ACT_MAX + 1];
int err = 0;
struct action_util *a = NULL;
if (arg == NULL)
return -1;
parse_rtattr_nested(tb, TCA_ACT_MAX, arg);
if (tb[TCA_ACT_KIND] == NULL) {
fprintf(stderr, "NULL Action!\n");
return -1;
}
a = get_action_kind(RTA_DATA(tb[TCA_ACT_KIND]));
if (a == NULL)
return err;
err = a->print_aopt(a, f, tb[TCA_ACT_OPTIONS]);
if (err < 0)
return err;
if (brief && tb[TCA_ACT_INDEX]) {
print_uint(PRINT_ANY, "index", "\t index %u",
rta_getattr_u32(tb[TCA_ACT_INDEX]));
print_nl();
}
if (show_stats && tb[TCA_ACT_STATS]) {
print_string(PRINT_FP, NULL, "\tAction statistics:", NULL);
print_nl();
open_json_object("stats");
print_tcstats2_attr(f, tb[TCA_ACT_STATS], "\t", NULL);
close_json_object();
print_nl();
}
if (tb[TCA_ACT_COOKIE]) {
int strsz = RTA_PAYLOAD(tb[TCA_ACT_COOKIE]);
char b1[strsz * 2 + 1];
print_string(PRINT_ANY, "cookie", "\tcookie %s",
hexstring_n2a(RTA_DATA(tb[TCA_ACT_COOKIE]),
strsz, b1, sizeof(b1)));
print_nl();
}
if (tb[TCA_ACT_FLAGS] || tb[TCA_ACT_IN_HW_COUNT]) {
bool skip_hw = false;
bool newline = false;
if (tb[TCA_ACT_FLAGS]) {
struct nla_bitfield32 *flags = RTA_DATA(tb[TCA_ACT_FLAGS]);
if (flags->selector & TCA_ACT_FLAGS_NO_PERCPU_STATS) {
newline = true;
print_bool(PRINT_ANY, "no_percpu", "\tno_percpu",
flags->value &
TCA_ACT_FLAGS_NO_PERCPU_STATS);
}
if (!bind) {
if (flags->selector & TCA_ACT_FLAGS_SKIP_HW) {
newline = true;
print_bool(PRINT_ANY, "skip_hw", "\tskip_hw",
flags->value &
TCA_ACT_FLAGS_SKIP_HW);
skip_hw = !!(flags->value & TCA_ACT_FLAGS_SKIP_HW);
}
if (flags->selector & TCA_ACT_FLAGS_SKIP_SW) {
newline = true;
print_bool(PRINT_ANY, "skip_sw", "\tskip_sw",
flags->value &
TCA_ACT_FLAGS_SKIP_SW);
}
}
}
if (tb[TCA_ACT_IN_HW_COUNT] && !bind && !skip_hw) {
__u32 count = rta_getattr_u32(tb[TCA_ACT_IN_HW_COUNT]);
newline = true;
if (count) {
print_bool(PRINT_ANY, "in_hw", "\tin_hw",
true);
print_uint(PRINT_ANY, "in_hw_count",
" in_hw_count %u", count);
} else {
print_bool(PRINT_ANY, "not_in_hw",
"\tnot_in_hw", true);
}
}
if (newline)
print_nl();
}
if (tb[TCA_ACT_HW_STATS])
print_hw_stats(tb[TCA_ACT_HW_STATS], false);
if (tb[TCA_ACT_USED_HW_STATS])
print_hw_stats(tb[TCA_ACT_USED_HW_STATS], true);
return 0;
}
static int
tc_print_action_flush(FILE *f, const struct rtattr *arg)
{
struct rtattr *tb[TCA_MAX + 1];
int err = 0;
struct action_util *a = NULL;
__u32 *delete_count = 0;
parse_rtattr_nested(tb, TCA_MAX, arg);
if (tb[TCA_KIND] == NULL) {
fprintf(stderr, "NULL Action!\n");
return -1;
}
a = get_action_kind(RTA_DATA(tb[TCA_KIND]));
if (a == NULL)
return err;
delete_count = RTA_DATA(tb[TCA_FCNT]);
fprintf(f, " %s (%d entries)\n", a->id, *delete_count);
tab_flush = 0;
return 0;
}
static int
tc_dump_action(FILE *f, const struct rtattr *arg, unsigned short tot_acts,
bool bind)
{
int i;
if (arg == NULL)
return 0;
if (!tot_acts)
tot_acts = TCA_ACT_MAX_PRIO;
struct rtattr *tb[tot_acts + 1];
parse_rtattr_nested(tb, tot_acts, arg);
if (tab_flush && tb[0] && !tb[1])
return tc_print_action_flush(f, tb[0]);
open_json_array(PRINT_JSON, "actions");
for (i = 0; i <= tot_acts; i++) {
if (tb[i]) {
open_json_object(NULL);
print_nl();
print_uint(PRINT_ANY, "order",
"\taction order %u: ", i);
if (tc_print_one_action(f, tb[i], bind) < 0)
fprintf(stderr, "Error printing action\n");
close_json_object();
}
}
close_json_array(PRINT_JSON, NULL);
return 0;
}
int
tc_print_action(FILE *f, const struct rtattr *arg, unsigned short tot_acts)
{
return tc_dump_action(f, arg, tot_acts, true);
}
int print_action(struct nlmsghdr *n, void *arg)
{
FILE *fp = (FILE *)arg;
struct tcamsg *t = NLMSG_DATA(n);
int len = n->nlmsg_len;
__u32 *tot_acts = NULL;
struct rtattr *tb[TCA_ROOT_MAX+1];
len -= NLMSG_LENGTH(sizeof(*t));
if (len < 0) {
fprintf(stderr, "Wrong len %d\n", len);
return -1;
}
parse_rtattr(tb, TCA_ROOT_MAX, TA_RTA(t), len);
if (tb[TCA_ROOT_COUNT])
tot_acts = RTA_DATA(tb[TCA_ROOT_COUNT]);
open_json_object(NULL);
print_uint(PRINT_ANY, "total acts", "total acts %u",
tot_acts ? *tot_acts : 0);
print_nl();
close_json_object();
if (tb[TCA_ACT_TAB] == NULL) {
if (n->nlmsg_type != RTM_GETACTION)
fprintf(stderr, "print_action: NULL kind\n");
return -1;
}
if (n->nlmsg_type == RTM_DELACTION) {
if (n->nlmsg_flags & NLM_F_ROOT) {
fprintf(fp, "Flushed table ");
tab_flush = 1;
} else {
fprintf(fp, "Deleted action ");
}
}
if (n->nlmsg_type == RTM_NEWACTION) {
if ((n->nlmsg_flags & NLM_F_CREATE) &&
!(n->nlmsg_flags & NLM_F_REPLACE)) {
fprintf(fp, "Added action ");
} else if (n->nlmsg_flags & NLM_F_REPLACE) {
fprintf(fp, "Replaced action ");
}
}
open_json_object(NULL);
tc_dump_action(fp, tb[TCA_ACT_TAB], tot_acts ? *tot_acts:0, false);
close_json_object();
return 0;
}
static int tc_action_gd(int cmd, unsigned int flags,
int *argc_p, char ***argv_p)
{
char k[FILTER_NAMESZ];
struct action_util *a = NULL;
int argc = *argc_p;
char **argv = *argv_p;
int prio = 0;
int ret = 0;
__u32 i = 0;
struct rtattr *tail;
struct rtattr *tail2;
struct nlmsghdr *ans = NULL;
struct {
struct nlmsghdr n;
struct tcamsg t;
char buf[MAX_MSG];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
.n.nlmsg_flags = NLM_F_REQUEST | flags,
.n.nlmsg_type = cmd,
.t.tca_family = AF_UNSPEC,
};
argc -= 1;
argv += 1;
tail = addattr_nest(&req.n, MAX_MSG, TCA_ACT_TAB);
while (argc > 0) {
if (strcmp(*argv, "action") == 0) {
argc--;
argv++;
continue;
} else if (strcmp(*argv, "help") == 0) {
return -1;
}
strncpy(k, *argv, sizeof(k) - 1);
a = get_action_kind(k);
if (a == NULL) {
fprintf(stderr, "Error: non existent action: %s\n", k);
ret = -1;
goto bad_val;
}
if (strcmp(a->id, k) != 0) {
fprintf(stderr, "Error: non existent action: %s\n", k);
ret = -1;
goto bad_val;
}
argc -= 1;
argv += 1;
if (argc <= 0) {
fprintf(stderr,
"Error: no index specified action: %s\n", k);
ret = -1;
goto bad_val;
}
if (matches(*argv, "index") == 0) {
NEXT_ARG();
if (get_u32(&i, *argv, 10)) {
fprintf(stderr, "Illegal \"index\"\n");
ret = -1;
goto bad_val;
}
argc -= 1;
argv += 1;
} else {
fprintf(stderr,
"Error: no index specified action: %s\n", k);
ret = -1;
goto bad_val;
}
tail2 = addattr_nest(&req.n, MAX_MSG, ++prio);
addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
if (i > 0)
addattr32(&req.n, MAX_MSG, TCA_ACT_INDEX, i);
addattr_nest_end(&req.n, tail2);
}
addattr_nest_end(&req.n, tail);
req.n.nlmsg_seq = rth.dump = ++rth.seq;
if (rtnl_talk(&rth, &req.n, cmd == RTM_DELACTION ? NULL : &ans) < 0) {
fprintf(stderr, "We have an error talking to the kernel\n");
return 1;
}
if (cmd == RTM_GETACTION) {
new_json_obj(json);
ret = print_action(ans, stdout);
if (ret < 0) {
fprintf(stderr, "Dump terminated\n");
free(ans);
delete_json_obj();
return 1;
}
delete_json_obj();
}
free(ans);
*argc_p = argc;
*argv_p = argv;
bad_val:
return ret;
}
static int tc_action_modify(int cmd, unsigned int flags,
int *argc_p, char ***argv_p)
{
int argc = *argc_p;
char **argv = *argv_p;
int ret = 0;
struct {
struct nlmsghdr n;
struct tcamsg t;
char buf[MAX_MSG];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
.n.nlmsg_flags = NLM_F_REQUEST | flags,
.n.nlmsg_type = cmd,
.t.tca_family = AF_UNSPEC,
};
struct rtattr *tail = NLMSG_TAIL(&req.n);
argc -= 1;
argv += 1;
if (parse_action(&argc, &argv, TCA_ACT_TAB, &req.n)) {
fprintf(stderr, "Illegal \"action\"\n");
return -1;
}
tail->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail;
if (rtnl_talk(&rth, &req.n, NULL) < 0) {
fprintf(stderr, "We have an error talking to the kernel\n");
ret = -1;
}
*argc_p = argc;
*argv_p = argv;
return ret;
}
static int tc_act_list_or_flush(int *argc_p, char ***argv_p, int event)
{
struct rtattr *tail, *tail2, *tail3, *tail4;
int ret = 0, prio = 0, msg_size = 0;
struct action_util *a = NULL;
struct nla_bitfield32 flag_select = { 0 };
char **argv = *argv_p;
__u32 msec_since = 0;
int argc = *argc_p;
char k[FILTER_NAMESZ];
struct {
struct nlmsghdr n;
struct tcamsg t;
char buf[MAX_MSG];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcamsg)),
.t.tca_family = AF_UNSPEC,
};
tail = addattr_nest(&req.n, MAX_MSG, TCA_ACT_TAB);
tail2 = NLMSG_TAIL(&req.n);
strncpy(k, *argv, sizeof(k) - 1);
#ifdef CONFIG_GACT
if (!gact_ld)
get_action_kind("gact");
#endif
a = get_action_kind(k);
if (a == NULL) {
fprintf(stderr, "bad action %s\n", k);
goto bad_val;
}
if (strcmp(a->id, k) != 0) {
fprintf(stderr, "bad action %s\n", k);
goto bad_val;
}
strncpy(k, *argv, sizeof(k) - 1);
argc -= 1;
argv += 1;
if (argc && (strcmp(*argv, "since") == 0)) {
NEXT_ARG();
if (get_u32(&msec_since, *argv, 0))
invarg("dump time \"since\" is invalid", *argv);
}
addattr_l(&req.n, MAX_MSG, ++prio, NULL, 0);
addattr_l(&req.n, MAX_MSG, TCA_ACT_KIND, k, strlen(k) + 1);
tail2->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail2;
addattr_nest_end(&req.n, tail);
tail3 = NLMSG_TAIL(&req.n);
flag_select.value |= TCA_ACT_FLAG_LARGE_DUMP_ON;
flag_select.selector |= TCA_ACT_FLAG_LARGE_DUMP_ON;
if (brief) {
flag_select.value |= TCA_ACT_FLAG_TERSE_DUMP;
flag_select.selector |= TCA_ACT_FLAG_TERSE_DUMP;
}
addattr_l(&req.n, MAX_MSG, TCA_ROOT_FLAGS, &flag_select,
sizeof(struct nla_bitfield32));
tail3->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail3;
if (msec_since) {
tail4 = NLMSG_TAIL(&req.n);
addattr32(&req.n, MAX_MSG, TCA_ROOT_TIME_DELTA, msec_since);
tail4->rta_len = (void *) NLMSG_TAIL(&req.n) - (void *) tail4;
}
msg_size = NLMSG_ALIGN(req.n.nlmsg_len)
- NLMSG_ALIGN(sizeof(struct nlmsghdr));
if (event == RTM_GETACTION) {
if (rtnl_dump_request(&rth, event,
(void *)&req.t, msg_size) < 0) {
perror("Cannot send dump request");
return 1;
}
new_json_obj(json);
ret = rtnl_dump_filter(&rth, print_action, stdout);
delete_json_obj();
}
if (event == RTM_DELACTION) {
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len);
req.n.nlmsg_type = RTM_DELACTION;
req.n.nlmsg_flags |= NLM_F_ROOT;
req.n.nlmsg_flags |= NLM_F_REQUEST;
if (rtnl_talk(&rth, &req.n, NULL) < 0) {
fprintf(stderr, "We have an error flushing\n");
return 1;
}
}
bad_val:
*argc_p = argc;
*argv_p = argv;
return ret;
}
int do_action(int argc, char **argv)
{
int ret = 0;
while (argc > 0) {
if (matches(*argv, "add") == 0) {
ret = tc_action_modify(RTM_NEWACTION,
NLM_F_EXCL | NLM_F_CREATE,
&argc, &argv);
} else if (matches(*argv, "change") == 0 ||
matches(*argv, "replace") == 0) {
ret = tc_action_modify(RTM_NEWACTION,
NLM_F_CREATE | NLM_F_REPLACE,
&argc, &argv);
} else if (matches(*argv, "delete") == 0) {
argc -= 1;
argv += 1;
ret = tc_action_gd(RTM_DELACTION, 0, &argc, &argv);
} else if (matches(*argv, "get") == 0) {
argc -= 1;
argv += 1;
ret = tc_action_gd(RTM_GETACTION, 0, &argc, &argv);
} else if (matches(*argv, "list") == 0 ||
matches(*argv, "show") == 0 ||
matches(*argv, "lst") == 0) {
if (argc <= 2) {
act_usage();
return -1;
}
argc -= 2;
argv += 2;
return tc_act_list_or_flush(&argc, &argv,
RTM_GETACTION);
} else if (matches(*argv, "flush") == 0) {
if (argc <= 2) {
act_usage();
return -1;
}
argc -= 2;
argv += 2;
return tc_act_list_or_flush(&argc, &argv,
RTM_DELACTION);
} else if (matches(*argv, "help") == 0) {
act_usage();
return -1;
} else {
fprintf(stderr,
"Command \"%s\" is unknown, try \"tc actions help\".\n",
*argv);
return -1;
}
if (ret < 0)
return -1;
}
return 0;
}