diff options
Diffstat (limited to '')
-rw-r--r-- | tc/.gitignore | 5 | ||||
-rw-r--r-- | tc/Makefile | 181 | ||||
-rw-r--r-- | tc/e_bpf.c | 176 | ||||
-rw-r--r-- | tc/em_canid.c | 186 | ||||
-rw-r--r-- | tc/em_cmp.c | 180 | ||||
-rw-r--r-- | tc/em_ipset.c | 260 | ||||
-rw-r--r-- | tc/em_ipt.c | 207 | ||||
-rw-r--r-- | tc/em_meta.c | 541 | ||||
-rw-r--r-- | tc/em_nbyte.c | 136 | ||||
-rw-r--r-- | tc/em_u32.c | 171 | ||||
-rw-r--r-- | tc/emp_ematch.l | 146 | ||||
-rw-r--r-- | tc/emp_ematch.y | 96 | ||||
-rw-r--r-- | tc/f_basic.c | 147 | ||||
-rw-r--r-- | tc/f_bpf.c | 266 | ||||
-rw-r--r-- | tc/f_cgroup.c | 109 | ||||
-rw-r--r-- | tc/f_flow.c | 362 | ||||
-rw-r--r-- | tc/f_flower.c | 3190 | ||||
-rw-r--r-- | tc/f_fw.c | 168 | ||||
-rw-r--r-- | tc/f_matchall.c | 167 | ||||
-rw-r--r-- | tc/f_route.c | 178 | ||||
-rw-r--r-- | tc/f_u32.c | 1389 | ||||
-rw-r--r-- | tc/m_action.c | 914 | ||||
-rw-r--r-- | tc/m_bpf.c | 215 | ||||
-rw-r--r-- | tc/m_connmark.c | 138 | ||||
-rw-r--r-- | tc/m_csum.c | 228 | ||||
-rw-r--r-- | tc/m_ct.c | 549 | ||||
-rw-r--r-- | tc/m_ctinfo.c | 268 | ||||
-rw-r--r-- | tc/m_ematch.c | 572 | ||||
-rw-r--r-- | tc/m_ematch.h | 110 | ||||
-rw-r--r-- | tc/m_estimator.c | 59 | ||||
-rw-r--r-- | tc/m_gact.c | 217 | ||||
-rw-r--r-- | tc/m_gate.c | 537 | ||||
-rw-r--r-- | tc/m_ife.c | 331 | ||||
-rw-r--r-- | tc/m_mirred.c | 325 | ||||
-rw-r--r-- | tc/m_mpls.c | 299 | ||||
-rw-r--r-- | tc/m_nat.c | 195 | ||||
-rw-r--r-- | tc/m_pedit.c | 863 | ||||
-rw-r--r-- | tc/m_pedit.h | 78 | ||||
-rw-r--r-- | tc/m_police.c | 362 | ||||
-rw-r--r-- | tc/m_sample.c | 185 | ||||
-rw-r--r-- | tc/m_simple.c | 202 | ||||
-rw-r--r-- | tc/m_skbedit.c | 266 | ||||
-rw-r--r-- | tc/m_skbmod.c | 234 | ||||
-rw-r--r-- | tc/m_tunnel_key.c | 758 | ||||
-rw-r--r-- | tc/m_vlan.c | 309 | ||||
-rw-r--r-- | tc/p_eth.c | 69 | ||||
-rw-r--r-- | tc/p_icmp.c | 31 | ||||
-rw-r--r-- | tc/p_ip.c | 156 | ||||
-rw-r--r-- | tc/p_ip6.c | 99 | ||||
-rw-r--r-- | tc/p_tcp.c | 67 | ||||
-rw-r--r-- | tc/p_udp.c | 61 | ||||
-rw-r--r-- | tc/q_cake.c | 829 | ||||
-rw-r--r-- | tc/q_cbs.c | 136 | ||||
-rw-r--r-- | tc/q_choke.c | 234 | ||||
-rw-r--r-- | tc/q_clsact.c | 34 | ||||
-rw-r--r-- | tc/q_codel.c | 200 | ||||
-rw-r--r-- | tc/q_drr.c | 114 | ||||
-rw-r--r-- | tc/q_etf.c | 145 | ||||
-rw-r--r-- | tc/q_ets.c | 337 | ||||
-rw-r--r-- | tc/q_fifo.c | 94 | ||||
-rw-r--r-- | tc/q_fq.c | 598 | ||||
-rw-r--r-- | tc/q_fq_codel.c | 325 | ||||
-rw-r--r-- | tc/q_fq_pie.c | 315 | ||||
-rw-r--r-- | tc/q_gred.c | 502 | ||||
-rw-r--r-- | tc/q_hfsc.c | 411 | ||||
-rw-r--r-- | tc/q_hhf.c | 210 | ||||
-rw-r--r-- | tc/q_htb.c | 385 | ||||
-rw-r--r-- | tc/q_ingress.c | 47 | ||||
-rw-r--r-- | tc/q_mqprio.c | 419 | ||||
-rw-r--r-- | tc/q_multiq.c | 71 | ||||
-rw-r--r-- | tc/q_netem.c | 867 | ||||
-rw-r--r-- | tc/q_pie.c | 242 | ||||
-rw-r--r-- | tc/q_plug.c | 76 | ||||
-rw-r--r-- | tc/q_prio.c | 126 | ||||
-rw-r--r-- | tc/q_qfq.c | 111 | ||||
-rw-r--r-- | tc/q_red.c | 278 | ||||
-rw-r--r-- | tc/q_sfb.c | 212 | ||||
-rw-r--r-- | tc/q_sfq.c | 279 | ||||
-rw-r--r-- | tc/q_skbprio.c | 80 | ||||
-rw-r--r-- | tc/q_taprio.c | 654 | ||||
-rw-r--r-- | tc/q_tbf.c | 343 | ||||
-rw-r--r-- | tc/static-syms.c | 15 | ||||
-rw-r--r-- | tc/tc.c | 357 | ||||
-rw-r--r-- | tc/tc_class.c | 488 | ||||
-rw-r--r-- | tc/tc_common.h | 30 | ||||
-rw-r--r-- | tc/tc_core.c | 253 | ||||
-rw-r--r-- | tc/tc_core.h | 36 | ||||
-rw-r--r-- | tc/tc_estimator.c | 40 | ||||
-rw-r--r-- | tc/tc_exec.c | 108 | ||||
-rw-r--r-- | tc/tc_filter.c | 797 | ||||
-rw-r--r-- | tc/tc_monitor.c | 115 | ||||
-rw-r--r-- | tc/tc_qdisc.c | 544 | ||||
-rw-r--r-- | tc/tc_qevent.c | 218 | ||||
-rw-r--r-- | tc/tc_qevent.h | 51 | ||||
-rw-r--r-- | tc/tc_red.c | 119 | ||||
-rw-r--r-- | tc/tc_red.h | 11 | ||||
-rw-r--r-- | tc/tc_stab.c | 136 | ||||
-rw-r--r-- | tc/tc_util.c | 916 | ||||
-rw-r--r-- | tc/tc_util.h | 141 |
99 files changed, 30007 insertions, 0 deletions
diff --git a/tc/.gitignore b/tc/.gitignore new file mode 100644 index 0000000..0dbe919 --- /dev/null +++ b/tc/.gitignore @@ -0,0 +1,5 @@ +*.tab.c +*.lex.c +*.output +*.tab.h +tc diff --git a/tc/Makefile b/tc/Makefile new file mode 100644 index 0000000..b5e853d --- /dev/null +++ b/tc/Makefile @@ -0,0 +1,181 @@ +# SPDX-License-Identifier: GPL-2.0 +TCOBJ= tc.o tc_qdisc.o tc_class.o tc_filter.o tc_util.o tc_monitor.o \ + tc_exec.o m_police.o m_estimator.o m_action.o m_ematch.o \ + emp_ematch.tab.o emp_ematch.lex.o + +include ../config.mk + +SHARED_LIBS ?= y + +TCMODULES := +TCMODULES += q_fifo.o +TCMODULES += q_sfq.o +TCMODULES += q_red.o +TCMODULES += q_prio.o +TCMODULES += q_skbprio.o +TCMODULES += q_tbf.o +TCMODULES += q_multiq.o +TCMODULES += q_netem.o +TCMODULES += q_choke.o +TCMODULES += q_sfb.o +TCMODULES += f_u32.o +TCMODULES += f_route.o +TCMODULES += f_fw.o +TCMODULES += f_basic.o +TCMODULES += f_bpf.o +TCMODULES += f_flow.o +TCMODULES += f_cgroup.o +TCMODULES += f_flower.o +TCMODULES += q_gred.o +TCMODULES += q_ingress.o +TCMODULES += q_hfsc.o +TCMODULES += q_htb.o +TCMODULES += q_drr.o +TCMODULES += q_qfq.o +TCMODULES += m_gact.o +TCMODULES += m_mirred.o +TCMODULES += m_mpls.o +TCMODULES += m_nat.o +TCMODULES += m_pedit.o +TCMODULES += m_ife.o +TCMODULES += m_skbedit.o +TCMODULES += m_skbmod.o +TCMODULES += m_csum.o +TCMODULES += m_simple.o +TCMODULES += m_vlan.o +TCMODULES += m_connmark.o +TCMODULES += m_ctinfo.o +TCMODULES += m_bpf.o +TCMODULES += m_tunnel_key.o +TCMODULES += m_sample.o +TCMODULES += m_ct.o +TCMODULES += m_gate.o +TCMODULES += p_ip.o +TCMODULES += p_ip6.o +TCMODULES += p_icmp.o +TCMODULES += p_eth.o +TCMODULES += p_tcp.o +TCMODULES += p_udp.o +TCMODULES += em_nbyte.o +TCMODULES += em_cmp.o +TCMODULES += em_u32.o +TCMODULES += em_canid.o +TCMODULES += em_meta.o +TCMODULES += q_mqprio.o +TCMODULES += q_codel.o +TCMODULES += q_fq_codel.o +TCMODULES += q_fq.o +TCMODULES += q_pie.o +TCMODULES += q_fq_pie.o +TCMODULES += q_cake.o +TCMODULES += q_hhf.o +TCMODULES += q_clsact.o +TCMODULES += e_bpf.o +TCMODULES += f_matchall.o +TCMODULES += q_cbs.o +TCMODULES += q_etf.o +TCMODULES += q_taprio.o +TCMODULES += q_plug.o +TCMODULES += q_ets.o + +TCSO := + +ifneq ($(TC_CONFIG_NO_XT),y) + ifeq ($(TC_CONFIG_XT),y) + TCMODULES += em_ipt.o + ifeq ($(TC_CONFIG_IPSET),y) + TCMODULES += em_ipset.o + endif + endif +endif + +TCOBJ += $(TCMODULES) +LDLIBS += -L. -lm + +ifeq ($(SHARED_LIBS),y) +LDLIBS += -ldl +LDFLAGS += -Wl,-export-dynamic +endif + +TCLIB := tc_core.o +TCLIB += tc_red.o +TCLIB += tc_estimator.o +TCLIB += tc_stab.o +TCLIB += tc_qevent.o + +CFLAGS += -DCONFIG_GACT -DCONFIG_GACT_PROB +ifneq ($(IPT_LIB_DIR),) + CFLAGS += -DIPT_LIB_DIR=\"$(IPT_LIB_DIR)\" +endif + +LEX := flex +CFLAGS += -DYY_NO_INPUT + +MODDESTDIR := $(DESTDIR)$(LIBDIR)/tc + +%.so: %.c + $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic $< -o $@ + + +all: tc $(TCSO) + +tc: $(TCOBJ) $(LIBNETLINK) libtc.a + $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +libtc.a: $(TCLIB) + $(QUIET_AR)$(AR) rcs $@ $^ + +install: all + mkdir -p $(MODDESTDIR) + install -m 0755 tc $(DESTDIR)$(SBINDIR) + for i in $(TCSO); \ + do install -m 755 $$i $(MODDESTDIR); \ + done + if [ ! -f $(MODDESTDIR)/m_ipt.so ]; then \ + if [ -f $(MODDESTDIR)/m_xt.so ]; \ + then ln -s m_xt.so $(MODDESTDIR)/m_ipt.so ; \ + elif [ -f $(MODDESTDIR)/m_xt_old.so ]; \ + then ln -s m_xt_old.so $(MODDESTDIR)/m_ipt.so ; \ + fi; \ + fi + +clean: + rm -f $(TCOBJ) $(TCLIB) libtc.a tc *.so emp_ematch.tab.h; \ + rm -f emp_ematch.tab.* + +m_xt.so: m_xt.c + $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic -o m_xt.so m_xt.c $$($(PKG_CONFIG) xtables --cflags --libs) + +m_xt_old.so: m_xt_old.c + $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fpic -o m_xt_old.so m_xt_old.c $$($(PKG_CONFIG) xtables --cflags --libs) + +em_ipset.o: CFLAGS += $$($(PKG_CONFIG) xtables --cflags) + +em_ipt.o: CFLAGS += $$($(PKG_CONFIG) xtables --cflags) + +ifeq ($(TC_CONFIG_XT),y) + LDLIBS += $$($(PKG_CONFIG) xtables --libs) +endif + +%.tab.c: %.y + $(QUIET_YACC)$(YACC) $(YACCFLAGS) -p ematch_ -b $(basename $(basename $@)) $< + +%.lex.c: %.l + $(QUIET_LEX)$(LEX) $(LEXFLAGS) -o$@ $< + +# our lexer includes the header from yacc, so make sure +# we don't attempt to compile it before the header has +# been generated as part of the yacc step. +emp_ematch.lex.o: emp_ematch.tab.c + +ifneq ($(SHARED_LIBS),y) + +tc: static-syms.o +static-syms.o: static-syms.h +static-syms.h: $(wildcard *.c) + files="$^" ; \ + for s in `grep -B 3 '\<dlsym' $$files | sed -n '/snprintf/{s:.*"\([^"]*\)".*:\1:;s:%s::;p}'` ; do \ + sed -n '/'$$s'[^ ]* =/{s:.* \([^ ]*'$$s'[^ ]*\) .*:extern char \1[] __attribute__((weak)); if (!strcmp(sym, "\1")) return \1;:;p}' $$files ; \ + done > $@ + +endif diff --git a/tc/e_bpf.c b/tc/e_bpf.c new file mode 100644 index 0000000..79cddac --- /dev/null +++ b/tc/e_bpf.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * e_bpf.c BPF exec proxy + * + * Authors: Daniel Borkmann <daniel@iogearbox.net> + */ + +#include <stdio.h> +#include <unistd.h> + +#include "utils.h" + +#include "tc_util.h" + +#include "bpf_util.h" +#include "bpf_elf.h" +#include "bpf_scm.h" + +#define BPF_DEFAULT_CMD "/bin/sh" + +static char *argv_default[] = { BPF_DEFAULT_CMD, NULL }; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... bpf [ import UDS_FILE ] [ run CMD ]\n" + " ... bpf [ debug ]\n" + " ... bpf [ graft MAP_FILE ] [ key KEY ]\n" + " `... [ object-file OBJ_FILE ] [ type TYPE ] [ section NAME ] [ verbose ]\n" + " `... [ object-pinned PROG_FILE ]\n" + "\n" + "Where UDS_FILE provides the name of a unix domain socket file\n" + "to import eBPF maps and the optional CMD denotes the command\n" + "to be executed (default: \'%s\').\n" + "Where MAP_FILE points to a pinned map, OBJ_FILE to an object file\n" + "and PROG_FILE to a pinned program. TYPE can be {cls, act}, where\n" + "\'cls\' is default. KEY is optional and can be inferred from the\n" + "section name, otherwise it needs to be provided.\n", + BPF_DEFAULT_CMD); +} + +static int bpf_num_env_entries(void) +{ + char **envp; + int num; + + for (num = 0, envp = environ; *envp != NULL; envp++) + num++; + return num; +} + +static int parse_bpf(struct exec_util *eu, int argc, char **argv) +{ + char **argv_run = argv_default, **envp_run, *tmp; + int ret, i, env_old, env_num, env_map; + const char *bpf_uds_name = NULL; + int fds[BPF_SCM_MAX_FDS] = {}; + struct bpf_map_aux aux = {}; + + if (argc == 0) + return 0; + + while (argc > 0) { + if (matches(*argv, "run") == 0) { + NEXT_ARG(); + argv_run = argv; + break; + } else if (matches(*argv, "import") == 0) { + NEXT_ARG(); + bpf_uds_name = *argv; + } else if (matches(*argv, "debug") == 0 || + matches(*argv, "dbg") == 0) { + if (bpf_trace_pipe()) + fprintf(stderr, + "No trace pipe, tracefs not mounted?\n"); + return -1; + } else if (matches(*argv, "graft") == 0) { + const char *bpf_map_path; + bool has_key = false; + uint32_t key; + + NEXT_ARG(); + bpf_map_path = *argv; + NEXT_ARG(); + if (matches(*argv, "key") == 0) { + NEXT_ARG(); + if (get_unsigned(&key, *argv, 0)) { + fprintf(stderr, "Illegal \"key\"\n"); + return -1; + } + has_key = true; + NEXT_ARG(); + } + return bpf_graft_map(bpf_map_path, has_key ? + &key : NULL, argc, argv); + } else { + explain(); + return -1; + } + + NEXT_ARG_FWD(); + } + + if (!bpf_uds_name) { + fprintf(stderr, "bpf: No import parameter provided!\n"); + explain(); + return -1; + } + + if (argv_run != argv_default && argc == 0) { + fprintf(stderr, "bpf: No run command provided!\n"); + explain(); + return -1; + } + + ret = bpf_recv_map_fds(bpf_uds_name, fds, &aux, ARRAY_SIZE(fds)); + if (ret < 0) { + fprintf(stderr, "bpf: Could not receive fds!\n"); + return -1; + } + + if (aux.num_ent == 0) { + envp_run = environ; + goto out; + } + + env_old = bpf_num_env_entries(); + env_num = env_old + aux.num_ent + 2; + env_map = env_old + 1; + + envp_run = malloc(sizeof(*envp_run) * env_num); + if (!envp_run) { + fprintf(stderr, "bpf: No memory left to allocate env!\n"); + goto err; + } + + for (i = 0; i < env_old; i++) + envp_run[i] = environ[i]; + + ret = asprintf(&tmp, "BPF_NUM_MAPS=%u", aux.num_ent); + if (ret < 0) + goto err_free; + + envp_run[env_old] = tmp; + + for (i = env_map; i < env_num - 1; i++) { + ret = asprintf(&tmp, "BPF_MAP%u=%u", + aux.ent[i - env_map].id, + fds[i - env_map]); + if (ret < 0) + goto err_free_env; + + envp_run[i] = tmp; + } + + envp_run[env_num - 1] = NULL; +out: + ret = execvpe(argv_run[0], argv_run, envp_run); + free(envp_run); + return ret; + +err_free_env: + for (--i; i >= env_old; i--) + free(envp_run[i]); +err_free: + free(envp_run); +err: + for (i = 0; i < aux.num_ent; i++) + close(fds[i]); + return -1; +} + +struct exec_util bpf_exec_util = { + .id = "bpf", + .parse_eopt = parse_bpf, +}; diff --git a/tc/em_canid.c b/tc/em_canid.c new file mode 100644 index 0000000..2285475 --- /dev/null +++ b/tc/em_canid.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * em_canid.c Ematch rule to match CAN frames according to their CAN identifiers + * + * Idea: Oliver Hartkopp <oliver.hartkopp@volkswagen.de> + * Copyright: (c) 2011 Czech Technical University in Prague + * (c) 2011 Volkswagen Group Research + * Authors: Michal Sojka <sojkam1@fel.cvut.cz> + * Pavel Pisa <pisa@cmp.felk.cvut.cz> + * Rostislav Lisovy <lisovy@gmail.cz> + * Funded by: Volkswagen Group Research + * + * Documentation: http://rtime.felk.cvut.cz/can/socketcan-qdisc-final.pdf + */ + +#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 <errno.h> +#include <linux/can.h> +#include <inttypes.h> +#include "m_ematch.h" + +#define EM_CANID_RULES_MAX 400 /* Main reason for this number is Netlink + message size limit equal to Single memory page size. When dump() + is invoked, there are even some ematch related headers sent from + kernel to userspace together with em_canid configuration -- + 400*sizeof(struct can_filter) should fit without any problems */ + +extern struct ematch_util canid_ematch_util; +struct rules { + struct can_filter *rules_raw; + int rules_capacity; /* Size of array allocated for rules_raw */ + int rules_cnt; /* Actual number of rules stored in rules_raw */ +}; + +static void canid_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: canid(IDLIST)\n" \ + "where: IDLIST := IDSPEC [ IDLIST ]\n" \ + " IDSPEC := { ’sff’ CANID | ’eff’ CANID }\n" \ + " CANID := ID[:MASK]\n" \ + " ID, MASK := hexadecimal number (i.e. 0x123)\n" \ + "Example: canid(sff 0x123 sff 0x124 sff 0x125:0xf)\n"); +} + +static int canid_parse_rule(struct rules *rules, struct bstr *a, int iseff) +{ + unsigned int can_id = 0; + unsigned int can_mask = 0; + + if (sscanf(a->data, "%"SCNx32 ":" "%"SCNx32, &can_id, &can_mask) != 2) { + if (sscanf(a->data, "%"SCNx32, &can_id) != 1) { + return -1; + } else { + can_mask = (iseff) ? CAN_EFF_MASK : CAN_SFF_MASK; + } + } + + /* Stretch rules array up to EM_CANID_RULES_MAX if necessary */ + if (rules->rules_cnt == rules->rules_capacity) { + if (rules->rules_capacity <= EM_CANID_RULES_MAX/2) { + rules->rules_capacity *= 2; + rules->rules_raw = realloc(rules->rules_raw, + sizeof(struct can_filter) * rules->rules_capacity); + } else { + return -2; + } + } + + rules->rules_raw[rules->rules_cnt].can_id = + can_id | ((iseff) ? CAN_EFF_FLAG : 0); + rules->rules_raw[rules->rules_cnt].can_mask = + can_mask | CAN_EFF_FLAG; + + rules->rules_cnt++; + + return 0; +} + +static int canid_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + int iseff = 0; + int ret = 0; + struct rules rules = { + .rules_capacity = 25, /* Denominator of EM_CANID_RULES_MAX + Will be multiplied by 2 to calculate the size for realloc() */ + .rules_cnt = 0 + }; + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &canid_ematch_util, FMT, ##ARGS) + + if (args == NULL) + return PARSE_ERR(args, "canid: missing arguments"); + + rules.rules_raw = calloc(rules.rules_capacity, + sizeof(struct can_filter)); + + do { + if (!bstrcmp(args, "sff")) { + iseff = 0; + } else if (!bstrcmp(args, "eff")) { + iseff = 1; + } else { + ret = PARSE_ERR(args, "canid: invalid key"); + goto exit; + } + + args = bstr_next(args); + if (args == NULL) { + ret = PARSE_ERR(args, "canid: missing argument"); + goto exit; + } + + ret = canid_parse_rule(&rules, args, iseff); + if (ret == -1) { + ret = PARSE_ERR(args, "canid: Improperly formed CAN ID & mask\n"); + goto exit; + } else if (ret == -2) { + ret = PARSE_ERR(args, "canid: Too many arguments on input\n"); + goto exit; + } + } while ((args = bstr_next(args)) != NULL); + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addraw_l(n, MAX_MSG, rules.rules_raw, + sizeof(struct can_filter) * rules.rules_cnt); + +#undef PARSE_ERR +exit: + free(rules.rules_raw); + return ret; +} + +static int canid_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + struct can_filter *conf = data; /* Array with rules */ + int rules_count; + int i; + + rules_count = data_len / sizeof(struct can_filter); + + for (i = 0; i < rules_count; i++) { + struct can_filter *pcfltr = &conf[i]; + + if (pcfltr->can_id & CAN_EFF_FLAG) { + if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_EFF_MASK)) + fprintf(fd, "eff 0x%"PRIX32, + pcfltr->can_id & CAN_EFF_MASK); + else + fprintf(fd, "eff 0x%"PRIX32":0x%"PRIX32, + pcfltr->can_id & CAN_EFF_MASK, + pcfltr->can_mask & CAN_EFF_MASK); + } else { + if (pcfltr->can_mask == (CAN_EFF_FLAG | CAN_SFF_MASK)) + fprintf(fd, "sff 0x%"PRIX32, + pcfltr->can_id & CAN_SFF_MASK); + else + fprintf(fd, "sff 0x%"PRIX32":0x%"PRIX32, + pcfltr->can_id & CAN_SFF_MASK, + pcfltr->can_mask & CAN_SFF_MASK); + } + + if ((i + 1) < rules_count) + fprintf(fd, " "); + } + + return 0; +} + +struct ematch_util canid_ematch_util = { + .kind = "canid", + .kind_num = TCF_EM_CANID, + .parse_eopt = canid_parse_eopt, + .print_eopt = canid_print_eopt, + .print_usage = canid_print_usage +}; diff --git a/tc/em_cmp.c b/tc/em_cmp.c new file mode 100644 index 0000000..dfd123d --- /dev/null +++ b/tc/em_cmp.c @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * em_cmp.c Simple comparison Ematch + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <errno.h> + +#include "m_ematch.h" +#include <linux/tc_ematch/tc_em_cmp.h> + +extern struct ematch_util cmp_ematch_util; + +static void cmp_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: cmp(ALIGN at OFFSET [ ATTRS ] { eq | lt | gt } VALUE)\n" \ + "where: ALIGN := { u8 | u16 | u32 }\n" \ + " ATTRS := [ layer LAYER ] [ mask MASK ] [ trans ]\n" \ + " LAYER := { link | network | transport | 0..%d }\n" \ + "\n" \ + "Example: cmp(u16 at 3 layer 2 mask 0xff00 gt 20)\n", + TCF_LAYER_MAX); +} + +static int cmp_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + struct bstr *a; + int align, opnd = 0; + unsigned long offset = 0, layer = TCF_LAYER_NETWORK, mask = 0, value = 0; + int offset_present = 0, value_present = 0; + struct tcf_em_cmp cmp = {}; + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &cmp_ematch_util, FMT, ##ARGS) + + if (args == NULL) + return PARSE_ERR(args, "cmp: missing arguments"); + + if (!bstrcmp(args, "u8")) + align = TCF_EM_ALIGN_U8; + else if (!bstrcmp(args, "u16")) + align = TCF_EM_ALIGN_U16; + else if (!bstrcmp(args, "u32")) + align = TCF_EM_ALIGN_U32; + else + return PARSE_ERR(args, "cmp: invalid alignment"); + + for (a = bstr_next(args); a; a = bstr_next(a)) { + if (!bstrcmp(a, "at")) { + if (a->next == NULL) + return PARSE_ERR(a, "cmp: missing argument"); + a = bstr_next(a); + + offset = bstrtoul(a); + if (offset == ULONG_MAX) + return PARSE_ERR(a, "cmp: invalid offset, " \ + "must be numeric"); + + offset_present = 1; + } else if (!bstrcmp(a, "layer")) { + if (a->next == NULL) + return PARSE_ERR(a, "cmp: missing argument"); + a = bstr_next(a); + + layer = parse_layer(a); + if (layer == INT_MAX) { + layer = bstrtoul(a); + if (layer == ULONG_MAX) + return PARSE_ERR(a, "cmp: invalid " \ + "layer"); + } + + if (layer > TCF_LAYER_MAX) + return PARSE_ERR(a, "cmp: illegal layer, " \ + "must be in 0..%d", TCF_LAYER_MAX); + } else if (!bstrcmp(a, "mask")) { + if (a->next == NULL) + return PARSE_ERR(a, "cmp: missing argument"); + a = bstr_next(a); + + mask = bstrtoul(a); + if (mask == ULONG_MAX) + return PARSE_ERR(a, "cmp: invalid mask"); + } else if (!bstrcmp(a, "trans")) { + cmp.flags |= TCF_EM_CMP_TRANS; + } else if (!bstrcmp(a, "eq") || !bstrcmp(a, "gt") || + !bstrcmp(a, "lt")) { + + if (!bstrcmp(a, "eq")) + opnd = TCF_EM_OPND_EQ; + else if (!bstrcmp(a, "gt")) + opnd = TCF_EM_OPND_GT; + else if (!bstrcmp(a, "lt")) + opnd = TCF_EM_OPND_LT; + + if (a->next == NULL) + return PARSE_ERR(a, "cmp: missing argument"); + a = bstr_next(a); + + value = bstrtoul(a); + if (value == ULONG_MAX) + return PARSE_ERR(a, "cmp: invalid value"); + + value_present = 1; + } else + return PARSE_ERR(a, "nbyte: unknown parameter"); + } + + if (offset_present == 0 || value_present == 0) + return PARSE_ERR(a, "cmp: offset and value required"); + + cmp.val = (__u32) value; + cmp.mask = (__u32) mask; + cmp.off = (__u16) offset; + cmp.align = (__u8) align; + cmp.layer = (__u8) layer; + cmp.opnd = (__u8) opnd; + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addraw_l(n, MAX_MSG, &cmp, sizeof(cmp)); + +#undef PARSE_ERR + return 0; +} + +static int cmp_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + struct tcf_em_cmp *cmp = data; + + if (data_len < sizeof(*cmp)) { + fprintf(stderr, "CMP header size mismatch\n"); + return -1; + } + + if (cmp->align == TCF_EM_ALIGN_U8) + fprintf(fd, "u8 "); + else if (cmp->align == TCF_EM_ALIGN_U16) + fprintf(fd, "u16 "); + else if (cmp->align == TCF_EM_ALIGN_U32) + fprintf(fd, "u32 "); + + fprintf(fd, "at %d layer %d ", cmp->off, cmp->layer); + + if (cmp->mask) + fprintf(fd, "mask 0x%x ", cmp->mask); + + if (cmp->flags & TCF_EM_CMP_TRANS) + fprintf(fd, "trans "); + + if (cmp->opnd == TCF_EM_OPND_EQ) + fprintf(fd, "eq "); + else if (cmp->opnd == TCF_EM_OPND_LT) + fprintf(fd, "lt "); + else if (cmp->opnd == TCF_EM_OPND_GT) + fprintf(fd, "gt "); + + fprintf(fd, "%d", cmp->val); + + return 0; +} + +struct ematch_util cmp_ematch_util = { + .kind = "cmp", + .kind_num = TCF_EM_CMP, + .parse_eopt = cmp_parse_eopt, + .print_eopt = cmp_print_eopt, + .print_usage = cmp_print_usage +}; diff --git a/tc/em_ipset.c b/tc/em_ipset.c new file mode 100644 index 0000000..f97abaf --- /dev/null +++ b/tc/em_ipset.c @@ -0,0 +1,260 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * em_ipset.c IPset Ematch + * + * (C) 2012 Florian Westphal <fw@strlen.de> + * + * Parts taken from iptables libxt_set.h: + * Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu> + * Patrick Schaaf <bof@bof.de> + * Martin Josefsson <gandalf@wlug.westbo.se> + * Copyright (C) 2003-2010 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + */ + +#include <stdbool.h> +#include <stdio.h> +#include <errno.h> +#include <netdb.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <getopt.h> + +#include <xtables.h> +#include <linux/netfilter/ipset/ip_set.h> + +#ifndef IPSET_INVALID_ID +typedef __u16 ip_set_id_t; + +enum ip_set_dim { + IPSET_DIM_ZERO = 0, + IPSET_DIM_ONE, + IPSET_DIM_TWO, + IPSET_DIM_THREE, + IPSET_DIM_MAX = 6, +}; +#endif /* IPSET_INVALID_ID */ + +#include <linux/netfilter/xt_set.h> +#include "m_ematch.h" + +#ifndef IPSET_INVALID_ID +#define IPSET_INVALID_ID 65535 +#define SO_IP_SET 83 + +union ip_set_name_index { + char name[IPSET_MAXNAMELEN]; + __u16 index; +}; + +#define IP_SET_OP_GET_BYNAME 0x00000006 /* Get set index by name */ +struct ip_set_req_get_set { + unsigned int op; + unsigned int version; + union ip_set_name_index set; +}; + +#define IP_SET_OP_GET_BYINDEX 0x00000007 /* Get set name by index */ +/* Uses ip_set_req_get_set */ + +#define IP_SET_OP_VERSION 0x00000100 /* Ask kernel version */ +struct ip_set_req_version { + unsigned int op; + unsigned int version; +}; +#endif /* IPSET_INVALID_ID */ + +extern struct ematch_util ipset_ematch_util; + +static int get_version(unsigned int *version) +{ + int res, sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + struct ip_set_req_version req_version; + socklen_t size = sizeof(req_version); + + if (sockfd < 0) { + fputs("Can't open socket to ipset.\n", stderr); + return -1; + } + + req_version.op = IP_SET_OP_VERSION; + res = getsockopt(sockfd, SOL_IP, SO_IP_SET, &req_version, &size); + if (res != 0) { + perror("xt_set getsockopt"); + close(sockfd); + return -1; + } + + *version = req_version.version; + return sockfd; +} + +static int do_getsockopt(struct ip_set_req_get_set *req) +{ + int sockfd, res; + socklen_t size = sizeof(struct ip_set_req_get_set); + + sockfd = get_version(&req->version); + if (sockfd < 0) + return -1; + res = getsockopt(sockfd, SOL_IP, SO_IP_SET, req, &size); + if (res != 0) + perror("Problem when communicating with ipset"); + close(sockfd); + if (res != 0) + return -1; + + if (size != sizeof(struct ip_set_req_get_set)) { + fprintf(stderr, + "Incorrect return size from kernel during ipset lookup, (want %zu, got %zu)\n", + sizeof(struct ip_set_req_get_set), (size_t)size); + return -1; + } + + return res; +} + +static int +get_set_byid(char *setname, unsigned int idx) +{ + struct ip_set_req_get_set req; + int res; + + req.op = IP_SET_OP_GET_BYINDEX; + req.set.index = idx; + res = do_getsockopt(&req); + if (res != 0) + return -1; + if (req.set.name[0] == '\0') { + fprintf(stderr, + "Set with index %i in kernel doesn't exist.\n", idx); + return -1; + } + + strncpy(setname, req.set.name, IPSET_MAXNAMELEN); + return 0; +} + +static int +get_set_byname(const char *setname, struct xt_set_info *info) +{ + struct ip_set_req_get_set req; + int res; + + req.op = IP_SET_OP_GET_BYNAME; + strlcpy(req.set.name, setname, IPSET_MAXNAMELEN); + res = do_getsockopt(&req); + if (res != 0) + return -1; + if (req.set.index == IPSET_INVALID_ID) + return -1; + info->index = req.set.index; + return 0; +} + +static int +parse_dirs(const char *opt_arg, struct xt_set_info *info) +{ + char *saved = strdup(opt_arg); + char *ptr, *tmp = saved; + + if (!tmp) { + perror("strdup"); + return -1; + } + + while (info->dim < IPSET_DIM_MAX && tmp != NULL) { + info->dim++; + ptr = strsep(&tmp, ","); + if (strncmp(ptr, "src", 3) == 0) + info->flags |= (1 << info->dim); + else if (strncmp(ptr, "dst", 3) != 0) { + fputs("You must specify (the comma separated list of) 'src' or 'dst'\n", stderr); + free(saved); + return -1; + } + } + + if (tmp) + fprintf(stderr, "Can't be more src/dst options than %u", IPSET_DIM_MAX); + free(saved); + return tmp ? -1 : 0; +} + +static void ipset_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: ipset(SETNAME FLAGS)\n" \ + "where: SETNAME:= string\n" \ + " FLAGS := { FLAG[,FLAGS] }\n" \ + " FLAG := { src | dst }\n" \ + "\n" \ + "Example: 'ipset(bulk src,dst)'\n"); +} + +static int ipset_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + struct xt_set_info set_info = {}; + int ret; + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &ipset_ematch_util, FMT, ##ARGS) + + if (args == NULL) + return PARSE_ERR(args, "ipset: missing set name"); + + if (args->len >= IPSET_MAXNAMELEN) + return PARSE_ERR(args, "ipset: set name too long (max %u)", IPSET_MAXNAMELEN - 1); + ret = get_set_byname(args->data, &set_info); + if (ret < 0) + return PARSE_ERR(args, "ipset: unknown set name '%s'", args->data); + + if (args->next == NULL) + return PARSE_ERR(args, "ipset: missing set flags"); + + args = bstr_next(args); + if (parse_dirs(args->data, &set_info)) + return PARSE_ERR(args, "ipset: error parsing set flags"); + + if (args->next) { + args = bstr_next(args); + return PARSE_ERR(args, "ipset: unknown parameter"); + } + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addraw_l(n, MAX_MSG, &set_info, sizeof(set_info)); + +#undef PARSE_ERR + return 0; +} + +static int ipset_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + int i; + char setname[IPSET_MAXNAMELEN]; + const struct xt_set_info *set_info = data; + + if (data_len != sizeof(*set_info)) { + fprintf(stderr, "xt_set_info struct size mismatch\n"); + return -1; + } + + if (get_set_byid(setname, set_info->index)) + return -1; + fputs(setname, fd); + for (i = 1; i <= set_info->dim; i++) { + fprintf(fd, "%s%s", i == 1 ? " " : ",", set_info->flags & (1 << i) ? "src" : "dst"); + } + + return 0; +} + +struct ematch_util ipset_ematch_util = { + .kind = "ipset", + .kind_num = TCF_EM_IPSET, + .parse_eopt = ipset_parse_eopt, + .print_eopt = ipset_print_eopt, + .print_usage = ipset_print_usage +}; diff --git a/tc/em_ipt.c b/tc/em_ipt.c new file mode 100644 index 0000000..69efefd --- /dev/null +++ b/tc/em_ipt.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * em_ipt.c IPtables extensions matching Ematch + * + * (C) 2018 Eyal Birger <eyal.birger@gmail.com> + */ + +#include <getopt.h> + +#include <linux/tc_ematch/tc_em_ipt.h> +#include <linux/pkt_cls.h> +#include <xtables.h> +#include "m_ematch.h" + +static void em_ipt_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: ipt([-6] -m MATCH_NAME [MATCH_OPTS])\n" + "Example: 'ipt(-m policy --reqid 1 --pol ipsec --dir in)'\n"); +} + +static struct option original_opts[] = { + { + .name = "match", + .has_arg = 1, + .val = 'm' + }, + { + .name = "ipv6", + .val = '6' + }, + {} +}; + +static struct xtables_globals em_tc_ipt_globals = { + .option_offset = 0, + .program_name = "tc-em-ipt", + .program_version = "0.1", + .orig_opts = original_opts, + .opts = original_opts, +#if (XTABLES_VERSION_CODE >= 11) + .compat_rev = xtables_compatible_revision, +#endif +}; + +static struct xt_entry_match *fake_xt_entry_match(int data_size, void *data) +{ + struct xt_entry_match *m; + + m = xtables_calloc(1, XT_ALIGN(sizeof(*m)) + data_size); + if (!m) + return NULL; + + if (data) + memcpy(m->data, data, data_size); + + m->u.user.match_size = data_size; + return m; +} + +static void scrub_match(struct xtables_match *match) +{ + match->mflags = 0; + free(match->m); + match->m = NULL; +} + +/* IPv4 and IPv6 share the same hooking enumeration */ +#define HOOK_PRE_ROUTING 0 +#define HOOK_POST_ROUTING 4 + +static __u32 em_ipt_hook(struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + + if (t->tcm_parent != TC_H_ROOT && + t->tcm_parent == TC_H_MAJ(TC_H_INGRESS)) + return HOOK_PRE_ROUTING; + + return HOOK_POST_ROUTING; +} + +static int em_ipt_parse_eopt_argv(struct nlmsghdr *n, + struct tcf_ematch_hdr *hdr, + int argc, char **argv) +{ + struct xtables_globals tmp_tcipt_globals = em_tc_ipt_globals; + struct xtables_match *match = NULL; + __u8 nfproto = NFPROTO_IPV4; + + while (1) { + struct option *opts; + int c; + + c = getopt_long(argc, argv, "6m:", tmp_tcipt_globals.opts, + NULL); + if (c == -1) + break; + + switch (c) { + case 'm': + xtables_init_all(&tmp_tcipt_globals, nfproto); + + match = xtables_find_match(optarg, XTF_TRY_LOAD, NULL); + if (!match || !match->x6_parse) { + fprintf(stderr, " failed to find match %s\n\n", + optarg); + return -1; + } + + match->m = fake_xt_entry_match(match->size, NULL); + if (!match->m) { + printf(" %s error\n", match->name); + return -1; + } + + if (match->init) + match->init(match->m); + + opts = xtables_options_xfrm(tmp_tcipt_globals.orig_opts, + tmp_tcipt_globals.opts, + match->x6_options, + &match->option_offset); + if (!opts) { + scrub_match(match); + return -1; + } + + tmp_tcipt_globals.opts = opts; + break; + + case '6': + nfproto = NFPROTO_IPV6; + break; + + default: + if (!match) { + fprintf(stderr, "failed to find match %s\n\n", + optarg); + return -1; + + } + xtables_option_mpcall(c, argv, 0, match, NULL); + break; + } + } + + if (!match) { + fprintf(stderr, " failed to parse parameters (%s)\n", *argv); + return -1; + } + + /* check that we passed the correct parameters to the match */ + xtables_option_mfcall(match); + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addattr32(n, MAX_MSG, TCA_EM_IPT_HOOK, em_ipt_hook(n)); + addattrstrz(n, MAX_MSG, TCA_EM_IPT_MATCH_NAME, match->name); + addattr8(n, MAX_MSG, TCA_EM_IPT_MATCH_REVISION, match->revision); + addattr8(n, MAX_MSG, TCA_EM_IPT_NFPROTO, nfproto); + addattr_l(n, MAX_MSG, TCA_EM_IPT_MATCH_DATA, match->m->data, + match->size); + + xtables_free_opts(1); + + scrub_match(match); + return 0; +} + +static int em_ipt_print_epot(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + struct rtattr *tb[TCA_EM_IPT_MAX + 1]; + struct xtables_match *match; + const char *mname; + __u8 nfproto; + + if (parse_rtattr(tb, TCA_EM_IPT_MAX, data, data_len) < 0) + return -1; + + nfproto = rta_getattr_u8(tb[TCA_EM_IPT_NFPROTO]); + + xtables_init_all(&em_tc_ipt_globals, nfproto); + + mname = rta_getattr_str(tb[TCA_EM_IPT_MATCH_NAME]); + match = xtables_find_match(mname, XTF_TRY_LOAD, NULL); + if (!match) + return -1; + + match->m = fake_xt_entry_match(RTA_PAYLOAD(tb[TCA_EM_IPT_MATCH_DATA]), + RTA_DATA(tb[TCA_EM_IPT_MATCH_DATA])); + if (!match->m) + return -1; + + match->print(NULL, match->m, 0); + + scrub_match(match); + return 0; +} + +struct ematch_util ipt_ematch_util = { + .kind = "ipt", + .kind_num = TCF_EM_IPT, + .parse_eopt_argv = em_ipt_parse_eopt_argv, + .print_eopt = em_ipt_print_epot, + .print_usage = em_ipt_print_usage +}; diff --git a/tc/em_meta.c b/tc/em_meta.c new file mode 100644 index 0000000..6a5654f --- /dev/null +++ b/tc/em_meta.c @@ -0,0 +1,541 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * em_meta.c Metadata Ematch + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <errno.h> + +#include "m_ematch.h" +#include <linux/tc_ematch/tc_em_meta.h> + +extern struct ematch_util meta_ematch_util; + +static void meta_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: meta(OBJECT { eq | lt | gt } OBJECT)\n" \ + "where: OBJECT := { META_ID | VALUE }\n" \ + " META_ID := id [ shift SHIFT ] [ mask MASK ]\n" \ + "\n" \ + "Example: meta(nf_mark gt 24)\n" \ + " meta(indev shift 1 eq \"ppp\")\n" \ + " meta(tcindex mask 0xf0 eq 0xf0)\n" \ + "\n" \ + "For a list of meta identifiers, use meta(list).\n"); +} + +static const struct meta_entry { + int id; + char *kind; + char *mask; + char *desc; +} meta_table[] = { +#define TCF_META_ID_SECTION 0 +#define __A(id, name, mask, desc) { TCF_META_ID_##id, name, mask, desc } + __A(SECTION, "Generic", "", ""), + __A(RANDOM, "random", "i", + "Random value (32 bit)"), + __A(LOADAVG_0, "loadavg_1", "i", + "Load average in last minute"), + __A(LOADAVG_1, "loadavg_5", "i", + "Load average in last 5 minutes"), + __A(LOADAVG_2, "loadavg_15", "i", + "Load average in last 15 minutes"), + + __A(SECTION, "Interfaces", "", ""), + __A(DEV, "dev", "iv", + "Device the packet is on"), + __A(SECTION, "Packet attributes", "", ""), + __A(PRIORITY, "priority", "i", + "Priority of packet"), + __A(PROTOCOL, "protocol", "i", + "Link layer protocol"), + __A(PKTTYPE, "pkt_type", "i", + "Packet type (uni|multi|broad|...)cast"), + __A(PKTLEN, "pkt_len", "i", + "Length of packet"), + __A(DATALEN, "data_len", "i", + "Length of data in packet"), + __A(MACLEN, "mac_len", "i", + "Length of link layer header"), + + __A(SECTION, "Netfilter", "", ""), + __A(NFMARK, "nf_mark", "i", + "Netfilter mark"), + __A(NFMARK, "fwmark", "i", + "Alias for nf_mark"), + + __A(SECTION, "Traffic Control", "", ""), + __A(TCINDEX, "tc_index", "i", "TC Index"), + __A(SECTION, "Routing", "", ""), + __A(RTCLASSID, "rt_classid", "i", + "Routing ClassID (cls_route)"), + __A(RTIIF, "rt_iif", "i", + "Incoming interface index"), + __A(VLAN_TAG, "vlan", "i", "Vlan tag"), + + __A(SECTION, "Sockets", "", ""), + __A(SK_FAMILY, "sk_family", "i", "Address family"), + __A(SK_STATE, "sk_state", "i", "State"), + __A(SK_REUSE, "sk_reuse", "i", "Reuse Flag"), + __A(SK_BOUND_IF, "sk_bind_if", "iv", "Bound interface"), + __A(SK_REFCNT, "sk_refcnt", "i", "Reference counter"), + __A(SK_SHUTDOWN, "sk_shutdown", "i", "Shutdown mask"), + __A(SK_PROTO, "sk_proto", "i", "Protocol"), + __A(SK_TYPE, "sk_type", "i", "Type"), + __A(SK_RCVBUF, "sk_rcvbuf", "i", "Receive buffer size"), + __A(SK_RMEM_ALLOC, "sk_rmem", "i", "RMEM"), + __A(SK_WMEM_ALLOC, "sk_wmem", "i", "WMEM"), + __A(SK_OMEM_ALLOC, "sk_omem", "i", "OMEM"), + __A(SK_WMEM_QUEUED, "sk_wmem_queue", "i", "WMEM queue"), + __A(SK_SND_QLEN, "sk_snd_queue", "i", "Send queue length"), + __A(SK_RCV_QLEN, "sk_rcv_queue", "i", "Receive queue length"), + __A(SK_ERR_QLEN, "sk_err_queue", "i", "Error queue length"), + __A(SK_FORWARD_ALLOCS, "sk_fwd_alloc", "i", "Forward allocations"), + __A(SK_SNDBUF, "sk_sndbuf", "i", "Send buffer size"), +#undef __A +}; + +static inline int map_type(char k) +{ + switch (k) { + case 'i': return TCF_META_TYPE_INT; + case 'v': return TCF_META_TYPE_VAR; + } + + fprintf(stderr, "BUG: Unknown map character '%c'\n", k); + return INT_MAX; +} + +static const struct meta_entry *lookup_meta_entry(struct bstr *kind) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(meta_table); i++) + if (!bstrcmp(kind, meta_table[i].kind) && + meta_table[i].id != 0) + return &meta_table[i]; + + return NULL; +} + +static const struct meta_entry *lookup_meta_entry_byid(int id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(meta_table); i++) + if (meta_table[i].id == id) + return &meta_table[i]; + + return NULL; +} + +static inline void dump_value(struct nlmsghdr *n, int tlv, unsigned long val, + struct tcf_meta_val *hdr) +{ + __u32 t; + + switch (TCF_META_TYPE(hdr->kind)) { + case TCF_META_TYPE_INT: + t = val; + addattr_l(n, MAX_MSG, tlv, &t, sizeof(t)); + break; + + case TCF_META_TYPE_VAR: + if (TCF_META_ID(hdr->kind) == TCF_META_ID_VALUE) { + struct bstr *a = (struct bstr *) val; + + addattr_l(n, MAX_MSG, tlv, a->data, a->len); + } + break; + } +} + +static inline int is_compatible(struct tcf_meta_val *what, + struct tcf_meta_val *needed) +{ + const struct meta_entry *entry; + char *p; + + entry = lookup_meta_entry_byid(TCF_META_ID(what->kind)); + + if (entry == NULL) + return 0; + + for (p = entry->mask; p; p++) + if (map_type(*p) == TCF_META_TYPE(needed->kind)) + return 1; + + return 0; +} + +static void list_meta_ids(FILE *fd) +{ + int i; + + fprintf(fd, + "--------------------------------------------------------\n" \ + " ID Type Description\n" \ + "--------------------------------------------------------"); + + for (i = 0; i < ARRAY_SIZE(meta_table); i++) { + if (meta_table[i].id == TCF_META_ID_SECTION) { + fprintf(fd, "\n%s:\n", meta_table[i].kind); + } else { + char *p = meta_table[i].mask; + char buf[64] = {0}; + + fprintf(fd, " %-16s ", meta_table[i].kind); + + while (*p) { + int type = map_type(*p); + + switch (type) { + case TCF_META_TYPE_INT: + strcat(buf, "INT"); + break; + + case TCF_META_TYPE_VAR: + strcat(buf, "VAR"); + break; + } + + if (*(++p)) + strcat(buf, ","); + } + + fprintf(fd, "%-10s %s\n", buf, meta_table[i].desc); + } + } + + fprintf(fd, + "--------------------------------------------------------\n"); +} + +#undef TCF_META_ID_SECTION + +#define PARSE_FAILURE ((void *) (-1)) + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &meta_ematch_util, FMT, ##ARGS) + +static inline int can_adopt(struct tcf_meta_val *val) +{ + return !!TCF_META_ID(val->kind); +} + +static inline int overwrite_type(struct tcf_meta_val *src, + struct tcf_meta_val *dst) +{ + return (TCF_META_TYPE(dst->kind) << 12) | TCF_META_ID(src->kind); +} + + +static inline struct bstr * +parse_object(struct bstr *args, struct bstr *arg, struct tcf_meta_val *obj, + unsigned long *dst, struct tcf_meta_val *left) +{ + const struct meta_entry *entry; + unsigned long num; + struct bstr *a; + + if (arg->quoted) { + obj->kind = TCF_META_TYPE_VAR << 12; + obj->kind |= TCF_META_ID_VALUE; + *dst = (unsigned long) arg; + return bstr_next(arg); + } + + num = bstrtoul(arg); + if (num != ULONG_MAX) { + obj->kind = TCF_META_TYPE_INT << 12; + obj->kind |= TCF_META_ID_VALUE; + *dst = (unsigned long) num; + return bstr_next(arg); + } + + entry = lookup_meta_entry(arg); + + if (entry == NULL) { + PARSE_ERR(arg, "meta: unknown meta id\n"); + return PARSE_FAILURE; + } + + obj->kind = entry->id | (map_type(entry->mask[0]) << 12); + + if (left) { + struct tcf_meta_val *right = obj; + + if (TCF_META_TYPE(right->kind) == TCF_META_TYPE(left->kind)) + goto compatible; + + if (can_adopt(left) && !can_adopt(right)) { + if (is_compatible(left, right)) + left->kind = overwrite_type(left, right); + else + goto not_compatible; + } else if (can_adopt(right) && !can_adopt(left)) { + if (is_compatible(right, left)) + right->kind = overwrite_type(right, left); + else + goto not_compatible; + } else if (can_adopt(left) && can_adopt(right)) { + if (is_compatible(left, right)) + left->kind = overwrite_type(left, right); + else if (is_compatible(right, left)) + right->kind = overwrite_type(right, left); + else + goto not_compatible; + } else + goto not_compatible; + } + +compatible: + + a = bstr_next(arg); + + while (a) { + if (!bstrcmp(a, "shift")) { + unsigned long shift; + + if (a->next == NULL) { + PARSE_ERR(a, "meta: missing argument"); + return PARSE_FAILURE; + } + a = bstr_next(a); + + shift = bstrtoul(a); + if (shift == ULONG_MAX) { + PARSE_ERR(a, "meta: invalid shift, must " \ + "be numeric"); + return PARSE_FAILURE; + } + + obj->shift = (__u8) shift; + a = bstr_next(a); + } else if (!bstrcmp(a, "mask")) { + unsigned long mask; + + if (a->next == NULL) { + PARSE_ERR(a, "meta: missing argument"); + return PARSE_FAILURE; + } + a = bstr_next(a); + + mask = bstrtoul(a); + if (mask == ULONG_MAX) { + PARSE_ERR(a, "meta: invalid mask, must be " \ + "numeric"); + return PARSE_FAILURE; + } + *dst = (unsigned long) mask; + a = bstr_next(a); + } else + break; + } + + return a; + +not_compatible: + PARSE_ERR(arg, "lvalue and rvalue are not compatible."); + return PARSE_FAILURE; +} + +static int meta_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + int opnd; + struct bstr *a; + struct tcf_meta_hdr meta_hdr = {}; + unsigned long lvalue = 0, rvalue = 0; + + if (args == NULL) + return PARSE_ERR(args, "meta: missing arguments"); + + if (!bstrcmp(args, "list")) { + list_meta_ids(stderr); + return -1; + } + + a = parse_object(args, args, &meta_hdr.left, &lvalue, NULL); + if (a == PARSE_FAILURE) + return -1; + else if (a == NULL) + return PARSE_ERR(args, "meta: missing operand"); + + if (!bstrcmp(a, "eq")) + opnd = TCF_EM_OPND_EQ; + else if (!bstrcmp(a, "gt")) + opnd = TCF_EM_OPND_GT; + else if (!bstrcmp(a, "lt")) + opnd = TCF_EM_OPND_LT; + else + return PARSE_ERR(a, "meta: invalid operand"); + + meta_hdr.left.op = (__u8) opnd; + + if (a->next == NULL) + return PARSE_ERR(args, "meta: missing rvalue"); + a = bstr_next(a); + + a = parse_object(args, a, &meta_hdr.right, &rvalue, &meta_hdr.left); + if (a == PARSE_FAILURE) + return -1; + else if (a != NULL) + return PARSE_ERR(a, "meta: unexpected trailer"); + + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + + addattr_l(n, MAX_MSG, TCA_EM_META_HDR, &meta_hdr, sizeof(meta_hdr)); + + dump_value(n, TCA_EM_META_LVALUE, lvalue, &meta_hdr.left); + dump_value(n, TCA_EM_META_RVALUE, rvalue, &meta_hdr.right); + + return 0; +} +#undef PARSE_ERR + +static inline void print_binary(FILE *fd, unsigned char *str, int len) +{ + int i; + + for (i = 0; i < len; i++) + if (!isprint(str[i])) + goto binary; + + for (i = 0; i < len; i++) + fprintf(fd, "%c", str[i]); + return; + +binary: + for (i = 0; i < len; i++) + fprintf(fd, "%02x ", str[i]); + + fprintf(fd, "\""); + for (i = 0; i < len; i++) + fprintf(fd, "%c", isprint(str[i]) ? str[i] : '.'); + fprintf(fd, "\""); +} + +static inline int print_value(FILE *fd, int type, struct rtattr *rta) +{ + if (rta == NULL) { + fprintf(stderr, "Missing value TLV\n"); + return -1; + } + + switch (type) { + case TCF_META_TYPE_INT: + if (RTA_PAYLOAD(rta) < sizeof(__u32)) { + fprintf(stderr, "meta int type value TLV " \ + "size mismatch.\n"); + return -1; + } + fprintf(fd, "%d", rta_getattr_u32(rta)); + break; + + case TCF_META_TYPE_VAR: + print_binary(fd, RTA_DATA(rta), RTA_PAYLOAD(rta)); + break; + } + + return 0; +} + +static int print_object(FILE *fd, struct tcf_meta_val *obj, struct rtattr *rta) +{ + int id = TCF_META_ID(obj->kind); + int type = TCF_META_TYPE(obj->kind); + const struct meta_entry *entry; + + if (id == TCF_META_ID_VALUE) + return print_value(fd, type, rta); + + entry = lookup_meta_entry_byid(id); + + if (entry == NULL) + fprintf(fd, "[unknown meta id %d]", id); + else + fprintf(fd, "%s", entry->kind); + + if (obj->shift) + fprintf(fd, " shift %d", obj->shift); + + switch (type) { + case TCF_META_TYPE_INT: + if (rta) { + if (RTA_PAYLOAD(rta) < sizeof(__u32)) + goto size_mismatch; + + if (rta_getattr_u32(rta)) + fprintf(fd, " mask 0x%08x", + rta_getattr_u32(rta)); + } + break; + } + + return 0; + +size_mismatch: + fprintf(stderr, "meta int type mask TLV size mismatch\n"); + return -1; +} + + +static int meta_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + struct rtattr *tb[TCA_EM_META_MAX+1]; + struct tcf_meta_hdr *meta_hdr; + + if (parse_rtattr(tb, TCA_EM_META_MAX, data, data_len) < 0) + return -1; + + if (tb[TCA_EM_META_HDR] == NULL) { + fprintf(stderr, "Missing meta header\n"); + return -1; + } + + if (RTA_PAYLOAD(tb[TCA_EM_META_HDR]) < sizeof(*meta_hdr)) { + fprintf(stderr, "Meta header size mismatch\n"); + return -1; + } + + meta_hdr = RTA_DATA(tb[TCA_EM_META_HDR]); + + if (print_object(fd, &meta_hdr->left, tb[TCA_EM_META_LVALUE]) < 0) + return -1; + + switch (meta_hdr->left.op) { + case TCF_EM_OPND_EQ: + fprintf(fd, " eq "); + break; + case TCF_EM_OPND_LT: + fprintf(fd, " lt "); + break; + case TCF_EM_OPND_GT: + fprintf(fd, " gt "); + break; + } + + return print_object(fd, &meta_hdr->right, tb[TCA_EM_META_RVALUE]); +} + +struct ematch_util meta_ematch_util = { + .kind = "meta", + .kind_num = TCF_EM_META, + .parse_eopt = meta_parse_eopt, + .print_eopt = meta_print_eopt, + .print_usage = meta_print_usage +}; diff --git a/tc/em_nbyte.c b/tc/em_nbyte.c new file mode 100644 index 0000000..9f421fb --- /dev/null +++ b/tc/em_nbyte.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * em_nbyte.c N-Byte Ematch + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <errno.h> + +#include "m_ematch.h" +#include <linux/tc_ematch/tc_em_nbyte.h> + +extern struct ematch_util nbyte_ematch_util; + +static void nbyte_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: nbyte(NEEDLE at OFFSET [layer LAYER])\n" \ + "where: NEEDLE := { string | \"c-escape-sequence\" }\n" \ + " OFFSET := int\n" \ + " LAYER := { link | network | transport | 0..%d }\n" \ + "\n" \ + "Example: nbyte(\"ababa\" at 12 layer 1)\n", + TCF_LAYER_MAX); +} + +static int nbyte_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + struct bstr *a; + struct bstr *needle = args; + unsigned long offset = 0, layer = TCF_LAYER_NETWORK; + int offset_present = 0; + struct tcf_em_nbyte nb = {}; + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &nbyte_ematch_util, FMT, ##ARGS) + + if (args == NULL) + return PARSE_ERR(args, "nbyte: missing arguments"); + + if (needle->len <= 0) + return PARSE_ERR(args, "nbyte: needle length is 0"); + + for (a = bstr_next(args); a; a = bstr_next(a)) { + if (!bstrcmp(a, "at")) { + if (a->next == NULL) + return PARSE_ERR(a, "nbyte: missing argument"); + a = bstr_next(a); + + offset = bstrtoul(a); + if (offset == ULONG_MAX) + return PARSE_ERR(a, "nbyte: invalid offset, " \ + "must be numeric"); + + offset_present = 1; + } else if (!bstrcmp(a, "layer")) { + if (a->next == NULL) + return PARSE_ERR(a, "nbyte: missing argument"); + a = bstr_next(a); + + layer = parse_layer(a); + if (layer == INT_MAX) { + layer = bstrtoul(a); + if (layer == ULONG_MAX) + return PARSE_ERR(a, "nbyte: invalid " \ + "layer"); + } + + if (layer > TCF_LAYER_MAX) + return PARSE_ERR(a, "nbyte: illegal layer, " \ + "must be in 0..%d", TCF_LAYER_MAX); + } else + return PARSE_ERR(a, "nbyte: unknown parameter"); + } + + if (offset_present == 0) + return PARSE_ERR(a, "nbyte: offset required"); + + nb.len = needle->len; + nb.layer = (__u8) layer; + nb.off = (__u16) offset; + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addraw_l(n, MAX_MSG, &nb, sizeof(nb)); + addraw_l(n, MAX_MSG, needle->data, needle->len); + +#undef PARSE_ERR + return 0; +} + +static int nbyte_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + int i; + struct tcf_em_nbyte *nb = data; + __u8 *needle; + + if (data_len < sizeof(*nb)) { + fprintf(stderr, "NByte header size mismatch\n"); + return -1; + } + + if (data_len < sizeof(*nb) + nb->len) { + fprintf(stderr, "NByte payload size mismatch\n"); + return -1; + } + + needle = data + sizeof(*nb); + + for (i = 0; i < nb->len; i++) + fprintf(fd, "%02x ", needle[i]); + + fprintf(fd, "\""); + for (i = 0; i < nb->len; i++) + fprintf(fd, "%c", isprint(needle[i]) ? needle[i] : '.'); + fprintf(fd, "\" at %d layer %d", nb->off, nb->layer); + + return 0; +} + +struct ematch_util nbyte_ematch_util = { + .kind = "nbyte", + .kind_num = TCF_EM_NBYTE, + .parse_eopt = nbyte_parse_eopt, + .print_eopt = nbyte_print_eopt, + .print_usage = nbyte_print_usage +}; diff --git a/tc/em_u32.c b/tc/em_u32.c new file mode 100644 index 0000000..a83382b --- /dev/null +++ b/tc/em_u32.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * em_u32.c U32 Ematch + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <errno.h> + +#include "m_ematch.h" + +extern struct ematch_util u32_ematch_util; + +static void u32_print_usage(FILE *fd) +{ + fprintf(fd, + "Usage: u32(ALIGN VALUE MASK at [ nexthdr+ ] OFFSET)\n" \ + "where: ALIGN := { u8 | u16 | u32 }\n" \ + "\n" \ + "Example: u32(u16 0x1122 0xffff at nexthdr+4)\n"); +} + +static int u32_parse_eopt(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct bstr *args) +{ + struct bstr *a; + int align, nh_len; + unsigned long key, mask, offmask = 0, offset; + struct tc_u32_key u_key = {}; + +#define PARSE_ERR(CARG, FMT, ARGS...) \ + em_parse_error(EINVAL, args, CARG, &u32_ematch_util, FMT, ##ARGS) + + if (args == NULL) + return PARSE_ERR(args, "u32: missing arguments"); + + if (!bstrcmp(args, "u8")) + align = 1; + else if (!bstrcmp(args, "u16")) + align = 2; + else if (!bstrcmp(args, "u32")) + align = 4; + else + return PARSE_ERR(args, "u32: invalid alignment"); + + a = bstr_next(args); + if (a == NULL) + return PARSE_ERR(a, "u32: missing key"); + + key = bstrtoul(a); + if (key == ULONG_MAX) + return PARSE_ERR(a, "u32: invalid key, must be numeric"); + + a = bstr_next(a); + if (a == NULL) + return PARSE_ERR(a, "u32: missing mask"); + + mask = bstrtoul(a); + if (mask == ULONG_MAX) + return PARSE_ERR(a, "u32: invalid mask, must be numeric"); + + a = bstr_next(a); + if (a == NULL || bstrcmp(a, "at") != 0) + return PARSE_ERR(a, "u32: missing \"at\""); + + a = bstr_next(a); + if (a == NULL) + return PARSE_ERR(a, "u32: missing offset"); + + nh_len = strlen("nexthdr+"); + if (a->len > nh_len && !memcmp(a->data, "nexthdr+", nh_len)) { + char buf[a->len - nh_len + 1]; + + offmask = -1; + strncpy(buf, a->data + nh_len, a->len - nh_len + 1); + offset = strtoul(buf, NULL, 0); + } else if (!bstrcmp(a, "nexthdr+")) { + a = bstr_next(a); + if (a == NULL) + return PARSE_ERR(a, "u32: missing offset"); + offset = bstrtoul(a); + } else + offset = bstrtoul(a); + + if (offset == ULONG_MAX) + return PARSE_ERR(a, "u32: invalid offset"); + + if (a->next) + return PARSE_ERR(a->next, "u32: unexpected trailer"); + + switch (align) { + case 1: + if (key > 0xFF) + return PARSE_ERR(a, "Illegal key (>0xFF)"); + if (mask > 0xFF) + return PARSE_ERR(a, "Illegal mask (>0xFF)"); + + key <<= 24 - ((offset & 3) * 8); + mask <<= 24 - ((offset & 3) * 8); + offset &= ~3; + break; + + case 2: + if (key > 0xFFFF) + return PARSE_ERR(a, "Illegal key (>0xFFFF)"); + if (mask > 0xFFFF) + return PARSE_ERR(a, "Illegal mask (>0xFFFF)"); + + if ((offset & 3) == 0) { + key <<= 16; + mask <<= 16; + } + offset &= ~3; + break; + } + + key = htonl(key); + mask = htonl(mask); + + if (offset % 4) + return PARSE_ERR(a, "u32: invalid offset alignment, " \ + "must be aligned to 4."); + + key &= mask; + + u_key.mask = mask; + u_key.val = key; + u_key.off = offset; + u_key.offmask = offmask; + + addraw_l(n, MAX_MSG, hdr, sizeof(*hdr)); + addraw_l(n, MAX_MSG, &u_key, sizeof(u_key)); + +#undef PARSE_ERR + return 0; +} + +static int u32_print_eopt(FILE *fd, struct tcf_ematch_hdr *hdr, void *data, + int data_len) +{ + struct tc_u32_key *u_key = data; + + if (data_len < sizeof(*u_key)) { + fprintf(stderr, "U32 header size mismatch\n"); + return -1; + } + + fprintf(fd, "%08x/%08x at %s%d", + (unsigned int) ntohl(u_key->val), + (unsigned int) ntohl(u_key->mask), + u_key->offmask ? "nexthdr+" : "", + u_key->off); + + return 0; +} + +struct ematch_util u32_ematch_util = { + .kind = "u32", + .kind_num = TCF_EM_U32, + .parse_eopt = u32_parse_eopt, + .print_eopt = u32_print_eopt, + .print_usage = u32_print_usage +}; diff --git a/tc/emp_ematch.l b/tc/emp_ematch.l new file mode 100644 index 0000000..2f4926d --- /dev/null +++ b/tc/emp_ematch.l @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +%{ + #include "emp_ematch.tab.h" + #include "m_ematch.h" + + extern int ematch_argc; + extern char **ematch_argv; + + #define yylval ematch_lval + + #define NEXT_EM_ARG() do { ematch_argc--; ematch_argv++; } while(0); + + #define YY_INPUT(buf, result, max_size) \ + { \ + next: \ + if (ematch_argc <= 0) \ + result = YY_NULL; \ + else if (**ematch_argv == '\0') { \ + NEXT_EM_ARG(); \ + goto next; \ + } else { \ + if (max_size <= strlen(*ematch_argv) + 1) { \ + fprintf(stderr, "match argument too long.\n"); \ + result = YY_NULL; \ + } else { \ + strcpy(buf, *ematch_argv); \ + result = strlen(*ematch_argv) + 1; \ + buf[result-1] = ' '; \ + buf[result] = '\0'; \ + NEXT_EM_ARG(); \ + } \ + } \ + } + + static void __attribute__ ((unused)) yyunput (int c,char *buf_ptr ); + static void __attribute__ ((unused)) yy_push_state (int new_state ); + static void __attribute__ ((unused)) yy_pop_state (void); + static int __attribute__ ((unused)) yy_top_state (void ); + + static char *strbuf; + static unsigned int strbuf_size; + static unsigned int strbuf_index; + + static void strbuf_enlarge(void) + { + strbuf_size += 512; + strbuf = realloc(strbuf, strbuf_size); + } + + static void strbuf_append_char(char c) + { + while (strbuf_index >= strbuf_size) + strbuf_enlarge(); + strbuf[strbuf_index++] = c; + } + + static void strbuf_append_charp(char *s) + { + while (strbuf_index >= strbuf_size) + strbuf_enlarge(); + memcpy(strbuf + strbuf_index, s, strlen(s)); + strbuf_index += strlen(s); + } + +%} + +%x lexstr + +%option 8bit stack warn noyywrap prefix="ematch_" +%% +[ \t\r\n]+ + +\" { + if (strbuf == NULL) { + strbuf_size = 512; + strbuf = calloc(1, strbuf_size); + if (strbuf == NULL) + return ERROR; + } + strbuf_index = 0; + + BEGIN(lexstr); + } + +<lexstr>\" { + BEGIN(INITIAL); + yylval.b = bstr_new(strbuf, strbuf_index); + yylval.b->quoted = 1; + return ATTRIBUTE; + } + +<lexstr>\\[0-7]{1,3} { /* octal escape sequence */ + int res; + + sscanf(yytext + 1, "%o", &res); + if (res > 0xFF) { + fprintf(stderr, "error: octal escape sequence" \ + " out of range\n"); + return ERROR; + } + strbuf_append_char((unsigned char) res); + } + +<lexstr>\\[0-9]+ { /* catch wrong octal escape seq. */ + fprintf(stderr, "error: invalid octale escape sequence\n"); + return ERROR; + } + +<lexstr>\\x[0-9a-fA-F]{1,2} { + int res; + + sscanf(yytext + 2, "%x", &res); + + if (res > 0xFF) { + fprintf(stderr, "error: hexadecimal escape " \ + "sequence out of range\n"); + return ERROR; + } + strbuf_append_char((unsigned char) res); + } + +<lexstr>\\n strbuf_append_char('\n'); +<lexstr>\\r strbuf_append_char('\r'); +<lexstr>\\t strbuf_append_char('\t'); +<lexstr>\\v strbuf_append_char('\v'); +<lexstr>\\b strbuf_append_char('\b'); +<lexstr>\\f strbuf_append_char('\f'); +<lexstr>\\a strbuf_append_char('\a'); + +<lexstr>\\(.|\n) strbuf_append_char(yytext[1]); +<lexstr>[^\\\n\"]+ strbuf_append_charp(yytext); + +[aA][nN][dD] return AND; +[oO][rR] return OR; +[nN][oO][tT] return NOT; +"(" | +")" { + return yylval.i = *yytext; + } +[^" \t\r\n()][^ \t\r\n()]* { + yylval.b = bstr_alloc(yytext); + if (yylval.b == NULL) + return ERROR; + return ATTRIBUTE; + } +%% diff --git a/tc/emp_ematch.y b/tc/emp_ematch.y new file mode 100644 index 0000000..716877b --- /dev/null +++ b/tc/emp_ematch.y @@ -0,0 +1,96 @@ +%{ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + #include <stdio.h> + #include <stdlib.h> + #include <malloc.h> + #include <string.h> + #include "m_ematch.h" +%} + +%union { + unsigned int i; + struct bstr *b; + struct ematch *e; +} + +%{ + extern int ematch_lex(void); + extern void yyerror(const char *s); + extern struct ematch *ematch_root; + extern char *ematch_err; +%} + +%token <i> ERROR +%token <b> ATTRIBUTE +%token <i> AND OR NOT +%type <i> invert relation +%type <e> match expr +%type <b> args +%right AND OR +%start input +%% +input: + /* empty */ + | expr + { ematch_root = $1; } + | expr error + { + ematch_root = $1; + YYACCEPT; + } + ; + +expr: + match + { $$ = $1; } + | match relation expr + { + $1->relation = $2; + $1->next = $3; + $$ = $1; + } + ; + +match: + invert ATTRIBUTE '(' args ')' + { + $2->next = $4; + $$ = new_ematch($2, $1); + if ($$ == NULL) + YYABORT; + } + | invert '(' expr ')' + { + $$ = new_ematch(NULL, $1); + if ($$ == NULL) + YYABORT; + $$->child = $3; + } + ; + +args: + ATTRIBUTE + { $$ = $1; } + | ATTRIBUTE args + { $1->next = $2; } + ; + +relation: + AND + { $$ = TCF_EM_REL_AND; } + | OR + { $$ = TCF_EM_REL_OR; } + ; + +invert: + /* empty */ + { $$ = 0; } + | NOT + { $$ = 1; } + ; +%% + + void yyerror(const char *s) + { + ematch_err = strdup(s); + } diff --git a/tc/f_basic.c b/tc/f_basic.c new file mode 100644 index 0000000..1ceb15d --- /dev/null +++ b/tc/f_basic.c @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_basic.c Basic Classifier + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <linux/if.h> + +#include "utils.h" +#include "tc_util.h" +#include "m_ematch.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... basic [ match EMATCH_TREE ]\n" + " [ action ACTION_SPEC ] [ classid CLASSID ]\n" + "\n" + "Where: SELECTOR := SAMPLE SAMPLE ...\n" + " FILTERID := X:Y:Z\n" + " ACTION_SPEC := ... look at individual actions\n" + "\n" + "NOTE: CLASSID is parsed as hexadecimal input.\n"); +} + +static int basic_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + long h = 0; + + if (handle) { + h = strtol(handle, NULL, 0); + if (h == LONG_MIN || h == LONG_MAX) { + fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n", + handle); + return -1; + } + } + t->tcm_handle = h; + + if (argc == 0) + return 0; + + tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len)); + addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0); + + while (argc > 0) { + if (matches(*argv, "match") == 0) { + NEXT_ARG(); + if (parse_ematch(&argc, &argv, TCA_BASIC_EMATCHES, n)) { + fprintf(stderr, "Illegal \"ematch\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "classid") == 0 || + strcmp(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + if (get_tc_classid(&classid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_BASIC_CLASSID, &classid, 4); + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_BASIC_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_BASIC_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail; + return 0; +} + +static int basic_print_opt(struct filter_util *qu, FILE *f, + struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_BASIC_MAX+1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_BASIC_MAX, opt); + + if (handle) + print_hex(PRINT_ANY, "handle", + "handle 0x%x ", handle); + + if (tb[TCA_BASIC_CLASSID]) { + uint32_t classid = rta_getattr_u32(tb[TCA_BASIC_CLASSID]); + SPRINT_BUF(b1); + + print_string(PRINT_ANY, "flowid", "flowid %s ", + sprint_tc_classid(classid, b1)); + } + + if (tb[TCA_BASIC_EMATCHES]) + print_ematch(f, tb[TCA_BASIC_EMATCHES]); + + if (tb[TCA_BASIC_POLICE]) { + print_nl(); + tc_print_police(f, tb[TCA_BASIC_POLICE]); + } + + if (tb[TCA_BASIC_ACT]) { + tc_print_action(f, tb[TCA_BASIC_ACT], 0); + } + + return 0; +} + +struct filter_util basic_filter_util = { + .id = "basic", + .parse_fopt = basic_parse_opt, + .print_fopt = basic_print_opt, +}; diff --git a/tc/f_bpf.c b/tc/f_bpf.c new file mode 100644 index 0000000..a6d4875 --- /dev/null +++ b/tc/f_bpf.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_bpf.c BPF-based Classifier + * + * Authors: Daniel Borkmann <daniel@iogearbox.net> + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <linux/bpf.h> + +#include "utils.h" + +#include "tc_util.h" +#include "bpf_util.h" + +static const enum bpf_prog_type bpf_type = BPF_PROG_TYPE_SCHED_CLS; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... bpf ...\n" + "\n" + "BPF use case:\n" + " bytecode BPF_BYTECODE\n" + " bytecode-file FILE\n" + "\n" + "eBPF use case:\n" + " object-file FILE [ section CLS_NAME ] [ export UDS_FILE ]" + " [ verbose ] [ direct-action ] [ skip_hw | skip_sw ]\n" + " object-pinned FILE [ direct-action ] [ skip_hw | skip_sw ]\n" + "\n" + "Common remaining options:\n" + " [ action ACTION_SPEC ]\n" + " [ classid CLASSID ]\n" + "\n" + "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n" + "c,t,f,k and s are decimals; s denotes number of 4-tuples\n" + "\n" + "Where FILE points to a file containing the BPF_BYTECODE string,\n" + "an ELF file containing eBPF map definitions and bytecode, or a\n" + "pinned eBPF program.\n" + "\n" + "Where CLS_NAME refers to the section name containing the\n" + "classifier (default \'%s\').\n" + "\n" + "Where UDS_FILE points to a unix domain socket file in order\n" + "to hand off control of all created eBPF maps to an agent.\n" + "\n" + "ACTION_SPEC := ... look at individual actions\n" + "NOTE: CLASSID is parsed as hexadecimal input.\n", + bpf_prog_to_default_section(bpf_type)); +} + +static void bpf_cbpf_cb(void *nl, const struct sock_filter *ops, int ops_len) +{ + addattr16(nl, MAX_MSG, TCA_BPF_OPS_LEN, ops_len); + addattr_l(nl, MAX_MSG, TCA_BPF_OPS, ops, + ops_len * sizeof(struct sock_filter)); +} + +static void bpf_ebpf_cb(void *nl, int fd, const char *annotation) +{ + addattr32(nl, MAX_MSG, TCA_BPF_FD, fd); + addattrstrz(nl, MAX_MSG, TCA_BPF_NAME, annotation); +} + +static const struct bpf_cfg_ops bpf_cb_ops = { + .cbpf_cb = bpf_cbpf_cb, + .ebpf_cb = bpf_ebpf_cb, +}; + +static int bpf_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + const char *bpf_obj = NULL, *bpf_uds_name = NULL; + struct tcmsg *t = NLMSG_DATA(n); + unsigned int bpf_gen_flags = 0; + unsigned int bpf_flags = 0; + struct bpf_cfg_in cfg = {}; + bool seen_run = false; + bool skip_sw = false; + struct rtattr *tail; + int ret = 0; + + if (handle) { + if (get_u32(&t->tcm_handle, handle, 0)) { + fprintf(stderr, "Illegal \"handle\"\n"); + return -1; + } + } + + if (argc == 0) + return 0; + + tail = (struct rtattr *)(((void *)n) + NLMSG_ALIGN(n->nlmsg_len)); + addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0); + + while (argc > 0) { + if (matches(*argv, "run") == 0) { + NEXT_ARG(); + + if (seen_run) + duparg("run", *argv); +opt_bpf: + seen_run = true; + cfg.type = bpf_type; + cfg.argc = argc; + cfg.argv = argv; + + if (bpf_parse_common(&cfg, &bpf_cb_ops) < 0) { + fprintf(stderr, + "Unable to parse bpf command line\n"); + return -1; + } + + argc = cfg.argc; + argv = cfg.argv; + + bpf_obj = cfg.object; + bpf_uds_name = cfg.uds; + } else if (matches(*argv, "classid") == 0 || + matches(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + if (get_tc_classid(&classid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr32(n, MAX_MSG, TCA_BPF_CLASSID, classid); + } else if (matches(*argv, "direct-action") == 0 || + matches(*argv, "da") == 0) { + bpf_flags |= TCA_BPF_FLAG_ACT_DIRECT; + } else if (matches(*argv, "skip_hw") == 0) { + bpf_gen_flags |= TCA_CLS_FLAGS_SKIP_HW; + } else if (matches(*argv, "skip_sw") == 0) { + bpf_gen_flags |= TCA_CLS_FLAGS_SKIP_SW; + skip_sw = true; + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_BPF_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_BPF_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "help") == 0) { + explain(); + return -1; + } else { + if (!seen_run) + goto opt_bpf; + + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + + NEXT_ARG_FWD(); + } + + if (skip_sw) + cfg.ifindex = t->tcm_ifindex; + if (bpf_load_common(&cfg, &bpf_cb_ops, n) < 0) { + fprintf(stderr, "Unable to load program\n"); + return -1; + } + + if (bpf_gen_flags) + addattr32(n, MAX_MSG, TCA_BPF_FLAGS_GEN, bpf_gen_flags); + if (bpf_flags) + addattr32(n, MAX_MSG, TCA_BPF_FLAGS, bpf_flags); + + tail->rta_len = (((void *)n) + n->nlmsg_len) - (void *)tail; + + if (bpf_uds_name) + ret = bpf_send_map_fds(bpf_uds_name, bpf_obj); + + return ret; +} + +static int bpf_print_opt(struct filter_util *qu, FILE *f, + struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_BPF_MAX + 1]; + int dump_ok = 0; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_BPF_MAX, opt); + + if (handle) + print_0xhex(PRINT_ANY, "handle", "handle %#llx ", handle); + + if (tb[TCA_BPF_CLASSID]) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "flowid", "flowid %s ", + sprint_tc_classid(rta_getattr_u32(tb[TCA_BPF_CLASSID]), b1)); + } + + if (tb[TCA_BPF_NAME]) + print_string(PRINT_ANY, "bpf_name", "%s ", + rta_getattr_str(tb[TCA_BPF_NAME])); + + if (tb[TCA_BPF_FLAGS]) { + unsigned int flags = rta_getattr_u32(tb[TCA_BPF_FLAGS]); + + if (flags & TCA_BPF_FLAG_ACT_DIRECT) + print_bool(PRINT_ANY, + "direct-action", "direct-action ", true); + } + + if (tb[TCA_BPF_FLAGS_GEN]) { + unsigned int flags = + rta_getattr_u32(tb[TCA_BPF_FLAGS_GEN]); + + if (flags & TCA_CLS_FLAGS_SKIP_HW) + print_bool(PRINT_ANY, "skip_hw", "skip_hw ", true); + if (flags & TCA_CLS_FLAGS_SKIP_SW) + print_bool(PRINT_ANY, "skip_sw", "skip_sw ", true); + if (flags & TCA_CLS_FLAGS_IN_HW) + print_bool(PRINT_ANY, "in_hw", "in_hw ", true); + else if (flags & TCA_CLS_FLAGS_NOT_IN_HW) + print_bool(PRINT_ANY, + "not_in_hw", "not_in_hw ", true); + } + + if (tb[TCA_BPF_OPS] && tb[TCA_BPF_OPS_LEN]) + bpf_print_ops(tb[TCA_BPF_OPS], + rta_getattr_u16(tb[TCA_BPF_OPS_LEN])); + + if (tb[TCA_BPF_ID]) + dump_ok = bpf_dump_prog_info(f, rta_getattr_u32(tb[TCA_BPF_ID])); + if (!dump_ok && tb[TCA_BPF_TAG]) { + SPRINT_BUF(b); + + print_string(PRINT_ANY, "tag", "tag %s ", + hexstring_n2a(RTA_DATA(tb[TCA_BPF_TAG]), + RTA_PAYLOAD(tb[TCA_BPF_TAG]), b, sizeof(b))); + } + + if (tb[TCA_BPF_POLICE]) { + print_nl(); + tc_print_police(f, tb[TCA_BPF_POLICE]); + } + + if (tb[TCA_BPF_ACT]) + tc_print_action(f, tb[TCA_BPF_ACT], 0); + + return 0; +} + +struct filter_util bpf_filter_util = { + .id = "bpf", + .parse_fopt = bpf_parse_opt, + .print_fopt = bpf_print_opt, +}; diff --git a/tc/f_cgroup.c b/tc/f_cgroup.c new file mode 100644 index 0000000..291d6e7 --- /dev/null +++ b/tc/f_cgroup.c @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_cgroup.c Control Group Classifier + * + * Authors: Thomas Graf <tgraf@infradead.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include "utils.h" +#include "tc_util.h" +#include "m_ematch.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... cgroup [ match EMATCH_TREE ]\n"); + fprintf(stderr, " [ action ACTION_SPEC ]\n"); +} + +static int cgroup_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + long h = 0; + + if (handle) { + h = strtol(handle, NULL, 0); + if (h == LONG_MIN || h == LONG_MAX) { + fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n", + handle); + return -1; + } + } + + t->tcm_handle = h; + + tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len)); + addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0); + + while (argc > 0) { + if (matches(*argv, "match") == 0) { + NEXT_ARG(); + if (parse_ematch(&argc, &argv, TCA_CGROUP_EMATCHES, n)) { + fprintf(stderr, "Illegal \"ematch\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_CGROUP_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_CGROUP_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + } + + tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail; + return 0; +} + +static int cgroup_print_opt(struct filter_util *qu, FILE *f, + struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_CGROUP_MAX+1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CGROUP_MAX, opt); + + if (handle) + print_0xhex(PRINT_ANY, "handle", "handle %#llx ", handle); + + if (tb[TCA_CGROUP_EMATCHES]) + print_ematch(f, tb[TCA_CGROUP_EMATCHES]); + + if (tb[TCA_CGROUP_POLICE]) { + print_nl(); + tc_print_police(f, tb[TCA_CGROUP_POLICE]); + } + + if (tb[TCA_CGROUP_ACT]) + tc_print_action(f, tb[TCA_CGROUP_ACT], 0); + + return 0; +} + +struct filter_util cgroup_filter_util = { + .id = "cgroup", + .parse_fopt = cgroup_parse_opt, + .print_fopt = cgroup_print_opt, +}; diff --git a/tc/f_flow.c b/tc/f_flow.c new file mode 100644 index 0000000..4a29af2 --- /dev/null +++ b/tc/f_flow.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_flow.c Flow filter + * + * Authors: Patrick McHardy <kaber@trash.net> + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include "utils.h" +#include "tc_util.h" +#include "m_ematch.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... flow ...\n" + "\n" + " [mapping mode]: map key KEY [ OPS ] ...\n" + " [hashing mode]: hash keys KEY-LIST ... [ perturb SECS ]\n" + "\n" + " [ divisor NUM ] [ baseclass ID ] [ match EMATCH_TREE ]\n" + " [ action ACTION_SPEC ]\n" + "\n" + "KEY-LIST := [ KEY-LIST , ] KEY\n" + "KEY := [ src | dst | proto | proto-src | proto-dst | iif | priority |\n" + " mark | nfct | nfct-src | nfct-dst | nfct-proto-src |\n" + " nfct-proto-dst | rt-classid | sk-uid | sk-gid |\n" + " vlan-tag | rxhash ]\n" + "OPS := [ or NUM | and NUM | xor NUM | rshift NUM | addend NUM ]\n" + "ID := X:Y\n" + ); +} + +static const char *flow_keys[FLOW_KEY_MAX+1] = { + [FLOW_KEY_SRC] = "src", + [FLOW_KEY_DST] = "dst", + [FLOW_KEY_PROTO] = "proto", + [FLOW_KEY_PROTO_SRC] = "proto-src", + [FLOW_KEY_PROTO_DST] = "proto-dst", + [FLOW_KEY_IIF] = "iif", + [FLOW_KEY_PRIORITY] = "priority", + [FLOW_KEY_MARK] = "mark", + [FLOW_KEY_NFCT] = "nfct", + [FLOW_KEY_NFCT_SRC] = "nfct-src", + [FLOW_KEY_NFCT_DST] = "nfct-dst", + [FLOW_KEY_NFCT_PROTO_SRC] = "nfct-proto-src", + [FLOW_KEY_NFCT_PROTO_DST] = "nfct-proto-dst", + [FLOW_KEY_RTCLASSID] = "rt-classid", + [FLOW_KEY_SKUID] = "sk-uid", + [FLOW_KEY_SKGID] = "sk-gid", + [FLOW_KEY_VLAN_TAG] = "vlan-tag", + [FLOW_KEY_RXHASH] = "rxhash", +}; + +static int flow_parse_keys(__u32 *keys, __u32 *nkeys, char *argv) +{ + char *s, *sep; + unsigned int i; + + *keys = 0; + *nkeys = 0; + s = argv; + while (s != NULL) { + sep = strchr(s, ','); + if (sep) + *sep = '\0'; + + for (i = 0; i <= FLOW_KEY_MAX; i++) { + if (matches(s, flow_keys[i]) == 0) { + *keys |= 1 << i; + (*nkeys)++; + break; + } + } + if (i > FLOW_KEY_MAX) { + fprintf(stderr, "Unknown flow key \"%s\"\n", s); + return -1; + } + s = sep ? sep + 1 : NULL; + } + return 0; +} + +static void transfer_bitop(__u32 *mask, __u32 *xor, __u32 m, __u32 x) +{ + *xor = x ^ (*xor & m); + *mask &= m; +} + +static int get_addend(__u32 *addend, char *argv, __u32 keys) +{ + inet_prefix addr; + int sign = 0; + __u32 tmp; + + if (*argv == '-') { + sign = 1; + argv++; + } + + if (get_u32(&tmp, argv, 0) == 0) + goto out; + + if (keys & (FLOW_KEY_SRC | FLOW_KEY_DST | + FLOW_KEY_NFCT_SRC | FLOW_KEY_NFCT_DST) && + get_addr(&addr, argv, AF_UNSPEC) == 0) { + switch (addr.family) { + case AF_INET: + tmp = ntohl(addr.data[0]); + goto out; + case AF_INET6: + tmp = ntohl(addr.data[3]); + goto out; + } + } + + return -1; +out: + if (sign) + tmp = -tmp; + *addend = tmp; + return 0; +} + +static int flow_parse_opt(struct filter_util *fu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + __u32 mask = ~0U, xor = 0; + __u32 keys = 0, nkeys = 0; + __u32 mode = FLOW_MODE_MAP; + __u32 tmp; + + if (handle) { + if (get_u32(&t->tcm_handle, handle, 0)) { + fprintf(stderr, "Illegal \"handle\"\n"); + return -1; + } + } + + tail = addattr_nest(n, 4096, TCA_OPTIONS); + + while (argc > 0) { + if (matches(*argv, "map") == 0) { + mode = FLOW_MODE_MAP; + } else if (matches(*argv, "hash") == 0) { + mode = FLOW_MODE_HASH; + } else if (matches(*argv, "keys") == 0) { + NEXT_ARG(); + if (flow_parse_keys(&keys, &nkeys, *argv)) + return -1; + addattr32(n, 4096, TCA_FLOW_KEYS, keys); + } else if (matches(*argv, "and") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"mask\"\n"); + return -1; + } + transfer_bitop(&mask, &xor, tmp, 0); + } else if (matches(*argv, "or") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"or\"\n"); + return -1; + } + transfer_bitop(&mask, &xor, ~tmp, tmp); + } else if (matches(*argv, "xor") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"xor\"\n"); + return -1; + } + transfer_bitop(&mask, &xor, ~0, tmp); + } else if (matches(*argv, "rshift") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"rshift\"\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_RSHIFT, tmp); + } else if (matches(*argv, "addend") == 0) { + NEXT_ARG(); + if (get_addend(&tmp, *argv, keys)) { + fprintf(stderr, "Illegal \"addend\"\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_ADDEND, tmp); + } else if (matches(*argv, "divisor") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"divisor\"\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_DIVISOR, tmp); + } else if (matches(*argv, "baseclass") == 0) { + NEXT_ARG(); + if (get_tc_classid(&tmp, *argv) || TC_H_MIN(tmp) == 0) { + fprintf(stderr, "Illegal \"baseclass\"\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_BASECLASS, tmp); + } else if (matches(*argv, "perturb") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 0)) { + fprintf(stderr, "Illegal \"perturb\"\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_PERTURB, tmp); + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_FLOW_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_FLOW_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + if (parse_ematch(&argc, &argv, TCA_FLOW_EMATCHES, n)) { + fprintf(stderr, "Illegal \"ematch\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argv++, argc--; + } + + if (nkeys > 1 && mode != FLOW_MODE_HASH) { + fprintf(stderr, "Invalid mode \"map\" for multiple keys\n"); + return -1; + } + addattr32(n, 4096, TCA_FLOW_MODE, mode); + + if (mask != ~0 || xor != 0) { + addattr32(n, 4096, TCA_FLOW_MASK, mask); + addattr32(n, 4096, TCA_FLOW_XOR, xor); + } + + addattr_nest_end(n, tail); + return 0; +} + +static const char *flow_mode2str(__u32 mode) +{ + static char buf[128]; + + switch (mode) { + case FLOW_MODE_MAP: + return "map"; + case FLOW_MODE_HASH: + return "hash"; + default: + snprintf(buf, sizeof(buf), "%#x", mode); + return buf; + } +} + +static int flow_print_opt(struct filter_util *fu, FILE *f, struct rtattr *opt, + __u32 handle) +{ + struct rtattr *tb[TCA_FLOW_MAX+1]; + + SPRINT_BUF(b1); + unsigned int i; + __u32 mask = ~0, val = 0; + + if (opt == NULL) + return -EINVAL; + + parse_rtattr_nested(tb, TCA_FLOW_MAX, opt); + + print_0xhex(PRINT_ANY, "handle", "handle %#llx ", handle); + + if (tb[TCA_FLOW_MODE]) { + __u32 mode = rta_getattr_u32(tb[TCA_FLOW_MODE]); + print_string(PRINT_ANY, "mode", "%s ", flow_mode2str(mode)); + } + + if (tb[TCA_FLOW_KEYS]) { + __u32 keymask = rta_getattr_u32(tb[TCA_FLOW_KEYS]); + char *sep = " "; + + open_json_array(PRINT_ANY, "keys"); + for (i = 0; i <= FLOW_KEY_MAX; i++) { + if (keymask & (1 << i)) { + print_string(PRINT_FP, NULL, "%s", sep); + print_string(PRINT_ANY, NULL, "%s", flow_keys[i]); + sep = ","; + } + } + close_json_array(PRINT_ANY, " "); + } + + if (tb[TCA_FLOW_MASK]) + mask = rta_getattr_u32(tb[TCA_FLOW_MASK]); + if (tb[TCA_FLOW_XOR]) + val = rta_getattr_u32(tb[TCA_FLOW_XOR]); + + if (mask != ~0 || val != 0) { + __u32 or = (mask & val) ^ val; + __u32 xor = mask & val; + + if (mask != ~0) + print_0xhex(PRINT_ANY, "and", "and 0x%.8x ", mask); + if (xor != 0) + print_0xhex(PRINT_ANY, "xor", "xor 0x%.8x ", xor); + if (or != 0) + print_0xhex(PRINT_ANY, "or", "or 0x%.8x ", or); + } + + if (tb[TCA_FLOW_RSHIFT]) + print_uint(PRINT_ANY, "rshift", "rshift %u ", + rta_getattr_u32(tb[TCA_FLOW_RSHIFT])); + if (tb[TCA_FLOW_ADDEND]) + print_0xhex(PRINT_ANY, "addend", "addend 0x%x ", + rta_getattr_u32(tb[TCA_FLOW_ADDEND])); + + if (tb[TCA_FLOW_DIVISOR]) + print_uint(PRINT_ANY, "divisor", "divisor %u ", + rta_getattr_u32(tb[TCA_FLOW_DIVISOR])); + if (tb[TCA_FLOW_BASECLASS]) + print_string(PRINT_ANY, "baseclass", "baseclass %s ", + sprint_tc_classid(rta_getattr_u32(tb[TCA_FLOW_BASECLASS]), b1)); + + if (tb[TCA_FLOW_PERTURB]) + print_uint(PRINT_ANY, "perturb", "perturb %usec ", + rta_getattr_u32(tb[TCA_FLOW_PERTURB])); + + if (tb[TCA_FLOW_EMATCHES]) + print_ematch(f, tb[TCA_FLOW_EMATCHES]); + if (tb[TCA_FLOW_POLICE]) + tc_print_police(f, tb[TCA_FLOW_POLICE]); + if (tb[TCA_FLOW_ACT]) { + print_nl(); + tc_print_action(f, tb[TCA_FLOW_ACT], 0); + } + return 0; +} + +struct filter_util flow_filter_util = { + .id = "flow", + .parse_fopt = flow_parse_opt, + .print_fopt = flow_print_opt, +}; diff --git a/tc/f_flower.c b/tc/f_flower.c new file mode 100644 index 0000000..53188f1 --- /dev/null +++ b/tc/f_flower.c @@ -0,0 +1,3190 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_flower.c Flower Classifier + * + * Authors: Jiri Pirko <jiri@resnulli.us> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <net/if.h> +#include <linux/limits.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/tc_act/tc_vlan.h> +#include <linux/mpls.h> +#include <linux/ppp_defs.h> + +#include "utils.h" +#include "tc_util.h" +#include "rt_names.h" + +#ifndef IPPROTO_L2TP +#define IPPROTO_L2TP 115 +#endif + +enum flower_matching_flags { + FLOWER_IP_FLAGS, +}; + +enum flower_endpoint { + FLOWER_ENDPOINT_SRC, + FLOWER_ENDPOINT_DST +}; + +enum flower_icmp_field { + FLOWER_ICMP_FIELD_TYPE, + FLOWER_ICMP_FIELD_CODE +}; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... flower [ MATCH-LIST ] [ verbose ]\n" + " [ skip_sw | skip_hw ]\n" + " [ action ACTION-SPEC ] [ classid CLASSID ]\n" + "\n" + "Where: MATCH-LIST := [ MATCH-LIST ] MATCH\n" + " MATCH := { indev DEV-NAME |\n" + " num_of_vlans VLANS_COUNT |\n" + " vlan_id VID |\n" + " vlan_prio PRIORITY |\n" + " vlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n" + " cvlan_id VID |\n" + " cvlan_prio PRIORITY |\n" + " cvlan_ethtype [ ipv4 | ipv6 | ETH-TYPE ] |\n" + " pppoe_sid PSID |\n" + " ppp_proto [ ipv4 | ipv6 | mpls_uc | mpls_mc | PPP_PROTO ] |\n" + " dst_mac MASKED-LLADDR |\n" + " src_mac MASKED-LLADDR |\n" + " ip_proto [tcp | udp | sctp | icmp | icmpv6 | l2tp | esp | ah | IP-PROTO ] |\n" + " ip_tos MASKED-IP_TOS |\n" + " ip_ttl MASKED-IP_TTL |\n" + " mpls LSE-LIST |\n" + " mpls_label LABEL |\n" + " mpls_tc TC |\n" + " mpls_bos BOS |\n" + " mpls_ttl TTL |\n" + " spi SPI-INDEX |\n" + " l2tpv3_sid LSID |\n" + " dst_ip PREFIX |\n" + " src_ip PREFIX |\n" + " dst_port PORT-NUMBER |\n" + " src_port PORT-NUMBER |\n" + " tcp_flags MASKED-TCP_FLAGS |\n" + " type MASKED-ICMP-TYPE |\n" + " code MASKED-ICMP-CODE |\n" + " arp_tip IPV4-PREFIX |\n" + " arp_sip IPV4-PREFIX |\n" + " arp_op [ request | reply | OP ] |\n" + " arp_tha MASKED-LLADDR |\n" + " arp_sha MASKED-LLADDR |\n" + " enc_dst_ip [ IPV4-ADDR | IPV6-ADDR ] |\n" + " enc_src_ip [ IPV4-ADDR | IPV6-ADDR ] |\n" + " enc_key_id [ KEY-ID ] |\n" + " enc_tos MASKED-IP_TOS |\n" + " enc_ttl MASKED-IP_TTL |\n" + " geneve_opts MASKED-OPTIONS |\n" + " vxlan_opts MASKED-OPTIONS |\n" + " erspan_opts MASKED-OPTIONS |\n" + " gtp_opts MASKED-OPTIONS |\n" + " ip_flags IP-FLAGS |\n" + " l2_miss L2_MISS |\n" + " enc_dst_port [ port_number ] |\n" + " ct_state MASKED_CT_STATE |\n" + " ct_label MASKED_CT_LABEL |\n" + " ct_mark MASKED_CT_MARK |\n" + " ct_zone MASKED_CT_ZONE |\n" + " cfm CFM }\n" + " LSE-LIST := [ LSE-LIST ] LSE\n" + " LSE := lse depth DEPTH { label LABEL | tc TC | bos BOS | ttl TTL }\n" + " FILTERID := X:Y:Z\n" + " MASKED_LLADDR := { LLADDR | LLADDR/MASK | LLADDR/BITS }\n" + " MASKED_CT_STATE := combination of {+|-} and flags trk,est,new,rel,rpl,inv\n" + " CFM := { mdl LEVEL | op OPCODE }\n" + " ACTION-SPEC := ... look at individual actions\n" + "\n" + "NOTE: CLASSID, IP-PROTO are parsed as hexadecimal input.\n" + "NOTE: There can be only used one mask per one prio. If user needs\n" + " to specify different mask, he has to use different prio.\n"); +} + +/* prints newline, two spaces, name/value */ +static void print_indent_name_value(const char *name, const char *value) +{ + print_string(PRINT_FP, NULL, "%s ", _SL_); + print_string_name_value(name, value); +} + +static void print_uint_indent_name_value(const char *name, unsigned int value) +{ + print_string(PRINT_FP, NULL, " ", NULL); + print_uint_name_value(name, value); +} + +static int flower_parse_eth_addr(char *str, int addr_type, int mask_type, + struct nlmsghdr *n) +{ + int ret, err = -1; + char addr[ETH_ALEN], *slash; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + ret = ll_addr_a2n(addr, sizeof(addr), str); + if (ret < 0) + goto err; + addattr_l(n, MAX_MSG, addr_type, addr, sizeof(addr)); + + if (slash) { + unsigned int bits; + + if (!get_unsigned(&bits, slash + 1, 10)) { + uint64_t mask; + + /* Extra 16 bit shift to push mac address into + * high bits of uint64_t + */ + mask = htonll(0xffffffffffffULL << (16 + 48 - bits)); + memcpy(addr, &mask, ETH_ALEN); + } else { + ret = ll_addr_a2n(addr, sizeof(addr), slash + 1); + if (ret < 0) + goto err; + } + } else { + memset(addr, 0xff, ETH_ALEN); + } + addattr_l(n, MAX_MSG, mask_type, addr, sizeof(addr)); + + err = 0; +err: + if (slash) + *slash = '/'; + return err; +} + +static bool eth_type_vlan(__be16 ethertype, bool good_num_of_vlans) +{ + return ethertype == htons(ETH_P_8021Q) || + ethertype == htons(ETH_P_8021AD) || + good_num_of_vlans; +} + +static int flower_parse_vlan_eth_type(char *str, __be16 eth_type, int type, + __be16 *p_vlan_eth_type, + struct nlmsghdr *n, bool good_num_of_vlans) +{ + __be16 vlan_eth_type; + + if (!eth_type_vlan(eth_type, good_num_of_vlans)) { + fprintf(stderr, "Can't set \"%s\" if ethertype isn't 802.1Q or 802.1AD and num_of_vlans %s\n", + type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "vlan_ethtype" : "cvlan_ethtype", + type == TCA_FLOWER_KEY_VLAN_ETH_TYPE ? "is 0" : "less than 2"); + return -1; + } + + if (ll_proto_a2n(&vlan_eth_type, str)) + invarg("invalid vlan_ethtype", str); + addattr16(n, MAX_MSG, type, vlan_eth_type); + *p_vlan_eth_type = vlan_eth_type; + return 0; +} + +struct flag_to_string { + int flag; + enum flower_matching_flags type; + char *string; +}; + +static struct flag_to_string flags_str[] = { + { TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT, FLOWER_IP_FLAGS, "frag" }, + { TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST, FLOWER_IP_FLAGS, "firstfrag" }, +}; + +static int flower_parse_matching_flags(char *str, + enum flower_matching_flags type, + __u32 *mtf, __u32 *mtf_mask) +{ + char *token; + bool no; + bool found; + int i; + + token = strtok(str, "/"); + + while (token) { + if (!strncmp(token, "no", 2)) { + no = true; + token += 2; + } else + no = false; + + found = false; + for (i = 0; i < ARRAY_SIZE(flags_str); i++) { + if (type != flags_str[i].type) + continue; + + if (!strcmp(token, flags_str[i].string)) { + if (no) + *mtf &= ~flags_str[i].flag; + else + *mtf |= flags_str[i].flag; + + *mtf_mask |= flags_str[i].flag; + found = true; + break; + } + } + if (!found) + return -1; + + token = strtok(NULL, "/"); + } + + return 0; +} + +static int flower_parse_u16(char *str, int value_type, int mask_type, + struct nlmsghdr *n, bool be) +{ + __u16 value, mask; + char *slash; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + if (get_u16(&value, str, 0)) + return -1; + + if (slash) { + if (get_u16(&mask, slash + 1, 0)) + return -1; + } else { + mask = UINT16_MAX; + } + + if (be) { + value = htons(value); + mask = htons(mask); + } + addattr16(n, MAX_MSG, value_type, value); + addattr16(n, MAX_MSG, mask_type, mask); + + return 0; +} + +static int flower_parse_u32(char *str, int value_type, int mask_type, + struct nlmsghdr *n) +{ + __u32 value, mask; + char *slash; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + if (get_u32(&value, str, 0)) + return -1; + + if (slash) { + if (get_u32(&mask, slash + 1, 0)) + return -1; + } else { + mask = UINT32_MAX; + } + + addattr32(n, MAX_MSG, value_type, value); + addattr32(n, MAX_MSG, mask_type, mask); + + return 0; +} + +static int flower_parse_ct_mark(char *str, struct nlmsghdr *n) +{ + return flower_parse_u32(str, + TCA_FLOWER_KEY_CT_MARK, + TCA_FLOWER_KEY_CT_MARK_MASK, + n); +} + +static int flower_parse_ct_zone(char *str, struct nlmsghdr *n) +{ + return flower_parse_u16(str, + TCA_FLOWER_KEY_CT_ZONE, + TCA_FLOWER_KEY_CT_ZONE_MASK, + n, + false); +} + +static int flower_parse_ct_labels(char *str, struct nlmsghdr *n) +{ +#define LABELS_SIZE 16 + uint8_t labels[LABELS_SIZE], lmask[LABELS_SIZE]; + char *slash, *mask = NULL; + size_t slen, slen_mask = 0; + + slash = index(str, '/'); + if (slash) { + *slash = 0; + mask = slash + 1; + slen_mask = strlen(mask); + } + + slen = strlen(str); + if (slen > LABELS_SIZE * 2 || slen_mask > LABELS_SIZE * 2) { + char errmsg[128]; + + snprintf(errmsg, sizeof(errmsg), + "%zd Max allowed size %d", + slen, LABELS_SIZE*2); + invarg(errmsg, str); + } + + if (hex2mem(str, labels, slen / 2) < 0) + invarg("labels must be a hex string\n", str); + addattr_l(n, MAX_MSG, TCA_FLOWER_KEY_CT_LABELS, labels, slen / 2); + + if (mask) { + if (hex2mem(mask, lmask, slen_mask / 2) < 0) + invarg("labels mask must be a hex string\n", mask); + } else { + memset(lmask, 0xff, sizeof(lmask)); + slen_mask = sizeof(lmask) * 2; + } + addattr_l(n, MAX_MSG, TCA_FLOWER_KEY_CT_LABELS_MASK, lmask, + slen_mask / 2); + + return 0; +} + +static struct flower_ct_states { + char *str; + int flag; +} flower_ct_states[] = { + { "trk", TCA_FLOWER_KEY_CT_FLAGS_TRACKED }, + { "new", TCA_FLOWER_KEY_CT_FLAGS_NEW }, + { "est", TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED }, + { "rel", TCA_FLOWER_KEY_CT_FLAGS_RELATED }, + { "inv", TCA_FLOWER_KEY_CT_FLAGS_INVALID }, + { "rpl", TCA_FLOWER_KEY_CT_FLAGS_REPLY }, +}; + +static int flower_parse_ct_state(char *str, struct nlmsghdr *n) +{ + int flags = 0, mask = 0, len, i; + bool p; + + while (*str != '\0') { + if (*str == '+') + p = true; + else if (*str == '-') + p = false; + else + return -1; + + for (i = 0; i < ARRAY_SIZE(flower_ct_states); i++) { + len = strlen(flower_ct_states[i].str); + if (strncmp(str + 1, flower_ct_states[i].str, len)) + continue; + + if (p) + flags |= flower_ct_states[i].flag; + mask |= flower_ct_states[i].flag; + break; + } + + if (i == ARRAY_SIZE(flower_ct_states)) + return -1; + + str += len + 1; + } + + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CT_STATE, flags); + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CT_STATE_MASK, mask); + return 0; +} + +static int flower_parse_ip_proto(char *str, __be16 eth_type, int type, + __u8 *p_ip_proto, struct nlmsghdr *n) +{ + int ret; + __u8 ip_proto; + + if (eth_type != htons(ETH_P_IP) && eth_type != htons(ETH_P_IPV6)) + goto err; + + if (matches(str, "tcp") == 0) { + ip_proto = IPPROTO_TCP; + } else if (matches(str, "udp") == 0) { + ip_proto = IPPROTO_UDP; + } else if (matches(str, "sctp") == 0) { + ip_proto = IPPROTO_SCTP; + } else if (matches(str, "icmp") == 0) { + if (eth_type != htons(ETH_P_IP)) + goto err; + ip_proto = IPPROTO_ICMP; + } else if (matches(str, "icmpv6") == 0) { + if (eth_type != htons(ETH_P_IPV6)) + goto err; + ip_proto = IPPROTO_ICMPV6; + } else if (!strcmp(str, "l2tp")) { + if (eth_type != htons(ETH_P_IP) && + eth_type != htons(ETH_P_IPV6)) + goto err; + ip_proto = IPPROTO_L2TP; + } else if (!strcmp(str, "esp")) { + if (eth_type != htons(ETH_P_IP) && + eth_type != htons(ETH_P_IPV6)) + goto err; + ip_proto = IPPROTO_ESP; + } else if (!strcmp(str, "ah")) { + if (eth_type != htons(ETH_P_IP) && + eth_type != htons(ETH_P_IPV6)) + goto err; + ip_proto = IPPROTO_AH; + } else { + ret = get_u8(&ip_proto, str, 16); + if (ret) + return -1; + } + addattr8(n, MAX_MSG, type, ip_proto); + *p_ip_proto = ip_proto; + return 0; + +err: + fprintf(stderr, "Illegal \"eth_type\" for ip proto\n"); + return -1; +} + +static int __flower_parse_ip_addr(char *str, int family, + int addr4_type, int mask4_type, + int addr6_type, int mask6_type, + struct nlmsghdr *n) +{ + int ret; + inet_prefix addr; + int bits; + int i; + + ret = get_prefix(&addr, str, family); + if (ret) + return -1; + + if (family && (addr.family != family)) { + fprintf(stderr, "Illegal \"eth_type\" for ip address\n"); + return -1; + } + + addattr_l(n, MAX_MSG, addr.family == AF_INET ? addr4_type : addr6_type, + addr.data, addr.bytelen); + + memset(addr.data, 0xff, addr.bytelen); + bits = addr.bitlen; + for (i = 0; i < addr.bytelen / 4; i++) { + if (!bits) { + addr.data[i] = 0; + } else if (bits / 32 >= 1) { + bits -= 32; + } else { + addr.data[i] <<= 32 - bits; + addr.data[i] = htonl(addr.data[i]); + bits = 0; + } + } + + addattr_l(n, MAX_MSG, addr.family == AF_INET ? mask4_type : mask6_type, + addr.data, addr.bytelen); + + return 0; +} + +static int flower_parse_ip_addr(char *str, __be16 eth_type, + int addr4_type, int mask4_type, + int addr6_type, int mask6_type, + struct nlmsghdr *n) +{ + int family; + + if (eth_type == htons(ETH_P_IP)) { + family = AF_INET; + } else if (eth_type == htons(ETH_P_IPV6)) { + family = AF_INET6; + } else if (!eth_type) { + family = AF_UNSPEC; + } else { + return -1; + } + + return __flower_parse_ip_addr(str, family, addr4_type, mask4_type, + addr6_type, mask6_type, n); +} + +static bool flower_eth_type_arp(__be16 eth_type) +{ + return eth_type == htons(ETH_P_ARP) || eth_type == htons(ETH_P_RARP); +} + +static int flower_parse_arp_ip_addr(char *str, __be16 eth_type, + int addr_type, int mask_type, + struct nlmsghdr *n) +{ + if (!flower_eth_type_arp(eth_type)) + return -1; + + return __flower_parse_ip_addr(str, AF_INET, addr_type, mask_type, + TCA_FLOWER_UNSPEC, TCA_FLOWER_UNSPEC, n); +} + +static int flower_parse_u8(char *str, int value_type, int mask_type, + int (*value_from_name)(const char *str, + __u8 *value), + bool (*value_validate)(__u8 value), + struct nlmsghdr *n) +{ + char *slash; + int ret, err = -1; + __u8 value, mask; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + ret = value_from_name ? value_from_name(str, &value) : -1; + if (ret < 0) { + ret = get_u8(&value, str, 10); + if (ret) + goto err; + } + + if (value_validate && !value_validate(value)) + goto err; + + if (slash) { + ret = get_u8(&mask, slash + 1, 10); + if (ret) + goto err; + } else { + mask = UINT8_MAX; + } + + addattr8(n, MAX_MSG, value_type, value); + addattr8(n, MAX_MSG, mask_type, mask); + + err = 0; +err: + if (slash) + *slash = '/'; + return err; +} + +static const char *flower_print_arp_op_to_name(__u8 op) +{ + switch (op) { + case ARPOP_REQUEST: + return "request"; + case ARPOP_REPLY: + return "reply"; + default: + return NULL; + } +} + +static int flower_arp_op_from_name(const char *name, __u8 *op) +{ + if (!strcmp(name, "request")) + *op = ARPOP_REQUEST; + else if (!strcmp(name, "reply")) + *op = ARPOP_REPLY; + else + return -1; + + return 0; +} + +static bool flow_arp_op_validate(__u8 op) +{ + return !op || op == ARPOP_REQUEST || op == ARPOP_REPLY; +} + +static int flower_parse_arp_op(char *str, __be16 eth_type, + int op_type, int mask_type, + struct nlmsghdr *n) +{ + if (!flower_eth_type_arp(eth_type)) + return -1; + + return flower_parse_u8(str, op_type, mask_type, flower_arp_op_from_name, + flow_arp_op_validate, n); +} + +static int flower_icmp_attr_type(__be16 eth_type, __u8 ip_proto, + enum flower_icmp_field field) +{ + if (eth_type == htons(ETH_P_IP) && ip_proto == IPPROTO_ICMP) + return field == FLOWER_ICMP_FIELD_CODE ? + TCA_FLOWER_KEY_ICMPV4_CODE : + TCA_FLOWER_KEY_ICMPV4_TYPE; + else if (eth_type == htons(ETH_P_IPV6) && ip_proto == IPPROTO_ICMPV6) + return field == FLOWER_ICMP_FIELD_CODE ? + TCA_FLOWER_KEY_ICMPV6_CODE : + TCA_FLOWER_KEY_ICMPV6_TYPE; + + return -1; +} + +static int flower_icmp_attr_mask_type(__be16 eth_type, __u8 ip_proto, + enum flower_icmp_field field) +{ + if (eth_type == htons(ETH_P_IP) && ip_proto == IPPROTO_ICMP) + return field == FLOWER_ICMP_FIELD_CODE ? + TCA_FLOWER_KEY_ICMPV4_CODE_MASK : + TCA_FLOWER_KEY_ICMPV4_TYPE_MASK; + else if (eth_type == htons(ETH_P_IPV6) && ip_proto == IPPROTO_ICMPV6) + return field == FLOWER_ICMP_FIELD_CODE ? + TCA_FLOWER_KEY_ICMPV6_CODE_MASK : + TCA_FLOWER_KEY_ICMPV6_TYPE_MASK; + + return -1; +} + +static int flower_parse_icmp(char *str, __u16 eth_type, __u8 ip_proto, + enum flower_icmp_field field, struct nlmsghdr *n) +{ + int value_type, mask_type; + + value_type = flower_icmp_attr_type(eth_type, ip_proto, field); + mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto, field); + if (value_type < 0 || mask_type < 0) + return -1; + + return flower_parse_u8(str, value_type, mask_type, NULL, NULL, n); +} + +static int flower_parse_spi(char *str, __u8 ip_proto, struct nlmsghdr *n) +{ + __be32 spi; + int ret; + + if (ip_proto != IPPROTO_UDP && ip_proto != IPPROTO_ESP && ip_proto != IPPROTO_AH) { + fprintf(stderr, + "Can't set \"spi\" if ip_proto isn't ESP/UDP/AH\n"); + return -1; + } + + ret = get_be32(&spi, str, 16); + if (ret < 0) { + fprintf(stderr, "Illegal \"spi index\"\n"); + return -1; + } + + addattr32(n, MAX_MSG, TCA_FLOWER_KEY_SPI, spi); + addattr32(n, MAX_MSG, TCA_FLOWER_KEY_SPI_MASK, UINT32_MAX); + + return 0; +} + +static int flower_parse_l2tpv3(char *str, __u8 ip_proto, + struct nlmsghdr *n) +{ + __be32 sid; + int ret; + + if (ip_proto != IPPROTO_L2TP) { + fprintf(stderr, + "Can't set \"l2tpv3_sid\" if ip_proto isn't l2tp\n"); + return -1; + } + ret = get_be32(&sid, str, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"l2tpv3 session id\"\n"); + return -1; + } + addattr32(n, MAX_MSG, TCA_FLOWER_KEY_L2TPV3_SID, sid); + + return 0; +} + +static int flower_port_attr_type(__u8 ip_proto, enum flower_endpoint endpoint) +{ + if (ip_proto == IPPROTO_TCP) + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_TCP_SRC : + TCA_FLOWER_KEY_TCP_DST; + else if (ip_proto == IPPROTO_UDP) + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_UDP_SRC : + TCA_FLOWER_KEY_UDP_DST; + else if (ip_proto == IPPROTO_SCTP) + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_SCTP_SRC : + TCA_FLOWER_KEY_SCTP_DST; + else + return -1; +} + +static int flower_port_attr_mask_type(__u8 ip_proto, + enum flower_endpoint endpoint) +{ + switch (ip_proto) { + case IPPROTO_TCP: + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_TCP_SRC_MASK : + TCA_FLOWER_KEY_TCP_DST_MASK; + case IPPROTO_UDP: + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_UDP_SRC_MASK : + TCA_FLOWER_KEY_UDP_DST_MASK; + case IPPROTO_SCTP: + return endpoint == FLOWER_ENDPOINT_SRC ? + TCA_FLOWER_KEY_SCTP_SRC_MASK : + TCA_FLOWER_KEY_SCTP_DST_MASK; + default: + return -1; + } +} + +static int flower_port_range_attr_type(__u8 ip_proto, enum flower_endpoint type, + __be16 *min_port_type, + __be16 *max_port_type) +{ + if (ip_proto == IPPROTO_TCP || ip_proto == IPPROTO_UDP || + ip_proto == IPPROTO_SCTP) { + if (type == FLOWER_ENDPOINT_SRC) { + *min_port_type = TCA_FLOWER_KEY_PORT_SRC_MIN; + *max_port_type = TCA_FLOWER_KEY_PORT_SRC_MAX; + } else { + *min_port_type = TCA_FLOWER_KEY_PORT_DST_MIN; + *max_port_type = TCA_FLOWER_KEY_PORT_DST_MAX; + } + } else { + return -1; + } + return 0; +} + +/* parse range args in format 10-20 */ +static int parse_range(char *str, __be16 *min, __be16 *max, bool *p_is_range) +{ + char *sep; + + sep = strchr(str, '-'); + if (sep) { + *sep = '\0'; + + if (get_be16(min, str, 10)) + return -1; + + if (get_be16(max, sep + 1, 10)) + return -1; + + *p_is_range = true; + } else { + if (get_be16(min, str, 10)) + return -1; + } + return 0; +} + +static int flower_parse_port(char *str, __u8 ip_proto, + enum flower_endpoint endpoint, + struct nlmsghdr *n) +{ + bool is_range = false; + char *slash = NULL; + __be16 min = 0; + __be16 max = 0; + int ret; + + ret = parse_range(str, &min, &max, &is_range); + if (ret) { + slash = strchr(str, '/'); + if (!slash) + return -1; + } + + if (is_range) { + __be16 min_port_type, max_port_type; + + if (ntohs(max) <= ntohs(min)) { + fprintf(stderr, "max value should be greater than min value\n"); + return -1; + } + if (flower_port_range_attr_type(ip_proto, endpoint, + &min_port_type, &max_port_type)) + return -1; + + addattr16(n, MAX_MSG, min_port_type, min); + addattr16(n, MAX_MSG, max_port_type, max); + } else { + int type; + + type = flower_port_attr_type(ip_proto, endpoint); + if (type < 0) + return -1; + + if (!slash) { + addattr16(n, MAX_MSG, type, min); + } else { + int mask_type; + + mask_type = flower_port_attr_mask_type(ip_proto, + endpoint); + if (mask_type < 0) + return -1; + return flower_parse_u16(str, type, mask_type, n, true); + } + } + return 0; +} + +#define TCP_FLAGS_MAX_MASK 0xfff + +static int flower_parse_tcp_flags(char *str, int flags_type, int mask_type, + struct nlmsghdr *n) +{ + char *slash; + int ret, err = -1; + __u16 flags; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + ret = get_u16(&flags, str, 16); + if (ret < 0 || flags & ~TCP_FLAGS_MAX_MASK) + goto err; + + addattr16(n, MAX_MSG, flags_type, htons(flags)); + + if (slash) { + ret = get_u16(&flags, slash + 1, 16); + if (ret < 0 || flags & ~TCP_FLAGS_MAX_MASK) + goto err; + } else { + flags = TCP_FLAGS_MAX_MASK; + } + addattr16(n, MAX_MSG, mask_type, htons(flags)); + + err = 0; +err: + if (slash) + *slash = '/'; + return err; +} + +static int flower_parse_ip_tos_ttl(char *str, int key_type, int mask_type, + struct nlmsghdr *n) +{ + char *slash; + int ret, err = -1; + __u8 tos_ttl; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + ret = get_u8(&tos_ttl, str, 10); + if (ret < 0) + ret = get_u8(&tos_ttl, str, 16); + if (ret < 0) + goto err; + + addattr8(n, MAX_MSG, key_type, tos_ttl); + + if (slash) { + ret = get_u8(&tos_ttl, slash + 1, 16); + if (ret < 0) + goto err; + } else { + tos_ttl = 0xff; + } + addattr8(n, MAX_MSG, mask_type, tos_ttl); + + err = 0; +err: + if (slash) + *slash = '/'; + return err; +} + +static int flower_parse_key_id(const char *str, int type, struct nlmsghdr *n) +{ + int ret; + __be32 key_id; + + ret = get_be32(&key_id, str, 10); + if (!ret) + addattr32(n, MAX_MSG, type, key_id); + + return ret; +} + +static int flower_parse_enc_port(char *str, int type, struct nlmsghdr *n) +{ + int ret; + __be16 port; + + ret = get_be16(&port, str, 10); + if (ret) + return -1; + + addattr16(n, MAX_MSG, type, port); + + return 0; +} + +static int flower_parse_geneve_opt(char *str, struct nlmsghdr *n) +{ + struct rtattr *nest; + char *token; + int i, err; + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_GENEVE); + + i = 1; + token = strsep(&str, ":"); + while (token) { + switch (i) { + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS: + { + __be16 opt_class; + + if (!strlen(token)) + break; + err = get_be16(&opt_class, token, 16); + if (err) + return err; + + addattr16(n, MAX_MSG, i, opt_class); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE: + { + __u8 opt_type; + + if (!strlen(token)) + break; + err = get_u8(&opt_type, token, 16); + if (err) + return err; + + addattr8(n, MAX_MSG, i, opt_type); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA: + { + size_t token_len = strlen(token); + __u8 *opts; + + if (!token_len) + break; + opts = malloc(token_len / 2); + if (!opts) + return -1; + if (hex2mem(token, opts, token_len / 2) < 0) { + free(opts); + return -1; + } + addattr_l(n, MAX_MSG, i, opts, token_len / 2); + free(opts); + + break; + } + default: + fprintf(stderr, "Unknown \"geneve_opts\" type\n"); + return -1; + } + + token = strsep(&str, ":"); + i++; + } + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_vxlan_opt(char *str, struct nlmsghdr *n) +{ + struct rtattr *nest; + __u32 gbp; + int err; + + nest = addattr_nest(n, MAX_MSG, + TCA_FLOWER_KEY_ENC_OPTS_VXLAN | NLA_F_NESTED); + + err = get_u32(&gbp, str, 0); + if (err) + return err; + addattr32(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPT_VXLAN_GBP, gbp); + + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_erspan_opt(char *str, struct nlmsghdr *n) +{ + struct rtattr *nest; + char *token; + int i, err; + + nest = addattr_nest(n, MAX_MSG, + TCA_FLOWER_KEY_ENC_OPTS_ERSPAN | NLA_F_NESTED); + + i = 1; + token = strsep(&str, ":"); + while (token) { + switch (i) { + case TCA_FLOWER_KEY_ENC_OPT_ERSPAN_VER: + { + __u8 opt_type; + + if (!strlen(token)) + break; + err = get_u8(&opt_type, token, 0); + if (err) + return err; + + addattr8(n, MAX_MSG, i, opt_type); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_ERSPAN_INDEX: + { + __be32 opt_index; + + if (!strlen(token)) + break; + err = get_be32(&opt_index, token, 0); + if (err) + return err; + + addattr32(n, MAX_MSG, i, opt_index); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_ERSPAN_DIR: + { + __u8 opt_type; + + if (!strlen(token)) + break; + err = get_u8(&opt_type, token, 0); + if (err) + return err; + + addattr8(n, MAX_MSG, i, opt_type); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_ERSPAN_HWID: + { + __u8 opt_type; + + if (!strlen(token)) + break; + err = get_u8(&opt_type, token, 0); + if (err) + return err; + + addattr8(n, MAX_MSG, i, opt_type); + break; + } + default: + fprintf(stderr, "Unknown \"geneve_opts\" type\n"); + return -1; + } + + token = strsep(&str, ":"); + i++; + } + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_gtp_opt(char *str, struct nlmsghdr *n) +{ + struct rtattr *nest; + char *token; + int arg, err; + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_GTP | NLA_F_NESTED); + + token = strsep(&str, ":"); + for (arg = 1; arg <= TCA_FLOWER_KEY_ENC_OPT_GTP_MAX; arg++) { + switch (arg) { + case TCA_FLOWER_KEY_ENC_OPT_GTP_PDU_TYPE: + { + __u8 pdu_type; + + if (!strlen(token)) + break; + err = get_u8(&pdu_type, token, 16); + if (err) + return err; + addattr8(n, MAX_MSG, arg, pdu_type); + break; + } + case TCA_FLOWER_KEY_ENC_OPT_GTP_QFI: + { + __u8 qfi; + + if (!strlen(token)) + break; + err = get_u8(&qfi, token, 16); + if (err) + return err; + addattr8(n, MAX_MSG, arg, qfi); + break; + } + default: + fprintf(stderr, "Unknown \"gtp_opts\" type\n"); + return -1; + } + token = strsep(&str, ":"); + } + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_geneve_opts(char *str, struct nlmsghdr *n) +{ + char *token; + int err; + + token = strsep(&str, ","); + while (token) { + err = flower_parse_geneve_opt(token, n); + if (err) + return err; + + token = strsep(&str, ","); + } + + return 0; +} + +static int flower_check_enc_opt_key(char *key) +{ + int key_len, col_cnt = 0; + + key_len = strlen(key); + while ((key = strchr(key, ':'))) { + if (strlen(key) == key_len) + return -1; + + key_len = strlen(key) - 1; + col_cnt++; + key++; + } + + if (col_cnt != 2 || !key_len) + return -1; + + return 0; +} + +static int flower_parse_enc_opts_geneve(char *str, struct nlmsghdr *n) +{ + char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX]; + int data_len, key_len, mask_len, err; + char *token, *slash; + struct rtattr *nest; + + key_len = 0; + mask_len = 0; + token = strsep(&str, ","); + while (token) { + slash = strchr(token, '/'); + if (slash) + *slash = '\0'; + + if ((key_len + strlen(token) > XATTR_SIZE_MAX) || + flower_check_enc_opt_key(token)) + return -1; + + strcpy(&key[key_len], token); + key_len += strlen(token) + 1; + key[key_len - 1] = ','; + + if (!slash) { + /* Pad out mask when not provided */ + if (mask_len + strlen(token) > XATTR_SIZE_MAX) + return -1; + + data_len = strlen(rindex(token, ':')); + sprintf(&mask[mask_len], "ffff:ff:"); + mask_len += 8; + memset(&mask[mask_len], 'f', data_len - 1); + mask_len += data_len; + mask[mask_len - 1] = ','; + token = strsep(&str, ","); + continue; + } + + if (mask_len + strlen(slash + 1) > XATTR_SIZE_MAX) + return -1; + + strcpy(&mask[mask_len], slash + 1); + mask_len += strlen(slash + 1) + 1; + mask[mask_len - 1] = ','; + + *slash = '/'; + token = strsep(&str, ","); + } + key[key_len - 1] = '\0'; + mask[mask_len - 1] = '\0'; + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS); + err = flower_parse_geneve_opts(key, n); + if (err) + return err; + addattr_nest_end(n, nest); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_MASK); + err = flower_parse_geneve_opts(mask, n); + if (err) + return err; + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_enc_opts_vxlan(char *str, struct nlmsghdr *n) +{ + char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX]; + struct rtattr *nest; + char *slash; + int err; + + slash = strchr(str, '/'); + if (slash) { + *slash++ = '\0'; + if (strlen(slash) > XATTR_SIZE_MAX) + return -1; + strcpy(mask, slash); + } else { + strcpy(mask, "0xffffffff"); + } + + if (strlen(str) > XATTR_SIZE_MAX) + return -1; + strcpy(key, str); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS | NLA_F_NESTED); + err = flower_parse_vxlan_opt(str, n); + if (err) + return err; + addattr_nest_end(n, nest); + + nest = addattr_nest(n, MAX_MSG, + TCA_FLOWER_KEY_ENC_OPTS_MASK | NLA_F_NESTED); + err = flower_parse_vxlan_opt(mask, n); + if (err) + return err; + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_enc_opts_erspan(char *str, struct nlmsghdr *n) +{ + char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX]; + struct rtattr *nest; + char *slash; + int err; + + + slash = strchr(str, '/'); + if (slash) { + *slash++ = '\0'; + if (strlen(slash) > XATTR_SIZE_MAX) + return -1; + strcpy(mask, slash); + } else { + int index; + + slash = strchr(str, ':'); + index = (int)(slash - str); + memcpy(mask, str, index); + strcpy(mask + index, ":0xffffffff:0xff:0xff"); + } + + if (strlen(str) > XATTR_SIZE_MAX) + return -1; + strcpy(key, str); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS | NLA_F_NESTED); + err = flower_parse_erspan_opt(key, n); + if (err) + return err; + addattr_nest_end(n, nest); + + nest = addattr_nest(n, MAX_MSG, + TCA_FLOWER_KEY_ENC_OPTS_MASK | NLA_F_NESTED); + err = flower_parse_erspan_opt(mask, n); + if (err) + return err; + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_enc_opts_gtp(char *str, struct nlmsghdr *n) +{ + char key[XATTR_SIZE_MAX], mask[XATTR_SIZE_MAX]; + struct rtattr *nest; + char *slash; + int err; + + slash = strchr(str, '/'); + if (slash) { + *slash++ = '\0'; + if (strlen(slash) > XATTR_SIZE_MAX) + return -1; + strcpy(mask, slash); + } else + strcpy(mask, "ff:ff"); + + if (strlen(str) > XATTR_SIZE_MAX) + return -1; + strcpy(key, str); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS | NLA_F_NESTED); + err = flower_parse_gtp_opt(key, n); + if (err) + return err; + addattr_nest_end(n, nest); + + nest = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_ENC_OPTS_MASK | NLA_F_NESTED); + err = flower_parse_gtp_opt(mask, n); + if (err) + return err; + addattr_nest_end(n, nest); + + return 0; +} + +static int flower_parse_mpls_lse(int *argc_p, char ***argv_p, + struct nlmsghdr *nlh) +{ + struct rtattr *lse_attr; + char **argv = *argv_p; + int argc = *argc_p; + __u8 depth = 0; + int ret; + + lse_attr = addattr_nest(nlh, MAX_MSG, + TCA_FLOWER_KEY_MPLS_OPTS_LSE | NLA_F_NESTED); + + while (argc > 0) { + if (matches(*argv, "depth") == 0) { + NEXT_ARG(); + ret = get_u8(&depth, *argv, 10); + if (ret < 0 || depth < 1) { + fprintf(stderr, "Illegal \"depth\"\n"); + return -1; + } + addattr8(nlh, MAX_MSG, + TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH, depth); + } else if (matches(*argv, "label") == 0) { + __u32 label; + + NEXT_ARG(); + ret = get_u32(&label, *argv, 10); + if (ret < 0 || + label & ~(MPLS_LS_LABEL_MASK >> MPLS_LS_LABEL_SHIFT)) { + fprintf(stderr, "Illegal \"label\"\n"); + return -1; + } + addattr32(nlh, MAX_MSG, + TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL, label); + } else if (matches(*argv, "tc") == 0) { + __u8 tc; + + NEXT_ARG(); + ret = get_u8(&tc, *argv, 10); + if (ret < 0 || + tc & ~(MPLS_LS_TC_MASK >> MPLS_LS_TC_SHIFT)) { + fprintf(stderr, "Illegal \"tc\"\n"); + return -1; + } + addattr8(nlh, MAX_MSG, TCA_FLOWER_KEY_MPLS_OPT_LSE_TC, + tc); + } else if (matches(*argv, "bos") == 0) { + __u8 bos; + + NEXT_ARG(); + ret = get_u8(&bos, *argv, 10); + if (ret < 0 || bos & ~(MPLS_LS_S_MASK >> MPLS_LS_S_SHIFT)) { + fprintf(stderr, "Illegal \"bos\"\n"); + return -1; + } + addattr8(nlh, MAX_MSG, TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS, + bos); + } else if (matches(*argv, "ttl") == 0) { + __u8 ttl; + + NEXT_ARG(); + ret = get_u8(&ttl, *argv, 10); + if (ret < 0 || ttl & ~(MPLS_LS_TTL_MASK >> MPLS_LS_TTL_SHIFT)) { + fprintf(stderr, "Illegal \"ttl\"\n"); + return -1; + } + addattr8(nlh, MAX_MSG, TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL, + ttl); + } else { + break; + } + argc--; argv++; + } + + if (!depth) { + missarg("depth"); + return -1; + } + + addattr_nest_end(nlh, lse_attr); + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static int flower_parse_mpls(int *argc_p, char ***argv_p, struct nlmsghdr *nlh) +{ + struct rtattr *mpls_attr; + char **argv = *argv_p; + int argc = *argc_p; + + mpls_attr = addattr_nest(nlh, MAX_MSG, + TCA_FLOWER_KEY_MPLS_OPTS | NLA_F_NESTED); + + while (argc > 0) { + if (matches(*argv, "lse") == 0) { + NEXT_ARG(); + if (flower_parse_mpls_lse(&argc, &argv, nlh) < 0) + return -1; + } else { + break; + } + } + + addattr_nest_end(nlh, mpls_attr); + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static int flower_parse_cfm(int *argc_p, char ***argv_p, __be16 eth_type, + struct nlmsghdr *n) +{ + struct rtattr *cfm_attr; + char **argv = *argv_p; + int argc = *argc_p; + int ret; + + if (eth_type != htons(ETH_P_CFM)) { + fprintf(stderr, + "Can't set attribute if ethertype isn't CFM\n"); + return -1; + } + + cfm_attr = addattr_nest(n, MAX_MSG, TCA_FLOWER_KEY_CFM | NLA_F_NESTED); + + while (argc > 0) { + if (!strcmp(*argv, "mdl")) { + __u8 val; + + NEXT_ARG(); + ret = get_u8(&val, *argv, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"cfm md level\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_KEY_CFM_MD_LEVEL, val); + } else if (!strcmp(*argv, "op")) { + __u8 val; + + NEXT_ARG(); + ret = get_u8(&val, *argv, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"cfm opcode\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_KEY_CFM_OPCODE, val); + } else { + break; + } + argc--; argv++; + } + + addattr_nest_end(n, cfm_attr); + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static int flower_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + int ret; + struct tcmsg *t = NLMSG_DATA(n); + bool mpls_format_old = false; + bool mpls_format_new = false; + struct rtattr *tail; + __be16 tc_proto = TC_H_MIN(t->tcm_info); + __be16 eth_type = tc_proto; + __be16 vlan_ethtype = 0; + __u8 num_of_vlans = 0; + __u8 ip_proto = 0xff; + __u32 flags = 0; + __u32 mtf = 0; + __u32 mtf_mask = 0; + + if (handle) { + ret = get_u32(&t->tcm_handle, handle, 0); + if (ret) { + fprintf(stderr, "Illegal \"handle\"\n"); + return -1; + } + } + + tail = (struct rtattr *) (((void *) n) + NLMSG_ALIGN(n->nlmsg_len)); + addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0); + + if (argc == 0) { + /*at minimal we will match all ethertype packets */ + goto parse_done; + } + + while (argc > 0) { + if (matches(*argv, "classid") == 0 || + matches(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + ret = get_tc_classid(&classid, *argv); + if (ret) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_FLOWER_CLASSID, &classid, 4); + } else if (matches(*argv, "hw_tc") == 0) { + unsigned int classid; + __u32 tc; + char *end; + + NEXT_ARG(); + tc = strtoul(*argv, &end, 0); + if (*end) { + fprintf(stderr, "Illegal TC index\n"); + return -1; + } + if (tc >= TC_QOPT_MAX_QUEUE) { + fprintf(stderr, "TC index exceeds max range\n"); + return -1; + } + classid = TC_H_MAKE(TC_H_MAJ(t->tcm_parent), + TC_H_MIN(tc + TC_H_MIN_PRIORITY)); + addattr_l(n, MAX_MSG, TCA_FLOWER_CLASSID, &classid, + sizeof(classid)); + } else if (matches(*argv, "ip_flags") == 0) { + NEXT_ARG(); + ret = flower_parse_matching_flags(*argv, + FLOWER_IP_FLAGS, + &mtf, + &mtf_mask); + if (ret < 0) { + fprintf(stderr, "Illegal \"ip_flags\"\n"); + return -1; + } + } else if (strcmp(*argv, "l2_miss") == 0) { + __u8 l2_miss; + + NEXT_ARG(); + if (get_u8(&l2_miss, *argv, 10)) { + fprintf(stderr, "Illegal \"l2_miss\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_L2_MISS, l2_miss); + } else if (matches(*argv, "verbose") == 0) { + flags |= TCA_CLS_FLAGS_VERBOSE; + } else if (matches(*argv, "skip_hw") == 0) { + flags |= TCA_CLS_FLAGS_SKIP_HW; + } else if (matches(*argv, "skip_sw") == 0) { + flags |= TCA_CLS_FLAGS_SKIP_SW; + } else if (matches(*argv, "ct_state") == 0) { + NEXT_ARG(); + ret = flower_parse_ct_state(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ct_state\"\n"); + return -1; + } + } else if (matches(*argv, "ct_zone") == 0) { + NEXT_ARG(); + ret = flower_parse_ct_zone(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ct_zone\"\n"); + return -1; + } + } else if (matches(*argv, "ct_mark") == 0) { + NEXT_ARG(); + ret = flower_parse_ct_mark(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ct_mark\"\n"); + return -1; + } + } else if (matches(*argv, "ct_label") == 0) { + NEXT_ARG(); + ret = flower_parse_ct_labels(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ct_label\"\n"); + return -1; + } + } else if (matches(*argv, "indev") == 0) { + NEXT_ARG(); + if (check_ifname(*argv)) + invarg("\"indev\" not a valid ifname", *argv); + addattrstrz(n, MAX_MSG, TCA_FLOWER_INDEV, *argv); + } else if (strcmp(*argv, "num_of_vlans") == 0) { + NEXT_ARG(); + ret = get_u8(&num_of_vlans, *argv, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"num_of_vlans\"\n"); + return -1; + } + addattr8(n, MAX_MSG, + TCA_FLOWER_KEY_NUM_OF_VLANS, num_of_vlans); + } else if (matches(*argv, "vlan_id") == 0) { + __u16 vid; + + NEXT_ARG(); + if (!eth_type_vlan(tc_proto, num_of_vlans > 0)) { + fprintf(stderr, "Can't set \"vlan_id\" if ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is 0\n"); + return -1; + } + ret = get_u16(&vid, *argv, 10); + if (ret < 0 || vid & ~0xfff) { + fprintf(stderr, "Illegal \"vlan_id\"\n"); + return -1; + } + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_VLAN_ID, vid); + } else if (matches(*argv, "vlan_prio") == 0) { + __u8 vlan_prio; + + NEXT_ARG(); + if (!eth_type_vlan(tc_proto, num_of_vlans > 0)) { + fprintf(stderr, "Can't set \"vlan_prio\" if ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is 0\n"); + return -1; + } + ret = get_u8(&vlan_prio, *argv, 10); + if (ret < 0 || vlan_prio & ~0x7) { + fprintf(stderr, "Illegal \"vlan_prio\"\n"); + return -1; + } + addattr8(n, MAX_MSG, + TCA_FLOWER_KEY_VLAN_PRIO, vlan_prio); + } else if (matches(*argv, "vlan_ethtype") == 0) { + NEXT_ARG(); + ret = flower_parse_vlan_eth_type(*argv, eth_type, + TCA_FLOWER_KEY_VLAN_ETH_TYPE, + &vlan_ethtype, n, num_of_vlans > 0); + if (ret < 0) + return -1; + /* get new ethtype for later parsing */ + eth_type = vlan_ethtype; + } else if (matches(*argv, "cvlan_id") == 0) { + __u16 vid; + + NEXT_ARG(); + if (!eth_type_vlan(vlan_ethtype, num_of_vlans > 1)) { + fprintf(stderr, "Can't set \"cvlan_id\" if inner vlan ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is less than 2\n"); + return -1; + } + ret = get_u16(&vid, *argv, 10); + if (ret < 0 || vid & ~0xfff) { + fprintf(stderr, "Illegal \"cvlan_id\"\n"); + return -1; + } + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_CVLAN_ID, vid); + } else if (matches(*argv, "cvlan_prio") == 0) { + __u8 cvlan_prio; + + NEXT_ARG(); + if (!eth_type_vlan(vlan_ethtype, num_of_vlans > 1)) { + fprintf(stderr, "Can't set \"cvlan_prio\" if inner vlan ethertype isn't 802.1Q or 802.1AD" + " and num_of_vlans is less than 2\n"); + return -1; + } + ret = get_u8(&cvlan_prio, *argv, 10); + if (ret < 0 || cvlan_prio & ~0x7) { + fprintf(stderr, "Illegal \"cvlan_prio\"\n"); + return -1; + } + addattr8(n, MAX_MSG, + TCA_FLOWER_KEY_CVLAN_PRIO, cvlan_prio); + } else if (matches(*argv, "cvlan_ethtype") == 0) { + NEXT_ARG(); + /* get new ethtype for later parsing */ + ret = flower_parse_vlan_eth_type(*argv, vlan_ethtype, + TCA_FLOWER_KEY_CVLAN_ETH_TYPE, + ð_type, n, num_of_vlans > 1); + if (ret < 0) + return -1; + } else if (matches(*argv, "mpls") == 0) { + NEXT_ARG(); + if (eth_type != htons(ETH_P_MPLS_UC) && + eth_type != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "Can't set \"mpls\" if ethertype isn't MPLS\n"); + return -1; + } + if (mpls_format_old) { + fprintf(stderr, + "Can't set \"mpls\" if \"mpls_label\", \"mpls_tc\", \"mpls_bos\" or \"mpls_ttl\" is set\n"); + return -1; + } + mpls_format_new = true; + if (flower_parse_mpls(&argc, &argv, n) < 0) + return -1; + continue; + } else if (matches(*argv, "mpls_label") == 0) { + __u32 label; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_MPLS_UC) && + eth_type != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "Can't set \"mpls_label\" if ethertype isn't MPLS\n"); + return -1; + } + if (mpls_format_new) { + fprintf(stderr, + "Can't set \"mpls_label\" if \"mpls\" is set\n"); + return -1; + } + mpls_format_old = true; + ret = get_u32(&label, *argv, 10); + if (ret < 0 || label & ~(MPLS_LS_LABEL_MASK >> MPLS_LS_LABEL_SHIFT)) { + fprintf(stderr, "Illegal \"mpls_label\"\n"); + return -1; + } + addattr32(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_LABEL, label); + } else if (matches(*argv, "mpls_tc") == 0) { + __u8 tc; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_MPLS_UC) && + eth_type != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "Can't set \"mpls_tc\" if ethertype isn't MPLS\n"); + return -1; + } + if (mpls_format_new) { + fprintf(stderr, + "Can't set \"mpls_tc\" if \"mpls\" is set\n"); + return -1; + } + mpls_format_old = true; + ret = get_u8(&tc, *argv, 10); + if (ret < 0 || tc & ~(MPLS_LS_TC_MASK >> MPLS_LS_TC_SHIFT)) { + fprintf(stderr, "Illegal \"mpls_tc\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_TC, tc); + } else if (matches(*argv, "mpls_bos") == 0) { + __u8 bos; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_MPLS_UC) && + eth_type != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "Can't set \"mpls_bos\" if ethertype isn't MPLS\n"); + return -1; + } + if (mpls_format_new) { + fprintf(stderr, + "Can't set \"mpls_bos\" if \"mpls\" is set\n"); + return -1; + } + mpls_format_old = true; + ret = get_u8(&bos, *argv, 10); + if (ret < 0 || bos & ~(MPLS_LS_S_MASK >> MPLS_LS_S_SHIFT)) { + fprintf(stderr, "Illegal \"mpls_bos\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_BOS, bos); + } else if (matches(*argv, "mpls_ttl") == 0) { + __u8 ttl; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_MPLS_UC) && + eth_type != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "Can't set \"mpls_ttl\" if ethertype isn't MPLS\n"); + return -1; + } + if (mpls_format_new) { + fprintf(stderr, + "Can't set \"mpls_ttl\" if \"mpls\" is set\n"); + return -1; + } + mpls_format_old = true; + ret = get_u8(&ttl, *argv, 10); + if (ret < 0 || ttl & ~(MPLS_LS_TTL_MASK >> MPLS_LS_TTL_SHIFT)) { + fprintf(stderr, "Illegal \"mpls_ttl\"\n"); + return -1; + } + addattr8(n, MAX_MSG, TCA_FLOWER_KEY_MPLS_TTL, ttl); + } else if (matches(*argv, "dst_mac") == 0) { + NEXT_ARG(); + ret = flower_parse_eth_addr(*argv, + TCA_FLOWER_KEY_ETH_DST, + TCA_FLOWER_KEY_ETH_DST_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"dst_mac\"\n"); + return -1; + } + } else if (matches(*argv, "src_mac") == 0) { + NEXT_ARG(); + ret = flower_parse_eth_addr(*argv, + TCA_FLOWER_KEY_ETH_SRC, + TCA_FLOWER_KEY_ETH_SRC_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"src_mac\"\n"); + return -1; + } + } else if (matches(*argv, "ip_proto") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_proto(*argv, eth_type, + TCA_FLOWER_KEY_IP_PROTO, + &ip_proto, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ip_proto\"\n"); + return -1; + } + } else if (matches(*argv, "ip_tos") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_tos_ttl(*argv, + TCA_FLOWER_KEY_IP_TOS, + TCA_FLOWER_KEY_IP_TOS_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ip_tos\"\n"); + return -1; + } + } else if (matches(*argv, "ip_ttl") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_tos_ttl(*argv, + TCA_FLOWER_KEY_IP_TTL, + TCA_FLOWER_KEY_IP_TTL_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ip_ttl\"\n"); + return -1; + } + } else if (matches(*argv, "dst_ip") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_addr(*argv, eth_type, + TCA_FLOWER_KEY_IPV4_DST, + TCA_FLOWER_KEY_IPV4_DST_MASK, + TCA_FLOWER_KEY_IPV6_DST, + TCA_FLOWER_KEY_IPV6_DST_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"dst_ip\"\n"); + return -1; + } + } else if (matches(*argv, "src_ip") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_addr(*argv, eth_type, + TCA_FLOWER_KEY_IPV4_SRC, + TCA_FLOWER_KEY_IPV4_SRC_MASK, + TCA_FLOWER_KEY_IPV6_SRC, + TCA_FLOWER_KEY_IPV6_SRC_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"src_ip\"\n"); + return -1; + } + } else if (matches(*argv, "dst_port") == 0) { + NEXT_ARG(); + ret = flower_parse_port(*argv, ip_proto, + FLOWER_ENDPOINT_DST, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"dst_port\"\n"); + return -1; + } + } else if (matches(*argv, "src_port") == 0) { + NEXT_ARG(); + ret = flower_parse_port(*argv, ip_proto, + FLOWER_ENDPOINT_SRC, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"src_port\"\n"); + return -1; + } + } else if (matches(*argv, "tcp_flags") == 0) { + NEXT_ARG(); + ret = flower_parse_tcp_flags(*argv, + TCA_FLOWER_KEY_TCP_FLAGS, + TCA_FLOWER_KEY_TCP_FLAGS_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"tcp_flags\"\n"); + return -1; + } + } else if (matches(*argv, "type") == 0) { + NEXT_ARG(); + ret = flower_parse_icmp(*argv, eth_type, ip_proto, + FLOWER_ICMP_FIELD_TYPE, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"icmp type\"\n"); + return -1; + } + } else if (matches(*argv, "code") == 0) { + NEXT_ARG(); + ret = flower_parse_icmp(*argv, eth_type, ip_proto, + FLOWER_ICMP_FIELD_CODE, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"icmp code\"\n"); + return -1; + } + } else if (!strcmp(*argv, "l2tpv3_sid")) { + NEXT_ARG(); + ret = flower_parse_l2tpv3(*argv, ip_proto, n); + if (ret < 0) + return -1; + } else if (!strcmp(*argv, "spi")) { + NEXT_ARG(); + ret = flower_parse_spi(*argv, ip_proto, n); + if (ret < 0) + return -1; + } else if (matches(*argv, "arp_tip") == 0) { + NEXT_ARG(); + ret = flower_parse_arp_ip_addr(*argv, eth_type, + TCA_FLOWER_KEY_ARP_TIP, + TCA_FLOWER_KEY_ARP_TIP_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"arp_tip\"\n"); + return -1; + } + } else if (matches(*argv, "arp_sip") == 0) { + NEXT_ARG(); + ret = flower_parse_arp_ip_addr(*argv, eth_type, + TCA_FLOWER_KEY_ARP_SIP, + TCA_FLOWER_KEY_ARP_SIP_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"arp_sip\"\n"); + return -1; + } + } else if (matches(*argv, "arp_op") == 0) { + NEXT_ARG(); + ret = flower_parse_arp_op(*argv, eth_type, + TCA_FLOWER_KEY_ARP_OP, + TCA_FLOWER_KEY_ARP_OP_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"arp_op\"\n"); + return -1; + } + } else if (matches(*argv, "arp_tha") == 0) { + NEXT_ARG(); + ret = flower_parse_eth_addr(*argv, + TCA_FLOWER_KEY_ARP_THA, + TCA_FLOWER_KEY_ARP_THA_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"arp_tha\"\n"); + return -1; + } + } else if (matches(*argv, "arp_sha") == 0) { + NEXT_ARG(); + ret = flower_parse_eth_addr(*argv, + TCA_FLOWER_KEY_ARP_SHA, + TCA_FLOWER_KEY_ARP_SHA_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"arp_sha\"\n"); + return -1; + } + + } else if (!strcmp(*argv, "pppoe_sid")) { + __be16 sid; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_PPP_SES)) { + fprintf(stderr, + "Can't set \"pppoe_sid\" if ethertype isn't PPPoE session\n"); + return -1; + } + ret = get_be16(&sid, *argv, 10); + if (ret < 0) { + fprintf(stderr, "Illegal \"pppoe_sid\"\n"); + return -1; + } + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_PPPOE_SID, sid); + } else if (!strcmp(*argv, "ppp_proto")) { + __be16 proto; + + NEXT_ARG(); + if (eth_type != htons(ETH_P_PPP_SES)) { + fprintf(stderr, + "Can't set \"ppp_proto\" if ethertype isn't PPPoE session\n"); + return -1; + } + if (ppp_proto_a2n(&proto, *argv)) + invarg("invalid ppp_proto", *argv); + /* get new ethtype for later parsing */ + if (proto == htons(PPP_IP)) + eth_type = htons(ETH_P_IP); + else if (proto == htons(PPP_IPV6)) + eth_type = htons(ETH_P_IPV6); + else if (proto == htons(PPP_MPLS_UC)) + eth_type = htons(ETH_P_MPLS_UC); + else if (proto == htons(PPP_MPLS_MC)) + eth_type = htons(ETH_P_MPLS_MC); + addattr16(n, MAX_MSG, TCA_FLOWER_KEY_PPP_PROTO, proto); + } else if (matches(*argv, "enc_dst_ip") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_addr(*argv, 0, + TCA_FLOWER_KEY_ENC_IPV4_DST, + TCA_FLOWER_KEY_ENC_IPV4_DST_MASK, + TCA_FLOWER_KEY_ENC_IPV6_DST, + TCA_FLOWER_KEY_ENC_IPV6_DST_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_dst_ip\"\n"); + return -1; + } + } else if (matches(*argv, "enc_src_ip") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_addr(*argv, 0, + TCA_FLOWER_KEY_ENC_IPV4_SRC, + TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK, + TCA_FLOWER_KEY_ENC_IPV6_SRC, + TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_src_ip\"\n"); + return -1; + } + } else if (matches(*argv, "enc_key_id") == 0) { + NEXT_ARG(); + ret = flower_parse_key_id(*argv, + TCA_FLOWER_KEY_ENC_KEY_ID, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_key_id\"\n"); + return -1; + } + } else if (matches(*argv, "enc_dst_port") == 0) { + NEXT_ARG(); + ret = flower_parse_enc_port(*argv, + TCA_FLOWER_KEY_ENC_UDP_DST_PORT, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_dst_port\"\n"); + return -1; + } + } else if (matches(*argv, "enc_tos") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_tos_ttl(*argv, + TCA_FLOWER_KEY_ENC_IP_TOS, + TCA_FLOWER_KEY_ENC_IP_TOS_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_tos\"\n"); + return -1; + } + } else if (matches(*argv, "enc_ttl") == 0) { + NEXT_ARG(); + ret = flower_parse_ip_tos_ttl(*argv, + TCA_FLOWER_KEY_ENC_IP_TTL, + TCA_FLOWER_KEY_ENC_IP_TTL_MASK, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"enc_ttl\"\n"); + return -1; + } + } else if (matches(*argv, "geneve_opts") == 0) { + NEXT_ARG(); + ret = flower_parse_enc_opts_geneve(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"geneve_opts\"\n"); + return -1; + } + } else if (matches(*argv, "vxlan_opts") == 0) { + NEXT_ARG(); + ret = flower_parse_enc_opts_vxlan(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"vxlan_opts\"\n"); + return -1; + } + } else if (matches(*argv, "erspan_opts") == 0) { + NEXT_ARG(); + ret = flower_parse_enc_opts_erspan(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"erspan_opts\"\n"); + return -1; + } + } else if (!strcmp(*argv, "gtp_opts")) { + NEXT_ARG(); + ret = flower_parse_enc_opts_gtp(*argv, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"gtp_opts\"\n"); + return -1; + } + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + ret = parse_action(&argc, &argv, TCA_FLOWER_ACT, n); + if (ret) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + } else if (!strcmp(*argv, "cfm")) { + NEXT_ARG(); + ret = flower_parse_cfm(&argc, &argv, eth_type, n); + if (ret < 0) + return -1; + continue; + } else { + if (strcmp(*argv, "help") != 0) + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + +parse_done: + ret = addattr32(n, MAX_MSG, TCA_FLOWER_FLAGS, flags); + if (ret) + return ret; + + if (mtf_mask) { + ret = addattr32(n, MAX_MSG, TCA_FLOWER_KEY_FLAGS, htonl(mtf)); + if (ret) + return ret; + + ret = addattr32(n, MAX_MSG, TCA_FLOWER_KEY_FLAGS_MASK, htonl(mtf_mask)); + if (ret) + return ret; + } + + if (tc_proto != htons(ETH_P_ALL)) { + ret = addattr16(n, MAX_MSG, TCA_FLOWER_KEY_ETH_TYPE, tc_proto); + if (ret) + return ret; + } + + tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail; + + return 0; +} + +static int __mask_bits(char *addr, size_t len) +{ + int bits = 0; + bool hole = false; + int i; + int j; + + for (i = 0; i < len; i++, addr++) { + for (j = 7; j >= 0; j--) { + if (((*addr) >> j) & 0x1) { + if (hole) + return -1; + bits++; + } else if (bits) { + hole = true; + } else { + return -1; + } + } + } + return bits; +} + +static void flower_print_eth_addr(const char *name, struct rtattr *addr_attr, + struct rtattr *mask_attr) +{ + SPRINT_BUF(out); + SPRINT_BUF(b1); + size_t done; + int bits; + + if (!addr_attr || RTA_PAYLOAD(addr_attr) != ETH_ALEN) + return; + done = sprintf(out, "%s", + ll_addr_n2a(RTA_DATA(addr_attr), ETH_ALEN, + 0, b1, sizeof(b1))); + if (mask_attr && RTA_PAYLOAD(mask_attr) == ETH_ALEN) { + bits = __mask_bits(RTA_DATA(mask_attr), ETH_ALEN); + if (bits < 0) + sprintf(out + done, "/%s", + ll_addr_n2a(RTA_DATA(mask_attr), ETH_ALEN, + 0, b1, sizeof(b1))); + else if (bits < ETH_ALEN * 8) + sprintf(out + done, "/%d", bits); + } + + print_indent_name_value(name, out); +} + +static void flower_print_eth_type(__be16 *p_eth_type, + struct rtattr *eth_type_attr) +{ + SPRINT_BUF(out); + __be16 eth_type; + + if (!eth_type_attr) + return; + + eth_type = rta_getattr_u16(eth_type_attr); + if (eth_type == htons(ETH_P_IP)) + sprintf(out, "ipv4"); + else if (eth_type == htons(ETH_P_IPV6)) + sprintf(out, "ipv6"); + else if (eth_type == htons(ETH_P_ARP)) + sprintf(out, "arp"); + else if (eth_type == htons(ETH_P_RARP)) + sprintf(out, "rarp"); + else + sprintf(out, "%04x", ntohs(eth_type)); + + print_nl(); + print_string(PRINT_ANY, "eth_type", " eth_type %s", out); + *p_eth_type = eth_type; +} + +static void flower_print_ip_proto(__u8 *p_ip_proto, + struct rtattr *ip_proto_attr) +{ + SPRINT_BUF(out); + __u8 ip_proto; + + if (!ip_proto_attr) + return; + + ip_proto = rta_getattr_u8(ip_proto_attr); + if (ip_proto == IPPROTO_TCP) + sprintf(out, "tcp"); + else if (ip_proto == IPPROTO_UDP) + sprintf(out, "udp"); + else if (ip_proto == IPPROTO_SCTP) + sprintf(out, "sctp"); + else if (ip_proto == IPPROTO_ICMP) + sprintf(out, "icmp"); + else if (ip_proto == IPPROTO_ICMPV6) + sprintf(out, "icmpv6"); + else if (ip_proto == IPPROTO_L2TP) + sprintf(out, "l2tp"); + else if (ip_proto == IPPROTO_ESP) + sprintf(out, "esp"); + else if (ip_proto == IPPROTO_AH) + sprintf(out, "ah"); + else + sprintf(out, "0x%02x", ip_proto); + + print_nl(); + print_string(PRINT_ANY, "ip_proto", " ip_proto %s", out); + *p_ip_proto = ip_proto; +} + +static void flower_print_ip_attr(const char *name, struct rtattr *key_attr, + struct rtattr *mask_attr) +{ + print_masked_u8(name, key_attr, mask_attr, true); +} + +static void flower_print_matching_flags(char *name, + enum flower_matching_flags type, + struct rtattr *attr, + struct rtattr *mask_attr) +{ + int i; + int count = 0; + __u32 mtf; + __u32 mtf_mask; + + if (!mask_attr || RTA_PAYLOAD(mask_attr) != 4) + return; + + mtf = ntohl(rta_getattr_u32(attr)); + mtf_mask = ntohl(rta_getattr_u32(mask_attr)); + + for (i = 0; i < ARRAY_SIZE(flags_str); i++) { + if (type != flags_str[i].type) + continue; + if (mtf_mask & flags_str[i].flag) { + if (++count == 1) { + print_nl(); + print_string(PRINT_FP, NULL, " %s ", name); + open_json_object(name); + } else { + print_string(PRINT_FP, NULL, "/", NULL); + } + + print_bool(PRINT_JSON, flags_str[i].string, NULL, + mtf & flags_str[i].flag); + if (mtf & flags_str[i].flag) + print_string(PRINT_FP, NULL, "%s", + flags_str[i].string); + else + print_string(PRINT_FP, NULL, "no%s", + flags_str[i].string); + } + } + if (count) + close_json_object(); +} + +static void flower_print_ip_addr(char *name, __be16 eth_type, + struct rtattr *addr4_attr, + struct rtattr *mask4_attr, + struct rtattr *addr6_attr, + struct rtattr *mask6_attr) +{ + struct rtattr *addr_attr; + struct rtattr *mask_attr; + SPRINT_BUF(out); + size_t done; + int family; + size_t len; + int bits; + + if (eth_type == htons(ETH_P_IP)) { + family = AF_INET; + addr_attr = addr4_attr; + mask_attr = mask4_attr; + len = 4; + } else if (eth_type == htons(ETH_P_IPV6)) { + family = AF_INET6; + addr_attr = addr6_attr; + mask_attr = mask6_attr; + len = 16; + } else { + return; + } + if (!addr_attr || RTA_PAYLOAD(addr_attr) != len) + return; + if (!mask_attr || RTA_PAYLOAD(mask_attr) != len) + return; + done = sprintf(out, "%s", rt_addr_n2a_rta(family, addr_attr)); + bits = __mask_bits(RTA_DATA(mask_attr), len); + if (bits < 0) + sprintf(out + done, "/%s", rt_addr_n2a_rta(family, mask_attr)); + else if (bits < len * 8) + sprintf(out + done, "/%d", bits); + + print_indent_name_value(name, out); +} + +static void flower_print_ip4_addr(char *name, struct rtattr *addr_attr, + struct rtattr *mask_attr) +{ + return flower_print_ip_addr(name, htons(ETH_P_IP), + addr_attr, mask_attr, 0, 0); +} + +static void flower_print_port(char *name, struct rtattr *attr, + struct rtattr *mask_attr) +{ + print_masked_be16(name, attr, mask_attr, true); +} + +static void flower_print_port_range(char *name, struct rtattr *min_attr, + struct rtattr *max_attr) +{ + if (!min_attr || !max_attr) + return; + + if (is_json_context()) { + open_json_object(name); + print_hu(PRINT_JSON, "start", NULL, rta_getattr_be16(min_attr)); + print_hu(PRINT_JSON, "end", NULL, rta_getattr_be16(max_attr)); + close_json_object(); + } else { + SPRINT_BUF(out); + size_t done; + + done = sprintf(out, "%u", rta_getattr_be16(min_attr)); + sprintf(out + done, "-%u", rta_getattr_be16(max_attr)); + print_indent_name_value(name, out); + } +} + +static void flower_print_tcp_flags(const char *name, struct rtattr *flags_attr, + struct rtattr *mask_attr) +{ + SPRINT_BUF(out); + size_t done; + + if (!flags_attr) + return; + + done = sprintf(out, "0x%x", rta_getattr_be16(flags_attr)); + if (mask_attr) + sprintf(out + done, "/%x", rta_getattr_be16(mask_attr)); + + print_indent_name_value(name, out); +} + +static void flower_print_ct_state(struct rtattr *flags_attr, + struct rtattr *mask_attr) +{ + SPRINT_BUF(out); + uint16_t state; + uint16_t state_mask; + size_t done = 0; + int i; + + if (!flags_attr) + return; + + state = rta_getattr_u16(flags_attr); + if (mask_attr) + state_mask = rta_getattr_u16(mask_attr); + else + state_mask = UINT16_MAX; + + for (i = 0; i < ARRAY_SIZE(flower_ct_states); i++) { + if (!(state_mask & flower_ct_states[i].flag)) + continue; + + if (state & flower_ct_states[i].flag) + done += sprintf(out + done, "+%s", + flower_ct_states[i].str); + else + done += sprintf(out + done, "-%s", + flower_ct_states[i].str); + } + + print_nl(); + print_string(PRINT_ANY, "ct_state", " ct_state %s", out); +} + +static void flower_print_ct_label(struct rtattr *attr, + struct rtattr *mask_attr) +{ + const unsigned char *str; + bool print_mask = false; + int data_len, i; + char out[128]; + char *p; + + if (!attr) + return; + + data_len = RTA_PAYLOAD(attr); + hexstring_n2a(RTA_DATA(attr), data_len, out, sizeof(out)); + p = out + data_len*2; + + data_len = RTA_PAYLOAD(attr); + str = RTA_DATA(mask_attr); + if (data_len != 16) + print_mask = true; + for (i = 0; !print_mask && i < data_len; i++) { + if (str[i] != 0xff) + print_mask = true; + } + if (print_mask) { + *p++ = '/'; + hexstring_n2a(RTA_DATA(mask_attr), data_len, p, + sizeof(out)-(p-out)); + p += data_len*2; + } + *p = '\0'; + + print_nl(); + print_string(PRINT_ANY, "ct_label", " ct_label %s", out); +} + +static void flower_print_ct_zone(struct rtattr *attr, + struct rtattr *mask_attr) +{ + print_masked_u16("ct_zone", attr, mask_attr, true); +} + +static void flower_print_ct_mark(struct rtattr *attr, + struct rtattr *mask_attr) +{ + print_masked_u32("ct_mark", attr, mask_attr, true); +} + +static void flower_print_key_id(const char *name, struct rtattr *attr) +{ + if (!attr) + return; + + print_nl(); + print_uint_indent_name_value(name, rta_getattr_be32(attr)); +} + +static void flower_print_geneve_opts(const char *name, struct rtattr *attr, + char *strbuf) +{ + struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX + 1]; + int ii, data_len, offset = 0, slen = 0; + struct rtattr *i = RTA_DATA(attr); + int rem = RTA_PAYLOAD(attr); + __u8 type, data_r[rem]; + char data[rem * 2 + 1]; + __u16 class; + + open_json_array(PRINT_JSON, name); + while (rem) { + parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX, i, rem); + class = rta_getattr_be16(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS]); + type = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE]); + data_len = RTA_PAYLOAD(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]); + hexstring_n2a(RTA_DATA(tb[TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA]), + data_len, data, sizeof(data)); + hex2mem(data, data_r, data_len); + offset += data_len + 20; + rem -= data_len + 20; + i = RTA_DATA(attr) + offset; + + open_json_object(NULL); + print_uint(PRINT_JSON, "class", NULL, class); + print_uint(PRINT_JSON, "type", NULL, type); + open_json_array(PRINT_JSON, "data"); + for (ii = 0; ii < data_len; ii++) + print_uint(PRINT_JSON, NULL, NULL, data_r[ii]); + close_json_array(PRINT_JSON, "data"); + close_json_object(); + + slen += sprintf(strbuf + slen, "%04x:%02x:%s", + class, type, data); + if (rem) + slen += sprintf(strbuf + slen, ","); + } + close_json_array(PRINT_JSON, name); +} + +static void flower_print_vxlan_opts(const char *name, struct rtattr *attr, + char *strbuf) +{ + struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_VXLAN_MAX + 1]; + struct rtattr *i = RTA_DATA(attr); + int rem = RTA_PAYLOAD(attr); + __u32 gbp; + + parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_VXLAN_MAX, i, rem); + gbp = rta_getattr_u32(tb[TCA_FLOWER_KEY_ENC_OPT_VXLAN_GBP]); + + open_json_array(PRINT_JSON, name); + open_json_object(NULL); + print_uint(PRINT_JSON, "gbp", NULL, gbp); + close_json_object(); + close_json_array(PRINT_JSON, name); + + sprintf(strbuf, "%u", gbp); +} + +static void flower_print_erspan_opts(const char *name, struct rtattr *attr, + char *strbuf) +{ + struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_ERSPAN_MAX + 1]; + __u8 ver, hwid, dir; + __u32 idx; + + parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_ERSPAN_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + ver = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_ERSPAN_VER]); + if (ver == 1) { + idx = rta_getattr_be32(tb[TCA_FLOWER_KEY_ENC_OPT_ERSPAN_INDEX]); + hwid = 0; + dir = 0; + } else { + idx = 0; + hwid = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_ERSPAN_HWID]); + dir = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_ERSPAN_DIR]); + } + + open_json_array(PRINT_JSON, name); + open_json_object(NULL); + print_uint(PRINT_JSON, "ver", NULL, ver); + print_uint(PRINT_JSON, "index", NULL, idx); + print_uint(PRINT_JSON, "dir", NULL, dir); + print_uint(PRINT_JSON, "hwid", NULL, hwid); + close_json_object(); + close_json_array(PRINT_JSON, name); + + sprintf(strbuf, "%u:%u:%u:%u", ver, idx, dir, hwid); +} + +static void flower_print_gtp_opts(const char *name, struct rtattr *attr, + char *strbuf, int len) +{ + struct rtattr *tb[TCA_FLOWER_KEY_ENC_OPT_GTP_MAX + 1]; + __u8 pdu_type, qfi; + + parse_rtattr(tb, TCA_FLOWER_KEY_ENC_OPT_GTP_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + + pdu_type = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_GTP_PDU_TYPE]); + qfi = rta_getattr_u8(tb[TCA_FLOWER_KEY_ENC_OPT_GTP_QFI]); + + snprintf(strbuf, len, "%02x:%02x", pdu_type, qfi); +} + +static void __attribute__((format(printf, 2, 0))) +flower_print_enc_parts(const char *name, const char *namefrm, + struct rtattr *attr, char *key, char *mask) +{ + char *key_token, *mask_token, *out; + int len; + + out = malloc(RTA_PAYLOAD(attr) * 4 + 3); + if (!out) + return; + + len = 0; + key_token = strsep(&key, ","); + mask_token = strsep(&mask, ","); + while (key_token) { + len += sprintf(&out[len], "%s/%s,", key_token, mask_token); + mask_token = strsep(&mask, ","); + key_token = strsep(&key, ","); + } + + out[len - 1] = '\0'; + print_nl(); + print_string(PRINT_FP, name, namefrm, out); + free(out); +} + +static void flower_print_enc_opts(const char *name, struct rtattr *attr, + struct rtattr *mask_attr) +{ + struct rtattr *key_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1]; + struct rtattr *msk_tb[TCA_FLOWER_KEY_ENC_OPTS_MAX + 1]; + char *key, *msk; + int len; + + if (!attr) + return; + + len = RTA_PAYLOAD(attr) * 2 + 1; + + key = malloc(len); + if (!key) + return; + + msk = malloc(len); + if (!msk) + goto err_key_free; + + parse_rtattr_nested(key_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, attr); + parse_rtattr_nested(msk_tb, TCA_FLOWER_KEY_ENC_OPTS_MAX, mask_attr); + + if (key_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE]) { + flower_print_geneve_opts("geneve_opt_key", + key_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], key); + + if (msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE]) + flower_print_geneve_opts("geneve_opt_mask", + msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GENEVE], msk); + + flower_print_enc_parts(name, " geneve_opts %s", attr, key, + msk); + } else if (key_tb[TCA_FLOWER_KEY_ENC_OPTS_VXLAN]) { + flower_print_vxlan_opts("vxlan_opt_key", + key_tb[TCA_FLOWER_KEY_ENC_OPTS_VXLAN], key); + + if (msk_tb[TCA_FLOWER_KEY_ENC_OPTS_VXLAN]) + flower_print_vxlan_opts("vxlan_opt_mask", + msk_tb[TCA_FLOWER_KEY_ENC_OPTS_VXLAN], msk); + + flower_print_enc_parts(name, " vxlan_opts %s", attr, key, + msk); + } else if (key_tb[TCA_FLOWER_KEY_ENC_OPTS_ERSPAN]) { + flower_print_erspan_opts("erspan_opt_key", + key_tb[TCA_FLOWER_KEY_ENC_OPTS_ERSPAN], key); + + if (msk_tb[TCA_FLOWER_KEY_ENC_OPTS_ERSPAN]) + flower_print_erspan_opts("erspan_opt_mask", + msk_tb[TCA_FLOWER_KEY_ENC_OPTS_ERSPAN], msk); + + flower_print_enc_parts(name, " erspan_opts %s", attr, key, + msk); + } else if (key_tb[TCA_FLOWER_KEY_ENC_OPTS_GTP]) { + flower_print_gtp_opts("gtp_opt_key", + key_tb[TCA_FLOWER_KEY_ENC_OPTS_GTP], + key, len); + + if (msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GTP]) + flower_print_gtp_opts("gtp_opt_mask", + msk_tb[TCA_FLOWER_KEY_ENC_OPTS_GTP], + msk, len); + + flower_print_enc_parts(name, " gtp_opts %s", attr, key, + msk); + } + + free(msk); +err_key_free: + free(key); +} + +static void flower_print_masked_u8(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, + const char *(*value_to_str)(__u8 value)) +{ + const char *value_str = NULL; + __u8 value, mask; + SPRINT_BUF(out); + size_t done; + + if (!attr) + return; + + value = rta_getattr_u8(attr); + mask = mask_attr ? rta_getattr_u8(mask_attr) : UINT8_MAX; + if (mask == UINT8_MAX && value_to_str) + value_str = value_to_str(value); + + if (value_str) + done = sprintf(out, "%s", value_str); + else + done = sprintf(out, "%d", value); + + if (mask != UINT8_MAX) + sprintf(out + done, "/%d", mask); + + print_indent_name_value(name, out); +} + +static void flower_print_u8(const char *name, struct rtattr *attr) +{ + flower_print_masked_u8(name, attr, NULL, NULL); +} + +static void flower_print_u32(const char *name, struct rtattr *attr) +{ + if (!attr) + return; + + print_nl(); + print_uint_indent_name_value(name, rta_getattr_u32(attr)); +} + +static void flower_print_mpls_opt_lse(struct rtattr *lse) +{ + struct rtattr *tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX + 1]; + struct rtattr *attr; + + if (lse->rta_type != (TCA_FLOWER_KEY_MPLS_OPTS_LSE | NLA_F_NESTED)) { + fprintf(stderr, "rta_type 0x%x, expecting 0x%x (0x%x & 0x%x)\n", + lse->rta_type, + TCA_FLOWER_KEY_MPLS_OPTS_LSE & NLA_F_NESTED, + TCA_FLOWER_KEY_MPLS_OPTS_LSE, NLA_F_NESTED); + return; + } + + parse_rtattr(tb, TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX, RTA_DATA(lse), + RTA_PAYLOAD(lse)); + + print_nl(); + print_string(PRINT_FP, NULL, " lse", NULL); + open_json_object(NULL); + attr = tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH]; + if (attr) + print_hhu(PRINT_ANY, "depth", " depth %u", + rta_getattr_u8(attr)); + attr = tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL]; + if (attr) + print_uint(PRINT_ANY, "label", " label %u", + rta_getattr_u32(attr)); + attr = tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_TC]; + if (attr) + print_hhu(PRINT_ANY, "tc", " tc %u", rta_getattr_u8(attr)); + attr = tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS]; + if (attr) + print_hhu(PRINT_ANY, "bos", " bos %u", rta_getattr_u8(attr)); + attr = tb[TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL]; + if (attr) + print_hhu(PRINT_ANY, "ttl", " ttl %u", rta_getattr_u8(attr)); + close_json_object(); +} + +static void flower_print_mpls_opts(struct rtattr *attr) +{ + struct rtattr *lse; + int rem; + + if (!attr || !(attr->rta_type & NLA_F_NESTED)) + return; + + print_nl(); + print_string(PRINT_FP, NULL, " mpls", NULL); + open_json_array(PRINT_JSON, "mpls"); + rem = RTA_PAYLOAD(attr); + lse = RTA_DATA(attr); + while (RTA_OK(lse, rem)) { + flower_print_mpls_opt_lse(lse); + lse = RTA_NEXT(lse, rem); + }; + if (rem) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", + rem, lse->rta_len); + close_json_array(PRINT_JSON, NULL); +} + +static void flower_print_arp_op(const char *name, + struct rtattr *op_attr, + struct rtattr *mask_attr) +{ + flower_print_masked_u8(name, op_attr, mask_attr, + flower_print_arp_op_to_name); +} + +static void flower_print_cfm(struct rtattr *attr) +{ + struct rtattr *tb[TCA_FLOWER_KEY_CFM_OPT_MAX + 1]; + + if (!attr || !(attr->rta_type & NLA_F_NESTED)) + return; + + parse_rtattr(tb, TCA_FLOWER_KEY_CFM_OPT_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + + print_nl(); + print_string(PRINT_FP, NULL, " cfm", NULL); + open_json_object("cfm"); + + if (tb[TCA_FLOWER_KEY_CFM_MD_LEVEL]) + print_hhu(PRINT_ANY, "mdl", " mdl %u", + rta_getattr_u8(tb[TCA_FLOWER_KEY_CFM_MD_LEVEL])); + + if (tb[TCA_FLOWER_KEY_CFM_OPCODE]) + print_hhu(PRINT_ANY, "op", " op %u", + rta_getattr_u8(tb[TCA_FLOWER_KEY_CFM_OPCODE])); + + close_json_object(); +} + +static int flower_print_opt(struct filter_util *qu, FILE *f, + struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_FLOWER_MAX + 1]; + __be16 min_port_type, max_port_type; + int nl_type, nl_mask_type; + __be16 eth_type = 0; + __u8 ip_proto = 0xff; + + if (!opt) + return 0; + + parse_rtattr_nested(tb, TCA_FLOWER_MAX, opt); + + if (handle) + print_uint(PRINT_ANY, "handle", "handle 0x%x ", handle); + + if (tb[TCA_FLOWER_CLASSID]) { + __u32 h = rta_getattr_u32(tb[TCA_FLOWER_CLASSID]); + + if (TC_H_MIN(h) < TC_H_MIN_PRIORITY || + TC_H_MIN(h) > (TC_H_MIN_PRIORITY + TC_QOPT_MAX_QUEUE - 1)) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "classid", "classid %s ", + sprint_tc_classid(h, b1)); + } else { + print_uint(PRINT_ANY, "hw_tc", "hw_tc %u ", + TC_H_MIN(h) - TC_H_MIN_PRIORITY); + } + } + + if (tb[TCA_FLOWER_INDEV]) { + struct rtattr *attr = tb[TCA_FLOWER_INDEV]; + + print_nl(); + print_string(PRINT_ANY, "indev", " indev %s", + rta_getattr_str(attr)); + } + + open_json_object("keys"); + + if (tb[TCA_FLOWER_KEY_NUM_OF_VLANS]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_NUM_OF_VLANS]; + + print_nl(); + print_uint(PRINT_ANY, "num_of_vlans", " num_of_vlans %d", + rta_getattr_u8(attr)); + } + + if (tb[TCA_FLOWER_KEY_VLAN_ID]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ID]; + + print_nl(); + print_uint(PRINT_ANY, "vlan_id", " vlan_id %u", + rta_getattr_u16(attr)); + } + + if (tb[TCA_FLOWER_KEY_VLAN_PRIO]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_PRIO]; + + print_nl(); + print_uint(PRINT_ANY, "vlan_prio", " vlan_prio %d", + rta_getattr_u8(attr)); + } + + if (tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) { + SPRINT_BUF(buf); + struct rtattr *attr = tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]; + + print_nl(); + print_string(PRINT_ANY, "vlan_ethtype", " vlan_ethtype %s", + ll_proto_n2a(rta_getattr_u16(attr), + buf, sizeof(buf))); + } + + if (tb[TCA_FLOWER_KEY_CVLAN_ID]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_ID]; + + print_nl(); + print_uint(PRINT_ANY, "cvlan_id", " cvlan_id %u", + rta_getattr_u16(attr)); + } + + if (tb[TCA_FLOWER_KEY_CVLAN_PRIO]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_PRIO]; + + print_nl(); + print_uint(PRINT_ANY, "cvlan_prio", " cvlan_prio %d", + rta_getattr_u8(attr)); + } + + if (tb[TCA_FLOWER_KEY_CVLAN_ETH_TYPE]) { + SPRINT_BUF(buf); + struct rtattr *attr = tb[TCA_FLOWER_KEY_CVLAN_ETH_TYPE]; + + print_nl(); + print_string(PRINT_ANY, "cvlan_ethtype", " cvlan_ethtype %s", + ll_proto_n2a(rta_getattr_u16(attr), + buf, sizeof(buf))); + } + + flower_print_eth_addr("dst_mac", tb[TCA_FLOWER_KEY_ETH_DST], + tb[TCA_FLOWER_KEY_ETH_DST_MASK]); + flower_print_eth_addr("src_mac", tb[TCA_FLOWER_KEY_ETH_SRC], + tb[TCA_FLOWER_KEY_ETH_SRC_MASK]); + + flower_print_eth_type(ð_type, tb[TCA_FLOWER_KEY_ETH_TYPE]); + flower_print_ip_proto(&ip_proto, tb[TCA_FLOWER_KEY_IP_PROTO]); + + flower_print_ip_attr("ip_tos", tb[TCA_FLOWER_KEY_IP_TOS], + tb[TCA_FLOWER_KEY_IP_TOS_MASK]); + flower_print_ip_attr("ip_ttl", tb[TCA_FLOWER_KEY_IP_TTL], + tb[TCA_FLOWER_KEY_IP_TTL_MASK]); + + flower_print_mpls_opts(tb[TCA_FLOWER_KEY_MPLS_OPTS]); + flower_print_u32("mpls_label", tb[TCA_FLOWER_KEY_MPLS_LABEL]); + flower_print_u8("mpls_tc", tb[TCA_FLOWER_KEY_MPLS_TC]); + flower_print_u8("mpls_bos", tb[TCA_FLOWER_KEY_MPLS_BOS]); + flower_print_u8("mpls_ttl", tb[TCA_FLOWER_KEY_MPLS_TTL]); + + flower_print_ip_addr("dst_ip", eth_type, + tb[TCA_FLOWER_KEY_IPV4_DST], + tb[TCA_FLOWER_KEY_IPV4_DST_MASK], + tb[TCA_FLOWER_KEY_IPV6_DST], + tb[TCA_FLOWER_KEY_IPV6_DST_MASK]); + + flower_print_ip_addr("src_ip", eth_type, + tb[TCA_FLOWER_KEY_IPV4_SRC], + tb[TCA_FLOWER_KEY_IPV4_SRC_MASK], + tb[TCA_FLOWER_KEY_IPV6_SRC], + tb[TCA_FLOWER_KEY_IPV6_SRC_MASK]); + + nl_type = flower_port_attr_type(ip_proto, FLOWER_ENDPOINT_DST); + nl_mask_type = flower_port_attr_mask_type(ip_proto, FLOWER_ENDPOINT_DST); + if (nl_type >= 0) + flower_print_port("dst_port", tb[nl_type], tb[nl_mask_type]); + nl_type = flower_port_attr_type(ip_proto, FLOWER_ENDPOINT_SRC); + nl_mask_type = flower_port_attr_mask_type(ip_proto, FLOWER_ENDPOINT_SRC); + if (nl_type >= 0) + flower_print_port("src_port", tb[nl_type], tb[nl_mask_type]); + + if (!flower_port_range_attr_type(ip_proto, FLOWER_ENDPOINT_DST, + &min_port_type, &max_port_type)) + flower_print_port_range("dst_port", + tb[min_port_type], tb[max_port_type]); + + if (!flower_port_range_attr_type(ip_proto, FLOWER_ENDPOINT_SRC, + &min_port_type, &max_port_type)) + flower_print_port_range("src_port", + tb[min_port_type], tb[max_port_type]); + + flower_print_tcp_flags("tcp_flags", tb[TCA_FLOWER_KEY_TCP_FLAGS], + tb[TCA_FLOWER_KEY_TCP_FLAGS_MASK]); + + nl_type = flower_icmp_attr_type(eth_type, ip_proto, + FLOWER_ICMP_FIELD_TYPE); + nl_mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto, + FLOWER_ICMP_FIELD_TYPE); + if (nl_type >= 0 && nl_mask_type >= 0) + flower_print_masked_u8("icmp_type", tb[nl_type], + tb[nl_mask_type], NULL); + + nl_type = flower_icmp_attr_type(eth_type, ip_proto, + FLOWER_ICMP_FIELD_CODE); + nl_mask_type = flower_icmp_attr_mask_type(eth_type, ip_proto, + FLOWER_ICMP_FIELD_CODE); + if (nl_type >= 0 && nl_mask_type >= 0) + flower_print_masked_u8("icmp_code", tb[nl_type], + tb[nl_mask_type], NULL); + + if (tb[TCA_FLOWER_KEY_L2TPV3_SID]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_L2TPV3_SID]; + + print_nl(); + print_uint(PRINT_ANY, "l2tpv3_sid", " l2tpv3_sid %u", + rta_getattr_be32(attr)); + } + + if (tb[TCA_FLOWER_KEY_SPI]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_SPI]; + + print_nl(); + print_hex(PRINT_ANY, "spi", " spi 0x%x", + rta_getattr_be32(attr)); + } + + flower_print_ip4_addr("arp_sip", tb[TCA_FLOWER_KEY_ARP_SIP], + tb[TCA_FLOWER_KEY_ARP_SIP_MASK]); + flower_print_ip4_addr("arp_tip", tb[TCA_FLOWER_KEY_ARP_TIP], + tb[TCA_FLOWER_KEY_ARP_TIP_MASK]); + flower_print_arp_op("arp_op", tb[TCA_FLOWER_KEY_ARP_OP], + tb[TCA_FLOWER_KEY_ARP_OP_MASK]); + flower_print_eth_addr("arp_sha", tb[TCA_FLOWER_KEY_ARP_SHA], + tb[TCA_FLOWER_KEY_ARP_SHA_MASK]); + flower_print_eth_addr("arp_tha", tb[TCA_FLOWER_KEY_ARP_THA], + tb[TCA_FLOWER_KEY_ARP_THA_MASK]); + + if (tb[TCA_FLOWER_KEY_PPPOE_SID]) { + struct rtattr *attr = tb[TCA_FLOWER_KEY_PPPOE_SID]; + + print_nl(); + print_uint(PRINT_ANY, "pppoe_sid", " pppoe_sid %u", + rta_getattr_be16(attr)); + } + + if (tb[TCA_FLOWER_KEY_PPP_PROTO]) { + SPRINT_BUF(buf); + struct rtattr *attr = tb[TCA_FLOWER_KEY_PPP_PROTO]; + + print_nl(); + print_string(PRINT_ANY, "ppp_proto", " ppp_proto %s", + ppp_proto_n2a(rta_getattr_u16(attr), + buf, sizeof(buf))); + } + + flower_print_ip_addr("enc_dst_ip", + tb[TCA_FLOWER_KEY_ENC_IPV4_DST_MASK] ? + htons(ETH_P_IP) : htons(ETH_P_IPV6), + tb[TCA_FLOWER_KEY_ENC_IPV4_DST], + tb[TCA_FLOWER_KEY_ENC_IPV4_DST_MASK], + tb[TCA_FLOWER_KEY_ENC_IPV6_DST], + tb[TCA_FLOWER_KEY_ENC_IPV6_DST_MASK]); + + flower_print_ip_addr("enc_src_ip", + tb[TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK] ? + htons(ETH_P_IP) : htons(ETH_P_IPV6), + tb[TCA_FLOWER_KEY_ENC_IPV4_SRC], + tb[TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK], + tb[TCA_FLOWER_KEY_ENC_IPV6_SRC], + tb[TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK]); + + flower_print_key_id("enc_key_id", tb[TCA_FLOWER_KEY_ENC_KEY_ID]); + + flower_print_port("enc_dst_port", tb[TCA_FLOWER_KEY_ENC_UDP_DST_PORT], + tb[TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK]); + + flower_print_ip_attr("enc_tos", tb[TCA_FLOWER_KEY_ENC_IP_TOS], + tb[TCA_FLOWER_KEY_ENC_IP_TOS_MASK]); + flower_print_ip_attr("enc_ttl", tb[TCA_FLOWER_KEY_ENC_IP_TTL], + tb[TCA_FLOWER_KEY_ENC_IP_TTL_MASK]); + flower_print_enc_opts("enc_opt", tb[TCA_FLOWER_KEY_ENC_OPTS], + tb[TCA_FLOWER_KEY_ENC_OPTS_MASK]); + + flower_print_matching_flags("ip_flags", FLOWER_IP_FLAGS, + tb[TCA_FLOWER_KEY_FLAGS], + tb[TCA_FLOWER_KEY_FLAGS_MASK]); + + if (tb[TCA_FLOWER_L2_MISS]) { + struct rtattr *attr = tb[TCA_FLOWER_L2_MISS]; + + print_nl(); + print_uint(PRINT_ANY, "l2_miss", " l2_miss %u", + rta_getattr_u8(attr)); + } + + flower_print_ct_state(tb[TCA_FLOWER_KEY_CT_STATE], + tb[TCA_FLOWER_KEY_CT_STATE_MASK]); + flower_print_ct_zone(tb[TCA_FLOWER_KEY_CT_ZONE], + tb[TCA_FLOWER_KEY_CT_ZONE_MASK]); + flower_print_ct_mark(tb[TCA_FLOWER_KEY_CT_MARK], + tb[TCA_FLOWER_KEY_CT_MARK_MASK]); + flower_print_ct_label(tb[TCA_FLOWER_KEY_CT_LABELS], + tb[TCA_FLOWER_KEY_CT_LABELS_MASK]); + + flower_print_cfm(tb[TCA_FLOWER_KEY_CFM]); + + close_json_object(); + + if (tb[TCA_FLOWER_FLAGS]) { + __u32 flags = rta_getattr_u32(tb[TCA_FLOWER_FLAGS]); + + if (flags & TCA_CLS_FLAGS_SKIP_HW) { + print_nl(); + print_bool(PRINT_ANY, "skip_hw", " skip_hw", true); + } + if (flags & TCA_CLS_FLAGS_SKIP_SW) { + print_nl(); + print_bool(PRINT_ANY, "skip_sw", " skip_sw", true); + } + if (flags & TCA_CLS_FLAGS_IN_HW) { + print_nl(); + print_bool(PRINT_ANY, "in_hw", " in_hw", true); + + if (tb[TCA_FLOWER_IN_HW_COUNT]) { + __u32 count = rta_getattr_u32(tb[TCA_FLOWER_IN_HW_COUNT]); + + print_uint(PRINT_ANY, "in_hw_count", + " in_hw_count %u", count); + } + } else if (flags & TCA_CLS_FLAGS_NOT_IN_HW) { + print_nl(); + print_bool(PRINT_ANY, "not_in_hw", " not_in_hw", true); + } + } + + if (tb[TCA_FLOWER_ACT]) + tc_print_action(f, tb[TCA_FLOWER_ACT], 0); + + return 0; +} + +struct filter_util flower_filter_util = { + .id = "flower", + .parse_fopt = flower_parse_opt, + .print_fopt = flower_print_opt, +}; diff --git a/tc/f_fw.c b/tc/f_fw.c new file mode 100644 index 0000000..5e72e52 --- /dev/null +++ b/tc/f_fw.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_fw.c FW filter. + * + * 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 <linux/if.h> /* IFNAMSIZ */ +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... fw [ classid CLASSID ] [ indev DEV ] [ action ACTION_SPEC ]\n" + " CLASSID := Push matching packets to the class identified by CLASSID with format X:Y\n" + " CLASSID is parsed as hexadecimal input.\n" + " DEV := specify device for incoming device classification.\n" + " ACTION_SPEC := Apply an action on matching packets.\n" + " NOTE: handle is represented as HANDLE[/FWMASK].\n" + " FWMASK is 0xffffffff by default.\n"); +} + +static int fw_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + __u32 mask = 0; + int mask_set = 0; + + if (handle) { + char *slash; + + if ((slash = strchr(handle, '/')) != NULL) + *slash = '\0'; + if (get_u32(&t->tcm_handle, handle, 0)) { + fprintf(stderr, "Illegal \"handle\"\n"); + return -1; + } + if (slash) { + if (get_u32(&mask, slash+1, 0)) { + fprintf(stderr, "Illegal \"handle\" mask\n"); + return -1; + } + mask_set = 1; + } + } + + if (argc == 0) + return 0; + + tail = addattr_nest(n, 4096, TCA_OPTIONS); + + if (mask_set) + addattr32(n, MAX_MSG, TCA_FW_MASK, mask); + + while (argc > 0) { + if (matches(*argv, "classid") == 0 || + matches(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + if (get_tc_classid(&classid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, 4096, TCA_FW_CLASSID, &classid, 4); + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_FW_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_FW_ACT, n)) { + fprintf(stderr, "Illegal fw \"action\"\n"); + return -1; + } + continue; + } else if (strcmp(*argv, "indev") == 0) { + char d[IFNAMSIZ+1] = {}; + + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "Illegal indev\n"); + return -1; + } + strncpy(d, *argv, sizeof(d) - 1); + addattr_l(n, MAX_MSG, TCA_FW_INDEV, d, strlen(d) + 1); + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + addattr_nest_end(n, tail); + return 0; +} + +static int fw_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_FW_MAX+1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_FW_MAX, opt); + + if (handle || tb[TCA_FW_MASK]) { + __u32 mark = 0, mask = 0; + + open_json_object("fw"); + if (handle) + mark = handle; + if (tb[TCA_FW_MASK] && + (mask = rta_getattr_u32(tb[TCA_FW_MASK])) != 0xFFFFFFFF) { + print_0xhex(PRINT_ANY, "mark", "handle 0x%x", mark); + print_0xhex(PRINT_ANY, "mask", "/0x%x ", mask); + } else { + print_0xhex(PRINT_ANY, "mark", "handle 0x%x ", mark); + print_0xhex(PRINT_JSON, "mask", NULL, 0xFFFFFFFF); + } + close_json_object(); + } + + if (tb[TCA_FW_CLASSID]) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "classid", "classid %s ", + sprint_tc_classid( + rta_getattr_u32(tb[TCA_FW_CLASSID]), b1)); + } + + if (tb[TCA_FW_POLICE]) + tc_print_police(f, tb[TCA_FW_POLICE]); + if (tb[TCA_FW_INDEV]) { + struct rtattr *idev = tb[TCA_FW_INDEV]; + + print_string(PRINT_ANY, "indev", "input dev %s ", + rta_getattr_str(idev)); + } + + if (tb[TCA_FW_ACT]) { + print_nl(); + tc_print_action(f, tb[TCA_FW_ACT], 0); + } + return 0; +} + +struct filter_util fw_filter_util = { + .id = "fw", + .parse_fopt = fw_parse_opt, + .print_fopt = fw_print_opt, +}; diff --git a/tc/f_matchall.c b/tc/f_matchall.c new file mode 100644 index 0000000..38b68d7 --- /dev/null +++ b/tc/f_matchall.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_matchall.c Match-all Classifier + * + * Authors: Jiri Pirko <jiri@mellanox.com>, Yotam Gigi <yotamg@mellanox.com> + */ + +#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 <linux/if.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... matchall [skip_sw | skip_hw]\n" + " [ action ACTION_SPEC ] [ classid CLASSID ]\n" + "\n" + "Where: SELECTOR := SAMPLE SAMPLE ...\n" + " FILTERID := X:Y:Z\n" + " ACTION_SPEC := ... look at individual actions\n" + "\n" + "NOTE: CLASSID is parsed as hexadecimal input.\n"); +} + +static int matchall_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + __u32 flags = 0; + long h = 0; + + if (handle) { + h = strtol(handle, NULL, 0); + if (h == LONG_MIN || h == LONG_MAX) { + fprintf(stderr, "Illegal handle \"%s\", must be numeric.\n", + handle); + return -1; + } + } + t->tcm_handle = h; + + if (argc == 0) + return 0; + + tail = (struct rtattr *)(((void *)n)+NLMSG_ALIGN(n->nlmsg_len)); + addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0); + + while (argc > 0) { + if (matches(*argv, "classid") == 0 || + strcmp(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + if (get_tc_classid(&classid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_MATCHALL_CLASSID, &classid, 4); + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_MATCHALL_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + + } else if (strcmp(*argv, "skip_hw") == 0) { + NEXT_ARG(); + flags |= TCA_CLS_FLAGS_SKIP_HW; + continue; + } else if (strcmp(*argv, "skip_sw") == 0) { + NEXT_ARG(); + flags |= TCA_CLS_FLAGS_SKIP_SW; + continue; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (flags) { + if (!(flags ^ (TCA_CLS_FLAGS_SKIP_HW | + TCA_CLS_FLAGS_SKIP_SW))) { + fprintf(stderr, + "skip_hw and skip_sw are mutually exclusive\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_MATCHALL_FLAGS, &flags, 4); + } + + tail->rta_len = (((void *)n)+n->nlmsg_len) - (void *)tail; + return 0; +} + +static int matchall_print_opt(struct filter_util *qu, FILE *f, + struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_MATCHALL_MAX+1]; + struct tc_matchall_pcnt *pf = NULL; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_MATCHALL_MAX, opt); + + if (handle) + print_uint(PRINT_ANY, "handle", "handle 0x%x ", handle); + + if (tb[TCA_MATCHALL_CLASSID]) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "flowid", "flowid %s ", + sprint_tc_classid(rta_getattr_u32(tb[TCA_MATCHALL_CLASSID]), b1)); + } + + if (tb[TCA_MATCHALL_FLAGS]) { + __u32 flags = rta_getattr_u32(tb[TCA_MATCHALL_FLAGS]); + + if (flags & TCA_CLS_FLAGS_SKIP_HW) + print_bool(PRINT_ANY, "skip_hw", "\n skip_hw", true); + if (flags & TCA_CLS_FLAGS_SKIP_SW) + print_bool(PRINT_ANY, "skip_sw", "\n skip_sw", true); + + if (flags & TCA_CLS_FLAGS_IN_HW) + print_bool(PRINT_ANY, "in_hw", "\n in_hw", true); + else if (flags & TCA_CLS_FLAGS_NOT_IN_HW) + print_bool(PRINT_ANY, "not_in_hw", "\n not_in_hw", true); + } + + if (tb[TCA_MATCHALL_PCNT]) { + if (RTA_PAYLOAD(tb[TCA_MATCHALL_PCNT]) < sizeof(*pf)) { + print_string(PRINT_FP, NULL, "Broken perf counters\n", NULL); + return -1; + } + pf = RTA_DATA(tb[TCA_MATCHALL_PCNT]); + } + + if (show_stats && NULL != pf) + print_u64(PRINT_ANY, "rule_hit", " (rule hit %llu)", + (unsigned long long) pf->rhit); + + + if (tb[TCA_MATCHALL_ACT]) + tc_print_action(f, tb[TCA_MATCHALL_ACT], 0); + + return 0; +} + +struct filter_util matchall_filter_util = { + .id = "matchall", + .parse_fopt = matchall_parse_opt, + .print_fopt = matchall_print_opt, +}; diff --git a/tc/f_route.c b/tc/f_route.c new file mode 100644 index 0000000..ca8a8dd --- /dev/null +++ b/tc/f_route.c @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * f_route.c ROUTE filter. + * + * 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 "utils.h" +#include "rt_names.h" +#include "tc_common.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... route [ from REALM | fromif TAG ] [ to REALM ]\n" + " [ classid CLASSID ] [ action ACTION_SPEC ]\n" + " ACTION_SPEC := ... look at individual actions\n" + " CLASSID := X:Y\n" + "\n" + "NOTE: CLASSID is parsed as hexadecimal input.\n"); +} + +static int route_parse_opt(struct filter_util *qu, char *handle, int argc, char **argv, struct nlmsghdr *n) +{ + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + __u32 fh = 0xFFFF8000; + __u32 order = 0; + + if (handle) { + if (get_u32(&t->tcm_handle, handle, 0)) { + fprintf(stderr, "Illegal \"handle\"\n"); + return -1; + } + } + + if (argc == 0) + return 0; + + tail = addattr_nest(n, 4096, TCA_OPTIONS); + + while (argc > 0) { + if (matches(*argv, "to") == 0) { + __u32 id; + + NEXT_ARG(); + if (rtnl_rtrealm_a2n(&id, *argv)) { + fprintf(stderr, "Illegal \"to\"\n"); + return -1; + } + addattr_l(n, 4096, TCA_ROUTE4_TO, &id, 4); + fh &= ~0x80FF; + fh |= id&0xFF; + } else if (matches(*argv, "from") == 0) { + __u32 id; + + NEXT_ARG(); + if (rtnl_rtrealm_a2n(&id, *argv)) { + fprintf(stderr, "Illegal \"from\"\n"); + return -1; + } + addattr_l(n, 4096, TCA_ROUTE4_FROM, &id, 4); + fh &= 0xFFFF; + fh |= id<<16; + } else if (matches(*argv, "fromif") == 0) { + __u32 id; + + NEXT_ARG(); + ll_init_map(&rth); + if ((id = ll_name_to_index(*argv)) <= 0) { + fprintf(stderr, "Illegal \"fromif\"\n"); + return -1; + } + addattr_l(n, 4096, TCA_ROUTE4_IIF, &id, 4); + fh &= 0xFFFF; + fh |= (0x8000|id)<<16; + } else if (matches(*argv, "classid") == 0 || + strcmp(*argv, "flowid") == 0) { + unsigned int classid; + + NEXT_ARG(); + if (get_tc_classid(&classid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, 4096, TCA_ROUTE4_CLASSID, &classid, 4); + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_ROUTE4_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_ROUTE4_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "order") == 0) { + NEXT_ARG(); + if (get_u32(&order, *argv, 0)) { + fprintf(stderr, "Illegal \"order\"\n"); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + addattr_nest_end(n, tail); + if (order) { + fh &= ~0x7F00; + fh |= (order<<8)&0x7F00; + } + if (!t->tcm_handle) + t->tcm_handle = fh; + return 0; +} + +static int route_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 handle) +{ + struct rtattr *tb[TCA_ROUTE4_MAX+1]; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_ROUTE4_MAX, opt); + + if (handle) + print_0xhex(PRINT_ANY, "fh", "fh 0x%08x ", handle); + if (handle&0x7F00) + print_uint(PRINT_ANY, "order", "order %d ", (handle >> 8) & 0x7F); + + if (tb[TCA_ROUTE4_CLASSID]) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "flowid", "flowid %s ", + sprint_tc_classid(rta_getattr_u32(tb[TCA_ROUTE4_CLASSID]), b1)); + } + if (tb[TCA_ROUTE4_TO]) + print_string(PRINT_ANY, "name", "to %s ", + rtnl_rtrealm_n2a(rta_getattr_u32(tb[TCA_ROUTE4_TO]), b1, sizeof(b1))); + if (tb[TCA_ROUTE4_FROM]) + print_string(PRINT_ANY, "name", "from %s ", + rtnl_rtrealm_n2a(rta_getattr_u32(tb[TCA_ROUTE4_FROM]), b1, sizeof(b1))); + if (tb[TCA_ROUTE4_IIF]) + print_color_string(PRINT_ANY, COLOR_IFNAME, "fromif", "fromif %s", + ll_index_to_name(rta_getattr_u32(tb[TCA_ROUTE4_IIF]))); + if (tb[TCA_ROUTE4_POLICE]) + tc_print_police(f, tb[TCA_ROUTE4_POLICE]); + if (tb[TCA_ROUTE4_ACT]) + tc_print_action(f, tb[TCA_ROUTE4_ACT], 0); + return 0; +} + +struct filter_util route_filter_util = { + .id = "route", + .parse_fopt = route_parse_opt, + .print_fopt = route_print_opt, +}; diff --git a/tc/f_u32.c b/tc/f_u32.c new file mode 100644 index 0000000..59aa4e3 --- /dev/null +++ b/tc/f_u32.c @@ -0,0 +1,1389 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_u32.c U32 filter. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * Match mark added by Catalin(ux aka Dino) BOIE <catab at umbrella.ro> [5 nov 2004] + * + */ + +#include <assert.h> +#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 <linux/if.h> +#include <linux/if_ether.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... u32 [ match SELECTOR ... ] [ link HTID ] [ classid CLASSID ]\n" + " [ action ACTION_SPEC ] [ offset OFFSET_SPEC ]\n" + " [ ht HTID ] [ hashkey HASHKEY_SPEC ]\n" + " [ sample SAMPLE ] [skip_hw | skip_sw]\n" + "or u32 divisor DIVISOR\n" + "\n" + "Where: SELECTOR := SAMPLE SAMPLE ...\n" + " SAMPLE := { ip | ip6 | udp | tcp | icmp | u{32|16|8} | mark }\n" + " SAMPLE_ARGS [ divisor DIVISOR ]\n" + " FILTERID := X:Y:Z\n" + "\nNOTE: CLASSID is parsed at hexadecimal input.\n"); +} + +static int get_u32_handle(__u32 *handle, const char *str) +{ + __u32 htid = 0, hash = 0, nodeid = 0; + char *tmp = strchr(str, ':'); + + if (tmp == NULL) { + if (memcmp("0x", str, 2) == 0) + return get_u32(handle, str, 16); + return -1; + } + htid = strtoul(str, &tmp, 16); + if (tmp == str && *str != ':' && *str != 0) + return -1; + if (htid >= 0x1000) + return -1; + if (*tmp) { + str = tmp + 1; + hash = strtoul(str, &tmp, 16); + if (tmp == str && *str != ':' && *str != 0) + return -1; + if (hash >= 0x100) + return -1; + if (*tmp) { + str = tmp + 1; + nodeid = strtoul(str, &tmp, 16); + if (tmp == str && *str != 0) + return -1; + if (nodeid >= 0x1000) + return -1; + } + } + *handle = (htid<<20)|(hash<<12)|nodeid; + return 0; +} + +static char *sprint_u32_handle(__u32 handle, char *buf) +{ + int bsize = SPRINT_BSIZE-1; + __u32 htid = TC_U32_HTID(handle); + __u32 hash = TC_U32_HASH(handle); + __u32 nodeid = TC_U32_NODE(handle); + char *b = buf; + + if (handle == 0) { + snprintf(b, bsize, "none"); + return b; + } + if (htid) { + int l = snprintf(b, bsize, "%x:", htid>>20); + + assert(l > 0 && l < bsize); + bsize -= l; + b += l; + } + if (nodeid|hash) { + if (hash) { + int l = snprintf(b, bsize, "%x", hash); + + assert(l > 0 && l < bsize); + bsize -= l; + b += l; + } + if (nodeid) { + int l = snprintf(b, bsize, ":%x", nodeid); + + assert(l > 0 && l < bsize); + bsize -= l; + b += l; + } + } + if (show_raw) + snprintf(b, bsize, "[%08x]", handle); + return buf; +} + +static int pack_key(struct tc_u32_sel *sel, __u32 key, __u32 mask, + int off, int offmask) +{ + int i; + int hwm = sel->nkeys; + + key &= mask; + + for (i = 0; i < hwm; i++) { + if (sel->keys[i].off == off && sel->keys[i].offmask == offmask) { + __u32 intersect = mask & sel->keys[i].mask; + + if ((key ^ sel->keys[i].val) & intersect) + return -1; + sel->keys[i].val |= key; + sel->keys[i].mask |= mask; + return 0; + } + } + + if (hwm >= 128) + return -1; + if (off % 4) + return -1; + sel->keys[hwm].val = key; + sel->keys[hwm].mask = mask; + sel->keys[hwm].off = off; + sel->keys[hwm].offmask = offmask; + sel->nkeys++; + return 0; +} + +static int pack_key32(struct tc_u32_sel *sel, __u32 key, __u32 mask, + int off, int offmask) +{ + key = htonl(key); + mask = htonl(mask); + return pack_key(sel, key, mask, off, offmask); +} + +static int pack_key16(struct tc_u32_sel *sel, __u32 key, __u32 mask, + int off, int offmask) +{ + if (key > 0xFFFF || mask > 0xFFFF) + return -1; + + if ((off & 3) == 0) { + key <<= 16; + mask <<= 16; + } + off &= ~3; + key = htonl(key); + mask = htonl(mask); + + return pack_key(sel, key, mask, off, offmask); +} + +static int pack_key8(struct tc_u32_sel *sel, __u32 key, __u32 mask, int off, + int offmask) +{ + if (key > 0xFF || mask > 0xFF) + return -1; + + if ((off & 3) == 0) { + key <<= 24; + mask <<= 24; + } else if ((off & 3) == 1) { + key <<= 16; + mask <<= 16; + } else if ((off & 3) == 2) { + key <<= 8; + mask <<= 8; + } + off &= ~3; + key = htonl(key); + mask = htonl(mask); + + return pack_key(sel, key, mask, off, offmask); +} + + +static int parse_at(int *argc_p, char ***argv_p, int *off, int *offmask) +{ + int argc = *argc_p; + char **argv = *argv_p; + char *p = *argv; + + if (argc <= 0) + return -1; + + if (strlen(p) > strlen("nexthdr+") && + memcmp(p, "nexthdr+", strlen("nexthdr+")) == 0) { + *offmask = -1; + p += strlen("nexthdr+"); + } else if (matches(*argv, "nexthdr+") == 0) { + NEXT_ARG(); + *offmask = -1; + p = *argv; + } + + if (get_integer(off, p, 0)) + return -1; + argc--; argv++; + + *argc_p = argc; + *argv_p = argv; + return 0; +} + + +static int parse_u32(int *argc_p, char ***argv_p, struct tc_u32_sel *sel, + int off, int offmask) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + __u32 key; + __u32 mask; + + if (argc < 2) + return -1; + + if (get_u32(&key, *argv, 0)) + return -1; + argc--; argv++; + + if (get_u32(&mask, *argv, 16)) + return -1; + argc--; argv++; + + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + + res = pack_key32(sel, key, mask, off, offmask); + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_u16(int *argc_p, char ***argv_p, struct tc_u32_sel *sel, + int off, int offmask) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + __u32 key; + __u32 mask; + + if (argc < 2) + return -1; + + if (get_u32(&key, *argv, 0)) + return -1; + argc--; argv++; + + if (get_u32(&mask, *argv, 16)) + return -1; + argc--; argv++; + + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + res = pack_key16(sel, key, mask, off, offmask); + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_u8(int *argc_p, char ***argv_p, struct tc_u32_sel *sel, + int off, int offmask) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + __u32 key; + __u32 mask; + + if (argc < 2) + return -1; + + if (get_u32(&key, *argv, 0)) + return -1; + argc--; argv++; + + if (get_u32(&mask, *argv, 16)) + return -1; + argc--; argv++; + + if (key > 0xFF || mask > 0xFF) + return -1; + + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + + res = pack_key8(sel, key, mask, off, offmask); + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ip_addr(int *argc_p, char ***argv_p, struct tc_u32_sel *sel, + int off) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + inet_prefix addr; + __u32 mask; + int offmask = 0; + + if (argc < 1) + return -1; + + if (get_prefix_1(&addr, *argv, AF_INET)) + return -1; + argc--; argv++; + + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + + mask = 0; + if (addr.bitlen) + mask = htonl(0xFFFFFFFF << (32 - addr.bitlen)); + if (pack_key(sel, addr.data[0], mask, off, offmask) < 0) + return -1; + res = 0; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ip6_addr(int *argc_p, char ***argv_p, + struct tc_u32_sel *sel, int off) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + int plen = 128; + int i; + inet_prefix addr; + int offmask = 0; + + if (argc < 1) + return -1; + + if (get_prefix_1(&addr, *argv, AF_INET6)) + return -1; + argc--; argv++; + + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + + plen = addr.bitlen; + for (i = 0; i < plen; i += 32) { + if (i + 31 < plen) { + res = pack_key(sel, addr.data[i / 32], + 0xFFFFFFFF, off + 4 * (i / 32), offmask); + if (res < 0) + return -1; + } else if (i < plen) { + __u32 mask = htonl(0xFFFFFFFF << (32 - (plen - i))); + + res = pack_key(sel, addr.data[i / 32], + mask, off + 4 * (i / 32), offmask); + if (res < 0) + return -1; + } + } + res = 0; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ip6_class(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + __u32 key; + __u32 mask; + int off = 0; + int offmask = 0; + + if (argc < 2) + return -1; + + if (get_u32(&key, *argv, 0)) + return -1; + argc--; argv++; + + if (get_u32(&mask, *argv, 16)) + return -1; + argc--; argv++; + + if (key > 0xFF || mask > 0xFF) + return -1; + + key <<= 20; + mask <<= 20; + key = htonl(key); + mask = htonl(mask); + + res = pack_key(sel, key, mask, off, offmask); + if (res < 0) + return -1; + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int parse_ether_addr(int *argc_p, char ***argv_p, + struct tc_u32_sel *sel, int off) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + __u8 addr[6]; + int offmask = 0; + int i; + + if (argc < 1) + return -1; + + if (sscanf(*argv, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + addr + 0, addr + 1, addr + 2, + addr + 3, addr + 4, addr + 5) != 6) { + fprintf(stderr, "parse_ether_addr: improperly formed address '%s'\n", + *argv); + return -1; + } + + argc--; argv++; + if (argc > 0 && strcmp(argv[0], "at") == 0) { + NEXT_ARG(); + if (parse_at(&argc, &argv, &off, &offmask)) + return -1; + } + + for (i = 0; i < 6; i++) { + res = pack_key8(sel, addr[i], 0xFF, off + i, offmask); + if (res < 0) + return -1; + } + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ip(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + res = parse_ip_addr(&argc, &argv, sel, 12); + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + res = parse_ip_addr(&argc, &argv, sel, 16); + } else if (strcmp(*argv, "tos") == 0 || + matches(*argv, "dsfield") == 0 || + matches(*argv, "precedence") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 1, 0); + } else if (strcmp(*argv, "ihl") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 0, 0); + } else if (strcmp(*argv, "protocol") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 9, 0); + } else if (strcmp(*argv, "nofrag") == 0) { + argc--; argv++; + res = pack_key16(sel, 0, 0x3FFF, 6, 0); + } else if (strcmp(*argv, "firstfrag") == 0) { + argc--; argv++; + res = pack_key16(sel, 0x2000, 0x3FFF, 6, 0); + } else if (strcmp(*argv, "df") == 0) { + argc--; argv++; + res = pack_key16(sel, 0x4000, 0x4000, 6, 0); + } else if (strcmp(*argv, "mf") == 0) { + argc--; argv++; + res = pack_key16(sel, 0x2000, 0x2000, 6, 0); + } else if (strcmp(*argv, "dport") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 22, 0); + } else if (strcmp(*argv, "sport") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 20, 0); + } else if (strcmp(*argv, "icmp_type") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 20, 0); + } else if (strcmp(*argv, "icmp_code") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 21, 0); + } else + return -1; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ip6(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + res = parse_ip6_addr(&argc, &argv, sel, 8); + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + res = parse_ip6_addr(&argc, &argv, sel, 24); + } else if (strcmp(*argv, "priority") == 0) { + NEXT_ARG(); + res = parse_ip6_class(&argc, &argv, sel); + } else if (strcmp(*argv, "protocol") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 6, 0); + } else if (strcmp(*argv, "flowlabel") == 0) { + NEXT_ARG(); + res = parse_u32(&argc, &argv, sel, 0, 0); + } else if (strcmp(*argv, "dport") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 42, 0); + } else if (strcmp(*argv, "sport") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 40, 0); + } else if (strcmp(*argv, "icmp_type") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 40, 0); + } else if (strcmp(*argv, "icmp_code") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 41, 1); + } else + return -1; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_ether(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + res = parse_ether_addr(&argc, &argv, sel, -8); + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + res = parse_ether_addr(&argc, &argv, sel, -14); + } else { + fprintf(stderr, "Unknown match: ether %s\n", *argv); + return -1; + } + + *argc_p = argc; + *argv_p = argv; + return res; +} + +#define parse_tcp parse_udp +static int parse_udp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 0, -1); + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 2, -1); + } else + return -1; + + *argc_p = argc; + *argv_p = argv; + return res; +} + + +static int parse_icmp(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (strcmp(*argv, "type") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 0, -1); + } else if (strcmp(*argv, "code") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 1, -1); + } else + return -1; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_mark(int *argc_p, char ***argv_p, struct nlmsghdr *n) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + struct tc_u32_mark mark; + + if (argc <= 1) + return -1; + + if (get_u32(&mark.val, *argv, 0)) { + fprintf(stderr, "Illegal \"mark\" value\n"); + return -1; + } + NEXT_ARG(); + + if (get_u32(&mark.mask, *argv, 0)) { + fprintf(stderr, "Illegal \"mark\" mask\n"); + return -1; + } + NEXT_ARG(); + + if ((mark.val & mark.mask) != mark.val) { + fprintf(stderr, "Illegal \"mark\" (impossible combination)\n"); + return -1; + } + + addattr_l(n, MAX_MSG, TCA_U32_MARK, &mark, sizeof(mark)); + res = 0; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_selector(int *argc_p, char ***argv_p, + struct tc_u32_sel *sel, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + int res = -1; + + if (argc <= 0) + return -1; + + if (matches(*argv, "u32") == 0) { + NEXT_ARG(); + res = parse_u32(&argc, &argv, sel, 0, 0); + } else if (matches(*argv, "u16") == 0) { + NEXT_ARG(); + res = parse_u16(&argc, &argv, sel, 0, 0); + } else if (matches(*argv, "u8") == 0) { + NEXT_ARG(); + res = parse_u8(&argc, &argv, sel, 0, 0); + } else if (matches(*argv, "ip") == 0) { + NEXT_ARG(); + res = parse_ip(&argc, &argv, sel); + } else if (matches(*argv, "ip6") == 0) { + NEXT_ARG(); + res = parse_ip6(&argc, &argv, sel); + } else if (matches(*argv, "udp") == 0) { + NEXT_ARG(); + res = parse_udp(&argc, &argv, sel); + } else if (matches(*argv, "tcp") == 0) { + NEXT_ARG(); + res = parse_tcp(&argc, &argv, sel); + } else if (matches(*argv, "icmp") == 0) { + NEXT_ARG(); + res = parse_icmp(&argc, &argv, sel); + } else if (matches(*argv, "mark") == 0) { + NEXT_ARG(); + res = parse_mark(&argc, &argv, n); + } else if (matches(*argv, "ether") == 0) { + NEXT_ARG(); + res = parse_ether(&argc, &argv, sel); + } else + return -1; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_offset(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int argc = *argc_p; + char **argv = *argv_p; + + while (argc > 0) { + if (matches(*argv, "plus") == 0) { + int off; + + NEXT_ARG(); + if (get_integer(&off, *argv, 0)) + return -1; + sel->off = off; + sel->flags |= TC_U32_OFFSET; + } else if (matches(*argv, "at") == 0) { + int off; + + NEXT_ARG(); + if (get_integer(&off, *argv, 0)) + return -1; + sel->offoff = off; + if (off%2) { + fprintf(stderr, "offset \"at\" must be even\n"); + return -1; + } + sel->flags |= TC_U32_VAROFFSET; + } else if (matches(*argv, "mask") == 0) { + NEXT_ARG(); + if (get_be16(&sel->offmask, *argv, 16)) + return -1; + sel->flags |= TC_U32_VAROFFSET; + } else if (matches(*argv, "shift") == 0) { + int shift; + + NEXT_ARG(); + if (get_integer(&shift, *argv, 0)) + return -1; + sel->offshift = shift; + sel->flags |= TC_U32_VAROFFSET; + } else if (matches(*argv, "eat") == 0) { + sel->flags |= TC_U32_EAT; + } else { + break; + } + argc--; argv++; + } + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int parse_hashkey(int *argc_p, char ***argv_p, struct tc_u32_sel *sel) +{ + int argc = *argc_p; + char **argv = *argv_p; + + while (argc > 0) { + if (matches(*argv, "mask") == 0) { + NEXT_ARG(); + if (get_be32(&sel->hmask, *argv, 16)) + return -1; + } else if (matches(*argv, "at") == 0) { + int num; + + NEXT_ARG(); + if (get_integer(&num, *argv, 0)) + return -1; + if (num%4) + return -1; + sel->hoff = num; + } else { + break; + } + argc--; argv++; + } + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static void print_ipv4(FILE *f, const struct tc_u32_key *key) +{ + char abuf[256]; + + open_json_object("match"); + switch (key->off) { + case 0: + switch (ntohl(key->mask)) { + case 0x0f000000: + print_nl(); + print_uint(PRINT_ANY, "ip_ihl", " match IP ihl %u", + ntohl(key->val) >> 24); + break; + case 0x00ff0000: + print_nl(); + print_0xhex(PRINT_ANY, "ip_dsfield", " match IP dsfield %#x", + ntohl(key->val) >> 16); + break; + } + break; + case 8: + if (ntohl(key->mask) == 0x00ff0000) { + print_nl(); + print_int(PRINT_ANY, "ip_protocol", " match IP protocol %d", + ntohl(key->val) >> 16); + } + break; + case 12: + case 16: { + int bits = mask2bits(key->mask); + + if (bits >= 0) { + const char *addr; + + if (key->off == 12) { + print_nl(); + print_null(PRINT_FP, NULL, " match IP src ", NULL); + open_json_object("src"); + } else { + print_nl(); + print_null(PRINT_FP, NULL, " match IP dst ", NULL); + open_json_object("dst"); + } + addr = inet_ntop(AF_INET, &key->val, abuf, sizeof(abuf)); + print_string(PRINT_ANY, "address", "%s", addr); + print_int(PRINT_ANY, "prefixlen", "/%d", bits); + close_json_object(); + } + } + break; + + case 20: + switch (ntohl(key->mask)) { + case 0x0000ffff: + print_uint(PRINT_ANY, "dport", "match dport %u", + ntohl(key->val) & 0xffff); + break; + case 0xffff0000: + print_nl(); + print_uint(PRINT_ANY, "sport", " match sport %u", + ntohl(key->val) >> 16); + break; + case 0xffffffff: + print_nl(); + print_uint(PRINT_ANY, "dport", " match dport %u, ", + ntohl(key->val) & 0xffff); + print_uint(PRINT_ANY, "sport", "match sport %u", + ntohl(key->val) >> 16); + break; + } + /* XXX: Default print_raw */ + } + close_json_object(); +} + +static void print_ipv6(FILE *f, const struct tc_u32_key *key) +{ + char abuf[256]; + + open_json_object("match"); + switch (key->off) { + case 0: + switch (ntohl(key->mask)) { + case 0x0f000000: + print_nl(); + print_uint(PRINT_ANY, "ip_ihl", " match IP ihl %u", + ntohl(key->val) >> 24); + break; + case 0x00ff0000: + print_nl(); + print_0xhex(PRINT_ANY, "ip_dsfield", " match IP dsfield %#x", + ntohl(key->val) >> 16); + break; + } + break; + case 8: + if (ntohl(key->mask) == 0x00ff0000) { + print_nl(); + print_int(PRINT_ANY, "ip_protocol", " match IP protocol %d", + ntohl(key->val) >> 16); + } + break; + case 12: + case 16: { + int bits = mask2bits(key->mask); + + if (bits >= 0) { + const char *addr; + + if (key->off == 12) { + print_nl(); + print_null(PRINT_FP, NULL, " match IP src ", NULL); + open_json_object("src"); + } else { + print_nl(); + print_null(PRINT_FP, NULL, " match IP dst ", NULL); + open_json_object("dst"); + } + addr = inet_ntop(AF_INET, &key->val, abuf, sizeof(abuf)); + print_string(PRINT_ANY, "address", "%s", addr); + print_int(PRINT_ANY, "prefixlen", "/%d", bits); + close_json_object(); + } + } + break; + + case 20: + switch (ntohl(key->mask)) { + case 0x0000ffff: + print_nl(); + print_uint(PRINT_ANY, "sport", " match sport %u", + ntohl(key->val) & 0xffff); + break; + case 0xffff0000: + print_uint(PRINT_ANY, "dport", "match dport %u", + ntohl(key->val) >> 16); + break; + case 0xffffffff: + print_nl(); + print_uint(PRINT_ANY, "sport", " match sport %u, ", + ntohl(key->val) & 0xffff); + print_uint(PRINT_ANY, "dport", "match dport %u", + ntohl(key->val) >> 16); + + break; + } + /* XXX: Default print_raw */ + } + close_json_object(); +} + +static void print_raw(FILE *f, const struct tc_u32_key *key) +{ + open_json_object("match"); + print_nl(); + print_hex(PRINT_ANY, "value", " match %08x", (unsigned int)ntohl(key->val)); + print_hex(PRINT_ANY, "mask", "/%08x ", (unsigned int)ntohl(key->mask)); + print_string(PRINT_ANY, "offmask", "at %s", key->offmask ? "nexthdr+" : ""); + print_int(PRINT_ANY, "off", "%d", key->off); + close_json_object(); +} + +static const struct { + __u16 proto; + __u16 pad; + void (*pprinter)(FILE *f, const struct tc_u32_key *key); +} u32_pprinters[] = { + {0, 0, print_raw}, + {ETH_P_IP, 0, print_ipv4}, + {ETH_P_IPV6, 0, print_ipv6}, +}; + +static void show_keys(FILE *f, const struct tc_u32_key *key) +{ + int i = 0; + + if (!pretty) + goto show_k; + + for (i = 0; i < ARRAY_SIZE(u32_pprinters); i++) { + if (u32_pprinters[i].proto == ntohs(f_proto)) { +show_k: + u32_pprinters[i].pprinter(f, key); + return; + } + } + + i = 0; + goto show_k; +} + +static __u32 u32_hash_fold(struct tc_u32_key *key) +{ + __u8 fshift = key->mask ? ffs(ntohl(key->mask)) - 1 : 0; + + return ntohl(key->val & key->mask) >> fshift; +} + +static int u32_parse_opt(struct filter_util *qu, char *handle, + int argc, char **argv, struct nlmsghdr *n) +{ + struct { + struct tc_u32_sel sel; + struct tc_u32_key keys[128]; + } sel = {}; + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tail; + int sel_ok = 0, terminal_ok = 0; + int sample_ok = 0; + __u32 htid = 0; + __u32 order = 0; + __u32 flags = 0; + + if (handle && get_u32_handle(&t->tcm_handle, handle)) { + fprintf(stderr, "Illegal filter ID\n"); + return -1; + } + + if (argc == 0) + return 0; + + tail = addattr_nest(n, MAX_MSG, TCA_OPTIONS); + + while (argc > 0) { + if (matches(*argv, "match") == 0) { + NEXT_ARG(); + if (parse_selector(&argc, &argv, &sel.sel, n)) { + fprintf(stderr, "Illegal \"match\"\n"); + return -1; + } + sel_ok++; + continue; + } else if (matches(*argv, "offset") == 0) { + NEXT_ARG(); + if (parse_offset(&argc, &argv, &sel.sel)) { + fprintf(stderr, "Illegal \"offset\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "hashkey") == 0) { + NEXT_ARG(); + if (parse_hashkey(&argc, &argv, &sel.sel)) { + fprintf(stderr, "Illegal \"hashkey\"\n"); + return -1; + } + continue; + } else if (matches(*argv, "classid") == 0 || + strcmp(*argv, "flowid") == 0) { + unsigned int flowid; + + NEXT_ARG(); + if (get_tc_classid(&flowid, *argv)) { + fprintf(stderr, "Illegal \"classid\"\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_U32_CLASSID, &flowid, 4); + sel.sel.flags |= TC_U32_TERMINAL; + } else if (matches(*argv, "divisor") == 0) { + unsigned int divisor; + + NEXT_ARG(); + if (get_unsigned(&divisor, *argv, 0) || + divisor == 0 || + divisor > 0x100 || ((divisor - 1) & divisor)) { + fprintf(stderr, "Illegal \"divisor\"\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_U32_DIVISOR, &divisor, 4); + } else if (matches(*argv, "order") == 0) { + NEXT_ARG(); + if (get_u32(&order, *argv, 0)) { + fprintf(stderr, "Illegal \"order\"\n"); + return -1; + } + } else if (strcmp(*argv, "link") == 0) { + unsigned int linkid; + + NEXT_ARG(); + if (get_u32_handle(&linkid, *argv)) { + fprintf(stderr, "Illegal \"link\"\n"); + return -1; + } + if (linkid && TC_U32_NODE(linkid)) { + fprintf(stderr, "\"link\" must be a hash table.\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_U32_LINK, &linkid, 4); + } else if (strcmp(*argv, "ht") == 0) { + unsigned int ht; + + NEXT_ARG(); + if (get_u32_handle(&ht, *argv)) { + fprintf(stderr, "Illegal \"ht\"\n"); + return -1; + } + if (handle && TC_U32_NODE(ht)) { + fprintf(stderr, "\"ht\" must be a hash table.\n"); + return -1; + } + if (sample_ok) + htid = (htid & 0xFF000) | (ht & 0xFFF00000); + else + htid = (ht & 0xFFFFF000); + } else if (strcmp(*argv, "sample") == 0) { + __u32 hash; + unsigned int divisor = 0x100; + struct { + struct tc_u32_sel sel; + struct tc_u32_key keys[4]; + } sel2 = {}; + + NEXT_ARG(); + if (parse_selector(&argc, &argv, &sel2.sel, n)) { + fprintf(stderr, "Illegal \"sample\"\n"); + return -1; + } + if (sel2.sel.nkeys != 1) { + fprintf(stderr, "\"sample\" must contain exactly ONE key.\n"); + return -1; + } + if (*argv != 0 && strcmp(*argv, "divisor") == 0) { + NEXT_ARG(); + if (get_unsigned(&divisor, *argv, 0) || + divisor == 0 || divisor > 0x100 || + ((divisor - 1) & divisor)) { + fprintf(stderr, "Illegal sample \"divisor\"\n"); + return -1; + } + NEXT_ARG(); + } + hash = u32_hash_fold(&sel2.keys[0]); + htid = ((hash % divisor) << 12) | (htid & 0xFFF00000); + sample_ok = 1; + continue; + } else if (strcmp(*argv, "indev") == 0) { + char ind[IFNAMSIZ + 1] = {}; + + argc--; + argv++; + if (argc < 1) { + fprintf(stderr, "Illegal indev\n"); + return -1; + } + strncpy(ind, *argv, sizeof(ind) - 1); + addattr_l(n, MAX_MSG, TCA_U32_INDEV, ind, + strlen(ind) + 1); + + } else if (matches(*argv, "action") == 0) { + NEXT_ARG(); + if (parse_action(&argc, &argv, TCA_U32_ACT, n)) { + fprintf(stderr, "Illegal \"action\"\n"); + return -1; + } + terminal_ok++; + continue; + + } else if (matches(*argv, "police") == 0) { + NEXT_ARG(); + if (parse_police(&argc, &argv, TCA_U32_POLICE, n)) { + fprintf(stderr, "Illegal \"police\"\n"); + return -1; + } + terminal_ok++; + continue; + } else if (strcmp(*argv, "skip_hw") == 0) { + flags |= TCA_CLS_FLAGS_SKIP_HW; + } else if (strcmp(*argv, "skip_sw") == 0) { + flags |= TCA_CLS_FLAGS_SKIP_SW; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + /* We don't necessarily need class/flowids */ + if (terminal_ok) + sel.sel.flags |= TC_U32_TERMINAL; + + if (order) { + if (TC_U32_NODE(t->tcm_handle) && + order != TC_U32_NODE(t->tcm_handle)) { + fprintf(stderr, "\"order\" contradicts \"handle\"\n"); + return -1; + } + t->tcm_handle |= order; + } + + if (htid) + addattr_l(n, MAX_MSG, TCA_U32_HASH, &htid, 4); + if (sel_ok) + addattr_l(n, MAX_MSG, TCA_U32_SEL, &sel, + sizeof(sel.sel) + + sel.sel.nkeys * sizeof(struct tc_u32_key)); + if (flags) { + if (!(flags ^ (TCA_CLS_FLAGS_SKIP_HW | + TCA_CLS_FLAGS_SKIP_SW))) { + fprintf(stderr, + "skip_hw and skip_sw are mutually exclusive\n"); + return -1; + } + addattr_l(n, MAX_MSG, TCA_U32_FLAGS, &flags, 4); + } + + addattr_nest_end(n, tail); + return 0; +} + +static int u32_print_opt(struct filter_util *qu, FILE *f, struct rtattr *opt, + __u32 handle) +{ + struct rtattr *tb[TCA_U32_MAX + 1]; + struct tc_u32_sel *sel = NULL; + struct tc_u32_pcnt *pf = NULL; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_U32_MAX, opt); + + if (handle) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "fh", "fh %s ", sprint_u32_handle(handle, b1)); + } + + if (TC_U32_NODE(handle)) + print_int(PRINT_ANY, "order", "order %d ", TC_U32_NODE(handle)); + + if (tb[TCA_U32_SEL]) { + if (RTA_PAYLOAD(tb[TCA_U32_SEL]) < sizeof(*sel)) + return -1; + + sel = RTA_DATA(tb[TCA_U32_SEL]); + } + + if (tb[TCA_U32_DIVISOR]) { + __u32 htdivisor = rta_getattr_u32(tb[TCA_U32_DIVISOR]); + + print_int(PRINT_ANY, "ht_divisor", "ht divisor %d ", htdivisor); + } else if (tb[TCA_U32_HASH]) { + __u32 htid = rta_getattr_u32(tb[TCA_U32_HASH]); + print_hex(PRINT_ANY, "key_ht", "key ht %x ", TC_U32_USERHTID(htid)); + print_hex(PRINT_ANY, "bkt", "bkt %x ", TC_U32_HASH(htid)); + } else { + fprintf(stderr, "divisor and hash missing "); + } + if (tb[TCA_U32_CLASSID]) { + __u32 classid = rta_getattr_u32(tb[TCA_U32_CLASSID]); + SPRINT_BUF(b1); + if (!sel || !(sel->flags & TC_U32_TERMINAL)) + print_string(PRINT_FP, NULL, "*", NULL); + + print_string(PRINT_ANY, "flowid", "flowid %s ", + sprint_tc_classid(classid, b1)); + } else if (sel && (sel->flags & TC_U32_TERMINAL)) { + print_string(PRINT_FP, NULL, "terminal flowid ", NULL); + } + if (tb[TCA_U32_LINK]) { + SPRINT_BUF(b1); + char *link = sprint_u32_handle(rta_getattr_u32(tb[TCA_U32_LINK]), b1); + + print_string(PRINT_ANY, "link", "link %s ", link); + } + + if (tb[TCA_U32_FLAGS]) { + __u32 flags = rta_getattr_u32(tb[TCA_U32_FLAGS]); + + if (flags & TCA_CLS_FLAGS_SKIP_HW) + print_bool(PRINT_ANY, "skip_hw", "skip_hw ", true); + if (flags & TCA_CLS_FLAGS_SKIP_SW) + print_bool(PRINT_ANY, "skip_sw", "skip_sw ", true); + + if (flags & TCA_CLS_FLAGS_IN_HW) + print_bool(PRINT_ANY, "in_hw", "in_hw ", true); + else if (flags & TCA_CLS_FLAGS_NOT_IN_HW) + print_bool(PRINT_ANY, "not_in_hw", "not_in_hw ", true); + } + + if (tb[TCA_U32_PCNT]) { + if (RTA_PAYLOAD(tb[TCA_U32_PCNT]) < sizeof(*pf)) { + fprintf(stderr, "Broken perf counters\n"); + return -1; + } + pf = RTA_DATA(tb[TCA_U32_PCNT]); + } + + if (sel && show_stats && NULL != pf) { + print_u64(PRINT_ANY, "rule_hit", "(rule hit %llu ", pf->rcnt); + print_u64(PRINT_ANY, "success", "success %llu)", pf->rhit); + } + + if (tb[TCA_U32_MARK]) { + struct tc_u32_mark *mark = RTA_DATA(tb[TCA_U32_MARK]); + + if (RTA_PAYLOAD(tb[TCA_U32_MARK]) < sizeof(*mark)) { + fprintf(stderr, "Invalid mark (kernel&iproute2 mismatch)\n"); + } else { + print_nl(); + print_0xhex(PRINT_ANY, "fwmark_value", " mark 0x%04x ", mark->val); + print_0xhex(PRINT_ANY, "fwmark_mask", "0x%04x ", mark->mask); + print_int(PRINT_ANY, "fwmark_success", "(success %d)", mark->success); + } + } + + if (sel) { + if (sel->nkeys) { + int i; + + for (i = 0; i < sel->nkeys; i++) { + show_keys(f, sel->keys + i); + if (show_stats && NULL != pf) + print_u64(PRINT_ANY, "success", " (success %llu ) ", + pf->kcnts[i]); + } + } + + if (sel->flags & (TC_U32_VAROFFSET | TC_U32_OFFSET)) { + print_nl(); + print_string(PRINT_FP, NULL, " offset ", NULL); + if (sel->flags & TC_U32_VAROFFSET) { + print_hex(PRINT_ANY, "offset_mask", "%04x", ntohs(sel->offmask)); + print_int(PRINT_ANY, "offset_shift", ">>%d ", sel->offshift); + print_int(PRINT_ANY, "offset_off", "at %d ", sel->offoff); + } + if (sel->off) + print_int(PRINT_ANY, "plus", "plus %d ", sel->off); + } + if (sel->flags & TC_U32_EAT) + print_string(PRINT_ANY, NULL, "%s", " eat "); + + if (sel->hmask) { + print_nl(); + unsigned int hmask = (unsigned int)htonl(sel->hmask); + + print_hex(PRINT_ANY, "hash_mask", " hash mask %08x ", hmask); + print_int(PRINT_ANY, "hash_off", "at %d ", sel->hoff); + } + } + + if (tb[TCA_U32_POLICE]) { + print_nl(); + tc_print_police(f, tb[TCA_U32_POLICE]); + } + + if (tb[TCA_U32_INDEV]) { + struct rtattr *idev = tb[TCA_U32_INDEV]; + print_nl(); + print_string(PRINT_ANY, "input_dev", " input dev %s", + rta_getattr_str(idev)); + print_nl(); + } + + if (tb[TCA_U32_ACT]) + tc_print_action(f, tb[TCA_U32_ACT], 0); + + return 0; +} + +struct filter_util u32_filter_util = { + .id = "u32", + .parse_fopt = u32_parse_opt, + .print_fopt = u32_print_opt, +}; diff --git a/tc/m_action.c b/tc/m_action.c new file mode 100644 index 0000000..f180ba0 --- /dev/null +++ b/tc/m_action.c @@ -0,0 +1,914 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_action.c Action Management + * + * 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(stderr, "[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(const 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; + 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); + + if (tb[TCA_ROOT_EXT_WARN_MSG]) { + print_string(PRINT_ANY, "warn", "%s", + rta_getattr_str(tb[TCA_ROOT_EXT_WARN_MSG])); + print_nl(); + } + + 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; +} diff --git a/tc/m_bpf.c b/tc/m_bpf.c new file mode 100644 index 0000000..da50c05 --- /dev/null +++ b/tc/m_bpf.c @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_bpf.c BPF based action module + * + * Authors: Jiri Pirko <jiri@resnulli.us> + * Daniel Borkmann <daniel@iogearbox.net> + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <linux/bpf.h> +#include <linux/tc_act/tc_bpf.h> + +#include "utils.h" + +#include "tc_util.h" +#include "bpf_util.h" + +static const enum bpf_prog_type bpf_type = BPF_PROG_TYPE_SCHED_ACT; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... bpf ... [ index INDEX ]\n" + "\n" + "BPF use case:\n" + " bytecode BPF_BYTECODE\n" + " bytecode-file FILE\n" + "\n" + "eBPF use case:\n" + " object-file FILE [ section ACT_NAME ] [ export UDS_FILE ]" + " [ verbose ]\n" + " object-pinned FILE\n" + "\n" + "Where BPF_BYTECODE := \'s,c t f k,c t f k,c t f k,...\'\n" + "c,t,f,k and s are decimals; s denotes number of 4-tuples\n" + "\n" + "Where FILE points to a file containing the BPF_BYTECODE string,\n" + "an ELF file containing eBPF map definitions and bytecode, or a\n" + "pinned eBPF program.\n" + "\n" + "Where ACT_NAME refers to the section name containing the\n" + "action (default \'%s\').\n" + "\n" + "Where UDS_FILE points to a unix domain socket file in order\n" + "to hand off control of all created eBPF maps to an agent.\n" + "\n" + "Where optionally INDEX points to an existing action, or\n" + "explicitly specifies an action index upon creation.\n", + bpf_prog_to_default_section(bpf_type)); +} + +static void bpf_cbpf_cb(void *nl, const struct sock_filter *ops, int ops_len) +{ + addattr16(nl, MAX_MSG, TCA_ACT_BPF_OPS_LEN, ops_len); + addattr_l(nl, MAX_MSG, TCA_ACT_BPF_OPS, ops, + ops_len * sizeof(struct sock_filter)); +} + +static void bpf_ebpf_cb(void *nl, int fd, const char *annotation) +{ + addattr32(nl, MAX_MSG, TCA_ACT_BPF_FD, fd); + addattrstrz(nl, MAX_MSG, TCA_ACT_BPF_NAME, annotation); +} + +static const struct bpf_cfg_ops bpf_cb_ops = { + .cbpf_cb = bpf_cbpf_cb, + .ebpf_cb = bpf_ebpf_cb, +}; + +static int bpf_parse_opt(struct action_util *a, int *ptr_argc, char ***ptr_argv, + int tca_id, struct nlmsghdr *n) +{ + const char *bpf_obj = NULL, *bpf_uds_name = NULL; + struct tc_act_bpf parm = {}; + struct bpf_cfg_in cfg = {}; + bool seen_run = false; + struct rtattr *tail; + int argc, ret = 0; + char **argv; + + argv = *ptr_argv; + argc = *ptr_argc; + + if (matches(*argv, "bpf") != 0) + return -1; + + NEXT_ARG(); + + tail = addattr_nest(n, MAX_MSG, tca_id); + + while (argc > 0) { + if (matches(*argv, "run") == 0) { + NEXT_ARG(); + + if (seen_run) + duparg("run", *argv); +opt_bpf: + seen_run = true; + cfg.type = bpf_type; + cfg.argc = argc; + cfg.argv = argv; + + if (bpf_parse_and_load_common(&cfg, &bpf_cb_ops, n)) + return -1; + + argc = cfg.argc; + argv = cfg.argv; + + bpf_obj = cfg.object; + bpf_uds_name = cfg.uds; + } else if (matches(*argv, "help") == 0) { + explain(); + return -1; + } else if (matches(*argv, "index") == 0) { + break; + } else { + if (!seen_run) + goto opt_bpf; + break; + } + + NEXT_ARG_FWD(); + } + + parse_action_control_dflt(&argc, &argv, &parm.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&parm.index, *argv, 10)) { + fprintf(stderr, "bpf: Illegal \"index\"\n"); + return -1; + } + + NEXT_ARG_FWD(); + } + } + + addattr_l(n, MAX_MSG, TCA_ACT_BPF_PARMS, &parm, sizeof(parm)); + addattr_nest_end(n, tail); + + if (bpf_uds_name) + ret = bpf_send_map_fds(bpf_uds_name, bpf_obj); + + *ptr_argc = argc; + *ptr_argv = argv; + + return ret; +} + +static int bpf_print_opt(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_ACT_BPF_MAX + 1]; + struct tc_act_bpf *parm; + int d_ok = 0; + + print_string(PRINT_ANY, "kind", "%s ", "bpf"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_ACT_BPF_MAX, arg); + + if (!tb[TCA_ACT_BPF_PARMS]) { + fprintf(stderr, "Missing bpf parameters\n"); + return -1; + } + + parm = RTA_DATA(tb[TCA_ACT_BPF_PARMS]); + + if (tb[TCA_ACT_BPF_NAME]) + print_string(PRINT_ANY, "bpf_name", "%s ", + rta_getattr_str(tb[TCA_ACT_BPF_NAME])); + if (tb[TCA_ACT_BPF_OPS] && tb[TCA_ACT_BPF_OPS_LEN]) { + bpf_print_ops(tb[TCA_ACT_BPF_OPS], + rta_getattr_u16(tb[TCA_ACT_BPF_OPS_LEN])); + print_string(PRINT_FP, NULL, "%s", " "); + } + + if (tb[TCA_ACT_BPF_ID]) + d_ok = bpf_dump_prog_info(f, + rta_getattr_u32(tb[TCA_ACT_BPF_ID])); + if (!d_ok && tb[TCA_ACT_BPF_TAG]) { + SPRINT_BUF(b); + + print_string(PRINT_ANY, "tag", "tag %s ", + hexstring_n2a(RTA_DATA(tb[TCA_ACT_BPF_TAG]), + RTA_PAYLOAD(tb[TCA_ACT_BPF_TAG]), + b, sizeof(b))); + } + + print_action_control(f, "default-action ", parm->action, _SL_); + print_uint(PRINT_ANY, "index", "\t index %u", parm->index); + print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt); + + if (show_stats) { + if (tb[TCA_ACT_BPF_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_ACT_BPF_TM]); + + print_tm(f, tm); + } + } + + print_string(PRINT_FP, NULL, "%s", "\n "); + return 0; +} + +struct action_util bpf_action_util = { + .id = "bpf", + .parse_aopt = bpf_parse_opt, + .print_aopt = bpf_print_opt, +}; diff --git a/tc/m_connmark.c b/tc/m_connmark.c new file mode 100644 index 0000000..8506d95 --- /dev/null +++ b/tc/m_connmark.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * m_connmark.c Connection tracking marking import + * + * Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_connmark.h> + +static void +explain(void) +{ + fprintf(stderr, + "Usage: ... connmark [zone ZONE] [CONTROL] [index <INDEX>]\n" + "where :\n" + "\tZONE is the conntrack zone\n" + "\tCONTROL := reclassify | pipe | drop | continue | ok |\n" + "\t goto chain <CHAIN_INDEX>\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_connmark(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, + struct nlmsghdr *n) +{ + struct tc_connmark sel = {}; + char **argv = *argv_p; + int argc = *argc_p; + int ok = 0; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "connmark") == 0) { + ok = 1; + argc--; + argv++; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + } + + if (!ok) { + explain(); + return -1; + } + + if (argc) { + if (matches(*argv, "zone") == 0) { + NEXT_ARG(); + if (get_u16(&sel.zone, *argv, 10)) { + fprintf(stderr, "connmark: Illegal \"zone\"\n"); + return -1; + } + argc--; + argv++; + } + } + + parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "connmark: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_CONNMARK_PARMS, &sel, sizeof(sel)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_connmark(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_CONNMARK_MAX + 1]; + struct tc_connmark *ci; + + print_string(PRINT_ANY, "kind", "%s ", "connmark"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CONNMARK_MAX, arg); + if (tb[TCA_CONNMARK_PARMS] == NULL) { + fprintf(stderr, "Missing connmark parameters\n"); + return -1; + } + + ci = RTA_DATA(tb[TCA_CONNMARK_PARMS]); + + print_uint(PRINT_ANY, "zone", "zone %u", ci->zone); + print_action_control(f, " ", ci->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", ci->index); + print_int(PRINT_ANY, "ref", " ref %d", ci->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", ci->bindcnt); + + if (show_stats) { + if (tb[TCA_CONNMARK_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_CONNMARK_TM]); + + print_tm(f, tm); + } + } + print_nl(); + + return 0; +} + +struct action_util connmark_action_util = { + .id = "connmark", + .parse_aopt = parse_connmark, + .print_aopt = print_connmark, +}; diff --git a/tc/m_csum.c b/tc/m_csum.c new file mode 100644 index 0000000..f5fe8f5 --- /dev/null +++ b/tc/m_csum.c @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_csum.c checksum updating action + * + * Authors: Gregoire Baron <baronchon@n7mm.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <linux/tc_act/tc_csum.h> + +#include "utils.h" +#include "tc_util.h" + +static void +explain(void) +{ + fprintf(stderr, "Usage: ... csum <UPDATE>\n" + "Where: UPDATE := <TARGET> [<UPDATE>]\n" + " TARGET := { ip4h | icmp | igmp | tcp | udp | udplite | sctp | <SWEETS> }\n" + " SWEETS := { and | or | \'+\' }\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_csum_args(int *argc_p, char ***argv_p, struct tc_csum *sel) +{ + int argc = *argc_p; + char **argv = *argv_p; + + if (argc <= 0) + return -1; + + while (argc > 0) { + if ((matches(*argv, "iph") == 0) || + (matches(*argv, "ip4h") == 0) || + (matches(*argv, "ipv4h") == 0)) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_IPV4HDR; + + else if (matches(*argv, "icmp") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_ICMP; + + else if (matches(*argv, "igmp") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_IGMP; + + else if (matches(*argv, "tcp") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_TCP; + + else if (matches(*argv, "udp") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_UDP; + + else if (matches(*argv, "udplite") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_UDPLITE; + + else if (matches(*argv, "sctp") == 0) + sel->update_flags |= TCA_CSUM_UPDATE_FLAG_SCTP; + + else if ((matches(*argv, "and") == 0) || + (matches(*argv, "or") == 0) || + (matches(*argv, "+") == 0)) + ; /* just ignore: ... csum iph and tcp or udp */ + else + break; + argc--; + argv++; + } + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static int +parse_csum(struct action_util *a, int *argc_p, + char ***argv_p, int tca_id, struct nlmsghdr *n) +{ + struct tc_csum sel = {}; + + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "csum") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "index") == 0) { + goto skip_args; + } else if (parse_csum_args(&argc, &argv, &sel)) { + fprintf(stderr, "Illegal csum construct (%s)\n", + *argv); + explain(); + return -1; + } + ok++; + continue; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + } + + if (!ok) { + explain(); + return -1; + } + + if (sel.update_flags == 0) { + fprintf(stderr, "Illegal csum construct, empty <UPDATE> list\n"); + return -1; + } + + parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_OK); + + if (argc) { + if (matches(*argv, "index") == 0) { +skip_args: + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "Illegal \"index\" (%s) <csum>\n", + *argv); + return -1; + } + argc--; + argv++; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_CSUM_PARMS, &sel, sizeof(sel)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static int +print_csum(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_csum *sel; + + struct rtattr *tb[TCA_CSUM_MAX + 1]; + + char *uflag_1 = ""; + char *uflag_2 = ""; + char *uflag_3 = ""; + char *uflag_4 = ""; + char *uflag_5 = ""; + char *uflag_6 = ""; + char *uflag_7 = ""; + SPRINT_BUF(buf); + + int uflag_count = 0; + + print_string(PRINT_ANY, "kind", "%s ", "csum"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CSUM_MAX, arg); + + if (tb[TCA_CSUM_PARMS] == NULL) { + fprintf(stderr, "Missing csum parameters\n"); + return -1; + } + sel = RTA_DATA(tb[TCA_CSUM_PARMS]); + + if (sel->update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) { + uflag_1 = "iph"; + uflag_count++; + } + #define CSUM_UFLAG_BUFFER(flag_buffer, flag_value, flag_string) \ + do { \ + if (sel->update_flags & flag_value) { \ + flag_buffer = uflag_count > 0 ? \ + ", " flag_string : flag_string; \ + uflag_count++; \ + } \ + } while (0) + CSUM_UFLAG_BUFFER(uflag_2, TCA_CSUM_UPDATE_FLAG_ICMP, "icmp"); + CSUM_UFLAG_BUFFER(uflag_3, TCA_CSUM_UPDATE_FLAG_IGMP, "igmp"); + CSUM_UFLAG_BUFFER(uflag_4, TCA_CSUM_UPDATE_FLAG_TCP, "tcp"); + CSUM_UFLAG_BUFFER(uflag_5, TCA_CSUM_UPDATE_FLAG_UDP, "udp"); + CSUM_UFLAG_BUFFER(uflag_6, TCA_CSUM_UPDATE_FLAG_UDPLITE, "udplite"); + CSUM_UFLAG_BUFFER(uflag_7, TCA_CSUM_UPDATE_FLAG_SCTP, "sctp"); + if (!uflag_count) { + uflag_1 = "?empty"; + } + + snprintf(buf, sizeof(buf), "%s%s%s%s%s%s%s", + uflag_1, uflag_2, uflag_3, + uflag_4, uflag_5, uflag_6, uflag_7); + print_string(PRINT_ANY, "csum", "(%s) ", buf); + + print_action_control(f, "action ", sel->action, _SL_); + print_uint(PRINT_ANY, "index", "\tindex %u", sel->index); + print_int(PRINT_ANY, "ref", " ref %d", sel->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", sel->bindcnt); + + if (show_stats) { + if (tb[TCA_CSUM_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_CSUM_TM]); + + print_tm(f, tm); + } + } + print_nl(); + + return 0; +} + +struct action_util csum_action_util = { + .id = "csum", + .parse_aopt = parse_csum, + .print_aopt = print_csum, +}; diff --git a/tc/m_ct.c b/tc/m_ct.c new file mode 100644 index 0000000..8c47148 --- /dev/null +++ b/tc/m_ct.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB +/* - + * m_ct.c Connection tracking action + * + * Authors: Paul Blakey <paulb@mellanox.com> + * Yossi Kuperman <yossiku@mellanox.com> + * Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "utils.h" +#include "tc_util.h" +#include "rt_names.h" +#include <linux/tc_act/tc_ct.h> + +static void +usage(void) +{ + fprintf(stderr, + "Usage: ct clear\n" + " ct commit [force] [zone ZONE] [mark MASKED_MARK] [label MASKED_LABEL] [nat NAT_SPEC] [helper HELPER]\n" + " ct [nat] [zone ZONE]\n" + "Where: ZONE is the conntrack zone table number\n" + " NAT_SPEC is {src|dst} addr addr1[-addr2] [port port1[-port2]]\n" + " HELPER is family-proto-name such as ipv4-tcp-ftp\n" + "\n"); + exit(-1); +} + +static int ct_parse_nat_addr_range(const char *str, struct nlmsghdr *n) +{ + inet_prefix addr = { .family = AF_UNSPEC, }; + char *addr1, *addr2 = 0; + SPRINT_BUF(buffer); + int attr; + int ret; + + strncpy(buffer, str, sizeof(buffer) - 1); + + addr1 = buffer; + addr2 = strchr(addr1, '-'); + if (addr2) { + *addr2 = '\0'; + addr2++; + } + + ret = get_addr(&addr, addr1, AF_UNSPEC); + if (ret) + return ret; + attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MIN : + TCA_CT_NAT_IPV6_MIN; + addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen); + + if (addr2) { + ret = get_addr(&addr, addr2, addr.family); + if (ret) + return ret; + } + attr = addr.family == AF_INET ? TCA_CT_NAT_IPV4_MAX : + TCA_CT_NAT_IPV6_MAX; + addattr_l(n, MAX_MSG, attr, addr.data, addr.bytelen); + + return 0; +} + +static int ct_parse_nat_port_range(const char *str, struct nlmsghdr *n) +{ + char *port1, *port2 = 0; + SPRINT_BUF(buffer); + __be16 port; + int ret; + + strncpy(buffer, str, sizeof(buffer) - 1); + + port1 = buffer; + port2 = strchr(port1, '-'); + if (port2) { + *port2 = '\0'; + port2++; + } + + ret = get_be16(&port, port1, 10); + if (ret) + return -1; + addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MIN, port); + + if (port2) { + ret = get_be16(&port, port2, 10); + if (ret) + return -1; + } + addattr16(n, MAX_MSG, TCA_CT_NAT_PORT_MAX, port); + + return 0; +} + + +static int ct_parse_u16(char *str, int value_type, int mask_type, + struct nlmsghdr *n) +{ + __u16 value, mask; + char *slash = 0; + + if (mask_type != TCA_CT_UNSPEC) { + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + } + + if (get_u16(&value, str, 0)) + return -1; + + if (slash) { + if (get_u16(&mask, slash + 1, 0)) + return -1; + } else { + mask = UINT16_MAX; + } + + addattr16(n, MAX_MSG, value_type, value); + if (mask_type != TCA_CT_UNSPEC) + addattr16(n, MAX_MSG, mask_type, mask); + + return 0; +} + +static int ct_parse_u32(char *str, int value_type, int mask_type, + struct nlmsghdr *n) +{ + __u32 value, mask; + char *slash; + + slash = strchr(str, '/'); + if (slash) + *slash = '\0'; + + if (get_u32(&value, str, 0)) + return -1; + + if (slash) { + if (get_u32(&mask, slash + 1, 0)) + return -1; + } else { + mask = UINT32_MAX; + } + + addattr32(n, MAX_MSG, value_type, value); + addattr32(n, MAX_MSG, mask_type, mask); + + return 0; +} + +static int ct_parse_mark(char *str, struct nlmsghdr *n) +{ + return ct_parse_u32(str, TCA_CT_MARK, TCA_CT_MARK_MASK, n); +} + +static int ct_parse_helper(char *str, struct nlmsghdr *n) +{ + char f[32], p[32], name[32]; + __u8 family; + int proto; + + if (strlen(str) >= 32 || + sscanf(str, "%[^-]-%[^-]-%[^-]", f, p, name) != 3) + return -1; + if (!strcmp(f, "ipv4")) + family = AF_INET; + else if (!strcmp(f, "ipv6")) + family = AF_INET6; + else + return -1; + + proto = inet_proto_a2n(p); + if (proto < 0) + return -1; + + addattr8(n, MAX_MSG, TCA_CT_HELPER_FAMILY, family); + addattr8(n, MAX_MSG, TCA_CT_HELPER_PROTO, proto); + addattrstrz(n, MAX_MSG, TCA_CT_HELPER_NAME, name); + return 0; +} + +static int ct_parse_labels(char *str, struct nlmsghdr *n) +{ +#define LABELS_SIZE 16 + uint8_t labels[LABELS_SIZE], lmask[LABELS_SIZE]; + char *slash, *mask = NULL; + size_t slen, slen_mask = 0; + + slash = index(str, '/'); + if (slash) { + *slash = 0; + mask = slash+1; + slen_mask = strlen(mask); + } + + slen = strlen(str); + if (slen > LABELS_SIZE*2 || slen_mask > LABELS_SIZE*2) { + char errmsg[128]; + + snprintf(errmsg, sizeof(errmsg), + "%zd Max allowed size %d", + slen, LABELS_SIZE*2); + invarg(errmsg, str); + } + + if (hex2mem(str, labels, slen/2) < 0) + invarg("ct: labels must be a hex string\n", str); + addattr_l(n, MAX_MSG, TCA_CT_LABELS, labels, slen/2); + + if (mask) { + if (hex2mem(mask, lmask, slen_mask/2) < 0) + invarg("ct: labels mask must be a hex string\n", mask); + } else { + memset(lmask, 0xff, sizeof(lmask)); + slen_mask = sizeof(lmask)*2; + } + addattr_l(n, MAX_MSG, TCA_CT_LABELS_MASK, lmask, slen_mask/2); + + return 0; +} + +static int +parse_ct(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, + struct nlmsghdr *n) +{ + struct tc_ct sel = {}; + char **argv = *argv_p; + struct rtattr *tail; + int argc = *argc_p; + int ct_action = 0; + int ret; + + tail = addattr_nest(n, MAX_MSG, tca_id); + + if (argc && matches(*argv, "ct") == 0) + NEXT_ARG_FWD(); + + while (argc > 0) { + if (matches(*argv, "zone") == 0) { + NEXT_ARG(); + + if (ct_parse_u16(*argv, + TCA_CT_ZONE, TCA_CT_UNSPEC, n)) { + fprintf(stderr, "ct: Illegal \"zone\"\n"); + return -1; + } + } else if (matches(*argv, "nat") == 0) { + ct_action |= TCA_CT_ACT_NAT; + + NEXT_ARG(); + if (matches(*argv, "src") == 0) + ct_action |= TCA_CT_ACT_NAT_SRC; + else if (matches(*argv, "dst") == 0) + ct_action |= TCA_CT_ACT_NAT_DST; + else + continue; + + NEXT_ARG(); + if (matches(*argv, "addr") != 0) + usage(); + + NEXT_ARG(); + ret = ct_parse_nat_addr_range(*argv, n); + if (ret) { + fprintf(stderr, "ct: Illegal nat address range\n"); + return -1; + } + + NEXT_ARG(); + if (matches(*argv, "port") != 0) + continue; + + NEXT_ARG(); + ret = ct_parse_nat_port_range(*argv, n); + if (ret) { + fprintf(stderr, "ct: Illegal nat port range\n"); + return -1; + } + } else if (matches(*argv, "clear") == 0) { + ct_action |= TCA_CT_ACT_CLEAR; + } else if (matches(*argv, "commit") == 0) { + ct_action |= TCA_CT_ACT_COMMIT; + } else if (matches(*argv, "force") == 0) { + ct_action |= TCA_CT_ACT_FORCE; + } else if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "ct: Illegal \"index\"\n"); + return -1; + } + } else if (matches(*argv, "mark") == 0) { + NEXT_ARG(); + + ret = ct_parse_mark(*argv, n); + if (ret) { + fprintf(stderr, "ct: Illegal \"mark\"\n"); + return -1; + } + } else if (matches(*argv, "label") == 0) { + NEXT_ARG(); + + ret = ct_parse_labels(*argv, n); + if (ret) { + fprintf(stderr, "ct: Illegal \"label\"\n"); + return -1; + } + } else if (matches(*argv, "help") == 0) { + usage(); + } else if (matches(*argv, "helper") == 0) { + NEXT_ARG(); + + ret = ct_parse_helper(*argv, n); + if (ret) { + fprintf(stderr, "ct: Illegal \"helper\"\n"); + return -1; + } + } else { + break; + } + NEXT_ARG_FWD(); + } + + if (ct_action & TCA_CT_ACT_CLEAR && + ct_action & ~TCA_CT_ACT_CLEAR) { + fprintf(stderr, "ct: clear can only be used alone\n"); + return -1; + } + + if (ct_action & TCA_CT_ACT_NAT_SRC && + ct_action & TCA_CT_ACT_NAT_DST) { + fprintf(stderr, "ct: src and dst nat can't be used together\n"); + return -1; + } + + if ((ct_action & TCA_CT_ACT_COMMIT) && + (ct_action & TCA_CT_ACT_NAT) && + !(ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) { + fprintf(stderr, "ct: commit and nat must set src or dst\n"); + return -1; + } + + if (!(ct_action & TCA_CT_ACT_COMMIT) && + (ct_action & (TCA_CT_ACT_NAT_SRC | TCA_CT_ACT_NAT_DST))) { + fprintf(stderr, "ct: src or dst is only valid if commit is set\n"); + return -1; + } + + parse_action_control_dflt(&argc, &argv, &sel.action, false, + TC_ACT_PIPE); + + addattr16(n, MAX_MSG, TCA_CT_ACTION, ct_action); + addattr_l(n, MAX_MSG, TCA_CT_PARMS, &sel, sizeof(sel)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int ct_sprint_port(char *buf, const char *prefix, struct rtattr *attr) +{ + if (!attr) + return 0; + + return sprintf(buf, "%s%d", prefix, rta_getattr_be16(attr)); +} + +static int ct_sprint_ip_addr(char *buf, const char *prefix, + struct rtattr *attr) +{ + int family; + size_t len; + + if (!attr) + return 0; + + len = RTA_PAYLOAD(attr); + + if (len == 4) + family = AF_INET; + else if (len == 16) + family = AF_INET6; + else + return 0; + + return sprintf(buf, "%s%s", prefix, rt_addr_n2a_rta(family, attr)); +} + +static void ct_print_nat(int ct_action, struct rtattr **tb) +{ + size_t done = 0; + char out[256] = ""; + bool nat = false; + + if (!(ct_action & TCA_CT_ACT_NAT)) + return; + + if (ct_action & TCA_CT_ACT_NAT_SRC) { + nat = true; + done += sprintf(out + done, "src"); + } else if (ct_action & TCA_CT_ACT_NAT_DST) { + nat = true; + done += sprintf(out + done, "dst"); + } + + if (nat) { + done += ct_sprint_ip_addr(out + done, " addr ", + tb[TCA_CT_NAT_IPV4_MIN]); + done += ct_sprint_ip_addr(out + done, " addr ", + tb[TCA_CT_NAT_IPV6_MIN]); + if (tb[TCA_CT_NAT_IPV4_MAX] && + memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV4_MIN]), + RTA_DATA(tb[TCA_CT_NAT_IPV4_MAX]), 4)) + done += ct_sprint_ip_addr(out + done, "-", + tb[TCA_CT_NAT_IPV4_MAX]); + else if (tb[TCA_CT_NAT_IPV6_MAX] && + memcmp(RTA_DATA(tb[TCA_CT_NAT_IPV6_MIN]), + RTA_DATA(tb[TCA_CT_NAT_IPV6_MAX]), 16)) + done += ct_sprint_ip_addr(out + done, "-", + tb[TCA_CT_NAT_IPV6_MAX]); + done += ct_sprint_port(out + done, " port ", + tb[TCA_CT_NAT_PORT_MIN]); + if (tb[TCA_CT_NAT_PORT_MAX] && + memcmp(RTA_DATA(tb[TCA_CT_NAT_PORT_MIN]), + RTA_DATA(tb[TCA_CT_NAT_PORT_MAX]), 2)) + done += ct_sprint_port(out + done, "-", + tb[TCA_CT_NAT_PORT_MAX]); + } + + if (done) + print_string(PRINT_ANY, "nat", " nat %s", out); + else + print_string(PRINT_ANY, "nat", " nat", ""); +} + +static void ct_print_labels(struct rtattr *attr, + struct rtattr *mask_attr) +{ + const unsigned char *str; + bool print_mask = false; + char out[256], *p; + int data_len, i; + + if (!attr) + return; + + data_len = RTA_PAYLOAD(attr); + hexstring_n2a(RTA_DATA(attr), data_len, out, sizeof(out)); + p = out + data_len*2; + + data_len = RTA_PAYLOAD(attr); + str = RTA_DATA(mask_attr); + if (data_len != 16) + print_mask = true; + for (i = 0; !print_mask && i < data_len; i++) { + if (str[i] != 0xff) + print_mask = true; + } + if (print_mask) { + *p++ = '/'; + hexstring_n2a(RTA_DATA(mask_attr), data_len, p, + sizeof(out)-(p-out)); + p += data_len*2; + } + *p = '\0'; + + print_string(PRINT_ANY, "label", " label %s", out); +} + +static void ct_print_helper(struct rtattr *family, struct rtattr *proto, struct rtattr *name) +{ + char helper[32], buf[32], *n; + int *f, *p; + + if (!family || !proto || !name) + return; + + f = RTA_DATA(family); + p = RTA_DATA(proto); + n = RTA_DATA(name); + snprintf(helper, sizeof(helper), "%s-%s-%s", (*f == AF_INET) ? "ipv4" : "ipv6", + inet_proto_n2a(*p, buf, sizeof(buf)), n); + print_string(PRINT_ANY, "helper", " helper %s", helper); +} + +static int print_ct(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_CT_MAX + 1]; + const char *commit; + struct tc_ct *p; + int ct_action = 0; + + print_string(PRINT_ANY, "kind", "%s", "ct"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CT_MAX, arg); + if (tb[TCA_CT_PARMS] == NULL) { + print_string(PRINT_FP, NULL, "%s", "[NULL ct parameters]"); + return -1; + } + + p = RTA_DATA(tb[TCA_CT_PARMS]); + + if (tb[TCA_CT_ACTION]) + ct_action = rta_getattr_u16(tb[TCA_CT_ACTION]); + if (ct_action & TCA_CT_ACT_COMMIT) { + commit = ct_action & TCA_CT_ACT_FORCE ? + "commit force" : "commit"; + print_string(PRINT_ANY, "action", " %s", commit); + } else if (ct_action & TCA_CT_ACT_CLEAR) { + print_string(PRINT_ANY, "action", " %s", "clear"); + } + + print_masked_u32("mark", tb[TCA_CT_MARK], tb[TCA_CT_MARK_MASK], false); + print_masked_u16("zone", tb[TCA_CT_ZONE], NULL, false); + ct_print_labels(tb[TCA_CT_LABELS], tb[TCA_CT_LABELS_MASK]); + ct_print_helper(tb[TCA_CT_HELPER_FAMILY], tb[TCA_CT_HELPER_PROTO], tb[TCA_CT_HELPER_NAME]); + ct_print_nat(ct_action, tb); + + print_action_control(f, " ", p->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + + if (show_stats) { + if (tb[TCA_CT_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_CT_TM]); + + print_tm(f, tm); + } + } + print_nl(); + + return 0; +} + +struct action_util ct_action_util = { + .id = "ct", + .parse_aopt = parse_ct, + .print_aopt = print_ct, +}; diff --git a/tc/m_ctinfo.c b/tc/m_ctinfo.c new file mode 100644 index 0000000..996a362 --- /dev/null +++ b/tc/m_ctinfo.c @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * m_ctinfo.c netfilter ctinfo mark action + * + * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_ctinfo.h> + +static void +explain(void) +{ + fprintf(stderr, + "Usage: ... ctinfo [dscp mask [statemask]] [cpmark [mask]] [zone ZONE] [CONTROL] [index <INDEX>]\n" + "where :\n" + "\tdscp MASK bitmask location of stored DSCP\n" + "\t STATEMASK bitmask to determine conditional restoring\n" + "\tcpmark MASK mask applied to mark on restoration\n" + "\tZONE is the conntrack zone\n" + "\tCONTROL := reclassify | pipe | drop | continue | ok |\n" + "\t goto chain <CHAIN_INDEX>\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_ctinfo(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, + struct nlmsghdr *n) +{ + unsigned int cpmarkmask = 0, dscpmask = 0, dscpstatemask = 0; + struct tc_ctinfo sel = {}; + unsigned short zone = 0; + char **argv = *argv_p; + struct rtattr *tail; + int argc = *argc_p; + int ok = 0; + __u8 i; + + while (argc > 0) { + if (matches(*argv, "ctinfo") == 0) { + ok = 1; + NEXT_ARG_FWD(); + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + } + + if (!ok) { + explain(); + return -1; + } + + if (argc) { + if (matches(*argv, "dscp") == 0) { + NEXT_ARG(); + if (get_u32(&dscpmask, *argv, 0)) { + fprintf(stderr, + "ctinfo: Illegal dscp \"mask\"\n"); + return -1; + } + if (NEXT_ARG_OK()) { + NEXT_ARG_FWD(); + if (!get_u32(&dscpstatemask, *argv, 0)) + NEXT_ARG_FWD(); /* was a statemask */ + } else { + NEXT_ARG_FWD(); + } + } + } + + /* cpmark has optional mask parameter, so the next arg might not */ + /* exist, or it might be the next option, or it may actually be a */ + /* 32bit mask */ + if (argc) { + if (matches(*argv, "cpmark") == 0) { + cpmarkmask = ~0; + if (NEXT_ARG_OK()) { + NEXT_ARG_FWD(); + if (!get_u32(&cpmarkmask, *argv, 0)) + NEXT_ARG_FWD(); /* was a mask */ + } else { + NEXT_ARG_FWD(); + } + } + } + + if (argc) { + if (matches(*argv, "zone") == 0) { + NEXT_ARG(); + if (get_u16(&zone, *argv, 10)) { + fprintf(stderr, "ctinfo: Illegal \"zone\"\n"); + return -1; + } + NEXT_ARG_FWD(); + } + } + + parse_action_control_dflt(&argc, &argv, &sel.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "ctinfo: Illegal \"index\"\n"); + return -1; + } + NEXT_ARG_FWD(); + } + } + + if (dscpmask & dscpstatemask) { + fprintf(stderr, + "ctinfo: dscp mask & statemask must NOT overlap\n"); + return -1; + } + + i = ffs(dscpmask); + if (i && ((~0 & (dscpmask >> (i - 1))) != 0x3f)) { + fprintf(stderr, + "ctinfo: dscp mask must be 6 contiguous bits long\n"); + return -1; + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_CTINFO_ACT, &sel, sizeof(sel)); + addattr16(n, MAX_MSG, TCA_CTINFO_ZONE, zone); + + if (dscpmask) + addattr32(n, MAX_MSG, + TCA_CTINFO_PARMS_DSCP_MASK, dscpmask); + + if (dscpstatemask) + addattr32(n, MAX_MSG, + TCA_CTINFO_PARMS_DSCP_STATEMASK, dscpstatemask); + + if (cpmarkmask) + addattr32(n, MAX_MSG, + TCA_CTINFO_PARMS_CPMARK_MASK, cpmarkmask); + + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static void print_ctinfo_stats(FILE *f, struct rtattr *tb[TCA_CTINFO_MAX + 1]) +{ + struct tcf_t *tm; + + if (tb[TCA_CTINFO_TM]) { + tm = RTA_DATA(tb[TCA_CTINFO_TM]); + + print_tm(f, tm); + } + + if (tb[TCA_CTINFO_STATS_DSCP_SET]) + print_lluint(PRINT_ANY, "dscpset", " DSCP set %llu", + rta_getattr_u64(tb[TCA_CTINFO_STATS_DSCP_SET])); + if (tb[TCA_CTINFO_STATS_DSCP_ERROR]) + print_lluint(PRINT_ANY, "dscperror", " error %llu", + rta_getattr_u64(tb[TCA_CTINFO_STATS_DSCP_ERROR])); + + if (tb[TCA_CTINFO_STATS_CPMARK_SET]) + print_lluint(PRINT_ANY, "cpmarkset", " CPMARK set %llu", + rta_getattr_u64(tb[TCA_CTINFO_STATS_CPMARK_SET])); +} + +static int print_ctinfo(struct action_util *au, FILE *f, struct rtattr *arg) +{ + unsigned int cpmarkmask = ~0, dscpmask = 0, dscpstatemask = 0; + struct rtattr *tb[TCA_CTINFO_MAX + 1]; + unsigned short zone = 0; + struct tc_ctinfo *ci; + + print_string(PRINT_ANY, "kind", "%s ", "ctinfo"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CTINFO_MAX, arg); + if (!tb[TCA_CTINFO_ACT]) { + print_string(PRINT_FP, NULL, "%s", + "[NULL ctinfo action parameters]"); + return -1; + } + + ci = RTA_DATA(tb[TCA_CTINFO_ACT]); + + if (tb[TCA_CTINFO_PARMS_DSCP_MASK]) { + if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_DSCP_MASK]) >= + sizeof(__u32)) + dscpmask = rta_getattr_u32( + tb[TCA_CTINFO_PARMS_DSCP_MASK]); + else + print_string(PRINT_FP, NULL, "%s", + "[invalid dscp mask parameter]"); + } + + if (tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]) { + if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]) >= + sizeof(__u32)) + dscpstatemask = rta_getattr_u32( + tb[TCA_CTINFO_PARMS_DSCP_STATEMASK]); + else + print_string(PRINT_FP, NULL, "%s", + "[invalid dscp statemask parameter]"); + } + + if (tb[TCA_CTINFO_PARMS_CPMARK_MASK]) { + if (RTA_PAYLOAD(tb[TCA_CTINFO_PARMS_CPMARK_MASK]) >= + sizeof(__u32)) + cpmarkmask = rta_getattr_u32( + tb[TCA_CTINFO_PARMS_CPMARK_MASK]); + else + print_string(PRINT_FP, NULL, "%s", + "[invalid cpmark mask parameter]"); + } + + if (tb[TCA_CTINFO_ZONE] && RTA_PAYLOAD(tb[TCA_CTINFO_ZONE]) >= + sizeof(__u16)) + zone = rta_getattr_u16(tb[TCA_CTINFO_ZONE]); + + print_hu(PRINT_ANY, "zone", "zone %u", zone); + print_action_control(f, " ", ci->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", ci->index); + print_int(PRINT_ANY, "ref", " ref %d", ci->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", ci->bindcnt); + + if (tb[TCA_CTINFO_PARMS_DSCP_MASK]) { + print_0xhex(PRINT_ANY, "dscpmask", " dscp %#010llx", dscpmask); + print_0xhex(PRINT_ANY, "dscpstatemask", " %#010llx", + dscpstatemask); + } + + if (tb[TCA_CTINFO_PARMS_CPMARK_MASK]) + print_0xhex(PRINT_ANY, "cpmark", " cpmark %#010llx", + cpmarkmask); + + if (show_stats) + print_ctinfo_stats(f, tb); + + print_nl(); + + return 0; +} + +struct action_util ctinfo_action_util = { + .id = "ctinfo", + .parse_aopt = parse_ctinfo, + .print_aopt = print_ctinfo, +}; diff --git a/tc/m_ematch.c b/tc/m_ematch.c new file mode 100644 index 0000000..fefc786 --- /dev/null +++ b/tc/m_ematch.c @@ -0,0 +1,572 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_ematch.c Extended Matches + * + * Authors: Thomas Graf <tgraf@suug.ch> + */ + +#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 <dlfcn.h> +#include <stdarg.h> +#include <errno.h> + +#include "utils.h" +#include "tc_util.h" +#include "m_ematch.h" + +#define EMATCH_MAP_USR CONF_USR_DIR "/ematch_map" +#define EMATCH_MAP_ETC CONF_ETC_DIR "/ematch_map" + +static struct ematch_util *ematch_list; + +/* export to bison parser */ +int ematch_argc; +char **ematch_argv; +char *ematch_err; +struct ematch *ematch_root; + +static int begin_argc; +static char **begin_argv; + +static void bstr_print(FILE *fd, const struct bstr *b, int ascii); + +static inline void map_warning(int num, char *kind) +{ + fprintf(stderr, + "Error: Unable to find ematch \"%s\" in %s or %s\n" \ + "Please assign a unique ID to the ematch kind the suggested " \ + "entry is:\n" \ + "\t%d\t%s\n", + kind, EMATCH_MAP_ETC, EMATCH_MAP_USR, num, kind); +} + +static int lookup_map(__u16 num, char *dst, int len, const char *file) +{ + int err = -EINVAL; + char buf[512]; + FILE *fd = fopen(file, "r"); + + if (fd == NULL) + return -errno; + + while (fgets(buf, sizeof(buf), fd)) { + char namebuf[512], *p = buf; + int id; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '#' || *p == '\n' || *p == 0) + continue; + + if (sscanf(p, "%d %s", &id, namebuf) != 2) { + fprintf(stderr, "ematch map %s corrupted at %s\n", + file, p); + goto out; + } + + if (id == num) { + if (dst) + strncpy(dst, namebuf, len - 1); + err = 0; + goto out; + } + } + + err = -ENOENT; +out: + fclose(fd); + return err; +} + +static int lookup_map_id(char *kind, int *dst, const char *file) +{ + int err = -EINVAL; + char buf[512]; + FILE *fd = fopen(file, "r"); + + if (fd == NULL) + return -errno; + + while (fgets(buf, sizeof(buf), fd)) { + char namebuf[512], *p = buf; + int id; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '#' || *p == '\n' || *p == 0) + continue; + + if (sscanf(p, "%d %s", &id, namebuf) != 2) { + fprintf(stderr, "ematch map %s corrupted at %s\n", + file, p); + goto out; + } + + if (!strcasecmp(namebuf, kind)) { + if (dst) + *dst = id; + err = 0; + goto out; + } + } + + err = -ENOENT; + *dst = 0; +out: + fclose(fd); + return err; +} + +static struct ematch_util *get_ematch_kind(char *kind) +{ + static void *body; + void *dlh; + char buf[256]; + struct ematch_util *e; + + for (e = ematch_list; e; e = e->next) { + if (strcmp(e->kind, kind) == 0) + return e; + } + + snprintf(buf, sizeof(buf), "em_%s.so", kind); + dlh = dlopen(buf, RTLD_LAZY); + if (dlh == NULL) { + dlh = body; + if (dlh == NULL) { + dlh = body = dlopen(NULL, RTLD_LAZY); + if (dlh == NULL) + return NULL; + } + } + + snprintf(buf, sizeof(buf), "%s_ematch_util", kind); + e = dlsym(dlh, buf); + if (e == NULL) + return NULL; + + e->next = ematch_list; + ematch_list = e; + + return e; +} + +static struct ematch_util *get_ematch_kind_num(__u16 kind) +{ + char name[513]; + int ret; + + ret = lookup_map(kind, name, sizeof(name), EMATCH_MAP_ETC); + if (ret == -ENOENT) + ret = lookup_map(kind, name, sizeof(name), EMATCH_MAP_USR); + if (ret < 0) + return NULL; + + return get_ematch_kind(name); +} + +static int em_parse_call(struct nlmsghdr *n, struct tcf_ematch_hdr *hdr, + struct ematch_util *e, struct ematch *t) +{ + if (e->parse_eopt_argv) { + int argc = 0, i = 0, ret; + struct bstr *args; + char **argv; + + for (args = t->args; args; args = bstr_next(args)) + argc++; + argv = calloc(argc, sizeof(char *)); + if (!argv) + return -1; + for (args = t->args; args; args = bstr_next(args)) + argv[i++] = args->data; + + ret = e->parse_eopt_argv(n, hdr, argc, argv); + + free(argv); + return ret; + } + + return e->parse_eopt(n, hdr, t->args->next); +} + +static int parse_tree(struct nlmsghdr *n, struct ematch *tree) +{ + int index = 1; + struct ematch *t; + + for (t = tree; t; t = t->next) { + struct rtattr *tail; + struct tcf_ematch_hdr hdr = { .flags = t->relation }; + + if (t->inverted) + hdr.flags |= TCF_EM_INVERT; + + tail = addattr_nest(n, MAX_MSG, index++); + + if (t->child) { + __u32 r = t->child_ref; + + addraw_l(n, MAX_MSG, &hdr, sizeof(hdr)); + addraw_l(n, MAX_MSG, &r, sizeof(r)); + } else { + int num = 0, err; + char buf[64]; + struct ematch_util *e; + + if (t->args == NULL) + return -1; + + strncpy(buf, (char *) t->args->data, sizeof(buf)-1); + e = get_ematch_kind(buf); + if (e == NULL) { + fprintf(stderr, "Unknown ematch \"%s\"\n", + buf); + return -1; + } + + err = lookup_map_id(buf, &num, EMATCH_MAP_ETC); + if (err == -ENOENT) + err = lookup_map_id(buf, &num, EMATCH_MAP_USR); + if (err < 0) { + if (err == -ENOENT) + map_warning(e->kind_num, buf); + return err; + } + + hdr.kind = num; + if (em_parse_call(n, &hdr, e, t) < 0) + return -1; + } + + addattr_nest_end(n, tail); + } + + return 0; +} + +static int flatten_tree(struct ematch *head, struct ematch *tree) +{ + int i, count = 0; + struct ematch *t; + + for (;;) { + count++; + + if (tree->child) { + for (t = head; t->next; t = t->next); + t->next = tree->child; + count += flatten_tree(head, tree->child); + } + + if (tree->relation == 0) + break; + + tree = tree->next; + } + + for (i = 0, t = head; t; t = t->next, i++) + t->index = i; + + for (t = head; t; t = t->next) + if (t->child) + t->child_ref = t->child->index; + + return count; +} + +__attribute__((format(printf, 5, 6))) +int em_parse_error(int err, struct bstr *args, struct bstr *carg, + struct ematch_util *e, char *fmt, ...) +{ + va_list a; + + va_start(a, fmt); + vfprintf(stderr, fmt, a); + va_end(a); + + if (ematch_err) + fprintf(stderr, ": %s\n... ", ematch_err); + else + fprintf(stderr, "\n... "); + + while (ematch_argc < begin_argc) { + if (ematch_argc == (begin_argc - 1)) + fprintf(stderr, ">>%s<< ", *begin_argv); + else + fprintf(stderr, "%s ", *begin_argv); + begin_argv++; + begin_argc--; + } + + fprintf(stderr, "...\n"); + + if (args) { + fprintf(stderr, "... %s(", e->kind); + while (args) { + fprintf(stderr, "%s", args == carg ? ">>" : ""); + bstr_print(stderr, args, 1); + fprintf(stderr, "%s%s", args == carg ? "<<" : "", + args->next ? " " : ""); + args = args->next; + } + fprintf(stderr, ")...\n"); + + } + + if (e == NULL) { + fprintf(stderr, + "Usage: EXPR\n" \ + "where: EXPR := TERM [ { and | or } EXPR ]\n" \ + " TERM := [ not ] { MATCH | '(' EXPR ')' }\n" \ + " MATCH := module '(' ARGS ')'\n" \ + " ARGS := ARG1 ARG2 ...\n" \ + "\n" \ + "Example: a(x y) and not (b(x) or c(x y z))\n"); + } else + e->print_usage(stderr); + + return -err; +} + +static inline void free_ematch_err(void) +{ + if (ematch_err) { + free(ematch_err); + ematch_err = NULL; + } +} + +extern int ematch_parse(void); + +int parse_ematch(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n) +{ + begin_argc = ematch_argc = *argc_p; + begin_argv = ematch_argv = *argv_p; + + if (ematch_parse()) { + int err = em_parse_error(EINVAL, NULL, NULL, NULL, + "Parse error"); + free_ematch_err(); + return err; + } + + free_ematch_err(); + + /* undo look ahead by parser */ + ematch_argc++; + ematch_argv--; + + if (ematch_root) { + struct rtattr *tail, *tail_list; + + struct tcf_ematch_tree_hdr hdr = { + .nmatches = flatten_tree(ematch_root, ematch_root), + .progid = TCF_EM_PROG_TC + }; + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_EMATCH_TREE_HDR, &hdr, sizeof(hdr)); + + tail_list = addattr_nest(n, MAX_MSG, TCA_EMATCH_TREE_LIST); + + if (parse_tree(n, ematch_root) < 0) + return -1; + + addattr_nest_end(n, tail_list); + addattr_nest_end(n, tail); + } + + *argc_p = ematch_argc; + *argv_p = ematch_argv; + + return 0; +} + +static int print_ematch_seq(FILE *fd, struct rtattr **tb, int start, + int prefix) +{ + int n, i = start; + struct tcf_ematch_hdr *hdr; + int dlen; + void *data; + + for (;;) { + if (tb[i] == NULL) + return -1; + + dlen = RTA_PAYLOAD(tb[i]) - sizeof(*hdr); + data = (void *) RTA_DATA(tb[i]) + sizeof(*hdr); + + if (dlen < 0) + return -1; + + hdr = RTA_DATA(tb[i]); + + if (hdr->flags & TCF_EM_INVERT) + fprintf(fd, "NOT "); + + if (hdr->kind == 0) { + __u32 ref; + + if (dlen < sizeof(__u32)) + return -1; + + ref = *(__u32 *) data; + fprintf(fd, "(\n"); + for (n = 0; n <= prefix; n++) + fprintf(fd, " "); + if (print_ematch_seq(fd, tb, ref + 1, prefix + 1) < 0) + return -1; + for (n = 0; n < prefix; n++) + fprintf(fd, " "); + fprintf(fd, ") "); + + } else { + struct ematch_util *e; + + e = get_ematch_kind_num(hdr->kind); + if (e == NULL) + fprintf(fd, "[unknown ematch %d]\n", + hdr->kind); + else { + fprintf(fd, "%s(", e->kind); + if (e->print_eopt(fd, hdr, data, dlen) < 0) + return -1; + fprintf(fd, ")\n"); + } + if (hdr->flags & TCF_EM_REL_MASK) + for (n = 0; n < prefix; n++) + fprintf(fd, " "); + } + + switch (hdr->flags & TCF_EM_REL_MASK) { + case TCF_EM_REL_AND: + fprintf(fd, "AND "); + break; + + case TCF_EM_REL_OR: + fprintf(fd, "OR "); + break; + + default: + return 0; + } + + i++; + } + + return 0; +} + +static int print_ematch_list(FILE *fd, struct tcf_ematch_tree_hdr *hdr, + struct rtattr *rta) +{ + int err = -1; + struct rtattr **tb; + + tb = malloc((hdr->nmatches + 1) * sizeof(struct rtattr *)); + if (tb == NULL) + return -1; + + if (hdr->nmatches > 0) { + if (parse_rtattr_nested(tb, hdr->nmatches, rta) < 0) + goto errout; + + fprintf(fd, "\n "); + if (print_ematch_seq(fd, tb, 1, 1) < 0) + goto errout; + } + + err = 0; +errout: + free(tb); + return err; +} + +int print_ematch(FILE *fd, const struct rtattr *rta) +{ + struct rtattr *tb[TCA_EMATCH_TREE_MAX+1]; + struct tcf_ematch_tree_hdr *hdr; + + if (parse_rtattr_nested(tb, TCA_EMATCH_TREE_MAX, rta) < 0) + return -1; + + if (tb[TCA_EMATCH_TREE_HDR] == NULL) { + fprintf(stderr, "Missing ematch tree header\n"); + return -1; + } + + if (tb[TCA_EMATCH_TREE_LIST] == NULL) { + fprintf(stderr, "Missing ematch tree list\n"); + return -1; + } + + if (RTA_PAYLOAD(tb[TCA_EMATCH_TREE_HDR]) < sizeof(*hdr)) { + fprintf(stderr, "Ematch tree header size mismatch\n"); + return -1; + } + + hdr = RTA_DATA(tb[TCA_EMATCH_TREE_HDR]); + + return print_ematch_list(fd, hdr, tb[TCA_EMATCH_TREE_LIST]); +} + +struct bstr *bstr_alloc(const char *text) +{ + struct bstr *b = calloc(1, sizeof(*b)); + + if (b == NULL) + return NULL; + + b->data = strdup(text); + if (b->data == NULL) { + free(b); + return NULL; + } + + b->len = strlen(text); + + return b; +} + +unsigned long bstrtoul(const struct bstr *b) +{ + char *inv = NULL; + unsigned long l; + char buf[b->len+1]; + + memcpy(buf, b->data, b->len); + buf[b->len] = '\0'; + + l = strtoul(buf, &inv, 0); + if (l == ULONG_MAX || inv == buf) + return ULONG_MAX; + + return l; +} + +static void bstr_print(FILE *fd, const struct bstr *b, int ascii) +{ + int i; + char *s = b->data; + + if (ascii) + for (i = 0; i < b->len; i++) + fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.'); + else { + for (i = 0; i < b->len; i++) + fprintf(fd, "%02x", s[i]); + fprintf(fd, "\""); + for (i = 0; i < b->len; i++) + fprintf(fd, "%c", isprint(s[i]) ? s[i] : '.'); + fprintf(fd, "\""); + } +} diff --git a/tc/m_ematch.h b/tc/m_ematch.h new file mode 100644 index 0000000..c4443ee --- /dev/null +++ b/tc/m_ematch.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __TC_EMATCH_H_ +#define __TC_EMATCH_H_ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "utils.h" +#include "tc_util.h" + +#define EMATCHKINDSIZ 16 + +struct bstr { + char *data; + unsigned int len; + int quoted; + struct bstr *next; +}; + +struct bstr *bstr_alloc(const char *text); + +static inline struct bstr *bstr_new(char *data, unsigned int len) +{ + struct bstr *b = calloc(1, sizeof(*b)); + + if (b == NULL) + return NULL; + + b->data = data; + b->len = len; + + return b; +} + +static inline int bstrcmp(const struct bstr *b, const char *text) +{ + int len = strlen(text); + int d = b->len - len; + + if (d == 0) + return strncmp(b->data, text, len); + + return d; +} + +static inline struct bstr *bstr_next(struct bstr *b) +{ + return b->next; +} + +unsigned long bstrtoul(const struct bstr *b); + +struct ematch { + struct bstr *args; + int index; + int inverted; + int relation; + int child_ref; + struct ematch *child; + struct ematch *next; +}; + +static inline struct ematch *new_ematch(struct bstr *args, int inverted) +{ + struct ematch *e = calloc(1, sizeof(*e)); + + if (e == NULL) + return NULL; + + e->args = args; + e->inverted = inverted; + + return e; +} + +void print_ematch_tree(const struct ematch *tree); + +struct ematch_util { + char kind[EMATCHKINDSIZ]; + int kind_num; + int (*parse_eopt)(struct nlmsghdr *, struct tcf_ematch_hdr *, + struct bstr *); + int (*parse_eopt_argv)(struct nlmsghdr *, struct tcf_ematch_hdr *, + int, char **); + int (*print_eopt)(FILE *, struct tcf_ematch_hdr *, void *, int); + void (*print_usage)(FILE *); + struct ematch_util *next; +}; + +static inline int parse_layer(const struct bstr *b) +{ + if (*((char *) b->data) == 'l') + return TCF_LAYER_LINK; + else if (*((char *) b->data) == 'n') + return TCF_LAYER_NETWORK; + else if (*((char *) b->data) == 't') + return TCF_LAYER_TRANSPORT; + else + return INT_MAX; +} + +__attribute__((format(printf, 5, 6))) +int em_parse_error(int err, struct bstr *args, struct bstr *carg, + struct ematch_util *, char *fmt, ...); +int print_ematch(FILE *, const struct rtattr *); +int parse_ematch(int *, char ***, int, struct nlmsghdr *); + +#endif diff --git a/tc/m_estimator.c b/tc/m_estimator.c new file mode 100644 index 0000000..98fc5e7 --- /dev/null +++ b/tc/m_estimator.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_estimator.c Parse/print estimator module options. + * + * 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 "utils.h" +#include "tc_util.h" +#include "tc_common.h" + +static void est_help(void); + +static void est_help(void) +{ + fprintf(stderr, + "Usage: ... estimator INTERVAL TIME-CONST\n" + " INTERVAL is interval between measurements\n" + " TIME-CONST is averaging time constant\n" + "Example: ... est 1sec 8sec\n"); +} + +int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est) +{ + int argc = *p_argc; + char **argv = *p_argv; + unsigned int A, time_const; + + NEXT_ARG(); + if (est->ewma_log) + duparg("estimator", *argv); + if (matches(*argv, "help") == 0) + est_help(); + if (get_time(&A, *argv)) + invarg("estimator", "invalid estimator interval"); + NEXT_ARG(); + if (matches(*argv, "help") == 0) + est_help(); + if (get_time(&time_const, *argv)) + invarg("estimator", "invalid estimator time constant"); + if (tc_setup_estimator(A, time_const, est) < 0) { + fprintf(stderr, "Error: estimator parameters are out of range.\n"); + return -1; + } + if (show_raw) + fprintf(stderr, "[estimator i=%hhd e=%u]\n", est->interval, est->ewma_log); + *p_argc = argc; + *p_argv = argv; + return 0; +} diff --git a/tc/m_gact.c b/tc/m_gact.c new file mode 100644 index 0000000..225ffce --- /dev/null +++ b/tc/m_gact.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_gact.c generic actions module + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + */ + +#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 "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_gact.h> + +/* define to turn on probability stuff */ + +#ifdef CONFIG_GACT_PROB +static const char *prob_n2a(int p) +{ + if (p == PGACT_NONE) + return "none"; + if (p == PGACT_NETRAND) + return "netrand"; + if (p == PGACT_DETERM) + return "determ"; + return "none"; +} +#endif + +static void +explain(void) +{ +#ifdef CONFIG_GACT_PROB + fprintf(stderr, "Usage: ... gact <ACTION> [RAND] [INDEX]\n"); + fprintf(stderr, + "Where: \tACTION := reclassify | drop | continue | pass | pipe |\n" + " \t goto chain <CHAIN_INDEX> | jump <JUMP_COUNT>\n" + "\tRAND := random <RANDTYPE> <ACTION> <VAL>\n" + "\tRANDTYPE := netrand | determ\n" + "\tVAL : = value not exceeding 10000\n" + "\tJUMP_COUNT := Absolute jump from start of action list\n" + "\tINDEX := index value used\n" + "\n"); +#else + fprintf(stderr, "Usage: ... gact <ACTION> [INDEX]\n" + "Where: \tACTION := reclassify | drop | continue | pass | pipe |\n" + " \t goto chain <CHAIN_INDEX> | jump <JUMP_COUNT>\n" + "\tINDEX := index value used\n" + "\tJUMP_COUNT := Absolute jump from start of action list\n" + "\n"); +#endif +} + + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_gact(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + struct tc_gact p = { 0 }; +#ifdef CONFIG_GACT_PROB + int rd = 0; + struct tc_gact_p pp; +#endif + struct rtattr *tail; + + if (argc < 0) + return -1; + + if (!matches(*argv, "gact")) + NEXT_ARG(); + /* we're binding existing gact action to filter by index. */ + if (!matches(*argv, "index")) + goto skip_args; + if (parse_action_control(&argc, &argv, &p.action, false)) + usage(); /* does not return */ + +#ifdef CONFIG_GACT_PROB + if (argc > 0) { + if (matches(*argv, "random") == 0) { + rd = 1; + NEXT_ARG(); + if (matches(*argv, "netrand") == 0) { + NEXT_ARG(); + pp.ptype = PGACT_NETRAND; + } else if (matches(*argv, "determ") == 0) { + NEXT_ARG(); + pp.ptype = PGACT_DETERM; + } else { + fprintf(stderr, "Illegal \"random type\"\n"); + return -1; + } + + if (parse_action_control(&argc, &argv, + &pp.paction, false) == -1) + usage(); + if (get_u16(&pp.pval, *argv, 10)) { + fprintf(stderr, + "Illegal probability val 0x%x\n", + pp.pval); + return -1; + } + if (pp.pval > 10000) { + fprintf(stderr, + "Illegal probability val 0x%x\n", + pp.pval); + return -1; + } + argc--; + argv++; + } else if (matches(*argv, "help") == 0) { + usage(); + } + } +#endif + + if (argc > 0) { + if (matches(*argv, "index") == 0) { +skip_args: + NEXT_ARG(); + if (get_u32(&p.index, *argv, 10)) { + fprintf(stderr, "Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } else if (matches(*argv, "help") == 0) { + usage(); + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_GACT_PARMS, &p, sizeof(p)); +#ifdef CONFIG_GACT_PROB + if (rd) + addattr_l(n, MAX_MSG, TCA_GACT_PROB, &pp, sizeof(pp)); +#endif + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int +print_gact(struct action_util *au, FILE *f, struct rtattr *arg) +{ +#ifdef CONFIG_GACT_PROB + struct tc_gact_p *pp = NULL; + struct tc_gact_p pp_dummy; +#endif + struct tc_gact *p = NULL; + struct rtattr *tb[TCA_GACT_MAX + 1]; + + print_string(PRINT_ANY, "kind", "%s ", "gact"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_GACT_MAX, arg); + + if (tb[TCA_GACT_PARMS] == NULL) { + fprintf(stderr, "Missing gact parameters\n"); + return -1; + } + p = RTA_DATA(tb[TCA_GACT_PARMS]); + + print_action_control(f, "action ", p->action, ""); +#ifdef CONFIG_GACT_PROB + if (tb[TCA_GACT_PROB] != NULL) { + pp = RTA_DATA(tb[TCA_GACT_PROB]); + } else { + /* need to keep consistent output */ + memset(&pp_dummy, 0, sizeof(pp_dummy)); + pp = &pp_dummy; + } + open_json_object("prob"); + print_nl(); + print_string(PRINT_ANY, "random_type", "\t random type %s", + prob_n2a(pp->ptype)); + print_action_control(f, " ", pp->paction, " "); + print_int(PRINT_ANY, "val", "val %d", pp->pval); + close_json_object(); +#endif + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + if (show_stats) { + if (tb[TCA_GACT_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_GACT_TM]); + + print_tm(f, tm); + } + } + print_nl(); + return 0; +} + +struct action_util gact_action_util = { + .id = "gact", + .parse_aopt = parse_gact, + .print_aopt = print_gact, +}; diff --git a/tc/m_gate.c b/tc/m_gate.c new file mode 100644 index 0000000..37afa42 --- /dev/null +++ b/tc/m_gate.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* Copyright 2020 NXP */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <linux/if_ether.h> +#include "utils.h" +#include "rt_names.h" +#include "tc_util.h" +#include "list.h" +#include <linux/tc_act/tc_gate.h> + +struct gate_entry { + struct list_head list; + uint8_t gate_state; + uint32_t interval; + int32_t ipv; + int32_t maxoctets; +}; + +static void explain(void) +{ + fprintf(stderr, + "Usage: gate [ priority PRIO-SPEC ] [ base-time BASE-TIME ]\n" + " [ cycle-time CYCLE-TIME ]\n" + " [ cycle-time-ext CYCLE-TIME-EXT ]\n" + " [ clockid CLOCKID ] [flags FLAGS]\n" + " [ sched-entry GATE0 INTERVAL [ INTERNAL-PRIO-VALUE MAX-OCTETS ] ]\n" + " [ sched-entry GATE1 INTERVAL [ INTERNAL-PRIO-VALUE MAX-OCTETS ] ]\n" + " ......\n" + " [ sched-entry GATEn INTERVAL [ INTERNAL-PRIO-VALUE MAX-OCTETS ] ]\n" + " [ CONTROL ]\n" + " GATEn := open | close\n" + " INTERVAL : nanoseconds period of gate slot\n" + " INTERNAL-PRIO-VALUE : internal priority decide which\n" + " rx queue number direct to.\n" + " default to be -1 which means wildcard.\n" + " MAX-OCTETS : maximum number of MSDU octets that are\n" + " permitted to pas the gate during the\n" + " specified TimeInterval.\n" + " default to be -1 which means wildcard.\n" + " CONTROL := pipe | drop | continue | pass |\n" + " goto chain <CHAIN_INDEX>\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static void explain_entry_format(void) +{ + fprintf(stderr, "Usage: sched-entry <open | close> <interval> [ <interval ipv> <octets max bytes> ]\n"); +} + +static int parse_gate(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n); +static int print_gate(struct action_util *au, FILE *f, struct rtattr *arg); + +struct action_util gate_action_util = { + .id = "gate", + .parse_aopt = parse_gate, + .print_aopt = print_gate, +}; + +static int get_gate_state(__u8 *val, const char *arg) +{ + if (!strcasecmp("OPEN", arg)) { + *val = 1; + return 0; + } + + if (!strcasecmp("CLOSE", arg)) { + *val = 0; + return 0; + } + + return -1; +} + +static struct gate_entry *create_gate_entry(uint8_t gate_state, + uint32_t interval, + int32_t ipv, + int32_t maxoctets) +{ + struct gate_entry *e; + + e = calloc(1, sizeof(*e)); + if (!e) + return NULL; + + e->gate_state = gate_state; + e->interval = interval; + e->ipv = ipv; + e->maxoctets = maxoctets; + + return e; +} + +static int add_gate_list(struct list_head *gate_entries, struct nlmsghdr *n) +{ + struct gate_entry *e; + + list_for_each_entry(e, gate_entries, list) { + struct rtattr *a; + + a = addattr_nest(n, 1024, TCA_GATE_ONE_ENTRY | NLA_F_NESTED); + + if (e->gate_state) + addattr(n, MAX_MSG, TCA_GATE_ENTRY_GATE); + + addattr_l(n, MAX_MSG, TCA_GATE_ENTRY_INTERVAL, + &e->interval, sizeof(e->interval)); + addattr_l(n, MAX_MSG, TCA_GATE_ENTRY_IPV, + &e->ipv, sizeof(e->ipv)); + addattr_l(n, MAX_MSG, TCA_GATE_ENTRY_MAX_OCTETS, + &e->maxoctets, sizeof(e->maxoctets)); + + addattr_nest_end(n, a); + } + + return 0; +} + +static void free_entries(struct list_head *gate_entries) +{ + struct gate_entry *e, *n; + + list_for_each_entry_safe(e, n, gate_entries, list) { + list_del(&e->list); + free(e); + } +} + +static int parse_gate(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + struct tc_gate parm = {.action = TC_ACT_PIPE}; + struct list_head gate_entries; + __s32 clockid = CLOCKID_INVALID; + struct rtattr *tail, *nle; + char **argv = *argv_p; + int argc = *argc_p; + __s64 base_time = 0; + __s64 cycle_time = 0; + __s64 cycle_time_ext = 0; + int entry_num = 0; + char *invalidarg; + __u32 flags = 0; + int prio = -1; + + int err; + + if (matches(*argv, "gate") != 0) + return -1; + + NEXT_ARG(); + if (argc <= 0) + return -1; + + INIT_LIST_HEAD(&gate_entries); + + while (argc > 0) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&parm.index, *argv, 10)) { + invalidarg = "index"; + goto err_arg; + } + } else if (matches(*argv, "priority") == 0) { + NEXT_ARG(); + if (get_s32(&prio, *argv, 0)) { + invalidarg = "priority"; + goto err_arg; + } + } else if (matches(*argv, "base-time") == 0) { + NEXT_ARG(); + if (get_s64(&base_time, *argv, 10) && + get_time64(&base_time, *argv)) { + invalidarg = "base-time"; + goto err_arg; + } + } else if (matches(*argv, "cycle-time") == 0) { + NEXT_ARG(); + if (get_s64(&cycle_time, *argv, 10) && + get_time64(&cycle_time, *argv)) { + invalidarg = "cycle-time"; + goto err_arg; + } + } else if (matches(*argv, "cycle-time-ext") == 0) { + NEXT_ARG(); + if (get_s64(&cycle_time_ext, *argv, 10) && + get_time64(&cycle_time_ext, *argv)) { + invalidarg = "cycle-time-ext"; + goto err_arg; + } + } else if (matches(*argv, "clockid") == 0) { + NEXT_ARG(); + if (get_clockid(&clockid, *argv)) { + invalidarg = "clockid"; + goto err_arg; + } + } else if (matches(*argv, "flags") == 0) { + NEXT_ARG(); + if (get_u32(&flags, *argv, 0)) { + invalidarg = "flags"; + goto err_arg; + } + } else if (matches(*argv, "sched-entry") == 0) { + unsigned int maxoctets_uint = 0; + int32_t maxoctets = -1; + struct gate_entry *e; + uint8_t gate_state = 0; + __s64 interval_s64 = 0; + uint32_t interval = 0; + int32_t ipv = -1; + + if (!NEXT_ARG_OK()) { + explain_entry_format(); + fprintf(stderr, "\"sched-entry\" is incomplete\n"); + free_entries(&gate_entries); + return -1; + } + + NEXT_ARG(); + + if (get_gate_state(&gate_state, *argv)) { + explain_entry_format(); + fprintf(stderr, "\"sched-entry\" is incomplete\n"); + free_entries(&gate_entries); + return -1; + } + + if (!NEXT_ARG_OK()) { + explain_entry_format(); + fprintf(stderr, "\"sched-entry\" is incomplete\n"); + free_entries(&gate_entries); + return -1; + } + + NEXT_ARG(); + + if (get_u32(&interval, *argv, 0) && + get_time64(&interval_s64, *argv)) { + explain_entry_format(); + fprintf(stderr, "\"sched-entry\" is incomplete\n"); + free_entries(&gate_entries); + return -1; + } + + if (interval_s64 > UINT_MAX) { + fprintf(stderr, "\"interval\" is too large\n"); + free_entries(&gate_entries); + return -1; + } else if (interval_s64) { + interval = interval_s64; + } + + if (!NEXT_ARG_OK()) + goto create_entry; + + NEXT_ARG(); + + if (get_s32(&ipv, *argv, 0)) { + PREV_ARG(); + goto create_entry; + } + + if (!gate_state) + ipv = -1; + + if (!NEXT_ARG_OK()) + goto create_entry; + + NEXT_ARG(); + + if (get_s32(&maxoctets, *argv, 0) && + get_size(&maxoctets_uint, *argv)) + PREV_ARG(); + + if (maxoctets_uint > INT_MAX) { + fprintf(stderr, "\"maxoctets\" is too large\n"); + free_entries(&gate_entries); + return -1; + } else if (maxoctets_uint ) { + maxoctets = maxoctets_uint; + } + + if (!gate_state) + maxoctets = -1; + +create_entry: + e = create_gate_entry(gate_state, interval, + ipv, maxoctets); + if (!e) { + fprintf(stderr, "gate: not enough memory\n"); + free_entries(&gate_entries); + return -1; + } + + list_add_tail(&e->list, &gate_entries); + entry_num++; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + argc--; + argv++; + } + + parse_action_control_dflt(&argc, &argv, &parm.action, + false, TC_ACT_PIPE); + + if (!entry_num && !parm.index) { + fprintf(stderr, "gate: must add at least one entry\n"); + return -1; + } + + tail = addattr_nest(n, MAX_MSG, tca_id | NLA_F_NESTED); + addattr_l(n, MAX_MSG, TCA_GATE_PARMS, &parm, sizeof(parm)); + + if (prio != -1) + addattr_l(n, MAX_MSG, TCA_GATE_PRIORITY, &prio, sizeof(prio)); + + if (flags) + addattr_l(n, MAX_MSG, TCA_GATE_FLAGS, &flags, sizeof(flags)); + + if (base_time) + addattr_l(n, MAX_MSG, TCA_GATE_BASE_TIME, + &base_time, sizeof(base_time)); + + if (cycle_time) + addattr_l(n, MAX_MSG, TCA_GATE_CYCLE_TIME, + &cycle_time, sizeof(cycle_time)); + + if (cycle_time_ext) + addattr_l(n, MAX_MSG, TCA_GATE_CYCLE_TIME_EXT, + &cycle_time_ext, sizeof(cycle_time_ext)); + + if (clockid != CLOCKID_INVALID) + addattr_l(n, MAX_MSG, TCA_GATE_CLOCKID, + &clockid, sizeof(clockid)); + + nle = addattr_nest(n, MAX_MSG, TCA_GATE_ENTRY_LIST | NLA_F_NESTED); + err = add_gate_list(&gate_entries, n); + if (err < 0) { + fprintf(stderr, "Could not add entries to netlink message\n"); + free_entries(&gate_entries); + return -1; + } + + addattr_nest_end(n, nle); + addattr_nest_end(n, tail); + free_entries(&gate_entries); + *argc_p = argc; + *argv_p = argv; + + return 0; +err_arg: + invarg(invalidarg, *argv); + free_entries(&gate_entries); + + return -1; +} + +static int print_gate_list(struct rtattr *list) +{ + struct rtattr *item; + int rem; + + rem = RTA_PAYLOAD(list); + + print_string(PRINT_FP, NULL, "%s", _SL_); + print_string(PRINT_FP, NULL, "\tschedule:%s", _SL_); + open_json_array(PRINT_JSON, "schedule"); + + for (item = RTA_DATA(list); + RTA_OK(item, rem); + item = RTA_NEXT(item, rem)) { + struct rtattr *tb[TCA_GATE_ENTRY_MAX + 1]; + __u32 index = 0, interval = 0; + __u8 gate_state = 0; + __s32 ipv = -1, maxoctets = -1; + SPRINT_BUF(buf); + + parse_rtattr_nested(tb, TCA_GATE_ENTRY_MAX, item); + + if (tb[TCA_GATE_ENTRY_INDEX]) + index = rta_getattr_u32(tb[TCA_GATE_ENTRY_INDEX]); + + if (tb[TCA_GATE_ENTRY_GATE]) + gate_state = 1; + + if (tb[TCA_GATE_ENTRY_INTERVAL]) + interval = rta_getattr_u32(tb[TCA_GATE_ENTRY_INTERVAL]); + + if (tb[TCA_GATE_ENTRY_IPV]) + ipv = rta_getattr_s32(tb[TCA_GATE_ENTRY_IPV]); + + if (tb[TCA_GATE_ENTRY_MAX_OCTETS]) + maxoctets = rta_getattr_s32(tb[TCA_GATE_ENTRY_MAX_OCTETS]); + + open_json_object(NULL); + print_uint(PRINT_ANY, "number", "\t number %4u", index); + print_string(PRINT_ANY, "gate_state", "\tgate-state %s ", + gate_state ? "open" : "close"); + + print_uint(PRINT_JSON, "interval", NULL, interval); + + memset(buf, 0, sizeof(buf)); + print_string(PRINT_FP, NULL, "\tinterval %s", + sprint_time64(interval, buf)); + + if (ipv != -1) { + print_uint(PRINT_ANY, "ipv", "\t ipv %-10u", ipv); + } else { + print_int(PRINT_JSON, "ipv", NULL, ipv); + print_string(PRINT_FP, NULL, "\t ipv %s", "wildcard"); + } + + if (maxoctets != -1) { + print_size(PRINT_ANY, "max_octets", "\t max-octets %s", + maxoctets); + } else { + print_string(PRINT_FP, NULL, + "\t max-octets %s", "wildcard"); + print_int(PRINT_JSON, "max_octets", NULL, maxoctets); + } + + close_json_object(); + print_string(PRINT_FP, NULL, "%s", _SL_); + } + + close_json_array(PRINT_ANY, ""); + + return 0; +} + +static int print_gate(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_gate *parm; + struct rtattr *tb[TCA_GATE_MAX + 1]; + __s32 clockid = CLOCKID_INVALID; + __s64 base_time = 0; + __s64 cycle_time = 0; + __s64 cycle_time_ext = 0; + SPRINT_BUF(buf); + int prio = -1; + + if (arg == NULL) + return -1; + + parse_rtattr_nested(tb, TCA_GATE_MAX, arg); + + if (!tb[TCA_GATE_PARMS]) { + fprintf(stderr, "Missing gate parameters\n"); + return -1; + } + + print_string(PRINT_FP, NULL, "%s", "\n"); + + parm = RTA_DATA(tb[TCA_GATE_PARMS]); + + if (tb[TCA_GATE_PRIORITY]) + prio = rta_getattr_s32(tb[TCA_GATE_PRIORITY]); + + if (prio != -1) { + print_int(PRINT_ANY, "priority", "\tpriority %-8d", prio); + } else { + print_string(PRINT_FP, NULL, "\tpriority %s", "wildcard"); + print_int(PRINT_JSON, "priority", NULL, prio); + } + + if (tb[TCA_GATE_CLOCKID]) + clockid = rta_getattr_s32(tb[TCA_GATE_CLOCKID]); + print_string(PRINT_ANY, "clockid", "\tclockid %s", + get_clock_name(clockid)); + + if (tb[TCA_GATE_FLAGS]) { + __u32 flags; + + flags = rta_getattr_u32(tb[TCA_GATE_FLAGS]); + print_0xhex(PRINT_ANY, "flags", "\tflags %#x", flags); + } + + print_string(PRINT_FP, NULL, "%s", "\n"); + + if (tb[TCA_GATE_BASE_TIME]) + base_time = rta_getattr_s64(tb[TCA_GATE_BASE_TIME]); + + memset(buf, 0, sizeof(buf)); + print_string(PRINT_FP, NULL, "\tbase-time %s", + sprint_time64(base_time, buf)); + print_lluint(PRINT_JSON, "base_time", NULL, base_time); + + if (tb[TCA_GATE_CYCLE_TIME]) + cycle_time = rta_getattr_s64(tb[TCA_GATE_CYCLE_TIME]); + + memset(buf, 0, sizeof(buf)); + print_string(PRINT_FP, NULL, + "\tcycle-time %s", sprint_time64(cycle_time, buf)); + print_lluint(PRINT_JSON, "cycle_time", NULL, cycle_time); + + if (tb[TCA_GATE_CYCLE_TIME_EXT]) + cycle_time_ext = rta_getattr_s64(tb[TCA_GATE_CYCLE_TIME_EXT]); + + memset(buf, 0, sizeof(buf)); + print_string(PRINT_FP, NULL, "\tcycle-time-ext %s", + sprint_time64(cycle_time_ext, buf)); + print_lluint(PRINT_JSON, "cycle_time_ext", NULL, cycle_time_ext); + + if (tb[TCA_GATE_ENTRY_LIST]) + print_gate_list(tb[TCA_GATE_ENTRY_LIST]); + + print_action_control(f, "\t", parm->action, ""); + + print_uint(PRINT_ANY, "index", "\n\t index %u", parm->index); + print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt); + + if (show_stats) { + if (tb[TCA_GATE_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_GATE_TM]); + + print_tm(f, tm); + } + } + + print_string(PRINT_FP, NULL, "%s", "\n"); + + return 0; +} diff --git a/tc/m_ife.c b/tc/m_ife.c new file mode 100644 index 0000000..162607c --- /dev/null +++ b/tc/m_ife.c @@ -0,0 +1,331 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_ife.c IFE actions module + * + * Authors: J Hadi Salim (jhs@mojatatu.com) + */ + +#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 <linux/netdevice.h> + +#include "rt_names.h" +#include "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_ife.h> + +static void ife_explain(void) +{ + fprintf(stderr, + "Usage:... ife {decode|encode} [{ALLOW|USE} ATTR] [dst DMAC] [src SMAC] [type TYPE] [CONTROL] [index INDEX]\n" + "\tALLOW := Encode direction. Allows encoding specified metadata\n" + "\t\t e.g \"allow mark\"\n" + "\tUSE := Encode direction. Enforce Static encoding of specified metadata\n" + "\t\t e.g \"use mark 0x12\"\n" + "\tATTR := mark (32-bit), prio (32-bit), tcindex (16-bit)\n" + "\tDMAC := 6 byte Destination MAC address to encode\n" + "\tSMAC := optional 6 byte Source MAC address to encode\n" + "\tTYPE := optional 16 bit ethertype to encode\n" + "\tCONTROL := reclassify|pipe|drop|continue|ok\n" + "\tINDEX := optional IFE table index value used\n" + "encode is used for sending IFE packets\n" + "decode is used for receiving IFE packets\n"); +} + +static void ife_usage(void) +{ + ife_explain(); + exit(-1); +} + +static int parse_ife(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct tc_ife p = { 0 }; + struct rtattr *tail; + struct rtattr *tail2; + char dbuf[ETH_ALEN]; + char sbuf[ETH_ALEN]; + __u16 ife_type = 0; + int user_type = 0; + __u32 ife_prio = 0; + __u32 ife_prio_v = 0; + __u32 ife_mark = 0; + __u32 ife_mark_v = 0; + __u16 ife_tcindex = 0; + __u16 ife_tcindex_v = 0; + char *daddr = NULL; + char *saddr = NULL; + + if (argc <= 0) + return -1; + + while (argc > 0) { + if (matches(*argv, "ife") == 0) { + NEXT_ARG(); + continue; + } else if (matches(*argv, "decode") == 0) { + p.flags = IFE_DECODE; /* readability aid */ + ok++; + } else if (matches(*argv, "encode") == 0) { + p.flags = IFE_ENCODE; + ok++; + } else if (matches(*argv, "allow") == 0) { + NEXT_ARG(); + if (matches(*argv, "mark") == 0) { + ife_mark = IFE_META_SKBMARK; + } else if (matches(*argv, "prio") == 0) { + ife_prio = IFE_META_PRIO; + } else if (matches(*argv, "tcindex") == 0) { + ife_tcindex = IFE_META_TCINDEX; + } else { + invarg("Illegal meta define", *argv); + } + } else if (matches(*argv, "use") == 0) { + NEXT_ARG(); + if (matches(*argv, "mark") == 0) { + NEXT_ARG(); + if (get_u32(&ife_mark_v, *argv, 0)) + invarg("ife mark val is invalid", + *argv); + } else if (matches(*argv, "prio") == 0) { + NEXT_ARG(); + if (get_u32(&ife_prio_v, *argv, 0)) + invarg("ife prio val is invalid", + *argv); + } else if (matches(*argv, "tcindex") == 0) { + NEXT_ARG(); + if (get_u16(&ife_tcindex_v, *argv, 0)) + invarg("ife tcindex val is invalid", + *argv); + } else { + invarg("Illegal meta use type", *argv); + } + } else if (matches(*argv, "type") == 0) { + NEXT_ARG(); + if (get_u16(&ife_type, *argv, 0)) + invarg("ife type is invalid", *argv); + fprintf(stderr, "IFE type 0x%04X\n", ife_type); + user_type = 1; + } else if (matches(*argv, "dst") == 0) { + NEXT_ARG(); + daddr = *argv; + if (sscanf(daddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + dbuf, dbuf + 1, dbuf + 2, + dbuf + 3, dbuf + 4, dbuf + 5) != 6) { + invarg("Invalid mac address", *argv); + } + fprintf(stderr, "dst MAC address <%s>\n", daddr); + + } else if (matches(*argv, "src") == 0) { + NEXT_ARG(); + saddr = *argv; + if (sscanf(saddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + sbuf, sbuf + 1, sbuf + 2, + sbuf + 3, sbuf + 4, sbuf + 5) != 6) { + invarg("Invalid mac address", *argv); + } + fprintf(stderr, "src MAC address <%s>\n", saddr); + } else if (matches(*argv, "help") == 0) { + ife_usage(); + } else { + break; + } + + argc--; + argv++; + } + + parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 0)) { + fprintf(stderr, "ife: Illegal \"index\"\n"); + return -1; + } + ok++; + argc--; + argv++; + } + } + + if (!ok) { + fprintf(stderr, "IFE requires decode/encode specified\n"); + ife_usage(); + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_IFE_PARMS, &p, sizeof(p)); + + if (!(p.flags & IFE_ENCODE)) + goto skip_encode; + + if (daddr) + addattr_l(n, MAX_MSG, TCA_IFE_DMAC, dbuf, ETH_ALEN); + if (user_type) + addattr_l(n, MAX_MSG, TCA_IFE_TYPE, &ife_type, 2); + else + fprintf(stderr, "IFE type 0x%04X\n", ETH_P_IFE); + if (saddr) + addattr_l(n, MAX_MSG, TCA_IFE_SMAC, sbuf, ETH_ALEN); + + tail2 = addattr_nest(n, MAX_MSG, TCA_IFE_METALST); + if (ife_mark || ife_mark_v) { + if (ife_mark_v) + addattr_l(n, MAX_MSG, IFE_META_SKBMARK, &ife_mark_v, 4); + else + addattr_l(n, MAX_MSG, IFE_META_SKBMARK, NULL, 0); + } + if (ife_prio || ife_prio_v) { + if (ife_prio_v) + addattr_l(n, MAX_MSG, IFE_META_PRIO, &ife_prio_v, 4); + else + addattr_l(n, MAX_MSG, IFE_META_PRIO, NULL, 0); + } + if (ife_tcindex || ife_tcindex_v) { + if (ife_tcindex_v) + addattr_l(n, MAX_MSG, IFE_META_TCINDEX, &ife_tcindex_v, + 2); + else + addattr_l(n, MAX_MSG, IFE_META_TCINDEX, NULL, 0); + } + + addattr_nest_end(n, tail2); + +skip_encode: + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_ife(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_ife *p; + struct rtattr *tb[TCA_IFE_MAX + 1]; + __u16 ife_type = 0; + __u32 mmark = 0; + __u16 mtcindex = 0; + __u32 mprio = 0; + int has_optional = 0; + SPRINT_BUF(b2); + + print_string(PRINT_ANY, "kind", "%s ", "ife"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_IFE_MAX, arg); + + if (tb[TCA_IFE_PARMS] == NULL) { + fprintf(stderr, "Missing ife parameters\n"); + return -1; + } + p = RTA_DATA(tb[TCA_IFE_PARMS]); + + print_string(PRINT_ANY, "mode", "%s ", + p->flags & IFE_ENCODE ? "encode" : "decode"); + print_action_control(f, "action ", p->action, " "); + + if (tb[TCA_IFE_TYPE]) { + ife_type = rta_getattr_u16(tb[TCA_IFE_TYPE]); + has_optional = 1; + print_0xhex(PRINT_ANY, "type", "type %#llX ", ife_type); + } + + if (has_optional) + print_string(PRINT_FP, NULL, "%s\t", _SL_); + + if (tb[TCA_IFE_METALST]) { + struct rtattr *metalist[IFE_META_MAX + 1]; + int len = 0; + + parse_rtattr_nested(metalist, IFE_META_MAX, + tb[TCA_IFE_METALST]); + + if (metalist[IFE_META_SKBMARK]) { + len = RTA_PAYLOAD(metalist[IFE_META_SKBMARK]); + if (len) { + mmark = rta_getattr_u32(metalist[IFE_META_SKBMARK]); + print_uint(PRINT_ANY, "mark", "use mark %u ", + mmark); + } else + print_string(PRINT_ANY, "mark", "%s mark ", + "allow"); + } + + if (metalist[IFE_META_TCINDEX]) { + len = RTA_PAYLOAD(metalist[IFE_META_TCINDEX]); + if (len) { + mtcindex = + rta_getattr_u16(metalist[IFE_META_TCINDEX]); + print_uint(PRINT_ANY, "tcindex", + "use tcindex %u ", mtcindex); + } else + print_string(PRINT_ANY, "tcindex", + "%s tcindex ", "allow"); + } + + if (metalist[IFE_META_PRIO]) { + len = RTA_PAYLOAD(metalist[IFE_META_PRIO]); + if (len) { + mprio = rta_getattr_u32(metalist[IFE_META_PRIO]); + print_uint(PRINT_ANY, "prio", "use prio %u ", + mprio); + } else + print_string(PRINT_ANY, "prio", "%s prio ", + "allow"); + } + + } + + if (tb[TCA_IFE_DMAC]) { + has_optional = 1; + print_string(PRINT_ANY, "dst", "dst %s ", + ll_addr_n2a(RTA_DATA(tb[TCA_IFE_DMAC]), + RTA_PAYLOAD(tb[TCA_IFE_DMAC]), 0, b2, + sizeof(b2))); + } + + if (tb[TCA_IFE_SMAC]) { + has_optional = 1; + print_string(PRINT_ANY, "src", "src %s ", + ll_addr_n2a(RTA_DATA(tb[TCA_IFE_SMAC]), + RTA_PAYLOAD(tb[TCA_IFE_SMAC]), 0, b2, + sizeof(b2))); + } + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + + if (show_stats) { + if (tb[TCA_IFE_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_IFE_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util ife_action_util = { + .id = "ife", + .parse_aopt = parse_ife, + .print_aopt = print_ife, +}; diff --git a/tc/m_mirred.c b/tc/m_mirred.c new file mode 100644 index 0000000..e5653e6 --- /dev/null +++ b/tc/m_mirred.c @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_egress.c ingress/egress packet mirror/redir actions module + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + * + * TODO: Add Ingress support + */ + +#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 "utils.h" +#include "tc_util.h" +#include "tc_common.h" +#include <linux/tc_act/tc_mirred.h> + +static void +explain(void) +{ + fprintf(stderr, + "Usage: mirred <DIRECTION> <ACTION> [index INDEX] <dev DEVICENAME>\n" + "where:\n" + "\tDIRECTION := <ingress | egress>\n" + "\tACTION := <mirror | redirect>\n" + "\tINDEX is the specific policy instance id\n" + "\tDEVICENAME is the devicename\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static const char *mirred_n2a(int action) +{ + switch (action) { + case TCA_EGRESS_REDIR: + return "Egress Redirect"; + case TCA_INGRESS_REDIR: + return "Ingress Redirect"; + case TCA_EGRESS_MIRROR: + return "Egress Mirror"; + case TCA_INGRESS_MIRROR: + return "Ingress Mirror"; + default: + return "unknown"; + } +} + +static const char *mirred_direction(int action) +{ + switch (action) { + case TCA_EGRESS_REDIR: + case TCA_EGRESS_MIRROR: + return "egress"; + case TCA_INGRESS_REDIR: + case TCA_INGRESS_MIRROR: + return "ingress"; + default: + return "unknown"; + } +} + +static const char *mirred_action(int action) +{ + switch (action) { + case TCA_EGRESS_REDIR: + case TCA_INGRESS_REDIR: + return "redirect"; + case TCA_EGRESS_MIRROR: + case TCA_INGRESS_MIRROR: + return "mirror"; + default: + return "unknown"; + } +} + +static int +parse_direction(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0, iok = 0, mirror = 0, redir = 0, ingress = 0, egress = 0; + struct tc_mirred p = {}; + struct rtattr *tail; + char d[IFNAMSIZ] = {}; + + while (argc > 0) { + + if (matches(*argv, "action") == 0) { + NEXT_ARG(); + break; + } else if (!egress && matches(*argv, "egress") == 0) { + egress = 1; + if (ingress) { + fprintf(stderr, + "Can't have both egress and ingress\n"); + return -1; + } + NEXT_ARG(); + ok++; + continue; + } else if (!ingress && matches(*argv, "ingress") == 0) { + ingress = 1; + if (egress) { + fprintf(stderr, + "Can't have both ingress and egress\n"); + return -1; + } + NEXT_ARG(); + ok++; + continue; + } else { + + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 10)) { + fprintf(stderr, "Illegal \"index\"\n"); + return -1; + } + iok++; + if (!ok) { + argc--; + argv++; + break; + } + } else if (!ok) { + fprintf(stderr, + "was expecting egress or ingress (%s)\n", + *argv); + break; + + } else if (!mirror && matches(*argv, "mirror") == 0) { + mirror = 1; + if (redir) { + fprintf(stderr, + "Can't have both mirror and redir\n"); + return -1; + } + p.eaction = egress ? TCA_EGRESS_MIRROR : + TCA_INGRESS_MIRROR; + p.action = TC_ACT_PIPE; + ok++; + } else if (!redir && matches(*argv, "redirect") == 0) { + redir = 1; + if (mirror) { + fprintf(stderr, + "Can't have both mirror and redir\n"); + return -1; + } + p.eaction = egress ? TCA_EGRESS_REDIR : + TCA_INGRESS_REDIR; + p.action = TC_ACT_STOLEN; + ok++; + } else if ((redir || mirror) && + matches(*argv, "dev") == 0) { + NEXT_ARG(); + if (strlen(d)) + duparg("dev", *argv); + + strncpy(d, *argv, sizeof(d)-1); + argc--; + argv++; + + break; + + } + } + + NEXT_ARG(); + } + + if (!ok && !iok) + return -1; + + if (d[0]) { + int idx; + + ll_init_map(&rth); + + idx = ll_name_to_index(d); + if (!idx) + return nodev(d); + + p.ifindex = idx; + } + + + if (p.eaction == TCA_EGRESS_MIRROR || p.eaction == TCA_INGRESS_MIRROR) + parse_action_control_dflt(&argc, &argv, &p.action, false, + TC_ACT_PIPE); + + if (argc) { + if (iok && matches(*argv, "index") == 0) { + fprintf(stderr, "mirred: Illegal double index\n"); + return -1; + } + + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 10)) { + fprintf(stderr, + "mirred: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_MIRRED_PARMS, &p, sizeof(p)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + + +static int +parse_mirred(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 0) { + fprintf(stderr, "mirred bad argument count %d\n", argc); + return -1; + } + + if (matches(*argv, "mirred") == 0) { + NEXT_ARG(); + } else { + fprintf(stderr, "mirred bad argument %s\n", *argv); + return -1; + } + + + if (matches(*argv, "egress") == 0 || matches(*argv, "ingress") == 0 || + matches(*argv, "index") == 0) { + int ret = parse_direction(a, &argc, &argv, tca_id, n); + + if (ret == 0) { + *argc_p = argc; + *argv_p = argv; + return 0; + } + + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + fprintf(stderr, "mirred option not supported %s\n", *argv); + } + + return -1; + +} + +static int +print_mirred(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_mirred *p; + struct rtattr *tb[TCA_MIRRED_MAX + 1]; + const char *dev; + + print_string(PRINT_ANY, "kind", "%s ", "mirred"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_MIRRED_MAX, arg); + + if (tb[TCA_MIRRED_PARMS] == NULL) { + fprintf(stderr, "Missing mirred parameters\n"); + return -1; + } + p = RTA_DATA(tb[TCA_MIRRED_PARMS]); + + dev = ll_index_to_name(p->ifindex); + if (dev == 0) { + fprintf(stderr, "Cannot find device %d\n", p->ifindex); + return -1; + } + + print_string(PRINT_FP, NULL, "(%s", mirred_n2a(p->eaction)); + print_string(PRINT_JSON, "mirred_action", NULL, + mirred_action(p->eaction)); + print_string(PRINT_JSON, "direction", NULL, + mirred_direction(p->eaction)); + print_string(PRINT_ANY, "to_dev", " to device %s)", dev); + print_action_control(f, " ", p->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\tindex %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + + if (show_stats) { + if (tb[TCA_MIRRED_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_MIRRED_TM]); + + print_tm(f, tm); + } + } + print_nl(); + return 0; +} + +struct action_util mirred_action_util = { + .id = "mirred", + .parse_aopt = parse_mirred, + .print_aopt = print_mirred, +}; diff --git a/tc/m_mpls.c b/tc/m_mpls.c new file mode 100644 index 0000000..dda4680 --- /dev/null +++ b/tc/m_mpls.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* Copyright (C) 2019 Netronome Systems, Inc. */ + +#include <linux/if_ether.h> +#include <linux/tc_act/tc_mpls.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "utils.h" +#include "rt_names.h" +#include "tc_util.h" + +static const char * const action_names[] = { + [TCA_MPLS_ACT_POP] = "pop", + [TCA_MPLS_ACT_PUSH] = "push", + [TCA_MPLS_ACT_MODIFY] = "modify", + [TCA_MPLS_ACT_DEC_TTL] = "dec_ttl", + [TCA_MPLS_ACT_MAC_PUSH] = "mac_push", +}; + +static void explain(void) +{ + fprintf(stderr, + "Usage: mpls pop [ protocol MPLS_PROTO ] [CONTROL]\n" + " mpls push [ protocol MPLS_PROTO ] [ label MPLS_LABEL ] [ tc MPLS_TC ]\n" + " [ ttl MPLS_TTL ] [ bos MPLS_BOS ] [CONTROL]\n" + " mpls mac_push [ protocol MPLS_PROTO ] [ label MPLS_LABEL ] [ tc MPLS_TC ]\n" + " [ ttl MPLS_TTL ] [ bos MPLS_BOS ] [CONTROL]\n" + " mpls modify [ label MPLS_LABEL ] [ tc MPLS_TC ] [ ttl MPLS_TTL ]\n" + " [ bos MPLS_BOS ] [CONTROL]\n" + " for pop, MPLS_PROTO is next header of packet - e.g. ip or mpls_uc\n" + " for push and mac_push, MPLS_PROTO is one of mpls_uc or mpls_mc\n" + " with default: mpls_uc\n" + " CONTROL := reclassify | pipe | drop | continue | pass |\n" + " goto chain <CHAIN_INDEX>\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static bool can_modify_mpls_fields(unsigned int action) +{ + return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH || + action == TCA_MPLS_ACT_MODIFY; +} + +static bool can_set_ethtype(unsigned int action) +{ + return action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH || + action == TCA_MPLS_ACT_POP; +} + +static bool is_valid_label(__u32 label) +{ + return label <= 0xfffff; +} + +static bool check_double_action(unsigned int action, const char *arg) +{ + if (!action) + return false; + + fprintf(stderr, + "Error: got \"%s\" but action already set to \"%s\"\n", + arg, action_names[action]); + explain(); + return true; +} + +static int parse_mpls(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + struct tc_mpls parm = {}; + __u32 label = 0xffffffff; + unsigned int action = 0; + char **argv = *argv_p; + struct rtattr *tail; + int argc = *argc_p; + __u16 proto = 0; + __u8 bos = 0xff; + __u8 tc = 0xff; + __u8 ttl = 0; + + if (matches(*argv, "mpls") != 0) + return -1; + + NEXT_ARG(); + + if (strcmp(*argv, "index") == 0) + goto skip_args; + + while (argc > 0) { + if (matches(*argv, "pop") == 0) { + if (check_double_action(action, *argv)) + return -1; + action = TCA_MPLS_ACT_POP; + } else if (matches(*argv, "push") == 0) { + if (check_double_action(action, *argv)) + return -1; + action = TCA_MPLS_ACT_PUSH; + } else if (matches(*argv, "modify") == 0) { + if (check_double_action(action, *argv)) + return -1; + action = TCA_MPLS_ACT_MODIFY; + } else if (matches(*argv, "mac_push") == 0) { + if (check_double_action(action, *argv)) + return -1; + action = TCA_MPLS_ACT_MAC_PUSH; + } else if (matches(*argv, "dec_ttl") == 0) { + if (check_double_action(action, *argv)) + return -1; + action = TCA_MPLS_ACT_DEC_TTL; + } else if (matches(*argv, "label") == 0) { + if (!can_modify_mpls_fields(action)) + invarg("only valid for push, mac_push and modify", + *argv); + NEXT_ARG(); + if (get_u32(&label, *argv, 0) || !is_valid_label(label)) + invarg("label must be <=0xFFFFF", *argv); + } else if (matches(*argv, "tc") == 0) { + if (!can_modify_mpls_fields(action)) + invarg("only valid for push, mac_push and modify", + *argv); + NEXT_ARG(); + if (get_u8(&tc, *argv, 0) || (tc & ~0x7)) + invarg("tc field is 3 bits max", *argv); + } else if (matches(*argv, "ttl") == 0) { + if (!can_modify_mpls_fields(action)) + invarg("only valid for push, mac_push and modify", + *argv); + NEXT_ARG(); + if (get_u8(&ttl, *argv, 0) || !ttl) + invarg("ttl must be >0 and <=255", *argv); + } else if (matches(*argv, "bos") == 0) { + if (!can_modify_mpls_fields(action)) + invarg("only valid for push, mac_push and modify", + *argv); + NEXT_ARG(); + if (get_u8(&bos, *argv, 0) || (bos & ~0x1)) + invarg("bos must be 0 or 1", *argv); + } else if (matches(*argv, "protocol") == 0) { + if (!can_set_ethtype(action)) + invarg("only valid for push, mac_push and pop", + *argv); + NEXT_ARG(); + if (ll_proto_a2n(&proto, *argv)) + invarg("protocol is invalid", *argv); + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + NEXT_ARG_FWD(); + } + + if (!action) + incomplete_command(); + + parse_action_control_dflt(&argc, &argv, &parm.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { +skip_args: + NEXT_ARG(); + if (get_u32(&parm.index, *argv, 10)) + invarg("illegal index", *argv); + NEXT_ARG_FWD(); + } + } + + if (action == TCA_MPLS_ACT_PUSH && label == 0xffffffff) + missarg("label"); + + if ((action == TCA_MPLS_ACT_PUSH || action == TCA_MPLS_ACT_MAC_PUSH) && + proto && + proto != htons(ETH_P_MPLS_UC) && proto != htons(ETH_P_MPLS_MC)) { + fprintf(stderr, + "invalid %spush protocol \"0x%04x\" - use mpls_(uc|mc)\n", + action == TCA_MPLS_ACT_MAC_PUSH ? "mac_" : "", + ntohs(proto)); + return -1; + } + + if (action == TCA_MPLS_ACT_POP && !proto) + missarg("protocol"); + + parm.m_action = action; + tail = addattr_nest(n, MAX_MSG, tca_id | NLA_F_NESTED); + addattr_l(n, MAX_MSG, TCA_MPLS_PARMS, &parm, sizeof(parm)); + if (label != 0xffffffff) + addattr_l(n, MAX_MSG, TCA_MPLS_LABEL, &label, sizeof(label)); + if (proto) + addattr_l(n, MAX_MSG, TCA_MPLS_PROTO, &proto, sizeof(proto)); + if (tc != 0xff) + addattr8(n, MAX_MSG, TCA_MPLS_TC, tc); + if (ttl) + addattr8(n, MAX_MSG, TCA_MPLS_TTL, ttl); + if (bos != 0xff) + addattr8(n, MAX_MSG, TCA_MPLS_BOS, bos); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_mpls(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_MPLS_MAX + 1]; + struct tc_mpls *parm; + SPRINT_BUF(b1); + __u32 val; + + print_string(PRINT_ANY, "kind", "%s ", "mpls"); + if (!arg) + return 0; + + parse_rtattr_nested(tb, TCA_MPLS_MAX, arg); + + if (!tb[TCA_MPLS_PARMS]) { + fprintf(stderr, "[NULL mpls parameters]\n"); + return -1; + } + parm = RTA_DATA(tb[TCA_MPLS_PARMS]); + + print_string(PRINT_ANY, "mpls_action", " %s", + action_names[parm->m_action]); + + switch (parm->m_action) { + case TCA_MPLS_ACT_POP: + if (tb[TCA_MPLS_PROTO]) { + __u16 proto; + + proto = rta_getattr_u16(tb[TCA_MPLS_PROTO]); + print_string(PRINT_ANY, "protocol", " protocol %s", + ll_proto_n2a(proto, b1, sizeof(b1))); + } + break; + case TCA_MPLS_ACT_PUSH: + case TCA_MPLS_ACT_MAC_PUSH: + if (tb[TCA_MPLS_PROTO]) { + __u16 proto; + + proto = rta_getattr_u16(tb[TCA_MPLS_PROTO]); + print_string(PRINT_ANY, "protocol", " protocol %s", + ll_proto_n2a(proto, b1, sizeof(b1))); + } + /* Fallthrough */ + case TCA_MPLS_ACT_MODIFY: + if (tb[TCA_MPLS_LABEL]) { + val = rta_getattr_u32(tb[TCA_MPLS_LABEL]); + print_uint(PRINT_ANY, "label", " label %u", val); + } + if (tb[TCA_MPLS_TC]) { + val = rta_getattr_u8(tb[TCA_MPLS_TC]); + print_uint(PRINT_ANY, "tc", " tc %u", val); + } + if (tb[TCA_MPLS_BOS]) { + val = rta_getattr_u8(tb[TCA_MPLS_BOS]); + print_uint(PRINT_ANY, "bos", " bos %u", val); + } + if (tb[TCA_MPLS_TTL]) { + val = rta_getattr_u8(tb[TCA_MPLS_TTL]); + print_uint(PRINT_ANY, "ttl", " ttl %u", val); + } + break; + } + print_action_control(f, " ", parm->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", parm->index); + print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt); + + if (show_stats) { + if (tb[TCA_MPLS_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_MPLS_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util mpls_action_util = { + .id = "mpls", + .parse_aopt = parse_mpls, + .print_aopt = print_mpls, +}; diff --git a/tc/m_nat.c b/tc/m_nat.c new file mode 100644 index 0000000..95b3558 --- /dev/null +++ b/tc/m_nat.c @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_nat.c NAT module + * + * Authors: Herbert Xu <herbert@gondor.apana.org.au> + */ + +#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 "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_nat.h> + +static void +explain(void) +{ + fprintf(stderr, "Usage: ... nat NAT\n" + "NAT := DIRECTION OLD NEW\n" + "DIRECTION := { ingress | egress }\n" + "OLD := PREFIX\n" + "NEW := ADDRESS\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_nat_args(int *argc_p, char ***argv_p, struct tc_nat *sel) +{ + int argc = *argc_p; + char **argv = *argv_p; + inet_prefix addr; + + if (argc <= 0) + return -1; + + if (matches(*argv, "egress") == 0) + sel->flags |= TCA_NAT_FLAG_EGRESS; + else if (matches(*argv, "ingress") != 0) + goto bad_val; + + NEXT_ARG(); + + if (get_prefix_1(&addr, *argv, AF_INET)) + goto bad_val; + + sel->old_addr = addr.data[0]; + sel->mask = htonl(~0u << (32 - addr.bitlen)); + + NEXT_ARG(); + + if (get_prefix_1(&addr, *argv, AF_INET)) + goto bad_val; + + sel->new_addr = addr.data[0]; + + argc--; + argv++; + + *argc_p = argc; + *argv_p = argv; + return 0; + +bad_val: + return -1; +} + +static int +parse_nat(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n) +{ + struct tc_nat sel = {}; + + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "nat") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "index") == 0) { + goto skip_args; + } else if (parse_nat_args(&argc, &argv, &sel)) { + fprintf(stderr, "Illegal nat construct (%s)\n", + *argv); + explain(); + return -1; + } + ok++; + continue; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + } + + if (!ok) { + explain(); + return -1; + } + + parse_action_control_dflt(&argc, &argv, &sel.action, false, TC_ACT_OK); + + if (argc) { + if (matches(*argv, "index") == 0) { +skip_args: + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "Nat: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_NAT_PARMS, &sel, sizeof(sel)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int +print_nat(struct action_util *au, FILE * f, struct rtattr *arg) +{ + struct tc_nat *sel; + struct rtattr *tb[TCA_NAT_MAX + 1]; + SPRINT_BUF(buf1); + SPRINT_BUF(buf2); + int len; + + print_string(PRINT_ANY, "type", " %s ", "nat"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_NAT_MAX, arg); + + if (tb[TCA_NAT_PARMS] == NULL) { + fprintf(stderr, "Missing nat parameters\n"); + return -1; + } + sel = RTA_DATA(tb[TCA_NAT_PARMS]); + + len = ffs(sel->mask); + len = len ? 33 - len : 0; + + print_string(PRINT_ANY, "direction", "%s", + sel->flags & TCA_NAT_FLAG_EGRESS ? "egress" : "ingress"); + + snprintf(buf2, sizeof(buf2), "%s/%d", + format_host_r(AF_INET, 4, &sel->old_addr, buf1, sizeof(buf1)), + len); + print_string(PRINT_ANY, "old_addr", " %s", buf2); + print_string(PRINT_ANY, "new_addr", " %s", + format_host_r(AF_INET, 4, &sel->new_addr, buf1, sizeof(buf1))); + + print_action_control(f, " ", sel->action, ""); + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", sel->index); + print_int(PRINT_ANY, "ref", " ref %d", sel->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", sel->bindcnt); + + if (show_stats) { + if (tb[TCA_NAT_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_NAT_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util nat_action_util = { + .id = "nat", + .parse_aopt = parse_nat, + .print_aopt = print_nat, +}; diff --git a/tc/m_pedit.c b/tc/m_pedit.c new file mode 100644 index 0000000..32f0341 --- /dev/null +++ b/tc/m_pedit.c @@ -0,0 +1,863 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit.c generic packet editor actions module + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + * + * TODO: + * 1) Big endian broken in some spots + * 2) A lot of this stuff was added on the fly; get a big double-double + * and clean it up at some point. + */ + +#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 <dlfcn.h> +#include "utils.h" +#include "tc_util.h" +#include "m_pedit.h" +#include "rt_names.h" + +static struct m_pedit_util *pedit_list; +static int pedit_debug; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... pedit munge [ex] <MUNGE> [CONTROL]\n" + "Where: MUNGE := <RAW>|<LAYERED>\n" + "\t<RAW>:= <OFFSETC>[ATC]<CMD>\n \t\tOFFSETC:= offset <offval> <u8|u16|u32>\n" + "\t\tATC:= at <atval> offmask <maskval> shift <shiftval>\n" + "\t\tNOTE: offval is byte offset, must be multiple of 4\n" + "\t\tNOTE: maskval is a 32 bit hex number\n \t\tNOTE: shiftval is a shift value\n" + "\t\tCMD:= clear | invert | set <setval> | add <addval> | decrement | retain\n" + "\t<LAYERED>:= ip <ipdata> | ip6 <ip6data>\n" + " \t\t| udp <udpdata> | tcp <tcpdata> | icmp <icmpdata>\n" + "\tCONTROL:= reclassify | pipe | drop | continue | pass |\n" + "\t goto chain <CHAIN_INDEX>\n" + "\tNOTE: if 'ex' is set, extended functionality will be supported (kernel >= 4.11)\n" + "For Example usage look at the examples directory\n"); + +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static int pedit_parse_nopopt(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, + struct m_pedit_key *tkey) +{ + int argc = *argc_p; + char **argv = *argv_p; + + if (argc) { + fprintf(stderr, + "Unknown action hence option \"%s\" is unparsable\n", + *argv); + return -1; + } + + return 0; + +} + +static struct m_pedit_util *get_pedit_kind(const char *str) +{ + static void *pBODY; + void *dlh; + char buf[256]; + struct m_pedit_util *p; + + for (p = pedit_list; p; p = p->next) { + if (strcmp(p->id, str) == 0) + return p; + } + + snprintf(buf, sizeof(buf), "p_%s.so", str); + dlh = dlopen(buf, RTLD_LAZY); + if (dlh == NULL) { + dlh = pBODY; + if (dlh == NULL) { + dlh = pBODY = dlopen(NULL, RTLD_LAZY); + if (dlh == NULL) + goto noexist; + } + } + + snprintf(buf, sizeof(buf), "p_pedit_%s", str); + p = dlsym(dlh, buf); + if (p == NULL) + goto noexist; + +reg: + p->next = pedit_list; + pedit_list = p; + return p; + +noexist: + p = calloc(1, sizeof(*p)); + if (p) { + strlcpy(p->id, str, sizeof(p->id)); + p->parse_peopt = pedit_parse_nopopt; + goto reg; + } + return p; +} + +static int pack_key(struct m_pedit_sel *_sel, struct m_pedit_key *tkey) +{ + struct tc_pedit_sel *sel = &_sel->sel; + struct m_pedit_key_ex *keys_ex = _sel->keys_ex; + int hwm = sel->nkeys; + + if (hwm >= MAX_OFFS) + return -1; + + if (tkey->off % 4) { + fprintf(stderr, "offsets MUST be in 32 bit boundaries\n"); + return -1; + } + + sel->keys[hwm].val = tkey->val; + sel->keys[hwm].mask = tkey->mask; + sel->keys[hwm].off = tkey->off; + sel->keys[hwm].at = tkey->at; + sel->keys[hwm].offmask = tkey->offmask; + sel->keys[hwm].shift = tkey->shift; + + if (_sel->extended) { + keys_ex[hwm].htype = tkey->htype; + keys_ex[hwm].cmd = tkey->cmd; + } else { + if (tkey->htype != TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK || + tkey->cmd != TCA_PEDIT_KEY_EX_CMD_SET) { + fprintf(stderr, + "Munge parameters not supported. Use 'pedit ex munge ...'.\n"); + return -1; + } + } + + sel->nkeys++; + return 0; +} + +static int pack_key32(__u32 retain, struct m_pedit_sel *sel, + struct m_pedit_key *tkey) +{ + if (tkey->off > (tkey->off & ~3)) { + fprintf(stderr, + "pack_key32: 32 bit offsets must begin in 32bit boundaries\n"); + return -1; + } + + tkey->val = htonl(tkey->val & retain); + tkey->mask = htonl(tkey->mask | ~retain); + return pack_key(sel, tkey); +} + +static int pack_key16(__u32 retain, struct m_pedit_sel *sel, + struct m_pedit_key *tkey) +{ + int ind, stride; + __u32 m[4] = { 0x0000FFFF, 0xFF0000FF, 0xFFFF0000 }; + + if (tkey->val > 0xFFFF || tkey->mask > 0xFFFF) { + fprintf(stderr, "pack_key16 bad value\n"); + return -1; + } + + ind = tkey->off & 3; + + if (ind == 3) { + fprintf(stderr, "pack_key16 bad index value %d\n", ind); + return -1; + } + + stride = 8 * (2 - ind); + tkey->val = htonl((tkey->val & retain) << stride); + tkey->mask = htonl(((tkey->mask | ~retain) << stride) | m[ind]); + + tkey->off &= ~3; + + if (pedit_debug) + printf("pack_key16: Final val %08x mask %08x\n", + tkey->val, tkey->mask); + return pack_key(sel, tkey); +} + +static int pack_key8(__u32 retain, struct m_pedit_sel *sel, + struct m_pedit_key *tkey) +{ + int ind, stride; + __u32 m[4] = { 0x00FFFFFF, 0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00 }; + + if (tkey->val > 0xFF || tkey->mask > 0xFF) { + fprintf(stderr, "pack_key8 bad value (val %x mask %x\n", + tkey->val, tkey->mask); + return -1; + } + + ind = tkey->off & 3; + + stride = 8 * (3 - ind); + tkey->val = htonl((tkey->val & retain) << stride); + tkey->mask = htonl(((tkey->mask | ~retain) << stride) | m[ind]); + + tkey->off &= ~3; + + if (pedit_debug) + printf("pack_key8: Final word off %d val %08x mask %08x\n", + tkey->off, tkey->val, tkey->mask); + return pack_key(sel, tkey); +} + +static int pack_mac(struct m_pedit_sel *sel, struct m_pedit_key *tkey, + __u8 *mac) +{ + int ret = 0; + + if (!(tkey->off & 0x3)) { + tkey->mask = 0; + tkey->val = ntohl(*((__u32 *)mac)); + ret |= pack_key32(~0, sel, tkey); + + tkey->off += 4; + tkey->mask = 0; + tkey->val = ntohs(*((__u16 *)&mac[4])); + ret |= pack_key16(~0, sel, tkey); + } else if (!(tkey->off & 0x1)) { + tkey->mask = 0; + tkey->val = ntohs(*((__u16 *)mac)); + ret |= pack_key16(~0, sel, tkey); + + tkey->off += 4; + tkey->mask = 0; + tkey->val = ntohl(*((__u32 *)(mac + 2))); + ret |= pack_key32(~0, sel, tkey); + } else { + fprintf(stderr, + "pack_mac: mac offsets must begin in 32bit or 16bit boundaries\n"); + return -1; + } + + return ret; +} + +static int pack_ipv6(struct m_pedit_sel *sel, struct m_pedit_key *tkey, + __u32 *ipv6) +{ + int ret = 0; + int i; + + if (tkey->off & 0x3) { + fprintf(stderr, + "pack_ipv6: IPv6 offsets must begin in 32bit boundaries\n"); + return -1; + } + + for (i = 0; i < 4; i++) { + tkey->mask = 0; + tkey->val = ntohl(ipv6[i]); + + ret = pack_key32(~0, sel, tkey); + if (ret) + return ret; + + tkey->off += 4; + } + + return 0; +} + +static int parse_val(int *argc_p, char ***argv_p, __u32 *val, int type) +{ + int argc = *argc_p; + char **argv = *argv_p; + + if (argc <= 0) + return -1; + + if (type == TINT) + return get_integer((int *)val, *argv, 0); + + if (type == TU32) + return get_u32(val, *argv, 0); + + if (type == TIPV4) { + inet_prefix addr; + + if (get_prefix_1(&addr, *argv, AF_INET)) + return -1; + + *val = addr.data[0]; + return 0; + } + + if (type == TIPV6) { + inet_prefix addr; + + if (get_prefix_1(&addr, *argv, AF_INET6)) + return -1; + + memcpy(val, addr.data, addr.bytelen); + + return 0; + } + + if (type == TMAC) { +#define MAC_ALEN 6 + int ret = ll_addr_a2n((char *)val, MAC_ALEN, *argv); + + if (ret == MAC_ALEN) + return 0; + } + + return -1; +} + +int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type, __u32 retain, + struct m_pedit_sel *sel, struct m_pedit_key *tkey, int flags) +{ + __u32 mask[4] = { 0 }; + __u32 val[4] = { 0 }; + __u32 *m = &mask[0]; + __u32 *v = &val[0]; + __u32 o = 0xFF; + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc <= 0) + return -1; + + if (pedit_debug) + printf("parse_cmd argc %d %s offset %d length %d\n", + argc, *argv, tkey->off, len); + + if (len == 2) + o = 0xFFFF; + if (len == 4) + o = 0xFFFFFFFF; + + if (matches(*argv, "invert") == 0) { + *v = *m = o; + } else if (matches(*argv, "set") == 0 || + matches(*argv, "add") == 0) { + if (matches(*argv, "add") == 0) + tkey->cmd = TCA_PEDIT_KEY_EX_CMD_ADD; + + if (!sel->extended && tkey->cmd) + goto non_ext_only_set_cmd; + + NEXT_ARG(); + if (parse_val(&argc, &argv, val, type)) + return -1; + } else if (matches(*argv, "decrement") == 0) { + if ((flags & PEDIT_ALLOW_DEC) == 0) { + fprintf(stderr, + "decrement command is not supported for this field\n"); + return -1; + } + + if (!sel->extended) + goto non_ext_only_set_cmd; + + tkey->cmd = TCA_PEDIT_KEY_EX_CMD_ADD; + *v = retain; /* decrement by overflow */ + } else if (matches(*argv, "preserve") == 0) { + retain = 0; + } else { + if (matches(*argv, "clear") != 0) + return -1; + } + + argc--; + argv++; + + if (argc && matches(*argv, "retain") == 0) { + NEXT_ARG(); + if (parse_val(&argc, &argv, &retain, TU32)) + return -1; + argc--; + argv++; + } + + if (len > 4 && retain != ~0) { + fprintf(stderr, + "retain is not supported for fields longer the 32 bits\n"); + return -1; + } + + if (type == TMAC) { + res = pack_mac(sel, tkey, (__u8 *)val); + goto done; + } + + if (type == TIPV6) { + res = pack_ipv6(sel, tkey, val); + goto done; + } + + tkey->val = *v; + tkey->mask = *m; + + if (type == TIPV4) + tkey->val = ntohl(tkey->val); + + if (len == 1) { + res = pack_key8(retain, sel, tkey); + goto done; + } + if (len == 2) { + res = pack_key16(retain, sel, tkey); + goto done; + } + if (len == 4) { + res = pack_key32(retain, sel, tkey); + goto done; + } + + return -1; +done: + if (pedit_debug) + printf("parse_cmd done argc %d %s offset %d length %d\n", + argc, *argv, tkey->off, len); + *argc_p = argc; + *argv_p = argv; + return res; + +non_ext_only_set_cmd: + fprintf(stderr, + "Non extended mode. only 'set' command is supported\n"); + return -1; +} + +static int parse_offset(int *argc_p, char ***argv_p, struct m_pedit_sel *sel, + struct m_pedit_key *tkey) +{ + int off; + __u32 len, retain; + int argc = *argc_p; + char **argv = *argv_p; + int res = -1; + + if (argc <= 0) + return -1; + + if (get_integer(&off, *argv, 0)) + return -1; + tkey->off = off; + + argc--; + argv++; + + if (argc <= 0) + return -1; + + if (matches(*argv, "u32") == 0) { + len = 4; + retain = 0xFFFFFFFF; + goto done; + } + if (matches(*argv, "u16") == 0) { + len = 2; + retain = 0xffff; + goto done; + } + if (matches(*argv, "u8") == 0) { + len = 1; + retain = 0xff; + goto done; + } + + return -1; + +done: + + NEXT_ARG(); + + /* [at <someval> offmask <maskval> shift <shiftval>] */ + if (matches(*argv, "at") == 0) { + + __u32 atv = 0, offmask = 0x0, shift = 0; + + NEXT_ARG(); + if (get_u32(&atv, *argv, 0)) + return -1; + tkey->at = atv; + + NEXT_ARG(); + + if (get_u32(&offmask, *argv, 16)) + return -1; + tkey->offmask = offmask; + + NEXT_ARG(); + + if (get_u32(&shift, *argv, 0)) + return -1; + tkey->shift = shift; + + NEXT_ARG(); + } + + res = parse_cmd(&argc, &argv, len, TU32, retain, sel, tkey, 0); + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int parse_munge(int *argc_p, char ***argv_p, struct m_pedit_sel *sel) +{ + struct m_pedit_key tkey = {}; + int argc = *argc_p; + char **argv = *argv_p; + int res = -1; + + if (argc <= 0) + return -1; + + if (matches(*argv, "offset") == 0) { + NEXT_ARG(); + res = parse_offset(&argc, &argv, sel, &tkey); + goto done; + } else { + char k[FILTER_NAMESZ]; + struct m_pedit_util *p = NULL; + + strncpy(k, *argv, sizeof(k) - 1); + + if (argc > 0) { + p = get_pedit_kind(k); + if (p == NULL) + goto bad_val; + NEXT_ARG(); + res = p->parse_peopt(&argc, &argv, sel, &tkey); + if (res < 0) { + fprintf(stderr, "bad pedit parsing\n"); + goto bad_val; + } + goto done; + } + } + +bad_val: + return -1; + +done: + + *argc_p = argc; + *argv_p = argv; + return res; +} + +static int pedit_keys_ex_getattr(struct rtattr *attr, + struct m_pedit_key_ex *keys_ex, int n) +{ + struct rtattr *i; + int rem = RTA_PAYLOAD(attr); + struct rtattr *tb[TCA_PEDIT_KEY_EX_MAX + 1]; + struct m_pedit_key_ex *k = keys_ex; + + for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + if (!n) + return -1; + + if (i->rta_type != TCA_PEDIT_KEY_EX) + return -1; + + parse_rtattr_nested(tb, TCA_PEDIT_KEY_EX_MAX, i); + + k->htype = rta_getattr_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]); + k->cmd = rta_getattr_u16(tb[TCA_PEDIT_KEY_EX_CMD]); + + k++; + n--; + } + + return !!n; +} + +static int pedit_keys_ex_addattr(struct m_pedit_sel *sel, struct nlmsghdr *n) +{ + struct m_pedit_key_ex *k = sel->keys_ex; + struct rtattr *keys_start; + int i; + + if (!sel->extended) + return 0; + + keys_start = addattr_nest(n, MAX_MSG, TCA_PEDIT_KEYS_EX | NLA_F_NESTED); + + for (i = 0; i < sel->sel.nkeys; i++) { + struct rtattr *key_start; + + key_start = addattr_nest(n, MAX_MSG, + TCA_PEDIT_KEY_EX | NLA_F_NESTED); + + if (addattr16(n, MAX_MSG, TCA_PEDIT_KEY_EX_HTYPE, k->htype) || + addattr16(n, MAX_MSG, TCA_PEDIT_KEY_EX_CMD, k->cmd)) { + return -1; + } + + addattr_nest_end(n, key_start); + + k++; + } + + addattr_nest_end(n, keys_start); + + return 0; +} + +static int parse_pedit(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + struct m_pedit_sel sel = {}; + + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct rtattr *tail; + + while (argc > 0) { + if (pedit_debug > 1) + fprintf(stderr, "while pedit (%d:%s)\n", argc, *argv); + if (matches(*argv, "pedit") == 0) { + NEXT_ARG(); + ok++; + + if (matches(*argv, "ex") == 0) { + if (ok > 1) { + fprintf(stderr, + "'ex' must be before first 'munge'\n"); + explain(); + return -1; + } + sel.extended = true; + NEXT_ARG(); + } + + continue; + } else if (matches(*argv, "help") == 0) { + usage(); + } else if (matches(*argv, "munge") == 0) { + if (!ok) { + fprintf(stderr, "Bad pedit construct (%s)\n", + *argv); + explain(); + return -1; + } + NEXT_ARG(); + + if (parse_munge(&argc, &argv, &sel)) { + fprintf(stderr, "Bad pedit construct (%s)\n", + *argv); + explain(); + return -1; + } + ok++; + } else { + break; + } + + } + + if (!ok) { + explain(); + return -1; + } + + parse_action_control_dflt(&argc, &argv, &sel.sel.action, false, TC_ACT_OK); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.sel.index, *argv, 10)) { + fprintf(stderr, "Pedit: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + if (!sel.extended) { + addattr_l(n, MAX_MSG, TCA_PEDIT_PARMS, &sel, + sizeof(sel.sel) + + sel.sel.nkeys * sizeof(struct tc_pedit_key)); + } else { + addattr_l(n, MAX_MSG, TCA_PEDIT_PARMS_EX, &sel, + sizeof(sel.sel) + + sel.sel.nkeys * sizeof(struct tc_pedit_key)); + + pedit_keys_ex_addattr(&sel, n); + } + + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static const char * const pedit_htype_str[] = { + [TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK] = "", + [TCA_PEDIT_KEY_EX_HDR_TYPE_ETH] = "eth", + [TCA_PEDIT_KEY_EX_HDR_TYPE_IP4] = "ipv4", + [TCA_PEDIT_KEY_EX_HDR_TYPE_IP6] = "ipv6", + [TCA_PEDIT_KEY_EX_HDR_TYPE_TCP] = "tcp", + [TCA_PEDIT_KEY_EX_HDR_TYPE_UDP] = "udp", +}; + +static int print_pedit_location(FILE *f, + enum pedit_header_type htype, __u32 off) +{ + char *buf = NULL; + int rc; + + if (htype != TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK) { + if (htype < ARRAY_SIZE(pedit_htype_str)) + rc = asprintf(&buf, "%s", pedit_htype_str[htype]); + else + rc = asprintf(&buf, "unknown(%d)", htype); + if (rc < 0) + return rc; + print_string(PRINT_ANY, "htype", "%s", buf); + print_int(PRINT_ANY, "offset", "%+d", off); + } else { + print_string(PRINT_JSON, "htype", NULL, "network"); + print_int(PRINT_ANY, "offset", "%d", off); + } + + free(buf); + return 0; +} + +static int print_pedit(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_pedit_sel *sel; + struct rtattr *tb[TCA_PEDIT_MAX + 1]; + struct m_pedit_key_ex *keys_ex = NULL; + int err; + + print_string(PRINT_ANY, "kind", " %s ", "pedit"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_PEDIT_MAX, arg); + + if (!tb[TCA_PEDIT_PARMS] && !tb[TCA_PEDIT_PARMS_EX]) { + fprintf(stderr, "Missing pedit parameters\n"); + return -1; + } + + if (tb[TCA_PEDIT_PARMS]) { + sel = RTA_DATA(tb[TCA_PEDIT_PARMS]); + } else { + int err; + + sel = RTA_DATA(tb[TCA_PEDIT_PARMS_EX]); + + if (!tb[TCA_PEDIT_KEYS_EX]) { + fprintf(f, "Netlink error\n"); + return -1; + } + + keys_ex = calloc(sel->nkeys, sizeof(*keys_ex)); + if (!keys_ex) { + fprintf(f, "Out of memory\n"); + return -1; + } + + err = pedit_keys_ex_getattr(tb[TCA_PEDIT_KEYS_EX], keys_ex, + sel->nkeys); + if (err) { + fprintf(f, "Netlink error\n"); + + free(keys_ex); + return -1; + } + } + + print_action_control(f, "action ", sel->action, " "); + print_uint(PRINT_ANY, "nkeys", "keys %d\n", sel->nkeys); + print_uint(PRINT_ANY, "index", " \t index %u", sel->index); + print_int(PRINT_ANY, "ref", " ref %d", sel->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", sel->bindcnt); + + if (show_stats) { + if (tb[TCA_PEDIT_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_PEDIT_TM]); + + print_tm(f, tm); + } + } + open_json_array(PRINT_JSON, "keys"); + if (sel->nkeys) { + int i; + struct tc_pedit_key *key = sel->keys; + struct m_pedit_key_ex *key_ex = keys_ex; + + for (i = 0; i < sel->nkeys; i++, key++) { + enum pedit_header_type htype = + TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK; + enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET; + + if (keys_ex) { + htype = key_ex->htype; + cmd = key_ex->cmd; + + key_ex++; + } + + open_json_object(NULL); + print_uint(PRINT_FP, NULL, "\n\t key #%d at ", i); + + err = print_pedit_location(f, htype, key->off); + if (err) { + free(keys_ex); + return err; + } + + /* In FP, report the "set" command as "val" to keep + * backward compatibility. Report the true name in JSON. + */ + print_string(PRINT_FP, NULL, ": %s", + cmd ? "add" : "val"); + print_string(PRINT_JSON, "cmd", NULL, + cmd ? "add" : "set"); + print_hex(PRINT_ANY, "val", " %08x", + (unsigned int)ntohl(key->val)); + print_hex(PRINT_ANY, "mask", " mask %08x", + (unsigned int)ntohl(key->mask)); + close_json_object(); + } + } else { + fprintf(f, "\npedit %x keys %d is not LEGIT", sel->index, + sel->nkeys); + } + close_json_array(PRINT_JSON, " "); + + print_nl(); + + free(keys_ex); + return 0; +} + +struct action_util pedit_action_util = { + .id = "pedit", + .parse_aopt = parse_pedit, + .print_aopt = print_pedit, +}; diff --git a/tc/m_pedit.h b/tc/m_pedit.h new file mode 100644 index 0000000..8f3771e --- /dev/null +++ b/tc/m_pedit.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit.h generic packet editor actions module + * + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + * + */ + +#ifndef _ACT_PEDIT_H_ +#define _ACT_PEDIT_H_ 1 + +#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 "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_pedit.h> + +#define MAX_OFFS 128 + +#define TIPV4 1 +#define TIPV6 2 +#define TINT 3 +#define TU32 4 +#define TMAC 5 + +#define RU32 0xFFFFFFFF +#define RU16 0xFFFF +#define RU8 0xFF + +#define PEDITKINDSIZ 16 + +enum m_pedit_flags { + PEDIT_ALLOW_DEC = 1<<0, +}; + +struct m_pedit_key { + __u32 mask; /* AND */ + __u32 val; /*XOR */ + __u32 off; /*offset */ + __u32 at; + __u32 offmask; + __u32 shift; + + enum pedit_header_type htype; + enum pedit_cmd cmd; +}; + +struct m_pedit_key_ex { + enum pedit_header_type htype; + enum pedit_cmd cmd; +}; + +struct m_pedit_sel { + struct tc_pedit_sel sel; + struct tc_pedit_key keys[MAX_OFFS]; + struct m_pedit_key_ex keys_ex[MAX_OFFS]; + bool extended; +}; + +struct m_pedit_util { + struct m_pedit_util *next; + char id[PEDITKINDSIZ]; + int (*parse_peopt)(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, + struct m_pedit_key *tkey); +}; + +int parse_cmd(int *argc_p, char ***argv_p, __u32 len, int type, + __u32 retain, + struct m_pedit_sel *sel, struct m_pedit_key *tkey, int flags); +#endif diff --git a/tc/m_police.c b/tc/m_police.c new file mode 100644 index 0000000..46c39a8 --- /dev/null +++ b/tc/m_police.c @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_police.c Parse/print policing module options. + * + * 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 "utils.h" +#include "tc_util.h" + +static int act_parse_police(struct action_util *a, int *argc_p, + char ***argv_p, int tca_id, struct nlmsghdr *n); +static int print_police(struct action_util *a, FILE *f, struct rtattr *tb); + +struct action_util police_action_util = { + .id = "police", + .parse_aopt = act_parse_police, + .print_aopt = print_police, +}; + +static void usage(void) +{ + fprintf(stderr, + "Usage: ... police [ rate BPS burst BYTES[/BYTES] ] \n" + " [ pkts_rate RATE pkts_burst PACKETS ] [ mtu BYTES[/BYTES] ]\n" + " [ peakrate BPS ] [ avrate BPS ] [ overhead BYTES ]\n" + " [ linklayer TYPE ] [ CONTROL ]\n" + "Where: CONTROL := conform-exceed <EXCEEDACT>[/NOTEXCEEDACT]\n" + " Define how to handle packets which exceed (<EXCEEDACT>)\n" + " or conform (<NOTEXCEEDACT>) the configured bandwidth limit.\n" + " EXCEEDACT/NOTEXCEEDACT := { pipe | ok | reclassify | drop | continue |\n" + " goto chain <CHAIN_INDEX> }\n"); + exit(-1); +} + +static int act_parse_police(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + int res = -1; + int ok = 0; + struct tc_police p = { .action = TC_POLICE_RECLASSIFY }; + __u32 rtab[256]; + __u32 ptab[256]; + __u32 avrate = 0; + int presult = 0; + unsigned buffer = 0, mtu = 0, mpu = 0; + unsigned short overhead = 0; + unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */ + int Rcell_log = -1, Pcell_log = -1; + struct rtattr *tail; + __u64 rate64 = 0, prate64 = 0; + __u64 pps64 = 0, ppsburst64 = 0; + + if (a) /* new way of doing things */ + NEXT_ARG(); + + if (argc <= 0) + return -1; + + while (argc > 0) { + + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 10)) + invarg("index", *argv); + } else if (matches(*argv, "burst") == 0 || + strcmp(*argv, "buffer") == 0 || + strcmp(*argv, "maxburst") == 0) { + NEXT_ARG(); + if (buffer) + duparg("buffer/burst", *argv); + if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) + invarg("buffer", *argv); + } else if (strcmp(*argv, "mtu") == 0 || + strcmp(*argv, "minburst") == 0) { + NEXT_ARG(); + if (mtu) + duparg("mtu/minburst", *argv); + if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) + invarg("mtu", *argv); + } else if (strcmp(*argv, "mpu") == 0) { + NEXT_ARG(); + if (mpu) + duparg("mpu", *argv); + if (get_size(&mpu, *argv)) + invarg("mpu", *argv); + } else if (strcmp(*argv, "rate") == 0) { + NEXT_ARG(); + if (rate64) + duparg("rate", *argv); + if (get_rate64(&rate64, *argv)) + invarg("rate", *argv); + } else if (strcmp(*argv, "avrate") == 0) { + NEXT_ARG(); + if (avrate) + duparg("avrate", *argv); + if (get_rate(&avrate, *argv)) + invarg("avrate", *argv); + } else if (matches(*argv, "peakrate") == 0) { + NEXT_ARG(); + if (prate64) + duparg("peakrate", *argv); + if (get_rate64(&prate64, *argv)) + invarg("peakrate", *argv); + } else if (matches(*argv, "reclassify") == 0 || + matches(*argv, "drop") == 0 || + matches(*argv, "shot") == 0 || + matches(*argv, "continue") == 0 || + matches(*argv, "pass") == 0 || + matches(*argv, "ok") == 0 || + matches(*argv, "pipe") == 0 || + matches(*argv, "goto") == 0) { + if (!parse_action_control(&argc, &argv, &p.action, false)) + goto action_ctrl_ok; + return -1; + } else if (strcmp(*argv, "conform-exceed") == 0) { + NEXT_ARG(); + if (!parse_action_control_slash(&argc, &argv, &p.action, + &presult, true)) + goto action_ctrl_ok; + return -1; + } else if (matches(*argv, "overhead") == 0) { + NEXT_ARG(); + if (get_u16(&overhead, *argv, 10)) + invarg("overhead", *argv); + } else if (matches(*argv, "linklayer") == 0) { + NEXT_ARG(); + if (get_linklayer(&linklayer, *argv)) + invarg("linklayer", *argv); + } else if (matches(*argv, "pkts_rate") == 0) { + NEXT_ARG(); + if (pps64) + duparg("pkts_rate", *argv); + if (get_u64(&pps64, *argv, 10)) + invarg("pkts_rate", *argv); + } else if (matches(*argv, "pkts_burst") == 0) { + NEXT_ARG(); + if (ppsburst64) + duparg("pkts_burst", *argv); + if (get_u64(&ppsburst64, *argv, 10)) + invarg("pkts_burst", *argv); + } else if (strcmp(*argv, "help") == 0) { + usage(); + } else { + break; + } + NEXT_ARG_FWD(); +action_ctrl_ok: + ok++; + } + + if (!ok) + return -1; + + if (rate64 && avrate) + return -1; + + /* Must at least do late binding, use TB or ewma policing */ + if (!rate64 && !avrate && !p.index && !mtu && !pps64) { + fprintf(stderr, "'rate' or 'avrate' or 'mtu' or 'pkts_rate' MUST be specified.\n"); + return -1; + } + + /* When the TB policer is used, burst is required */ + if (rate64 && !buffer && !avrate) { + fprintf(stderr, "'burst' requires 'rate'.\n"); + return -1; + } + + /* When the packets TB policer is used, pkts_burst is required */ + if (pps64 && !ppsburst64) { + fprintf(stderr, "'pkts_burst' requires 'pkts_rate'.\n"); + return -1; + } + + /* forbid rate and pkts_rate in same action */ + if (pps64 && rate64) { + fprintf(stderr, "'rate' and 'pkts_rate' are not allowed in same action.\n"); + return -1; + } + + if (prate64) { + if (!rate64) { + fprintf(stderr, "'peakrate' requires 'rate'.\n"); + return -1; + } + if (!mtu) { + fprintf(stderr, "'mtu' is required, if 'peakrate' is requested.\n"); + return -1; + } + } + + if (rate64) { + p.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64; + p.rate.mpu = mpu; + p.rate.overhead = overhead; + if (tc_calc_rtable_64(&p.rate, rtab, Rcell_log, mtu, + linklayer, rate64) < 0) { + fprintf(stderr, "POLICE: failed to calculate rate table.\n"); + return -1; + } + p.burst = tc_calc_xmittime(rate64, buffer); + } + p.mtu = mtu; + if (prate64) { + p.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64; + p.peakrate.mpu = mpu; + p.peakrate.overhead = overhead; + if (tc_calc_rtable_64(&p.peakrate, ptab, Pcell_log, mtu, + linklayer, prate64) < 0) { + fprintf(stderr, "POLICE: failed to calculate peak rate table.\n"); + return -1; + } + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_POLICE_TBF, &p, sizeof(p)); + if (rate64) { + addattr_l(n, MAX_MSG, TCA_POLICE_RATE, rtab, 1024); + if (rate64 >= (1ULL << 32)) + addattr64(n, MAX_MSG, TCA_POLICE_RATE64, rate64); + } + if (prate64) { + addattr_l(n, MAX_MSG, TCA_POLICE_PEAKRATE, ptab, 1024); + if (prate64 >= (1ULL << 32)) + addattr64(n, MAX_MSG, TCA_POLICE_PEAKRATE64, prate64); + } + if (avrate) + addattr32(n, MAX_MSG, TCA_POLICE_AVRATE, avrate); + if (presult) + addattr32(n, MAX_MSG, TCA_POLICE_RESULT, presult); + + if (pps64) { + addattr64(n, MAX_MSG, TCA_POLICE_PKTRATE64, pps64); + ppsburst64 = tc_calc_xmittime(pps64, ppsburst64); + addattr64(n, MAX_MSG, TCA_POLICE_PKTBURST64, ppsburst64); + } + + addattr_nest_end(n, tail); + res = 0; + + *argc_p = argc; + *argv_p = argv; + return res; +} + +int parse_police(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n) +{ + return act_parse_police(NULL, argc_p, argv_p, tca_id, n); +} + +static int print_police(struct action_util *a, FILE *f, struct rtattr *arg) +{ + SPRINT_BUF(b2); + struct tc_police *p; + struct rtattr *tb[TCA_POLICE_MAX+1]; + unsigned int buffer; + unsigned int linklayer; + __u64 rate64, prate64; + __u64 pps64, ppsburst64; + + print_string(PRINT_JSON, "kind", "%s", "police"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_POLICE_MAX, arg); + + if (tb[TCA_POLICE_TBF] == NULL) { + fprintf(stderr, "[NULL police tbf]"); + return -1; + } +#ifndef STOOPID_8BYTE + if (RTA_PAYLOAD(tb[TCA_POLICE_TBF]) < sizeof(*p)) { + fprintf(stderr, "[truncated police tbf]"); + return -1; + } +#endif + p = RTA_DATA(tb[TCA_POLICE_TBF]); + + rate64 = p->rate.rate; + if (tb[TCA_POLICE_RATE64] && + RTA_PAYLOAD(tb[TCA_POLICE_RATE64]) >= sizeof(rate64)) + rate64 = rta_getattr_u64(tb[TCA_POLICE_RATE64]); + + print_hex(PRINT_FP, NULL, " police 0x%x ", p->index); + print_uint(PRINT_JSON, "index", NULL, p->index); + tc_print_rate(PRINT_FP, NULL, "rate %s ", rate64); + buffer = tc_calc_xmitsize(rate64, p->burst); + print_size(PRINT_FP, NULL, "burst %s ", buffer); + print_size(PRINT_FP, NULL, "mtu %s ", p->mtu); + if (show_raw) + print_hex(PRINT_FP, NULL, "[%08x] ", p->burst); + + prate64 = p->peakrate.rate; + if (tb[TCA_POLICE_PEAKRATE64] && + RTA_PAYLOAD(tb[TCA_POLICE_PEAKRATE64]) >= sizeof(prate64)) + prate64 = rta_getattr_u64(tb[TCA_POLICE_PEAKRATE64]); + + if (prate64) + tc_print_rate(PRINT_FP, NULL, "peakrate %s ", prate64); + + if (tb[TCA_POLICE_AVRATE]) + tc_print_rate(PRINT_FP, NULL, "avrate %s ", + rta_getattr_u32(tb[TCA_POLICE_AVRATE])); + + if ((tb[TCA_POLICE_PKTRATE64] && + RTA_PAYLOAD(tb[TCA_POLICE_PKTRATE64]) >= sizeof(pps64)) && + (tb[TCA_POLICE_PKTBURST64] && + RTA_PAYLOAD(tb[TCA_POLICE_PKTBURST64]) >= sizeof(ppsburst64))) { + pps64 = rta_getattr_u64(tb[TCA_POLICE_PKTRATE64]); + ppsburst64 = rta_getattr_u64(tb[TCA_POLICE_PKTBURST64]); + ppsburst64 = tc_calc_xmitsize(pps64, ppsburst64); + print_u64(PRINT_ANY, "pkts_rate", "pkts_rate %llu ", pps64); + print_u64(PRINT_ANY, "pkts_burst", "pkts_burst %llu ", ppsburst64); + } + + print_action_control(f, "action ", p->action, ""); + + if (tb[TCA_POLICE_RESULT]) { + __u32 action = rta_getattr_u32(tb[TCA_POLICE_RESULT]); + + print_action_control(f, "/", action, " "); + } else { + print_string(PRINT_FP, NULL, " ", NULL); + } + + print_size(PRINT_ANY, "overhead", "overhead %s ", p->rate.overhead); + linklayer = (p->rate.linklayer & TC_LINKLAYER_MASK); + if (linklayer > TC_LINKLAYER_ETHERNET || show_details) + print_string(PRINT_ANY, "linklayer", "linklayer %s ", + sprint_linklayer(linklayer, b2)); + print_nl(); + print_int(PRINT_ANY, "ref", "\tref %d ", p->refcnt); + print_int(PRINT_ANY, "bind", "bind %d ", p->bindcnt); + if (show_stats) { + if (tb[TCA_POLICE_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_POLICE_TM]); + + print_tm(f, tm); + } + } + print_nl(); + + + return 0; +} + +int tc_print_police(FILE *f, struct rtattr *arg) +{ + return print_police(&police_action_util, f, arg); +} diff --git a/tc/m_sample.c b/tc/m_sample.c new file mode 100644 index 0000000..769de14 --- /dev/null +++ b/tc/m_sample.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_sample.c ingress/egress packet sampling module + * + * Authors: Yotam Gigi <yotamg@mellanox.com> + */ + +#include <stdio.h> +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" +#include <linux/tc_act/tc_sample.h> + +static void explain(void) +{ + fprintf(stderr, + "Usage: sample SAMPLE_CONF\n" + "where:\n" + "\tSAMPLE_CONF := SAMPLE_PARAMS | SAMPLE_INDEX\n" + "\tSAMPLE_PARAMS := rate RATE group GROUP [trunc SIZE] [SAMPLE_INDEX]\n" + "\tSAMPLE_INDEX := index INDEX\n" + "\tRATE := The ratio of packets observed at the data source to the samples generated.\n" + "\tGROUP := the psample sampling group\n" + "\tSIZE := the truncation size\n" + "\tINDEX := integer index of the sample action\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static int parse_sample(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + struct tc_sample p = { 0 }; + bool trunc_set = false; + bool group_set = false; + bool rate_set = false; + char **argv = *argv_p; + struct rtattr *tail; + int argc = *argc_p; + __u32 trunc; + __u32 group; + __u32 rate; + + if (argc <= 1) { + fprintf(stderr, "sample bad argument count %d\n", argc); + usage(); + return -1; + } + + if (matches(*argv, "sample") == 0) { + NEXT_ARG(); + } else { + fprintf(stderr, "sample bad argument %s\n", *argv); + return -1; + } + + while (argc > 0) { + if (matches(*argv, "rate") == 0) { + NEXT_ARG(); + if (get_u32(&rate, *argv, 10) != 0) { + fprintf(stderr, "Illegal rate %s\n", *argv); + usage(); + return -1; + } + rate_set = true; + } else if (matches(*argv, "group") == 0) { + NEXT_ARG(); + if (get_u32(&group, *argv, 10) != 0) { + fprintf(stderr, "Illegal group num %s\n", + *argv); + usage(); + return -1; + } + group_set = true; + } else if (matches(*argv, "trunc") == 0) { + NEXT_ARG(); + if (get_u32(&trunc, *argv, 10) != 0) { + fprintf(stderr, "Illegal truncation size %s\n", + *argv); + usage(); + return -1; + } + trunc_set = true; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + + NEXT_ARG_FWD(); + } + + parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 10)) { + fprintf(stderr, "sample: Illegal \"index\"\n"); + return -1; + } + NEXT_ARG_FWD(); + } + } + + if (!p.index && !group_set) { + fprintf(stderr, "param \"group\" not set\n"); + usage(); + } + + if (!p.index && !rate_set) { + fprintf(stderr, "param \"rate\" not set\n"); + usage(); + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_SAMPLE_PARMS, &p, sizeof(p)); + if (rate_set) + addattr32(n, MAX_MSG, TCA_SAMPLE_RATE, rate); + if (group_set) + addattr32(n, MAX_MSG, TCA_SAMPLE_PSAMPLE_GROUP, group); + if (trunc_set) + addattr32(n, MAX_MSG, TCA_SAMPLE_TRUNC_SIZE, trunc); + + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_sample(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_SAMPLE_MAX + 1]; + struct tc_sample *p; + + print_string(PRINT_ANY, "kind", "%s ", "sample"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_SAMPLE_MAX, arg); + + if (!tb[TCA_SAMPLE_PARMS] || !tb[TCA_SAMPLE_RATE] || + !tb[TCA_SAMPLE_PSAMPLE_GROUP]) { + fprintf(stderr, "Missing sample parameters\n"); + return -1; + } + p = RTA_DATA(tb[TCA_SAMPLE_PARMS]); + + print_uint(PRINT_ANY, "rate", "rate 1/%u ", + rta_getattr_u32(tb[TCA_SAMPLE_RATE])); + print_uint(PRINT_ANY, "group", "group %u", + rta_getattr_u32(tb[TCA_SAMPLE_PSAMPLE_GROUP])); + + if (tb[TCA_SAMPLE_TRUNC_SIZE]) + print_uint(PRINT_ANY, "trunc_size", " trunc_size %u", + rta_getattr_u32(tb[TCA_SAMPLE_TRUNC_SIZE])); + + print_action_control(f, " ", p->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + + if (show_stats) { + if (tb[TCA_SAMPLE_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_SAMPLE_TM]); + + print_tm(f, tm); + } + } + print_nl(); + return 0; +} + +struct action_util sample_action_util = { + .id = "sample", + .parse_aopt = parse_sample, + .print_aopt = print_sample, +}; diff --git a/tc/m_simple.c b/tc/m_simple.c new file mode 100644 index 0000000..fe2bca2 --- /dev/null +++ b/tc/m_simple.c @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_simple.c simple action + * + * Authors: J Hadi Salim <jhs@mojatatu.com> + * + * Pedagogical example. Adds a string that will be printed every time + * the simple instance is hit. + * Use this as a skeleton action and keep modifying it to meet your needs. + * Look at linux/tc_act/tc_defact.h for the different components ids and + * definitions used in this actions + * + * example use, yell "Incoming ICMP!" every time you see an incoming ICMP on + * eth0. Steps are: + * 1) Add an ingress qdisc point to eth0 + * 2) Start a chain on ingress of eth0 that first matches ICMP then invokes + * the simple action to shout. + * 3) display stats and show that no packet has been seen by the action + * 4) Send one ping packet to google (expect to receive a response back) + * 5) grep the logs to see the logged message + * 6) display stats again and observe increment by 1 + * + hadi@noma1:$ tc qdisc add dev eth0 ingress + hadi@noma1:$tc filter add dev eth0 parent ffff: protocol ip prio 5 \ + u32 match ip protocol 1 0xff flowid 1:1 action simple "Incoming ICMP" + + hadi@noma1:$ sudo tc -s filter ls dev eth0 parent ffff: + filter protocol ip pref 5 u32 + filter protocol ip pref 5 u32 fh 800: ht divisor 1 + filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 + match 00010000/00ff0000 at 8 + action order 1: Simple <Incoming ICMP> + index 4 ref 1 bind 1 installed 29 sec used 29 sec + Action statistics: + Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + backlog 0b 0p requeues 0 + + + hadi@noma1$ ping -c 1 www.google.ca + PING www.google.ca (74.125.225.120) 56(84) bytes of data. + 64 bytes from ord08s08-in-f24.1e100.net (74.125.225.120): icmp_req=1 ttl=53 time=31.3 ms + + --- www.google.ca ping statistics --- + 1 packets transmitted, 1 received, 0% packet loss, time 0ms + rtt min/avg/max/mdev = 31.316/31.316/31.316/0.000 ms + + hadi@noma1$ dmesg | grep simple + [135354.473951] simple: Incoming ICMP_1 + + hadi@noma1$ sudo tc/tc -s filter ls dev eth0 parent ffff: + filter protocol ip pref 5 u32 + filter protocol ip pref 5 u32 fh 800: ht divisor 1 + filter protocol ip pref 5 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:1 + match 00010000/00ff0000 at 8 + action order 1: Simple <Incoming ICMP> + index 4 ref 1 bind 1 installed 206 sec used 67 sec + Action statistics: + Sent 84 bytes 1 pkt (dropped 0, overlimits 0 requeues 0) + backlog 0b 0p requeues 0 +*/ + +#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 "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_defact.h> + +#ifndef SIMP_MAX_DATA +#define SIMP_MAX_DATA 32 +#endif +static void explain(void) +{ + fprintf(stderr, + "Usage:... simple [sdata STRING] [index INDEX] [CONTROL]\n" + "\tSTRING being an arbitrary string\n" + "\tINDEX := optional index value used\n" + "\tCONTROL := reclassify|pipe|drop|continue|ok\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_simple(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, + struct nlmsghdr *n) +{ + struct tc_defact sel = {}; + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct rtattr *tail; + char *simpdata = NULL; + + while (argc > 0) { + if (matches(*argv, "simple") == 0) { + NEXT_ARG(); + } else if (matches(*argv, "sdata") == 0) { + NEXT_ARG(); + ok += 1; + simpdata = *argv; + argc--; + argv++; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + } + + parse_action_control_dflt(&argc, &argv, &sel.action, false, + TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "simple: Illegal \"index\" (%s)\n", + *argv); + return -1; + } + ok += 1; + argc--; + argv++; + } + } + + if (!ok) { + explain(); + return -1; + } + + if (simpdata && (strlen(simpdata) > (SIMP_MAX_DATA - 1))) { + fprintf(stderr, "simple: Illegal string len %zu <%s>\n", + strlen(simpdata), simpdata); + return -1; + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_DEF_PARMS, &sel, sizeof(sel)); + if (simpdata) + addattr_l(n, MAX_MSG, TCA_DEF_DATA, simpdata, SIMP_MAX_DATA); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_simple(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_defact *sel; + struct rtattr *tb[TCA_DEF_MAX + 1]; + char *simpdata; + + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_DEF_MAX, arg); + + if (tb[TCA_DEF_PARMS] == NULL) { + fprintf(stderr, "Missing simple parameters\n"); + return -1; + } + sel = RTA_DATA(tb[TCA_DEF_PARMS]); + + if (tb[TCA_DEF_DATA] == NULL) { + fprintf(stderr, "Missing simple string\n"); + return -1; + } + + simpdata = RTA_DATA(tb[TCA_DEF_DATA]); + + fprintf(f, "Simple <%s>\n", simpdata); + fprintf(f, "\t index %u ref %d bind %d", sel->index, + sel->refcnt, sel->bindcnt); + + if (show_stats) { + if (tb[TCA_DEF_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_DEF_TM]); + + print_tm(f, tm); + } + } + print_nl(); + + return 0; +} + +struct action_util simple_action_util = { + .id = "simple", + .parse_aopt = parse_simple, + .print_aopt = print_simple, +}; diff --git a/tc/m_skbedit.c b/tc/m_skbedit.c new file mode 100644 index 0000000..d55a612 --- /dev/null +++ b/tc/m_skbedit.c @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * m_skbedit.c SKB Editing module + * + * Copyright (c) 2008, Intel Corporation. + * + * Authors: Alexander Duyck <alexander.h.duyck@intel.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_skbedit.h> +#include <linux/if_packet.h> + +static void explain(void) +{ + fprintf(stderr, "Usage: ... skbedit <[QM] [PM] [MM] [PT] [IF]>\n" + "QM = queue_mapping QUEUE_MAPPING\n" + "PM = priority PRIORITY\n" + "MM = mark MARK[/MASK]\n" + "PT = ptype PACKETYPE\n" + "IF = inheritdsfield\n" + "PACKETYPE = is one of:\n" + " host, otherhost, broadcast, multicast\n" + "QUEUE_MAPPING = device transmit queue to use\n" + "PRIORITY = classID to assign to priority field\n" + "MARK = firewall mark to set\n" + "MASK = mask applied to firewall mark (0xffffffff by default)\n" + "note: inheritdsfield maps DS field to skb->priority\n"); +} + +static void +usage(void) +{ + explain(); + exit(-1); +} + +static int +parse_skbedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, + struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct rtattr *tail; + unsigned int tmp; + __u16 queue_mapping, ptype; + __u32 flags = 0, priority, mark, mask; + __u64 pure_flags = 0; + struct tc_skbedit sel = { 0 }; + + if (matches(*argv, "skbedit") != 0) + return -1; + + NEXT_ARG(); + + while (argc > 0) { + if (matches(*argv, "queue_mapping") == 0) { + flags |= SKBEDIT_F_QUEUE_MAPPING; + NEXT_ARG(); + if (get_unsigned(&tmp, *argv, 10) || tmp > 65535) { + fprintf(stderr, "Illegal queue_mapping\n"); + return -1; + } + queue_mapping = tmp; + ok++; + } else if (matches(*argv, "priority") == 0) { + flags |= SKBEDIT_F_PRIORITY; + NEXT_ARG(); + if (get_tc_classid(&priority, *argv)) { + fprintf(stderr, "Illegal priority\n"); + return -1; + } + ok++; + } else if (matches(*argv, "mark") == 0) { + char *slash; + + NEXT_ARG(); + slash = strchr(*argv, '/'); + if (slash) + *slash = '\0'; + + flags |= SKBEDIT_F_MARK; + if (get_u32(&mark, *argv, 0)) { + fprintf(stderr, "Illegal mark\n"); + return -1; + } + + if (slash) { + if (get_u32(&mask, slash + 1, 0)) { + fprintf(stderr, "Illegal mask\n"); + return -1; + } + flags |= SKBEDIT_F_MASK; + } + ok++; + } else if (matches(*argv, "ptype") == 0) { + + NEXT_ARG(); + if (matches(*argv, "host") == 0) { + ptype = PACKET_HOST; + } else if (matches(*argv, "broadcast") == 0) { + ptype = PACKET_BROADCAST; + } else if (matches(*argv, "multicast") == 0) { + ptype = PACKET_MULTICAST; + } else if (matches(*argv, "otherhost") == 0) { + ptype = PACKET_OTHERHOST; + } else { + fprintf(stderr, "Illegal ptype (%s)\n", + *argv); + return -1; + } + flags |= SKBEDIT_F_PTYPE; + ok++; + } else if (matches(*argv, "inheritdsfield") == 0) { + pure_flags |= SKBEDIT_F_INHERITDSFIELD; + ok++; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + argc--; + argv++; + } + + parse_action_control_dflt(&argc, &argv, &sel.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&sel.index, *argv, 10)) { + fprintf(stderr, "skbedit: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + ok++; + } + } + + if (!ok) { + explain(); + return -1; + } + + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_SKBEDIT_PARMS, &sel, sizeof(sel)); + if (flags & SKBEDIT_F_QUEUE_MAPPING) + addattr_l(n, MAX_MSG, TCA_SKBEDIT_QUEUE_MAPPING, + &queue_mapping, sizeof(queue_mapping)); + if (flags & SKBEDIT_F_PRIORITY) + addattr_l(n, MAX_MSG, TCA_SKBEDIT_PRIORITY, + &priority, sizeof(priority)); + if (flags & SKBEDIT_F_MARK) + addattr_l(n, MAX_MSG, TCA_SKBEDIT_MARK, + &mark, sizeof(mark)); + if (flags & SKBEDIT_F_MASK) + addattr_l(n, MAX_MSG, TCA_SKBEDIT_MASK, + &mask, sizeof(mask)); + if (flags & SKBEDIT_F_PTYPE) + addattr_l(n, MAX_MSG, TCA_SKBEDIT_PTYPE, + &ptype, sizeof(ptype)); + if (pure_flags != 0) + addattr64(n, MAX_MSG, TCA_SKBEDIT_FLAGS, pure_flags); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_skbedit(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_SKBEDIT_MAX + 1]; + + SPRINT_BUF(b1); + __u32 priority; + __u16 ptype; + struct tc_skbedit *p; + + print_string(PRINT_ANY, "kind", "%s ", "skbedit"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_SKBEDIT_MAX, arg); + + if (tb[TCA_SKBEDIT_PARMS] == NULL) { + fprintf(stderr, "Missing skbedit parameters\n"); + return -1; + } + p = RTA_DATA(tb[TCA_SKBEDIT_PARMS]); + + if (tb[TCA_SKBEDIT_QUEUE_MAPPING] != NULL) { + print_uint(PRINT_ANY, "queue_mapping", "queue_mapping %u", + rta_getattr_u16(tb[TCA_SKBEDIT_QUEUE_MAPPING])); + } + if (tb[TCA_SKBEDIT_PRIORITY] != NULL) { + priority = rta_getattr_u32(tb[TCA_SKBEDIT_PRIORITY]); + print_string(PRINT_ANY, "priority", " priority %s", + sprint_tc_classid(priority, b1)); + } + if (tb[TCA_SKBEDIT_MARK] != NULL) { + print_uint(PRINT_ANY, "mark", " mark %u", + rta_getattr_u32(tb[TCA_SKBEDIT_MARK])); + } + if (tb[TCA_SKBEDIT_MASK]) { + print_hex(PRINT_ANY, "mask", "/%#x", + rta_getattr_u32(tb[TCA_SKBEDIT_MASK])); + } + if (tb[TCA_SKBEDIT_PTYPE] != NULL) { + ptype = rta_getattr_u16(tb[TCA_SKBEDIT_PTYPE]); + if (ptype == PACKET_HOST) + print_string(PRINT_ANY, "ptype", " ptype %s", "host"); + else if (ptype == PACKET_BROADCAST) + print_string(PRINT_ANY, "ptype", " ptype %s", + "broadcast"); + else if (ptype == PACKET_MULTICAST) + print_string(PRINT_ANY, "ptype", " ptype %s", + "multicast"); + else if (ptype == PACKET_OTHERHOST) + print_string(PRINT_ANY, "ptype", " ptype %s", + "otherhost"); + else + print_uint(PRINT_ANY, "ptype", " ptype %u", ptype); + } + if (tb[TCA_SKBEDIT_FLAGS] != NULL) { + __u64 flags = rta_getattr_u64(tb[TCA_SKBEDIT_FLAGS]); + + if (flags & SKBEDIT_F_INHERITDSFIELD) + print_null(PRINT_ANY, "inheritdsfield", " %s", + "inheritdsfield"); + } + + print_action_control(f, " ", p->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", p->index); + print_int(PRINT_ANY, "ref", " ref %d", p->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", p->bindcnt); + + if (show_stats) { + if (tb[TCA_SKBEDIT_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_SKBEDIT_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util skbedit_action_util = { + .id = "skbedit", + .parse_aopt = parse_skbedit, + .print_aopt = print_skbedit, +}; diff --git a/tc/m_skbmod.c b/tc/m_skbmod.c new file mode 100644 index 0000000..b1c8d00 --- /dev/null +++ b/tc/m_skbmod.c @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_skbmod.c skb modifier action module + * + * Authors: J Hadi Salim (jhs@mojatatu.com) + */ + +#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 <linux/netdevice.h> + +#include "rt_names.h" +#include "utils.h" +#include "tc_util.h" +#include <linux/tc_act/tc_skbmod.h> + +static void skbmod_explain(void) +{ + fprintf(stderr, + "Usage:... skbmod { set <SETTABLE> | swap <SWAPPABLE> | ecn } [CONTROL] [index INDEX]\n" + "where SETTABLE is: [dmac DMAC] [smac SMAC] [etype ETYPE]\n" + "where SWAPPABLE is: \"mac\" to swap mac addresses\n" + "\tDMAC := 6 byte Destination MAC address\n" + "\tSMAC := optional 6 byte Source MAC address\n" + "\tETYPE := optional 16 bit ethertype\n" + "\tCONTROL := reclassify | pipe | drop | continue | ok |\n" + "\t goto chain <CHAIN_INDEX>\n" + "\tINDEX := skbmod index value to use\n"); +} + +static void skbmod_usage(void) +{ + skbmod_explain(); + exit(-1); +} + +static int parse_skbmod(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + int ok = 0; + struct tc_skbmod p; + struct rtattr *tail; + char dbuf[ETH_ALEN]; + char sbuf[ETH_ALEN]; + __u16 skbmod_etype = 0; + char *daddr = NULL; + char *saddr = NULL; + + memset(&p, 0, sizeof(p)); + + if (argc <= 0) + return -1; + + while (argc > 0) { + if (matches(*argv, "skbmod") == 0) { + NEXT_ARG(); + continue; + } else if (matches(*argv, "swap") == 0) { + NEXT_ARG(); + continue; + } else if (matches(*argv, "mac") == 0) { + p.flags |= SKBMOD_F_SWAPMAC; + ok += 1; + } else if (matches(*argv, "set") == 0) { + NEXT_ARG(); + continue; + } else if (matches(*argv, "etype") == 0) { + NEXT_ARG(); + if (get_u16(&skbmod_etype, *argv, 0)) + invarg("ethertype is invalid", *argv); + fprintf(stderr, "skbmod etype 0x%x\n", skbmod_etype); + p.flags |= SKBMOD_F_ETYPE; + ok += 1; + } else if (matches(*argv, "dmac") == 0) { + NEXT_ARG(); + daddr = *argv; + if (sscanf(daddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + dbuf, dbuf + 1, dbuf + 2, + dbuf + 3, dbuf + 4, dbuf + 5) != 6) { + fprintf(stderr, "Invalid dst mac address %s\n", + daddr); + return -1; + } + p.flags |= SKBMOD_F_DMAC; + fprintf(stderr, "dst MAC address <%s>\n", daddr); + ok += 1; + + } else if (matches(*argv, "smac") == 0) { + NEXT_ARG(); + saddr = *argv; + if (sscanf(saddr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + sbuf, sbuf + 1, sbuf + 2, + sbuf + 3, sbuf + 4, sbuf + 5) != 6) { + fprintf(stderr, "Invalid smac address %s\n", + saddr); + return -1; + } + p.flags |= SKBMOD_F_SMAC; + fprintf(stderr, "src MAC address <%s>\n", saddr); + ok += 1; + } else if (matches(*argv, "ecn") == 0) { + p.flags |= SKBMOD_F_ECN; + ok += 1; + } else if (matches(*argv, "help") == 0) { + skbmod_usage(); + } else { + break; + } + + argc--; + argv++; + } + + parse_action_control_dflt(&argc, &argv, &p.action, false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&p.index, *argv, 0)) { + fprintf(stderr, "skbmod: Illegal \"index\"\n"); + return -1; + } + ok++; + argc--; + argv++; + } + } + + if (!ok) { + fprintf(stderr, "skbmod requires at least one option\n"); + skbmod_usage(); + } + + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_SKBMOD_PARMS, &p, sizeof(p)); + + if (daddr) + addattr_l(n, MAX_MSG, TCA_SKBMOD_DMAC, dbuf, ETH_ALEN); + if (skbmod_etype) + addattr16(n, MAX_MSG, TCA_SKBMOD_ETYPE, skbmod_etype); + if (saddr) + addattr_l(n, MAX_MSG, TCA_SKBMOD_SMAC, sbuf, ETH_ALEN); + + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_skbmod(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct tc_skbmod *p; + struct rtattr *tb[TCA_SKBMOD_MAX + 1]; + __u16 skbmod_etype = 0; + int has_optional = 0; + SPRINT_BUF(b1); + SPRINT_BUF(b2); + + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_SKBMOD_MAX, arg); + + if (tb[TCA_SKBMOD_PARMS] == NULL) { + fprintf(stderr, "Missing skbmod parameters\n"); + return -1; + } + + p = RTA_DATA(tb[TCA_SKBMOD_PARMS]); + + fprintf(f, "skbmod "); + print_action_control(f, "", p->action, " "); + + if (tb[TCA_SKBMOD_ETYPE]) { + skbmod_etype = rta_getattr_u16(tb[TCA_SKBMOD_ETYPE]); + has_optional = 1; + fprintf(f, "set etype 0x%X ", skbmod_etype); + } + + if (has_optional) + fprintf(f, "\n\t "); + + if (tb[TCA_SKBMOD_DMAC]) { + has_optional = 1; + fprintf(f, "set dmac %s ", + ll_addr_n2a(RTA_DATA(tb[TCA_SKBMOD_DMAC]), + RTA_PAYLOAD(tb[TCA_SKBMOD_DMAC]), 0, b1, + sizeof(b1))); + + } + + if (tb[TCA_SKBMOD_SMAC]) { + has_optional = 1; + fprintf(f, "set smac %s ", + ll_addr_n2a(RTA_DATA(tb[TCA_SKBMOD_SMAC]), + RTA_PAYLOAD(tb[TCA_SKBMOD_SMAC]), 0, b2, + sizeof(b2))); + } + + if (p->flags & SKBMOD_F_SWAPMAC) + fprintf(f, "swap mac "); + + if (p->flags & SKBMOD_F_ECN) + fprintf(f, "ecn "); + + fprintf(f, "\n\t index %u ref %d bind %d", p->index, p->refcnt, + p->bindcnt); + if (show_stats) { + if (tb[TCA_SKBMOD_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_SKBMOD_TM]); + + print_tm(f, tm); + } + } + + fprintf(f, "\n"); + + return 0; +} + +struct action_util skbmod_action_util = { + .id = "skbmod", + .parse_aopt = parse_skbmod, + .print_aopt = print_skbmod, +}; diff --git a/tc/m_tunnel_key.c b/tc/m_tunnel_key.c new file mode 100644 index 0000000..ff699cc --- /dev/null +++ b/tc/m_tunnel_key.c @@ -0,0 +1,758 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_tunnel_key.c ip tunnel manipulation module + * + * Authors: Amir Vadai <amir@vadai.me> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <linux/if_ether.h> +#include "utils.h" +#include "rt_names.h" +#include "tc_util.h" +#include <linux/tc_act/tc_tunnel_key.h> + +static void explain(void) +{ + fprintf(stderr, + "Usage: tunnel_key unset\n" + " tunnel_key set <TUNNEL_KEY>\n" + "Where TUNNEL_KEY is a combination of:\n" + "id <TUNNELID>\n" + "src_ip <IP> (mandatory)\n" + "dst_ip <IP> (mandatory)\n" + "dst_port <UDP_PORT>\n" + "geneve_opts | vxlan_opts | erspan_opts <OPTIONS>\n" + "csum | nocsum (default is \"csum\")\n" + "nofrag\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static int tunnel_key_parse_ip_addr(const char *str, int addr4_type, + int addr6_type, struct nlmsghdr *n) +{ + inet_prefix addr; + int ret; + + ret = get_addr(&addr, str, AF_UNSPEC); + if (ret) + return ret; + + addattr_l(n, MAX_MSG, addr.family == AF_INET ? addr4_type : addr6_type, + addr.data, addr.bytelen); + + return 0; +} + +static int tunnel_key_parse_key_id(const char *str, int type, + struct nlmsghdr *n) +{ + __be32 key_id; + int ret; + + ret = get_be32(&key_id, str, 10); + if (!ret) + addattr32(n, MAX_MSG, type, key_id); + + return ret; +} + +static int tunnel_key_parse_dst_port(char *str, int type, struct nlmsghdr *n) +{ + int ret; + __be16 dst_port; + + ret = get_be16(&dst_port, str, 10); + if (ret) + return -1; + + addattr16(n, MAX_MSG, type, dst_port); + + return 0; +} + +static int tunnel_key_parse_be16(char *str, int base, int type, + struct nlmsghdr *n) +{ + int ret; + __be16 value; + + ret = get_be16(&value, str, base); + if (ret) + return ret; + + addattr16(n, MAX_MSG, type, value); + + return 0; +} + +static int tunnel_key_parse_be32(char *str, int base, int type, + struct nlmsghdr *n) +{ + __be32 value; + int ret; + + ret = get_be32(&value, str, base); + if (ret) + return ret; + + addattr32(n, MAX_MSG, type, value); + + return 0; +} + +static int tunnel_key_parse_u8(char *str, int base, int type, + struct nlmsghdr *n) +{ + int ret; + __u8 value; + + ret = get_u8(&value, str, base); + if (ret) + return ret; + + addattr8(n, MAX_MSG, type, value); + + return 0; +} + +static int tunnel_key_parse_u32(char *str, int base, int type, + struct nlmsghdr *n) +{ + __u32 value; + int ret; + + ret = get_u32(&value, str, base); + if (ret) + return ret; + + addattr32(n, MAX_MSG, type, value); + + return 0; +} + +static int tunnel_key_parse_geneve_opt(char *str, struct nlmsghdr *n) +{ + char *token, *saveptr = NULL; + struct rtattr *nest; + int i, ret; + + nest = addattr_nest(n, MAX_MSG, TCA_TUNNEL_KEY_ENC_OPTS_GENEVE); + + token = strtok_r(str, ":", &saveptr); + i = 1; + while (token) { + switch (i) { + case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS: + { + ret = tunnel_key_parse_be16(token, 16, i, n); + if (ret) + return ret; + break; + } + case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE: + { + ret = tunnel_key_parse_u8(token, 16, i, n); + if (ret) + return ret; + break; + } + case TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA: + { + size_t token_len = strlen(token); + uint8_t *opts; + + opts = malloc(token_len / 2); + if (!opts) + return -1; + if (hex2mem(token, opts, token_len / 2) < 0) { + free(opts); + return -1; + } + addattr_l(n, MAX_MSG, i, opts, token_len / 2); + free(opts); + + break; + } + default: + return -1; + } + + token = strtok_r(NULL, ":", &saveptr); + i++; + } + + addattr_nest_end(n, nest); + + return 0; +} + +static int tunnel_key_parse_geneve_opts(char *str, struct nlmsghdr *n) +{ + char *token, *saveptr = NULL; + struct rtattr *nest; + int ret; + + nest = addattr_nest(n, MAX_MSG, TCA_TUNNEL_KEY_ENC_OPTS); + + token = strtok_r(str, ",", &saveptr); + while (token) { + ret = tunnel_key_parse_geneve_opt(token, n); + if (ret) + return ret; + + token = strtok_r(NULL, ",", &saveptr); + } + + addattr_nest_end(n, nest); + + return 0; +} + +static int tunnel_key_parse_vxlan_opt(char *str, struct nlmsghdr *n) +{ + struct rtattr *encap, *nest; + int ret; + + encap = addattr_nest(n, MAX_MSG, + TCA_TUNNEL_KEY_ENC_OPTS | NLA_F_NESTED); + nest = addattr_nest(n, MAX_MSG, + TCA_TUNNEL_KEY_ENC_OPTS_VXLAN | NLA_F_NESTED); + + ret = tunnel_key_parse_u32(str, 0, + TCA_TUNNEL_KEY_ENC_OPT_VXLAN_GBP, n); + if (ret) + return ret; + + addattr_nest_end(n, nest); + addattr_nest_end(n, encap); + + return 0; +} + +static int tunnel_key_parse_erspan_opt(char *str, struct nlmsghdr *n) +{ + char *token, *saveptr = NULL; + struct rtattr *encap, *nest; + int i, ret; + + encap = addattr_nest(n, MAX_MSG, + TCA_TUNNEL_KEY_ENC_OPTS | NLA_F_NESTED); + nest = addattr_nest(n, MAX_MSG, + TCA_TUNNEL_KEY_ENC_OPTS_ERSPAN | NLA_F_NESTED); + + token = strtok_r(str, ":", &saveptr); + i = 1; + while (token) { + switch (i) { + case TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_VER: + { + ret = tunnel_key_parse_u8(token, 0, i, n); + if (ret) + return ret; + break; + } + case TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_INDEX: + { + ret = tunnel_key_parse_be32(token, 0, i, n); + if (ret) + return ret; + break; + } + case TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_DIR: + { + ret = tunnel_key_parse_u8(token, 0, i, n); + if (ret) + return ret; + break; + } + case TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_HWID: + { + ret = tunnel_key_parse_u8(token, 0, i, n); + if (ret) + return ret; + break; + } + default: + return -1; + } + + token = strtok_r(NULL, ":", &saveptr); + i++; + } + + addattr_nest_end(n, nest); + addattr_nest_end(n, encap); + + return 0; +} + +static int tunnel_key_parse_tos_ttl(char *str, int type, struct nlmsghdr *n) +{ + int ret; + __u8 val; + + ret = get_u8(&val, str, 10); + if (ret) + ret = get_u8(&val, str, 16); + if (ret) + return -1; + + addattr8(n, MAX_MSG, type, val); + + return 0; +} + +static int parse_tunnel_key(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + struct tc_tunnel_key parm = {}; + char **argv = *argv_p; + int argc = *argc_p; + struct rtattr *tail; + int action = 0; + int ret; + int has_src_ip = 0; + int has_dst_ip = 0; + int csum = 1, nofrag = 0; + + if (matches(*argv, "tunnel_key") != 0) + return -1; + + tail = addattr_nest(n, MAX_MSG, tca_id); + + NEXT_ARG(); + + while (argc > 0) { + if (matches(*argv, "unset") == 0) { + if (action) { + fprintf(stderr, "unexpected \"%s\" - action already specified\n", + *argv); + explain(); + return -1; + } + action = TCA_TUNNEL_KEY_ACT_RELEASE; + } else if (matches(*argv, "set") == 0) { + if (action) { + fprintf(stderr, "unexpected \"%s\" - action already specified\n", + *argv); + explain(); + return -1; + } + action = TCA_TUNNEL_KEY_ACT_SET; + } else if (matches(*argv, "src_ip") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_ip_addr(*argv, + TCA_TUNNEL_KEY_ENC_IPV4_SRC, + TCA_TUNNEL_KEY_ENC_IPV6_SRC, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"src_ip\"\n"); + return -1; + } + has_src_ip = 1; + } else if (matches(*argv, "dst_ip") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_ip_addr(*argv, + TCA_TUNNEL_KEY_ENC_IPV4_DST, + TCA_TUNNEL_KEY_ENC_IPV6_DST, + n); + if (ret < 0) { + fprintf(stderr, "Illegal \"dst_ip\"\n"); + return -1; + } + has_dst_ip = 1; + } else if (matches(*argv, "id") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_key_id(*argv, TCA_TUNNEL_KEY_ENC_KEY_ID, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"id\"\n"); + return -1; + } + } else if (matches(*argv, "dst_port") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_dst_port(*argv, + TCA_TUNNEL_KEY_ENC_DST_PORT, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"dst port\"\n"); + return -1; + } + } else if (matches(*argv, "geneve_opts") == 0) { + NEXT_ARG(); + + if (tunnel_key_parse_geneve_opts(*argv, n)) { + fprintf(stderr, "Illegal \"geneve_opts\"\n"); + return -1; + } + } else if (matches(*argv, "vxlan_opts") == 0) { + NEXT_ARG(); + + if (tunnel_key_parse_vxlan_opt(*argv, n)) { + fprintf(stderr, "Illegal \"vxlan_opts\"\n"); + return -1; + } + } else if (matches(*argv, "erspan_opts") == 0) { + NEXT_ARG(); + + if (tunnel_key_parse_erspan_opt(*argv, n)) { + fprintf(stderr, "Illegal \"erspan_opts\"\n"); + return -1; + } + } else if (matches(*argv, "tos") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_tos_ttl(*argv, + TCA_TUNNEL_KEY_ENC_TOS, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"tos\"\n"); + return -1; + } + } else if (matches(*argv, "ttl") == 0) { + NEXT_ARG(); + ret = tunnel_key_parse_tos_ttl(*argv, + TCA_TUNNEL_KEY_ENC_TTL, n); + if (ret < 0) { + fprintf(stderr, "Illegal \"ttl\"\n"); + return -1; + } + } else if (matches(*argv, "csum") == 0) { + csum = 1; + } else if (matches(*argv, "nocsum") == 0) { + csum = 0; + } else if (strcmp(*argv, "nofrag") == 0) { + nofrag = 1; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + NEXT_ARG_FWD(); + } + + addattr8(n, MAX_MSG, TCA_TUNNEL_KEY_NO_CSUM, !csum); + + if (nofrag) + addattr(n, MAX_MSG, TCA_TUNNEL_KEY_NO_FRAG); + + parse_action_control_dflt(&argc, &argv, &parm.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&parm.index, *argv, 10)) { + fprintf(stderr, "tunnel_key: Illegal \"index\"\n"); + return -1; + } + + NEXT_ARG_FWD(); + } + } + + if (action == TCA_TUNNEL_KEY_ACT_SET && + (!has_src_ip || !has_dst_ip)) { + fprintf(stderr, "set needs tunnel_key parameters\n"); + explain(); + return -1; + } + + parm.t_action = action; + addattr_l(n, MAX_MSG, TCA_TUNNEL_KEY_PARMS, &parm, sizeof(parm)); + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + + return 0; +} + +static void tunnel_key_print_ip_addr(FILE *f, const char *name, + struct rtattr *attr) +{ + int family; + size_t len; + + if (!attr) + return; + + len = RTA_PAYLOAD(attr); + + if (len == 4) + family = AF_INET; + else if (len == 16) + family = AF_INET6; + else + return; + + print_nl(); + if (matches(name, "src_ip") == 0) + print_string(PRINT_ANY, "src_ip", "\tsrc_ip %s", + rt_addr_n2a_rta(family, attr)); + else if (matches(name, "dst_ip") == 0) + print_string(PRINT_ANY, "dst_ip", "\tdst_ip %s", + rt_addr_n2a_rta(family, attr)); +} + +static void tunnel_key_print_key_id(FILE *f, const char *name, + struct rtattr *attr) +{ + if (!attr) + return; + print_nl(); + print_uint(PRINT_ANY, "key_id", "\tkey_id %u", rta_getattr_be32(attr)); +} + +static void tunnel_key_print_dst_port(FILE *f, char *name, + struct rtattr *attr) +{ + if (!attr) + return; + print_nl(); + print_uint(PRINT_ANY, "dst_port", "\tdst_port %u", + rta_getattr_be16(attr)); +} + +static const struct { + const char *name; + unsigned int nl_flag; +} tunnel_key_flag_names[] = { + { "", TCA_TUNNEL_KEY_NO_CSUM }, /* special handling, not bool */ + { "nofrag", TCA_TUNNEL_KEY_NO_FRAG }, +}; + +static void tunnel_key_print_flags(struct rtattr *tb[]) +{ + unsigned int i, nl_flag; + + print_nl(); + for (i = 0; i < ARRAY_SIZE(tunnel_key_flag_names); i++) { + nl_flag = tunnel_key_flag_names[i].nl_flag; + if (nl_flag == TCA_TUNNEL_KEY_NO_CSUM) { + /* special handling to preserve csum/nocsum design */ + if (!tb[nl_flag]) + continue; + print_string(PRINT_ANY, "flag", "\t%s", + rta_getattr_u8(tb[nl_flag]) ? + "nocsum" : "csum" ); + } else { + if (tb[nl_flag]) + print_string(PRINT_FP, NULL, "\t%s", + tunnel_key_flag_names[i].name); + print_bool(PRINT_JSON, tunnel_key_flag_names[i].name, + NULL, !!tb[nl_flag]); + } + } +} + +static void tunnel_key_print_geneve_options(struct rtattr *attr) +{ + struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX + 1]; + struct rtattr *i = RTA_DATA(attr); + int ii, data_len = 0, offset = 0; + int rem = RTA_PAYLOAD(attr); + char *name = "geneve_opts"; + char strbuf[rem * 2 + 1]; + char data[rem * 2 + 1]; + uint8_t data_r[rem]; + uint16_t clss; + uint8_t type; + + open_json_array(PRINT_JSON, name); + print_nl(); + print_string(PRINT_FP, name, "\t%s ", name); + + while (rem) { + parse_rtattr(tb, TCA_TUNNEL_KEY_ENC_OPT_GENEVE_MAX, i, rem); + clss = rta_getattr_be16(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_CLASS]); + type = rta_getattr_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_TYPE]); + data_len = RTA_PAYLOAD(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]); + hexstring_n2a(RTA_DATA(tb[TCA_TUNNEL_KEY_ENC_OPT_GENEVE_DATA]), + data_len, data, sizeof(data)); + hex2mem(data, data_r, data_len); + offset += data_len + 20; + rem -= data_len + 20; + i = RTA_DATA(attr) + offset; + + open_json_object(NULL); + print_uint(PRINT_JSON, "class", NULL, clss); + print_uint(PRINT_JSON, "type", NULL, type); + open_json_array(PRINT_JSON, "data"); + for (ii = 0; ii < data_len; ii++) + print_uint(PRINT_JSON, NULL, NULL, data_r[ii]); + close_json_array(PRINT_JSON, "data"); + close_json_object(); + + sprintf(strbuf, "%04x:%02x:%s", clss, type, data); + if (rem) + print_string(PRINT_FP, NULL, "%s,", strbuf); + else + print_string(PRINT_FP, NULL, "%s", strbuf); + } + + close_json_array(PRINT_JSON, name); +} + +static void tunnel_key_print_vxlan_options(struct rtattr *attr) +{ + struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPT_VXLAN_MAX + 1]; + struct rtattr *i = RTA_DATA(attr); + int rem = RTA_PAYLOAD(attr); + char *name = "vxlan_opts"; + __u32 gbp; + + parse_rtattr(tb, TCA_TUNNEL_KEY_ENC_OPT_VXLAN_MAX, i, rem); + gbp = rta_getattr_u32(tb[TCA_TUNNEL_KEY_ENC_OPT_VXLAN_GBP]); + + print_nl(); + print_string(PRINT_FP, name, "\t%s ", name); + open_json_array(PRINT_JSON, name); + open_json_object(NULL); + print_uint(PRINT_ANY, "gbp", "%u", gbp); + close_json_object(); + close_json_array(PRINT_JSON, name); +} + +static void tunnel_key_print_erspan_options(struct rtattr *attr) +{ + struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_MAX + 1]; + struct rtattr *i = RTA_DATA(attr); + int rem = RTA_PAYLOAD(attr); + char *name = "erspan_opts"; + __u8 ver, hwid, dir; + __u32 idx; + + parse_rtattr(tb, TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_MAX, i, rem); + ver = rta_getattr_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_VER]); + if (ver == 1) { + idx = rta_getattr_be32(tb[TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_INDEX]); + dir = 0; + hwid = 0; + } else { + idx = 0; + dir = rta_getattr_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_DIR]); + hwid = rta_getattr_u8(tb[TCA_TUNNEL_KEY_ENC_OPT_ERSPAN_HWID]); + } + + print_nl(); + print_string(PRINT_FP, name, "\t%s ", name); + open_json_array(PRINT_JSON, name); + open_json_object(NULL); + print_uint(PRINT_ANY, "ver", "%u", ver); + print_uint(PRINT_ANY, "index", ":%u", idx); + print_uint(PRINT_ANY, "dir", ":%u", dir); + print_uint(PRINT_ANY, "hwid", ":%u", hwid); + close_json_object(); + close_json_array(PRINT_JSON, name); +} + +static void tunnel_key_print_key_opt(struct rtattr *attr) +{ + struct rtattr *tb[TCA_TUNNEL_KEY_ENC_OPTS_MAX + 1]; + + if (!attr) + return; + + parse_rtattr_nested(tb, TCA_TUNNEL_KEY_ENC_OPTS_MAX, attr); + if (tb[TCA_TUNNEL_KEY_ENC_OPTS_GENEVE]) + tunnel_key_print_geneve_options( + tb[TCA_TUNNEL_KEY_ENC_OPTS_GENEVE]); + else if (tb[TCA_TUNNEL_KEY_ENC_OPTS_VXLAN]) + tunnel_key_print_vxlan_options( + tb[TCA_TUNNEL_KEY_ENC_OPTS_VXLAN]); + else if (tb[TCA_TUNNEL_KEY_ENC_OPTS_ERSPAN]) + tunnel_key_print_erspan_options( + tb[TCA_TUNNEL_KEY_ENC_OPTS_ERSPAN]); +} + +static void tunnel_key_print_tos_ttl(FILE *f, char *name, + struct rtattr *attr) +{ + if (!attr) + return; + + if (matches(name, "tos") == 0 && rta_getattr_u8(attr) != 0) { + print_nl(); + print_uint(PRINT_ANY, "tos", "\ttos 0x%x", + rta_getattr_u8(attr)); + } else if (matches(name, "ttl") == 0 && rta_getattr_u8(attr) != 0) { + print_nl(); + print_uint(PRINT_ANY, "ttl", "\tttl %u", + rta_getattr_u8(attr)); + } +} + +static int print_tunnel_key(struct action_util *au, FILE *f, struct rtattr *arg) +{ + struct rtattr *tb[TCA_TUNNEL_KEY_MAX + 1]; + struct tc_tunnel_key *parm; + + print_string(PRINT_ANY, "kind", "%s ", "tunnel_key"); + if (!arg) + return 0; + + parse_rtattr_nested(tb, TCA_TUNNEL_KEY_MAX, arg); + + if (!tb[TCA_TUNNEL_KEY_PARMS]) { + fprintf(stderr, "Missing tunnel_key parameters\n"); + return -1; + } + parm = RTA_DATA(tb[TCA_TUNNEL_KEY_PARMS]); + + switch (parm->t_action) { + case TCA_TUNNEL_KEY_ACT_RELEASE: + print_string(PRINT_ANY, "mode", " %s", "unset"); + break; + case TCA_TUNNEL_KEY_ACT_SET: + print_string(PRINT_ANY, "mode", " %s", "set"); + tunnel_key_print_ip_addr(f, "src_ip", + tb[TCA_TUNNEL_KEY_ENC_IPV4_SRC]); + tunnel_key_print_ip_addr(f, "dst_ip", + tb[TCA_TUNNEL_KEY_ENC_IPV4_DST]); + tunnel_key_print_ip_addr(f, "src_ip", + tb[TCA_TUNNEL_KEY_ENC_IPV6_SRC]); + tunnel_key_print_ip_addr(f, "dst_ip", + tb[TCA_TUNNEL_KEY_ENC_IPV6_DST]); + tunnel_key_print_key_id(f, "key_id", + tb[TCA_TUNNEL_KEY_ENC_KEY_ID]); + tunnel_key_print_dst_port(f, "dst_port", + tb[TCA_TUNNEL_KEY_ENC_DST_PORT]); + tunnel_key_print_key_opt(tb[TCA_TUNNEL_KEY_ENC_OPTS]); + tunnel_key_print_flags(tb); + tunnel_key_print_tos_ttl(f, "tos", + tb[TCA_TUNNEL_KEY_ENC_TOS]); + tunnel_key_print_tos_ttl(f, "ttl", + tb[TCA_TUNNEL_KEY_ENC_TTL]); + break; + } + print_action_control(f, " ", parm->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", parm->index); + print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt); + + if (show_stats) { + if (tb[TCA_TUNNEL_KEY_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_TUNNEL_KEY_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util tunnel_key_action_util = { + .id = "tunnel_key", + .parse_aopt = parse_tunnel_key, + .print_aopt = print_tunnel_key, +}; diff --git a/tc/m_vlan.c b/tc/m_vlan.c new file mode 100644 index 0000000..c1dc8b4 --- /dev/null +++ b/tc/m_vlan.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_vlan.c vlan manipulation module + * + * Authors: Jiri Pirko <jiri@resnulli.us> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <linux/if_ether.h> +#include "utils.h" +#include "rt_names.h" +#include "tc_util.h" +#include <linux/tc_act/tc_vlan.h> + +static const char * const action_names[] = { + [TCA_VLAN_ACT_POP] = "pop", + [TCA_VLAN_ACT_PUSH] = "push", + [TCA_VLAN_ACT_MODIFY] = "modify", + [TCA_VLAN_ACT_POP_ETH] = "pop_eth", + [TCA_VLAN_ACT_PUSH_ETH] = "push_eth", +}; + +static void explain(void) +{ + fprintf(stderr, + "Usage: vlan pop [CONTROL]\n" + " vlan push [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n" + " vlan modify [ protocol VLANPROTO ] id VLANID [ priority VLANPRIO ] [CONTROL]\n" + " vlan pop_eth [CONTROL]\n" + " vlan push_eth dst_mac LLADDR src_mac LLADDR [CONTROL]\n" + " VLANPROTO is one of 802.1Q or 802.1AD\n" + " with default: 802.1Q\n" + " CONTROL := reclassify | pipe | drop | continue | pass |\n" + " goto chain <CHAIN_INDEX>\n"); +} + +static void usage(void) +{ + explain(); + exit(-1); +} + +static bool has_push_attribs(int action) +{ + return action == TCA_VLAN_ACT_PUSH || action == TCA_VLAN_ACT_MODIFY; +} + +static void unexpected(const char *arg) +{ + fprintf(stderr, + "unexpected \"%s\" - action already specified\n", + arg); + explain(); +} + +static int parse_vlan(struct action_util *a, int *argc_p, char ***argv_p, + int tca_id, struct nlmsghdr *n) +{ + int argc = *argc_p; + char **argv = *argv_p; + struct rtattr *tail; + int action = 0; + char dst_mac[ETH_ALEN] = {}; + int dst_mac_set = 0; + char src_mac[ETH_ALEN] = {}; + int src_mac_set = 0; + __u16 id; + int id_set = 0; + __u16 proto; + int proto_set = 0; + __u8 prio; + int prio_set = 0; + struct tc_vlan parm = {}; + + if (matches(*argv, "vlan") != 0) + return -1; + + NEXT_ARG(); + + while (argc > 0) { + if (matches(*argv, "pop") == 0) { + if (action) { + unexpected(*argv); + return -1; + } + action = TCA_VLAN_ACT_POP; + } else if (matches(*argv, "push") == 0) { + if (action) { + unexpected(*argv); + return -1; + } + action = TCA_VLAN_ACT_PUSH; + } else if (matches(*argv, "modify") == 0) { + if (action) { + unexpected(*argv); + return -1; + } + action = TCA_VLAN_ACT_MODIFY; + } else if (matches(*argv, "pop_eth") == 0) { + if (action) { + unexpected(*argv); + return -1; + } + action = TCA_VLAN_ACT_POP_ETH; + } else if (matches(*argv, "push_eth") == 0) { + if (action) { + unexpected(*argv); + return -1; + } + action = TCA_VLAN_ACT_PUSH_ETH; + } else if (matches(*argv, "id") == 0) { + if (!has_push_attribs(action)) + invarg("only valid for push/modify", *argv); + + NEXT_ARG(); + if (get_u16(&id, *argv, 0)) + invarg("id is invalid", *argv); + id_set = 1; + } else if (matches(*argv, "protocol") == 0) { + if (!has_push_attribs(action)) + invarg("only valid for push/modify", *argv); + + NEXT_ARG(); + if (ll_proto_a2n(&proto, *argv)) + invarg("protocol is invalid", *argv); + proto_set = 1; + } else if (matches(*argv, "priority") == 0) { + if (!has_push_attribs(action)) + invarg("only valid for push/modify", *argv); + + NEXT_ARG(); + if (get_u8(&prio, *argv, 0) || (prio & ~0x7)) + invarg("prio is invalid", *argv); + prio_set = 1; + } else if (matches(*argv, "dst_mac") == 0) { + if (action != TCA_VLAN_ACT_PUSH_ETH) + invarg("only valid for push_eth", *argv); + + NEXT_ARG(); + if (ll_addr_a2n(dst_mac, sizeof(dst_mac), *argv) < 0) + invarg("dst_mac is invalid", *argv); + dst_mac_set = 1; + } else if (matches(*argv, "src_mac") == 0) { + if (action != TCA_VLAN_ACT_PUSH_ETH) + invarg("only valid for push_eth", *argv); + + NEXT_ARG(); + if (ll_addr_a2n(src_mac, sizeof(src_mac), *argv) < 0) + invarg("src_mac is invalid", *argv); + src_mac_set = 1; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + break; + } + argc--; + argv++; + } + + parse_action_control_dflt(&argc, &argv, &parm.action, + false, TC_ACT_PIPE); + + if (argc) { + if (matches(*argv, "index") == 0) { + NEXT_ARG(); + if (get_u32(&parm.index, *argv, 10)) { + fprintf(stderr, "vlan: Illegal \"index\"\n"); + return -1; + } + argc--; + argv++; + } + } + + if (has_push_attribs(action) && !id_set) { + fprintf(stderr, "id needs to be set for %s\n", + action_names[action]); + explain(); + return -1; + } + + if (action == TCA_VLAN_ACT_PUSH_ETH) { + if (!dst_mac_set) { + fprintf(stderr, "dst_mac needs to be set for %s\n", + action_names[action]); + explain(); + return -1; + } else if (!src_mac_set) { + fprintf(stderr, "src_mac needs to be set for %s\n", + action_names[action]); + explain(); + return -1; + } + } + + parm.v_action = action; + tail = addattr_nest(n, MAX_MSG, tca_id); + addattr_l(n, MAX_MSG, TCA_VLAN_PARMS, &parm, sizeof(parm)); + if (id_set) + addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_ID, &id, 2); + if (proto_set) { + if (proto != htons(ETH_P_8021Q) && + proto != htons(ETH_P_8021AD)) { + fprintf(stderr, "protocol not supported\n"); + explain(); + return -1; + } + + addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PROTOCOL, &proto, 2); + } + if (prio_set) + addattr8(n, MAX_MSG, TCA_VLAN_PUSH_VLAN_PRIORITY, prio); + if (dst_mac_set) + addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_ETH_DST, dst_mac, + sizeof(dst_mac)); + if (src_mac_set) + addattr_l(n, MAX_MSG, TCA_VLAN_PUSH_ETH_SRC, src_mac, + sizeof(src_mac)); + + addattr_nest_end(n, tail); + + *argc_p = argc; + *argv_p = argv; + return 0; +} + +static int print_vlan(struct action_util *au, FILE *f, struct rtattr *arg) +{ + SPRINT_BUF(b1); + struct rtattr *tb[TCA_VLAN_MAX + 1]; + __u16 val; + struct tc_vlan *parm; + + print_string(PRINT_ANY, "kind", "%s ", "vlan"); + if (arg == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_VLAN_MAX, arg); + + if (!tb[TCA_VLAN_PARMS]) { + fprintf(stderr, "Missing vlan parameters\n"); + return -1; + } + parm = RTA_DATA(tb[TCA_VLAN_PARMS]); + + print_string(PRINT_ANY, "vlan_action", " %s", + action_names[parm->v_action]); + + switch (parm->v_action) { + case TCA_VLAN_ACT_PUSH: + case TCA_VLAN_ACT_MODIFY: + if (tb[TCA_VLAN_PUSH_VLAN_ID]) { + val = rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_ID]); + print_uint(PRINT_ANY, "id", " id %u", val); + } + if (tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]) { + __u16 proto; + + proto = rta_getattr_u16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]); + print_string(PRINT_ANY, "protocol", " protocol %s", + ll_proto_n2a(proto, b1, sizeof(b1))); + } + if (tb[TCA_VLAN_PUSH_VLAN_PRIORITY]) { + val = rta_getattr_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]); + print_uint(PRINT_ANY, "priority", " priority %u", val); + } + break; + case TCA_VLAN_ACT_PUSH_ETH: + if (tb[TCA_VLAN_PUSH_ETH_DST] && + RTA_PAYLOAD(tb[TCA_VLAN_PUSH_ETH_DST]) == ETH_ALEN) { + ll_addr_n2a(RTA_DATA(tb[TCA_VLAN_PUSH_ETH_DST]), + ETH_ALEN, 0, b1, sizeof(b1)); + print_string(PRINT_ANY, "dst_mac", " dst_mac %s", b1); + } + if (tb[TCA_VLAN_PUSH_ETH_SRC] && + RTA_PAYLOAD(tb[TCA_VLAN_PUSH_ETH_SRC]) == ETH_ALEN) { + ll_addr_n2a(RTA_DATA(tb[TCA_VLAN_PUSH_ETH_SRC]), + ETH_ALEN, 0, b1, sizeof(b1)); + print_string(PRINT_ANY, "src_mac", " src_mac %s", b1); + } + } + print_action_control(f, " ", parm->action, ""); + + print_nl(); + print_uint(PRINT_ANY, "index", "\t index %u", parm->index); + print_int(PRINT_ANY, "ref", " ref %d", parm->refcnt); + print_int(PRINT_ANY, "bind", " bind %d", parm->bindcnt); + + if (show_stats) { + if (tb[TCA_VLAN_TM]) { + struct tcf_t *tm = RTA_DATA(tb[TCA_VLAN_TM]); + + print_tm(f, tm); + } + } + + print_nl(); + + return 0; +} + +struct action_util vlan_action_util = { + .id = "vlan", + .parse_aopt = parse_vlan, + .print_aopt = print_vlan, +}; diff --git a/tc/p_eth.c b/tc/p_eth.c new file mode 100644 index 0000000..b35e0c2 --- /dev/null +++ b/tc/p_eth.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit_eth.c packet editor: ETH header + * + * + * Authors: Amir Vadai (amir@vadai.me) + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + +static int +parse_eth(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (!sel->extended) + return -1; + + tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_ETH; + + if (strcmp(*argv, "type") == 0) { + NEXT_ARG(); + tkey->off = 12; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + + if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + tkey->off = 0; + res = parse_cmd(&argc, &argv, 6, TMAC, RU32, sel, tkey, 0); + goto done; + } + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 6, TMAC, RU32, sel, tkey, 0); + goto done; + } + + return -1; + +done: + *argc_p = argc; + *argv_p = argv; + return res; +} + +struct m_pedit_util p_pedit_eth = { + .id = "eth", + .parse_peopt = parse_eth, +}; diff --git a/tc/p_icmp.c b/tc/p_icmp.c new file mode 100644 index 0000000..074d02f --- /dev/null +++ b/tc/p_icmp.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit_icmp.c packet editor: ICMP header + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + + +static int +parse_icmp(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + return -1; +} + +struct m_pedit_util p_pedit_icmp = { + .id = "icmp", + .parse_peopt = parse_icmp, +}; diff --git a/tc/p_ip.c b/tc/p_ip.c new file mode 100644 index 0000000..7f66ef3 --- /dev/null +++ b/tc/p_ip.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * p_ip.c packet editor: IPV4 header + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + +static int +parse_ip(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + tkey->htype = sel->extended ? + TCA_PEDIT_KEY_EX_HDR_TYPE_IP4 : + TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + tkey->off = 12; + res = parse_cmd(&argc, &argv, 4, TIPV4, RU32, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + tkey->off = 16; + res = parse_cmd(&argc, &argv, 4, TIPV4, RU32, sel, tkey, 0); + goto done; + } + /* jamal - look at these and make them either old or new + ** scheme given diffserv + ** don't forget the CE bit + */ + if (strcmp(*argv, "tos") == 0 || matches(*argv, "dsfield") == 0) { + NEXT_ARG(); + tkey->off = 1; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "ihl") == 0) { + NEXT_ARG(); + tkey->off = 0; + res = parse_cmd(&argc, &argv, 1, TU32, 0x0f, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "ttl") == 0) { + NEXT_ARG(); + tkey->off = 8; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, PEDIT_ALLOW_DEC); + goto done; + } + if (strcmp(*argv, "protocol") == 0) { + NEXT_ARG(); + tkey->off = 9; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + /* jamal - fix this */ + if (matches(*argv, "precedence") == 0) { + NEXT_ARG(); + tkey->off = 1; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + /* jamal - validate this at some point */ + if (strcmp(*argv, "nofrag") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, 0x3F, sel, tkey, 0); + goto done; + } + /* jamal - validate this at some point */ + if (strcmp(*argv, "firstfrag") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, 0x1F, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "ce") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, 0x80, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "df") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, 0x40, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "mf") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, 0x20, sel, tkey, 0); + goto done; + } + + if (sel->extended) + return -1; /* fields located outside IP header should be + * addressed using the relevant header type in + * extended pedit kABI + */ + + if (strcmp(*argv, "dport") == 0) { + NEXT_ARG(); + tkey->off = 22; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "sport") == 0) { + NEXT_ARG(); + tkey->off = 20; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "icmp_type") == 0) { + NEXT_ARG(); + tkey->off = 20; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "icmp_code") == 0) { + NEXT_ARG(); + tkey->off = 20; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + return -1; + +done: + *argc_p = argc; + *argv_p = argv; + return res; +} + +struct m_pedit_util p_pedit_ip = { + .id = "ip", + .parse_peopt = parse_ip, +}; diff --git a/tc/p_ip6.c b/tc/p_ip6.c new file mode 100644 index 0000000..3f8c23e --- /dev/null +++ b/tc/p_ip6.c @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * p_ip6.c packet editor: IPV6 header + * + * Authors: Amir Vadai <amir@vadai.me> + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + +static int +parse_ip6(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (!sel->extended) + return -1; + + tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_IP6; + + if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + tkey->off = 8; + res = parse_cmd(&argc, &argv, 16, TIPV6, RU32, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + tkey->off = 24; + res = parse_cmd(&argc, &argv, 16, TIPV6, RU32, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "flow_lbl") == 0) { + NEXT_ARG(); + tkey->off = 0; + res = parse_cmd(&argc, &argv, 4, TU32, 0x0007ffff, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "payload_len") == 0) { + NEXT_ARG(); + tkey->off = 4; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "nexthdr") == 0) { + NEXT_ARG(); + tkey->off = 6; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + if (strcmp(*argv, "hoplimit") == 0) { + NEXT_ARG(); + tkey->off = 7; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, PEDIT_ALLOW_DEC); + goto done; + } + if (strcmp(*argv, "traffic_class") == 0) { + NEXT_ARG(); + tkey->off = 1; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + + /* Shift the field by 4 bits on success. */ + if (!res) { + int nkeys = sel->sel.nkeys; + struct tc_pedit_key *key = &sel->keys[nkeys - 1]; + + key->mask = htonl(ntohl(key->mask) << 4 | 0xf); + key->val = htonl(ntohl(key->val) << 4); + } + goto done; + } + + return -1; + +done: + *argc_p = argc; + *argv_p = argv; + return res; +} + +struct m_pedit_util p_pedit_ip6 = { + .id = "ip6", + .parse_peopt = parse_ip6, +}; diff --git a/tc/p_tcp.c b/tc/p_tcp.c new file mode 100644 index 0000000..43d7fb4 --- /dev/null +++ b/tc/p_tcp.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit_tcp.c packet editor: TCP header + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + +static int +parse_tcp(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (!sel->extended) + return -1; + + tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_TCP; + + if (strcmp(*argv, "sport") == 0) { + NEXT_ARG(); + tkey->off = 0; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + + if (strcmp(*argv, "dport") == 0) { + NEXT_ARG(); + tkey->off = 2; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + + if (strcmp(*argv, "flags") == 0) { + NEXT_ARG(); + tkey->off = 13; + res = parse_cmd(&argc, &argv, 1, TU32, RU8, sel, tkey, 0); + goto done; + } + + return -1; + +done: + *argc_p = argc; + *argv_p = argv; + return res; +} +struct m_pedit_util p_pedit_tcp = { + .id = "tcp", + .parse_peopt = parse_tcp, +}; diff --git a/tc/p_udp.c b/tc/p_udp.c new file mode 100644 index 0000000..d98224f --- /dev/null +++ b/tc/p_udp.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * m_pedit_udp.c packet editor: UDP header + * + * Authors: J Hadi Salim (hadi@cyberus.ca) + */ + +#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 "utils.h" +#include "tc_util.h" +#include "m_pedit.h" + +static int +parse_udp(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey) +{ + int res = -1; + int argc = *argc_p; + char **argv = *argv_p; + + if (argc < 2) + return -1; + + if (!sel->extended) + return -1; + + tkey->htype = TCA_PEDIT_KEY_EX_HDR_TYPE_UDP; + + if (strcmp(*argv, "sport") == 0) { + NEXT_ARG(); + tkey->off = 0; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + + if (strcmp(*argv, "dport") == 0) { + NEXT_ARG(); + tkey->off = 2; + res = parse_cmd(&argc, &argv, 2, TU32, RU16, sel, tkey, 0); + goto done; + } + + return -1; + +done: + *argc_p = argc; + *argv_p = argv; + return res; +} + +struct m_pedit_util p_pedit_udp = { + .id = "udp", + .parse_peopt = parse_udp, +}; diff --git a/tc/q_cake.c b/tc/q_cake.c new file mode 100644 index 0000000..c438b76 --- /dev/null +++ b/tc/q_cake.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +/* + * Common Applications Kept Enhanced -- CAKE + * + * Copyright (C) 2014-2018 Jonathan Morton <chromatix99@gmail.com> + * Copyright (C) 2017-2018 Toke Høiland-Jørgensen <toke@toke.dk> + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <inttypes.h> + +#include "utils.h" +#include "tc_util.h" + +struct cake_preset { + char *name; + unsigned int target; + unsigned int interval; +}; + +static struct cake_preset presets[] = { + {"datacentre", 5, 100}, + {"lan", 50, 1000}, + {"metro", 500, 10000}, + {"regional", 1500, 30000}, + {"internet", 5000, 100000}, + {"oceanic", 15000, 300000}, + {"satellite", 50000, 1000000}, + {"interplanetary", 50000000, 1000000000}, +}; + +static const char * diffserv_names[CAKE_DIFFSERV_MAX] = { + [CAKE_DIFFSERV_DIFFSERV3] = "diffserv3", + [CAKE_DIFFSERV_DIFFSERV4] = "diffserv4", + [CAKE_DIFFSERV_DIFFSERV8] = "diffserv8", + [CAKE_DIFFSERV_BESTEFFORT] = "besteffort", + [CAKE_DIFFSERV_PRECEDENCE] = "precedence", +}; + +static const char * flowmode_names[CAKE_FLOW_MAX] = { + [CAKE_FLOW_NONE] = "flowblind", + [CAKE_FLOW_SRC_IP] = "srchost", + [CAKE_FLOW_DST_IP] = "dsthost", + [CAKE_FLOW_HOSTS] = "hosts", + [CAKE_FLOW_FLOWS] = "flows", + [CAKE_FLOW_DUAL_SRC] = "dual-srchost", + [CAKE_FLOW_DUAL_DST] = "dual-dsthost", + [CAKE_FLOW_TRIPLE] = "triple-isolate", +}; + +static struct cake_preset *find_preset(char *argv) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(presets); i++) + if (!strcmp(argv, presets[i].name)) + return &presets[i]; + return NULL; +} + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... cake [ bandwidth RATE | unlimited* | autorate-ingress ]\n" + " [ rtt TIME | datacentre | lan | metro | regional |\n" + " internet* | oceanic | satellite | interplanetary ]\n" + " [ besteffort | diffserv8 | diffserv4 | diffserv3* ]\n" + " [ flowblind | srchost | dsthost | hosts | flows |\n" + " dual-srchost | dual-dsthost | triple-isolate* ]\n" + " [ nat | nonat* ]\n" + " [ wash | nowash* ]\n" + " [ split-gso* | no-split-gso ]\n" + " [ ack-filter | ack-filter-aggressive | no-ack-filter* ]\n" + " [ memlimit LIMIT ]\n" + " [ fwmark MASK ]\n" + " [ ptm | atm | noatm* ] [ overhead N | conservative | raw* ]\n" + " [ mpu N ] [ ingress | egress* ]\n" + " (* marks defaults)\n"); +} + +static int cake_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct cake_preset *preset, *preset_set = NULL; + bool overhead_override = false; + bool overhead_set = false; + unsigned int interval = 0; + int diffserv = -1; + unsigned int memlimit = 0; + unsigned int fwmark = 0; + unsigned int target = 0; + __u64 bandwidth = 0; + int ack_filter = -1; + struct rtattr *tail; + int split_gso = -1; + int unlimited = 0; + int flowmode = -1; + int autorate = -1; + int ingress = -1; + int overhead = 0; + int wash = -1; + int nat = -1; + int atm = -1; + int mpu = 0; + + while (argc > 0) { + if (strcmp(*argv, "bandwidth") == 0) { + NEXT_ARG(); + if (get_rate64(&bandwidth, *argv)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + unlimited = 0; + autorate = 0; + } else if (strcmp(*argv, "unlimited") == 0) { + bandwidth = 0; + unlimited = 1; + autorate = 0; + } else if (strcmp(*argv, "autorate-ingress") == 0) { + autorate = 1; + } else if (strcmp(*argv, "rtt") == 0) { + NEXT_ARG(); + if (get_time(&interval, *argv)) { + fprintf(stderr, "Illegal \"rtt\"\n"); + return -1; + } + target = interval / 20; + if (!target) + target = 1; + } else if ((preset = find_preset(*argv))) { + if (preset_set) + duparg(*argv, preset_set->name); + preset_set = preset; + target = preset->target; + interval = preset->interval; + } else if (strcmp(*argv, "besteffort") == 0) { + diffserv = CAKE_DIFFSERV_BESTEFFORT; + } else if (strcmp(*argv, "precedence") == 0) { + diffserv = CAKE_DIFFSERV_PRECEDENCE; + } else if (strcmp(*argv, "diffserv8") == 0) { + diffserv = CAKE_DIFFSERV_DIFFSERV8; + } else if (strcmp(*argv, "diffserv4") == 0) { + diffserv = CAKE_DIFFSERV_DIFFSERV4; + } else if (strcmp(*argv, "diffserv") == 0) { + diffserv = CAKE_DIFFSERV_DIFFSERV4; + } else if (strcmp(*argv, "diffserv3") == 0) { + diffserv = CAKE_DIFFSERV_DIFFSERV3; + } else if (strcmp(*argv, "nowash") == 0) { + wash = 0; + } else if (strcmp(*argv, "wash") == 0) { + wash = 1; + } else if (strcmp(*argv, "split-gso") == 0) { + split_gso = 1; + } else if (strcmp(*argv, "no-split-gso") == 0) { + split_gso = 0; + } else if (strcmp(*argv, "flowblind") == 0) { + flowmode = CAKE_FLOW_NONE; + } else if (strcmp(*argv, "srchost") == 0) { + flowmode = CAKE_FLOW_SRC_IP; + } else if (strcmp(*argv, "dsthost") == 0) { + flowmode = CAKE_FLOW_DST_IP; + } else if (strcmp(*argv, "hosts") == 0) { + flowmode = CAKE_FLOW_HOSTS; + } else if (strcmp(*argv, "flows") == 0) { + flowmode = CAKE_FLOW_FLOWS; + } else if (strcmp(*argv, "dual-srchost") == 0) { + flowmode = CAKE_FLOW_DUAL_SRC; + } else if (strcmp(*argv, "dual-dsthost") == 0) { + flowmode = CAKE_FLOW_DUAL_DST; + } else if (strcmp(*argv, "triple-isolate") == 0) { + flowmode = CAKE_FLOW_TRIPLE; + } else if (strcmp(*argv, "nat") == 0) { + nat = 1; + } else if (strcmp(*argv, "nonat") == 0) { + nat = 0; + } else if (strcmp(*argv, "ptm") == 0) { + atm = CAKE_ATM_PTM; + } else if (strcmp(*argv, "atm") == 0) { + atm = CAKE_ATM_ATM; + } else if (strcmp(*argv, "noatm") == 0) { + atm = CAKE_ATM_NONE; + } else if (strcmp(*argv, "raw") == 0) { + atm = CAKE_ATM_NONE; + overhead = 0; + overhead_set = true; + overhead_override = true; + } else if (strcmp(*argv, "conservative") == 0) { + /* + * Deliberately over-estimate overhead: + * one whole ATM cell plus ATM framing. + * A safe choice if the actual overhead is unknown. + */ + atm = CAKE_ATM_ATM; + overhead = 48; + overhead_set = true; + + /* Various ADSL framing schemes, all over ATM cells */ + } else if (strcmp(*argv, "ipoa-vcmux") == 0) { + atm = CAKE_ATM_ATM; + overhead += 8; + overhead_set = true; + } else if (strcmp(*argv, "ipoa-llcsnap") == 0) { + atm = CAKE_ATM_ATM; + overhead += 16; + overhead_set = true; + } else if (strcmp(*argv, "bridged-vcmux") == 0) { + atm = CAKE_ATM_ATM; + overhead += 24; + overhead_set = true; + } else if (strcmp(*argv, "bridged-llcsnap") == 0) { + atm = CAKE_ATM_ATM; + overhead += 32; + overhead_set = true; + } else if (strcmp(*argv, "pppoa-vcmux") == 0) { + atm = CAKE_ATM_ATM; + overhead += 10; + overhead_set = true; + } else if (strcmp(*argv, "pppoa-llc") == 0) { + atm = CAKE_ATM_ATM; + overhead += 14; + overhead_set = true; + } else if (strcmp(*argv, "pppoe-vcmux") == 0) { + atm = CAKE_ATM_ATM; + overhead += 32; + overhead_set = true; + } else if (strcmp(*argv, "pppoe-llcsnap") == 0) { + atm = CAKE_ATM_ATM; + overhead += 40; + overhead_set = true; + + /* Typical VDSL2 framing schemes, both over PTM */ + /* PTM has 64b/65b coding which absorbs some bandwidth */ + } else if (strcmp(*argv, "pppoe-ptm") == 0) { + /* 2B PPP + 6B PPPoE + 6B dest MAC + 6B src MAC + * + 2B ethertype + 4B Frame Check Sequence + * + 1B Start of Frame (S) + 1B End of Frame (Ck) + * + 2B TC-CRC (PTM-FCS) = 30B + */ + atm = CAKE_ATM_PTM; + overhead += 30; + overhead_set = true; + } else if (strcmp(*argv, "bridged-ptm") == 0) { + /* 6B dest MAC + 6B src MAC + 2B ethertype + * + 4B Frame Check Sequence + * + 1B Start of Frame (S) + 1B End of Frame (Ck) + * + 2B TC-CRC (PTM-FCS) = 22B + */ + atm = CAKE_ATM_PTM; + overhead += 22; + overhead_set = true; + } else if (strcmp(*argv, "via-ethernet") == 0) { + /* + * We used to use this flag to manually compensate for + * Linux including the Ethernet header on Ethernet-type + * interfaces, but not on IP-type interfaces. + * + * It is no longer needed, because Cake now adjusts for + * that automatically, and is thus ignored. + * + * It would be deleted entirely, but it appears in the + * stats output when the automatic compensation is + * active. + */ + } else if (strcmp(*argv, "ethernet") == 0) { + /* ethernet pre-amble & interframe gap & FCS + * you may need to add vlan tag + */ + overhead += 38; + overhead_set = true; + mpu = 84; + + /* Additional Ethernet-related overhead used by some ISPs */ + } else if (strcmp(*argv, "ether-vlan") == 0) { + /* 802.1q VLAN tag - may be repeated */ + overhead += 4; + overhead_set = true; + + /* + * DOCSIS cable shapers account for Ethernet frame with FCS, + * but not interframe gap or preamble. + */ + } else if (strcmp(*argv, "docsis") == 0) { + atm = CAKE_ATM_NONE; + overhead += 18; + overhead_set = true; + mpu = 64; + } else if (strcmp(*argv, "overhead") == 0) { + char *p = NULL; + + NEXT_ARG(); + overhead = strtol(*argv, &p, 10); + if (!p || *p || overhead < -64 || overhead > 256) { + fprintf(stderr, + "Illegal \"overhead\", valid range is -64 to 256\\n"); + return -1; + } + overhead_set = true; + + } else if (strcmp(*argv, "mpu") == 0) { + char *p = NULL; + + NEXT_ARG(); + mpu = strtol(*argv, &p, 10); + if (!p || *p || mpu < 0 || mpu > 256) { + fprintf(stderr, + "Illegal \"mpu\", valid range is 0 to 256\\n"); + return -1; + } + } else if (strcmp(*argv, "ingress") == 0) { + ingress = 1; + } else if (strcmp(*argv, "egress") == 0) { + ingress = 0; + } else if (strcmp(*argv, "no-ack-filter") == 0) { + ack_filter = CAKE_ACK_NONE; + } else if (strcmp(*argv, "ack-filter") == 0) { + ack_filter = CAKE_ACK_FILTER; + } else if (strcmp(*argv, "ack-filter-aggressive") == 0) { + ack_filter = CAKE_ACK_AGGRESSIVE; + } else if (strcmp(*argv, "memlimit") == 0) { + NEXT_ARG(); + if (get_size(&memlimit, *argv)) { + fprintf(stderr, + "Illegal value for \"memlimit\": \"%s\"\n", *argv); + return -1; + } + } else if (strcmp(*argv, "fwmark") == 0) { + NEXT_ARG(); + if (get_u32(&fwmark, *argv, 0)) { + fprintf(stderr, + "Illegal value for \"fwmark\": \"%s\"\n", *argv); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = NLMSG_TAIL(n); + addattr_l(n, 1024, TCA_OPTIONS, NULL, 0); + if (bandwidth || unlimited) + addattr_l(n, 1024, TCA_CAKE_BASE_RATE64, &bandwidth, + sizeof(bandwidth)); + if (diffserv != -1) + addattr_l(n, 1024, TCA_CAKE_DIFFSERV_MODE, &diffserv, + sizeof(diffserv)); + if (atm != -1) + addattr_l(n, 1024, TCA_CAKE_ATM, &atm, sizeof(atm)); + if (flowmode != -1) + addattr_l(n, 1024, TCA_CAKE_FLOW_MODE, &flowmode, + sizeof(flowmode)); + if (overhead_set) + addattr_l(n, 1024, TCA_CAKE_OVERHEAD, &overhead, + sizeof(overhead)); + if (overhead_override) { + unsigned int zero = 0; + + addattr_l(n, 1024, TCA_CAKE_RAW, &zero, sizeof(zero)); + } + if (mpu > 0) + addattr_l(n, 1024, TCA_CAKE_MPU, &mpu, sizeof(mpu)); + if (interval) + addattr_l(n, 1024, TCA_CAKE_RTT, &interval, sizeof(interval)); + if (target) + addattr_l(n, 1024, TCA_CAKE_TARGET, &target, sizeof(target)); + if (autorate != -1) + addattr_l(n, 1024, TCA_CAKE_AUTORATE, &autorate, + sizeof(autorate)); + if (memlimit) + addattr_l(n, 1024, TCA_CAKE_MEMORY, &memlimit, + sizeof(memlimit)); + if (fwmark) + addattr_l(n, 1024, TCA_CAKE_FWMARK, &fwmark, + sizeof(fwmark)); + if (nat != -1) + addattr_l(n, 1024, TCA_CAKE_NAT, &nat, sizeof(nat)); + if (wash != -1) + addattr_l(n, 1024, TCA_CAKE_WASH, &wash, sizeof(wash)); + if (split_gso != -1) + addattr_l(n, 1024, TCA_CAKE_SPLIT_GSO, &split_gso, + sizeof(split_gso)); + if (ingress != -1) + addattr_l(n, 1024, TCA_CAKE_INGRESS, &ingress, sizeof(ingress)); + if (ack_filter != -1) + addattr_l(n, 1024, TCA_CAKE_ACK_FILTER, &ack_filter, + sizeof(ack_filter)); + + tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; + return 0; +} + +static void cake_print_mode(unsigned int value, unsigned int max, + const char *key, const char **table) +{ + if (value < max && table[value]) { + print_string(PRINT_ANY, key, "%s ", table[value]); + } else { + print_string(PRINT_JSON, key, NULL, "unknown"); + print_string(PRINT_FP, NULL, "(?%s?)", key); + } +} + +static int cake_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_CAKE_MAX + 1]; + unsigned int interval = 0; + unsigned int memlimit = 0; + unsigned int fwmark = 0; + __u64 bandwidth = 0; + int ack_filter = 0; + int split_gso = 0; + int overhead = 0; + int autorate = 0; + int ingress = 0; + int wash = 0; + int raw = 0; + int mpu = 0; + int atm = 0; + int nat = 0; + + SPRINT_BUF(b2); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CAKE_MAX, opt); + + if (tb[TCA_CAKE_BASE_RATE64] && + RTA_PAYLOAD(tb[TCA_CAKE_BASE_RATE64]) >= sizeof(bandwidth)) { + bandwidth = rta_getattr_u64(tb[TCA_CAKE_BASE_RATE64]); + if (bandwidth) + tc_print_rate(PRINT_ANY, "bandwidth", "bandwidth %s ", + bandwidth); + else + print_string(PRINT_ANY, "bandwidth", "bandwidth %s ", + "unlimited"); + } + if (tb[TCA_CAKE_AUTORATE] && + RTA_PAYLOAD(tb[TCA_CAKE_AUTORATE]) >= sizeof(__u32)) { + autorate = rta_getattr_u32(tb[TCA_CAKE_AUTORATE]); + if (autorate == 1) + print_string(PRINT_ANY, "autorate", "%s ", + "autorate-ingress"); + else if (autorate) + print_string(PRINT_ANY, "autorate", "(?autorate?) ", + "unknown"); + } + if (tb[TCA_CAKE_DIFFSERV_MODE] && + RTA_PAYLOAD(tb[TCA_CAKE_DIFFSERV_MODE]) >= sizeof(__u32)) { + cake_print_mode(rta_getattr_u32(tb[TCA_CAKE_DIFFSERV_MODE]), + CAKE_DIFFSERV_MAX, "diffserv", diffserv_names); + } + if (tb[TCA_CAKE_FLOW_MODE] && + RTA_PAYLOAD(tb[TCA_CAKE_FLOW_MODE]) >= sizeof(__u32)) { + cake_print_mode(rta_getattr_u32(tb[TCA_CAKE_FLOW_MODE]), + CAKE_FLOW_MAX, "flowmode", flowmode_names); + } + + if (tb[TCA_CAKE_NAT] && + RTA_PAYLOAD(tb[TCA_CAKE_NAT]) >= sizeof(__u32)) { + nat = rta_getattr_u32(tb[TCA_CAKE_NAT]); + } + + if (nat) + print_string(PRINT_FP, NULL, "nat ", NULL); + else + print_string(PRINT_FP, NULL, "nonat ", NULL); + print_bool(PRINT_JSON, "nat", NULL, nat); + + if (tb[TCA_CAKE_WASH] && + RTA_PAYLOAD(tb[TCA_CAKE_WASH]) >= sizeof(__u32)) { + wash = rta_getattr_u32(tb[TCA_CAKE_WASH]); + } + if (tb[TCA_CAKE_ATM] && + RTA_PAYLOAD(tb[TCA_CAKE_ATM]) >= sizeof(__u32)) { + atm = rta_getattr_u32(tb[TCA_CAKE_ATM]); + } + if (tb[TCA_CAKE_OVERHEAD] && + RTA_PAYLOAD(tb[TCA_CAKE_OVERHEAD]) >= sizeof(__s32)) { + overhead = *(__s32 *) RTA_DATA(tb[TCA_CAKE_OVERHEAD]); + } + if (tb[TCA_CAKE_MPU] && + RTA_PAYLOAD(tb[TCA_CAKE_MPU]) >= sizeof(__u32)) { + mpu = rta_getattr_u32(tb[TCA_CAKE_MPU]); + } + if (tb[TCA_CAKE_INGRESS] && + RTA_PAYLOAD(tb[TCA_CAKE_INGRESS]) >= sizeof(__u32)) { + ingress = rta_getattr_u32(tb[TCA_CAKE_INGRESS]); + } + if (tb[TCA_CAKE_ACK_FILTER] && + RTA_PAYLOAD(tb[TCA_CAKE_ACK_FILTER]) >= sizeof(__u32)) { + ack_filter = rta_getattr_u32(tb[TCA_CAKE_ACK_FILTER]); + } + if (tb[TCA_CAKE_SPLIT_GSO] && + RTA_PAYLOAD(tb[TCA_CAKE_SPLIT_GSO]) >= sizeof(__u32)) { + split_gso = rta_getattr_u32(tb[TCA_CAKE_SPLIT_GSO]); + } + if (tb[TCA_CAKE_RAW]) { + raw = 1; + } + if (tb[TCA_CAKE_RTT] && + RTA_PAYLOAD(tb[TCA_CAKE_RTT]) >= sizeof(__u32)) { + interval = rta_getattr_u32(tb[TCA_CAKE_RTT]); + } + if (tb[TCA_CAKE_MEMORY] && + RTA_PAYLOAD(tb[TCA_CAKE_MEMORY]) >= sizeof(__u32)) { + memlimit = rta_getattr_u32(tb[TCA_CAKE_MEMORY]); + } + if (tb[TCA_CAKE_FWMARK] && + RTA_PAYLOAD(tb[TCA_CAKE_FWMARK]) >= sizeof(__u32)) { + fwmark = rta_getattr_u32(tb[TCA_CAKE_FWMARK]); + } + + if (wash) + print_string(PRINT_FP, NULL, "wash ", NULL); + else + print_string(PRINT_FP, NULL, "nowash ", NULL); + print_bool(PRINT_JSON, "wash", NULL, wash); + + if (ingress) + print_string(PRINT_FP, NULL, "ingress ", NULL); + print_bool(PRINT_JSON, "ingress", NULL, ingress); + + if (ack_filter == CAKE_ACK_AGGRESSIVE) + print_string(PRINT_ANY, "ack-filter", "ack-filter-%s ", + "aggressive"); + else if (ack_filter == CAKE_ACK_FILTER) + print_string(PRINT_ANY, "ack-filter", "ack-filter ", "enabled"); + else + print_string(PRINT_ANY, "ack-filter", "no-ack-filter ", "disabled"); + + if (split_gso) + print_string(PRINT_FP, NULL, "split-gso ", NULL); + else + print_string(PRINT_FP, NULL, "no-split-gso ", NULL); + print_bool(PRINT_JSON, "split_gso", NULL, split_gso); + + if (interval) + print_string(PRINT_FP, NULL, "rtt %s ", + sprint_time(interval, b2)); + print_uint(PRINT_JSON, "rtt", NULL, interval); + + if (raw) + print_string(PRINT_FP, NULL, "raw ", NULL); + print_bool(PRINT_JSON, "raw", NULL, raw); + + if (atm == CAKE_ATM_ATM) + print_string(PRINT_ANY, "atm", "%s ", "atm"); + else if (atm == CAKE_ATM_PTM) + print_string(PRINT_ANY, "atm", "%s ", "ptm"); + else if (!raw) + print_string(PRINT_ANY, "atm", "%s ", "noatm"); + + print_int(PRINT_ANY, "overhead", "overhead %d ", overhead); + + if (mpu) + print_uint(PRINT_ANY, "mpu", "mpu %u ", mpu); + + if (memlimit) + print_size(PRINT_ANY, "memlimit", "memlimit %s ", memlimit); + + if (fwmark) + print_uint(PRINT_FP, NULL, "fwmark 0x%x ", fwmark); + print_0xhex(PRINT_JSON, "fwmark", NULL, fwmark); + + return 0; +} + +static void cake_print_json_tin(struct rtattr **tstat) +{ +#define PRINT_TSTAT_JSON(type, name, attr) if (tstat[TCA_CAKE_TIN_STATS_ ## attr]) \ + print_u64(PRINT_JSON, name, NULL, \ + rta_getattr_ ## type((struct rtattr *) \ + tstat[TCA_CAKE_TIN_STATS_ ## attr])) + + open_json_object(NULL); + PRINT_TSTAT_JSON(u64, "threshold_rate", THRESHOLD_RATE64); + PRINT_TSTAT_JSON(u64, "sent_bytes", SENT_BYTES64); + PRINT_TSTAT_JSON(u32, "backlog_bytes", BACKLOG_BYTES); + PRINT_TSTAT_JSON(u32, "target_us", TARGET_US); + PRINT_TSTAT_JSON(u32, "interval_us", INTERVAL_US); + PRINT_TSTAT_JSON(u32, "peak_delay_us", PEAK_DELAY_US); + PRINT_TSTAT_JSON(u32, "avg_delay_us", AVG_DELAY_US); + PRINT_TSTAT_JSON(u32, "base_delay_us", BASE_DELAY_US); + PRINT_TSTAT_JSON(u32, "sent_packets", SENT_PACKETS); + PRINT_TSTAT_JSON(u32, "way_indirect_hits", WAY_INDIRECT_HITS); + PRINT_TSTAT_JSON(u32, "way_misses", WAY_MISSES); + PRINT_TSTAT_JSON(u32, "way_collisions", WAY_COLLISIONS); + PRINT_TSTAT_JSON(u32, "drops", DROPPED_PACKETS); + PRINT_TSTAT_JSON(u32, "ecn_mark", ECN_MARKED_PACKETS); + PRINT_TSTAT_JSON(u32, "ack_drops", ACKS_DROPPED_PACKETS); + PRINT_TSTAT_JSON(u32, "sparse_flows", SPARSE_FLOWS); + PRINT_TSTAT_JSON(u32, "bulk_flows", BULK_FLOWS); + PRINT_TSTAT_JSON(u32, "unresponsive_flows", UNRESPONSIVE_FLOWS); + PRINT_TSTAT_JSON(u32, "max_pkt_len", MAX_SKBLEN); + PRINT_TSTAT_JSON(u32, "flow_quantum", FLOW_QUANTUM); + close_json_object(); + +#undef PRINT_TSTAT_JSON +} + +static int cake_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct rtattr *st[TCA_CAKE_STATS_MAX + 1]; + SPRINT_BUF(b1); + int i; + + if (xstats == NULL) + return 0; + +#define GET_STAT_U32(attr) rta_getattr_u32(st[TCA_CAKE_STATS_ ## attr]) +#define GET_STAT_S32(attr) (*(__s32 *)RTA_DATA(st[TCA_CAKE_STATS_ ## attr])) +#define GET_STAT_U64(attr) rta_getattr_u64(st[TCA_CAKE_STATS_ ## attr]) + + parse_rtattr_nested(st, TCA_CAKE_STATS_MAX, xstats); + + if (st[TCA_CAKE_STATS_MEMORY_USED] && + st[TCA_CAKE_STATS_MEMORY_LIMIT]) { + print_size(PRINT_FP, NULL, " memory used: %s", + GET_STAT_U32(MEMORY_USED)); + + print_size(PRINT_FP, NULL, " of %s\n", + GET_STAT_U32(MEMORY_LIMIT)); + + print_uint(PRINT_JSON, "memory_used", NULL, + GET_STAT_U32(MEMORY_USED)); + print_uint(PRINT_JSON, "memory_limit", NULL, + GET_STAT_U32(MEMORY_LIMIT)); + } + + if (st[TCA_CAKE_STATS_CAPACITY_ESTIMATE64]) + tc_print_rate(PRINT_ANY, "capacity_estimate", + " capacity estimate: %s\n", + GET_STAT_U64(CAPACITY_ESTIMATE64)); + + if (st[TCA_CAKE_STATS_MIN_NETLEN] && + st[TCA_CAKE_STATS_MAX_NETLEN]) { + print_uint(PRINT_ANY, "min_network_size", + " min/max network layer size: %12u", + GET_STAT_U32(MIN_NETLEN)); + print_uint(PRINT_ANY, "max_network_size", + " /%8u\n", GET_STAT_U32(MAX_NETLEN)); + } + + if (st[TCA_CAKE_STATS_MIN_ADJLEN] && + st[TCA_CAKE_STATS_MAX_ADJLEN]) { + print_uint(PRINT_ANY, "min_adj_size", + " min/max overhead-adjusted size: %8u", + GET_STAT_U32(MIN_ADJLEN)); + print_uint(PRINT_ANY, "max_adj_size", + " /%8u\n", GET_STAT_U32(MAX_ADJLEN)); + } + + if (st[TCA_CAKE_STATS_AVG_NETOFF]) + print_uint(PRINT_ANY, "avg_hdr_offset", + " average network hdr offset: %12u\n\n", + GET_STAT_U32(AVG_NETOFF)); + + /* class stats */ + if (st[TCA_CAKE_STATS_DEFICIT]) + print_int(PRINT_ANY, "deficit", " deficit %d", + GET_STAT_S32(DEFICIT)); + if (st[TCA_CAKE_STATS_COBALT_COUNT]) + print_uint(PRINT_ANY, "count", " count %u", + GET_STAT_U32(COBALT_COUNT)); + + if (st[TCA_CAKE_STATS_DROPPING] && GET_STAT_U32(DROPPING)) { + print_bool(PRINT_ANY, "dropping", " dropping", true); + if (st[TCA_CAKE_STATS_DROP_NEXT_US]) { + int drop_next = GET_STAT_S32(DROP_NEXT_US); + + if (drop_next < 0) { + print_string(PRINT_FP, NULL, " drop_next -%s", + sprint_time(-drop_next, b1)); + } else { + print_uint(PRINT_JSON, "drop_next", NULL, + drop_next); + print_string(PRINT_FP, NULL, " drop_next %s", + sprint_time(drop_next, b1)); + } + } + } + + if (st[TCA_CAKE_STATS_P_DROP]) { + print_uint(PRINT_ANY, "blue_prob", " blue_prob %u", + GET_STAT_U32(P_DROP)); + if (st[TCA_CAKE_STATS_BLUE_TIMER_US]) { + int blue_timer = GET_STAT_S32(BLUE_TIMER_US); + + if (blue_timer < 0) { + print_string(PRINT_FP, NULL, " blue_timer -%s", + sprint_time(blue_timer, b1)); + } else { + print_uint(PRINT_JSON, "blue_timer", NULL, + blue_timer); + print_string(PRINT_FP, NULL, " blue_timer %s", + sprint_time(blue_timer, b1)); + } + } + } + +#undef GET_STAT_U32 +#undef GET_STAT_S32 +#undef GET_STAT_U64 + + if (st[TCA_CAKE_STATS_TIN_STATS]) { + struct rtattr *tstat[TC_CAKE_MAX_TINS][TCA_CAKE_TIN_STATS_MAX + 1]; + struct rtattr *tins[TC_CAKE_MAX_TINS + 1]; + int num_tins = 0; + + parse_rtattr_nested(tins, TC_CAKE_MAX_TINS, + st[TCA_CAKE_STATS_TIN_STATS]); + + for (i = 1; i <= TC_CAKE_MAX_TINS && tins[i]; i++) { + parse_rtattr_nested(tstat[i-1], TCA_CAKE_TIN_STATS_MAX, + tins[i]); + num_tins++; + } + + if (!num_tins) + return 0; + + if (is_json_context()) { + open_json_array(PRINT_JSON, "tins"); + for (i = 0; i < num_tins; i++) + cake_print_json_tin(tstat[i]); + close_json_array(PRINT_JSON, NULL); + + return 0; + } + + + switch (num_tins) { + case 3: + fprintf(f, " Bulk Best Effort Voice\n"); + break; + + case 4: + fprintf(f, " Bulk Best Effort Video Voice\n"); + break; + + default: + fprintf(f, " "); + for (i = 0; i < num_tins; i++) + fprintf(f, " Tin %u", i); + fprintf(f, "%s", _SL_); + }; + +#define GET_TSTAT(i, attr) (tstat[i][TCA_CAKE_TIN_STATS_ ## attr]) +#define PRINT_TSTAT(name, attr, fmts, val) do { \ + if (GET_TSTAT(0, attr)) { \ + fprintf(f, name); \ + for (i = 0; i < num_tins; i++) \ + fprintf(f, " %12" fmts, val); \ + fprintf(f, "%s", _SL_); \ + } \ + } while (0) + +#define SPRINT_TSTAT(pfunc, type, name, attr) PRINT_TSTAT( \ + name, attr, "s", sprint_ ## pfunc( \ + rta_getattr_ ## type(GET_TSTAT(i, attr)), b1)) + +#define PRINT_TSTAT_U32(name, attr) PRINT_TSTAT( \ + name, attr, "u", rta_getattr_u32(GET_TSTAT(i, attr))) + +#define PRINT_TSTAT_U64(name, attr) PRINT_TSTAT( \ + name, attr, "llu", rta_getattr_u64(GET_TSTAT(i, attr))) + + if (GET_TSTAT(0, THRESHOLD_RATE64)) { + fprintf(f, " thresh "); + for (i = 0; i < num_tins; i++) + tc_print_rate(PRINT_FP, NULL, " %12s", + rta_getattr_u64(GET_TSTAT(i, THRESHOLD_RATE64))); + fprintf(f, "%s", _SL_); + } + + SPRINT_TSTAT(time, u32, " target ", TARGET_US); + SPRINT_TSTAT(time, u32, " interval", INTERVAL_US); + SPRINT_TSTAT(time, u32, " pk_delay", PEAK_DELAY_US); + SPRINT_TSTAT(time, u32, " av_delay", AVG_DELAY_US); + SPRINT_TSTAT(time, u32, " sp_delay", BASE_DELAY_US); + SPRINT_TSTAT(size, u32, " backlog ", BACKLOG_BYTES); + + PRINT_TSTAT_U32(" pkts ", SENT_PACKETS); + PRINT_TSTAT_U64(" bytes ", SENT_BYTES64); + + PRINT_TSTAT_U32(" way_inds", WAY_INDIRECT_HITS); + PRINT_TSTAT_U32(" way_miss", WAY_MISSES); + PRINT_TSTAT_U32(" way_cols", WAY_COLLISIONS); + PRINT_TSTAT_U32(" drops ", DROPPED_PACKETS); + PRINT_TSTAT_U32(" marks ", ECN_MARKED_PACKETS); + PRINT_TSTAT_U32(" ack_drop", ACKS_DROPPED_PACKETS); + PRINT_TSTAT_U32(" sp_flows", SPARSE_FLOWS); + PRINT_TSTAT_U32(" bk_flows", BULK_FLOWS); + PRINT_TSTAT_U32(" un_flows", UNRESPONSIVE_FLOWS); + PRINT_TSTAT_U32(" max_len ", MAX_SKBLEN); + PRINT_TSTAT_U32(" quantum ", FLOW_QUANTUM); + +#undef GET_STAT +#undef PRINT_TSTAT +#undef SPRINT_TSTAT +#undef PRINT_TSTAT_U32 +#undef PRINT_TSTAT_U64 + } + return 0; +} + +struct qdisc_util cake_qdisc_util = { + .id = "cake", + .parse_qopt = cake_parse_opt, + .print_qopt = cake_print_opt, + .print_xstats = cake_print_xstats, +}; diff --git a/tc/q_cbs.c b/tc/q_cbs.c new file mode 100644 index 0000000..788535c --- /dev/null +++ b/tc/q_cbs.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_cbs.c CBS. + * + * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... cbs hicredit BYTES locredit BYTES sendslope BPS idleslope BPS\n" + " [offload 0|1]\n"); +} + +static void explain1(const char *arg, const char *val) +{ + fprintf(stderr, "cbs: illegal value for \"%s\": \"%s\"\n", arg, val); +} + +static int cbs_parse_opt(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev) +{ + struct tc_cbs_qopt opt = {}; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "offload") == 0) { + NEXT_ARG(); + if (opt.offload) { + fprintf(stderr, "cbs: duplicate \"offload\" specification\n"); + return -1; + } + if (get_u8(&opt.offload, *argv, 0)) { + explain1("offload", *argv); + return -1; + } + } else if (matches(*argv, "hicredit") == 0) { + NEXT_ARG(); + if (opt.hicredit) { + fprintf(stderr, "cbs: duplicate \"hicredit\" specification\n"); + return -1; + } + if (get_s32(&opt.hicredit, *argv, 0)) { + explain1("hicredit", *argv); + return -1; + } + } else if (matches(*argv, "locredit") == 0) { + NEXT_ARG(); + if (opt.locredit) { + fprintf(stderr, "cbs: duplicate \"locredit\" specification\n"); + return -1; + } + if (get_s32(&opt.locredit, *argv, 0)) { + explain1("locredit", *argv); + return -1; + } + } else if (matches(*argv, "sendslope") == 0) { + NEXT_ARG(); + if (opt.sendslope) { + fprintf(stderr, "cbs: duplicate \"sendslope\" specification\n"); + return -1; + } + if (get_s32(&opt.sendslope, *argv, 0)) { + explain1("sendslope", *argv); + return -1; + } + } else if (matches(*argv, "idleslope") == 0) { + NEXT_ARG(); + if (opt.idleslope) { + fprintf(stderr, "cbs: duplicate \"idleslope\" specification\n"); + return -1; + } + if (get_s32(&opt.idleslope, *argv, 0)) { + explain1("idleslope", *argv); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "cbs: unknown parameter \"%s\"\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 2024, TCA_CBS_PARMS, &opt, sizeof(opt)); + addattr_nest_end(n, tail); + return 0; +} + +static int cbs_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_CBS_MAX+1]; + struct tc_cbs_qopt *qopt; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CBS_MAX, opt); + + if (tb[TCA_CBS_PARMS] == NULL) + return -1; + + qopt = RTA_DATA(tb[TCA_CBS_PARMS]); + if (RTA_PAYLOAD(tb[TCA_CBS_PARMS]) < sizeof(*qopt)) + return -1; + + print_int(PRINT_ANY, "hicredit", "hicredit %d ", qopt->hicredit); + print_int(PRINT_ANY, "locredit", "locredit %d ", qopt->locredit); + print_int(PRINT_ANY, "sendslope", "sendslope %d ", qopt->sendslope); + print_int(PRINT_ANY, "idleslope", "idleslope %d ", qopt->idleslope); + print_int(PRINT_ANY, "offload", "offload %d ", qopt->offload); + + return 0; +} + +struct qdisc_util cbs_qdisc_util = { + .id = "cbs", + .parse_qopt = cbs_parse_opt, + .print_qopt = cbs_print_opt, +}; diff --git a/tc/q_choke.c b/tc/q_choke.c new file mode 100644 index 0000000..7653eb7 --- /dev/null +++ b/tc/q_choke.c @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_choke.c CHOKE. + * + * Authors: Stephen Hemminger <shemminger@vyatta.com> + */ + +#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_red.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... choke limit PACKETS bandwidth KBPS [ecn]\n" + " [ min PACKETS ] [ max PACKETS ] [ burst PACKETS ]\n"); +} + +static int choke_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_red_qopt opt = {}; + unsigned int burst = 0; + unsigned int avpkt = 1000; + double probability = 0.02; + unsigned int rate = 0; + int ecn_ok = 0; + int wlog; + __u8 sbuf[256]; + __u32 max_P; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&opt.limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "bandwidth") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&rate, *argv, dev)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + } else if (get_rate(&rate, *argv)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + ecn_ok = 1; + } else if (strcmp(*argv, "min") == 0) { + NEXT_ARG(); + if (get_unsigned(&opt.qth_min, *argv, 0)) { + fprintf(stderr, "Illegal \"min\"\n"); + return -1; + } + } else if (strcmp(*argv, "max") == 0) { + NEXT_ARG(); + if (get_unsigned(&opt.qth_max, *argv, 0)) { + fprintf(stderr, "Illegal \"max\"\n"); + return -1; + } + } else if (strcmp(*argv, "burst") == 0) { + NEXT_ARG(); + if (get_unsigned(&burst, *argv, 0)) { + fprintf(stderr, "Illegal \"burst\"\n"); + return -1; + } + } else if (strcmp(*argv, "avpkt") == 0) { + NEXT_ARG(); + if (get_size(&avpkt, *argv)) { + fprintf(stderr, "Illegal \"avpkt\"\n"); + return -1; + } + } else if (strcmp(*argv, "probability") == 0) { + NEXT_ARG(); + if (sscanf(*argv, "%lg", &probability) != 1) { + fprintf(stderr, "Illegal \"probability\"\n"); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!rate || !opt.limit) { + fprintf(stderr, "Required parameter (bandwidth, limit) is missing\n"); + return -1; + } + + /* Compute default min/max thresholds based on + Sally Floyd's recommendations: + http://www.icir.org/floyd/REDparameters.txt + */ + if (!opt.qth_max) + opt.qth_max = opt.limit / 4; + if (!opt.qth_min) + opt.qth_min = opt.qth_max / 3; + if (!burst) + burst = (2 * opt.qth_min + opt.qth_max) / 3; + + if (opt.qth_max > opt.limit) { + fprintf(stderr, "\"max\" is larger than \"limit\"\n"); + return -1; + } + + if (opt.qth_min >= opt.qth_max) { + fprintf(stderr, "\"min\" is not smaller than \"max\"\n"); + return -1; + } + + wlog = tc_red_eval_ewma(opt.qth_min*avpkt, burst, avpkt); + if (wlog < 0) { + fprintf(stderr, "CHOKE: failed to calculate EWMA constant.\n"); + return -1; + } + if (wlog >= 10) + fprintf(stderr, "CHOKE: WARNING. Burst %d seems to be too large.\n", burst); + opt.Wlog = wlog; + + wlog = tc_red_eval_P(opt.qth_min*avpkt, opt.qth_max*avpkt, probability); + if (wlog < 0) { + fprintf(stderr, "CHOKE: failed to calculate probability.\n"); + return -1; + } + opt.Plog = wlog; + + wlog = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf); + if (wlog < 0) { + fprintf(stderr, "CHOKE: failed to calculate idle damping table.\n"); + return -1; + } + opt.Scell_log = wlog; + if (ecn_ok) + opt.flags |= TC_RED_ECN; + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 1024, TCA_CHOKE_PARMS, &opt, sizeof(opt)); + addattr_l(n, 1024, TCA_CHOKE_STAB, sbuf, 256); + max_P = probability * pow(2, 32); + addattr_l(n, 1024, TCA_CHOKE_MAX_P, &max_P, sizeof(max_P)); + addattr_nest_end(n, tail); + return 0; +} + +static int choke_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_CHOKE_MAX+1]; + const struct tc_red_qopt *qopt; + __u32 max_P = 0; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CHOKE_MAX, opt); + + if (tb[TCA_CHOKE_PARMS] == NULL) + return -1; + qopt = RTA_DATA(tb[TCA_CHOKE_PARMS]); + if (RTA_PAYLOAD(tb[TCA_CHOKE_PARMS]) < sizeof(*qopt)) + return -1; + if (tb[TCA_CHOKE_MAX_P] && + RTA_PAYLOAD(tb[TCA_CHOKE_MAX_P]) >= sizeof(__u32)) + max_P = rta_getattr_u32(tb[TCA_CHOKE_MAX_P]); + + print_uint(PRINT_ANY, "limit", "limit %up ", qopt->limit); + print_uint(PRINT_ANY, "min", "min %up ", qopt->qth_min); + print_uint(PRINT_ANY, "max", "max %up ", qopt->qth_max); + + tc_red_print_flags(qopt->flags); + + if (show_details) { + print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog); + + if (max_P) + print_float(PRINT_ANY, "probability", + "probability %lg ", max_P / pow(2, 32)); + else + print_uint(PRINT_ANY, "Plog", "Plog %u ", qopt->Plog); + + print_uint(PRINT_ANY, "Scell_log", "Scell_log %u", + qopt->Scell_log); + } + return 0; +} + +static int choke_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_choke_xstats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + + print_uint(PRINT_ANY, "marked", " marked %u", st->marked); + print_uint(PRINT_ANY, "early", " early %u", st->early); + print_uint(PRINT_ANY, "pdrop", " pdrop %u", st->pdrop); + print_uint(PRINT_ANY, "other", " other %u", st->other); + print_uint(PRINT_ANY, "matched", " matched %u", st->matched); + + return 0; + +} + +struct qdisc_util choke_qdisc_util = { + .id = "choke", + .parse_qopt = choke_parse_opt, + .print_qopt = choke_print_opt, + .print_xstats = choke_print_xstats, +}; diff --git a/tc/q_clsact.c b/tc/q_clsact.c new file mode 100644 index 0000000..341f653 --- /dev/null +++ b/tc/q_clsact.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <stdio.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... clsact\n"); +} + +static int clsact_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + if (argc > 0) { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + + return 0; +} + +static int clsact_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + return 0; +} + +struct qdisc_util clsact_qdisc_util = { + .id = "clsact", + .parse_qopt = clsact_parse_opt, + .print_qopt = clsact_print_opt, +}; diff --git a/tc/q_codel.c b/tc/q_codel.c new file mode 100644 index 0000000..03b6f92 --- /dev/null +++ b/tc/q_codel.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Codel - The Controlled-Delay Active Queue Management algorithm + * + * Copyright (C) 2011-2012 Kathleen Nichols <nichols@pollere.com> + * Copyright (C) 2011-2012 Van Jacobson <van@pollere.com> + * Copyright (C) 2012 Michael D. Taht <dave.taht@bufferbloat.net> + * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... codel [ limit PACKETS ] [ target TIME ]\n" + " [ interval TIME ] [ ecn | noecn ]\n" + " [ ce_threshold TIME ]\n"); +} + +static int codel_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int limit = 0; + unsigned int target = 0; + unsigned int interval = 0; + unsigned int ce_threshold = ~0U; + int ecn = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "target") == 0) { + NEXT_ARG(); + if (get_time(&target, *argv)) { + fprintf(stderr, "Illegal \"target\"\n"); + return -1; + } + } else if (strcmp(*argv, "ce_threshold") == 0) { + NEXT_ARG(); + if (get_time(&ce_threshold, *argv)) { + fprintf(stderr, "Illegal \"ce_threshold\"\n"); + return -1; + } + } else if (strcmp(*argv, "interval") == 0) { + NEXT_ARG(); + if (get_time(&interval, *argv)) { + fprintf(stderr, "Illegal \"interval\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + ecn = 1; + } else if (strcmp(*argv, "noecn") == 0) { + ecn = 0; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (limit) + addattr_l(n, 1024, TCA_CODEL_LIMIT, &limit, sizeof(limit)); + if (interval) + addattr_l(n, 1024, TCA_CODEL_INTERVAL, &interval, sizeof(interval)); + if (target) + addattr_l(n, 1024, TCA_CODEL_TARGET, &target, sizeof(target)); + if (ecn != -1) + addattr_l(n, 1024, TCA_CODEL_ECN, &ecn, sizeof(ecn)); + if (ce_threshold != ~0U) + addattr_l(n, 1024, TCA_CODEL_CE_THRESHOLD, + &ce_threshold, sizeof(ce_threshold)); + + addattr_nest_end(n, tail); + return 0; +} + +static int codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_CODEL_MAX + 1]; + unsigned int limit; + unsigned int interval; + unsigned int target; + unsigned int ecn; + unsigned int ce_threshold; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_CODEL_MAX, opt); + + if (tb[TCA_CODEL_LIMIT] && + RTA_PAYLOAD(tb[TCA_CODEL_LIMIT]) >= sizeof(__u32)) { + limit = rta_getattr_u32(tb[TCA_CODEL_LIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", limit); + } + if (tb[TCA_CODEL_TARGET] && + RTA_PAYLOAD(tb[TCA_CODEL_TARGET]) >= sizeof(__u32)) { + target = rta_getattr_u32(tb[TCA_CODEL_TARGET]); + print_uint(PRINT_JSON, "target", NULL, target); + print_string(PRINT_FP, NULL, "target %s ", + sprint_time(target, b1)); + } + if (tb[TCA_CODEL_CE_THRESHOLD] && + RTA_PAYLOAD(tb[TCA_CODEL_CE_THRESHOLD]) >= sizeof(__u32)) { + ce_threshold = rta_getattr_u32(tb[TCA_CODEL_CE_THRESHOLD]); + print_uint(PRINT_JSON, "ce_threshold", NULL, ce_threshold); + print_string(PRINT_FP, NULL, "ce_threshold %s ", + sprint_time(ce_threshold, b1)); + } + if (tb[TCA_CODEL_INTERVAL] && + RTA_PAYLOAD(tb[TCA_CODEL_INTERVAL]) >= sizeof(__u32)) { + interval = rta_getattr_u32(tb[TCA_CODEL_INTERVAL]); + print_uint(PRINT_JSON, "interval", NULL, interval); + print_string(PRINT_FP, NULL, "interval %s ", + sprint_time(interval, b1)); + } + if (tb[TCA_CODEL_ECN] && + RTA_PAYLOAD(tb[TCA_CODEL_ECN]) >= sizeof(__u32)) { + ecn = rta_getattr_u32(tb[TCA_CODEL_ECN]); + if (ecn) + print_bool(PRINT_ANY, "ecn", "ecn ", true); + } + + return 0; +} + +static int codel_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_codel_xstats _st = {}, *st; + + SPRINT_BUF(b1); + + if (xstats == NULL) + return 0; + + st = RTA_DATA(xstats); + if (RTA_PAYLOAD(xstats) < sizeof(*st)) { + memcpy(&_st, st, RTA_PAYLOAD(xstats)); + st = &_st; + } + + print_uint(PRINT_ANY, "count", " count %u", st->count); + print_uint(PRINT_ANY, "lastcount", " lastcount %u", st->lastcount); + print_uint(PRINT_JSON, "ldelay", NULL, st->ldelay); + print_string(PRINT_FP, NULL, " ldelay %s", sprint_time(st->ldelay, b1)); + + if (st->dropping) + print_bool(PRINT_ANY, "dropping", " dropping", true); + + print_int(PRINT_JSON, "drop_next", NULL, st->drop_next); + if (st->drop_next < 0) + print_string(PRINT_FP, NULL, " drop_next -%s", + sprint_time(-st->drop_next, b1)); + else + print_string(PRINT_FP, NULL, " drop_next %s", + sprint_time(st->drop_next, b1)); + + print_nl(); + print_uint(PRINT_ANY, "maxpacket", " maxpacket %u", st->maxpacket); + print_uint(PRINT_ANY, "ecn_mark", " ecn_mark %u", st->ecn_mark); + print_uint(PRINT_ANY, "drop_overlimit", " drop_overlimit %u", + st->drop_overlimit); + + if (st->ce_mark) + print_uint(PRINT_ANY, "ce_mark", " ce_mark %u", st->ce_mark); + + return 0; + +} + +struct qdisc_util codel_qdisc_util = { + .id = "codel", + .parse_qopt = codel_parse_opt, + .print_qopt = codel_print_opt, + .print_xstats = codel_print_xstats, +}; diff --git a/tc/q_drr.c b/tc/q_drr.c new file mode 100644 index 0000000..03c4744 --- /dev/null +++ b/tc/q_drr.c @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_drr.c DRR. + * + * Authors: Patrick McHardy <kaber@trash.net> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... drr\n"); +} + +static void explain2(void) +{ + fprintf(stderr, "Usage: ... drr quantum SIZE\n"); +} + + +static int drr_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + while (argc) { + if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + } + return 0; +} + +static int drr_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct rtattr *tail; + __u32 tmp; + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + + while (argc > 0) { + if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_size(&tmp, *argv)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + addattr_l(n, 1024, TCA_DRR_QUANTUM, &tmp, sizeof(tmp)); + } else if (strcmp(*argv, "help") == 0) { + explain2(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain2(); + return -1; + } + argc--; argv++; + } + + addattr_nest_end(n, tail); + return 0; +} + +static int drr_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_DRR_MAX + 1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_DRR_MAX, opt); + + if (tb[TCA_DRR_QUANTUM]) + print_size(PRINT_FP, NULL, "quantum %s ", + rta_getattr_u32(tb[TCA_DRR_QUANTUM])); + return 0; +} + +static int drr_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats) +{ + struct tc_drr_stats *x; + + if (xstats == NULL) + return 0; + if (RTA_PAYLOAD(xstats) < sizeof(*x)) + return -1; + x = RTA_DATA(xstats); + + print_size(PRINT_FP, NULL, " deficit %s ", x->deficit); + return 0; +} + +struct qdisc_util drr_qdisc_util = { + .id = "drr", + .parse_qopt = drr_parse_opt, + .print_qopt = drr_print_opt, + .print_xstats = drr_print_xstats, + .parse_copt = drr_parse_class_opt, + .print_copt = drr_print_opt, +}; diff --git a/tc/q_etf.c b/tc/q_etf.c new file mode 100644 index 0000000..d16188d --- /dev/null +++ b/tc/q_etf.c @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_etf.c Earliest TxTime First (ETF). + * + * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> + * Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... etf delta NANOS clockid CLOCKID [offload] [deadline_mode]\n" + "CLOCKID must be a valid SYS-V id (i.e. CLOCK_TAI)\n"); +} + +static void explain1(const char *arg, const char *val) +{ + fprintf(stderr, "etf: illegal value for \"%s\": \"%s\"\n", arg, val); +} + +static void explain_clockid(const char *val) +{ + fprintf(stderr, + "etf: illegal value for \"clockid\": \"%s\".\n" + "It must be a valid SYS-V id (i.e. CLOCK_TAI)\n", + val); +} + +static int etf_parse_opt(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev) +{ + struct tc_etf_qopt opt = { + .clockid = CLOCKID_INVALID, + }; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "offload") == 0) { + if (opt.flags & TC_ETF_OFFLOAD_ON) { + fprintf(stderr, "etf: duplicate \"offload\" specification\n"); + return -1; + } + + opt.flags |= TC_ETF_OFFLOAD_ON; + } else if (matches(*argv, "deadline_mode") == 0) { + if (opt.flags & TC_ETF_DEADLINE_MODE_ON) { + fprintf(stderr, "etf: duplicate \"deadline_mode\" specification\n"); + return -1; + } + + opt.flags |= TC_ETF_DEADLINE_MODE_ON; + } else if (matches(*argv, "delta") == 0) { + NEXT_ARG(); + if (opt.delta) { + fprintf(stderr, "etf: duplicate \"delta\" specification\n"); + return -1; + } + if (get_s32(&opt.delta, *argv, 0)) { + explain1("delta", *argv); + return -1; + } + } else if (matches(*argv, "clockid") == 0) { + NEXT_ARG(); + if (opt.clockid != CLOCKID_INVALID) { + fprintf(stderr, "etf: duplicate \"clockid\" specification\n"); + return -1; + } + if (get_clockid(&opt.clockid, *argv)) { + explain_clockid(*argv); + return -1; + } + } else if (strcmp(*argv, "skip_sock_check") == 0) { + if (opt.flags & TC_ETF_SKIP_SOCK_CHECK) { + fprintf(stderr, "etf: duplicate \"skip_sock_check\" specification\n"); + return -1; + } + + opt.flags |= TC_ETF_SKIP_SOCK_CHECK; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "etf: unknown parameter \"%s\"\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = NLMSG_TAIL(n); + addattr_l(n, 1024, TCA_OPTIONS, NULL, 0); + addattr_l(n, 2024, TCA_ETF_PARMS, &opt, sizeof(opt)); + tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; + return 0; +} + +static int etf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_ETF_MAX+1]; + struct tc_etf_qopt *qopt; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_ETF_MAX, opt); + + if (tb[TCA_ETF_PARMS] == NULL) + return -1; + + qopt = RTA_DATA(tb[TCA_ETF_PARMS]); + if (RTA_PAYLOAD(tb[TCA_ETF_PARMS]) < sizeof(*qopt)) + return -1; + + print_string(PRINT_ANY, "clockid", "clockid %s ", + get_clock_name(qopt->clockid)); + + print_uint(PRINT_ANY, "delta", "delta %d ", qopt->delta); + print_string(PRINT_ANY, "offload", "offload %s ", + (qopt->flags & TC_ETF_OFFLOAD_ON) ? "on" : "off"); + print_string(PRINT_ANY, "deadline_mode", "deadline_mode %s ", + (qopt->flags & TC_ETF_DEADLINE_MODE_ON) ? "on" : "off"); + print_string(PRINT_ANY, "skip_sock_check", "skip_sock_check %s", + (qopt->flags & TC_ETF_SKIP_SOCK_CHECK) ? "on" : "off"); + + return 0; +} + +struct qdisc_util etf_qdisc_util = { + .id = "etf", + .parse_qopt = etf_parse_opt, + .print_qopt = etf_print_opt, +}; diff --git a/tc/q_ets.c b/tc/q_ets.c new file mode 100644 index 0000000..7380bb2 --- /dev/null +++ b/tc/q_ets.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +/* + * Enhanced Transmission Selection - 802.1Qaz-based Qdisc + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... ets [bands NUMBER] [strict NUMBER] [quanta Q1 Q2...] [priomap P1 P2...]\n"); +} + +static void cexplain(void) +{ + fprintf(stderr, "Usage: ... ets [quantum Q1]\n"); +} + +static unsigned int parse_quantum(const char *arg) +{ + unsigned int quantum; + + if (get_unsigned(&quantum, arg, 10)) { + fprintf(stderr, "Illegal \"quanta\" element\n"); + return 0; + } + if (!quantum) + fprintf(stderr, "\"quanta\" must be > 0\n"); + return quantum; +} + +static int parse_nbands(const char *arg, __u8 *pnbands, const char *what) +{ + unsigned int tmp; + + if (get_unsigned(&tmp, arg, 10)) { + fprintf(stderr, "Illegal \"%s\"\n", what); + return -1; + } + if (tmp > TCQ_ETS_MAX_BANDS) { + fprintf(stderr, "The number of \"%s\" must be <= %d\n", + what, TCQ_ETS_MAX_BANDS); + return -1; + } + + *pnbands = tmp; + return 0; +} + +static int ets_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + __u8 nbands = 0; + __u8 nstrict = 0; + bool quanta_mode = false; + unsigned int nquanta = 0; + __u32 quanta[TCQ_ETS_MAX_BANDS]; + bool priomap_mode = false; + unsigned int nprio = 0; + __u8 priomap[TC_PRIO_MAX + 1]; + unsigned int tmp; + struct rtattr *tail, *nest; + + while (argc > 0) { + if (strcmp(*argv, "bands") == 0) { + if (nbands) { + fprintf(stderr, "Duplicate \"bands\"\n"); + return -1; + } + NEXT_ARG(); + if (parse_nbands(*argv, &nbands, "bands")) + return -1; + priomap_mode = quanta_mode = false; + } else if (strcmp(*argv, "strict") == 0) { + if (nstrict) { + fprintf(stderr, "Duplicate \"strict\"\n"); + return -1; + } + NEXT_ARG(); + if (parse_nbands(*argv, &nstrict, "strict")) + return -1; + priomap_mode = quanta_mode = false; + } else if (strcmp(*argv, "quanta") == 0) { + if (nquanta) { + fprintf(stderr, "Duplicate \"quanta\"\n"); + return -1; + } + NEXT_ARG(); + priomap_mode = false; + quanta_mode = true; + goto parse_quantum; + } else if (strcmp(*argv, "priomap") == 0) { + if (nprio) { + fprintf(stderr, "Duplicate \"priomap\"\n"); + return -1; + } + NEXT_ARG(); + priomap_mode = true; + quanta_mode = false; + goto parse_priomap; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else if (quanta_mode) { + unsigned int quantum; + +parse_quantum: + quantum = parse_quantum(*argv); + if (!quantum) + return -1; + quanta[nquanta++] = quantum; + } else if (priomap_mode) { + unsigned int band; + +parse_priomap: + if (get_unsigned(&band, *argv, 10)) { + fprintf(stderr, "Illegal \"priomap\" element\n"); + return -1; + } + if (nprio > TC_PRIO_MAX) { + fprintf(stderr, "\"priomap\" index cannot be higher than %u\n", TC_PRIO_MAX); + return -1; + } + priomap[nprio++] = band; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!nbands) + nbands = nquanta + nstrict; + if (!nbands) { + fprintf(stderr, "One of \"bands\", \"quanta\" or \"strict\" needs to be specified\n"); + explain(); + return -1; + } + if (nstrict + nquanta > nbands) { + fprintf(stderr, "Not enough total bands to cover all the strict bands and quanta\n"); + explain(); + return -1; + } + for (tmp = 0; tmp < nprio; tmp++) { + if (priomap[tmp] >= nbands) { + fprintf(stderr, "\"priomap\" element is out of bounds\n"); + return -1; + } + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS | NLA_F_NESTED); + addattr_l(n, 1024, TCA_ETS_NBANDS, &nbands, sizeof(nbands)); + if (nstrict) + addattr_l(n, 1024, TCA_ETS_NSTRICT, &nstrict, sizeof(nstrict)); + if (nquanta) { + nest = addattr_nest(n, 1024, TCA_ETS_QUANTA | NLA_F_NESTED); + for (tmp = 0; tmp < nquanta; tmp++) + addattr_l(n, 1024, TCA_ETS_QUANTA_BAND, + &quanta[tmp], sizeof(quanta[0])); + addattr_nest_end(n, nest); + } + if (nprio) { + nest = addattr_nest(n, 1024, TCA_ETS_PRIOMAP | NLA_F_NESTED); + for (tmp = 0; tmp < nprio; tmp++) + addattr_l(n, 1024, TCA_ETS_PRIOMAP_BAND, + &priomap[tmp], sizeof(priomap[0])); + addattr_nest_end(n, nest); + } + addattr_nest_end(n, tail); + + return 0; +} + +static int ets_parse_copt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int quantum = 0; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "quantum") == 0) { + if (quantum) { + fprintf(stderr, "Duplicate \"quantum\"\n"); + return -1; + } + NEXT_ARG(); + quantum = parse_quantum(*argv); + if (!quantum) + return -1; + } else if (strcmp(*argv, "help") == 0) { + cexplain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + cexplain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS | NLA_F_NESTED); + if (quantum) + addattr_l(n, 1024, TCA_ETS_QUANTA_BAND, &quantum, + sizeof(quantum)); + addattr_nest_end(n, tail); + + return 0; +} + +static int ets_print_opt_quanta(struct rtattr *opt) +{ + int len = RTA_PAYLOAD(opt); + unsigned int offset; + + open_json_array(PRINT_ANY, "quanta"); + for (offset = 0; offset < len; ) { + struct rtattr *tb[TCA_ETS_MAX + 1] = {NULL}; + struct rtattr *attr; + __u32 quantum; + + attr = RTA_DATA(opt) + offset; + parse_rtattr(tb, TCA_ETS_MAX, attr, len - offset); + offset += RTA_LENGTH(RTA_PAYLOAD(attr)); + + if (!tb[TCA_ETS_QUANTA_BAND]) { + fprintf(stderr, "No ETS band quantum\n"); + return -1; + } + + quantum = rta_getattr_u32(tb[TCA_ETS_QUANTA_BAND]); + print_uint(PRINT_ANY, NULL, " %u", quantum); + + } + close_json_array(PRINT_ANY, " "); + + return 0; +} + +static int ets_print_opt_priomap(struct rtattr *opt) +{ + int len = RTA_PAYLOAD(opt); + unsigned int offset; + + open_json_array(PRINT_ANY, "priomap"); + for (offset = 0; offset < len; ) { + struct rtattr *tb[TCA_ETS_MAX + 1] = {NULL}; + struct rtattr *attr; + __u8 band; + + attr = RTA_DATA(opt) + offset; + parse_rtattr(tb, TCA_ETS_MAX, attr, len - offset); + offset += RTA_LENGTH(RTA_PAYLOAD(attr)) + 3 /* padding */; + + if (!tb[TCA_ETS_PRIOMAP_BAND]) { + fprintf(stderr, "No ETS priomap band\n"); + return -1; + } + + band = rta_getattr_u8(tb[TCA_ETS_PRIOMAP_BAND]); + print_uint(PRINT_ANY, NULL, " %u", band); + + } + close_json_array(PRINT_ANY, " "); + + return 0; +} + +static int ets_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_ETS_MAX + 1]; + __u8 nbands; + __u8 nstrict; + int err; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_ETS_MAX, opt); + + if (!tb[TCA_ETS_NBANDS] || !tb[TCA_ETS_PRIOMAP]) { + fprintf(stderr, "Incomplete ETS options\n"); + return -1; + } + + nbands = rta_getattr_u8(tb[TCA_ETS_NBANDS]); + print_uint(PRINT_ANY, "bands", "bands %u ", nbands); + + if (tb[TCA_ETS_NSTRICT]) { + nstrict = rta_getattr_u8(tb[TCA_ETS_NSTRICT]); + print_uint(PRINT_ANY, "strict", "strict %u ", nstrict); + } + + if (tb[TCA_ETS_QUANTA]) { + err = ets_print_opt_quanta(tb[TCA_ETS_QUANTA]); + if (err) + return err; + } + + return ets_print_opt_priomap(tb[TCA_ETS_PRIOMAP]); +} + +static int ets_print_copt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_ETS_MAX + 1]; + __u32 quantum; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_ETS_MAX, opt); + + if (tb[TCA_ETS_QUANTA_BAND]) { + quantum = rta_getattr_u32(tb[TCA_ETS_QUANTA_BAND]); + print_uint(PRINT_ANY, "quantum", "quantum %u ", quantum); + } + + return 0; +} + +struct qdisc_util ets_qdisc_util = { + .id = "ets", + .parse_qopt = ets_parse_opt, + .parse_copt = ets_parse_copt, + .print_qopt = ets_print_opt, + .print_copt = ets_print_copt, +}; diff --git a/tc/q_fifo.c b/tc/q_fifo.c new file mode 100644 index 0000000..9b2c534 --- /dev/null +++ b/tc/q_fifo.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_fifo.c FIFO. + * + * 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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... <[p|b]fifo | pfifo_head_drop> [ limit NUMBER ]\n"); +} + +static int fifo_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + int ok = 0; + struct tc_fifo_qopt opt = {}; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + fprintf(stderr, "%s: Illegal value for \"limit\": \"%s\"\n", qu->id, *argv); + return -1; + } + ok++; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "%s: unknown parameter \"%s\"\n", qu->id, *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (ok) + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + return 0; +} + +static int fifo_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_fifo_qopt *qopt; + + if (opt == NULL) + return 0; + + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + return -1; + qopt = RTA_DATA(opt); + if (strcmp(qu->id, "bfifo") == 0) + print_size(PRINT_ANY, "limit", "limit %s", qopt->limit); + else + print_uint(PRINT_ANY, "limit", "limit %up", qopt->limit); + return 0; +} + + +struct qdisc_util bfifo_qdisc_util = { + .id = "bfifo", + .parse_qopt = fifo_parse_opt, + .print_qopt = fifo_print_opt, +}; + +struct qdisc_util pfifo_qdisc_util = { + .id = "pfifo", + .parse_qopt = fifo_parse_opt, + .print_qopt = fifo_print_opt, +}; + +struct qdisc_util pfifo_head_drop_qdisc_util = { + .id = "pfifo_head_drop", + .parse_qopt = fifo_parse_opt, + .print_qopt = fifo_print_opt, +}; + +struct qdisc_util pfifo_fast_qdisc_util = { + .id = "pfifo_fast", + .print_qopt = prio_print_opt, +}; diff --git a/tc/q_fq.c b/tc/q_fq.c new file mode 100644 index 0000000..7f8a2b8 --- /dev/null +++ b/tc/q_fq.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Fair Queue + * + * Copyright (C) 2013-2015 Eric Dumazet <edumazet@google.com> + */ + +#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 <stdbool.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... fq [ limit PACKETS ] [ flow_limit PACKETS ]\n" + " [ quantum BYTES ] [ initial_quantum BYTES ]\n" + " [ maxrate RATE ] [ buckets NUMBER ]\n" + " [ [no]pacing ] [ refill_delay TIME ]\n" + " [ bands 3 priomap P0 P1 ... P14 P15 ]\n" + " [ weights W1 W2 W3 ]\n" + " [ low_rate_threshold RATE ]\n" + " [ orphan_mask MASK]\n" + " [ timer_slack TIME]\n" + " [ ce_threshold TIME ]\n" + " [ horizon TIME ]\n" + " [ horizon_{cap|drop} ]\n"); +} + +static unsigned int ilog2(unsigned int val) +{ + unsigned int res = 0; + + val--; + while (val) { + res++; + val >>= 1; + } + return res; +} + +static int fq_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_prio_qopt prio2band; + unsigned int plimit; + unsigned int flow_plimit; + unsigned int quantum; + unsigned int initial_quantum; + unsigned int buckets = 0; + unsigned int maxrate; + unsigned int low_rate_threshold; + unsigned int defrate; + unsigned int refill_delay; + unsigned int orphan_mask; + unsigned int ce_threshold; + unsigned int timer_slack; + unsigned int horizon; + __u8 horizon_drop = 255; + bool set_plimit = false; + bool set_flow_plimit = false; + bool set_quantum = false; + bool set_initial_quantum = false; + bool set_maxrate = false; + bool set_defrate = false; + bool set_refill_delay = false; + bool set_orphan_mask = false; + bool set_low_rate_threshold = false; + bool set_ce_threshold = false; + bool set_timer_slack = false; + bool set_horizon = false; + bool set_priomap = false; + bool set_weights = false; + int weights[FQ_BANDS]; + int pacing = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&plimit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + set_plimit = true; + } else if (strcmp(*argv, "flow_limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&flow_plimit, *argv, 0)) { + fprintf(stderr, "Illegal \"flow_limit\"\n"); + return -1; + } + set_flow_plimit = true; + } else if (strcmp(*argv, "buckets") == 0) { + NEXT_ARG(); + if (get_unsigned(&buckets, *argv, 0)) { + fprintf(stderr, "Illegal \"buckets\"\n"); + return -1; + } + } else if (strcmp(*argv, "maxrate") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&maxrate, *argv, dev)) { + fprintf(stderr, "Illegal \"maxrate\"\n"); + return -1; + } + } else if (get_rate(&maxrate, *argv)) { + fprintf(stderr, "Illegal \"maxrate\"\n"); + return -1; + } + set_maxrate = true; + } else if (strcmp(*argv, "low_rate_threshold") == 0) { + NEXT_ARG(); + if (get_rate(&low_rate_threshold, *argv)) { + fprintf(stderr, "Illegal \"low_rate_threshold\"\n"); + return -1; + } + set_low_rate_threshold = true; + } else if (strcmp(*argv, "ce_threshold") == 0) { + NEXT_ARG(); + if (get_time(&ce_threshold, *argv)) { + fprintf(stderr, "Illegal \"ce_threshold\"\n"); + return -1; + } + set_ce_threshold = true; + } else if (strcmp(*argv, "timer_slack") == 0) { + __s64 t64; + + NEXT_ARG(); + if (get_time64(&t64, *argv)) { + fprintf(stderr, "Illegal \"timer_slack\"\n"); + return -1; + } + timer_slack = t64; + if (timer_slack != t64) { + fprintf(stderr, "Illegal (out of range) \"timer_slack\"\n"); + return -1; + } + set_timer_slack = true; + } else if (strcmp(*argv, "horizon_drop") == 0) { + horizon_drop = 1; + } else if (strcmp(*argv, "horizon_cap") == 0) { + horizon_drop = 0; + } else if (strcmp(*argv, "horizon") == 0) { + NEXT_ARG(); + if (get_time(&horizon, *argv)) { + fprintf(stderr, "Illegal \"horizon\"\n"); + return -1; + } + set_horizon = true; + } else if (strcmp(*argv, "defrate") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&defrate, *argv, dev)) { + fprintf(stderr, "Illegal \"defrate\"\n"); + return -1; + } + } else if (get_rate(&defrate, *argv)) { + fprintf(stderr, "Illegal \"defrate\"\n"); + return -1; + } + set_defrate = true; + } else if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + set_quantum = true; + } else if (strcmp(*argv, "initial_quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&initial_quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"initial_quantum\"\n"); + return -1; + } + set_initial_quantum = true; + } else if (strcmp(*argv, "orphan_mask") == 0) { + NEXT_ARG(); + if (get_unsigned(&orphan_mask, *argv, 0)) { + fprintf(stderr, "Illegal \"initial_quantum\"\n"); + return -1; + } + set_orphan_mask = true; + } else if (strcmp(*argv, "refill_delay") == 0) { + NEXT_ARG(); + if (get_time(&refill_delay, *argv)) { + fprintf(stderr, "Illegal \"refill_delay\"\n"); + return -1; + } + set_refill_delay = true; + } else if (strcmp(*argv, "pacing") == 0) { + pacing = 1; + } else if (strcmp(*argv, "nopacing") == 0) { + pacing = 0; + } else if (strcmp(*argv, "bands") == 0) { + int idx; + + if (set_priomap) { + fprintf(stderr, "Duplicate \"bands\"\n"); + return -1; + } + memset(&prio2band, 0, sizeof(prio2band)); + NEXT_ARG(); + if (get_integer(&prio2band.bands, *argv, 10)) { + fprintf(stderr, "Illegal \"bands\"\n"); + return -1; + } + if (prio2band.bands != 3) { + fprintf(stderr, "\"bands\" must be 3\n"); + return -1; + } + NEXT_ARG(); + if (strcmp(*argv, "priomap") != 0) { + fprintf(stderr, "\"priomap\" expected\n"); + return -1; + } + for (idx = 0; idx <= TC_PRIO_MAX; ++idx) { + unsigned band; + + if (!NEXT_ARG_OK()) { + fprintf(stderr, "Not enough elements in priomap\n"); + return -1; + } + NEXT_ARG(); + if (get_unsigned(&band, *argv, 10)) { + fprintf(stderr, "Illegal \"priomap\" element, number in [0..%u] expected\n", + prio2band.bands - 1); + return -1; + } + if (band >= prio2band.bands) { + fprintf(stderr, "\"priomap\" element %u too big\n", band); + return -1; + } + prio2band.priomap[idx] = band; + } + set_priomap = true; + } else if (strcmp(*argv, "weights") == 0) { + int idx; + + if (set_weights) { + fprintf(stderr, "Duplicate \"weights\"\n"); + return -1; + } + NEXT_ARG(); + for (idx = 0; idx < FQ_BANDS; ++idx) { + int val; + + if (!NEXT_ARG_OK()) { + fprintf(stderr, "Not enough elements in weights\n"); + return -1; + } + NEXT_ARG(); + if (get_integer(&val, *argv, 10)) { + fprintf(stderr, "Illegal \"weights\" element, positive number expected\n"); + return -1; + } + if (val < FQ_MIN_WEIGHT) { + fprintf(stderr, "\"weight\" element %d too small\n", val); + return -1; + } + weights[idx] = val; + } + set_weights = true; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (buckets) { + unsigned int log = ilog2(buckets); + + addattr_l(n, 1024, TCA_FQ_BUCKETS_LOG, + &log, sizeof(log)); + } + if (set_plimit) + addattr_l(n, 1024, TCA_FQ_PLIMIT, + &plimit, sizeof(plimit)); + if (set_flow_plimit) + addattr_l(n, 1024, TCA_FQ_FLOW_PLIMIT, + &flow_plimit, sizeof(flow_plimit)); + if (set_quantum) + addattr_l(n, 1024, TCA_FQ_QUANTUM, &quantum, sizeof(quantum)); + if (set_initial_quantum) + addattr_l(n, 1024, TCA_FQ_INITIAL_QUANTUM, + &initial_quantum, sizeof(initial_quantum)); + if (pacing != -1) + addattr_l(n, 1024, TCA_FQ_RATE_ENABLE, + &pacing, sizeof(pacing)); + if (set_maxrate) + addattr_l(n, 1024, TCA_FQ_FLOW_MAX_RATE, + &maxrate, sizeof(maxrate)); + if (set_low_rate_threshold) + addattr_l(n, 1024, TCA_FQ_LOW_RATE_THRESHOLD, + &low_rate_threshold, sizeof(low_rate_threshold)); + if (set_defrate) + addattr_l(n, 1024, TCA_FQ_FLOW_DEFAULT_RATE, + &defrate, sizeof(defrate)); + if (set_refill_delay) + addattr_l(n, 1024, TCA_FQ_FLOW_REFILL_DELAY, + &refill_delay, sizeof(refill_delay)); + if (set_orphan_mask) + addattr_l(n, 1024, TCA_FQ_ORPHAN_MASK, + &orphan_mask, sizeof(orphan_mask)); + if (set_ce_threshold) + addattr_l(n, 1024, TCA_FQ_CE_THRESHOLD, + &ce_threshold, sizeof(ce_threshold)); + if (set_timer_slack) + addattr_l(n, 1024, TCA_FQ_TIMER_SLACK, + &timer_slack, sizeof(timer_slack)); + if (set_horizon) + addattr_l(n, 1024, TCA_FQ_HORIZON, + &horizon, sizeof(horizon)); + if (horizon_drop != 255) + addattr_l(n, 1024, TCA_FQ_HORIZON_DROP, + &horizon_drop, sizeof(horizon_drop)); + if (set_priomap) + addattr_l(n, 1024, TCA_FQ_PRIOMAP, + &prio2band, sizeof(prio2band)); + if (set_weights) + addattr_l(n, 1024, TCA_FQ_WEIGHTS, + weights, sizeof(weights)); + addattr_nest_end(n, tail); + return 0; +} + +static int fq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_FQ_MAX + 1]; + unsigned int plimit, flow_plimit; + unsigned int buckets_log; + int pacing; + unsigned int rate, quantum; + unsigned int refill_delay; + unsigned int orphan_mask; + unsigned int ce_threshold; + unsigned int timer_slack; + unsigned int horizon; + __u8 horizon_drop; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_FQ_MAX, opt); + + if (tb[TCA_FQ_PLIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_PLIMIT]) >= sizeof(__u32)) { + plimit = rta_getattr_u32(tb[TCA_FQ_PLIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", plimit); + } + if (tb[TCA_FQ_FLOW_PLIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_PLIMIT]) >= sizeof(__u32)) { + flow_plimit = rta_getattr_u32(tb[TCA_FQ_FLOW_PLIMIT]); + print_uint(PRINT_ANY, "flow_limit", "flow_limit %up ", + flow_plimit); + } + if (tb[TCA_FQ_BUCKETS_LOG] && + RTA_PAYLOAD(tb[TCA_FQ_BUCKETS_LOG]) >= sizeof(__u32)) { + buckets_log = rta_getattr_u32(tb[TCA_FQ_BUCKETS_LOG]); + print_uint(PRINT_ANY, "buckets", "buckets %u ", + 1U << buckets_log); + } + if (tb[TCA_FQ_ORPHAN_MASK] && + RTA_PAYLOAD(tb[TCA_FQ_ORPHAN_MASK]) >= sizeof(__u32)) { + orphan_mask = rta_getattr_u32(tb[TCA_FQ_ORPHAN_MASK]); + print_uint(PRINT_ANY, "orphan_mask", "orphan_mask %u ", + orphan_mask); + } + if (tb[TCA_FQ_RATE_ENABLE] && + RTA_PAYLOAD(tb[TCA_FQ_RATE_ENABLE]) >= sizeof(int)) { + pacing = rta_getattr_u32(tb[TCA_FQ_RATE_ENABLE]); + if (pacing == 0) + print_bool(PRINT_ANY, "pacing", "nopacing ", false); + } + if (tb[TCA_FQ_PRIOMAP] && + RTA_PAYLOAD(tb[TCA_FQ_PRIOMAP]) >= sizeof(struct tc_prio_qopt)) { + struct tc_prio_qopt *prio2band = RTA_DATA(tb[TCA_FQ_PRIOMAP]); + int i; + + print_uint(PRINT_ANY, "bands", "bands %u ", prio2band->bands); + open_json_array(PRINT_ANY, "priomap "); + for (i = 0; i <= TC_PRIO_MAX; i++) + print_uint(PRINT_ANY, NULL, "%d ", prio2band->priomap[i]); + close_json_array(PRINT_ANY, ""); + } + if (tb[TCA_FQ_WEIGHTS] && + RTA_PAYLOAD(tb[TCA_FQ_WEIGHTS]) >= FQ_BANDS * sizeof(int)) { + const int *weights = RTA_DATA(tb[TCA_FQ_WEIGHTS]); + int i; + + open_json_array(PRINT_ANY, "weights "); + for (i = 0; i < FQ_BANDS; ++i) + print_uint(PRINT_ANY, NULL, "%d ", weights[i]); + close_json_array(PRINT_ANY, ""); + } + if (tb[TCA_FQ_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_QUANTUM]); + print_size(PRINT_ANY, "quantum", "quantum %s ", quantum); + } + if (tb[TCA_FQ_INITIAL_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_INITIAL_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_INITIAL_QUANTUM]); + print_size(PRINT_ANY, "initial_quantum", "initial_quantum %s ", + quantum); + } + if (tb[TCA_FQ_FLOW_MAX_RATE] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_MAX_RATE]) >= sizeof(__u32)) { + rate = rta_getattr_u32(tb[TCA_FQ_FLOW_MAX_RATE]); + + if (rate != ~0U) + tc_print_rate(PRINT_ANY, + "maxrate", "maxrate %s ", rate); + } + if (tb[TCA_FQ_FLOW_DEFAULT_RATE] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_DEFAULT_RATE]) >= sizeof(__u32)) { + rate = rta_getattr_u32(tb[TCA_FQ_FLOW_DEFAULT_RATE]); + + if (rate != 0) + tc_print_rate(PRINT_ANY, + "defrate", "defrate %s ", rate); + } + if (tb[TCA_FQ_LOW_RATE_THRESHOLD] && + RTA_PAYLOAD(tb[TCA_FQ_LOW_RATE_THRESHOLD]) >= sizeof(__u32)) { + rate = rta_getattr_u32(tb[TCA_FQ_LOW_RATE_THRESHOLD]); + + if (rate != 0) + tc_print_rate(PRINT_ANY, "low_rate_threshold", + "low_rate_threshold %s ", rate); + } + if (tb[TCA_FQ_FLOW_REFILL_DELAY] && + RTA_PAYLOAD(tb[TCA_FQ_FLOW_REFILL_DELAY]) >= sizeof(__u32)) { + refill_delay = rta_getattr_u32(tb[TCA_FQ_FLOW_REFILL_DELAY]); + print_uint(PRINT_JSON, "refill_delay", NULL, refill_delay); + print_string(PRINT_FP, NULL, "refill_delay %s ", + sprint_time(refill_delay, b1)); + } + + if (tb[TCA_FQ_CE_THRESHOLD] && + RTA_PAYLOAD(tb[TCA_FQ_CE_THRESHOLD]) >= sizeof(__u32)) { + ce_threshold = rta_getattr_u32(tb[TCA_FQ_CE_THRESHOLD]); + if (ce_threshold != ~0U) { + print_uint(PRINT_JSON, "ce_threshold", NULL, + ce_threshold); + print_string(PRINT_FP, NULL, "ce_threshold %s ", + sprint_time(ce_threshold, b1)); + } + } + + if (tb[TCA_FQ_TIMER_SLACK] && + RTA_PAYLOAD(tb[TCA_FQ_TIMER_SLACK]) >= sizeof(__u32)) { + timer_slack = rta_getattr_u32(tb[TCA_FQ_TIMER_SLACK]); + print_uint(PRINT_JSON, "timer_slack", NULL, timer_slack); + print_string(PRINT_FP, NULL, "timer_slack %s ", + sprint_time64(timer_slack, b1)); + } + + if (tb[TCA_FQ_HORIZON] && + RTA_PAYLOAD(tb[TCA_FQ_HORIZON]) >= sizeof(__u32)) { + horizon = rta_getattr_u32(tb[TCA_FQ_HORIZON]); + print_uint(PRINT_JSON, "horizon", NULL, horizon); + print_string(PRINT_FP, NULL, "horizon %s ", + sprint_time(horizon, b1)); + } + + if (tb[TCA_FQ_HORIZON_DROP] && + RTA_PAYLOAD(tb[TCA_FQ_HORIZON_DROP]) >= sizeof(__u8)) { + horizon_drop = rta_getattr_u8(tb[TCA_FQ_HORIZON_DROP]); + if (!horizon_drop) + print_null(PRINT_ANY, "horizon_cap", "horizon_cap ", NULL); + else + print_null(PRINT_ANY, "horizon_drop", "horizon_drop ", NULL); + } + + return 0; +} + +static int fq_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_fq_qd_stats *st, _st; + + SPRINT_BUF(b1); + + if (xstats == NULL) + return 0; + + memset(&_st, 0, sizeof(_st)); + memcpy(&_st, RTA_DATA(xstats), min(RTA_PAYLOAD(xstats), sizeof(*st))); + + st = &_st; + + print_uint(PRINT_ANY, "flows", " flows %u", st->flows); + print_uint(PRINT_ANY, "inactive", " (inactive %u", st->inactive_flows); + print_uint(PRINT_ANY, "throttled", " throttled %u)", + st->throttled_flows); + + print_uint(PRINT_ANY, "band0_pkts", " band0_pkts %u", st->band_pkt_count[0]); + print_uint(PRINT_ANY, "band1_pkts", " band1_pkts %u", st->band_pkt_count[1]); + print_uint(PRINT_ANY, "band2_pkts", " band2_pkts %u", st->band_pkt_count[2]); + + if (st->time_next_delayed_flow > 0) { + print_lluint(PRINT_JSON, "next_packet_delay", NULL, + st->time_next_delayed_flow); + print_string(PRINT_FP, NULL, " next_packet_delay %s", + sprint_time64(st->time_next_delayed_flow, b1)); + } + + print_nl(); + print_lluint(PRINT_ANY, "gc", " gc %llu", st->gc_flows); + print_lluint(PRINT_ANY, "highprio", " highprio %llu", + st->highprio_packets); + + if (st->fastpath_packets) + print_lluint(PRINT_ANY, "fastpath", " fastpath %llu", + st->fastpath_packets); + + if (st->tcp_retrans) + print_lluint(PRINT_ANY, "retrans", " retrans %llu", + st->tcp_retrans); + + print_lluint(PRINT_ANY, "throttled", " throttled %llu", st->throttled); + + if (st->unthrottle_latency_ns) { + print_uint(PRINT_JSON, "latency", NULL, + st->unthrottle_latency_ns); + print_string(PRINT_FP, NULL, " latency %s", + sprint_time64(st->unthrottle_latency_ns, b1)); + } + + if (st->ce_mark) + print_lluint(PRINT_ANY, "ce_mark", " ce_mark %llu", + st->ce_mark); + + if (st->flows_plimit) + print_lluint(PRINT_ANY, "flows_plimit", " flows_plimit %llu", + st->flows_plimit); + + if (st->pkts_too_long || st->allocation_errors || + st->horizon_drops || st->horizon_caps || + st->band_drops[0] || + st->band_drops[1] || + st->band_drops[2]) { + print_nl(); + if (st->pkts_too_long) + print_lluint(PRINT_ANY, "pkts_too_long", + " pkts_too_long %llu", + st->pkts_too_long); + if (st->allocation_errors) + print_lluint(PRINT_ANY, "alloc_errors", + " alloc_errors %llu", + st->allocation_errors); + if (st->horizon_drops) + print_lluint(PRINT_ANY, "horizon_drops", + " horizon_drops %llu", + st->horizon_drops); + if (st->horizon_caps) + print_lluint(PRINT_ANY, "horizon_caps", + " horizon_caps %llu", + st->horizon_caps); + if (st->band_drops[0]) + print_lluint(PRINT_ANY, "band0_drops", + " band0_drops %llu", + st->band_drops[0]); + if (st->band_drops[1]) + print_lluint(PRINT_ANY, "band1_drops", + " band1_drops %llu", + st->band_drops[1]); + if (st->band_drops[2]) + print_lluint(PRINT_ANY, "band2_drops", + " band2_drops %llu", + st->band_drops[2]); + } + + return 0; +} + +struct qdisc_util fq_qdisc_util = { + .id = "fq", + .parse_qopt = fq_parse_opt, + .print_qopt = fq_print_opt, + .print_xstats = fq_print_xstats, +}; diff --git a/tc/q_fq_codel.c b/tc/q_fq_codel.c new file mode 100644 index 0000000..9c9d7bc --- /dev/null +++ b/tc/q_fq_codel.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Fair Queue Codel + * + * Copyright (C) 2012,2015 Eric Dumazet <edumazet@google.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... fq_codel [ limit PACKETS ] [ flows NUMBER ]\n" + "[ memory_limit BYTES ]\n" + "[ target TIME ] [ interval TIME ]\n" + "[ quantum BYTES ] [ [no]ecn ]\n" + "[ ce_threshold TIME ]\n" + "[ ce_threshold_selector VALUE/MASK ]\n" + "[ drop_batch SIZE ]\n"); +} + +static int fq_codel_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int drop_batch = 0; + unsigned int limit = 0; + unsigned int flows = 0; + unsigned int target = 0; + unsigned int interval = 0; + unsigned int quantum = 0; + unsigned int ce_threshold = ~0U; + unsigned int memory = ~0U; + __u8 ce_threshold_mask = 0; + __u8 ce_threshold_selector = 0xFF; + int ecn = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "flows") == 0) { + NEXT_ARG(); + if (get_unsigned(&flows, *argv, 0)) { + fprintf(stderr, "Illegal \"flows\"\n"); + return -1; + } + } else if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + } else if (strcmp(*argv, "drop_batch") == 0) { + NEXT_ARG(); + if (get_unsigned(&drop_batch, *argv, 0)) { + fprintf(stderr, "Illegal \"drop_batch\"\n"); + return -1; + } + } else if (strcmp(*argv, "target") == 0) { + NEXT_ARG(); + if (get_time(&target, *argv)) { + fprintf(stderr, "Illegal \"target\"\n"); + return -1; + } + } else if (strcmp(*argv, "ce_threshold") == 0) { + NEXT_ARG(); + if (get_time(&ce_threshold, *argv)) { + fprintf(stderr, "Illegal \"ce_threshold\"\n"); + return -1; + } + } else if (strcmp(*argv, "ce_threshold_selector") == 0) { + char *sep; + + NEXT_ARG(); + sep = strchr(*argv, '/'); + if (!sep) { + fprintf(stderr, "Missing mask for \"ce_threshold_selector\"\n"); + return -1; + } + *sep++ = '\0'; + if (get_u8(&ce_threshold_mask, sep, 0)) { + fprintf(stderr, "Illegal mask for \"ce_threshold_selector\"\n"); + return -1; + } + if (get_u8(&ce_threshold_selector, *argv, 0)) { + fprintf(stderr, "Illegal \"ce_threshold_selector\"\n"); + return -1; + } + } else if (strcmp(*argv, "memory_limit") == 0) { + NEXT_ARG(); + if (get_size(&memory, *argv)) { + fprintf(stderr, "Illegal \"memory_limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "interval") == 0) { + NEXT_ARG(); + if (get_time(&interval, *argv)) { + fprintf(stderr, "Illegal \"interval\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + ecn = 1; + } else if (strcmp(*argv, "noecn") == 0) { + ecn = 0; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (limit) + addattr_l(n, 1024, TCA_FQ_CODEL_LIMIT, &limit, sizeof(limit)); + if (flows) + addattr_l(n, 1024, TCA_FQ_CODEL_FLOWS, &flows, sizeof(flows)); + if (quantum) + addattr_l(n, 1024, TCA_FQ_CODEL_QUANTUM, &quantum, sizeof(quantum)); + if (interval) + addattr_l(n, 1024, TCA_FQ_CODEL_INTERVAL, &interval, sizeof(interval)); + if (target) + addattr_l(n, 1024, TCA_FQ_CODEL_TARGET, &target, sizeof(target)); + if (ecn != -1) + addattr_l(n, 1024, TCA_FQ_CODEL_ECN, &ecn, sizeof(ecn)); + if (ce_threshold != ~0U) + addattr_l(n, 1024, TCA_FQ_CODEL_CE_THRESHOLD, + &ce_threshold, sizeof(ce_threshold)); + if (ce_threshold_selector != 0xFF) { + addattr8(n, 1024, TCA_FQ_CODEL_CE_THRESHOLD_MASK, ce_threshold_mask); + addattr8(n, 1024, TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR, ce_threshold_selector); + } + if (memory != ~0U) + addattr_l(n, 1024, TCA_FQ_CODEL_MEMORY_LIMIT, + &memory, sizeof(memory)); + if (drop_batch) + addattr_l(n, 1024, TCA_FQ_CODEL_DROP_BATCH_SIZE, &drop_batch, sizeof(drop_batch)); + + addattr_nest_end(n, tail); + return 0; +} + +static int fq_codel_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_FQ_CODEL_MAX + 1]; + unsigned int limit; + unsigned int flows; + unsigned int interval; + unsigned int target; + unsigned int ecn; + unsigned int quantum; + unsigned int ce_threshold; + __u8 ce_threshold_selector = 0; + __u8 ce_threshold_mask = 0; + unsigned int memory_limit; + unsigned int drop_batch; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_FQ_CODEL_MAX, opt); + + if (tb[TCA_FQ_CODEL_LIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_LIMIT]) >= sizeof(__u32)) { + limit = rta_getattr_u32(tb[TCA_FQ_CODEL_LIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", limit); + } + if (tb[TCA_FQ_CODEL_FLOWS] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_FLOWS]) >= sizeof(__u32)) { + flows = rta_getattr_u32(tb[TCA_FQ_CODEL_FLOWS]); + print_uint(PRINT_ANY, "flows", "flows %u ", flows); + } + if (tb[TCA_FQ_CODEL_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_CODEL_QUANTUM]); + print_uint(PRINT_ANY, "quantum", "quantum %u ", quantum); + } + if (tb[TCA_FQ_CODEL_TARGET] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_TARGET]) >= sizeof(__u32)) { + target = rta_getattr_u32(tb[TCA_FQ_CODEL_TARGET]); + print_uint(PRINT_JSON, "target", NULL, target); + print_string(PRINT_FP, NULL, "target %s ", + sprint_time(target, b1)); + } + if (tb[TCA_FQ_CODEL_CE_THRESHOLD] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_CE_THRESHOLD]) >= sizeof(__u32)) { + ce_threshold = rta_getattr_u32(tb[TCA_FQ_CODEL_CE_THRESHOLD]); + print_uint(PRINT_JSON, "ce_threshold", NULL, ce_threshold); + print_string(PRINT_FP, NULL, "ce_threshold %s ", + sprint_time(ce_threshold, b1)); + } + if (tb[TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR]) >= sizeof(__u8)) + ce_threshold_selector = rta_getattr_u8(tb[TCA_FQ_CODEL_CE_THRESHOLD_SELECTOR]); + if (tb[TCA_FQ_CODEL_CE_THRESHOLD_MASK] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_CE_THRESHOLD_MASK]) >= sizeof(__u8)) + ce_threshold_mask = rta_getattr_u8(tb[TCA_FQ_CODEL_CE_THRESHOLD_MASK]); + if (ce_threshold_mask || ce_threshold_selector) { + print_hhu(PRINT_ANY, "ce_threshold_selector", "ce_threshold_selector %#x", + ce_threshold_selector); + print_hhu(PRINT_ANY, "ce_threshold_mask", "/%#x ", + ce_threshold_mask); + } + + if (tb[TCA_FQ_CODEL_INTERVAL] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_INTERVAL]) >= sizeof(__u32)) { + interval = rta_getattr_u32(tb[TCA_FQ_CODEL_INTERVAL]); + print_uint(PRINT_JSON, "interval", NULL, interval); + print_string(PRINT_FP, NULL, "interval %s ", + sprint_time(interval, b1)); + } + if (tb[TCA_FQ_CODEL_MEMORY_LIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_MEMORY_LIMIT]) >= sizeof(__u32)) { + memory_limit = rta_getattr_u32(tb[TCA_FQ_CODEL_MEMORY_LIMIT]); + print_size(PRINT_ANY, "memory_limit", "memory_limit %s ", + memory_limit); + } + if (tb[TCA_FQ_CODEL_ECN] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_ECN]) >= sizeof(__u32)) { + ecn = rta_getattr_u32(tb[TCA_FQ_CODEL_ECN]); + if (ecn) + print_bool(PRINT_ANY, "ecn", "ecn ", true); + } + if (tb[TCA_FQ_CODEL_DROP_BATCH_SIZE] && + RTA_PAYLOAD(tb[TCA_FQ_CODEL_DROP_BATCH_SIZE]) >= sizeof(__u32)) { + drop_batch = rta_getattr_u32(tb[TCA_FQ_CODEL_DROP_BATCH_SIZE]); + if (drop_batch) + print_uint(PRINT_ANY, "drop_batch", "drop_batch %u ", drop_batch); + } + + return 0; +} + +static int fq_codel_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_fq_codel_xstats _st = {}, *st; + + SPRINT_BUF(b1); + + if (xstats == NULL) + return 0; + + st = RTA_DATA(xstats); + if (RTA_PAYLOAD(xstats) < sizeof(*st)) { + memcpy(&_st, st, RTA_PAYLOAD(xstats)); + st = &_st; + } + if (st->type == TCA_FQ_CODEL_XSTATS_QDISC) { + print_uint(PRINT_ANY, "maxpacket", " maxpacket %u", + st->qdisc_stats.maxpacket); + print_uint(PRINT_ANY, "drop_overlimit", " drop_overlimit %u", + st->qdisc_stats.drop_overlimit); + print_uint(PRINT_ANY, "new_flow_count", " new_flow_count %u", + st->qdisc_stats.new_flow_count); + print_uint(PRINT_ANY, "ecn_mark", " ecn_mark %u", + st->qdisc_stats.ecn_mark); + if (st->qdisc_stats.ce_mark) + print_uint(PRINT_ANY, "ce_mark", " ce_mark %u", + st->qdisc_stats.ce_mark); + if (st->qdisc_stats.memory_usage) + print_uint(PRINT_ANY, "memory_used", " memory_used %u", + st->qdisc_stats.memory_usage); + if (st->qdisc_stats.drop_overmemory) + print_uint(PRINT_ANY, "drop_overmemory", " drop_overmemory %u", + st->qdisc_stats.drop_overmemory); + print_nl(); + print_uint(PRINT_ANY, "new_flows_len", " new_flows_len %u", + st->qdisc_stats.new_flows_len); + print_uint(PRINT_ANY, "old_flows_len", " old_flows_len %u", + st->qdisc_stats.old_flows_len); + } + if (st->type == TCA_FQ_CODEL_XSTATS_CLASS) { + print_int(PRINT_ANY, "deficit", " deficit %d", + st->class_stats.deficit); + print_uint(PRINT_ANY, "count", " count %u", + st->class_stats.count); + print_uint(PRINT_ANY, "lastcount", " lastcount %u", + st->class_stats.lastcount); + print_uint(PRINT_JSON, "ldelay", NULL, + st->class_stats.ldelay); + print_string(PRINT_FP, NULL, " ldelay %s", + sprint_time(st->class_stats.ldelay, b1)); + if (st->class_stats.dropping) { + print_bool(PRINT_ANY, "dropping", " dropping", true); + print_int(PRINT_JSON, "drop_next", NULL, + st->class_stats.drop_next); + if (st->class_stats.drop_next < 0) + print_string(PRINT_FP, NULL, " drop_next -%s", + sprint_time(-st->class_stats.drop_next, b1)); + else { + print_string(PRINT_FP, NULL, " drop_next %s", + sprint_time(st->class_stats.drop_next, b1)); + } + } + } + return 0; + +} + +struct qdisc_util fq_codel_qdisc_util = { + .id = "fq_codel", + .parse_qopt = fq_codel_parse_opt, + .print_qopt = fq_codel_print_opt, + .print_xstats = fq_codel_print_xstats, +}; diff --git a/tc/q_fq_pie.c b/tc/q_fq_pie.c new file mode 100644 index 0000000..9cbef47 --- /dev/null +++ b/tc/q_fq_pie.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Flow Queue PIE + * + * Copyright (C) 2019 Mohit P. Tahiliani <tahiliani@nitk.edu.in> + * Copyright (C) 2019 Sachin D. Patil <sdp.sachin@gmail.com> + * Copyright (C) 2019 V. Saicharan <vsaicharan1998@gmail.com> + * Copyright (C) 2019 Mohit Bhasi <mohitbhasi1998@gmail.com> + * Copyright (C) 2019 Leslie Monis <lesliemonis@gmail.com> + * Copyright (C) 2019 Gautam Ramakrishnan <gautamramk@gmail.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... fq_pie [ limit PACKETS ] [ flows NUMBER ]\n" + " [ target TIME ] [ tupdate TIME ]\n" + " [ alpha NUMBER ] [ beta NUMBER ]\n" + " [ quantum BYTES ] [ memory_limit BYTES ]\n" + " [ ecn_prob PERCENTAGE ] [ [no]ecn ]\n" + " [ [no]bytemode ] [ [no_]dq_rate_estimator ]\n"); +} + +#define ALPHA_MAX 32 +#define BETA_MAX 32 + +static int fq_pie_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int limit = 0; + unsigned int flows = 0; + unsigned int target = 0; + unsigned int tupdate = 0; + unsigned int alpha = 0; + unsigned int beta = 0; + unsigned int quantum = 0; + unsigned int memory_limit = 0; + unsigned int ecn_prob = 0; + int ecn = -1; + int bytemode = -1; + int dq_rate_estimator = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "flows") == 0) { + NEXT_ARG(); + if (get_unsigned(&flows, *argv, 0)) { + fprintf(stderr, "Illegal \"flows\"\n"); + return -1; + } + } else if (strcmp(*argv, "target") == 0) { + NEXT_ARG(); + if (get_time(&target, *argv)) { + fprintf(stderr, "Illegal \"target\"\n"); + return -1; + } + } else if (strcmp(*argv, "tupdate") == 0) { + NEXT_ARG(); + if (get_time(&tupdate, *argv)) { + fprintf(stderr, "Illegal \"tupdate\"\n"); + return -1; + } + } else if (strcmp(*argv, "alpha") == 0) { + NEXT_ARG(); + if (get_unsigned(&alpha, *argv, 0) || + alpha > ALPHA_MAX) { + fprintf(stderr, "Illegal \"alpha\"\n"); + return -1; + } + } else if (strcmp(*argv, "beta") == 0) { + NEXT_ARG(); + if (get_unsigned(&beta, *argv, 0) || + beta > BETA_MAX) { + fprintf(stderr, "Illegal \"beta\"\n"); + return -1; + } + } else if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_size(&quantum, *argv)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + } else if (strcmp(*argv, "memory_limit") == 0) { + NEXT_ARG(); + if (get_size(&memory_limit, *argv)) { + fprintf(stderr, "Illegal \"memory_limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn_prob") == 0) { + NEXT_ARG(); + if (get_unsigned(&ecn_prob, *argv, 0) || + ecn_prob >= 100) { + fprintf(stderr, "Illegal \"ecn_prob\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + ecn = 1; + } else if (strcmp(*argv, "noecn") == 0) { + ecn = 0; + } else if (strcmp(*argv, "bytemode") == 0) { + bytemode = 1; + } else if (strcmp(*argv, "nobytemode") == 0) { + bytemode = 0; + } else if (strcmp(*argv, "dq_rate_estimator") == 0) { + dq_rate_estimator = 1; + } else if (strcmp(*argv, "no_dq_rate_estimator") == 0) { + dq_rate_estimator = 0; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + + argc--; + argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS | NLA_F_NESTED); + if (limit) + addattr_l(n, 1024, TCA_FQ_PIE_LIMIT, &limit, sizeof(limit)); + if (flows) + addattr_l(n, 1024, TCA_FQ_PIE_FLOWS, &flows, sizeof(flows)); + if (target) + addattr_l(n, 1024, TCA_FQ_PIE_TARGET, &target, sizeof(target)); + if (tupdate) + addattr_l(n, 1024, TCA_FQ_PIE_TUPDATE, &tupdate, + sizeof(tupdate)); + if (alpha) + addattr_l(n, 1024, TCA_FQ_PIE_ALPHA, &alpha, sizeof(alpha)); + if (beta) + addattr_l(n, 1024, TCA_FQ_PIE_BETA, &beta, sizeof(beta)); + if (quantum) + addattr_l(n, 1024, TCA_FQ_PIE_QUANTUM, &quantum, + sizeof(quantum)); + if (memory_limit) + addattr_l(n, 1024, TCA_FQ_PIE_MEMORY_LIMIT, &memory_limit, + sizeof(memory_limit)); + if (ecn_prob) + addattr_l(n, 1024, TCA_FQ_PIE_ECN_PROB, &ecn_prob, + sizeof(ecn_prob)); + if (ecn != -1) + addattr_l(n, 1024, TCA_FQ_PIE_ECN, &ecn, sizeof(ecn)); + if (bytemode != -1) + addattr_l(n, 1024, TCA_FQ_PIE_BYTEMODE, &bytemode, + sizeof(bytemode)); + if (dq_rate_estimator != -1) + addattr_l(n, 1024, TCA_FQ_PIE_DQ_RATE_ESTIMATOR, + &dq_rate_estimator, sizeof(dq_rate_estimator)); + addattr_nest_end(n, tail); + + return 0; +} + +static int fq_pie_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_FQ_PIE_MAX + 1]; + unsigned int limit = 0; + unsigned int flows = 0; + unsigned int target = 0; + unsigned int tupdate = 0; + unsigned int alpha = 0; + unsigned int beta = 0; + unsigned int quantum = 0; + unsigned int memory_limit = 0; + unsigned int ecn_prob = 0; + int ecn = -1; + int bytemode = -1; + int dq_rate_estimator = -1; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_FQ_PIE_MAX, opt); + + if (tb[TCA_FQ_PIE_LIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_LIMIT]) >= sizeof(__u32)) { + limit = rta_getattr_u32(tb[TCA_FQ_PIE_LIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", limit); + } + if (tb[TCA_FQ_PIE_FLOWS] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_FLOWS]) >= sizeof(__u32)) { + flows = rta_getattr_u32(tb[TCA_FQ_PIE_FLOWS]); + print_uint(PRINT_ANY, "flows", "flows %u ", flows); + } + if (tb[TCA_FQ_PIE_TARGET] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_TARGET]) >= sizeof(__u32)) { + target = rta_getattr_u32(tb[TCA_FQ_PIE_TARGET]); + print_uint(PRINT_JSON, "target", NULL, target); + print_string(PRINT_FP, NULL, "target %s ", + sprint_time(target, b1)); + } + if (tb[TCA_FQ_PIE_TUPDATE] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_TUPDATE]) >= sizeof(__u32)) { + tupdate = rta_getattr_u32(tb[TCA_FQ_PIE_TUPDATE]); + print_uint(PRINT_JSON, "tupdate", NULL, tupdate); + print_string(PRINT_FP, NULL, "tupdate %s ", + sprint_time(tupdate, b1)); + } + if (tb[TCA_FQ_PIE_ALPHA] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_ALPHA]) >= sizeof(__u32)) { + alpha = rta_getattr_u32(tb[TCA_FQ_PIE_ALPHA]); + print_uint(PRINT_ANY, "alpha", "alpha %u ", alpha); + } + if (tb[TCA_FQ_PIE_BETA] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_BETA]) >= sizeof(__u32)) { + beta = rta_getattr_u32(tb[TCA_FQ_PIE_BETA]); + print_uint(PRINT_ANY, "beta", "beta %u ", beta); + } + if (tb[TCA_FQ_PIE_QUANTUM] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_FQ_PIE_QUANTUM]); + print_size(PRINT_ANY, "quantum", "quantum %s ", quantum); + } + if (tb[TCA_FQ_PIE_MEMORY_LIMIT] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_MEMORY_LIMIT]) >= sizeof(__u32)) { + memory_limit = rta_getattr_u32(tb[TCA_FQ_PIE_MEMORY_LIMIT]); + print_size(PRINT_ANY, "memory_limit", "memory_limit %s ", + memory_limit); + } + if (tb[TCA_FQ_PIE_ECN_PROB] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_ECN_PROB]) >= sizeof(__u32)) { + ecn_prob = rta_getattr_u32(tb[TCA_FQ_PIE_ECN_PROB]); + print_uint(PRINT_ANY, "ecn_prob", "ecn_prob %u ", ecn_prob); + } + if (tb[TCA_FQ_PIE_ECN] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_ECN]) >= sizeof(__u32)) { + ecn = rta_getattr_u32(tb[TCA_FQ_PIE_ECN]); + if (ecn) + print_bool(PRINT_ANY, "ecn", "ecn ", true); + } + if (tb[TCA_FQ_PIE_BYTEMODE] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_BYTEMODE]) >= sizeof(__u32)) { + bytemode = rta_getattr_u32(tb[TCA_FQ_PIE_BYTEMODE]); + if (bytemode) + print_bool(PRINT_ANY, "bytemode", "bytemode ", true); + } + if (tb[TCA_FQ_PIE_DQ_RATE_ESTIMATOR] && + RTA_PAYLOAD(tb[TCA_FQ_PIE_DQ_RATE_ESTIMATOR]) >= sizeof(__u32)) { + dq_rate_estimator = + rta_getattr_u32(tb[TCA_FQ_PIE_DQ_RATE_ESTIMATOR]); + if (dq_rate_estimator) + print_bool(PRINT_ANY, "dq_rate_estimator", + "dq_rate_estimator ", true); + } + + return 0; +} + +static int fq_pie_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_fq_pie_xstats _st = {}, *st; + + if (xstats == NULL) + return 0; + + st = RTA_DATA(xstats); + if (RTA_PAYLOAD(xstats) < sizeof(*st)) { + memcpy(&_st, st, RTA_PAYLOAD(xstats)); + st = &_st; + } + + print_uint(PRINT_ANY, "pkts_in", " pkts_in %u", + st->packets_in); + print_uint(PRINT_ANY, "overlimit", " overlimit %u", + st->overlimit); + print_uint(PRINT_ANY, "overmemory", " overmemory %u", + st->overmemory); + print_uint(PRINT_ANY, "dropped", " dropped %u", + st->dropped); + print_uint(PRINT_ANY, "ecn_mark", " ecn_mark %u", + st->ecn_mark); + print_nl(); + print_uint(PRINT_ANY, "new_flow_count", " new_flow_count %u", + st->new_flow_count); + print_uint(PRINT_ANY, "new_flows_len", " new_flows_len %u", + st->new_flows_len); + print_uint(PRINT_ANY, "old_flows_len", " old_flows_len %u", + st->old_flows_len); + print_uint(PRINT_ANY, "memory_used", " memory_used %u", + st->memory_usage); + + return 0; + +} + +struct qdisc_util fq_pie_qdisc_util = { + .id = "fq_pie", + .parse_qopt = fq_pie_parse_opt, + .print_qopt = fq_pie_print_opt, + .print_xstats = fq_pie_print_xstats, +}; diff --git a/tc/q_gred.c b/tc/q_gred.c new file mode 100644 index 0000000..f6a3f05 --- /dev/null +++ b/tc/q_gred.c @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_gred.c GRED. + * + * Authors: J Hadi Salim(hadi@nortelnetworks.com) + * code ruthlessly ripped from + * 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_red.h" + +#ifdef DEBUG +#define DPRINTF(format, args...) fprintf(stderr, format, ##args) +#else +#define DPRINTF(format, args...) +#endif + +static void explain(void) +{ + fprintf(stderr, + "Usage: tc qdisc { add | replace | change } ... gred setup vqs NUMBER\n" + " default DEFAULT_VQ [ grio ] [ limit BYTES ] [ecn] [harddrop]\n" + " tc qdisc change ... gred vq VQ [ prio VALUE ] limit BYTES\n" + " min BYTES max BYTES avpkt BYTES [ burst PACKETS ]\n" + " [ probability PROBABILITY ] [ bandwidth KBPS ] [ecn] [harddrop]\n"); +} + +static int init_gred(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n) +{ + + struct rtattr *tail; + struct tc_gred_sopt opt = { 0 }; + __u32 limit = 0; + + opt.def_DP = MAX_DPs; + + while (argc > 0) { + DPRINTF(stderr, "init_gred: invoked with %s\n", *argv); + if (strcmp(*argv, "vqs") == 0 || + strcmp(*argv, "DPs") == 0) { + NEXT_ARG(); + if (get_unsigned(&opt.DPs, *argv, 10)) { + fprintf(stderr, "Illegal \"vqs\"\n"); + return -1; + } else if (opt.DPs > MAX_DPs) { + fprintf(stderr, "GRED: only %u VQs are currently supported\n", + MAX_DPs); + return -1; + } + } else if (strcmp(*argv, "default") == 0) { + if (opt.DPs == 0) { + fprintf(stderr, "\"default\" must be defined after \"vqs\"\n"); + return -1; + } + NEXT_ARG(); + if (get_unsigned(&opt.def_DP, *argv, 10)) { + fprintf(stderr, "Illegal \"default\"\n"); + return -1; + } else if (opt.def_DP >= opt.DPs) { + fprintf(stderr, "\"default\" must be less than \"vqs\"\n"); + return -1; + } + } else if (strcmp(*argv, "grio") == 0) { + opt.grio = 1; + } else if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&limit, *argv)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + opt.flags |= TC_RED_ECN; + } else if (strcmp(*argv, "harddrop") == 0) { + opt.flags |= TC_RED_HARDDROP; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!opt.DPs || opt.def_DP == MAX_DPs) { + fprintf(stderr, "Illegal gred setup parameters\n"); + return -1; + } + + DPRINTF("TC_GRED: sending DPs=%u def_DP=%u\n", opt.DPs, opt.def_DP); + n->nlmsg_flags |= NLM_F_CREATE; + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 1024, TCA_GRED_DPS, &opt, sizeof(struct tc_gred_sopt)); + if (limit) + addattr32(n, 1024, TCA_GRED_LIMIT, limit); + addattr_nest_end(n, tail); + return 0; +} +/* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +*/ +static int gred_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev) +{ + struct rtattr *tail, *entry, *vqs; + int ok = 0; + struct tc_gred_qopt opt = { 0 }; + unsigned int burst = 0; + unsigned int avpkt = 0; + unsigned int flags = 0; + double probability = 0.02; + unsigned int rate = 0; + int parm; + __u8 sbuf[256]; + __u32 max_P; + + opt.DP = MAX_DPs; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "setup") == 0) { + if (ok) { + fprintf(stderr, "Illegal \"setup\"\n"); + return -1; + } + return init_gred(qu, argc-1, argv+1, n); + } else if (strcmp(*argv, "min") == 0) { + NEXT_ARG(); + if (get_size(&opt.qth_min, *argv)) { + fprintf(stderr, "Illegal \"min\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "max") == 0) { + NEXT_ARG(); + if (get_size(&opt.qth_max, *argv)) { + fprintf(stderr, "Illegal \"max\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "vq") == 0 || + strcmp(*argv, "DP") == 0) { + NEXT_ARG(); + if (get_unsigned(&opt.DP, *argv, 10)) { + fprintf(stderr, "Illegal \"vq\"\n"); + return -1; + } else if (opt.DP >= MAX_DPs) { + fprintf(stderr, "GRED: only %u VQs are currently supported\n", + MAX_DPs); + return -1; + } /* need a better error check */ + ok++; + } else if (strcmp(*argv, "burst") == 0) { + NEXT_ARG(); + if (get_unsigned(&burst, *argv, 0)) { + fprintf(stderr, "Illegal \"burst\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "avpkt") == 0) { + NEXT_ARG(); + if (get_size(&avpkt, *argv)) { + fprintf(stderr, "Illegal \"avpkt\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "probability") == 0) { + NEXT_ARG(); + if (sscanf(*argv, "%lg", &probability) != 1) { + fprintf(stderr, "Illegal \"probability\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "prio") == 0) { + NEXT_ARG(); + opt.prio = strtol(*argv, (char **)NULL, 10); + /* some error check here */ + ok++; + } else if (strcmp(*argv, "bandwidth") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&rate, *argv, dev)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + } else if (get_rate(&rate, *argv)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "ecn") == 0) { + flags |= TC_RED_ECN; + } else if (strcmp(*argv, "harddrop") == 0) { + flags |= TC_RED_HARDDROP; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!ok) { + explain(); + return -1; + } + if (opt.DP == MAX_DPs || !opt.limit || !opt.qth_min || !opt.qth_max || + !avpkt) { + fprintf(stderr, "Required parameter (vq, limit, min, max, avpkt) is missing\n"); + return -1; + } + if (!burst) { + burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt); + fprintf(stderr, "GRED: set burst to %u\n", burst); + } + if (!rate) { + get_rate(&rate, "10Mbit"); + fprintf(stderr, "GRED: set bandwidth to 10Mbit\n"); + } + if ((parm = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) { + fprintf(stderr, "GRED: failed to calculate EWMA constant.\n"); + return -1; + } + if (parm >= 10) + fprintf(stderr, "GRED: WARNING. Burst %u seems to be too large.\n", + burst); + opt.Wlog = parm; + if ((parm = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) { + fprintf(stderr, "GRED: failed to calculate probability.\n"); + return -1; + } + opt.Plog = parm; + if ((parm = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0) + { + fprintf(stderr, "GRED: failed to calculate idle damping table.\n"); + return -1; + } + opt.Scell_log = parm; + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 1024, TCA_GRED_PARMS, &opt, sizeof(opt)); + addattr_l(n, 1024, TCA_GRED_STAB, sbuf, 256); + max_P = probability * pow(2, 32); + addattr32(n, 1024, TCA_GRED_MAX_P, max_P); + + vqs = addattr_nest(n, 1024, TCA_GRED_VQ_LIST); + entry = addattr_nest(n, 1024, TCA_GRED_VQ_ENTRY); + addattr32(n, 1024, TCA_GRED_VQ_DP, opt.DP); + addattr32(n, 1024, TCA_GRED_VQ_FLAGS, flags); + addattr_nest_end(n, entry); + addattr_nest_end(n, vqs); + + addattr_nest_end(n, tail); + return 0; +} + +struct tc_gred_info { + bool flags_present; + __u64 bytes; + __u32 packets; + __u32 backlog; + __u32 prob_drop; + __u32 prob_mark; + __u32 forced_drop; + __u32 forced_mark; + __u32 pdrop; + __u32 other; + __u32 flags; +}; + +static void +gred_parse_vqs(struct tc_gred_info *info, struct rtattr *vqs) +{ + int rem = RTA_PAYLOAD(vqs); + unsigned int offset = 0; + + while (rem > offset) { + struct rtattr *tb_entry[TCA_GRED_VQ_ENTRY_MAX + 1] = {}; + struct rtattr *tb[TCA_GRED_VQ_MAX + 1] = {}; + struct rtattr *entry; + unsigned int len; + unsigned int dp; + + entry = RTA_DATA(vqs) + offset; + + parse_rtattr(tb_entry, TCA_GRED_VQ_ENTRY_MAX, entry, + rem - offset); + len = RTA_LENGTH(RTA_PAYLOAD(entry)); + offset += len; + + if (!tb_entry[TCA_GRED_VQ_ENTRY]) { + fprintf(stderr, + "ERROR: Failed to parse Virtual Queue entry\n"); + continue; + } + + parse_rtattr_nested(tb, TCA_GRED_VQ_MAX, + tb_entry[TCA_GRED_VQ_ENTRY]); + + if (!tb[TCA_GRED_VQ_DP]) { + fprintf(stderr, + "ERROR: Virtual Queue without DP attribute\n"); + continue; + } + + dp = rta_getattr_u32(tb[TCA_GRED_VQ_DP]); + + if (tb[TCA_GRED_VQ_STAT_BYTES]) + info[dp].bytes = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_BYTES]); + if (tb[TCA_GRED_VQ_STAT_PACKETS]) + info[dp].packets = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PACKETS]); + if (tb[TCA_GRED_VQ_STAT_BACKLOG]) + info[dp].backlog = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_BACKLOG]); + if (tb[TCA_GRED_VQ_STAT_PROB_DROP]) + info[dp].prob_drop = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PROB_DROP]); + if (tb[TCA_GRED_VQ_STAT_PROB_MARK]) + info[dp].prob_mark = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PROB_MARK]); + if (tb[TCA_GRED_VQ_STAT_FORCED_DROP]) + info[dp].forced_drop = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_FORCED_DROP]); + if (tb[TCA_GRED_VQ_STAT_FORCED_MARK]) + info[dp].forced_mark = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_FORCED_MARK]); + if (tb[TCA_GRED_VQ_STAT_PDROP]) + info[dp].pdrop = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_PDROP]); + if (tb[TCA_GRED_VQ_STAT_OTHER]) + info[dp].other = + rta_getattr_u32(tb[TCA_GRED_VQ_STAT_OTHER]); + info[dp].flags_present = !!tb[TCA_GRED_VQ_FLAGS]; + if (tb[TCA_GRED_VQ_FLAGS]) + info[dp].flags = + rta_getattr_u32(tb[TCA_GRED_VQ_FLAGS]); + } +} + +static void +gred_print_stats(struct tc_gred_info *info, struct tc_gred_qopt *qopt) +{ + __u64 bytes = info ? info->bytes : qopt->bytesin; + + if (!is_json_context()) + printf("\n Queue size: "); + + print_size(PRINT_ANY, "qave", "average %s ", qopt->qave); + print_size(PRINT_ANY, "backlog", "current %s ", qopt->backlog); + + if (!is_json_context()) + printf("\n Dropped packets: "); + + if (info) { + print_uint(PRINT_ANY, "forced_drop", "forced %u ", + info->forced_drop); + print_uint(PRINT_ANY, "prob_drop", "early %u ", + info->prob_drop); + print_uint(PRINT_ANY, "pdrop", "pdrop %u ", info->pdrop); + print_uint(PRINT_ANY, "other", "other %u ", info->other); + + if (!is_json_context()) + printf("\n Marked packets: "); + print_uint(PRINT_ANY, "forced_mark", "forced %u ", + info->forced_mark); + print_uint(PRINT_ANY, "prob_mark", "early %u ", + info->prob_mark); + } else { + print_uint(PRINT_ANY, "forced_drop", "forced %u ", + qopt->forced); + print_uint(PRINT_ANY, "prob_drop", "early %u ", qopt->early); + print_uint(PRINT_ANY, "pdrop", "pdrop %u ", qopt->pdrop); + print_uint(PRINT_ANY, "other", "other %u ", qopt->other); + } + + if (!is_json_context()) + printf("\n Total packets: "); + + print_uint(PRINT_ANY, "packets", "%u ", qopt->packets); + print_size(PRINT_ANY, "bytes", "(%s) ", bytes); +} + +static int gred_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_gred_info infos[MAX_DPs] = {}; + struct rtattr *tb[TCA_GRED_MAX + 1]; + struct tc_gred_sopt *sopt; + struct tc_gred_qopt *qopt; + bool vq_info = false; + __u32 *max_p = NULL; + __u32 *limit = NULL; + unsigned int i; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_GRED_MAX, opt); + + if (tb[TCA_GRED_PARMS] == NULL) + return -1; + + if (tb[TCA_GRED_MAX_P] && + RTA_PAYLOAD(tb[TCA_GRED_MAX_P]) >= sizeof(__u32) * MAX_DPs) + max_p = RTA_DATA(tb[TCA_GRED_MAX_P]); + + if (tb[TCA_GRED_LIMIT] && + RTA_PAYLOAD(tb[TCA_GRED_LIMIT]) == sizeof(__u32)) + limit = RTA_DATA(tb[TCA_GRED_LIMIT]); + + sopt = RTA_DATA(tb[TCA_GRED_DPS]); + qopt = RTA_DATA(tb[TCA_GRED_PARMS]); + if (RTA_PAYLOAD(tb[TCA_GRED_DPS]) < sizeof(*sopt) || + RTA_PAYLOAD(tb[TCA_GRED_PARMS]) < sizeof(*qopt)*MAX_DPs) { + fprintf(f, "\n GRED received message smaller than expected\n"); + return -1; + } + + if (tb[TCA_GRED_VQ_LIST]) { + gred_parse_vqs(infos, tb[TCA_GRED_VQ_LIST]); + vq_info = true; + } + + print_uint(PRINT_ANY, "dp_cnt", "vqs %u ", sopt->DPs); + print_uint(PRINT_ANY, "dp_default", "default %u ", sopt->def_DP); + + if (sopt->grio) + print_bool(PRINT_ANY, "grio", "grio ", true); + else + print_bool(PRINT_ANY, "grio", NULL, false); + + if (limit) + print_size(PRINT_ANY, "limit", "limit %s ", *limit); + + tc_red_print_flags(sopt->flags); + + open_json_array(PRINT_JSON, "vqs"); + for (i = 0; i < MAX_DPs; i++, qopt++) { + if (qopt->DP >= MAX_DPs) + continue; + + open_json_object(NULL); + + print_uint(PRINT_ANY, "vq", "\n vq %u ", qopt->DP); + print_hhu(PRINT_ANY, "prio", "prio %hhu ", qopt->prio); + print_size(PRINT_ANY, "limit", "limit %s ", qopt->limit); + print_size(PRINT_ANY, "min", "min %s ", qopt->qth_min); + print_size(PRINT_ANY, "max", "max %s ", qopt->qth_max); + + if (infos[i].flags_present) + tc_red_print_flags(infos[i].flags); + + if (show_details) { + print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog); + if (max_p) + print_float(PRINT_ANY, "probability", + "probability %lg ", + max_p[i] / pow(2, 32)); + else + print_uint(PRINT_ANY, "Plog", "Plog %u ", + qopt->Plog); + print_uint(PRINT_ANY, "Scell_log", "Scell_log %u ", + qopt->Scell_log); + } + if (show_stats) + gred_print_stats(vq_info ? &infos[i] : NULL, qopt); + close_json_object(); + } + close_json_array(PRINT_JSON, "vqs"); + return 0; +} + +struct qdisc_util gred_qdisc_util = { + .id = "gred", + .parse_qopt = gred_parse_opt, + .print_qopt = gred_print_opt, +}; diff --git a/tc/q_hfsc.c b/tc/q_hfsc.c new file mode 100644 index 0000000..609d925 --- /dev/null +++ b/tc/q_hfsc.c @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_hfsc.c HFSC. + * + * Authors: Patrick McHardy, <kaber@trash.net> + */ + +#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" + +static int hfsc_get_sc(int *, char ***, + struct tc_service_curve *, const char *); + +static void +explain_qdisc(void) +{ + fprintf(stderr, + "Usage: ... hfsc [ default CLASSID ]\n" + "\n" + " default: default class for unclassified packets\n" + ); +} + +static void +explain_class(void) +{ + fprintf(stderr, + "Usage: ... hfsc [ [ rt SC ] [ ls SC ] | [ sc SC ] ] [ ul SC ]\n" + "\n" + "SC := [ [ m1 BPS ] d SEC ] m2 BPS\n" + "\n" + " m1 : slope of first segment\n" + " d : x-coordinate of intersection\n" + " m2 : slope of second segment\n" + "\n" + "Alternative format:\n" + "\n" + "SC := [ [ umax BYTE ] dmax SEC ] rate BPS\n" + "\n" + " umax : maximum unit of work\n" + " dmax : maximum delay\n" + " rate : rate\n" + "\n" + "Remarks:\n" + " - at least one of 'rt', 'ls' or 'sc' must be specified\n" + " - 'ul' can only be specified with 'ls' or 'sc'\n" + "\n" + ); +} + +static void +explain1(char *arg) +{ + fprintf(stderr, "HFSC: Illegal \"%s\"\n", arg); +} + +static int +hfsc_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_hfsc_qopt qopt = {}; + + while (argc > 0) { + if (matches(*argv, "default") == 0) { + NEXT_ARG(); + if (qopt.defcls != 0) { + fprintf(stderr, "HFSC: Double \"default\"\n"); + return -1; + } + if (get_u16(&qopt.defcls, *argv, 16) < 0) { + explain1("default"); + return -1; + } + } else if (matches(*argv, "help") == 0) { + explain_qdisc(); + return -1; + } else { + fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv); + explain_qdisc(); + return -1; + } + argc--, argv++; + } + + addattr_l(n, 1024, TCA_OPTIONS, &qopt, sizeof(qopt)); + return 0; +} + +static int +hfsc_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_hfsc_qopt *qopt; + + if (opt == NULL) + return 0; + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + return -1; + qopt = RTA_DATA(opt); + + if (qopt->defcls != 0) + fprintf(f, "default %x ", qopt->defcls); + + return 0; +} + +static int +hfsc_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats) +{ + struct tc_hfsc_stats *st; + + if (xstats == NULL) + return 0; + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + st = RTA_DATA(xstats); + + fprintf(f, " period %u ", st->period); + if (st->work != 0) + fprintf(f, "work %llu bytes ", (unsigned long long) st->work); + if (st->rtwork != 0) + fprintf(f, "rtwork %llu bytes ", (unsigned long long) st->rtwork); + fprintf(f, "level %u ", st->level); + fprintf(f, "\n"); + + return 0; +} + +static int +hfsc_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_service_curve rsc = {}, fsc = {}, usc = {}; + int rsc_ok = 0, fsc_ok = 0, usc_ok = 0; + struct rtattr *tail; + + while (argc > 0) { + if (matches(*argv, "rt") == 0) { + NEXT_ARG(); + if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) { + explain1("rt"); + return -1; + } + rsc_ok = 1; + } else if (matches(*argv, "ls") == 0) { + NEXT_ARG(); + if (hfsc_get_sc(&argc, &argv, &fsc, dev) < 0) { + explain1("ls"); + return -1; + } + fsc_ok = 1; + } else if (matches(*argv, "sc") == 0) { + NEXT_ARG(); + if (hfsc_get_sc(&argc, &argv, &rsc, dev) < 0) { + explain1("sc"); + return -1; + } + memcpy(&fsc, &rsc, sizeof(fsc)); + rsc_ok = 1; + fsc_ok = 1; + } else if (matches(*argv, "ul") == 0) { + NEXT_ARG(); + if (hfsc_get_sc(&argc, &argv, &usc, dev) < 0) { + explain1("ul"); + return -1; + } + usc_ok = 1; + } else if (matches(*argv, "help") == 0) { + explain_class(); + return -1; + } else { + fprintf(stderr, "HFSC: What is \"%s\" ?\n", *argv); + explain_class(); + return -1; + } + argc--, argv++; + } + + if (!(rsc_ok || fsc_ok || usc_ok)) { + fprintf(stderr, "HFSC: no parameters given\n"); + explain_class(); + return -1; + } + if (usc_ok && !fsc_ok) { + fprintf(stderr, "HFSC: Upper-limit Service Curve without Link-Share Service Curve\n"); + explain_class(); + return -1; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (rsc_ok) + addattr_l(n, 1024, TCA_HFSC_RSC, &rsc, sizeof(rsc)); + if (fsc_ok) + addattr_l(n, 1024, TCA_HFSC_FSC, &fsc, sizeof(fsc)); + if (usc_ok) + addattr_l(n, 1024, TCA_HFSC_USC, &usc, sizeof(usc)); + + addattr_nest_end(n, tail); + return 0; +} + +static void +hfsc_print_sc(FILE *f, char *name, struct tc_service_curve *sc) +{ + SPRINT_BUF(b1); + + fprintf(f, "%s ", name); + tc_print_rate(PRINT_FP, NULL, "m1 %s ", sc->m1); + fprintf(f, "d %s ", sprint_time(tc_core_ktime2time(sc->d), b1)); + tc_print_rate(PRINT_FP, NULL, "m2 %s ", sc->m2); +} + +static int +hfsc_print_class_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_HFSC_MAX+1]; + struct tc_service_curve *rsc = NULL, *fsc = NULL, *usc = NULL; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_HFSC_MAX, opt); + + if (tb[TCA_HFSC_RSC]) { + if (RTA_PAYLOAD(tb[TCA_HFSC_RSC]) < sizeof(*rsc)) + fprintf(stderr, "HFSC: truncated realtime option\n"); + else + rsc = RTA_DATA(tb[TCA_HFSC_RSC]); + } + if (tb[TCA_HFSC_FSC]) { + if (RTA_PAYLOAD(tb[TCA_HFSC_FSC]) < sizeof(*fsc)) + fprintf(stderr, "HFSC: truncated linkshare option\n"); + else + fsc = RTA_DATA(tb[TCA_HFSC_FSC]); + } + if (tb[TCA_HFSC_USC]) { + if (RTA_PAYLOAD(tb[TCA_HFSC_USC]) < sizeof(*usc)) + fprintf(stderr, "HFSC: truncated upperlimit option\n"); + else + usc = RTA_DATA(tb[TCA_HFSC_USC]); + } + + + if (rsc != NULL && fsc != NULL && + memcmp(rsc, fsc, sizeof(*rsc)) == 0) + hfsc_print_sc(f, "sc", rsc); + else { + if (rsc != NULL) + hfsc_print_sc(f, "rt", rsc); + if (fsc != NULL) + hfsc_print_sc(f, "ls", fsc); + } + if (usc != NULL) + hfsc_print_sc(f, "ul", usc); + + return 0; +} + +struct qdisc_util hfsc_qdisc_util = { + .id = "hfsc", + .parse_qopt = hfsc_parse_opt, + .print_qopt = hfsc_print_opt, + .print_xstats = hfsc_print_xstats, + .parse_copt = hfsc_parse_class_opt, + .print_copt = hfsc_print_class_opt, +}; + +static int +hfsc_get_sc1(int *argcp, char ***argvp, + struct tc_service_curve *sc, const char *dev) +{ + char **argv = *argvp; + int argc = *argcp; + unsigned int m1 = 0, d = 0, m2 = 0; + + if (matches(*argv, "m1") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&m1, *argv, dev)) { + explain1("m1"); + return -1; + } + } else if (get_rate(&m1, *argv) < 0) { + explain1("m1"); + return -1; + } + NEXT_ARG(); + } + + if (matches(*argv, "d") == 0) { + NEXT_ARG(); + if (get_time(&d, *argv) < 0) { + explain1("d"); + return -1; + } + NEXT_ARG(); + } + + if (matches(*argv, "m2") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&m2, *argv, dev)) { + explain1("m2"); + return -1; + } + } else if (get_rate(&m2, *argv) < 0) { + explain1("m2"); + return -1; + } + } else + return -1; + + sc->m1 = m1; + sc->d = tc_core_time2ktime(d); + sc->m2 = m2; + + *argvp = argv; + *argcp = argc; + return 0; +} + +static int +hfsc_get_sc2(int *argcp, char ***argvp, struct tc_service_curve *sc, const char *dev) +{ + char **argv = *argvp; + int argc = *argcp; + unsigned int umax = 0, dmax = 0, rate = 0; + + if (matches(*argv, "umax") == 0) { + NEXT_ARG(); + if (get_size(&umax, *argv) < 0) { + explain1("umax"); + return -1; + } + NEXT_ARG(); + } + + if (matches(*argv, "dmax") == 0) { + NEXT_ARG(); + if (get_time(&dmax, *argv) < 0) { + explain1("dmax"); + return -1; + } + NEXT_ARG(); + } + + if (matches(*argv, "rate") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&rate, *argv, dev)) { + explain1("rate"); + return -1; + } + } else if (get_rate(&rate, *argv) < 0) { + explain1("rate"); + return -1; + } + } else + return -1; + + if (umax != 0 && dmax == 0) { + fprintf(stderr, "HFSC: umax given but dmax is zero.\n"); + return -1; + } + + if (dmax != 0 && ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax) > rate) { + /* + * concave curve, slope of first segment is umax/dmax, + * intersection is at dmax + */ + sc->m1 = ceil(1.0 * umax * TIME_UNITS_PER_SEC / dmax); /* in bps */ + sc->d = tc_core_time2ktime(dmax); + sc->m2 = rate; + } else { + /* + * convex curve, slope of first segment is 0, intersection + * is at dmax - umax / rate + */ + sc->m1 = 0; + sc->d = tc_core_time2ktime(ceil(dmax - umax * TIME_UNITS_PER_SEC / rate)); + sc->m2 = rate; + } + + *argvp = argv; + *argcp = argc; + return 0; +} + +static int +hfsc_get_sc(int *argcp, char ***argvp, struct tc_service_curve *sc, const char *dev) +{ + if (hfsc_get_sc1(argcp, argvp, sc, dev) < 0 && + hfsc_get_sc2(argcp, argvp, sc, dev) < 0) + return -1; + + if (sc->m1 == 0 && sc->m2 == 0) { + fprintf(stderr, "HFSC: Service Curve has two zero slopes\n"); + return -1; + } + + return 0; +} diff --git a/tc/q_hhf.c b/tc/q_hhf.c new file mode 100644 index 0000000..95e49f3 --- /dev/null +++ b/tc/q_hhf.c @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* q_hhf.c Heavy-Hitter Filter (HHF) + * + * Copyright (C) 2013 Terry Lam <vtlam@google.com> + */ +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... hhf [ limit PACKETS ] [ quantum BYTES]\n" + " [ hh_limit NUMBER ]\n" + " [ reset_timeout TIME ]\n" + " [ admit_bytes BYTES ]\n" + " [ evict_timeout TIME ]\n" + " [ non_hh_weight NUMBER ]\n"); +} + +static int hhf_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int limit = 0; + unsigned int quantum = 0; + unsigned int hh_limit = 0; + unsigned int reset_timeout = 0; + unsigned int admit_bytes = 0; + unsigned int evict_timeout = 0; + unsigned int non_hh_weight = 0; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_unsigned(&quantum, *argv, 0)) { + fprintf(stderr, "Illegal \"quantum\"\n"); + return -1; + } + } else if (strcmp(*argv, "hh_limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&hh_limit, *argv, 0)) { + fprintf(stderr, "Illegal \"hh_limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "reset_timeout") == 0) { + NEXT_ARG(); + if (get_time(&reset_timeout, *argv)) { + fprintf(stderr, "Illegal \"reset_timeout\"\n"); + return -1; + } + } else if (strcmp(*argv, "admit_bytes") == 0) { + NEXT_ARG(); + if (get_unsigned(&admit_bytes, *argv, 0)) { + fprintf(stderr, "Illegal \"admit_bytes\"\n"); + return -1; + } + } else if (strcmp(*argv, "evict_timeout") == 0) { + NEXT_ARG(); + if (get_time(&evict_timeout, *argv)) { + fprintf(stderr, "Illegal \"evict_timeout\"\n"); + return -1; + } + } else if (strcmp(*argv, "non_hh_weight") == 0) { + NEXT_ARG(); + if (get_unsigned(&non_hh_weight, *argv, 0)) { + fprintf(stderr, "Illegal \"non_hh_weight\"\n"); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (limit) + addattr_l(n, 1024, TCA_HHF_BACKLOG_LIMIT, &limit, + sizeof(limit)); + if (quantum) + addattr_l(n, 1024, TCA_HHF_QUANTUM, &quantum, sizeof(quantum)); + if (hh_limit) + addattr_l(n, 1024, TCA_HHF_HH_FLOWS_LIMIT, &hh_limit, + sizeof(hh_limit)); + if (reset_timeout) + addattr_l(n, 1024, TCA_HHF_RESET_TIMEOUT, &reset_timeout, + sizeof(reset_timeout)); + if (admit_bytes) + addattr_l(n, 1024, TCA_HHF_ADMIT_BYTES, &admit_bytes, + sizeof(admit_bytes)); + if (evict_timeout) + addattr_l(n, 1024, TCA_HHF_EVICT_TIMEOUT, &evict_timeout, + sizeof(evict_timeout)); + if (non_hh_weight) + addattr_l(n, 1024, TCA_HHF_NON_HH_WEIGHT, &non_hh_weight, + sizeof(non_hh_weight)); + addattr_nest_end(n, tail); + return 0; +} + +static int hhf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_HHF_MAX + 1]; + unsigned int limit; + unsigned int quantum; + unsigned int hh_limit; + unsigned int reset_timeout; + unsigned int admit_bytes; + unsigned int evict_timeout; + unsigned int non_hh_weight; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_HHF_MAX, opt); + + if (tb[TCA_HHF_BACKLOG_LIMIT] && + RTA_PAYLOAD(tb[TCA_HHF_BACKLOG_LIMIT]) >= sizeof(__u32)) { + limit = rta_getattr_u32(tb[TCA_HHF_BACKLOG_LIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", limit); + } + if (tb[TCA_HHF_QUANTUM] && + RTA_PAYLOAD(tb[TCA_HHF_QUANTUM]) >= sizeof(__u32)) { + quantum = rta_getattr_u32(tb[TCA_HHF_QUANTUM]); + print_size(PRINT_ANY, "quantum", "quantum %s ", quantum); + } + if (tb[TCA_HHF_HH_FLOWS_LIMIT] && + RTA_PAYLOAD(tb[TCA_HHF_HH_FLOWS_LIMIT]) >= sizeof(__u32)) { + hh_limit = rta_getattr_u32(tb[TCA_HHF_HH_FLOWS_LIMIT]); + print_uint(PRINT_ANY, "hh_limit", "hh_limit %u ", hh_limit); + } + if (tb[TCA_HHF_RESET_TIMEOUT] && + RTA_PAYLOAD(tb[TCA_HHF_RESET_TIMEOUT]) >= sizeof(__u32)) { + reset_timeout = rta_getattr_u32(tb[TCA_HHF_RESET_TIMEOUT]); + print_uint(PRINT_JSON, "reset_timeout", NULL, reset_timeout); + print_string(PRINT_FP, NULL, "reset_timeout %s ", + sprint_time(reset_timeout, b1)); + } + if (tb[TCA_HHF_ADMIT_BYTES] && + RTA_PAYLOAD(tb[TCA_HHF_ADMIT_BYTES]) >= sizeof(__u32)) { + admit_bytes = rta_getattr_u32(tb[TCA_HHF_ADMIT_BYTES]); + print_size(PRINT_ANY, "admit_bytes", "admit_bytes %s ", + admit_bytes); + } + if (tb[TCA_HHF_EVICT_TIMEOUT] && + RTA_PAYLOAD(tb[TCA_HHF_EVICT_TIMEOUT]) >= sizeof(__u32)) { + evict_timeout = rta_getattr_u32(tb[TCA_HHF_EVICT_TIMEOUT]); + print_uint(PRINT_JSON, "evict_timeout", NULL, evict_timeout); + print_string(PRINT_FP, NULL, "evict_timeout %s ", + sprint_time(evict_timeout, b1)); + } + if (tb[TCA_HHF_NON_HH_WEIGHT] && + RTA_PAYLOAD(tb[TCA_HHF_NON_HH_WEIGHT]) >= sizeof(__u32)) { + non_hh_weight = rta_getattr_u32(tb[TCA_HHF_NON_HH_WEIGHT]); + print_uint(PRINT_ANY, "non_hh_weight", "non_hh_weight %u ", + non_hh_weight); + } + return 0; +} + +static int hhf_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_hhf_xstats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + + print_uint(PRINT_ANY, "drop_overlimit", " drop_overlimit %u", + st->drop_overlimit); + print_uint(PRINT_ANY, "hh_overlimit", " hh_overlimit %u", + st->hh_overlimit); + print_uint(PRINT_ANY, "tot_hh", " tot_hh %u", st->hh_tot_count); + print_uint(PRINT_ANY, "cur_hh", " cur_hh %u", st->hh_cur_count); + + return 0; +} + +struct qdisc_util hhf_qdisc_util = { + .id = "hhf", + .parse_qopt = hhf_parse_opt, + .print_qopt = hhf_print_opt, + .print_xstats = hhf_print_xstats, +}; diff --git a/tc/q_htb.c b/tc/q_htb.c new file mode 100644 index 0000000..9afb293 --- /dev/null +++ b/tc/q_htb.c @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* q_htb.c Hierarchical Token Bucket + * + * Author: Martin Devera, devik@cdi.cz + */ + +#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 "utils.h" +#include "tc_util.h" + +#define HTB_TC_VER 0x30003 +#if HTB_TC_VER >> 16 != TC_HTB_PROTOVER +#error "Different kernel and TC HTB versions" +#endif + +static void explain(void) +{ + fprintf(stderr, "Usage: ... qdisc add ... htb [default N] [r2q N]\n" + " [direct_qlen P] [offload]\n" + " default minor id of class to which unclassified packets are sent {0}\n" + " r2q DRR quantums are computed as rate in Bps/r2q {10}\n" + " debug string of 16 numbers each 0-3 {0}\n\n" + " direct_qlen Limit of the direct queue {in packets}\n" + " offload enable hardware offload\n" + "... class add ... htb rate R1 [burst B1] [mpu B] [overhead O]\n" + " [prio P] [slot S] [pslot PS]\n" + " [ceil R2] [cburst B2] [mtu MTU] [quantum Q]\n" + " rate rate allocated to this class (class can still borrow)\n" + " burst max bytes burst which can be accumulated during idle period {computed}\n" + " mpu minimum packet size used in rate computations\n" + " overhead per-packet size overhead used in rate computations\n" + " linklay adapting to a linklayer e.g. atm\n" + " ceil definite upper class rate (no borrows) {rate}\n" + " cburst burst but for ceil {computed}\n" + " mtu max packet size we create rate map for {1600}\n" + " prio priority of leaf; lower are served first {0}\n" + " quantum how much bytes to serve from leaf at once {use r2q}\n" + "\nTC HTB version %d.%d\n", HTB_TC_VER>>16, HTB_TC_VER&0xffff + ); +} + +static void explain1(char *arg) +{ + fprintf(stderr, "Illegal \"%s\"\n", arg); + explain(); +} + +static int htb_parse_opt(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev) +{ + unsigned int direct_qlen = ~0U; + struct tc_htb_glob opt = { + .rate2quantum = 10, + .version = 3, + }; + struct rtattr *tail; + unsigned int i; char *p; + bool offload = false; + + while (argc > 0) { + if (matches(*argv, "r2q") == 0) { + NEXT_ARG(); + if (get_u32(&opt.rate2quantum, *argv, 10)) { + explain1("r2q"); return -1; + } + } else if (matches(*argv, "default") == 0) { + NEXT_ARG(); + if (get_u32(&opt.defcls, *argv, 16)) { + explain1("default"); return -1; + } + } else if (matches(*argv, "debug") == 0) { + NEXT_ARG(); p = *argv; + for (i = 0; i < 16; i++, p++) { + if (*p < '0' || *p > '3') break; + opt.debug |= (*p-'0')<<(2*i); + } + } else if (matches(*argv, "direct_qlen") == 0) { + NEXT_ARG(); + if (get_u32(&direct_qlen, *argv, 10)) { + explain1("direct_qlen"); return -1; + } + } else if (matches(*argv, "offload") == 0) { + offload = true; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 2024, TCA_HTB_INIT, &opt, NLMSG_ALIGN(sizeof(opt))); + if (direct_qlen != ~0U) + addattr_l(n, 2024, TCA_HTB_DIRECT_QLEN, + &direct_qlen, sizeof(direct_qlen)); + if (offload) + addattr(n, 2024, TCA_HTB_OFFLOAD); + addattr_nest_end(n, tail); + return 0; +} + +static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_htb_opt opt = {}; + __u32 rtab[256], ctab[256]; + unsigned buffer = 0, cbuffer = 0; + int cell_log = -1, ccell_log = -1; + unsigned int mtu = 1600; /* eth packet len */ + unsigned short mpu = 0; + unsigned short overhead = 0; + unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */ + struct rtattr *tail; + __u64 ceil64 = 0, rate64 = 0; + char *param; + + while (argc > 0) { + if (matches(*argv, "prio") == 0) { + NEXT_ARG(); + if (get_u32(&opt.prio, *argv, 10)) { + explain1("prio"); return -1; + } + } else if (matches(*argv, "mtu") == 0) { + NEXT_ARG(); + if (get_u32(&mtu, *argv, 10)) { + explain1("mtu"); return -1; + } + } else if (matches(*argv, "mpu") == 0) { + NEXT_ARG(); + if (get_u16(&mpu, *argv, 10)) { + explain1("mpu"); return -1; + } + } else if (matches(*argv, "overhead") == 0) { + NEXT_ARG(); + if (get_u16(&overhead, *argv, 10)) { + explain1("overhead"); return -1; + } + } else if (matches(*argv, "linklayer") == 0) { + NEXT_ARG(); + if (get_linklayer(&linklayer, *argv)) { + explain1("linklayer"); return -1; + } + } else if (matches(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_u32(&opt.quantum, *argv, 10)) { + explain1("quantum"); return -1; + } + } else if (matches(*argv, "burst") == 0 || + strcmp(*argv, "buffer") == 0 || + strcmp(*argv, "maxburst") == 0) { + param = *argv; + NEXT_ARG(); + if (get_size_and_cell(&buffer, &cell_log, *argv) < 0) { + explain1(param); + return -1; + } + } else if (matches(*argv, "cburst") == 0 || + strcmp(*argv, "cbuffer") == 0 || + strcmp(*argv, "cmaxburst") == 0) { + param = *argv; + NEXT_ARG(); + if (get_size_and_cell(&cbuffer, &ccell_log, *argv) < 0) { + explain1(param); + return -1; + } + } else if (strcmp(*argv, "ceil") == 0) { + NEXT_ARG(); + if (ceil64) { + fprintf(stderr, "Double \"ceil\" spec\n"); + return -1; + } + if (strchr(*argv, '%')) { + if (get_percent_rate64(&ceil64, *argv, dev)) { + explain1("ceil"); + return -1; + } + } else if (get_rate64(&ceil64, *argv)) { + explain1("ceil"); + return -1; + } + } else if (strcmp(*argv, "rate") == 0) { + NEXT_ARG(); + if (rate64) { + fprintf(stderr, "Double \"rate\" spec\n"); + return -1; + } + if (strchr(*argv, '%')) { + if (get_percent_rate64(&rate64, *argv, dev)) { + explain1("rate"); + return -1; + } + } else if (get_rate64(&rate64, *argv)) { + explain1("rate"); + return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!rate64) { + fprintf(stderr, "\"rate\" is required.\n"); + return -1; + } + /* if ceil params are missing, use the same as rate */ + if (!ceil64) + ceil64 = rate64; + + opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64; + opt.ceil.rate = (ceil64 >= (1ULL << 32)) ? ~0U : ceil64; + + /* compute minimal allowed burst from rate; mtu is added here to make + sure that buffer is larger than mtu and to have some safeguard space */ + if (!buffer) + buffer = rate64 / get_hz() + mtu; + if (!cbuffer) + cbuffer = ceil64 / get_hz() + mtu; + + opt.ceil.overhead = overhead; + opt.rate.overhead = overhead; + + opt.ceil.mpu = mpu; + opt.rate.mpu = mpu; + + if (tc_calc_rtable(&opt.rate, rtab, cell_log, mtu, linklayer) < 0) { + fprintf(stderr, "htb: failed to calculate rate table.\n"); + return -1; + } + opt.buffer = tc_calc_xmittime(rate64, buffer); + + if (tc_calc_rtable(&opt.ceil, ctab, ccell_log, mtu, linklayer) < 0) { + fprintf(stderr, "htb: failed to calculate ceil rate table.\n"); + return -1; + } + opt.cbuffer = tc_calc_xmittime(ceil64, cbuffer); + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + + if (rate64 >= (1ULL << 32)) + addattr_l(n, 1124, TCA_HTB_RATE64, &rate64, sizeof(rate64)); + + if (ceil64 >= (1ULL << 32)) + addattr_l(n, 1224, TCA_HTB_CEIL64, &ceil64, sizeof(ceil64)); + + addattr_l(n, 2024, TCA_HTB_PARMS, &opt, sizeof(opt)); + addattr_l(n, 3024, TCA_HTB_RTAB, rtab, 1024); + addattr_l(n, 4024, TCA_HTB_CTAB, ctab, 1024); + addattr_nest_end(n, tail); + return 0; +} + +static int htb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_HTB_MAX + 1]; + struct tc_htb_opt *hopt; + struct tc_htb_glob *gopt; + double buffer, cbuffer; + unsigned int linklayer; + __u64 rate64, ceil64; + + SPRINT_BUF(b1); + SPRINT_BUF(b3); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_HTB_MAX, opt); + + if (tb[TCA_HTB_PARMS]) { + hopt = RTA_DATA(tb[TCA_HTB_PARMS]); + if (RTA_PAYLOAD(tb[TCA_HTB_PARMS]) < sizeof(*hopt)) return -1; + + if (!hopt->level) { + print_int(PRINT_ANY, "prio", "prio %d ", (int)hopt->prio); + if (show_details) + print_int(PRINT_ANY, "quantum", "quantum %d ", + (int)hopt->quantum); + } + + rate64 = hopt->rate.rate; + if (tb[TCA_HTB_RATE64] && + RTA_PAYLOAD(tb[TCA_HTB_RATE64]) >= sizeof(rate64)) { + rate64 = rta_getattr_u64(tb[TCA_HTB_RATE64]); + } + + ceil64 = hopt->ceil.rate; + if (tb[TCA_HTB_CEIL64] && + RTA_PAYLOAD(tb[TCA_HTB_CEIL64]) >= sizeof(ceil64)) + ceil64 = rta_getattr_u64(tb[TCA_HTB_CEIL64]); + + tc_print_rate(PRINT_ANY, "rate", "rate %s ", rate64); + if (hopt->rate.overhead) + print_uint(PRINT_ANY, "overhead", "overhead %u ", hopt->rate.overhead); + buffer = tc_calc_xmitsize(rate64, hopt->buffer); + + tc_print_rate(PRINT_ANY, "ceil", "ceil %s ", ceil64); + cbuffer = tc_calc_xmitsize(ceil64, hopt->cbuffer); + linklayer = (hopt->rate.linklayer & TC_LINKLAYER_MASK); + if (linklayer > TC_LINKLAYER_ETHERNET || show_details) + print_string(PRINT_ANY, "linklayer", "linklayer %s ", + sprint_linklayer(linklayer, b3)); + if (show_details) { + print_size(PRINT_ANY, "burst", "burst %s/", buffer); + print_uint(PRINT_ANY, "burst_cell", "%u", 1<<hopt->rate.cell_log); + print_size(PRINT_ANY, "mpu_rate", "mpu %s ", hopt->rate.mpu); + print_size(PRINT_ANY, "cburst", "cburst %s/", cbuffer); + print_uint(PRINT_ANY, "cburst_cell", "%u", 1<<hopt->ceil.cell_log); + print_size(PRINT_ANY, "mpu_ceil", "mpu %s ", hopt->ceil.mpu); + print_int(PRINT_ANY, "level", "level %d ", (int)hopt->level); + } else { + print_size(PRINT_ANY, "burst", "burst %s ", buffer); + print_size(PRINT_ANY, "cburst", "cburst %s", cbuffer); + } + if (show_raw) + fprintf(f, "buffer [%08x] cbuffer [%08x] ", + hopt->buffer, hopt->cbuffer); + } + if (tb[TCA_HTB_INIT]) { + gopt = RTA_DATA(tb[TCA_HTB_INIT]); + if (RTA_PAYLOAD(tb[TCA_HTB_INIT]) < sizeof(*gopt)) return -1; + + print_int(PRINT_ANY, "r2q", "r2q %d", gopt->rate2quantum); + print_0xhex(PRINT_ANY, "default", " default %#llx", gopt->defcls); + print_uint(PRINT_ANY, "direct_packets_stat", + " direct_packets_stat %u", gopt->direct_pkts); + if (show_details) { + sprintf(b1, "%d.%d", gopt->version >> 16, gopt->version & 0xffff); + print_string(PRINT_ANY, "ver", " ver %s", b1); + } + } + if (tb[TCA_HTB_DIRECT_QLEN] && + RTA_PAYLOAD(tb[TCA_HTB_DIRECT_QLEN]) >= sizeof(__u32)) { + __u32 direct_qlen = rta_getattr_u32(tb[TCA_HTB_DIRECT_QLEN]); + + print_uint(PRINT_ANY, "direct_qlen", " direct_qlen %u", + direct_qlen); + } + if (tb[TCA_HTB_OFFLOAD]) + print_null(PRINT_ANY, "offload", " offload", NULL); + return 0; +} + +static int htb_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats) +{ + struct tc_htb_xstats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + print_uint(PRINT_ANY, "lended", " lended: %u ", st->lends); + print_uint(PRINT_ANY, "borrowed", "borrowed: %u ", st->borrows); + print_uint(PRINT_ANY, "giants", "giants: %u", st->giants); + print_nl(); + print_int(PRINT_ANY, "tokens", " tokens: %d ", st->tokens); + print_int(PRINT_ANY, "ctokens", "ctokens: %d", st->ctokens); + print_nl(); + return 0; +} + +struct qdisc_util htb_qdisc_util = { + .id = "htb", + .parse_qopt = htb_parse_opt, + .print_qopt = htb_print_opt, + .print_xstats = htb_print_xstats, + .parse_copt = htb_parse_class_opt, + .print_copt = htb_print_opt, +}; diff --git a/tc/q_ingress.c b/tc/q_ingress.c new file mode 100644 index 0000000..3df4914 --- /dev/null +++ b/tc/q_ingress.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_ingress.c INGRESS. + * + * Authors: J Hadi Salim + */ + +#include <stdio.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... ingress\n"); +} + +static int ingress_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + while (argc > 0) { + if (strcmp(*argv, "handle") == 0) { + NEXT_ARG(); + argc--; argv++; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + } + + return 0; +} + +static int ingress_print_opt(struct qdisc_util *qu, FILE *f, + struct rtattr *opt) +{ + print_string(PRINT_FP, NULL, "---------------- ", NULL); + return 0; +} + +struct qdisc_util ingress_qdisc_util = { + .id = "ingress", + .parse_qopt = ingress_parse_opt, + .print_qopt = ingress_print_opt, +}; diff --git a/tc/q_mqprio.c b/tc/q_mqprio.c new file mode 100644 index 0000000..7a4417f --- /dev/null +++ b/tc/q_mqprio.c @@ -0,0 +1,419 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_mqprio.c MQ prio qdisc + * + * Author: John Fastabend, <john.r.fastabend@intel.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... mqprio [num_tc NUMBER] [map P0 P1 ...]\n" + " [queues count1@offset1 count2@offset2 ...] " + "[hw 1|0]\n" + " [fp FP0 FP1 FP2 ...]\n" + " [mode dcb|channel]\n" + " [shaper bw_rlimit SHAPER_PARAMS]\n" + "Where: SHAPER_PARAMS := { min_rate MIN_RATE1 MIN_RATE2 ...|\n" + " max_rate MAX_RATE1 MAX_RATE2 ... }\n"); +} + +static void add_tc_entries(struct nlmsghdr *n, __u32 fp[TC_QOPT_MAX_QUEUE], + int num_fp_entries) +{ + struct rtattr *l; + __u32 tc; + + for (tc = 0; tc < num_fp_entries; tc++) { + l = addattr_nest(n, 1024, TCA_MQPRIO_TC_ENTRY | NLA_F_NESTED); + + addattr32(n, 1024, TCA_MQPRIO_TC_ENTRY_INDEX, tc); + addattr32(n, 1024, TCA_MQPRIO_TC_ENTRY_FP, fp[tc]); + + addattr_nest_end(n, l); + } +} + +static int mqprio_parse_opt(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev) +{ + int idx; + struct tc_mqprio_qopt opt = { + .num_tc = 8, + .prio_tc_map = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 1, 1, 3, 3, 3, 3 }, + .hw = 1, + .count = { }, + .offset = { }, + }; + __u64 min_rate64[TC_QOPT_MAX_QUEUE] = {0}; + __u64 max_rate64[TC_QOPT_MAX_QUEUE] = {0}; + __u16 shaper = TC_MQPRIO_SHAPER_DCB; + __u32 fp[TC_QOPT_MAX_QUEUE] = { }; + __u16 mode = TC_MQPRIO_MODE_DCB; + bool have_tc_entries = false; + int num_fp_entries = 0; + int cnt_off_pairs = 0; + struct rtattr *tail; + __u32 flags = 0; + + while (argc > 0) { + idx = 0; + if (strcmp(*argv, "num_tc") == 0) { + NEXT_ARG(); + if (get_u8(&opt.num_tc, *argv, 10)) { + fprintf(stderr, "Illegal \"num_tc\"\n"); + return -1; + } + } else if (strcmp(*argv, "map") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (get_u8(&opt.prio_tc_map[idx], *argv, 10)) { + PREV_ARG(); + break; + } + idx++; + } + for ( ; idx < TC_QOPT_MAX_QUEUE; idx++) + opt.prio_tc_map[idx] = 0; + } else if (strcmp(*argv, "queues") == 0) { + char *tmp, *tok; + + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + + tmp = strdup(*argv); + if (!tmp) + break; + + tok = strtok(tmp, "@"); + if (get_u16(&opt.count[idx], tok, 10)) { + free(tmp); + PREV_ARG(); + break; + } + tok = strtok(NULL, "@"); + if (get_u16(&opt.offset[idx], tok, 10)) { + free(tmp); + PREV_ARG(); + break; + } + free(tmp); + idx++; + cnt_off_pairs++; + } + } else if (strcmp(*argv, "fp") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (strcmp(*argv, "E") == 0) { + fp[idx] = TC_FP_EXPRESS; + } else if (strcmp(*argv, "P") == 0) { + fp[idx] = TC_FP_PREEMPTIBLE; + } else { + PREV_ARG(); + break; + } + num_fp_entries++; + idx++; + } + have_tc_entries = true; + } else if (strcmp(*argv, "hw") == 0) { + NEXT_ARG(); + if (get_u8(&opt.hw, *argv, 10)) { + fprintf(stderr, "Illegal \"hw\"\n"); + return -1; + } + idx++; + } else if (opt.hw && strcmp(*argv, "mode") == 0) { + NEXT_ARG(); + if (matches(*argv, "dcb") == 0) { + mode = TC_MQPRIO_MODE_DCB; + } else if (matches(*argv, "channel") == 0) { + mode = TC_MQPRIO_MODE_CHANNEL; + } else { + fprintf(stderr, "Illegal mode (%s)\n", + *argv); + return -1; + } + if (mode != TC_MQPRIO_MODE_DCB) + flags |= TC_MQPRIO_F_MODE; + idx++; + } else if (opt.hw && strcmp(*argv, "shaper") == 0) { + NEXT_ARG(); + if (matches(*argv, "dcb") == 0) { + shaper = TC_MQPRIO_SHAPER_DCB; + } else if (matches(*argv, "bw_rlimit") == 0) { + shaper = TC_MQPRIO_SHAPER_BW_RATE; + if (!NEXT_ARG_OK()) { + fprintf(stderr, "Incomplete shaper arguments\n"); + return -1; + } + } else { + fprintf(stderr, "Illegal shaper (%s)\n", + *argv); + return -1; + } + if (shaper != TC_MQPRIO_SHAPER_DCB) + flags |= TC_MQPRIO_F_SHAPER; + idx++; + } else if ((shaper == TC_MQPRIO_SHAPER_BW_RATE) && + strcmp(*argv, "min_rate") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (get_rate64(&min_rate64[idx], *argv)) { + PREV_ARG(); + break; + } + idx++; + } + if (idx < opt.num_tc && !NEXT_ARG_OK()) { + fprintf(stderr, "Incomplete arguments, min_rate values expected\n"); + return -1; + } + flags |= TC_MQPRIO_F_MIN_RATE; + } else if ((shaper == TC_MQPRIO_SHAPER_BW_RATE) && + strcmp(*argv, "max_rate") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (get_rate64(&max_rate64[idx], *argv)) { + PREV_ARG(); + break; + } + idx++; + } + if (idx < opt.num_tc && !NEXT_ARG_OK()) { + fprintf(stderr, "Incomplete arguments, max_rate values expected\n"); + return -1; + } + flags |= TC_MQPRIO_F_MAX_RATE; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + invarg("unknown argument", *argv); + } + argc--; argv++; + } + + if (cnt_off_pairs > opt.num_tc) { + fprintf(stderr, "queues count/offset pair count %d can not be higher than given num_tc %d\n", + cnt_off_pairs, opt.num_tc); + return -1; + } + + tail = NLMSG_TAIL(n); + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + + if (flags & TC_MQPRIO_F_MODE) + addattr_l(n, 1024, TCA_MQPRIO_MODE, + &mode, sizeof(mode)); + if (flags & TC_MQPRIO_F_SHAPER) + addattr_l(n, 1024, TCA_MQPRIO_SHAPER, + &shaper, sizeof(shaper)); + + if (have_tc_entries) + add_tc_entries(n, fp, num_fp_entries); + + if (flags & TC_MQPRIO_F_MIN_RATE) { + struct rtattr *start; + + start = addattr_nest(n, 1024, + TCA_MQPRIO_MIN_RATE64 | NLA_F_NESTED); + + for (idx = 0; idx < TC_QOPT_MAX_QUEUE; idx++) + addattr_l(n, 1024, TCA_MQPRIO_MIN_RATE64, + &min_rate64[idx], sizeof(min_rate64[idx])); + + addattr_nest_end(n, start); + } + + if (flags & TC_MQPRIO_F_MAX_RATE) { + struct rtattr *start; + + start = addattr_nest(n, 1024, + TCA_MQPRIO_MAX_RATE64 | NLA_F_NESTED); + + for (idx = 0; idx < TC_QOPT_MAX_QUEUE; idx++) + addattr_l(n, 1024, TCA_MQPRIO_MAX_RATE64, + &max_rate64[idx], sizeof(max_rate64[idx])); + + addattr_nest_end(n, start); + } + + tail->rta_len = (void *)NLMSG_TAIL(n) - (void *)tail; + + return 0; +} + +static void dump_tc_entry(struct rtattr *rta, __u32 fp[TC_QOPT_MAX_QUEUE], + int *max_tc_fp) +{ + struct rtattr *tb[TCA_MQPRIO_TC_ENTRY_MAX + 1]; + __u32 tc, val = 0; + + parse_rtattr_nested(tb, TCA_MQPRIO_TC_ENTRY_MAX, rta); + + if (!tb[TCA_MQPRIO_TC_ENTRY_INDEX]) { + fprintf(stderr, "Missing tc entry index\n"); + return; + } + + tc = rta_getattr_u32(tb[TCA_MQPRIO_TC_ENTRY_INDEX]); + /* Prevent array out of bounds access */ + if (tc >= TC_QOPT_MAX_QUEUE) { + fprintf(stderr, "Unexpected tc entry index %d\n", tc); + return; + } + + if (tb[TCA_MQPRIO_TC_ENTRY_FP]) { + val = rta_getattr_u32(tb[TCA_MQPRIO_TC_ENTRY_FP]); + fp[tc] = val; + + if (*max_tc_fp < (int)tc) + *max_tc_fp = tc; + } +} + +static void dump_tc_entries(FILE *f, struct rtattr *opt, int len) +{ + __u32 fp[TC_QOPT_MAX_QUEUE] = {}; + int max_tc_fp = -1; + struct rtattr *rta; + int tc; + + for (rta = opt; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + if (rta->rta_type != (TCA_MQPRIO_TC_ENTRY | NLA_F_NESTED)) + continue; + + dump_tc_entry(rta, fp, &max_tc_fp); + } + + if (max_tc_fp >= 0) { + open_json_array(PRINT_ANY, + is_json_context() ? "fp" : "\n fp:"); + for (tc = 0; tc <= max_tc_fp; tc++) { + print_string(PRINT_ANY, NULL, " %s", + fp[tc] == TC_FP_PREEMPTIBLE ? "P" : + fp[tc] == TC_FP_EXPRESS ? "E" : + "?"); + } + close_json_array(PRINT_ANY, ""); + + print_nl(); + } +} + +static int mqprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + int i; + struct tc_mqprio_qopt *qopt; + __u64 min_rate64[TC_QOPT_MAX_QUEUE] = {0}; + __u64 max_rate64[TC_QOPT_MAX_QUEUE] = {0}; + int len; + + if (opt == NULL) + return 0; + + len = RTA_PAYLOAD(opt) - RTA_ALIGN(sizeof(*qopt)); + if (len < 0) { + fprintf(stderr, "options size error\n"); + return -1; + } + + qopt = RTA_DATA(opt); + + print_uint(PRINT_ANY, "tc", "tc %u ", qopt->num_tc); + open_json_array(PRINT_ANY, is_json_context() ? "map" : "map "); + for (i = 0; i <= TC_PRIO_MAX; i++) + print_uint(PRINT_ANY, NULL, "%u ", qopt->prio_tc_map[i]); + close_json_array(PRINT_ANY, ""); + open_json_array(PRINT_ANY, is_json_context() ? "queues" : "\n queues:"); + for (i = 0; i < qopt->num_tc; i++) { + open_json_array(PRINT_JSON, NULL); + print_uint(PRINT_ANY, NULL, "(%u:", qopt->offset[i]); + print_uint(PRINT_ANY, NULL, "%u) ", qopt->offset[i] + qopt->count[i] - 1); + close_json_array(PRINT_JSON, NULL); + } + close_json_array(PRINT_ANY, ""); + + if (len > 0) { + struct rtattr *tb[TCA_MQPRIO_MAX + 1]; + + parse_rtattr(tb, TCA_MQPRIO_MAX, + RTA_DATA(opt) + RTA_ALIGN(sizeof(*qopt)), + len); + + if (tb[TCA_MQPRIO_MODE]) { + __u16 *mode = RTA_DATA(tb[TCA_MQPRIO_MODE]); + + if (*mode == TC_MQPRIO_MODE_CHANNEL) + print_string(PRINT_ANY, "mode", "\n mode:%s", "channel"); + } else { + print_string(PRINT_ANY, "mode", "\n mode:%s", "dcb"); + } + + if (tb[TCA_MQPRIO_SHAPER]) { + __u16 *shaper = RTA_DATA(tb[TCA_MQPRIO_SHAPER]); + + if (*shaper == TC_MQPRIO_SHAPER_BW_RATE) + print_string(PRINT_ANY, "shaper", "\n shaper:%s", "bw_rlimit"); + } else { + print_string(PRINT_ANY, "shaper", "\n shaper:%s", "dcb"); + } + + if (tb[TCA_MQPRIO_MIN_RATE64]) { + struct rtattr *r; + int rem = RTA_PAYLOAD(tb[TCA_MQPRIO_MIN_RATE64]); + __u64 *min = min_rate64; + + for (r = RTA_DATA(tb[TCA_MQPRIO_MIN_RATE64]); + RTA_OK(r, rem); r = RTA_NEXT(r, rem)) { + if (r->rta_type != TCA_MQPRIO_MIN_RATE64) + return -1; + *(min++) = rta_getattr_u64(r); + } + open_json_array(PRINT_ANY, is_json_context() ? "min_rate" : " min_rate:"); + for (i = 0; i < qopt->num_tc; i++) + tc_print_rate(PRINT_ANY, NULL, "%s ", min_rate64[i]); + close_json_array(PRINT_ANY, ""); + } + + if (tb[TCA_MQPRIO_MAX_RATE64]) { + struct rtattr *r; + int rem = RTA_PAYLOAD(tb[TCA_MQPRIO_MAX_RATE64]); + __u64 *max = max_rate64; + + for (r = RTA_DATA(tb[TCA_MQPRIO_MAX_RATE64]); + RTA_OK(r, rem); r = RTA_NEXT(r, rem)) { + if (r->rta_type != TCA_MQPRIO_MAX_RATE64) + return -1; + *(max++) = rta_getattr_u64(r); + } + open_json_array(PRINT_ANY, is_json_context() ? "max_rate" : " max_rate:"); + for (i = 0; i < qopt->num_tc; i++) + tc_print_rate(PRINT_ANY, NULL, "%s ", max_rate64[i]); + close_json_array(PRINT_ANY, ""); + } + + dump_tc_entries(f, RTA_DATA(opt) + RTA_ALIGN(sizeof(*qopt)), len); + } + + return 0; +} + +struct qdisc_util mqprio_qdisc_util = { + .id = "mqprio", + .parse_qopt = mqprio_parse_opt, + .print_qopt = mqprio_print_opt, +}; diff --git a/tc/q_multiq.c b/tc/q_multiq.c new file mode 100644 index 0000000..b1e6c9a --- /dev/null +++ b/tc/q_multiq.c @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * q_multiq.c Multiqueue aware qdisc + * + * Copyright (c) 2008, Intel Corporation. + * + * Author: Alexander Duyck <alexander.h.duyck@intel.com> + * + * Original Authors: PJ Waskiewicz, <peter.p.waskiewicz.jr@intel.com> (RR) + * Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> (from PRIO) + * + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... multiq [help]\n"); +} + +static int multiq_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_multiq_qopt opt = {}; + + if (argc) { + if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + } + + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + return 0; +} + +static int multiq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_multiq_qopt *qopt; + + if (opt == NULL) + return 0; + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + return 0; + + qopt = RTA_DATA(opt); + + fprintf(f, "bands %u/%u ", qopt->bands, qopt->max_bands); + + return 0; +} + +struct qdisc_util multiq_qdisc_util = { + .id = "multiq", + .parse_qopt = multiq_parse_opt, + .print_qopt = multiq_print_opt, +}; diff --git a/tc/q_netem.c b/tc/q_netem.c new file mode 100644 index 0000000..4ce9ab6 --- /dev/null +++ b/tc/q_netem.c @@ -0,0 +1,867 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Author: Stephen Hemminger <shemminger@linux-foundation.org> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdint.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <errno.h> + +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... netem [ limit PACKETS ]\n" + " [ delay TIME [ JITTER [ CORRELATION ] ] ]\n" + " [ distribution {uniform|normal|pareto|paretonormal} ]\n" + " [ corrupt PERCENT [ CORRELATION ] ]\n" + " [ duplicate PERCENT [ CORRELATION ] ]\n" + " [ loss random PERCENT [ CORRELATION ] ]\n" + " [ loss state P13 [ P31 [ P32 [ P23 [ P14 ] ] ] ] ]\n" + " [ loss gemodel PERCENT [ R [ 1-H [ 1-K ] ] ] ]\n" + " [ seed SEED ]\n" + " [ ecn ]\n" + " [ reorder PERCENT [ CORRELATION ] [ gap DISTANCE ] ]\n" + " [ rate RATE [ PACKETOVERHEAD ] [ CELLSIZE ] [ CELLOVERHEAD ] ]\n" + " [ slot MIN_DELAY [ MAX_DELAY ] [ packets MAX_PACKETS ] [ bytes MAX_BYTES ] ]\n" + " [ slot distribution {uniform|normal|pareto|paretonormal|custom}\n" + " DELAY JITTER [ packets MAX_PACKETS ] [ bytes MAX_BYTES ] ]\n"); +} + +static void explain1(const char *arg) +{ + fprintf(stderr, "Illegal \"%s\"\n", arg); +} + +/* Upper bound on size of distribution + * really (TCA_BUF_MAX - other headers) / sizeof (__s16) + */ +#define MAX_DIST (16*1024) + +/* Print values only if they are non-zero */ +static void __attribute__((format(printf, 2, 0))) +__print_int_opt(const char *label_json, const char *label_fp, int val) +{ + print_int(PRINT_JSON, label_json, NULL, val); + if (val != 0) + print_int(PRINT_FP, NULL, label_fp, val); +} +#define PRINT_INT_OPT(label, val) \ + __print_int_opt(label, " " label " %d", (val)) + +/* Time print prints normally with varying units, but for JSON prints + * in seconds (1ms vs 0.001). + */ +static void __attribute__((format(printf, 2, 0))) +__print_time64(const char *label_json, const char *label_fp, __u64 val) +{ + SPRINT_BUF(b1); + + print_string(PRINT_FP, NULL, label_fp, sprint_time64(val, b1)); + print_float(PRINT_JSON, label_json, NULL, val / 1000000000.); +} +#define __PRINT_TIME64(label_json, label_fp, val) \ + __print_time64(label_json, label_fp " %s", (val)) +#define PRINT_TIME64(label, val) __PRINT_TIME64(label, " " label, (val)) + +/* Percent print prints normally in percentage points, but for JSON prints + * an absolute value (1% vs 0.01). + */ +static void __attribute__((format(printf, 2, 0))) +__print_percent(const char *label_json, const char *label_fp, __u32 per) +{ + print_float(PRINT_FP, NULL, label_fp, (100. * per) / UINT32_MAX); + print_float(PRINT_JSON, label_json, NULL, (1. * per) / UINT32_MAX); +} +#define __PRINT_PERCENT(label_json, label_fp, per) \ + __print_percent(label_json, label_fp " %g%%", (per)) +#define PRINT_PERCENT(label, per) __PRINT_PERCENT(label, " " label, (per)) + +/* scaled value used to percent of maximum. */ +static void set_percent(__u32 *percent, double per) +{ + *percent = rint(per * UINT32_MAX); +} + +static int get_percent(__u32 *percent, const char *str) +{ + double per; + + if (parse_percent(&per, str)) + return -1; + + set_percent(percent, per); + return 0; +} + +static void print_corr(bool present, __u32 value) +{ + if (!is_json_context()) { + if (present) + __PRINT_PERCENT("", "", value); + } else { + PRINT_PERCENT("correlation", value); + } +} + +/* + * Simplistic file parser for distribution data. + * Format is: + * # comment line(s) + * data0 data1 ... + */ +static int get_distribution(const char *type, __s16 *data, int maxdata) +{ + FILE *f; + int n; + long x; + size_t len; + char *line = NULL; + char name[128]; + + snprintf(name, sizeof(name), "%s/%s.dist", get_tc_lib(), type); + f = fopen(name, "r"); + if (f == NULL) { + fprintf(stderr, "No distribution data for %s (%s: %s)\n", + type, name, strerror(errno)); + return -1; + } + + n = 0; + while (getline(&line, &len, f) != -1) { + char *p, *endp; + + if (*line == '\n' || *line == '#') + continue; + + for (p = line; ; p = endp) { + x = strtol(p, &endp, 0); + if (endp == p) + break; + + if (n >= maxdata) { + fprintf(stderr, "%s: too much data\n", + name); + n = -1; + goto error; + } + data[n++] = x; + } + } + error: + free(line); + fclose(f); + return n; +} + +#define NEXT_IS_NUMBER() (NEXT_ARG_OK() && isdigit(argv[1][0])) +#define NEXT_IS_SIGNED_NUMBER() \ + (NEXT_ARG_OK() && (isdigit(argv[1][0]) || argv[1][0] == '-')) + +/* + * Adjust for the fact that psched_ticks aren't always usecs + * (based on kernel PSCHED_CLOCK configuration + */ +static int get_ticks(__u32 *ticks, const char *str) +{ + unsigned int t; + + if (get_time(&t, str)) + return -1; + + if (tc_core_time2big(t)) { + fprintf(stderr, "Illegal %u time (too large)\n", t); + return -1; + } + + *ticks = tc_core_time2tick(t); + return 0; +} + +static int netem_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + int dist_size = 0; + int slot_dist_size = 0; + struct rtattr *tail; + struct tc_netem_qopt opt = { .limit = 1000 }; + struct tc_netem_corr cor = {}; + struct tc_netem_reorder reorder = {}; + struct tc_netem_corrupt corrupt = {}; + struct tc_netem_gimodel gimodel; + struct tc_netem_gemodel gemodel; + struct tc_netem_rate rate = {}; + struct tc_netem_slot slot = {}; + __s16 *dist_data = NULL; + __s16 *slot_dist_data = NULL; + __u16 loss_type = NETEM_LOSS_UNSPEC; + int present[__TCA_NETEM_MAX] = {}; + __u64 rate64 = 0; + __u64 seed = 0; + + for ( ; argc > 0; --argc, ++argv) { + if (matches(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + explain1("limit"); + return -1; + } + } else if (matches(*argv, "latency") == 0 || + matches(*argv, "delay") == 0) { + NEXT_ARG(); + if (get_ticks(&opt.latency, *argv)) { + explain1("latency"); + return -1; + } + + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_ticks(&opt.jitter, *argv)) { + explain1("latency"); + return -1; + } + + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + ++present[TCA_NETEM_CORR]; + if (get_percent(&cor.delay_corr, *argv)) { + explain1("latency"); + return -1; + } + } + } + } else if (matches(*argv, "loss") == 0 || + matches(*argv, "drop") == 0) { + if (opt.loss > 0 || loss_type != NETEM_LOSS_UNSPEC) { + explain1("duplicate loss argument\n"); + return -1; + } + + NEXT_ARG(); + /* Old (deprecated) random loss model syntax */ + if (isdigit(argv[0][0])) + goto random_loss_model; + + if (!strcmp(*argv, "random")) { + NEXT_ARG(); +random_loss_model: + if (get_percent(&opt.loss, *argv)) { + explain1("loss percent"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + ++present[TCA_NETEM_CORR]; + if (get_percent(&cor.loss_corr, *argv)) { + explain1("loss correlation"); + return -1; + } + } + } else if (!strcmp(*argv, "state")) { + double p13; + + NEXT_ARG(); + if (parse_percent(&p13, *argv)) { + explain1("loss p13"); + return -1; + } + + /* set defaults */ + set_percent(&gimodel.p13, p13); + set_percent(&gimodel.p31, 1. - p13); + set_percent(&gimodel.p32, 0); + set_percent(&gimodel.p23, 1.); + set_percent(&gimodel.p14, 0); + loss_type = NETEM_LOSS_GI; + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gimodel.p31, *argv)) { + explain1("loss p31"); + return -1; + } + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gimodel.p32, *argv)) { + explain1("loss p32"); + return -1; + } + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gimodel.p23, *argv)) { + explain1("loss p23"); + return -1; + } + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gimodel.p14, *argv)) { + explain1("loss p14"); + return -1; + } + + } else if (!strcmp(*argv, "gemodel")) { + double p; + + NEXT_ARG(); + if (parse_percent(&p, *argv)) { + explain1("loss gemodel p"); + return -1; + } + set_percent(&gemodel.p, p); + + /* set defaults */ + set_percent(&gemodel.r, 1. - p); + set_percent(&gemodel.h, 0); + set_percent(&gemodel.k1, 0); + loss_type = NETEM_LOSS_GE; + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gemodel.r, *argv)) { + explain1("loss gemodel r"); + return -1; + } + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gemodel.h, *argv)) { + explain1("loss gemodel h"); + return -1; + } + /* netem option is "1-h" but kernel + * expects "h". + */ + gemodel.h = UINT32_MAX - gemodel.h; + + if (!NEXT_IS_NUMBER()) + continue; + NEXT_ARG(); + if (get_percent(&gemodel.k1, *argv)) { + explain1("loss gemodel k"); + return -1; + } + } else { + fprintf(stderr, "Unknown loss parameter: %s\n", + *argv); + return -1; + } + } else if (matches(*argv, "seed") == 0) { + NEXT_ARG(); + present[TCA_NETEM_PRNG_SEED] = 1; + if (get_u64(&seed, *argv, 10)) { + explain1("seed"); + return -1; + } + } else if (matches(*argv, "ecn") == 0) { + present[TCA_NETEM_ECN] = 1; + } else if (matches(*argv, "reorder") == 0) { + NEXT_ARG(); + present[TCA_NETEM_REORDER] = 1; + if (get_percent(&reorder.probability, *argv)) { + explain1("reorder"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + ++present[TCA_NETEM_CORR]; + if (get_percent(&reorder.correlation, *argv)) { + explain1("reorder"); + return -1; + } + } + } else if (matches(*argv, "corrupt") == 0) { + NEXT_ARG(); + present[TCA_NETEM_CORRUPT] = 1; + if (get_percent(&corrupt.probability, *argv)) { + explain1("corrupt"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + ++present[TCA_NETEM_CORR]; + if (get_percent(&corrupt.correlation, *argv)) { + explain1("corrupt"); + return -1; + } + } + } else if (matches(*argv, "gap") == 0) { + NEXT_ARG(); + if (get_u32(&opt.gap, *argv, 0)) { + explain1("gap"); + return -1; + } + } else if (matches(*argv, "duplicate") == 0) { + NEXT_ARG(); + if (get_percent(&opt.duplicate, *argv)) { + explain1("duplicate"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_percent(&cor.dup_corr, *argv)) { + explain1("duplicate"); + return -1; + } + } + } else if (matches(*argv, "distribution") == 0) { + NEXT_ARG(); + dist_data = calloc(sizeof(dist_data[0]), MAX_DIST); + if (dist_data == NULL) + return -1; + + dist_size = get_distribution(*argv, dist_data, MAX_DIST); + if (dist_size <= 0) { + free(dist_data); + return -1; + } + } else if (matches(*argv, "rate") == 0) { + ++present[TCA_NETEM_RATE]; + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate64(&rate64, *argv, dev)) { + explain1("rate"); + return -1; + } + } else if (get_rate64(&rate64, *argv)) { + explain1("rate"); + return -1; + } + if (NEXT_IS_SIGNED_NUMBER()) { + NEXT_ARG(); + if (get_s32(&rate.packet_overhead, *argv, 0)) { + explain1("rate"); + return -1; + } + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_u32(&rate.cell_size, *argv, 0)) { + explain1("rate"); + return -1; + } + } + if (NEXT_IS_SIGNED_NUMBER()) { + NEXT_ARG(); + if (get_s32(&rate.cell_overhead, *argv, 0)) { + explain1("rate"); + return -1; + } + } + } else if (matches(*argv, "slot") == 0) { + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + present[TCA_NETEM_SLOT] = 1; + if (get_time64(&slot.min_delay, *argv)) { + explain1("slot min_delay"); + return -1; + } + if (NEXT_IS_NUMBER()) { + NEXT_ARG(); + if (get_time64(&slot.max_delay, *argv) || + slot.max_delay < slot.min_delay) { + explain1("slot max_delay"); + return -1; + } + } else { + slot.max_delay = slot.min_delay; + } + } else { + NEXT_ARG(); + if (strcmp(*argv, "distribution") == 0) { + present[TCA_NETEM_SLOT] = 1; + NEXT_ARG(); + slot_dist_data = calloc(sizeof(slot_dist_data[0]), MAX_DIST); + if (!slot_dist_data) + return -1; + slot_dist_size = get_distribution(*argv, slot_dist_data, MAX_DIST); + if (slot_dist_size <= 0) { + free(slot_dist_data); + return -1; + } + NEXT_ARG(); + if (get_time64(&slot.dist_delay, *argv)) { + explain1("slot delay"); + return -1; + } + NEXT_ARG(); + if (get_time64(&slot.dist_jitter, *argv)) { + explain1("slot jitter"); + return -1; + } + if (slot.dist_jitter <= 0) { + fprintf(stderr, "Non-positive jitter\n"); + return -1; + } + } else { + fprintf(stderr, "Unknown slot parameter: %s\n", + *argv); + return -1; + } + } + if (NEXT_ARG_OK() && + matches(*(argv+1), "packets") == 0) { + NEXT_ARG(); + if (!NEXT_ARG_OK() || + get_s32(&slot.max_packets, *(argv+1), 0)) { + explain1("slot packets"); + return -1; + } + NEXT_ARG(); + } + if (NEXT_ARG_OK() && + matches(*(argv+1), "bytes") == 0) { + unsigned int max_bytes; + + NEXT_ARG(); + if (!NEXT_ARG_OK() || + get_size(&max_bytes, *(argv+1))) { + explain1("slot bytes"); + return -1; + } + slot.max_bytes = (int) max_bytes; + NEXT_ARG(); + } + } else { + if (strcmp(*argv, "help") != 0) + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + } + + tail = NLMSG_TAIL(n); + + if (reorder.probability) { + if (opt.latency == 0) { + fprintf(stderr, "reordering not possible without specifying some delay\n"); + explain(); + return -1; + } + if (opt.gap == 0) + opt.gap = 1; + } else if (opt.gap > 0) { + fprintf(stderr, "gap specified without reorder probability\n"); + explain(); + return -1; + } + + if (present[TCA_NETEM_ECN]) { + if (opt.loss <= 0 && loss_type == NETEM_LOSS_UNSPEC) { + fprintf(stderr, "ecn requested without loss model\n"); + explain(); + return -1; + } + } + + if (dist_data && (opt.latency == 0 || opt.jitter == 0)) { + fprintf(stderr, "distribution specified but no latency and jitter values\n"); + explain(); + return -1; + } + + if (addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)) < 0) + return -1; + + if (present[TCA_NETEM_CORR] && + addattr_l(n, 1024, TCA_NETEM_CORR, &cor, sizeof(cor)) < 0) + return -1; + + if (present[TCA_NETEM_REORDER] && + addattr_l(n, 1024, TCA_NETEM_REORDER, &reorder, sizeof(reorder)) < 0) + return -1; + + if (present[TCA_NETEM_ECN] && + addattr_l(n, 1024, TCA_NETEM_ECN, &present[TCA_NETEM_ECN], + sizeof(present[TCA_NETEM_ECN])) < 0) + return -1; + + if (present[TCA_NETEM_CORRUPT] && + addattr_l(n, 1024, TCA_NETEM_CORRUPT, &corrupt, sizeof(corrupt)) < 0) + return -1; + + if (present[TCA_NETEM_SLOT] && + addattr_l(n, 1024, TCA_NETEM_SLOT, &slot, sizeof(slot)) < 0) + return -1; + + if (loss_type != NETEM_LOSS_UNSPEC) { + struct rtattr *start; + + start = addattr_nest(n, 1024, TCA_NETEM_LOSS | NLA_F_NESTED); + if (loss_type == NETEM_LOSS_GI) { + if (addattr_l(n, 1024, NETEM_LOSS_GI, + &gimodel, sizeof(gimodel)) < 0) + return -1; + } else if (loss_type == NETEM_LOSS_GE) { + if (addattr_l(n, 1024, NETEM_LOSS_GE, + &gemodel, sizeof(gemodel)) < 0) + return -1; + } else { + fprintf(stderr, "loss in the weeds!\n"); + return -1; + } + + addattr_nest_end(n, start); + } + + if (present[TCA_NETEM_RATE]) { + if (rate64 >= (1ULL << 32)) { + if (addattr_l(n, 1024, + TCA_NETEM_RATE64, &rate64, sizeof(rate64)) < 0) + return -1; + rate.rate = ~0U; + } else { + rate.rate = rate64; + } + if (addattr_l(n, 1024, TCA_NETEM_RATE, &rate, sizeof(rate)) < 0) + return -1; + } + + if (present[TCA_NETEM_PRNG_SEED] && + addattr_l(n, 1024, TCA_NETEM_PRNG_SEED, &seed, + sizeof(seed)) < 0) + return -1; + + + if (dist_data) { + if (addattr_l(n, MAX_DIST * sizeof(dist_data[0]), + TCA_NETEM_DELAY_DIST, + dist_data, dist_size * sizeof(dist_data[0])) < 0) + return -1; + free(dist_data); + } + + if (slot_dist_data) { + if (addattr_l(n, MAX_DIST * sizeof(slot_dist_data[0]), + TCA_NETEM_SLOT_DIST, + slot_dist_data, slot_dist_size * sizeof(slot_dist_data[0])) < 0) + return -1; + free(slot_dist_data); + } + tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; + return 0; +} + +static int netem_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + const struct tc_netem_corr *cor = NULL; + const struct tc_netem_reorder *reorder = NULL; + const struct tc_netem_corrupt *corrupt = NULL; + const struct tc_netem_gimodel *gimodel = NULL; + const struct tc_netem_gemodel *gemodel = NULL; + int *ecn = NULL; + struct tc_netem_qopt qopt; + const struct tc_netem_rate *rate = NULL; + const struct tc_netem_slot *slot = NULL; + bool seed_present = false; + __u64 seed = 0; + int len; + __u64 rate64 = 0; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + len = RTA_PAYLOAD(opt) - sizeof(qopt); + if (len < 0) { + fprintf(stderr, "options size error\n"); + return -1; + } + memcpy(&qopt, RTA_DATA(opt), sizeof(qopt)); + + if (len > 0) { + struct rtattr *tb[TCA_NETEM_MAX+1]; + + parse_rtattr(tb, TCA_NETEM_MAX, RTA_DATA(opt) + sizeof(qopt), + len); + + if (tb[TCA_NETEM_CORR]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_CORR]) < sizeof(*cor)) + return -1; + cor = RTA_DATA(tb[TCA_NETEM_CORR]); + } + if (tb[TCA_NETEM_REORDER]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_REORDER]) < sizeof(*reorder)) + return -1; + reorder = RTA_DATA(tb[TCA_NETEM_REORDER]); + } + if (tb[TCA_NETEM_CORRUPT]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_CORRUPT]) < sizeof(*corrupt)) + return -1; + corrupt = RTA_DATA(tb[TCA_NETEM_CORRUPT]); + } + if (tb[TCA_NETEM_LOSS]) { + struct rtattr *lb[NETEM_LOSS_MAX + 1]; + + parse_rtattr_nested(lb, NETEM_LOSS_MAX, tb[TCA_NETEM_LOSS]); + if (lb[NETEM_LOSS_GI]) + gimodel = RTA_DATA(lb[NETEM_LOSS_GI]); + if (lb[NETEM_LOSS_GE]) + gemodel = RTA_DATA(lb[NETEM_LOSS_GE]); + } + if (tb[TCA_NETEM_RATE]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_RATE]) < sizeof(*rate)) + return -1; + rate = RTA_DATA(tb[TCA_NETEM_RATE]); + } + if (tb[TCA_NETEM_ECN]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_ECN]) < sizeof(*ecn)) + return -1; + ecn = RTA_DATA(tb[TCA_NETEM_ECN]); + } + if (tb[TCA_NETEM_RATE64]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_RATE64]) < sizeof(rate64)) + return -1; + rate64 = rta_getattr_u64(tb[TCA_NETEM_RATE64]); + } + if (tb[TCA_NETEM_SLOT]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_SLOT]) < sizeof(*slot)) + return -1; + slot = RTA_DATA(tb[TCA_NETEM_SLOT]); + } + if (tb[TCA_NETEM_PRNG_SEED]) { + if (RTA_PAYLOAD(tb[TCA_NETEM_PRNG_SEED]) < sizeof(seed)) + return -1; + seed_present = true; + seed = rta_getattr_u64(tb[TCA_NETEM_PRNG_SEED]); + } + } + + print_uint(PRINT_ANY, "limit", "limit %d", qopt.limit); + + if (qopt.latency) { + open_json_object("delay"); + if (!is_json_context()) { + print_string(PRINT_FP, NULL, " delay %s", + sprint_ticks(qopt.latency, b1)); + + if (qopt.jitter) + print_string(PRINT_FP, NULL, " %s", + sprint_ticks(qopt.jitter, b1)); + } else { + print_float(PRINT_JSON, "delay", NULL, + tc_core_tick2time(qopt.latency) / + 1000000.); + print_float(PRINT_JSON, "jitter", NULL, + tc_core_tick2time(qopt.jitter) / + 1000000.); + } + print_corr(qopt.jitter && cor && cor->delay_corr, + cor ? cor->delay_corr : 0); + close_json_object(); + } + + if (qopt.loss) { + open_json_object("loss-random"); + PRINT_PERCENT("loss", qopt.loss); + print_corr(cor && cor->loss_corr, cor ? cor->loss_corr : 0); + close_json_object(); + } + + if (gimodel) { + open_json_object("loss-state"); + __PRINT_PERCENT("p13", " loss state p13", gimodel->p13); + PRINT_PERCENT("p31", gimodel->p31); + PRINT_PERCENT("p32", gimodel->p32); + PRINT_PERCENT("p23", gimodel->p23); + PRINT_PERCENT("p14", gimodel->p14); + close_json_object(); + } + + if (gemodel) { + open_json_object("loss-gemodel"); + __PRINT_PERCENT("p", " loss gemodel p", gemodel->p); + PRINT_PERCENT("r", gemodel->r); + PRINT_PERCENT("1-h", UINT32_MAX - gemodel->h); + PRINT_PERCENT("1-k", gemodel->k1); + close_json_object(); + } + + if (qopt.duplicate) { + open_json_object("duplicate"); + PRINT_PERCENT("duplicate", qopt.duplicate); + print_corr(cor && cor->dup_corr, cor ? cor->dup_corr : 0); + close_json_object(); + } + + if (reorder && reorder->probability) { + open_json_object("reorder"); + PRINT_PERCENT("reorder", reorder->probability); + print_corr(reorder->correlation, reorder->correlation); + close_json_object(); + } + + if (corrupt && corrupt->probability) { + open_json_object("corrupt"); + PRINT_PERCENT("corrupt", corrupt->probability); + print_corr(corrupt->correlation, corrupt->correlation); + close_json_object(); + } + + if (rate && rate->rate) { + open_json_object("rate"); + rate64 = rate64 ? : rate->rate; + tc_print_rate(PRINT_ANY, "rate", " rate %s", rate64); + PRINT_INT_OPT("packetoverhead", rate->packet_overhead); + + print_uint(PRINT_JSON, "cellsize", NULL, rate->cell_size); + if (rate->cell_size) + print_uint(PRINT_FP, NULL, " cellsize %u", rate->cell_size); + PRINT_INT_OPT("celloverhead", rate->cell_overhead); + close_json_object(); + } + + if (slot) { + open_json_object("slot"); + if (slot->dist_jitter > 0) { + __PRINT_TIME64("distribution", " slot distribution", + slot->dist_delay); + __PRINT_TIME64("jitter", "", slot->dist_jitter); + } else { + __PRINT_TIME64("min-delay", " slot", slot->min_delay); + __PRINT_TIME64("max-delay", "", slot->max_delay); + } + PRINT_INT_OPT("packets", slot->max_packets); + PRINT_INT_OPT("bytes", slot->max_bytes); + close_json_object(); + } + + if (seed_present) + print_u64(PRINT_ANY, "seed", " seed %llu", seed); + + print_bool(PRINT_JSON, "ecn", NULL, ecn); + if (ecn) + print_bool(PRINT_FP, NULL, " ecn ", ecn); + + print_luint(PRINT_JSON, "gap", NULL, qopt.gap); + if (qopt.gap) + print_luint(PRINT_FP, NULL, " gap %lu", qopt.gap); + + return 0; +} + +struct qdisc_util netem_qdisc_util = { + .id = "netem", + .parse_qopt = netem_parse_opt, + .print_qopt = netem_print_opt, +}; diff --git a/tc/q_pie.c b/tc/q_pie.c new file mode 100644 index 0000000..177cdca --- /dev/null +++ b/tc/q_pie.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 Cisco Systems, Inc, 2013. + * + * Author: Vijay Subramanian <vijaynsu@cisco.com> + * Author: Mythili Prabhu <mysuryan@cisco.com> + */ + +#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" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... pie [ limit PACKETS ] [ target TIME ]\n" + " [ tupdate TIME ] [ alpha ALPHA ] [ beta BETA ]\n" + " [ bytemode | nobytemode ] [ ecn | noecn ]\n" + " [ dq_rate_estimator | no_dq_rate_estimator ]\n"); +} + +#define ALPHA_MAX 32 +#define BETA_MAX 32 + +static int pie_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + unsigned int limit = 0; + unsigned int target = 0; + unsigned int tupdate = 0; + unsigned int alpha = 0; + unsigned int beta = 0; + int ecn = -1; + int bytemode = -1; + int dq_rate_estimator = -1; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_unsigned(&limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "target") == 0) { + NEXT_ARG(); + if (get_time(&target, *argv)) { + fprintf(stderr, "Illegal \"target\"\n"); + return -1; + } + } else if (strcmp(*argv, "tupdate") == 0) { + NEXT_ARG(); + if (get_time(&tupdate, *argv)) { + fprintf(stderr, "Illegal \"tupdate\"\n"); + return -1; + } + } else if (strcmp(*argv, "alpha") == 0) { + NEXT_ARG(); + if (get_unsigned(&alpha, *argv, 0) || + (alpha > ALPHA_MAX)) { + fprintf(stderr, "Illegal \"alpha\"\n"); + return -1; + } + } else if (strcmp(*argv, "beta") == 0) { + NEXT_ARG(); + if (get_unsigned(&beta, *argv, 0) || + (beta > BETA_MAX)) { + fprintf(stderr, "Illegal \"beta\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + ecn = 1; + } else if (strcmp(*argv, "noecn") == 0) { + ecn = 0; + } else if (strcmp(*argv, "bytemode") == 0) { + bytemode = 1; + } else if (strcmp(*argv, "nobytemode") == 0) { + bytemode = 0; + } else if (strcmp(*argv, "dq_rate_estimator") == 0) { + dq_rate_estimator = 1; + } else if (strcmp(*argv, "no_dq_rate_estimator") == 0) { + dq_rate_estimator = 0; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; + argv++; + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + if (limit) + addattr_l(n, 1024, TCA_PIE_LIMIT, &limit, sizeof(limit)); + if (tupdate) + addattr_l(n, 1024, TCA_PIE_TUPDATE, &tupdate, sizeof(tupdate)); + if (target) + addattr_l(n, 1024, TCA_PIE_TARGET, &target, sizeof(target)); + if (alpha) + addattr_l(n, 1024, TCA_PIE_ALPHA, &alpha, sizeof(alpha)); + if (beta) + addattr_l(n, 1024, TCA_PIE_BETA, &beta, sizeof(beta)); + if (ecn != -1) + addattr_l(n, 1024, TCA_PIE_ECN, &ecn, sizeof(ecn)); + if (bytemode != -1) + addattr_l(n, 1024, TCA_PIE_BYTEMODE, &bytemode, + sizeof(bytemode)); + if (dq_rate_estimator != -1) + addattr_l(n, 1024, TCA_PIE_DQ_RATE_ESTIMATOR, + &dq_rate_estimator, sizeof(dq_rate_estimator)); + + addattr_nest_end(n, tail); + return 0; +} + +static int pie_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_PIE_MAX + 1]; + unsigned int limit; + unsigned int tupdate; + unsigned int target; + unsigned int alpha; + unsigned int beta; + unsigned int ecn; + unsigned int bytemode; + unsigned int dq_rate_estimator; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_PIE_MAX, opt); + + if (tb[TCA_PIE_LIMIT] && + RTA_PAYLOAD(tb[TCA_PIE_LIMIT]) >= sizeof(__u32)) { + limit = rta_getattr_u32(tb[TCA_PIE_LIMIT]); + print_uint(PRINT_ANY, "limit", "limit %up ", limit); + } + if (tb[TCA_PIE_TARGET] && + RTA_PAYLOAD(tb[TCA_PIE_TARGET]) >= sizeof(__u32)) { + target = rta_getattr_u32(tb[TCA_PIE_TARGET]); + print_uint(PRINT_JSON, "target", NULL, target); + print_string(PRINT_FP, NULL, "target %s ", + sprint_time(target, b1)); + } + if (tb[TCA_PIE_TUPDATE] && + RTA_PAYLOAD(tb[TCA_PIE_TUPDATE]) >= sizeof(__u32)) { + tupdate = rta_getattr_u32(tb[TCA_PIE_TUPDATE]); + print_uint(PRINT_JSON, "tupdate", NULL, tupdate); + print_string(PRINT_FP, NULL, "tupdate %s ", + sprint_time(tupdate, b1)); + } + if (tb[TCA_PIE_ALPHA] && + RTA_PAYLOAD(tb[TCA_PIE_ALPHA]) >= sizeof(__u32)) { + alpha = rta_getattr_u32(tb[TCA_PIE_ALPHA]); + print_uint(PRINT_ANY, "alpha", "alpha %u ", alpha); + } + if (tb[TCA_PIE_BETA] && + RTA_PAYLOAD(tb[TCA_PIE_BETA]) >= sizeof(__u32)) { + beta = rta_getattr_u32(tb[TCA_PIE_BETA]); + print_uint(PRINT_ANY, "beta", "beta %u ", beta); + } + + if (tb[TCA_PIE_ECN] && RTA_PAYLOAD(tb[TCA_PIE_ECN]) >= sizeof(__u32)) { + ecn = rta_getattr_u32(tb[TCA_PIE_ECN]); + if (ecn) + print_bool(PRINT_ANY, "ecn", "ecn ", true); + } + + if (tb[TCA_PIE_BYTEMODE] && + RTA_PAYLOAD(tb[TCA_PIE_BYTEMODE]) >= sizeof(__u32)) { + bytemode = rta_getattr_u32(tb[TCA_PIE_BYTEMODE]); + if (bytemode) + print_bool(PRINT_ANY, "bytemode", "bytemode ", true); + } + + if (tb[TCA_PIE_DQ_RATE_ESTIMATOR] && + RTA_PAYLOAD(tb[TCA_PIE_DQ_RATE_ESTIMATOR]) >= sizeof(__u32)) { + dq_rate_estimator = + rta_getattr_u32(tb[TCA_PIE_DQ_RATE_ESTIMATOR]); + if (dq_rate_estimator) + print_bool(PRINT_ANY, "dq_rate_estimator", + "dq_rate_estimator ", true); + } + + return 0; +} + +static int pie_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_pie_xstats *st; + + SPRINT_BUF(b1); + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + + /* prob is returned as a fracion of maximum integer value */ + print_float(PRINT_ANY, "prob", " prob %lg", + (double)st->prob / (double)UINT64_MAX); + print_uint(PRINT_JSON, "delay", NULL, st->delay); + print_string(PRINT_FP, NULL, " delay %s", sprint_time(st->delay, b1)); + + if (st->dq_rate_estimating) + print_uint(PRINT_ANY, "avg_dq_rate", " avg_dq_rate %u", + st->avg_dq_rate); + + print_nl(); + print_uint(PRINT_ANY, "pkts_in", " pkts_in %u", st->packets_in); + print_uint(PRINT_ANY, "overlimit", " overlimit %u", st->overlimit); + print_uint(PRINT_ANY, "dropped", " dropped %u", st->dropped); + print_uint(PRINT_ANY, "maxq", " maxq %u", st->maxq); + print_uint(PRINT_ANY, "ecn_mark", " ecn_mark %u", st->ecn_mark); + + return 0; + +} + +struct qdisc_util pie_qdisc_util = { + .id = "pie", + .parse_qopt = pie_parse_opt, + .print_qopt = pie_print_opt, + .print_xstats = pie_print_xstats, +}; diff --git a/tc/q_plug.c b/tc/q_plug.c new file mode 100644 index 0000000..8adf9b9 --- /dev/null +++ b/tc/q_plug.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * q_plug.c plug scheduler + * + * Copyright (C) 2019 Paolo Abeni <pabeni@redhat.com> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... plug [block | release | release_indefinite | limit NUMBER]\n"); +} + +static int plug_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_plug_qopt opt = {}; + int ok = 0; + + while (argc > 0) { + if (strcmp(*argv, "block") == 0) { + opt.action = TCQ_PLUG_BUFFER; + ok++; + } else if (strcmp(*argv, "release") == 0) { + opt.action = TCQ_PLUG_RELEASE_ONE; + ok++; + } else if (strcmp(*argv, "release_indefinite") == 0) { + opt.action = TCQ_PLUG_RELEASE_INDEFINITE; + ok++; + } else if (strcmp(*argv, "limit") == 0) { + opt.action = TCQ_PLUG_LIMIT; + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + fprintf(stderr, "Illegal value for \"limit\": \"%s\"\n", *argv); + return -1; + } + ok++; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "%s: unknown parameter \"%s\"\n", qu->id, *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (ok) + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + return 0; +} + +static int plug_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + /* dummy implementation as sch_plug does not implement a dump op */ + return 0; +} + + +struct qdisc_util plug_qdisc_util = { + .id = "plug", + .parse_qopt = plug_parse_opt, + .print_qopt = plug_print_opt, +}; diff --git a/tc/q_prio.c b/tc/q_prio.c new file mode 100644 index 0000000..a3781ff --- /dev/null +++ b/tc/q_prio.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_prio.c PRIO. + * + * 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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... prio bands NUMBER priomap P1 P2...[multiqueue]\n"); +} + +static int prio_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + int pmap_mode = 0; + int idx = 0; + struct tc_prio_qopt opt = {3, { 1, 2, 2, 2, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 } }; + struct rtattr *nest; + unsigned char mq = 0; + + while (argc > 0) { + if (strcmp(*argv, "bands") == 0) { + if (pmap_mode) + explain(); + NEXT_ARG(); + if (get_integer(&opt.bands, *argv, 10)) { + fprintf(stderr, "Illegal \"bands\"\n"); + return -1; + } + } else if (strcmp(*argv, "priomap") == 0) { + if (pmap_mode) { + fprintf(stderr, "Error: duplicate priomap\n"); + return -1; + } + pmap_mode = 1; + } else if (strcmp(*argv, "multiqueue") == 0) { + mq = 1; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + unsigned int band; + + if (!pmap_mode) { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + if (get_unsigned(&band, *argv, 10)) { + fprintf(stderr, "Illegal \"priomap\" element\n"); + return -1; + } + if (band >= opt.bands) { + fprintf(stderr, "\"priomap\" element is out of bands\n"); + return -1; + } + if (idx > TC_PRIO_MAX) { + fprintf(stderr, "\"priomap\" index > TC_PRIO_MAX=%u\n", TC_PRIO_MAX); + return -1; + } + opt.priomap[idx++] = band; + } + argc--; argv++; + } + +/* + if (pmap_mode) { + for (; idx < TC_PRIO_MAX; idx++) + opt.priomap[idx] = opt.priomap[TC_PRIO_BESTEFFORT]; + } +*/ + nest = addattr_nest_compat(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + if (mq) + addattr_l(n, 1024, TCA_PRIO_MQ, NULL, 0); + addattr_nest_compat_end(n, nest); + return 0; +} + +int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + int i; + struct tc_prio_qopt *qopt; + struct rtattr *tb[TCA_PRIO_MAX+1]; + + if (opt == NULL) + return 0; + + if (parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt, + sizeof(*qopt))) + return -1; + if (qopt == NULL) + return -1; /* missing data from kernel */ + + print_uint(PRINT_ANY, "bands", "bands %u ", qopt->bands); + open_json_array(PRINT_ANY, "priomap"); + for (i = 0; i <= TC_PRIO_MAX; i++) + print_uint(PRINT_ANY, NULL, " %d", qopt->priomap[i]); + close_json_array(PRINT_ANY, ""); + + if (tb[TCA_PRIO_MQ]) + print_string(PRINT_FP, NULL, " multiqueue: %s ", + rta_getattr_u8(tb[TCA_PRIO_MQ]) ? "on" : "off"); + print_bool(PRINT_JSON, "multiqueue", NULL, + tb[TCA_PRIO_MQ] && rta_getattr_u8(tb[TCA_PRIO_MQ])); + + return 0; +} + +struct qdisc_util prio_qdisc_util = { + .id = "prio", + .parse_qopt = prio_parse_opt, + .print_qopt = prio_print_opt, +}; diff --git a/tc/q_qfq.c b/tc/q_qfq.c new file mode 100644 index 0000000..c9955cc --- /dev/null +++ b/tc/q_qfq.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_qfq.c QFQ. + * + * Authors: Stephen Hemminger <shemminger@vyatta.com> + * Fabio Checconi <fabio@gandalf.sssup.it> + * + */ +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... qfq\n"); +} + +static void explain1(const char *arg) +{ + fprintf(stderr, "Illegal \"%s\"\n", arg); +} + +static void explain_class(void) +{ + fprintf(stderr, "Usage: ... qfq weight NUMBER maxpkt BYTES\n"); +} + +static int qfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + if (argc > 0) { + if (matches(*argv, "help") != 0) + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + + return 0; +} + +static int qfq_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct rtattr *tail; + __u32 tmp; + + tail = addattr_nest(n, 4096, TCA_OPTIONS); + + while (argc > 0) { + if (matches(*argv, "weight") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 10)) { + explain1("weight"); return -1; + } + addattr32(n, 4096, TCA_QFQ_WEIGHT, tmp); + } else if (matches(*argv, "maxpkt") == 0) { + NEXT_ARG(); + if (get_u32(&tmp, *argv, 10)) { + explain1("maxpkt"); return -1; + } + addattr32(n, 4096, TCA_QFQ_LMAX, tmp); + } else if (strcmp(*argv, "help") == 0) { + explain_class(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain_class(); + return -1; + } + argc--; argv++; + } + + addattr_nest_end(n, tail); + + return 0; +} + +static int qfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_QFQ_MAX + 1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_QFQ_MAX, opt); + + if (tb[TCA_QFQ_WEIGHT]) { + fprintf(f, "weight %u ", + rta_getattr_u32(tb[TCA_QFQ_WEIGHT])); + } + + if (tb[TCA_QFQ_LMAX]) { + fprintf(f, "maxpkt %u ", + rta_getattr_u32(tb[TCA_QFQ_LMAX])); + } + + return 0; +} + +struct qdisc_util qfq_qdisc_util = { + .id = "qfq", + .parse_qopt = qfq_parse_opt, + .print_qopt = qfq_print_opt, + .parse_copt = qfq_parse_class_opt, + .print_copt = qfq_print_opt, +}; diff --git a/tc/q_red.c b/tc/q_red.c new file mode 100644 index 0000000..f760253 --- /dev/null +++ b/tc/q_red.c @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_red.c RED. + * + * 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_qevent.h" + +#include "tc_red.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... red limit BYTES [min BYTES] [max BYTES] avpkt BYTES [burst PACKETS]\n" + " [adaptive] [probability PROBABILITY] [bandwidth KBPS]\n" + " [ecn] [harddrop] [nodrop]\n" + " [qevent early_drop block IDX] [qevent mark block IDX]\n"); +} + +#define RED_SUPPORTED_FLAGS (TC_RED_HISTORIC_FLAGS | TC_RED_NODROP) + +static struct qevent_plain qe_early_drop = {}; +static struct qevent_plain qe_mark = {}; +static struct qevent_util qevents[] = { + QEVENT("early_drop", plain, &qe_early_drop, TCA_RED_EARLY_DROP_BLOCK), + QEVENT("mark", plain, &qe_mark, TCA_RED_MARK_BLOCK), + {}, +}; + +static int red_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct nla_bitfield32 flags_bf = { + .selector = RED_SUPPORTED_FLAGS, + }; + struct tc_red_qopt opt = {}; + unsigned int burst = 0; + unsigned int avpkt = 0; + double probability = 0.02; + unsigned int rate = 0; + int parm; + __u8 sbuf[256]; + __u32 max_P; + struct rtattr *tail; + + qevents_init(qevents); + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "min") == 0) { + NEXT_ARG(); + if (get_size(&opt.qth_min, *argv)) { + fprintf(stderr, "Illegal \"min\"\n"); + return -1; + } + } else if (strcmp(*argv, "max") == 0) { + NEXT_ARG(); + if (get_size(&opt.qth_max, *argv)) { + fprintf(stderr, "Illegal \"max\"\n"); + return -1; + } + } else if (strcmp(*argv, "burst") == 0) { + NEXT_ARG(); + if (get_unsigned(&burst, *argv, 0)) { + fprintf(stderr, "Illegal \"burst\"\n"); + return -1; + } + } else if (strcmp(*argv, "avpkt") == 0) { + NEXT_ARG(); + if (get_size(&avpkt, *argv)) { + fprintf(stderr, "Illegal \"avpkt\"\n"); + return -1; + } + } else if (strcmp(*argv, "probability") == 0) { + NEXT_ARG(); + if (sscanf(*argv, "%lg", &probability) != 1) { + fprintf(stderr, "Illegal \"probability\"\n"); + return -1; + } + } else if (strcmp(*argv, "bandwidth") == 0) { + NEXT_ARG(); + if (strchr(*argv, '%')) { + if (get_percent_rate(&rate, *argv, dev)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + } else if (get_rate(&rate, *argv)) { + fprintf(stderr, "Illegal \"bandwidth\"\n"); + return -1; + } + } else if (strcmp(*argv, "ecn") == 0) { + flags_bf.value |= TC_RED_ECN; + } else if (strcmp(*argv, "harddrop") == 0) { + flags_bf.value |= TC_RED_HARDDROP; + } else if (strcmp(*argv, "nodrop") == 0) { + flags_bf.value |= TC_RED_NODROP; + } else if (strcmp(*argv, "adaptative") == 0) { + flags_bf.value |= TC_RED_ADAPTATIVE; + } else if (strcmp(*argv, "adaptive") == 0) { + flags_bf.value |= TC_RED_ADAPTATIVE; + } else if (matches(*argv, "qevent") == 0) { + NEXT_ARG(); + if (qevent_parse(qevents, &argc, &argv)) + return -1; + continue; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (!opt.limit || !avpkt) { + fprintf(stderr, "RED: Required parameter (limit, avpkt) is missing\n"); + return -1; + } + /* Compute default min/max thresholds based on + * Sally Floyd's recommendations: + * http://www.icir.org/floyd/REDparameters.txt + */ + if (!opt.qth_max) + opt.qth_max = opt.qth_min ? opt.qth_min * 3 : opt.limit / 4; + if (!opt.qth_min) + opt.qth_min = opt.qth_max / 3; + if (!burst) + burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt); + if (!rate) { + get_rate(&rate, "10Mbit"); + fprintf(stderr, "RED: set bandwidth to 10Mbit\n"); + } + if ((parm = tc_red_eval_ewma(opt.qth_min, burst, avpkt)) < 0) { + fprintf(stderr, "RED: failed to calculate EWMA constant.\n"); + return -1; + } + if (parm >= 10) + fprintf(stderr, "RED: WARNING. Burst %u seems to be too large.\n", burst); + opt.Wlog = parm; + if ((parm = tc_red_eval_P(opt.qth_min, opt.qth_max, probability)) < 0) { + fprintf(stderr, "RED: failed to calculate probability.\n"); + return -1; + } + opt.Plog = parm; + if ((parm = tc_red_eval_idle_damping(opt.Wlog, avpkt, rate, sbuf)) < 0) { + fprintf(stderr, "RED: failed to calculate idle damping table.\n"); + return -1; + } + opt.Scell_log = parm; + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 1024, TCA_RED_PARMS, &opt, sizeof(opt)); + addattr_l(n, 1024, TCA_RED_STAB, sbuf, 256); + max_P = probability * pow(2, 32); + addattr_l(n, 1024, TCA_RED_MAX_P, &max_P, sizeof(max_P)); + addattr_l(n, 1024, TCA_RED_FLAGS, &flags_bf, sizeof(flags_bf)); + if (qevents_dump(qevents, n)) + return -1; + addattr_nest_end(n, tail); + return 0; +} + +static int red_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_RED_MAX + 1]; + struct nla_bitfield32 *flags_bf; + struct tc_red_qopt *qopt; + __u32 max_P = 0; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_RED_MAX, opt); + + if (tb[TCA_RED_PARMS] == NULL) + return -1; + qopt = RTA_DATA(tb[TCA_RED_PARMS]); + if (RTA_PAYLOAD(tb[TCA_RED_PARMS]) < sizeof(*qopt)) + return -1; + + if (tb[TCA_RED_MAX_P] && + RTA_PAYLOAD(tb[TCA_RED_MAX_P]) >= sizeof(__u32)) + max_P = rta_getattr_u32(tb[TCA_RED_MAX_P]); + + if (tb[TCA_RED_FLAGS] && + RTA_PAYLOAD(tb[TCA_RED_FLAGS]) >= sizeof(*flags_bf)) { + flags_bf = RTA_DATA(tb[TCA_RED_FLAGS]); + qopt->flags = flags_bf->value; + } + + print_size(PRINT_ANY, "limit", "limit %s ", qopt->limit); + print_size(PRINT_ANY, "min", "min %s ", qopt->qth_min); + print_size(PRINT_ANY, "max", "max %s ", qopt->qth_max); + + tc_red_print_flags(qopt->flags); + + if (show_details) { + print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt->Wlog); + if (max_P) + print_float(PRINT_ANY, "probability", + "probability %lg ", max_P / pow(2, 32)); + else + print_uint(PRINT_ANY, "Plog", "Plog %u ", qopt->Plog); + print_uint(PRINT_ANY, "Scell_log", "Scell_log %u", + qopt->Scell_log); + } + + qevents_init(qevents); + if (qevents_read(qevents, tb)) + return -1; + qevents_print(qevents, f); + return 0; +} + +static int red_print_xstats(struct qdisc_util *qu, FILE *f, struct rtattr *xstats) +{ +#ifdef TC_RED_ECN + struct tc_red_xstats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + print_uint(PRINT_ANY, "marked", " marked %u ", st->marked); + print_uint(PRINT_ANY, "early", "early %u ", st->early); + print_uint(PRINT_ANY, "pdrop", "pdrop %u ", st->pdrop); + print_uint(PRINT_ANY, "other", "other %u ", st->other); +#endif + return 0; +} + +static int red_has_block(struct qdisc_util *qu, struct rtattr *opt, __u32 block_idx, bool *p_has) +{ + struct rtattr *tb[TCA_RED_MAX + 1]; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_RED_MAX, opt); + + qevents_init(qevents); + if (qevents_read(qevents, tb)) + return -1; + + *p_has = qevents_have_block(qevents, block_idx); + return 0; +} + +struct qdisc_util red_qdisc_util = { + .id = "red", + .parse_qopt = red_parse_opt, + .print_qopt = red_print_opt, + .print_xstats = red_print_xstats, + .has_block = red_has_block, +}; diff --git a/tc/q_sfb.c b/tc/q_sfb.c new file mode 100644 index 0000000..a2eef28 --- /dev/null +++ b/tc/q_sfb.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_sfb.c Stochastic Fair Blue. + * + * Authors: Juliusz Chroboczek <jch@pps.jussieu.fr> + */ + +#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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... sfb [ rehash SECS ] [ db SECS ]\n" + " [ limit PACKETS ] [ max PACKETS ] [ target PACKETS ]\n" + " [ increment FLOAT ] [ decrement FLOAT ]\n" + " [ penalty_rate PPS ] [ penalty_burst PACKETS ]\n"); +} + +static int get_prob(__u32 *val, const char *arg) +{ + double d; + char *ptr; + + if (!arg || !*arg) + return -1; + d = strtod(arg, &ptr); + if (!ptr || ptr == arg || d < 0.0 || d > 1.0) + return -1; + *val = (__u32)(d * SFB_MAX_PROB + 0.5); + return 0; +} + +static int sfb_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_sfb_qopt opt = { + .rehash_interval = 600*1000, + .warmup_time = 60*1000, + .penalty_rate = 10, + .penalty_burst = 20, + .increment = (SFB_MAX_PROB + 1000) / 2000, + .decrement = (SFB_MAX_PROB + 10000) / 20000, + }; + struct rtattr *tail; + + while (argc > 0) { + if (strcmp(*argv, "rehash") == 0) { + NEXT_ARG(); + if (get_u32(&opt.rehash_interval, *argv, 0)) { + fprintf(stderr, "Illegal \"rehash\"\n"); + return -1; + } + } else if (strcmp(*argv, "db") == 0) { + NEXT_ARG(); + if (get_u32(&opt.warmup_time, *argv, 0)) { + fprintf(stderr, "Illegal \"db\"\n"); + return -1; + } + } else if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_u32(&opt.limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + } else if (strcmp(*argv, "max") == 0) { + NEXT_ARG(); + if (get_u32(&opt.max, *argv, 0)) { + fprintf(stderr, "Illegal \"max\"\n"); + return -1; + } + } else if (strcmp(*argv, "target") == 0) { + NEXT_ARG(); + if (get_u32(&opt.bin_size, *argv, 0)) { + fprintf(stderr, "Illegal \"target\"\n"); + return -1; + } + } else if (strcmp(*argv, "increment") == 0) { + NEXT_ARG(); + if (get_prob(&opt.increment, *argv)) { + fprintf(stderr, "Illegal \"increment\"\n"); + return -1; + } + } else if (strcmp(*argv, "decrement") == 0) { + NEXT_ARG(); + if (get_prob(&opt.decrement, *argv)) { + fprintf(stderr, "Illegal \"decrement\"\n"); + return -1; + } + } else if (strcmp(*argv, "penalty_rate") == 0) { + NEXT_ARG(); + if (get_u32(&opt.penalty_rate, *argv, 0)) { + fprintf(stderr, "Illegal \"penalty_rate\"\n"); + return -1; + } + } else if (strcmp(*argv, "penalty_burst") == 0) { + NEXT_ARG(); + if (get_u32(&opt.penalty_burst, *argv, 0)) { + fprintf(stderr, "Illegal \"penalty_burst\"\n"); + return -1; + } + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (opt.max == 0) { + if (opt.bin_size >= 1) + opt.max = (opt.bin_size * 5 + 1) / 4; + else + opt.max = 25; + } + if (opt.bin_size == 0) + opt.bin_size = (opt.max * 4 + 3) / 5; + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 1024, TCA_SFB_PARMS, &opt, sizeof(opt)); + addattr_nest_end(n, tail); + return 0; +} + +static int sfb_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[__TCA_SFB_MAX]; + struct tc_sfb_qopt *qopt; + + SPRINT_BUF(b1); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_SFB_MAX, opt); + if (tb[TCA_SFB_PARMS] == NULL) + return -1; + qopt = RTA_DATA(tb[TCA_SFB_PARMS]); + if (RTA_PAYLOAD(tb[TCA_SFB_PARMS]) < sizeof(*qopt)) + return -1; + + print_uint(PRINT_JSON, "rehash", NULL, qopt->rehash_interval * 1000); + print_string(PRINT_FP, NULL, "rehash %s ", + sprint_time(qopt->rehash_interval * 1000, b1)); + + print_uint(PRINT_JSON, "db", NULL, qopt->warmup_time * 1000); + print_string(PRINT_FP, NULL, "db %s ", + sprint_time(qopt->warmup_time * 1000, b1)); + + print_uint(PRINT_ANY, "limit", "limit %up ", qopt->limit); + print_uint(PRINT_ANY, "max", "max %up ", qopt->max); + print_uint(PRINT_ANY, "target", "target %up ", qopt->bin_size); + + print_float(PRINT_ANY, "increment", "increment %lg ", + (double)qopt->increment / SFB_MAX_PROB); + print_float(PRINT_ANY, "decrement", "decrement %lg ", + (double)qopt->decrement / SFB_MAX_PROB); + + print_uint(PRINT_ANY, "penalty_rate", "penalty_rate %upps ", + qopt->penalty_rate); + print_uint(PRINT_ANY, "penalty_burst", "penalty_burst %up ", + qopt->penalty_burst); + + return 0; +} + +static int sfb_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_sfb_xstats *st; + + if (xstats == NULL) + return 0; + + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + + st = RTA_DATA(xstats); + + print_uint(PRINT_ANY, "earlydrop", " earlydrop %u", st->earlydrop); + print_uint(PRINT_ANY, "penaltydrop", " penaltydrop %u", + st->penaltydrop); + print_uint(PRINT_ANY, "bucketdrop", " bucketdrop %u", st->bucketdrop); + print_uint(PRINT_ANY, "queuedrop", " queuedrop %u", st->queuedrop); + print_uint(PRINT_ANY, "childdrop", " childdrop %u", st->childdrop); + print_uint(PRINT_ANY, "marked", " marked %u", st->marked); + print_nl(); + print_uint(PRINT_ANY, "maxqlen", " maxqlen %u", st->maxqlen); + + print_float(PRINT_ANY, "maxprob", " maxprob %lg", + (double)st->maxprob / SFB_MAX_PROB); + print_float(PRINT_ANY, "avgprob", " avgprob %lg", + (double)st->avgprob / SFB_MAX_PROB); + + return 0; +} + +struct qdisc_util sfb_qdisc_util = { + .id = "sfb", + .parse_qopt = sfb_parse_opt, + .print_qopt = sfb_print_opt, + .print_xstats = sfb_print_xstats, +}; diff --git a/tc/q_sfq.c b/tc/q_sfq.c new file mode 100644 index 0000000..17bf8f6 --- /dev/null +++ b/tc/q_sfq.c @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_sfq.c SFQ. + * + * 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_red.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... sfq [ limit NUMBER ] [ perturb SECS ] [ quantum BYTES ]\n" + " [ divisor NUMBER ] [ flows NUMBER] [ depth NUMBER ]\n" + " [ headdrop ]\n" + " [ redflowlimit BYTES ] [ min BYTES ] [ max BYTES ]\n" + " [ avpkt BYTES ] [ burst PACKETS ] [ probability P ]\n" + " [ ecn ] [ harddrop ]\n"); +} + +static int sfq_parse_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev) +{ + int ok = 0, red = 0; + struct tc_sfq_qopt_v1 opt = {}; + unsigned int burst = 0; + int wlog; + unsigned int avpkt = 1000; + double probability = 0.02; + + while (argc > 0) { + if (strcmp(*argv, "quantum") == 0) { + NEXT_ARG(); + if (get_size(&opt.v0.quantum, *argv)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "perturb") == 0) { + NEXT_ARG(); + if (get_integer(&opt.v0.perturb_period, *argv, 0)) { + fprintf(stderr, "Illegal \"perturb\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_u32(&opt.v0.limit, *argv, 0)) { + fprintf(stderr, "Illegal \"limit\"\n"); + return -1; + } + if (opt.v0.limit < 2) { + fprintf(stderr, "Illegal \"limit\", must be > 1\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "divisor") == 0) { + NEXT_ARG(); + if (get_u32(&opt.v0.divisor, *argv, 0)) { + fprintf(stderr, "Illegal \"divisor\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "flows") == 0) { + NEXT_ARG(); + if (get_u32(&opt.v0.flows, *argv, 0)) { + fprintf(stderr, "Illegal \"flows\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "depth") == 0) { + NEXT_ARG(); + if (get_u32(&opt.depth, *argv, 0)) { + fprintf(stderr, "Illegal \"flows\"\n"); + return -1; + } + ok++; + } else if (strcmp(*argv, "headdrop") == 0) { + opt.headdrop = 1; + ok++; + } else if (strcmp(*argv, "redflowlimit") == 0) { + NEXT_ARG(); + if (get_u32(&opt.limit, *argv, 0)) { + fprintf(stderr, "Illegal \"redflowlimit\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "min") == 0) { + NEXT_ARG(); + if (get_u32(&opt.qth_min, *argv, 0)) { + fprintf(stderr, "Illegal \"min\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "max") == 0) { + NEXT_ARG(); + if (get_u32(&opt.qth_max, *argv, 0)) { + fprintf(stderr, "Illegal \"max\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "burst") == 0) { + NEXT_ARG(); + if (get_unsigned(&burst, *argv, 0)) { + fprintf(stderr, "Illegal \"burst\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "avpkt") == 0) { + NEXT_ARG(); + if (get_size(&avpkt, *argv)) { + fprintf(stderr, "Illegal \"avpkt\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "probability") == 0) { + NEXT_ARG(); + if (sscanf(*argv, "%lg", &probability) != 1) { + fprintf(stderr, "Illegal \"probability\"\n"); + return -1; + } + red++; + } else if (strcmp(*argv, "ecn") == 0) { + opt.flags |= TC_RED_ECN; + red++; + } else if (strcmp(*argv, "harddrop") == 0) { + opt.flags |= TC_RED_HARDDROP; + red++; + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "What is \"%s\"?\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + if (red) { + if (!opt.limit) { + fprintf(stderr, "Required parameter (redflowlimit) is missing\n"); + return -1; + } + /* Compute default min/max thresholds based on + Sally Floyd's recommendations: + http://www.icir.org/floyd/REDparameters.txt + */ + if (!opt.qth_max) + opt.qth_max = opt.limit / 4; + if (!opt.qth_min) + opt.qth_min = opt.qth_max / 3; + if (!burst) + burst = (2 * opt.qth_min + opt.qth_max) / (3 * avpkt); + + if (opt.qth_max > opt.limit) { + fprintf(stderr, "\"max\" is larger than \"limit\"\n"); + return -1; + } + + if (opt.qth_min >= opt.qth_max) { + fprintf(stderr, "\"min\" is not smaller than \"max\"\n"); + return -1; + } + + wlog = tc_red_eval_ewma(opt.qth_min, burst, avpkt); + if (wlog < 0) { + fprintf(stderr, "SFQ: failed to calculate EWMA constant.\n"); + return -1; + } + if (wlog >= 10) + fprintf(stderr, "SFQ: WARNING. Burst %u seems to be too large.\n", burst); + opt.Wlog = wlog; + + wlog = tc_red_eval_P(opt.qth_min, opt.qth_max, probability); + if (wlog < 0) { + fprintf(stderr, "SFQ: failed to calculate probability.\n"); + return -1; + } + opt.Plog = wlog; + opt.max_P = probability * pow(2, 32); + } + + if (ok || red) + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + return 0; +} + +static int sfq_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_sfq_qopt *qopt; + struct tc_sfq_qopt_v1 *qopt_ext = NULL; + + if (opt == NULL) + return 0; + + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + return -1; + if (RTA_PAYLOAD(opt) >= sizeof(*qopt_ext)) + qopt_ext = RTA_DATA(opt); + qopt = RTA_DATA(opt); + + print_uint(PRINT_ANY, "limit", "limit %up ", qopt->limit); + print_size(PRINT_ANY, "quantum", "quantum %s ", qopt->quantum); + + if (qopt_ext && qopt_ext->depth) + print_uint(PRINT_ANY, "depth", "depth %u ", qopt_ext->depth); + if (qopt_ext && qopt_ext->headdrop) + print_bool(PRINT_ANY, "headdrop", "headdrop ", true); + if (show_details) + print_uint(PRINT_ANY, "flows", "flows %u ", qopt->flows); + + print_uint(PRINT_ANY, "divisor", "divisor %u ", qopt->divisor); + + if (qopt->perturb_period) + print_int(PRINT_ANY, "perturb", "perturb %dsec ", + qopt->perturb_period); + if (qopt_ext && qopt_ext->qth_min) { + print_uint(PRINT_ANY, "ewma", "ewma %u ", qopt_ext->Wlog); + print_size(PRINT_ANY, "min", "min %s ", qopt_ext->qth_min); + print_size(PRINT_ANY, "max", "max %s ", qopt_ext->qth_max); + print_float(PRINT_ANY, "probability", "probability %lg ", + qopt_ext->max_P / pow(2, 32)); + tc_red_print_flags(qopt_ext->flags); + if (show_stats) { + print_nl(); + print_uint(PRINT_ANY, "prob_mark", " prob_mark %u", + qopt_ext->stats.prob_mark); + print_uint(PRINT_ANY, "prob_mark_head", + " prob_mark_head %u", + qopt_ext->stats.prob_mark_head); + print_uint(PRINT_ANY, "prob_drop", " prob_drop %u", + qopt_ext->stats.prob_drop); + print_nl(); + print_uint(PRINT_ANY, "forced_mark", + " forced_mark %u", + qopt_ext->stats.forced_mark); + print_uint(PRINT_ANY, "forced_mark_head", + " forced_mark_head %u", + qopt_ext->stats.forced_mark_head); + print_uint(PRINT_ANY, "forced_drop", " forced_drop %u", + qopt_ext->stats.forced_drop); + } + } + return 0; +} + +static int sfq_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct tc_sfq_xstats *st; + + if (xstats == NULL) + return 0; + if (RTA_PAYLOAD(xstats) < sizeof(*st)) + return -1; + st = RTA_DATA(xstats); + + print_int(PRINT_ANY, "allot", " allot %d", st->allot); + + return 0; +} + +struct qdisc_util sfq_qdisc_util = { + .id = "sfq", + .parse_qopt = sfq_parse_opt, + .print_qopt = sfq_print_opt, + .print_xstats = sfq_print_xstats, +}; diff --git a/tc/q_skbprio.c b/tc/q_skbprio.c new file mode 100644 index 0000000..b0ba180 --- /dev/null +++ b/tc/q_skbprio.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_skbprio.c SKB PRIORITY QUEUE. + * + * Authors: Nishanth Devarajan, <ndev2021@gmail.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, "Usage: ... <skbprio> [ limit NUMBER ]\n"); +} + +static int skbprio_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + int ok = 0; + struct tc_skbprio_qopt opt = {}; + + while (argc > 0) { + if (strcmp(*argv, "limit") == 0) { + NEXT_ARG(); + if (get_size(&opt.limit, *argv)) { + fprintf(stderr, + "%s: Illegal \"limit\" value:\"%s\"\n", + qu->id, *argv); + return -1; + } + ok++; + } + else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, + "%s: unknown parameter \"%s\"\n", + qu->id, *argv); + explain(); + return -1; + } + argc--; argv++; + } + + if (ok) + addattr_l(n, 1024, TCA_OPTIONS, &opt, sizeof(opt)); + return 0; +} + +static int skbprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct tc_skbprio_qopt *qopt; + + if (opt == NULL) + return 0; + + if (RTA_PAYLOAD(opt) < sizeof(*qopt)) + return -1; + qopt = RTA_DATA(opt); + + print_uint(PRINT_ANY, "limit", "limit %u ", qopt->limit); + return 0; +} + +struct qdisc_util skbprio_qdisc_util = { + .id = "skbprio", + .parse_qopt = skbprio_parse_opt, + .print_qopt = skbprio_print_opt, +}; diff --git a/tc/q_taprio.c b/tc/q_taprio.c new file mode 100644 index 0000000..c47fe24 --- /dev/null +++ b/tc/q_taprio.c @@ -0,0 +1,654 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_taprio.c Time Aware Priority Scheduler + * + * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> + * Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <fcntl.h> +#include <inttypes.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_util.h" +#include "list.h" + +struct sched_entry { + struct list_head list; + uint32_t index; + uint32_t interval; + uint32_t gatemask; + uint8_t cmd; +}; + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... taprio clockid CLOCKID\n" + " [num_tc NUMBER] [map P0 P1 ...]\n" + " [queues COUNT@OFFSET COUNT@OFFSET COUNT@OFFSET ...]\n" + " [ [sched-entry index cmd gate-mask interval] ... ]\n" + " [base-time time] [txtime-delay delay]\n" + " [fp FP0 FP1 FP2 ...]\n" + "\n" + "CLOCKID must be a valid SYS-V id (i.e. CLOCK_TAI)\n"); +} + +static void explain_clockid(const char *val) +{ + fprintf(stderr, "taprio: illegal value for \"clockid\": \"%s\".\n", val); + fprintf(stderr, "It must be a valid SYS-V id (i.e. CLOCK_TAI)\n"); +} + +static const char *entry_cmd_to_str(__u8 cmd) +{ + switch (cmd) { + case TC_TAPRIO_CMD_SET_GATES: + return "S"; + default: + return "Invalid"; + } +} + +static int str_to_entry_cmd(const char *str) +{ + if (strcmp(str, "S") == 0) + return TC_TAPRIO_CMD_SET_GATES; + + return -1; +} + +static int add_sched_list(struct list_head *sched_entries, struct nlmsghdr *n) +{ + struct sched_entry *e; + + list_for_each_entry(e, sched_entries, list) { + struct rtattr *a; + + a = addattr_nest(n, 1024, TCA_TAPRIO_SCHED_ENTRY); + + addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_CMD, &e->cmd, sizeof(e->cmd)); + addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_GATE_MASK, &e->gatemask, sizeof(e->gatemask)); + addattr_l(n, 1024, TCA_TAPRIO_SCHED_ENTRY_INTERVAL, &e->interval, sizeof(e->interval)); + + addattr_nest_end(n, a); + } + + return 0; +} + +static void explain_sched_entry(void) +{ + fprintf(stderr, "Usage: ... taprio ... sched-entry <cmd> <gate mask> <interval>\n"); +} + +static struct sched_entry *create_entry(uint32_t gatemask, uint32_t interval, uint8_t cmd) +{ + struct sched_entry *e; + + e = calloc(1, sizeof(*e)); + if (!e) + return NULL; + + e->gatemask = gatemask; + e->interval = interval; + e->cmd = cmd; + + return e; +} + +static void add_tc_entries(struct nlmsghdr *n, __u32 max_sdu[TC_QOPT_MAX_QUEUE], + int num_max_sdu_entries, __u32 fp[TC_QOPT_MAX_QUEUE], + int num_fp_entries) +{ + struct rtattr *l; + int num_tc; + __u32 tc; + + num_tc = max(num_max_sdu_entries, num_fp_entries); + + for (tc = 0; tc < num_tc; tc++) { + l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_TC_ENTRY | NLA_F_NESTED); + + addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_INDEX, &tc, sizeof(tc)); + + if (tc < num_max_sdu_entries) { + addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_MAX_SDU, + &max_sdu[tc], sizeof(max_sdu[tc])); + } + + if (tc < num_fp_entries) { + addattr_l(n, 1024, TCA_TAPRIO_TC_ENTRY_FP, &fp[tc], + sizeof(fp[tc])); + } + + addattr_nest_end(n, l); + } +} + +static int taprio_parse_opt(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev) +{ + __u32 max_sdu[TC_QOPT_MAX_QUEUE] = { }; + __u32 fp[TC_QOPT_MAX_QUEUE] = { }; + __s32 clockid = CLOCKID_INVALID; + struct tc_mqprio_qopt opt = { }; + __s64 cycle_time_extension = 0; + struct list_head sched_entries; + bool have_tc_entries = false; + int num_max_sdu_entries = 0; + struct rtattr *tail, *l; + int num_fp_entries = 0; + __u32 taprio_flags = 0; + __u32 txtime_delay = 0; + __s64 cycle_time = 0; + __s64 base_time = 0; + int err, idx; + + INIT_LIST_HEAD(&sched_entries); + + while (argc > 0) { + idx = 0; + if (strcmp(*argv, "num_tc") == 0) { + NEXT_ARG(); + if (get_u8(&opt.num_tc, *argv, 10)) { + fprintf(stderr, "Illegal \"num_tc\"\n"); + return -1; + } + } else if (strcmp(*argv, "map") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (get_u8(&opt.prio_tc_map[idx], *argv, 10)) { + PREV_ARG(); + break; + } + idx++; + } + for ( ; idx < TC_QOPT_MAX_QUEUE; idx++) + opt.prio_tc_map[idx] = 0; + } else if (strcmp(*argv, "queues") == 0) { + char *tmp, *tok; + + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + + tmp = strdup(*argv); + if (!tmp) + break; + + tok = strtok(tmp, "@"); + if (get_u16(&opt.count[idx], tok, 10)) { + free(tmp); + PREV_ARG(); + break; + } + tok = strtok(NULL, "@"); + if (get_u16(&opt.offset[idx], tok, 10)) { + free(tmp); + PREV_ARG(); + break; + } + free(tmp); + idx++; + } + } else if (strcmp(*argv, "fp") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (strcmp(*argv, "E") == 0) { + fp[idx] = TC_FP_EXPRESS; + } else if (strcmp(*argv, "P") == 0) { + fp[idx] = TC_FP_PREEMPTIBLE; + } else { + PREV_ARG(); + break; + } + num_fp_entries++; + idx++; + } + have_tc_entries = true; + } else if (strcmp(*argv, "max-sdu") == 0) { + while (idx < TC_QOPT_MAX_QUEUE && NEXT_ARG_OK()) { + NEXT_ARG(); + if (get_u32(&max_sdu[idx], *argv, 10)) { + PREV_ARG(); + break; + } + num_max_sdu_entries++; + idx++; + } + have_tc_entries = true; + } else if (strcmp(*argv, "sched-entry") == 0) { + uint32_t mask, interval; + struct sched_entry *e; + uint8_t cmd; + + NEXT_ARG(); + err = str_to_entry_cmd(*argv); + if (err < 0) { + explain_sched_entry(); + return -1; + } + cmd = err; + + NEXT_ARG(); + if (get_u32(&mask, *argv, 16)) { + explain_sched_entry(); + return -1; + } + + NEXT_ARG(); + if (get_u32(&interval, *argv, 0)) { + explain_sched_entry(); + return -1; + } + + e = create_entry(mask, interval, cmd); + if (!e) { + fprintf(stderr, "taprio: not enough memory for new schedule entry\n"); + return -1; + } + + list_add_tail(&e->list, &sched_entries); + + } else if (strcmp(*argv, "base-time") == 0) { + NEXT_ARG(); + if (get_s64(&base_time, *argv, 10)) { + PREV_ARG(); + break; + } + } else if (strcmp(*argv, "cycle-time") == 0) { + NEXT_ARG(); + if (cycle_time) { + fprintf(stderr, "taprio: duplicate \"cycle-time\" specification\n"); + return -1; + } + + if (get_s64(&cycle_time, *argv, 10)) { + PREV_ARG(); + break; + } + + } else if (strcmp(*argv, "cycle-time-extension") == 0) { + NEXT_ARG(); + if (cycle_time_extension) { + fprintf(stderr, "taprio: duplicate \"cycle-time-extension\" specification\n"); + return -1; + } + + if (get_s64(&cycle_time_extension, *argv, 10)) { + PREV_ARG(); + break; + } + } else if (strcmp(*argv, "clockid") == 0) { + NEXT_ARG(); + if (clockid != CLOCKID_INVALID) { + fprintf(stderr, "taprio: duplicate \"clockid\" specification\n"); + return -1; + } + if (get_clockid(&clockid, *argv)) { + explain_clockid(*argv); + return -1; + } + } else if (strcmp(*argv, "flags") == 0) { + NEXT_ARG(); + if (taprio_flags) { + fprintf(stderr, "taprio: duplicate \"flags\" specification\n"); + return -1; + } + if (get_u32(&taprio_flags, *argv, 0)) { + PREV_ARG(); + return -1; + } + + } else if (strcmp(*argv, "txtime-delay") == 0) { + NEXT_ARG(); + if (txtime_delay != 0) { + fprintf(stderr, "taprio: duplicate \"txtime-delay\" specification\n"); + return -1; + } + if (get_u32(&txtime_delay, *argv, 0)) { + PREV_ARG(); + return -1; + } + + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "Unknown argument\n"); + return -1; + } + argc--; argv++; + } + + tail = NLMSG_TAIL(n); + addattr_l(n, 1024, TCA_OPTIONS, NULL, 0); + + if (clockid != CLOCKID_INVALID) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CLOCKID, &clockid, sizeof(clockid)); + + if (taprio_flags) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_FLAGS, &taprio_flags, sizeof(taprio_flags)); + + if (opt.num_tc > 0) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_PRIOMAP, &opt, sizeof(opt)); + + if (txtime_delay) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_TXTIME_DELAY, &txtime_delay, sizeof(txtime_delay)); + + if (base_time) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_BASE_TIME, &base_time, sizeof(base_time)); + + if (cycle_time) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME, + &cycle_time, sizeof(cycle_time)); + + if (cycle_time_extension) + addattr_l(n, 1024, TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION, + &cycle_time_extension, sizeof(cycle_time_extension)); + + if (have_tc_entries) + add_tc_entries(n, max_sdu, num_max_sdu_entries, fp, num_fp_entries); + + l = addattr_nest(n, 1024, TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST | NLA_F_NESTED); + + err = add_sched_list(&sched_entries, n); + if (err < 0) { + fprintf(stderr, "Could not add schedule to netlink message\n"); + return -1; + } + + addattr_nest_end(n, l); + + tail->rta_len = (void *) NLMSG_TAIL(n) - (void *) tail; + + return 0; +} + +static void print_sched_list(FILE *f, struct rtattr *list) +{ + struct rtattr *item, *nla; + int rem; + + rem = RTA_PAYLOAD(list); + + open_json_array(PRINT_JSON, "schedule"); + + print_nl(); + + for (item = RTA_DATA(list); RTA_OK(item, rem); item = RTA_NEXT(item, rem)) { + struct rtattr *tb[TCA_TAPRIO_SCHED_ENTRY_MAX + 1]; + + parse_rtattr_nested(tb, TCA_TAPRIO_SCHED_ENTRY_MAX, item); + + open_json_object(NULL); + + nla = tb[TCA_TAPRIO_SCHED_ENTRY_INDEX]; + if (nla) { + __u32 index = rta_getattr_u32(nla); + + print_uint(PRINT_ANY, "index", "\tindex %u", index); + } + + nla = tb[TCA_TAPRIO_SCHED_ENTRY_CMD]; + if (nla) { + __u8 command = rta_getattr_u8(nla); + + print_string(PRINT_ANY, "cmd", " cmd %s", + entry_cmd_to_str(command)); + } + + nla = tb[TCA_TAPRIO_SCHED_ENTRY_GATE_MASK]; + if (nla) { + __u32 gatemask = rta_getattr_u32(nla); + + print_0xhex(PRINT_ANY, "gatemask", " gatemask %#llx", + gatemask); + } + + nla = tb[TCA_TAPRIO_SCHED_ENTRY_INTERVAL]; + if (nla) { + __u32 interval = rta_getattr_u32(nla); + + print_uint(PRINT_ANY, "interval", " interval %u", + interval); + } + + close_json_object(); + + print_nl(); + } + + close_json_array(PRINT_ANY, ""); +} + +static int print_schedule(FILE *f, struct rtattr **tb) +{ + struct rtattr *nla; + + nla = tb[TCA_TAPRIO_ATTR_SCHED_BASE_TIME]; + if (nla) { + int64_t base_time = rta_getattr_s64(nla); + + print_lluint(PRINT_ANY, "base_time", "\tbase-time %lld", + base_time); + } + + nla = tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME]; + if (nla) { + int64_t cycle_time = rta_getattr_s64(nla); + + print_lluint(PRINT_ANY, "cycle_time", " cycle-time %lld", + cycle_time); + } + + nla = tb[TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION]; + if (nla) { + int64_t cycle_time_extension = rta_getattr_s64(nla); + + print_lluint(PRINT_ANY, "cycle_time_extension", + " cycle-time-extension %lld", + cycle_time_extension); + } + + nla = tb[TCA_TAPRIO_ATTR_SCHED_ENTRY_LIST]; + if (nla) + print_sched_list(f, nla); + + return 0; +} + +static void dump_tc_entry(struct rtattr *item, + __u32 max_sdu[TC_QOPT_MAX_QUEUE], + __u32 fp[TC_QOPT_MAX_QUEUE], + int *max_tc_max_sdu, int *max_tc_fp) +{ + struct rtattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1]; + __u32 tc, val = 0; + + parse_rtattr_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, item); + + if (!tb[TCA_TAPRIO_TC_ENTRY_INDEX]) { + fprintf(stderr, "Missing tc entry index\n"); + return; + } + + tc = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_INDEX]); + /* Prevent array out of bounds access */ + if (tc >= TC_QOPT_MAX_QUEUE) { + fprintf(stderr, "Unexpected tc entry index %d\n", tc); + return; + } + + if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]) { + val = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]); + max_sdu[tc] = val; + if (*max_tc_max_sdu < (int)tc) + *max_tc_max_sdu = tc; + } + + if (tb[TCA_TAPRIO_TC_ENTRY_FP]) { + val = rta_getattr_u32(tb[TCA_TAPRIO_TC_ENTRY_FP]); + fp[tc] = val; + + if (*max_tc_fp < (int)tc) + *max_tc_fp = tc; + } +} + +static void dump_tc_entries(FILE *f, struct rtattr *opt) +{ + __u32 max_sdu[TC_QOPT_MAX_QUEUE] = {}; + __u32 fp[TC_QOPT_MAX_QUEUE] = {}; + int max_tc_max_sdu = -1; + int max_tc_fp = -1; + struct rtattr *i; + int tc, rem; + + rem = RTA_PAYLOAD(opt); + + for (i = RTA_DATA(opt); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + if (i->rta_type != (TCA_TAPRIO_ATTR_TC_ENTRY | NLA_F_NESTED)) + continue; + + dump_tc_entry(i, max_sdu, fp, &max_tc_max_sdu, &max_tc_fp); + } + + if (max_tc_max_sdu >= 0) { + open_json_array(PRINT_ANY, "max-sdu"); + for (tc = 0; tc <= max_tc_max_sdu; tc++) + print_uint(PRINT_ANY, NULL, " %u", max_sdu[tc]); + close_json_array(PRINT_ANY, ""); + + print_nl(); + } + + if (max_tc_fp >= 0) { + open_json_array(PRINT_ANY, "fp"); + for (tc = 0; tc <= max_tc_fp; tc++) { + print_string(PRINT_ANY, NULL, " %s", + fp[tc] == TC_FP_PREEMPTIBLE ? "P" : + fp[tc] == TC_FP_EXPRESS ? "E" : + "?"); + } + close_json_array(PRINT_ANY, ""); + + print_nl(); + } +} + +static int taprio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_TAPRIO_ATTR_MAX + 1]; + struct tc_mqprio_qopt *qopt = 0; + int i; + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_TAPRIO_ATTR_MAX, opt); + + if (tb[TCA_TAPRIO_ATTR_PRIOMAP] == NULL) + return -1; + + qopt = RTA_DATA(tb[TCA_TAPRIO_ATTR_PRIOMAP]); + + print_uint(PRINT_ANY, "tc", "tc %u ", qopt->num_tc); + + open_json_array(PRINT_ANY, "map"); + for (i = 0; i <= TC_PRIO_MAX; i++) + print_uint(PRINT_ANY, NULL, " %u", qopt->prio_tc_map[i]); + close_json_array(PRINT_ANY, ""); + + print_nl(); + + open_json_array(PRINT_ANY, "queues"); + for (i = 0; i < qopt->num_tc; i++) { + open_json_object(NULL); + print_uint(PRINT_ANY, "offset", " offset %u", qopt->offset[i]); + print_uint(PRINT_ANY, "count", " count %u", qopt->count[i]); + close_json_object(); + } + close_json_array(PRINT_ANY, ""); + + print_nl(); + + if (tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]) { + __s32 clockid; + + clockid = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_SCHED_CLOCKID]); + print_string(PRINT_ANY, "clockid", "clockid %s", + get_clock_name(clockid)); + } + + if (tb[TCA_TAPRIO_ATTR_FLAGS]) { + __u32 flags; + + flags = rta_getattr_u32(tb[TCA_TAPRIO_ATTR_FLAGS]); + print_0xhex(PRINT_ANY, "flags", " flags %#x", flags); + } + + if (tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]) { + __u32 txtime_delay; + + txtime_delay = rta_getattr_s32(tb[TCA_TAPRIO_ATTR_TXTIME_DELAY]); + print_uint(PRINT_ANY, "txtime_delay", " txtime delay %d", txtime_delay); + } + + print_schedule(f, tb); + + if (tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]) { + struct rtattr *t[TCA_TAPRIO_ATTR_MAX + 1]; + + parse_rtattr_nested(t, TCA_TAPRIO_ATTR_MAX, + tb[TCA_TAPRIO_ATTR_ADMIN_SCHED]); + + open_json_object("admin"); + + print_schedule(f, t); + + close_json_object(); + } + + dump_tc_entries(f, opt); + + return 0; +} + +static int taprio_print_xstats(struct qdisc_util *qu, FILE *f, + struct rtattr *xstats) +{ + struct rtattr *tb[TCA_TAPRIO_OFFLOAD_STATS_MAX + 1], *nla; + + if (!xstats) + return 0; + + parse_rtattr_nested(tb, TCA_TAPRIO_OFFLOAD_STATS_MAX, xstats); + + nla = tb[TCA_TAPRIO_OFFLOAD_STATS_WINDOW_DROPS]; + if (nla) + print_lluint(PRINT_ANY, "window_drops", " window_drops %llu", + rta_getattr_u64(nla)); + + nla = tb[TCA_TAPRIO_OFFLOAD_STATS_TX_OVERRUNS]; + if (nla) + print_lluint(PRINT_ANY, "tx_overruns", " tx_overruns %llu", + rta_getattr_u64(nla)); + + return 0; +} + +struct qdisc_util taprio_qdisc_util = { + .id = "taprio", + .parse_qopt = taprio_parse_opt, + .print_qopt = taprio_print_opt, + .print_xstats = taprio_print_xstats, +}; diff --git a/tc/q_tbf.c b/tc/q_tbf.c new file mode 100644 index 0000000..f621756 --- /dev/null +++ b/tc/q_tbf.c @@ -0,0 +1,343 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * q_tbf.c TBF. + * + * 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 "utils.h" +#include "tc_util.h" + +static void explain(void) +{ + fprintf(stderr, + "Usage: ... tbf limit BYTES burst BYTES[/BYTES] rate KBPS [ mtu BYTES[/BYTES] ]\n" + " [ peakrate KBPS ] [ latency TIME ] " + "[ overhead BYTES ] [ linklayer TYPE ]\n"); +} + +static void explain1(const char *arg, const char *val) +{ + fprintf(stderr, "tbf: illegal value for \"%s\": \"%s\"\n", arg, val); +} + + +static int tbf_parse_opt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + struct tc_tbf_qopt opt = {}; + __u32 rtab[256]; + __u32 ptab[256]; + unsigned buffer = 0, mtu = 0, mpu = 0, latency = 0; + int Rcell_log = -1, Pcell_log = -1; + unsigned short overhead = 0; + unsigned int linklayer = LINKLAYER_ETHERNET; /* Assume ethernet */ + struct rtattr *tail; + __u64 rate64 = 0, prate64 = 0; + + while (argc > 0) { + if (matches(*argv, "limit") == 0) { + NEXT_ARG(); + if (opt.limit) { + fprintf(stderr, "tbf: duplicate \"limit\" specification\n"); + return -1; + } + if (latency) { + fprintf(stderr, "tbf: specifying both \"latency\" and \"limit\" is not allowed\n"); + return -1; + } + if (get_size(&opt.limit, *argv)) { + explain1("limit", *argv); + return -1; + } + } else if (matches(*argv, "latency") == 0) { + NEXT_ARG(); + if (latency) { + fprintf(stderr, "tbf: duplicate \"latency\" specification\n"); + return -1; + } + if (opt.limit) { + fprintf(stderr, "tbf: specifying both \"limit\" and \"/latency\" is not allowed\n"); + return -1; + } + if (get_time(&latency, *argv)) { + explain1("latency", *argv); + return -1; + } + } else if (matches(*argv, "burst") == 0 || + strcmp(*argv, "buffer") == 0 || + strcmp(*argv, "maxburst") == 0) { + const char *parm_name = *argv; + + NEXT_ARG(); + if (buffer) { + fprintf(stderr, "tbf: duplicate \"buffer/burst/maxburst\" specification\n"); + return -1; + } + if (get_size_and_cell(&buffer, &Rcell_log, *argv) < 0) { + explain1(parm_name, *argv); + return -1; + } + } else if (strcmp(*argv, "mtu") == 0 || + strcmp(*argv, "minburst") == 0) { + const char *parm_name = *argv; + + NEXT_ARG(); + if (mtu) { + fprintf(stderr, "tbf: duplicate \"mtu/minburst\" specification\n"); + return -1; + } + if (get_size_and_cell(&mtu, &Pcell_log, *argv) < 0) { + explain1(parm_name, *argv); + return -1; + } + } else if (strcmp(*argv, "mpu") == 0) { + NEXT_ARG(); + if (mpu) { + fprintf(stderr, "tbf: duplicate \"mpu\" specification\n"); + return -1; + } + if (get_size(&mpu, *argv)) { + explain1("mpu", *argv); + return -1; + } + } else if (strcmp(*argv, "rate") == 0) { + NEXT_ARG(); + if (rate64) { + fprintf(stderr, "tbf: duplicate \"rate\" specification\n"); + return -1; + } + if (strchr(*argv, '%')) { + if (get_percent_rate64(&rate64, *argv, dev)) { + explain1("rate", *argv); + return -1; + } + } else if (get_rate64(&rate64, *argv)) { + explain1("rate", *argv); + return -1; + } + } else if (matches(*argv, "peakrate") == 0) { + NEXT_ARG(); + if (prate64) { + fprintf(stderr, "tbf: duplicate \"peakrate\" specification\n"); + return -1; + } + if (strchr(*argv, '%')) { + if (get_percent_rate64(&prate64, *argv, dev)) { + explain1("peakrate", *argv); + return -1; + } + } else if (get_rate64(&prate64, *argv)) { + explain1("peakrate", *argv); + return -1; + } + } else if (matches(*argv, "overhead") == 0) { + NEXT_ARG(); + if (overhead) { + fprintf(stderr, "tbf: duplicate \"overhead\" specification\n"); + return -1; + } + if (get_u16(&overhead, *argv, 10)) { + explain1("overhead", *argv); return -1; + } + } else if (matches(*argv, "linklayer") == 0) { + NEXT_ARG(); + if (get_linklayer(&linklayer, *argv)) { + explain1("linklayer", *argv); return -1; + } + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "tbf: unknown parameter \"%s\"\n", *argv); + explain(); + return -1; + } + argc--; argv++; + } + + int verdict = 0; + + /* Be nice to the user: try to emit all error messages in + * one go rather than reveal one more problem when a + * previous one has been fixed. + */ + if (rate64 == 0) { + fprintf(stderr, "tbf: the \"rate\" parameter is mandatory.\n"); + verdict = -1; + } + if (!buffer) { + fprintf(stderr, "tbf: the \"burst\" parameter is mandatory.\n"); + verdict = -1; + } + if (prate64) { + if (!mtu) { + fprintf(stderr, "tbf: when \"peakrate\" is specified, \"mtu\" must also be specified.\n"); + verdict = -1; + } + } + + if (opt.limit == 0 && latency == 0) { + fprintf(stderr, "tbf: either \"limit\" or \"latency\" is required.\n"); + verdict = -1; + } + + if (verdict != 0) { + explain(); + return verdict; + } + + opt.rate.rate = (rate64 >= (1ULL << 32)) ? ~0U : rate64; + opt.peakrate.rate = (prate64 >= (1ULL << 32)) ? ~0U : prate64; + + if (opt.limit == 0) { + double lim = rate64*(double)latency/TIME_UNITS_PER_SEC + buffer; + + if (prate64) { + double lim2 = prate64*(double)latency/TIME_UNITS_PER_SEC + mtu; + + if (lim2 < lim) + lim = lim2; + } + opt.limit = lim; + } + + opt.rate.mpu = mpu; + opt.rate.overhead = overhead; + if (tc_calc_rtable(&opt.rate, rtab, Rcell_log, mtu, linklayer) < 0) { + fprintf(stderr, "tbf: failed to calculate rate table.\n"); + return -1; + } + opt.buffer = tc_calc_xmittime(opt.rate.rate, buffer); + + if (opt.peakrate.rate) { + opt.peakrate.mpu = mpu; + opt.peakrate.overhead = overhead; + if (tc_calc_rtable(&opt.peakrate, ptab, Pcell_log, mtu, linklayer) < 0) { + fprintf(stderr, "tbf: failed to calculate peak rate table.\n"); + return -1; + } + opt.mtu = tc_calc_xmittime(opt.peakrate.rate, mtu); + } + + tail = addattr_nest(n, 1024, TCA_OPTIONS); + addattr_l(n, 2024, TCA_TBF_PARMS, &opt, sizeof(opt)); + addattr_l(n, 2124, TCA_TBF_BURST, &buffer, sizeof(buffer)); + if (rate64 >= (1ULL << 32)) + addattr_l(n, 2124, TCA_TBF_RATE64, &rate64, sizeof(rate64)); + addattr_l(n, 3024, TCA_TBF_RTAB, rtab, 1024); + if (opt.peakrate.rate) { + if (prate64 >= (1ULL << 32)) + addattr_l(n, 3124, TCA_TBF_PRATE64, &prate64, sizeof(prate64)); + addattr_l(n, 3224, TCA_TBF_PBURST, &mtu, sizeof(mtu)); + addattr_l(n, 4096, TCA_TBF_PTAB, ptab, 1024); + } + addattr_nest_end(n, tail); + return 0; +} + +static int tbf_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt) +{ + struct rtattr *tb[TCA_TBF_MAX+1]; + struct tc_tbf_qopt *qopt; + unsigned int linklayer; + double buffer, mtu; + double latency, lat2; + __u64 rate64 = 0, prate64 = 0; + + SPRINT_BUF(b1); + SPRINT_BUF(b2); + SPRINT_BUF(b3); + + if (opt == NULL) + return 0; + + parse_rtattr_nested(tb, TCA_TBF_MAX, opt); + + if (tb[TCA_TBF_PARMS] == NULL) + return -1; + + qopt = RTA_DATA(tb[TCA_TBF_PARMS]); + if (RTA_PAYLOAD(tb[TCA_TBF_PARMS]) < sizeof(*qopt)) + return -1; + rate64 = qopt->rate.rate; + if (tb[TCA_TBF_RATE64] && + RTA_PAYLOAD(tb[TCA_TBF_RATE64]) >= sizeof(rate64)) + rate64 = rta_getattr_u64(tb[TCA_TBF_RATE64]); + tc_print_rate(PRINT_ANY, "rate", "rate %s ", rate64); + buffer = tc_calc_xmitsize(rate64, qopt->buffer); + if (show_details) { + sprintf(b1, "%s/%u", sprint_size(buffer, b2), + 1 << qopt->rate.cell_log); + print_string(PRINT_ANY, "burst", "burst %s ", b1); + print_size(PRINT_ANY, "mpu", "mpu %s ", qopt->rate.mpu); + } else { + print_size(PRINT_ANY, "burst", "burst %s ", buffer); + } + if (show_raw) + print_hex(PRINT_ANY, "burst_raw", "[%08x] ", qopt->buffer); + prate64 = qopt->peakrate.rate; + if (tb[TCA_TBF_PRATE64] && + RTA_PAYLOAD(tb[TCA_TBF_PRATE64]) >= sizeof(prate64)) + prate64 = rta_getattr_u64(tb[TCA_TBF_PRATE64]); + if (prate64) { + tc_print_rate(PRINT_FP, "peakrate", "peakrate %s ", prate64); + if (qopt->mtu || qopt->peakrate.mpu) { + mtu = tc_calc_xmitsize(prate64, qopt->mtu); + if (show_details) { + sprintf(b1, "%s/%u", sprint_size(mtu, b2), + 1 << qopt->peakrate.cell_log); + print_string(PRINT_ANY, "mtu", "mtu %s ", b1); + print_size(PRINT_ANY, "mpu", "mpu %s ", + qopt->peakrate.mpu); + } else { + print_size(PRINT_ANY, "minburst", + "minburst %s ", mtu); + } + if (show_raw) + print_hex(PRINT_ANY, "mtu_raw", "[%08x] ", + qopt->mtu); + } + } + + latency = TIME_UNITS_PER_SEC * (qopt->limit / (double)rate64) - + tc_core_tick2time(qopt->buffer); + if (prate64) { + lat2 = TIME_UNITS_PER_SEC * (qopt->limit / (double)prate64) - + tc_core_tick2time(qopt->mtu); + + if (lat2 > latency) + latency = lat2; + } + if (latency >= 0.0) { + print_u64(PRINT_JSON, "lat", NULL, latency); + print_string(PRINT_FP, NULL, "lat %s ", + sprint_time(latency, b1)); + } + if (show_raw || latency < 0.0) + print_size(PRINT_ANY, "limit", "limit %s ", qopt->limit); + if (qopt->rate.overhead) + print_int(PRINT_ANY, "overhead", "overhead %d ", + qopt->rate.overhead); + linklayer = (qopt->rate.linklayer & TC_LINKLAYER_MASK); + if (linklayer > TC_LINKLAYER_ETHERNET || show_details) + print_string(PRINT_ANY, "linklayer", "linklayer %s ", + sprint_linklayer(linklayer, b3)); + + return 0; +} + +struct qdisc_util tbf_qdisc_util = { + .id = "tbf", + .parse_qopt = tbf_parse_opt, + .print_qopt = tbf_print_opt, +}; diff --git a/tc/static-syms.c b/tc/static-syms.c new file mode 100644 index 0000000..47c4092 --- /dev/null +++ b/tc/static-syms.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file creates a dummy version of dynamic loading + * for environments where dynamic linking + * is not used or available. + */ + +#include <string.h> +#include "dlfcn.h" + +void *_dlsym(const char *sym) +{ +#include "static-syms.h" + return NULL; +} @@ -0,0 +1,357 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc.c "tc" utility frontend. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <errno.h> + +#include "version.h" +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" +#include "namespace.h" +#include "rt_names.h" +#include "bpf_util.h" + +int show_stats; +int show_details; +int show_raw; +int show_graph; +int timestamp; + +int batch_mode; +int use_iec; +int force; +bool use_names; +int json; +int oneline; +int brief; + +static char *conf_file; + +struct rtnl_handle rth; + +static void *BODY; /* cached handle dlopen(NULL) */ +static struct qdisc_util *qdisc_list; +static struct filter_util *filter_list; + +static int print_noqopt(struct qdisc_util *qu, FILE *f, + struct rtattr *opt) +{ + if (opt && RTA_PAYLOAD(opt)) + fprintf(f, "[Unknown qdisc, optlen=%u] ", + (unsigned int) RTA_PAYLOAD(opt)); + return 0; +} + +static int parse_noqopt(struct qdisc_util *qu, int argc, char **argv, + struct nlmsghdr *n, const char *dev) +{ + if (argc) { + fprintf(stderr, + "Unknown qdisc \"%s\", hence option \"%s\" is unparsable\n", + qu->id, *argv); + return -1; + } + return 0; +} + +static int print_nofopt(struct filter_util *qu, FILE *f, struct rtattr *opt, __u32 fhandle) +{ + if (opt && RTA_PAYLOAD(opt)) + fprintf(f, "fh %08x [Unknown filter, optlen=%u] ", + fhandle, (unsigned int) RTA_PAYLOAD(opt)); + else if (fhandle) + fprintf(f, "fh %08x ", fhandle); + return 0; +} + +static int parse_nofopt(struct filter_util *qu, char *fhandle, + int argc, char **argv, struct nlmsghdr *n) +{ + __u32 handle; + + if (argc) { + fprintf(stderr, + "Unknown filter \"%s\", hence option \"%s\" is unparsable\n", + qu->id, *argv); + return -1; + } + if (fhandle) { + struct tcmsg *t = NLMSG_DATA(n); + + if (get_u32(&handle, fhandle, 16)) { + fprintf(stderr, "Unparsable filter ID \"%s\"\n", fhandle); + return -1; + } + t->tcm_handle = handle; + } + return 0; +} + +struct qdisc_util *get_qdisc_kind(const char *str) +{ + void *dlh; + char buf[256]; + struct qdisc_util *q; + + for (q = qdisc_list; q; q = q->next) + if (strcmp(q->id, str) == 0) + return q; + + snprintf(buf, sizeof(buf), "%s/q_%s.so", get_tc_lib(), str); + dlh = dlopen(buf, RTLD_LAZY); + if (!dlh) { + /* look in current binary, only open once */ + dlh = BODY; + if (dlh == NULL) { + dlh = BODY = dlopen(NULL, RTLD_LAZY); + if (dlh == NULL) + goto noexist; + } + } + + snprintf(buf, sizeof(buf), "%s_qdisc_util", str); + q = dlsym(dlh, buf); + if (q == NULL) + goto noexist; + +reg: + q->next = qdisc_list; + qdisc_list = q; + return q; + +noexist: + q = calloc(1, sizeof(*q)); + if (q) { + q->id = strdup(str); + q->parse_qopt = parse_noqopt; + q->print_qopt = print_noqopt; + goto reg; + } + return q; +} + + +struct filter_util *get_filter_kind(const char *str) +{ + void *dlh; + char buf[256]; + struct filter_util *q; + + for (q = filter_list; q; q = q->next) + if (strcmp(q->id, str) == 0) + return q; + + snprintf(buf, sizeof(buf), "%s/f_%s.so", get_tc_lib(), str); + dlh = dlopen(buf, RTLD_LAZY); + if (dlh == NULL) { + dlh = BODY; + if (dlh == NULL) { + dlh = BODY = dlopen(NULL, RTLD_LAZY); + if (dlh == NULL) + goto noexist; + } + } + + snprintf(buf, sizeof(buf), "%s_filter_util", str); + q = dlsym(dlh, buf); + if (q == NULL) + goto noexist; + +reg: + q->next = filter_list; + filter_list = q; + return q; +noexist: + q = calloc(1, sizeof(*q)); + if (q) { + strncpy(q->id, str, 15); + q->parse_fopt = parse_nofopt; + q->print_fopt = print_nofopt; + goto reg; + } + return q; +} + +static void usage(void) +{ + fprintf(stderr, + "Usage: tc [ OPTIONS ] OBJECT { COMMAND | help }\n" + " tc [-force] -batch filename\n" + "where OBJECT := { qdisc | class | filter | chain |\n" + " action | monitor | exec }\n" + " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[aw] |\n" + " -o[neline] | -j[son] | -p[retty] | -c[olor]\n" + " -b[atch] [filename] | -n[etns] name | -N[umeric] |\n" + " -nm | -nam[es] | { -cf | -conf } path\n" + " -br[ief] }\n"); +} + +static int do_cmd(int argc, char **argv) +{ + if (matches(*argv, "qdisc") == 0) + return do_qdisc(argc-1, argv+1); + if (matches(*argv, "class") == 0) + return do_class(argc-1, argv+1); + if (matches(*argv, "filter") == 0) + return do_filter(argc-1, argv+1); + if (matches(*argv, "chain") == 0) + return do_chain(argc-1, argv+1); + if (matches(*argv, "actions") == 0) + return do_action(argc-1, argv+1); + if (matches(*argv, "monitor") == 0) + return do_tcmonitor(argc-1, argv+1); + if (matches(*argv, "exec") == 0) + return do_exec(argc-1, argv+1); + if (matches(*argv, "help") == 0) { + usage(); + return 0; + } + + fprintf(stderr, "Object \"%s\" is unknown, try \"tc help\".\n", + *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) +{ + int ret; + + batch_mode = 1; + tc_core_init(); + + if (rtnl_open(&rth, 0) < 0) { + fprintf(stderr, "Cannot open rtnetlink\n"); + return -1; + } + + ret = do_batch(name, force, tc_batch_cmd, NULL); + + rtnl_close(&rth); + return ret; +} + + +int main(int argc, char **argv) +{ + const char *libbpf_version; + char *batch_file = NULL; + int color = CONF_COLOR; + int ret; + + while (argc > 1) { + if (argv[1][0] != '-') + break; + if (matches(argv[1], "-stats") == 0 || + matches(argv[1], "-statistics") == 0) { + ++show_stats; + } else if (matches(argv[1], "-details") == 0) { + ++show_details; + } else if (matches(argv[1], "-raw") == 0) { + ++show_raw; + } else if (matches(argv[1], "-pretty") == 0) { + ++pretty; + } else if (matches(argv[1], "-graph") == 0) { + show_graph = 1; + } else if (matches(argv[1], "-Version") == 0) { + printf("tc utility, iproute2-%s", version); + libbpf_version = get_libbpf_version(); + if (libbpf_version) + printf(", libbpf %s", libbpf_version); + printf("\n"); + return 0; + } else if (matches(argv[1], "-iec") == 0) { + ++use_iec; + } else if (matches(argv[1], "-help") == 0) { + usage(); + return 0; + } else if (matches(argv[1], "-force") == 0) { + ++force; + } else if (matches(argv[1], "-batch") == 0) { + argc--; argv++; + if (argc <= 1) + usage(); + batch_file = argv[1]; + } else if (matches(argv[1], "-netns") == 0) { + NEXT_ARG(); + if (netns_switch(argv[1])) + return -1; + } else if (matches(argv[1], "-Numeric") == 0) { + ++numeric; + } else if (matches(argv[1], "-names") == 0 || + matches(argv[1], "-nm") == 0) { + use_names = true; + } else if (matches(argv[1], "-cf") == 0 || + matches(argv[1], "-conf") == 0) { + NEXT_ARG(); + conf_file = argv[1]; + } else if (matches_color(argv[1], &color)) { + } else if (matches(argv[1], "-timestamp") == 0) { + timestamp++; + } else if (matches(argv[1], "-tshort") == 0) { + ++timestamp; + ++timestamp_short; + } else if (matches(argv[1], "-json") == 0) { + ++json; + } else if (matches(argv[1], "-oneline") == 0) { + ++oneline; + } else if (matches(argv[1], "-brief") == 0) { + ++brief; + } else { + fprintf(stderr, + "Option \"%s\" is unknown, try \"tc -help\".\n", + argv[1]); + return -1; + } + argc--; argv++; + } + + _SL_ = oneline ? "\\" : "\n"; + + check_enable_color(color, json); + + if (batch_file) + return batch(batch_file); + + if (argc <= 1) { + usage(); + return 0; + } + + tc_core_init(); + if (rtnl_open(&rth, 0) < 0) { + fprintf(stderr, "Cannot open rtnetlink\n"); + exit(1); + } + + if (use_names && cls_names_init(conf_file)) { + ret = -1; + goto Exit; + } + + ret = do_cmd(argc-1, argv+1); +Exit: + rtnl_close(&rth); + + if (use_names) + cls_names_uninit(); + + return ret; +} diff --git a/tc/tc_class.c b/tc/tc_class.c new file mode 100644 index 0000000..f6a3d13 --- /dev/null +++ b/tc/tc_class.c @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_class.c "tc class". + * + * 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" + " [ classid CLASSID ] [ root | parent CLASSID ]\n" + " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" + "\n" + " tc class show [ dev STRING ] [ root | parent CLASSID ]\n" + "Where:\n" + "QDISC_KIND := { prio | etc. }\n" + "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; +} + +static int filter_ifindex; +static __u32 filter_qdisc; +static __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[300] = {}; + + 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); + snprintf(str, sizeof(str), + "+---(%s)", cls_id_str); + strcat(buf, str); + + parse_rtattr_flags(tb, TCA_MAX, (struct rtattr *)cls->data, + cls->data_len, NLA_F_NESTED); + + 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(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_flags(tb, TCA_MAX, TCA_RTA(t), len, NLA_F_NESTED); + + if (tb[TCA_KIND] == NULL) { + fprintf(stderr, "print_class: NULL kind\n"); + return -1; + } + + open_json_object(NULL); + if (n->nlmsg_type == RTM_DELTCLASS) + print_null(PRINT_ANY, "deleted", "deleted ", NULL); + + 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); + } + print_string(PRINT_ANY, "class", "class %s ", rta_getattr_str(tb[TCA_KIND])); + print_string(PRINT_ANY, "handle", "%s ", abuf); + + if (filter_ifindex == 0) + print_devname(PRINT_ANY, t->tcm_ifindex); + + if (t->tcm_parent == TC_H_ROOT) + print_bool(PRINT_ANY, "root", "root ", true); + 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); + print_string(PRINT_ANY, "parent", "parent %s ", abuf); + } + if (t->tcm_info) + print_0xhex(PRINT_ANY, "leaf", "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(stderr, "[cannot parse class parameters]"); + } + print_nl(); + if (show_stats) { + struct rtattr *xstats = NULL; + open_json_object("stats"); + if (tb[TCA_STATS] || tb[TCA_STATS2]) { + print_tcstats_attr(fp, tb, " ", &xstats); + print_string(PRINT_FP, NULL, "\n", NULL); + } + if (q && (xstats || tb[TCA_XSTATS]) && q->print_xstats) { + q->print_xstats(q, fp, xstats ? : tb[TCA_XSTATS]); + print_string(PRINT_FP, NULL, "\n", NULL); + } + close_json_object(); + } + close_json_object(); + 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; + } + new_json_obj(json); + if (rtnl_dump_filter(&rth, print_class, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + delete_json_obj(); + return 1; + } + delete_json_obj(); + + 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 (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; +} diff --git a/tc/tc_common.h b/tc/tc_common.h new file mode 100644 index 0000000..f1561d8 --- /dev/null +++ b/tc/tc_common.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define TCA_BUF_MAX (64*1024) + +extern struct rtnl_handle rth; + +int do_qdisc(int argc, char **argv); +int do_class(int argc, char **argv); +int do_filter(int argc, char **argv); +int do_chain(int argc, char **argv); +int do_action(int argc, char **argv); +int do_tcmonitor(int argc, char **argv); +int do_exec(int argc, char **argv); + +int print_action(struct nlmsghdr *n, void *arg); +int print_filter(struct nlmsghdr *n, void *arg); +int print_qdisc(struct nlmsghdr *n, void *arg); +int print_class(struct nlmsghdr *n, void *arg); +void print_size_table(struct rtattr *rta); + +struct tc_estimator; +int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est); + +struct tc_sizespec; +int parse_size_table(int *p_argc, char ***p_argv, struct tc_sizespec *s); +int check_size_table_opts(struct tc_sizespec *s); + +extern int show_graph; +extern bool use_names; +extern int use_iec; diff --git a/tc/tc_core.c b/tc/tc_core.c new file mode 100644 index 0000000..871ceb4 --- /dev/null +++ b/tc/tc_core.c @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_core.c TC core library. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_core.h" +#include <linux/atm.h> + +static double tick_in_usec = 1; +static double clock_factor = 1; + +int tc_core_time2big(unsigned int time) +{ + __u64 t = time; + + t *= tick_in_usec; + return (t >> 32) != 0; +} + + +unsigned int tc_core_time2tick(unsigned int time) +{ + return time*tick_in_usec; +} + +unsigned int tc_core_tick2time(unsigned int tick) +{ + return tick/tick_in_usec; +} + +unsigned int tc_core_time2ktime(unsigned int time) +{ + return time * clock_factor; +} + +unsigned int tc_core_ktime2time(unsigned int ktime) +{ + return ktime / clock_factor; +} + +unsigned int tc_calc_xmittime(__u64 rate, unsigned int size) +{ + return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate)); +} + +unsigned int tc_calc_xmitsize(__u64 rate, unsigned int ticks) +{ + return ((double)rate*tc_core_tick2time(ticks))/TIME_UNITS_PER_SEC; +} + +/* + * The align to ATM cells is used for determining the (ATM) SAR + * alignment overhead at the ATM layer. (SAR = Segmentation And + * Reassembly). This is for example needed when scheduling packet on + * an ADSL connection. Note that the extra ATM-AAL overhead is _not_ + * included in this calculation. This overhead is added in the kernel + * before doing the rate table lookup, as this gives better precision + * (as the table will always be aligned for 48 bytes). + * --Hawk, d.7/11-2004. <hawk@diku.dk> + */ +static unsigned int tc_align_to_atm(unsigned int size) +{ + int linksize, cells; + + cells = size / ATM_CELL_PAYLOAD; + if ((size % ATM_CELL_PAYLOAD) > 0) + cells++; + + linksize = cells * ATM_CELL_SIZE; /* Use full cell size to add ATM tax */ + return linksize; +} + +static unsigned int tc_adjust_size(unsigned int sz, unsigned int mpu, enum link_layer linklayer) +{ + if (sz < mpu) + sz = mpu; + + switch (linklayer) { + case LINKLAYER_ATM: + return tc_align_to_atm(sz); + case LINKLAYER_ETHERNET: + default: + /* No size adjustments on Ethernet */ + return sz; + } +} + +/* Notice, the rate table calculated here, have gotten replaced in the + * kernel and is no-longer used for lookups. + * + * This happened in kernel release v3.8 caused by kernel + * - commit 56b765b79 ("htb: improved accuracy at high rates"). + * This change unfortunately caused breakage of tc overhead and + * linklayer parameters. + * + * Kernel overhead handling got fixed in kernel v3.10 by + * - commit 01cb71d2d47 (net_sched: restore "overhead xxx" handling) + * + * Kernel linklayer handling got fixed in kernel v3.11 by + * - commit 8a8e3d84b17 (net_sched: restore "linklayer atm" handling) + */ + +/* + rtab[pkt_len>>cell_log] = pkt_xmit_time + */ + +int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned int mtu, + enum link_layer linklayer) +{ + int i; + unsigned int sz; + unsigned int bps = r->rate; + unsigned int mpu = r->mpu; + + if (mtu == 0) + mtu = 2047; + + if (cell_log < 0) { + cell_log = 0; + while ((mtu >> cell_log) > 255) + cell_log++; + } + + for (i = 0; i < 256; i++) { + sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer); + rtab[i] = tc_calc_xmittime(bps, sz); + } + + r->cell_align = -1; + r->cell_log = cell_log; + r->linklayer = (linklayer & TC_LINKLAYER_MASK); + return cell_log; +} + +int tc_calc_rtable_64(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned int mtu, + enum link_layer linklayer, __u64 rate) +{ + int i; + unsigned int sz; + __u64 bps = rate; + unsigned int mpu = r->mpu; + + if (mtu == 0) + mtu = 2047; + + if (cell_log < 0) { + cell_log = 0; + while ((mtu >> cell_log) > 255) + cell_log++; + } + + for (i = 0; i < 256; i++) { + sz = tc_adjust_size((i + 1) << cell_log, mpu, linklayer); + rtab[i] = tc_calc_xmittime(bps, sz); + } + + r->cell_align = -1; + r->cell_log = cell_log; + r->linklayer = (linklayer & TC_LINKLAYER_MASK); + return cell_log; +} + +/* + stab[pkt_len>>cell_log] = pkt_xmit_size>>size_log + */ + +int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab) +{ + int i; + enum link_layer linklayer = s->linklayer; + unsigned int sz; + + if (linklayer <= LINKLAYER_ETHERNET && s->mpu == 0) { + /* don't need data table in this case (only overhead set) */ + s->mtu = 0; + s->tsize = 0; + s->cell_log = 0; + s->cell_align = 0; + *stab = NULL; + return 0; + } + + if (s->mtu == 0) + s->mtu = 2047; + if (s->tsize == 0) + s->tsize = 512; + + s->cell_log = 0; + while ((s->mtu >> s->cell_log) > s->tsize - 1) + s->cell_log++; + + *stab = malloc(s->tsize * sizeof(__u16)); + if (!*stab) + return -1; + +again: + for (i = s->tsize - 1; i >= 0; i--) { + sz = tc_adjust_size((i + 1) << s->cell_log, s->mpu, linklayer); + if ((sz >> s->size_log) > UINT16_MAX) { + s->size_log++; + goto again; + } + (*stab)[i] = sz >> s->size_log; + } + + s->cell_align = -1; /* Due to the sz calc */ + return 0; +} + +int tc_core_init(void) +{ + FILE *fp; + __u32 clock_res; + __u32 t2us; + __u32 us2t; + + fp = fopen("/proc/net/psched", "r"); + if (fp == NULL) + return -1; + + if (fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) { + fclose(fp); + return -1; + } + fclose(fp); + + /* compatibility hack: for old iproute binaries (ignoring + * the kernel clock resolution) the kernel advertises a + * tick multiplier of 1000 in case of nano-second resolution, + * which really is 1. */ + if (clock_res == 1000000000) + t2us = us2t; + + clock_factor = (double)clock_res / TIME_UNITS_PER_SEC; + tick_in_usec = (double)t2us / us2t * clock_factor; + return 0; +} diff --git a/tc/tc_core.h b/tc/tc_core.h new file mode 100644 index 0000000..6dab272 --- /dev/null +++ b/tc/tc_core.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TC_CORE_H_ +#define _TC_CORE_H_ 1 + +#include <asm/types.h> +#include <linux/pkt_sched.h> + +enum link_layer { + LINKLAYER_UNSPEC, + LINKLAYER_ETHERNET, + LINKLAYER_ATM, +}; + + +int tc_core_time2big(unsigned time); +unsigned tc_core_time2tick(unsigned time); +unsigned tc_core_tick2time(unsigned tick); +unsigned tc_core_time2ktime(unsigned time); +unsigned tc_core_ktime2time(unsigned ktime); +unsigned tc_calc_xmittime(__u64 rate, unsigned size); +unsigned tc_calc_xmitsize(__u64 rate, unsigned ticks); +int tc_calc_rtable(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned mtu, enum link_layer link_layer); +int tc_calc_rtable_64(struct tc_ratespec *r, __u32 *rtab, + int cell_log, unsigned mtu, enum link_layer link_layer, + __u64 rate); +int tc_calc_size_table(struct tc_sizespec *s, __u16 **stab); + +int tc_setup_estimator(unsigned A, unsigned time_const, struct tc_estimator *est); + +int tc_core_init(void); + +extern struct rtnl_handle g_rth; +extern int is_batch_mode; + +#endif diff --git a/tc/tc_estimator.c b/tc/tc_estimator.c new file mode 100644 index 0000000..275f254 --- /dev/null +++ b/tc/tc_estimator.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_core.c TC core library. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_core.h" + +int tc_setup_estimator(unsigned int A, unsigned int time_const, struct tc_estimator *est) +{ + for (est->interval = 0; est->interval <= 5; est->interval++) { + if (A <= (1<<est->interval)*(TIME_UNITS_PER_SEC/4)) + break; + } + if (est->interval > 5) + return -1; + est->interval -= 2; + for (est->ewma_log = 1; est->ewma_log < 32; est->ewma_log++) { + double w = 1.0 - 1.0/(1<<est->ewma_log); + + if (A/(-log(w)) > time_const) + break; + } + est->ewma_log--; + if (est->ewma_log == 0 || est->ewma_log >= 31) + return -1; + return 0; +} diff --git a/tc/tc_exec.c b/tc/tc_exec.c new file mode 100644 index 0000000..182fbb4 --- /dev/null +++ b/tc/tc_exec.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_exec.c "tc exec". + * + * Authors: Daniel Borkmann <daniel@iogearbox.net> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <dlfcn.h> + +#include "utils.h" + +#include "tc_util.h" +#include "tc_common.h" + +static struct exec_util *exec_list; +static void *BODY; + +static void usage(void) +{ + fprintf(stderr, + "Usage: tc exec [ EXEC_TYPE ] [ help | OPTIONS ]\n" + "Where:\n" + "EXEC_TYPE := { bpf | etc. }\n" + "OPTIONS := ... try tc exec <desired EXEC_KIND> help\n"); +} + +static int parse_noeopt(struct exec_util *eu, int argc, char **argv) +{ + if (argc) { + fprintf(stderr, "Unknown exec \"%s\", hence option \"%s\" is unparsable\n", + eu->id, *argv); + return -1; + } + + return 0; +} + +static struct exec_util *get_exec_kind(const char *name) +{ + struct exec_util *eu; + char buf[256]; + void *dlh; + + for (eu = exec_list; eu; eu = eu->next) + if (strcmp(eu->id, name) == 0) + return eu; + + snprintf(buf, sizeof(buf), "%s/e_%s.so", get_tc_lib(), name); + dlh = dlopen(buf, RTLD_LAZY); + if (dlh == NULL) { + dlh = BODY; + if (dlh == NULL) { + dlh = BODY = dlopen(NULL, RTLD_LAZY); + if (dlh == NULL) + goto noexist; + } + } + + snprintf(buf, sizeof(buf), "%s_exec_util", name); + eu = dlsym(dlh, buf); + if (eu == NULL) + goto noexist; +reg: + eu->next = exec_list; + exec_list = eu; + + return eu; +noexist: + eu = calloc(1, sizeof(*eu)); + if (eu) { + strncpy(eu->id, name, sizeof(eu->id) - 1); + eu->parse_eopt = parse_noeopt; + goto reg; + } + + return eu; +} + +int do_exec(int argc, char **argv) +{ + struct exec_util *eu; + char kind[FILTER_NAMESZ] = {}; + + if (argc < 1) { + fprintf(stderr, "No command given, try \"tc exec help\".\n"); + return -1; + } + + if (matches(*argv, "help") == 0) { + usage(); + return 0; + } + + strncpy(kind, *argv, sizeof(kind) - 1); + + eu = get_exec_kind(kind); + if (eu == NULL) { + fprintf(stderr, "Allocation failed finding exec\n"); + return -1; + } + + argc--; + argv++; + + return eu->parse_eopt(eu, argc, argv); +} diff --git a/tc/tc_filter.c b/tc/tc_filter.c new file mode 100644 index 0000000..eb45c58 --- /dev/null +++ b/tc/tc_filter.c @@ -0,0 +1,797 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_filter.c "tc filter". + * + * 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 <linux/if_ether.h> + +#include "rt_names.h" +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" + +static void usage(void) +{ + fprintf(stderr, + "Usage: tc filter [ add | del | change | replace | show ] [ dev STRING ]\n" + " tc filter [ add | del | change | replace | show ] [ block BLOCK_INDEX ]\n" + " tc filter get dev STRING parent CLASSID protocol PROTO handle FILTERID pref PRIO FILTER_TYPE\n" + " tc filter get block BLOCK_INDEX protocol PROTO handle FILTERID pref PRIO FILTER_TYPE\n" + " [ pref PRIO ] protocol PROTO [ chain CHAIN_INDEX ]\n" + " [ estimator INTERVAL TIME_CONSTANT ]\n" + " [ root | ingress | egress | parent CLASSID ]\n" + " [ handle FILTERID ] [ [ FILTER_TYPE ] [ help | OPTIONS ] ]\n" + "\n" + " tc filter show [ dev STRING ] [ root | ingress | egress | parent CLASSID ]\n" + " tc filter show [ block BLOCK_INDEX ]\n" + "Where:\n" + "FILTER_TYPE := { u32 | bpf | fw | route | etc. }\n" + "FILTERID := ... format depends on classifier, see there\n" + "OPTIONS := ... try tc filter add <desired FILTER_KIND> help\n"); +} + +static void chain_usage(void) +{ + fprintf(stderr, + "Usage: tc chain [ add | del | get | show ] [ dev STRING ]\n" + " tc chain [ add | del | get | show ] [ block BLOCK_INDEX ] ]\n"); +} + +struct tc_filter_req { + struct nlmsghdr n; + struct tcmsg t; + char buf[MAX_MSG]; +}; + +static int tc_filter_modify(int cmd, unsigned int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[MAX_MSG]; + } 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 filter_util *q = NULL; + __u32 prio = 0; + __u32 protocol = 0; + int protocol_set = 0; + __u32 block_index = 0; + __u32 chain_index = 0; + int chain_index_set = 0; + char *fhandle = NULL; + char d[IFNAMSIZ] = {}; + char k[FILTER_NAMESZ] = {}; + struct tc_estimator est = {}; + + if (cmd == RTM_NEWTFILTER && flags & NLM_F_CREATE) + protocol = htons(ETH_P_ALL); + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (d[0]) + duparg("dev", *argv); + if (block_index) { + fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n"); + return -1; + } + strncpy(d, *argv, sizeof(d)-1); + } else if (matches(*argv, "block") == 0) { + NEXT_ARG(); + if (block_index) + duparg("block", *argv); + if (d[0]) { + fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n"); + return -1; + } + if (get_u32(&block_index, *argv, 0) || !block_index) + invarg("invalid block index value", *argv); + } 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, "ingress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"ingress\" is duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_INGRESS); + } else if (strcmp(*argv, "egress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"egress\" is duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_EGRESS); + } 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 (strcmp(*argv, "handle") == 0) { + NEXT_ARG(); + if (fhandle) + duparg("handle", *argv); + fhandle = *argv; + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "priority") == 0) { + NEXT_ARG(); + if (prio) + duparg("priority", *argv); + if (get_u32(&prio, *argv, 0) || prio > 0xFFFF) + invarg("invalid priority value", *argv); + } else if (matches(*argv, "protocol") == 0) { + __u16 id; + + NEXT_ARG(); + if (protocol_set) + duparg("protocol", *argv); + if (ll_proto_a2n(&id, *argv)) + invarg("invalid protocol", *argv); + protocol = id; + protocol_set = 1; + } else if (matches(*argv, "chain") == 0) { + NEXT_ARG(); + if (chain_index_set) + duparg("chain", *argv); + if (get_u32(&chain_index, *argv, 0)) + invarg("invalid chain index value", *argv); + chain_index_set = 1; + } else if (matches(*argv, "estimator") == 0) { + if (parse_estimator(&argc, &argv, &est) < 0) + return -1; + } else if (matches(*argv, "help") == 0) { + usage(); + return 0; + } else { + strncpy(k, *argv, sizeof(k)-1); + + q = get_filter_kind(k); + argc--; argv++; + break; + } + + argc--; argv++; + } + + req.t.tcm_info = TC_H_MAKE(prio<<16, protocol); + + if (chain_index_set) + addattr32(&req.n, sizeof(req), TCA_CHAIN, chain_index); + + if (k[0]) + addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1); + + if (d[0]) { + ll_init_map(&rth); + + req.t.tcm_ifindex = ll_name_to_index(d); + if (req.t.tcm_ifindex == 0) { + fprintf(stderr, "Cannot find device \"%s\"\n", d); + return 1; + } + } else if (block_index) { + req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK; + req.t.tcm_block_index = block_index; + } + + if (q) { + if (q->parse_fopt(q, fhandle, argc, argv, &req.n)) + return 1; + } else { + if (fhandle) { + fprintf(stderr, + "Must specify filter type when using \"handle\"\n"); + return -1; + } + if (argc) { + if (matches(*argv, "help") == 0) + usage(); + fprintf(stderr, + "Garbage instead of arguments \"%s ...\". Try \"tc filter help\".\n", + *argv); + return -1; + } + } + + if (est.ewma_log) + addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est)); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) { + fprintf(stderr, "We have an error talking to the kernel\n"); + return 2; + } + + return 0; +} + +static __u32 filter_parent; +static int filter_ifindex; +static __u32 filter_prio; +static __u32 filter_protocol; +static __u32 filter_chain_index; +static int filter_chain_index_set; +static __u32 filter_block_index; +__u16 f_proto; + +int print_filter(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 filter_util *q; + char abuf[256]; + + if (n->nlmsg_type != RTM_NEWTFILTER && + n->nlmsg_type != RTM_GETTFILTER && + n->nlmsg_type != RTM_DELTFILTER && + n->nlmsg_type != RTM_NEWCHAIN && + n->nlmsg_type != RTM_GETCHAIN && + n->nlmsg_type != RTM_DELCHAIN) { + fprintf(stderr, "Not a filter(cmd %d)\n", n->nlmsg_type); + return 0; + } + len -= NLMSG_LENGTH(sizeof(*t)); + if (len < 0) { + fprintf(stderr, "Wrong len %d\n", len); + return -1; + } + + parse_rtattr_flags(tb, TCA_MAX, TCA_RTA(t), len, NLA_F_NESTED); + + if (tb[TCA_KIND] == NULL && (n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_GETTFILTER || + n->nlmsg_type == RTM_DELTFILTER)) { + fprintf(stderr, "print_filter: NULL kind\n"); + return -1; + } + + open_json_object(NULL); + + if (n->nlmsg_type == RTM_DELTFILTER || n->nlmsg_type == RTM_DELCHAIN) + print_bool(PRINT_ANY, "deleted", "deleted ", true); + + if ((n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_NEWCHAIN) && + (n->nlmsg_flags & NLM_F_CREATE) && + !(n->nlmsg_flags & NLM_F_EXCL)) + print_bool(PRINT_ANY, "replaced", "replaced ", true); + + if ((n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_NEWCHAIN) && + (n->nlmsg_flags & NLM_F_CREATE) && + (n->nlmsg_flags & NLM_F_EXCL)) + print_bool(PRINT_ANY, "added", "added ", true); + + if (n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_GETTFILTER || + n->nlmsg_type == RTM_DELTFILTER) + print_string(PRINT_FP, NULL, "filter ", NULL); + else + print_string(PRINT_FP, NULL, "chain ", NULL); + if (t->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK) { + if (!filter_block_index || + filter_block_index != t->tcm_block_index) + print_uint(PRINT_ANY, "block", "block %u ", + t->tcm_block_index); + } else { + if (!filter_ifindex || filter_ifindex != t->tcm_ifindex) + print_devname(PRINT_ANY, t->tcm_ifindex); + + if (!filter_parent || filter_parent != t->tcm_parent) { + if (t->tcm_parent == TC_H_ROOT) + print_bool(PRINT_ANY, "root", "root ", true); + else if (t->tcm_parent == TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS)) + print_bool(PRINT_ANY, "ingress", "ingress ", true); + else if (t->tcm_parent == TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS)) + print_bool(PRINT_ANY, "egress", "egress ", true); + else { + print_tc_classid(abuf, sizeof(abuf), t->tcm_parent); + print_string(PRINT_ANY, "parent", "parent %s ", abuf); + } + } + } + + if (t->tcm_info && (n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_DELTFILTER || + n->nlmsg_type == RTM_GETTFILTER)) { + f_proto = TC_H_MIN(t->tcm_info); + __u32 prio = TC_H_MAJ(t->tcm_info)>>16; + + if (!filter_protocol || filter_protocol != f_proto) { + if (f_proto) { + SPRINT_BUF(b1); + print_string(PRINT_ANY, "protocol", + "protocol %s ", + ll_proto_n2a(f_proto, b1, sizeof(b1))); + } + } + if (!filter_prio || filter_prio != prio) { + if (prio) + print_uint(PRINT_ANY, "pref", "pref %u ", prio); + } + } + if (tb[TCA_KIND]) + print_string(PRINT_ANY, "kind", "%s ", rta_getattr_str(tb[TCA_KIND])); + + if (tb[TCA_CHAIN]) { + __u32 chain_index = rta_getattr_u32(tb[TCA_CHAIN]); + + if (!filter_chain_index_set || + filter_chain_index != chain_index) + print_uint(PRINT_ANY, "chain", "chain %u ", + chain_index); + } + + if (tb[TCA_KIND]) { + q = get_filter_kind(RTA_DATA(tb[TCA_KIND])); + if (tb[TCA_OPTIONS]) { + open_json_object("options"); + if (q) + q->print_fopt(q, fp, tb[TCA_OPTIONS], t->tcm_handle); + else + fprintf(stderr, "cannot parse option parameters\n"); + close_json_object(); + } + } + print_nl(); + + if (show_stats && (tb[TCA_STATS] || tb[TCA_STATS2])) { + print_tcstats_attr(fp, tb, " ", NULL); + print_nl(); + } + + print_ext_msg(tb); + close_json_object(); + fflush(fp); + return 0; +} + +static int tc_filter_get(int cmd, unsigned int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[MAX_MSG]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), + /* NLM_F_ECHO is for backward compatibility. old kernels never + * respond without it and newer kernels will ignore it. + * In old kernels there is a side effect: + * In addition to a response to the GET you will receive an + * event (if you do tc mon). + */ + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ECHO | flags, + .n.nlmsg_type = cmd, + .t.tcm_parent = TC_H_UNSPEC, + .t.tcm_family = AF_UNSPEC, + }; + struct nlmsghdr *answer; + struct filter_util *q = NULL; + __u32 prio = 0; + __u32 protocol = 0; + int protocol_set = 0; + __u32 chain_index; + int chain_index_set = 0; + __u32 block_index = 0; + __u32 parent_handle = 0; + char *fhandle = NULL; + char d[IFNAMSIZ] = {}; + char k[FILTER_NAMESZ] = {}; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (d[0]) + duparg("dev", *argv); + if (block_index) { + fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n"); + return -1; + } + strncpy(d, *argv, sizeof(d)-1); + } else if (matches(*argv, "block") == 0) { + NEXT_ARG(); + if (block_index) + duparg("block", *argv); + if (d[0]) { + fprintf(stderr, "Error: \"dev\" and \"block\" are mutually exclusive\n"); + return -1; + } + if (get_u32(&block_index, *argv, 0) || !block_index) + invarg("invalid block index value", *argv); + } 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, "ingress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"ingress\" is duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_INGRESS); + } else if (strcmp(*argv, "egress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"egress\" is duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_EGRESS); + } else if (strcmp(*argv, "parent") == 0) { + + NEXT_ARG(); + if (req.t.tcm_parent) + duparg("parent", *argv); + if (get_tc_classid(&parent_handle, *argv)) + invarg("Invalid parent ID", *argv); + req.t.tcm_parent = parent_handle; + } else if (strcmp(*argv, "handle") == 0) { + NEXT_ARG(); + if (fhandle) + duparg("handle", *argv); + fhandle = *argv; + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "priority") == 0) { + NEXT_ARG(); + if (prio) + duparg("priority", *argv); + if (get_u32(&prio, *argv, 0) || prio > 0xFFFF) + invarg("invalid priority value", *argv); + } else if (matches(*argv, "protocol") == 0) { + __u16 id; + + NEXT_ARG(); + if (protocol_set) + duparg("protocol", *argv); + if (ll_proto_a2n(&id, *argv)) + invarg("invalid protocol", *argv); + protocol = id; + protocol_set = 1; + } else if (matches(*argv, "chain") == 0) { + NEXT_ARG(); + if (chain_index_set) + duparg("chain", *argv); + if (get_u32(&chain_index, *argv, 0)) + invarg("invalid chain index value", *argv); + chain_index_set = 1; + } else if (matches(*argv, "help") == 0) { + usage(); + return 0; + } else { + if (!**argv) + invarg("invalid filter name", *argv); + + strncpy(k, *argv, sizeof(k)-1); + + q = get_filter_kind(k); + argc--; argv++; + break; + } + + argc--; argv++; + } + + if (cmd == RTM_GETTFILTER) { + if (!protocol_set) { + fprintf(stderr, "Must specify filter protocol\n"); + return -1; + } + + if (!prio) { + fprintf(stderr, "Must specify filter priority\n"); + return -1; + } + + req.t.tcm_info = TC_H_MAKE(prio<<16, protocol); + } + + if (chain_index_set) + addattr32(&req.n, sizeof(req), TCA_CHAIN, chain_index); + + if (req.t.tcm_parent == TC_H_UNSPEC) { + fprintf(stderr, "Must specify filter parent\n"); + return -1; + } + + if (cmd == RTM_GETTFILTER) { + if (k[0]) + addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1); + else { + fprintf(stderr, "Must specify filter type\n"); + 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); + filter_ifindex = req.t.tcm_ifindex; + } else if (block_index) { + req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK; + req.t.tcm_block_index = block_index; + filter_block_index = block_index; + } else { + fprintf(stderr, "Must specify netdevice \"dev\" or block index \"block\"\n"); + return -1; + } + + if (cmd == RTM_GETTFILTER && + q->parse_fopt(q, fhandle, argc, argv, &req.n)) + return 1; + + if (!fhandle && cmd == RTM_GETTFILTER) { + fprintf(stderr, "Must specify filter \"handle\"\n"); + return -1; + } + + if (argc) { + if (matches(*argv, "help") == 0) + usage(); + fprintf(stderr, + "Garbage instead of arguments \"%s ...\". Try \"tc filter help\".\n", + *argv); + return -1; + } + + if (rtnl_talk(&rth, &req.n, &answer) < 0) { + fprintf(stderr, "We have an error talking to the kernel\n"); + return 2; + } + + new_json_obj(json); + print_filter(answer, (void *)stdout); + delete_json_obj(); + + free(answer); + return 0; +} + +static int tc_filter_list(int cmd, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[MAX_MSG]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), + .n.nlmsg_type = cmd, + .t.tcm_parent = TC_H_UNSPEC, + .t.tcm_family = AF_UNSPEC, + }; + char d[IFNAMSIZ] = {}; + __u32 prio = 0; + __u32 protocol = 0; + __u32 block_index = 0; + char *fhandle = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (d[0]) + duparg("dev", *argv); + if (block_index) { + fprintf(stderr, "Error: \"dev\" cannot be used in the same time as \"block\"\n"); + return -1; + } + strncpy(d, *argv, sizeof(d)-1); + } else if (matches(*argv, "block") == 0) { + NEXT_ARG(); + if (block_index) + duparg("block", *argv); + if (d[0]) { + fprintf(stderr, "Error: \"block\" cannot be used in the same time as \"dev\"\n"); + return -1; + } + if (get_u32(&block_index, *argv, 0) || !block_index) + invarg("invalid block index value", *argv); + } else if (strcmp(*argv, "root") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"root\" is duplicate parent ID\n"); + return -1; + } + filter_parent = req.t.tcm_parent = TC_H_ROOT; + } else if (strcmp(*argv, "ingress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"ingress\" is duplicate parent ID\n"); + return -1; + } + filter_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_INGRESS); + req.t.tcm_parent = filter_parent; + } else if (strcmp(*argv, "egress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, + "Error: \"egress\" is duplicate parent ID\n"); + return -1; + } + filter_parent = TC_H_MAKE(TC_H_CLSACT, + TC_H_MIN_EGRESS); + req.t.tcm_parent = filter_parent; + } 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); + filter_parent = req.t.tcm_parent = handle; + } else if (strcmp(*argv, "handle") == 0) { + NEXT_ARG(); + if (fhandle) + duparg("handle", *argv); + fhandle = *argv; + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "priority") == 0) { + NEXT_ARG(); + if (prio) + duparg("priority", *argv); + if (get_u32(&prio, *argv, 0)) + invarg("invalid preference", *argv); + filter_prio = prio; + } else if (matches(*argv, "protocol") == 0) { + __u16 res; + + NEXT_ARG(); + if (protocol) + duparg("protocol", *argv); + if (ll_proto_a2n(&res, *argv)) + invarg("invalid protocol", *argv); + protocol = res; + filter_protocol = protocol; + } else if (matches(*argv, "chain") == 0) { + __u32 chain_index; + + NEXT_ARG(); + if (filter_chain_index_set) + duparg("chain", *argv); + if (get_u32(&chain_index, *argv, 0)) + invarg("invalid chain index value", *argv); + filter_chain_index_set = 1; + filter_chain_index = chain_index; + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + fprintf(stderr, + " What is \"%s\"? Try \"tc filter help\"\n", + *argv); + return -1; + } + + argc--; argv++; + } + + req.t.tcm_info = TC_H_MAKE(prio<<16, protocol); + + ll_init_map(&rth); + + if (d[0]) { + req.t.tcm_ifindex = ll_name_to_index(d); + if (!req.t.tcm_ifindex) + return -nodev(d); + filter_ifindex = req.t.tcm_ifindex; + } else if (block_index) { + if (!tc_qdisc_block_exists(block_index)) { + fprintf(stderr, "Cannot find block \"%u\"\n", block_index); + return 1; + } + req.t.tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK; + req.t.tcm_block_index = block_index; + filter_block_index = block_index; + } + + if (filter_chain_index_set) + addattr32(&req.n, sizeof(req), TCA_CHAIN, filter_chain_index); + + if (brief) { + struct nla_bitfield32 flags = { + .value = TCA_DUMP_FLAGS_TERSE, + .selector = TCA_DUMP_FLAGS_TERSE + }; + + addattr_l(&req.n, MAX_MSG, TCA_DUMP_FLAGS, &flags, sizeof(flags)); + } + + if (rtnl_dump_request_n(&rth, &req.n) < 0) { + perror("Cannot send dump request"); + return 1; + } + + new_json_obj(json); + if (rtnl_dump_filter(&rth, print_filter, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + delete_json_obj(); + return 1; + } + delete_json_obj(); + + return 0; +} + +int do_filter(int argc, char **argv) +{ + if (argc < 1) + return tc_filter_list(RTM_GETTFILTER, 0, NULL); + if (matches(*argv, "add") == 0) + return tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE, + argc-1, argv+1); + if (matches(*argv, "change") == 0) + return tc_filter_modify(RTM_NEWTFILTER, 0, argc-1, argv+1); + if (matches(*argv, "replace") == 0) + return tc_filter_modify(RTM_NEWTFILTER, NLM_F_CREATE, argc-1, + argv+1); + if (matches(*argv, "delete") == 0) + return tc_filter_modify(RTM_DELTFILTER, 0, argc-1, argv+1); + if (matches(*argv, "get") == 0) + return tc_filter_get(RTM_GETTFILTER, 0, argc-1, argv+1); + if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0 + || matches(*argv, "lst") == 0) + return tc_filter_list(RTM_GETTFILTER, argc-1, argv+1); + if (matches(*argv, "help") == 0) { + usage(); + return 0; + } + fprintf(stderr, "Command \"%s\" is unknown, try \"tc filter help\".\n", + *argv); + return -1; +} + +int do_chain(int argc, char **argv) +{ + if (argc < 1) + return tc_filter_list(RTM_GETCHAIN, 0, NULL); + if (matches(*argv, "add") == 0) { + return tc_filter_modify(RTM_NEWCHAIN, NLM_F_EXCL | NLM_F_CREATE, + argc - 1, argv + 1); + } else if (matches(*argv, "delete") == 0) { + return tc_filter_modify(RTM_DELCHAIN, 0, + argc - 1, argv + 1); + } else if (matches(*argv, "get") == 0) { + return tc_filter_get(RTM_GETCHAIN, 0, + argc - 1, argv + 1); + } else if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0) { + return tc_filter_list(RTM_GETCHAIN, argc - 1, argv + 1); + } else if (matches(*argv, "help") == 0) { + chain_usage(); + return 0; + } + fprintf(stderr, "Command \"%s\" is unknown, try \"tc chain help\".\n", + *argv); + return -1; +} diff --git a/tc/tc_monitor.c b/tc/tc_monitor.c new file mode 100644 index 0000000..5b9bccb --- /dev/null +++ b/tc/tc_monitor.c @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_monitor.c "tc monitor". + * + * Authors: Jamal Hadi Salim + */ + +#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 <time.h> +#include "rt_names.h" +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" + + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: tc [-timestamp [-tshort] monitor\n"); + exit(-1); +} + + +static int accept_tcmsg(struct rtnl_ctrl_data *ctrl, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE *)arg; + + if (timestamp) + print_timestamp(fp); + + if (n->nlmsg_type == RTM_NEWTFILTER || + n->nlmsg_type == RTM_DELTFILTER || + n->nlmsg_type == RTM_NEWCHAIN || + n->nlmsg_type == RTM_DELCHAIN) { + print_filter(n, arg); + return 0; + } + if (n->nlmsg_type == RTM_NEWTCLASS || n->nlmsg_type == RTM_DELTCLASS) { + print_class(n, arg); + return 0; + } + if (n->nlmsg_type == RTM_NEWQDISC || n->nlmsg_type == RTM_DELQDISC) { + print_qdisc(n, arg); + return 0; + } + if (n->nlmsg_type == RTM_GETACTION || n->nlmsg_type == RTM_NEWACTION || + n->nlmsg_type == RTM_DELACTION) { + print_action(n, arg); + return 0; + } + if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP && + n->nlmsg_type != NLMSG_DONE) { + fprintf(stderr, "Unknown message: length %08d type %08x flags %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + } + return 0; +} + +int do_tcmonitor(int argc, char **argv) +{ + struct rtnl_handle rth; + char *file = NULL; + unsigned int groups = nl_mgrp(RTNLGRP_TC); + + while (argc > 0) { + if (matches(*argv, "file") == 0) { + NEXT_ARG(); + file = *argv; + } else { + if (matches(*argv, "help") == 0) { + usage(); + } else { + fprintf(stderr, "Argument \"%s\" is unknown, try \"tc monitor help\".\n", *argv); + exit(-1); + } + } + argc--; argv++; + } + + if (file) { + FILE *fp = fopen(file, "r"); + int ret; + + if (fp == NULL) { + perror("Cannot fopen"); + exit(-1); + } + + ret = rtnl_from_file(fp, accept_tcmsg, stdout); + fclose(fp); + return ret; + } + + if (rtnl_open(&rth, groups) < 0) + exit(1); + + ll_init_map(&rth); + + if (rtnl_listen(&rth, accept_tcmsg, (void *)stdout) < 0) { + rtnl_close(&rth); + exit(2); + } + + rtnl_close(&rth); + exit(0); +} diff --git a/tc/tc_qdisc.c b/tc/tc_qdisc.c new file mode 100644 index 0000000..84fd659 --- /dev/null +++ b/tc/tc_qdisc.c @@ -0,0 +1,544 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_qdisc.c "tc qdisc". + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + * J Hadi Salim: Extension to ingress + */ + +#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 <malloc.h> + +#include "utils.h" +#include "tc_util.h" +#include "tc_common.h" + +static int usage(void) +{ + fprintf(stderr, + "Usage: tc qdisc [ add | del | replace | change | show ] dev STRING\n" + " [ handle QHANDLE ] [ root | ingress | clsact | parent CLASSID ]\n" + " [ estimator INTERVAL TIME_CONSTANT ]\n" + " [ stab [ help | STAB_OPTIONS] ]\n" + " [ ingress_block BLOCK_INDEX ] [ egress_block BLOCK_INDEX ]\n" + " [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n" + "\n" + " tc qdisc { show | list } [ dev STRING ] [ QDISC_ID ] [ invisible ]\n" + "Where:\n" + "QDISC_KIND := { [p|b]fifo | tbf | prio | red | etc. }\n" + "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n" + "STAB_OPTIONS := ... try tc qdisc add stab help\n" + "QDISC_ID := { root | ingress | handle QHANDLE | parent CLASSID }\n"); + return -1; +} + +static int tc_qdisc_modify(int cmd, unsigned int flags, int argc, char **argv) +{ + struct qdisc_util *q = NULL; + struct tc_estimator est = {}; + struct { + struct tc_sizespec szopts; + __u16 *data; + } stab = {}; + char d[IFNAMSIZ] = {}; + char k[FILTER_NAMESZ] = {}; + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[TCA_BUF_MAX]; + } 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, + }; + __u32 ingress_block = 0; + __u32 egress_block = 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, "handle") == 0) { + __u32 handle; + + if (req.t.tcm_handle) + duparg("handle", *argv); + NEXT_ARG(); + if (get_qdisc_handle(&handle, *argv)) + invarg("invalid qdisc ID", *argv); + req.t.tcm_handle = handle; + } 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, "clsact") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, "Error: \"clsact\" is a duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_CLSACT; + strncpy(k, "clsact", sizeof(k) - 1); + q = get_qdisc_kind(k); + req.t.tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0); + NEXT_ARG_FWD(); + break; + } else if (strcmp(*argv, "ingress") == 0) { + if (req.t.tcm_parent) { + fprintf(stderr, "Error: \"ingress\" is a duplicate parent ID\n"); + return -1; + } + req.t.tcm_parent = TC_H_INGRESS; + strncpy(k, "ingress", sizeof(k) - 1); + q = get_qdisc_kind(k); + req.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0); + NEXT_ARG_FWD(); + break; + } 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, "stab") == 0) { + if (parse_size_table(&argc, &argv, &stab.szopts) < 0) + return -1; + continue; + } else if (matches(*argv, "ingress_block") == 0) { + NEXT_ARG(); + if (get_u32(&ingress_block, *argv, 0) || !ingress_block) + invarg("invalid ingress block index value", *argv); + } else if (matches(*argv, "egress_block") == 0) { + NEXT_ARG(); + if (get_u32(&egress_block, *argv, 0) || !egress_block) + invarg("invalid egress block index value", *argv); + } 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 (ingress_block) + addattr32(&req.n, sizeof(req), + TCA_INGRESS_BLOCK, ingress_block); + if (egress_block) + addattr32(&req.n, sizeof(req), + TCA_EGRESS_BLOCK, egress_block); + + if (q) { + if (q->parse_qopt) { + if (q->parse_qopt(q, argc, argv, &req.n, d)) + return 1; + } else if (argc) { + fprintf(stderr, "qdisc '%s' does not support option parsing\n", k); + return -1; + } + } else { + if (argc) { + if (matches(*argv, "help") == 0) + usage(); + + fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc qdisc help\".\n", *argv); + return -1; + } + } + + if (check_size_table_opts(&stab.szopts)) { + struct rtattr *tail; + + if (tc_calc_size_table(&stab.szopts, &stab.data) < 0) { + fprintf(stderr, "failed to calculate size table.\n"); + return -1; + } + + tail = addattr_nest(&req.n, sizeof(req), TCA_STAB); + addattr_l(&req.n, sizeof(req), TCA_STAB_BASE, &stab.szopts, + sizeof(stab.szopts)); + if (stab.data) + addattr_l(&req.n, sizeof(req), TCA_STAB_DATA, stab.data, + stab.szopts.tsize * sizeof(__u16)); + addattr_nest_end(&req.n, tail); + free(stab.data); + } + + if (d[0]) { + int idx; + + ll_init_map(&rth); + + idx = ll_name_to_index(d); + if (!idx) + return -nodev(d); + req.t.tcm_ifindex = idx; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return 2; + + return 0; +} + +static int filter_ifindex; +static __u32 filter_parent; +static __u32 filter_handle; + +int print_qdisc(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_NEWQDISC && n->nlmsg_type != RTM_DELQDISC) { + fprintf(stderr, "Not a qdisc\n"); + return 0; + } + len -= NLMSG_LENGTH(sizeof(*t)); + if (len < 0) { + fprintf(stderr, "Wrong len %d\n", len); + return -1; + } + + if (filter_ifindex && filter_ifindex != t->tcm_ifindex) + return 0; + + if (filter_handle && filter_handle != t->tcm_handle) + return 0; + + if (filter_parent && filter_parent != t->tcm_parent) + return 0; + + parse_rtattr_flags(tb, TCA_MAX, TCA_RTA(t), len, NLA_F_NESTED); + + if (tb[TCA_KIND] == NULL) { + fprintf(stderr, "print_qdisc: NULL kind\n"); + return -1; + } + + open_json_object(NULL); + + if (n->nlmsg_type == RTM_DELQDISC) + print_bool(PRINT_ANY, "deleted", "deleted ", true); + + if (n->nlmsg_type == RTM_NEWQDISC && + (n->nlmsg_flags & NLM_F_CREATE) && + (n->nlmsg_flags & NLM_F_REPLACE)) + print_bool(PRINT_ANY, "replaced", "replaced ", true); + + if (n->nlmsg_type == RTM_NEWQDISC && + (n->nlmsg_flags & NLM_F_CREATE) && + (n->nlmsg_flags & NLM_F_EXCL)) + print_bool(PRINT_ANY, "added", "added ", true); + + print_string(PRINT_ANY, "kind", "qdisc %s", + rta_getattr_str(tb[TCA_KIND])); + sprintf(abuf, "%x:", t->tcm_handle >> 16); + print_string(PRINT_ANY, "handle", " %s", abuf); + if (show_raw) { + sprintf(abuf, "[%08x]", t->tcm_handle); + print_string(PRINT_FP, NULL, "%s", abuf); + } + print_string(PRINT_FP, NULL, " ", NULL); + + if (filter_ifindex == 0) + print_devname(PRINT_ANY, t->tcm_ifindex); + + if (t->tcm_parent == TC_H_ROOT) + print_bool(PRINT_ANY, "root", "root ", true); + else if (t->tcm_parent) { + print_tc_classid(abuf, sizeof(abuf), t->tcm_parent); + print_string(PRINT_ANY, "parent", "parent %s ", abuf); + } + + if (t->tcm_info != 1) + print_uint(PRINT_ANY, "refcnt", "refcnt %u ", t->tcm_info); + + if (tb[TCA_HW_OFFLOAD] && + (rta_getattr_u8(tb[TCA_HW_OFFLOAD]))) + print_bool(PRINT_ANY, "offloaded", "offloaded ", true); + + if (tb[TCA_INGRESS_BLOCK] && + RTA_PAYLOAD(tb[TCA_INGRESS_BLOCK]) >= sizeof(__u32)) { + __u32 block = rta_getattr_u32(tb[TCA_INGRESS_BLOCK]); + + if (block) + print_uint(PRINT_ANY, "ingress_block", + "ingress_block %u ", block); + } + + if (tb[TCA_EGRESS_BLOCK] && + RTA_PAYLOAD(tb[TCA_EGRESS_BLOCK]) >= sizeof(__u32)) { + __u32 block = rta_getattr_u32(tb[TCA_EGRESS_BLOCK]); + + if (block) + print_uint(PRINT_ANY, "egress_block", + "egress_block %u ", block); + } + + /* pfifo_fast is generic enough to warrant the hardcoding --JHS */ + if (strcmp("pfifo_fast", RTA_DATA(tb[TCA_KIND])) == 0) + q = get_qdisc_kind("prio"); + else + q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND])); + + open_json_object("options"); + if (tb[TCA_OPTIONS]) { + if (q) + q->print_qopt(q, fp, tb[TCA_OPTIONS]); + else + fprintf(stderr, "Cannot parse qdisc parameters\n"); + } + close_json_object(); + + print_nl(); + + if (show_details && tb[TCA_STAB]) { + print_size_table(tb[TCA_STAB]); + print_nl(); + } + + if (show_stats) { + struct rtattr *xstats = NULL; + + if (tb[TCA_STATS] || tb[TCA_STATS2] || tb[TCA_XSTATS]) { + print_tcstats_attr(fp, tb, " ", &xstats); + print_nl(); + } + + if (q && xstats && q->print_xstats) { + q->print_xstats(q, fp, xstats); + print_nl(); + } + } + + print_ext_msg(tb); + close_json_object(); + fflush(fp); + return 0; +} + +static int tc_qdisc_list(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct tcmsg t; + char buf[256]; + } req = { + .n.nlmsg_type = RTM_GETQDISC, + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)), + .t.tcm_family = AF_UNSPEC, + }; + + char d[IFNAMSIZ] = {}; + bool dump_invisible = false; + __u32 handle; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + strncpy(d, *argv, sizeof(d)-1); + } else if (strcmp(*argv, "root") == 0) { + if (filter_parent) + invarg("parent is already specified", *argv); + else if (filter_handle) + invarg("handle is already specified", *argv); + filter_parent = TC_H_ROOT; + } else if (strcmp(*argv, "ingress") == 0 || + strcmp(*argv, "clsact") == 0) { + if (filter_parent) + invarg("parent is already specified", *argv); + else if (filter_handle) + invarg("handle is already specified", *argv); + filter_parent = TC_H_INGRESS; + } else if (matches(*argv, "parent") == 0) { + if (filter_parent) + invarg("parent is already specified", *argv); + else if (filter_handle) + invarg("handle is already specified", *argv); + NEXT_ARG(); + if (get_tc_classid(&handle, *argv)) + invarg("invalid parent ID", *argv); + filter_parent = handle; + } else if (matches(*argv, "handle") == 0) { + if (filter_parent) + invarg("parent is already specified", *argv); + else if (filter_handle) + invarg("handle is already specified", *argv); + NEXT_ARG(); + if (get_qdisc_handle(&handle, *argv)) + invarg("invalid handle ID", *argv); + filter_handle = handle; + } else if (matches(*argv, "help") == 0) { + usage(); + } else if (strcmp(*argv, "invisible") == 0) { + dump_invisible = true; + } else { + fprintf(stderr, "What is \"%s\"? Try \"tc qdisc help\".\n", *argv); + return -1; + } + + argc--; argv++; + } + + ll_init_map(&rth); + + if (d[0]) { + req.t.tcm_ifindex = ll_name_to_index(d); + if (!req.t.tcm_ifindex) + return -nodev(d); + filter_ifindex = req.t.tcm_ifindex; + } + + if (dump_invisible) { + addattr(&req.n, 256, TCA_DUMP_INVISIBLE); + } + + if (rtnl_dump_request_n(&rth, &req.n) < 0) { + perror("Cannot send request"); + return 1; + } + + new_json_obj(json); + if (rtnl_dump_filter(&rth, print_qdisc, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + delete_json_obj(); + return 1; + } + delete_json_obj(); + + return 0; +} + +int do_qdisc(int argc, char **argv) +{ + if (argc < 1) + return tc_qdisc_list(0, NULL); + if (matches(*argv, "add") == 0) + return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1); + if (matches(*argv, "change") == 0) + return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1); + if (matches(*argv, "replace") == 0) + return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1); + if (matches(*argv, "link") == 0) + return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return tc_qdisc_modify(RTM_DELQDISC, 0, argc-1, argv+1); + if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0 + || matches(*argv, "lst") == 0) + return tc_qdisc_list(argc-1, argv+1); + if (matches(*argv, "help") == 0) { + usage(); + return 0; + } + fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv); + return -1; +} + +struct tc_qdisc_block_exists_ctx { + __u32 block_index; + bool found; +}; + +static int tc_qdisc_block_exists_cb(struct nlmsghdr *n, void *arg) +{ + struct tc_qdisc_block_exists_ctx *ctx = arg; + struct tcmsg *t = NLMSG_DATA(n); + struct rtattr *tb[TCA_MAX+1]; + int len = n->nlmsg_len; + struct qdisc_util *q; + const char *kind; + int err; + + if (n->nlmsg_type != RTM_NEWQDISC) + return 0; + + len -= NLMSG_LENGTH(sizeof(*t)); + if (len < 0) + return -1; + + parse_rtattr_flags(tb, TCA_MAX, TCA_RTA(t), len, NLA_F_NESTED); + + if (tb[TCA_KIND] == NULL) + return -1; + + if (tb[TCA_INGRESS_BLOCK] && + RTA_PAYLOAD(tb[TCA_INGRESS_BLOCK]) >= sizeof(__u32)) { + __u32 block = rta_getattr_u32(tb[TCA_INGRESS_BLOCK]); + + if (block == ctx->block_index) + ctx->found = true; + } + + if (tb[TCA_EGRESS_BLOCK] && + RTA_PAYLOAD(tb[TCA_EGRESS_BLOCK]) >= sizeof(__u32)) { + __u32 block = rta_getattr_u32(tb[TCA_EGRESS_BLOCK]); + + if (block == ctx->block_index) + ctx->found = true; + } + + kind = rta_getattr_str(tb[TCA_KIND]); + q = get_qdisc_kind(kind); + if (!q) + return -1; + if (q->has_block) { + bool found = false; + + err = q->has_block(q, tb[TCA_OPTIONS], ctx->block_index, &found); + if (err) + return err; + if (found) + ctx->found = true; + } + + return 0; +} + +bool tc_qdisc_block_exists(__u32 block_index) +{ + struct tc_qdisc_block_exists_ctx ctx = { .block_index = block_index }; + struct tcmsg t = { .tcm_family = AF_UNSPEC }; + + if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) { + perror("Cannot send dump request"); + return false; + } + + if (rtnl_dump_filter(&rth, tc_qdisc_block_exists_cb, &ctx) < 0) { + perror("Dump terminated\n"); + return false; + } + + return ctx.found; +} diff --git a/tc/tc_qevent.c b/tc/tc_qevent.c new file mode 100644 index 0000000..3456807 --- /dev/null +++ b/tc/tc_qevent.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +/* + * Helpers for handling qevents. + */ + +#include <stdio.h> +#include <string.h> + +#include "tc_qevent.h" +#include "utils.h" + +void qevents_init(struct qevent_util *qevents) +{ + if (!qevents) + return; + + for (; qevents->id; qevents++) + memset(qevents->data, 0, qevents->data_size); +} + +int qevent_parse(struct qevent_util *qevents, int *p_argc, char ***p_argv) +{ + char **argv = *p_argv; + int argc = *p_argc; + const char *name = *argv; + int err; + + if (!qevents) + goto out; + + for (; qevents->id; qevents++) { + if (strcmp(name, qevents->id) == 0) { + NEXT_ARG(); + err = qevents->parse_qevent(qevents, &argc, &argv); + if (err) + return err; + + *p_argc = argc; + *p_argv = argv; + return 0; + } + } + +out: + fprintf(stderr, "Unknown qevent `%s'\n", name); + return -1; +} + +int qevents_read(struct qevent_util *qevents, struct rtattr **tb) +{ + int err; + + if (!qevents) + return 0; + + for (; qevents->id; qevents++) { + if (tb[qevents->attr]) { + err = qevents->read_qevent(qevents, tb); + if (err) + return err; + } + } + + return 0; +} + +void qevents_print(struct qevent_util *qevents, FILE *f) +{ + int first = true; + + if (!qevents) + return; + + for (; qevents->id; qevents++) { + struct qevent_base *qeb = qevents->data; + + if (qeb->block_idx) { + if (first) { + open_json_array(PRINT_JSON, "qevents"); + first = false; + } + + open_json_object(NULL); + print_string(PRINT_ANY, "kind", "qevent %s", qevents->id); + qevents->print_qevent(qevents, f); + print_string(PRINT_FP, NULL, "%s", " "); + close_json_object(); + } + } + + if (!first) + close_json_array(PRINT_ANY, ""); +} + +bool qevents_have_block(struct qevent_util *qevents, __u32 block_idx) +{ + if (!qevents) + return false; + + for (; qevents->id; qevents++) { + struct qevent_base *qeb = qevents->data; + + if (qeb->block_idx == block_idx) + return true; + } + + return false; +} + +int qevents_dump(struct qevent_util *qevents, struct nlmsghdr *n) +{ + int err; + + if (!qevents) + return 0; + + for (; qevents->id; qevents++) { + struct qevent_base *qeb = qevents->data; + + if (qeb->block_idx) { + err = qevents->dump_qevent(qevents, n); + if (err) + return err; + } + } + + return 0; +} + +static int parse_block_idx(const char *arg, struct qevent_base *qeb) +{ + if (qeb->block_idx) { + fprintf(stderr, "Qevent block index already specified\n"); + return -1; + } + + if (get_unsigned(&qeb->block_idx, arg, 10) || !qeb->block_idx) { + fprintf(stderr, "Illegal qevent block index\n"); + return -1; + } + + return 0; +} + +static int read_block_idx(struct rtattr *attr, struct qevent_base *qeb) +{ + if (qeb->block_idx) { + fprintf(stderr, "Qevent block index already specified\n"); + return -1; + } + + qeb->block_idx = rta_getattr_u32(attr); + if (!qeb->block_idx) { + fprintf(stderr, "Illegal qevent block index\n"); + return -1; + } + + return 0; +} + +static void print_block_idx(FILE *f, __u32 block_idx) +{ + print_uint(PRINT_ANY, "block", " block %u", block_idx); +} + +int qevent_parse_plain(struct qevent_util *qu, int *p_argc, char ***p_argv) +{ + struct qevent_plain *qe = qu->data; + char **argv = *p_argv; + int argc = *p_argc; + + if (qe->base.block_idx) { + fprintf(stderr, "Duplicate qevent\n"); + return -1; + } + + while (argc > 0) { + if (strcmp(*argv, "block") == 0) { + NEXT_ARG(); + if (parse_block_idx(*argv, &qe->base)) + return -1; + } else { + break; + } + NEXT_ARG_FWD(); + } + + if (!qe->base.block_idx) { + fprintf(stderr, "Unspecified qevent block index\n"); + return -1; + } + + *p_argc = argc; + *p_argv = argv; + return 0; +} + +int qevent_read_plain(struct qevent_util *qu, struct rtattr **tb) +{ + struct qevent_plain *qe = qu->data; + + return read_block_idx(tb[qu->attr], &qe->base); +} + +void qevent_print_plain(struct qevent_util *qu, FILE *f) +{ + struct qevent_plain *qe = qu->data; + + print_block_idx(f, qe->base.block_idx); +} + +int qevent_dump_plain(struct qevent_util *qu, struct nlmsghdr *n) +{ + struct qevent_plain *qe = qu->data; + + return addattr32(n, 1024, qu->attr, qe->base.block_idx); +} diff --git a/tc/tc_qevent.h b/tc/tc_qevent.h new file mode 100644 index 0000000..d60c3f7 --- /dev/null +++ b/tc/tc_qevent.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TC_QEVENT_H_ +#define _TC_QEVENT_H_ + +#include <stdbool.h> +#include <linux/types.h> +#include <libnetlink.h> + +struct qevent_base { + __u32 block_idx; +}; + +struct qevent_util { + const char *id; + int (*parse_qevent)(struct qevent_util *qu, int *argc, char ***argv); + int (*read_qevent)(struct qevent_util *qu, struct rtattr **tb); + void (*print_qevent)(struct qevent_util *qu, FILE *f); + int (*dump_qevent)(struct qevent_util *qu, struct nlmsghdr *n); + size_t data_size; + void *data; + int attr; +}; + +#define QEVENT(_name, _form, _data, _attr) \ + { \ + .id = _name, \ + .parse_qevent = qevent_parse_##_form, \ + .read_qevent = qevent_read_##_form, \ + .print_qevent = qevent_print_##_form, \ + .dump_qevent = qevent_dump_##_form, \ + .data_size = sizeof(struct qevent_##_form), \ + .data = _data, \ + .attr = _attr, \ + } + +void qevents_init(struct qevent_util *qevents); +int qevent_parse(struct qevent_util *qevents, int *p_argc, char ***p_argv); +int qevents_read(struct qevent_util *qevents, struct rtattr **tb); +int qevents_dump(struct qevent_util *qevents, struct nlmsghdr *n); +void qevents_print(struct qevent_util *qevents, FILE *f); +bool qevents_have_block(struct qevent_util *qevents, __u32 block_idx); + +struct qevent_plain { + struct qevent_base base; +}; +int qevent_parse_plain(struct qevent_util *qu, int *p_argc, char ***p_argv); +int qevent_read_plain(struct qevent_util *qu, struct rtattr **tb); +void qevent_print_plain(struct qevent_util *qu, FILE *f); +int qevent_dump_plain(struct qevent_util *qu, struct nlmsghdr *n); + +#endif diff --git a/tc/tc_red.c b/tc/tc_red.c new file mode 100644 index 0000000..700a921 --- /dev/null +++ b/tc/tc_red.c @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_red.c RED maintenance routines. + * + * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include "utils.h" +#include "tc_core.h" +#include "tc_util.h" +#include "tc_red.h" + +/* + Plog = log(prob/(qmax - qmin)) + */ +int tc_red_eval_P(unsigned int qmin, unsigned int qmax, double prob) +{ + int i = qmax - qmin; + + if (!i) + return 0; + if (i < 0) + return -1; + + prob /= i; + + for (i = 0; i < 32; i++) { + if (prob > 1.0) + break; + prob *= 2; + } + if (i >= 32) + return -1; + return i; +} + +/* + burst + 1 - qmin/avpkt < (1-(1-W)^burst)/W + */ + +int tc_red_eval_ewma(unsigned int qmin, unsigned int burst, unsigned int avpkt) +{ + int wlog = 1; + double W = 0.5; + double a = (double)burst + 1 - (double)qmin/avpkt; + + if (a < 1.0) { + fprintf(stderr, "tc_red_eval_ewma() burst %u is too small ? Try burst %u\n", + burst, 1 + qmin/avpkt); + return -1; + } + for (wlog = 1; wlog < 32; wlog++, W /= 2) { + if (a <= (1 - pow(1-W, burst))/W) + return wlog; + } + return -1; +} + +/* + Stab[t>>Scell_log] = -log(1-W) * t/xmit_time + */ + +int tc_red_eval_idle_damping(int Wlog, unsigned int avpkt, unsigned int bps, __u8 *sbuf) +{ + double xmit_time = tc_calc_xmittime(bps, avpkt); + double lW = -log(1.0 - 1.0/(1<<Wlog))/xmit_time; + double maxtime = 31/lW; + int clog; + int i; + + for (clog = 0; clog < 32; clog++) { + if (maxtime/(1<<clog) < 512) + break; + } + if (clog >= 32) + return -1; + + sbuf[0] = 0; + for (i = 1; i < 255; i++) { + sbuf[i] = (i<<clog)*lW; + if (sbuf[i] > 31) + sbuf[i] = 31; + } + sbuf[255] = 31; + return clog; +} + +void tc_red_print_flags(__u32 flags) +{ + if (flags & TC_RED_ECN) + print_bool(PRINT_ANY, "ecn", "ecn ", true); + else + print_bool(PRINT_ANY, "ecn", NULL, false); + + if (flags & TC_RED_HARDDROP) + print_bool(PRINT_ANY, "harddrop", "harddrop ", true); + else + print_bool(PRINT_ANY, "harddrop", NULL, false); + + if (flags & TC_RED_ADAPTATIVE) + print_bool(PRINT_ANY, "adaptive", "adaptive ", true); + else + print_bool(PRINT_ANY, "adaptive", NULL, false); + + if (flags & TC_RED_NODROP) + print_bool(PRINT_ANY, "nodrop", "nodrop ", true); + else + print_bool(PRINT_ANY, "nodrop", NULL, false); +} diff --git a/tc/tc_red.h b/tc/tc_red.h new file mode 100644 index 0000000..3882c83 --- /dev/null +++ b/tc/tc_red.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TC_RED_H_ +#define _TC_RED_H_ 1 + +int tc_red_eval_P(unsigned qmin, unsigned qmax, double prob); +int tc_red_eval_ewma(unsigned qmin, unsigned burst, unsigned avpkt); +int tc_red_eval_idle_damping(int wlog, unsigned avpkt, unsigned bandwidth, + __u8 *sbuf); +void tc_red_print_flags(__u32 flags); + +#endif diff --git a/tc/tc_stab.c b/tc/tc_stab.c new file mode 100644 index 0000000..a773372 --- /dev/null +++ b/tc/tc_stab.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_stab.c "tc qdisc ... stab *". + * + * Authors: Jussi Kivilinna, <jussi.kivilinna@mbnet.fi> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <math.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <malloc.h> + +#include "utils.h" +#include "tc_util.h" +#include "tc_core.h" +#include "tc_common.h" + +static void stab_help(void) +{ + fprintf(stderr, + "Usage: ... stab [ mtu BYTES ] [ tsize SLOTS ] [ mpu BYTES ]\n" + " [ overhead BYTES ] [ linklayer TYPE ] ...\n" + " mtu : max packet size we create rate map for {2047}\n" + " tsize : how many slots should size table have {512}\n" + " mpu : minimum packet size used in rate computations\n" + " overhead : per-packet size overhead used in rate computations\n" + " linklayer : adapting to a linklayer e.g. atm\n" + "Example: ... stab overhead 20 linklayer atm\n"); + +} + +int check_size_table_opts(struct tc_sizespec *s) +{ + return s->linklayer >= LINKLAYER_ETHERNET || s->mpu != 0 || + s->overhead != 0; +} + +int parse_size_table(int *argcp, char ***argvp, struct tc_sizespec *sp) +{ + char **argv = *argvp; + int argc = *argcp; + struct tc_sizespec s = {}; + + NEXT_ARG(); + if (matches(*argv, "help") == 0) { + stab_help(); + return -1; + } + while (argc > 0) { + if (matches(*argv, "mtu") == 0) { + NEXT_ARG(); + if (s.mtu) + duparg("mtu", *argv); + if (get_u32(&s.mtu, *argv, 10)) + invarg("mtu", "invalid mtu"); + } else if (matches(*argv, "mpu") == 0) { + NEXT_ARG(); + if (s.mpu) + duparg("mpu", *argv); + if (get_u32(&s.mpu, *argv, 10)) + invarg("mpu", "invalid mpu"); + } else if (matches(*argv, "overhead") == 0) { + NEXT_ARG(); + if (s.overhead) + duparg("overhead", *argv); + if (get_integer(&s.overhead, *argv, 10)) + invarg("overhead", "invalid overhead"); + } else if (matches(*argv, "tsize") == 0) { + NEXT_ARG(); + if (s.tsize) + duparg("tsize", *argv); + if (get_u32(&s.tsize, *argv, 10)) + invarg("tsize", "invalid table size"); + } else if (matches(*argv, "linklayer") == 0) { + NEXT_ARG(); + if (s.linklayer != LINKLAYER_UNSPEC) + duparg("linklayer", *argv); + if (get_linklayer(&s.linklayer, *argv)) + invarg("linklayer", "invalid linklayer"); + } else + break; + argc--; argv++; + } + + if (!check_size_table_opts(&s)) + return -1; + + *sp = s; + *argvp = argv; + *argcp = argc; + return 0; +} + +void print_size_table(struct rtattr *rta) +{ + struct rtattr *tb[TCA_STAB_MAX + 1]; + + SPRINT_BUF(b1); + + parse_rtattr_nested(tb, TCA_STAB_MAX, rta); + + if (tb[TCA_STAB_BASE]) { + struct tc_sizespec s = {0}; + + memcpy(&s, RTA_DATA(tb[TCA_STAB_BASE]), + MIN(RTA_PAYLOAD(tb[TCA_STAB_BASE]), sizeof(s))); + + open_json_object("stab"); + print_string(PRINT_FP, NULL, " ", NULL); + + if (s.linklayer) + print_string(PRINT_ANY, "linklayer", + "linklayer %s ", + sprint_linklayer(s.linklayer, b1)); + if (s.overhead) + print_int(PRINT_ANY, "overhead", + "overhead %d ", s.overhead); + if (s.mpu) + print_uint(PRINT_ANY, "mpu", + "mpu %u ", s.mpu); + if (s.mtu) + print_uint(PRINT_ANY, "mtu", + "mtu %u ", s.mtu); + if (s.tsize) + print_uint(PRINT_ANY, "tsize", + "tsize %u ", s.tsize); + close_json_object(); + } +} diff --git a/tc/tc_util.c b/tc/tc_util.c new file mode 100644 index 0000000..aa7cf60 --- /dev/null +++ b/tc/tc_util.c @@ -0,0 +1,916 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * tc_util.c Misc TC utility functions. + * + * 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 <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <math.h> +#include <errno.h> + +#include "utils.h" +#include "names.h" +#include "tc_util.h" +#include "tc_common.h" + +#ifndef LIBDIR +#define LIBDIR "/usr/lib" +#endif + +static struct db_names *cls_names; + +#define NAMES_DB_USR CONF_USR_DIR "/tc_cls" +#define NAMES_DB_ETC CONF_ETC_DIR "/tc_cls" + +int cls_names_init(char *path) +{ + int ret; + + cls_names = db_names_alloc(); + if (!cls_names) + return -1; + + if (path) { + ret = db_names_load(cls_names, path); + if (ret == -ENOENT) { + fprintf(stderr, "Can't open class names file: %s\n", path); + return -1; + } + } + + ret = db_names_load(cls_names, NAMES_DB_ETC); + if (ret == -ENOENT) + ret = db_names_load(cls_names, NAMES_DB_USR); + + if (ret) { + db_names_free(cls_names); + cls_names = NULL; + } + + return 0; +} + +void cls_names_uninit(void) +{ + db_names_free(cls_names); +} + +const char *get_tc_lib(void) +{ + const char *lib_dir; + + lib_dir = getenv("TC_LIB_DIR"); + if (!lib_dir) + lib_dir = LIBDIR "/tc/"; + + return lib_dir; +} + +int get_qdisc_handle(__u32 *h, const char *str) +{ + unsigned long maj; + char *p; + + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str || maj >= (1 << 16)) + return -1; + maj <<= 16; + if (*p != ':' && *p != 0) + return -1; +ok: + *h = maj; + return 0; +} + +int get_tc_classid(__u32 *h, const char *str) +{ + unsigned long maj, min; + char *p; + + maj = TC_H_ROOT; + if (strcmp(str, "root") == 0) + goto ok; + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) { + maj = 0; + if (*p != ':') + return -1; + } + if (*p == ':') { + if (maj >= (1<<16)) + return -1; + maj <<= 16; + str = p+1; + min = strtoul(str, &p, 16); + if (*p != 0) + return -1; + if (min >= (1<<16)) + return -1; + maj |= min; + } else if (*p != 0) + return -1; + +ok: + *h = maj; + return 0; +} + +int print_tc_classid(char *buf, int blen, __u32 h) +{ + SPRINT_BUF(handle) = {}; + int hlen = SPRINT_BSIZE - 1; + + if (h == TC_H_ROOT) + sprintf(handle, "root"); + else if (h == TC_H_UNSPEC) + snprintf(handle, hlen, "none"); + else if (TC_H_MAJ(h) == 0) + snprintf(handle, hlen, ":%x", TC_H_MIN(h)); + else if (TC_H_MIN(h) == 0) + snprintf(handle, hlen, "%x:", TC_H_MAJ(h) >> 16); + else + snprintf(handle, hlen, "%x:%x", TC_H_MAJ(h) >> 16, TC_H_MIN(h)); + + if (use_names) { + char clname[IDNAME_MAX] = {}; + + if (id_to_name(cls_names, h, clname)) + snprintf(buf, blen, "%s#%s", clname, handle); + else + snprintf(buf, blen, "%s", handle); + } else { + snprintf(buf, blen, "%s", handle); + } + + return 0; +} + +char *sprint_tc_classid(__u32 h, char *buf) +{ + if (print_tc_classid(buf, SPRINT_BSIZE-1, h)) + strcpy(buf, "???"); + return buf; +} + +/* Parse a percent e.g: '30%' + * return: 0 = ok, -1 = error, 1 = out of range + */ +int parse_percent(double *val, const char *str) +{ + char *p; + + *val = strtod(str, &p) / 100.; + if (*val > 1.0 || *val < 0.0) + return 1; + if (*p && strcmp(p, "%")) + return -1; + + return 0; +} + +static int parse_percent_rate(char *rate, size_t len, + const char *str, const char *dev) +{ + long dev_mbit; + int ret; + double perc, rate_bit; + char *str_perc = NULL; + + if (!dev[0]) { + fprintf(stderr, "No device specified; specify device to rate limit by percentage\n"); + return -1; + } + + if (read_prop(dev, "speed", &dev_mbit)) + return -1; + + ret = sscanf(str, "%m[0-9.%]", &str_perc); + if (ret != 1) + goto malf; + + ret = parse_percent(&perc, str_perc); + if (ret == 1) { + fprintf(stderr, "Invalid rate specified; should be between [0,100]%% but is %s\n", str); + goto err; + } else if (ret == -1) { + goto malf; + } + + free(str_perc); + + rate_bit = perc * dev_mbit * 1000 * 1000; + + ret = snprintf(rate, len, "%lf", rate_bit); + if (ret <= 0 || ret >= len) { + fprintf(stderr, "Unable to parse calculated rate\n"); + return -1; + } + + return 0; + +malf: + fprintf(stderr, "Specified rate value could not be read or is malformed\n"); +err: + free(str_perc); + return -1; +} + +int get_percent_rate(unsigned int *rate, const char *str, const char *dev) +{ + char r_str[20]; + + if (parse_percent_rate(r_str, sizeof(r_str), str, dev)) + return -1; + + return get_rate(rate, r_str); +} + +int get_percent_rate64(__u64 *rate, const char *str, const char *dev) +{ + char r_str[20]; + + if (parse_percent_rate(r_str, sizeof(r_str), str, dev)) + return -1; + + return get_rate64(rate, r_str); +} + +void __attribute__((format(printf, 3, 0))) +tc_print_rate(enum output_type t, const char *key, const char *fmt, + unsigned long long rate) +{ + print_rate(use_iec, t, key, fmt, rate); +} + +char *sprint_ticks(__u32 ticks, char *buf) +{ + return sprint_time(tc_core_tick2time(ticks), buf); +} + +int get_size_and_cell(unsigned int *size, int *cell_log, char *str) +{ + char *slash = strchr(str, '/'); + + if (slash) + *slash = 0; + + if (get_size(size, str)) + return -1; + + if (slash) { + int cell; + int i; + + if (get_integer(&cell, slash+1, 0)) + return -1; + *slash = '/'; + + for (i = 0; i < 32; i++) { + if ((1<<i) == cell) { + *cell_log = i; + return 0; + } + } + return -1; + } + return 0; +} + +void print_devname(enum output_type type, int ifindex) +{ + const char *ifname = ll_index_to_name(ifindex); + + if (!is_json_context()) + printf("dev "); + + print_color_string(type, COLOR_IFNAME, + "dev", "%s ", ifname); +} + +static const char *action_n2a(int action) +{ + static char buf[64]; + + if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN)) + return "goto"; + if (TC_ACT_EXT_CMP(action, TC_ACT_JUMP)) + return "jump"; + switch (action) { + case TC_ACT_UNSPEC: + return "continue"; + case TC_ACT_OK: + return "pass"; + case TC_ACT_SHOT: + return "drop"; + case TC_ACT_RECLASSIFY: + return "reclassify"; + case TC_ACT_PIPE: + return "pipe"; + case TC_ACT_STOLEN: + return "stolen"; + case TC_ACT_TRAP: + return "trap"; + default: + snprintf(buf, 64, "%d", action); + return buf; + } +} + +/* Convert action branch name into numeric format. + * + * Parameters: + * @arg - string to parse + * @result - pointer to output variable + * @allow_num - whether @arg may be in numeric format already + * + * In error case, returns -1 and does not touch @result. Otherwise returns 0. + */ +int action_a2n(char *arg, int *result, bool allow_num) +{ + int n; + char dummy; + struct { + const char *a; + int n; + } a2n[] = { + {"continue", TC_ACT_UNSPEC}, + {"drop", TC_ACT_SHOT}, + {"shot", TC_ACT_SHOT}, + {"pass", TC_ACT_OK}, + {"ok", TC_ACT_OK}, + {"reclassify", TC_ACT_RECLASSIFY}, + {"pipe", TC_ACT_PIPE}, + {"goto", TC_ACT_GOTO_CHAIN}, + {"jump", TC_ACT_JUMP}, + {"trap", TC_ACT_TRAP}, + { NULL }, + }, *iter; + + for (iter = a2n; iter->a; iter++) { + if (matches(arg, iter->a) != 0) + continue; + n = iter->n; + goto out_ok; + } + if (!allow_num || sscanf(arg, "%d%c", &n, &dummy) != 1) + return -1; + +out_ok: + if (result) + *result = n; + return 0; +} + +static int __parse_action_control(int *argc_p, char ***argv_p, int *result_p, + bool allow_num, bool ignore_a2n_miss) +{ + int argc = *argc_p; + char **argv = *argv_p; + int result; + + if (!argc) + return -1; + if (action_a2n(*argv, &result, allow_num) == -1) { + if (!ignore_a2n_miss) + fprintf(stderr, "Bad action type %s\n", *argv); + return -1; + } + if (result == TC_ACT_GOTO_CHAIN) { + __u32 chain_index; + + NEXT_ARG(); + if (matches(*argv, "chain") != 0) { + fprintf(stderr, "\"chain index\" expected\n"); + return -1; + } + NEXT_ARG(); + if (get_u32(&chain_index, *argv, 10) || + chain_index > TC_ACT_EXT_VAL_MASK) { + fprintf(stderr, "Illegal \"chain index\"\n"); + return -1; + } + result |= chain_index; + } + if (result == TC_ACT_JUMP) { + __u32 jump_cnt = 0; + + NEXT_ARG(); + if (get_u32(&jump_cnt, *argv, 10) || + jump_cnt > TC_ACT_EXT_VAL_MASK) { + fprintf(stderr, "Invalid \"jump count\" (%s)\n", *argv); + return -1; + } + result |= jump_cnt; + } + NEXT_ARG_FWD(); + *argc_p = argc; + *argv_p = argv; + *result_p = result; + return 0; +} + +/* Parse action control including possible options. + * + * Parameters: + * @argc_p - pointer to argc to parse + * @argv_p - pointer to argv to parse + * @result_p - pointer to output variable + * @allow_num - whether action may be in numeric format already + * + * In error case, returns -1 and does not touch @result_1p. Otherwise returns 0. + */ +int parse_action_control(int *argc_p, char ***argv_p, + int *result_p, bool allow_num) +{ + return __parse_action_control(argc_p, argv_p, result_p, + allow_num, false); +} + +/* Parse action control including possible options. + * + * Parameters: + * @argc_p - pointer to argc to parse + * @argv_p - pointer to argv to parse + * @result_p - pointer to output variable + * @allow_num - whether action may be in numeric format already + * @default_result - set as a result in case of parsing error + * + * In case there is an error during parsing, the default result is used. + */ +void parse_action_control_dflt(int *argc_p, char ***argv_p, + int *result_p, bool allow_num, + int default_result) +{ + if (__parse_action_control(argc_p, argv_p, result_p, allow_num, true)) + *result_p = default_result; +} + +static int parse_action_control_slash_spaces(int *argc_p, char ***argv_p, + int *result1_p, int *result2_p, + bool allow_num) +{ + int argc = *argc_p; + char **argv = *argv_p; + int result1 = -1, result2 = -1; + int *result_p = &result1; + int ok = 0; + int ret; + + while (argc > 0) { + switch (ok) { + case 1: + if (strcmp(*argv, "/") != 0) + goto out; + result_p = &result2; + NEXT_ARG(); + /* fall-through */ + case 0: + ret = parse_action_control(&argc, &argv, + result_p, allow_num); + if (ret) + return ret; + ok++; + break; + default: + goto out; + } + } +out: + *result1_p = result1; + if (ok == 2) + *result2_p = result2; + *argc_p = argc; + *argv_p = argv; + return 0; +} + +/* Parse action control with slash including possible options. + * + * Parameters: + * @argc_p - pointer to argc to parse + * @argv_p - pointer to argv to parse + * @result1_p - pointer to the first (before slash) output variable + * @result2_p - pointer to the second (after slash) output variable + * @allow_num - whether action may be in numeric format already + * + * In error case, returns -1 and does not touch @result*. Otherwise returns 0. + */ +int parse_action_control_slash(int *argc_p, char ***argv_p, + int *result1_p, int *result2_p, bool allow_num) +{ + int result1, result2, argc = *argc_p; + char **argv = *argv_p; + char *p = strchr(*argv, '/'); + + if (!p) + return parse_action_control_slash_spaces(argc_p, argv_p, + result1_p, result2_p, + allow_num); + *p = 0; + if (action_a2n(*argv, &result1, allow_num)) { + *p = '/'; + return -1; + } + + *p = '/'; + if (action_a2n(p + 1, &result2, allow_num)) + return -1; + + *result1_p = result1; + *result2_p = result2; + NEXT_ARG_FWD(); + *argc_p = argc; + *argv_p = argv; + return 0; +} + +void print_action_control(FILE *f, const char *prefix, + int action, const char *suffix) +{ + print_string(PRINT_FP, NULL, "%s", prefix); + open_json_object("control_action"); + print_string(PRINT_ANY, "type", "%s", action_n2a(action)); + if (TC_ACT_EXT_CMP(action, TC_ACT_GOTO_CHAIN)) + print_uint(PRINT_ANY, "chain", " chain %u", + action & TC_ACT_EXT_VAL_MASK); + if (TC_ACT_EXT_CMP(action, TC_ACT_JUMP)) + print_uint(PRINT_ANY, "jump", " %u", + action & TC_ACT_EXT_VAL_MASK); + close_json_object(); + print_string(PRINT_FP, NULL, "%s", suffix); +} + +int get_linklayer(unsigned int *val, const char *arg) +{ + int res; + + if (matches(arg, "ethernet") == 0) + res = LINKLAYER_ETHERNET; + else if (matches(arg, "atm") == 0) + res = LINKLAYER_ATM; + else if (matches(arg, "adsl") == 0) + res = LINKLAYER_ATM; + else + return -1; /* Indicate error */ + + *val = res; + return 0; +} + +static void print_linklayer(char *buf, int len, unsigned int linklayer) +{ + switch (linklayer) { + case LINKLAYER_UNSPEC: + snprintf(buf, len, "%s", "unspec"); + return; + case LINKLAYER_ETHERNET: + snprintf(buf, len, "%s", "ethernet"); + return; + case LINKLAYER_ATM: + snprintf(buf, len, "%s", "atm"); + return; + default: + snprintf(buf, len, "%s", "unknown"); + return; + } +} + +char *sprint_linklayer(unsigned int linklayer, char *buf) +{ + print_linklayer(buf, SPRINT_BSIZE-1, linklayer); + return buf; +} + +/* + * Limited list of clockid's + * Since these are the ones the kernel qdisc can use + * because they are available via ktim_get + */ +static const struct clockid_table { + const char *name; + clockid_t clockid; +} clockt_map[] = { +#ifdef CLOCK_BOOTTIME + { "BOOTTIME", CLOCK_BOOTTIME }, +#endif +#ifdef CLOCK_MONOTONIC + { "MONOTONIC", CLOCK_MONOTONIC }, +#endif +#ifdef CLOCK_REALTIME + { "REALTIME", CLOCK_REALTIME }, +#endif +#ifdef CLOCK_TAI + { "TAI", CLOCK_TAI }, +#endif + { NULL } +}; + +int get_clockid(__s32 *val, const char *arg) +{ + const struct clockid_table *c; + + /* skip prefix if present */ + if (strcasestr(arg, "CLOCK_") != NULL) + arg += sizeof("CLOCK_") - 1; + + for (c = clockt_map; c->name; c++) { + if (strcasecmp(c->name, arg) == 0) { + *val = c->clockid; + return 0; + } + } + + return -1; +} + +const char *get_clock_name(clockid_t clockid) +{ + const struct clockid_table *c; + + for (c = clockt_map; c->name; c++) { + if (clockid == c->clockid) + return c->name; + } + + return "invalid"; +} + +void print_tm(FILE *f, const struct tcf_t *tm) +{ + int hz = get_user_hz(); + + if (tm->install != 0) + print_uint(PRINT_ANY, "installed", " installed %u sec", + tm->install / hz); + + if (tm->lastuse != 0) + print_uint(PRINT_ANY, "last_used", " used %u sec", + tm->lastuse / hz); + + if (tm->firstuse != 0) + print_uint(PRINT_ANY, "first_used", " firstused %u sec", + tm->firstuse / hz); + + if (tm->expires != 0) + print_uint(PRINT_ANY, "expires", " expires %u sec", + tm->expires / hz); +} + +static void print_tcstats_basic_hw(struct rtattr **tbs, const char *prefix) +{ + struct gnet_stats_basic bs_hw; + + if (!tbs[TCA_STATS_BASIC_HW]) + return; + + memcpy(&bs_hw, RTA_DATA(tbs[TCA_STATS_BASIC_HW]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC_HW]), sizeof(bs_hw))); + + if (bs_hw.bytes == 0 && bs_hw.packets == 0) + return; + + if (tbs[TCA_STATS_BASIC]) { + struct gnet_stats_basic bs; + + memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]), + sizeof(bs))); + + if (bs.bytes >= bs_hw.bytes && bs.packets >= bs_hw.packets) { + print_nl(); + print_string(PRINT_FP, NULL, "%s", prefix); + print_lluint(PRINT_ANY, "sw_bytes", + "Sent software %llu bytes", + bs.bytes - bs_hw.bytes); + print_uint(PRINT_ANY, "sw_packets", " %u pkt", + bs.packets - bs_hw.packets); + } + } + + print_nl(); + print_string(PRINT_FP, NULL, "%s", prefix); + print_lluint(PRINT_ANY, "hw_bytes", "Sent hardware %llu bytes", + bs_hw.bytes); + print_uint(PRINT_ANY, "hw_packets", " %u pkt", bs_hw.packets); +} + +void print_tcstats2_attr(FILE *fp, struct rtattr *rta, + const char *prefix, struct rtattr **xstats) +{ + struct rtattr *tbs[TCA_STATS_MAX + 1]; + + parse_rtattr_nested(tbs, TCA_STATS_MAX, rta); + + if (tbs[TCA_STATS_BASIC]) { + struct gnet_stats_basic bs = {0}; + __u64 packets64 = 0; + + if (tbs[TCA_STATS_PKT64]) + packets64 = rta_getattr_u64(tbs[TCA_STATS_PKT64]); + + memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]), sizeof(bs))); + print_string(PRINT_FP, NULL, "%s", prefix); + print_lluint(PRINT_ANY, "bytes", "Sent %llu bytes", bs.bytes); + if (packets64) + print_lluint(PRINT_ANY, "packets", + " %llu pkt", packets64); + else + print_uint(PRINT_ANY, "packets", + " %u pkt", bs.packets); + } + + if (tbs[TCA_STATS_QUEUE]) { + struct gnet_stats_queue q = {0}; + + memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q))); + print_uint(PRINT_ANY, "drops", " (dropped %u", q.drops); + print_uint(PRINT_ANY, "overlimits", ", overlimits %u", + q.overlimits); + print_uint(PRINT_ANY, "requeues", " requeues %u) ", q.requeues); + } + + if (tbs[TCA_STATS_BASIC_HW]) + print_tcstats_basic_hw(tbs, prefix); + + if (tbs[TCA_STATS_RATE_EST64]) { + struct gnet_stats_rate_est64 re = {0}; + + memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST64]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST64]), + sizeof(re))); + print_string(PRINT_FP, NULL, "\n%s", prefix); + print_lluint(PRINT_JSON, "rate", NULL, re.bps); + tc_print_rate(PRINT_FP, NULL, "rate %s", re.bps); + print_lluint(PRINT_ANY, "pps", " %llupps", re.pps); + } else if (tbs[TCA_STATS_RATE_EST]) { + struct gnet_stats_rate_est re = {0}; + + memcpy(&re, RTA_DATA(tbs[TCA_STATS_RATE_EST]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_RATE_EST]), sizeof(re))); + print_string(PRINT_FP, NULL, "\n%s", prefix); + print_uint(PRINT_JSON, "rate", NULL, re.bps); + tc_print_rate(PRINT_FP, NULL, "rate %s", re.bps); + print_uint(PRINT_ANY, "pps", " %upps", re.pps); + } + + if (tbs[TCA_STATS_QUEUE]) { + struct gnet_stats_queue q = {0}; + + memcpy(&q, RTA_DATA(tbs[TCA_STATS_QUEUE]), + MIN(RTA_PAYLOAD(tbs[TCA_STATS_QUEUE]), sizeof(q))); + if (!tbs[TCA_STATS_RATE_EST]) + print_nl(); + print_string(PRINT_FP, NULL, "%s", prefix); + print_size(PRINT_ANY, "backlog", "backlog %s", q.backlog); + print_uint(PRINT_ANY, "qlen", " %up", q.qlen); + print_uint(PRINT_FP, NULL, " requeues %u", q.requeues); + } + + if (xstats) + *xstats = tbs[TCA_STATS_APP] ? : NULL; +} + +void print_tcstats_attr(FILE *fp, struct rtattr *tb[], const char *prefix, + struct rtattr **xstats) +{ + if (tb[TCA_STATS2]) { + print_tcstats2_attr(fp, tb[TCA_STATS2], prefix, xstats); + if (xstats && !*xstats) + goto compat_xstats; + return; + } + /* backward compatibility */ + if (tb[TCA_STATS]) { + struct tc_stats st = {}; + + /* handle case where kernel returns more/less than we know about */ + memcpy(&st, RTA_DATA(tb[TCA_STATS]), + MIN(RTA_PAYLOAD(tb[TCA_STATS]), sizeof(st))); + + fprintf(fp, + "%sSent %llu bytes %u pkts (dropped %u, overlimits %u) ", + prefix, (unsigned long long)st.bytes, + st.packets, st.drops, st.overlimits); + + if (st.bps || st.pps || st.qlen || st.backlog) { + fprintf(fp, "\n%s", prefix); + if (st.bps || st.pps) { + fprintf(fp, "rate "); + if (st.bps) + tc_print_rate(PRINT_FP, NULL, "%s ", + st.bps); + if (st.pps) + fprintf(fp, "%upps ", st.pps); + } + if (st.qlen || st.backlog) { + fprintf(fp, "backlog "); + if (st.backlog) + print_size(PRINT_FP, NULL, "%s ", + st.backlog); + if (st.qlen) + fprintf(fp, "%up ", st.qlen); + } + } + } + +compat_xstats: + if (tb[TCA_XSTATS] && xstats) + *xstats = tb[TCA_XSTATS]; +} + +static void print_masked_type(__u32 type_max, + __u32 (*rta_getattr_type)(const struct rtattr *), + const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline) +{ + __u32 value, mask; + + if (!attr) + return; + + value = rta_getattr_type(attr); + mask = mask_attr ? rta_getattr_type(mask_attr) : type_max; + + if (newline) + print_string(PRINT_FP, NULL, "%s ", _SL_); + else + print_string(PRINT_FP, NULL, " ", _SL_); + + print_uint_name_value(name, value); + + if (mask != type_max) { + char mask_name[SPRINT_BSIZE-6]; + + snprintf(mask_name, sizeof(mask_name), "%s_mask", name); + print_hex(PRINT_ANY, mask_name, "/0x%x", mask); + } +} + +void print_masked_u32(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline) +{ + print_masked_type(UINT32_MAX, rta_getattr_u32, name, attr, mask_attr, + newline); +} + +static __u32 __rta_getattr_u16_u32(const struct rtattr *attr) +{ + return rta_getattr_u16(attr); +} + +void print_masked_u16(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline) +{ + print_masked_type(UINT16_MAX, __rta_getattr_u16_u32, name, attr, + mask_attr, newline); +} + +static __u32 __rta_getattr_u8_u32(const struct rtattr *attr) +{ + return rta_getattr_u8(attr); +} + +void print_masked_u8(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline) +{ + print_masked_type(UINT8_MAX, __rta_getattr_u8_u32, name, attr, + mask_attr, newline); +} + +static __u32 __rta_getattr_be16_u32(const struct rtattr *attr) +{ + return rta_getattr_be16(attr); +} + +void print_masked_be16(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline) +{ + print_masked_type(UINT16_MAX, __rta_getattr_be16_u32, name, attr, + mask_attr, newline); +} + +void print_ext_msg(struct rtattr **tb) +{ + if (!tb[TCA_EXT_WARN_MSG]) + return; + + print_string(PRINT_ANY, "warn", "%s", rta_getattr_str(tb[TCA_EXT_WARN_MSG])); + print_nl(); +} diff --git a/tc/tc_util.h b/tc/tc_util.h new file mode 100644 index 0000000..623d988 --- /dev/null +++ b/tc/tc_util.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TC_UTIL_H_ +#define _TC_UTIL_H_ 1 + +#define MAX_MSG 16384 +#include <limits.h> +#include <linux/if.h> +#include <stdbool.h> + +#include <linux/pkt_sched.h> +#include <linux/pkt_cls.h> +#include <linux/gen_stats.h> + +#include "tc_core.h" +#include "json_print.h" + +/* This is the deprecated multiqueue interface */ +#ifndef TCA_PRIO_MAX +enum +{ + TCA_PRIO_UNSPEC, + TCA_PRIO_MQ, + __TCA_PRIO_MAX +}; + +#define TCA_PRIO_MAX (__TCA_PRIO_MAX - 1) +#endif + +#define FILTER_NAMESZ 16 + +struct qdisc_util { + struct qdisc_util *next; + const char *id; + int (*parse_qopt)(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev); + int (*print_qopt)(struct qdisc_util *qu, + FILE *f, struct rtattr *opt); + int (*print_xstats)(struct qdisc_util *qu, + FILE *f, struct rtattr *xstats); + + int (*parse_copt)(struct qdisc_util *qu, int argc, + char **argv, struct nlmsghdr *n, const char *dev); + int (*print_copt)(struct qdisc_util *qu, FILE *f, struct rtattr *opt); + int (*has_block)(struct qdisc_util *qu, struct rtattr *opt, __u32 block_idx, bool *p_has); +}; + +extern __u16 f_proto; +struct filter_util { + struct filter_util *next; + char id[FILTER_NAMESZ]; + int (*parse_fopt)(struct filter_util *qu, char *fhandle, + int argc, char **argv, struct nlmsghdr *n); + int (*print_fopt)(struct filter_util *qu, + FILE *f, struct rtattr *opt, __u32 fhandle); +}; + +struct action_util { + struct action_util *next; + char id[FILTER_NAMESZ]; + int (*parse_aopt)(struct action_util *a, int *argc, + char ***argv, int code, struct nlmsghdr *n); + int (*print_aopt)(struct action_util *au, FILE *f, struct rtattr *opt); + int (*print_xstats)(struct action_util *au, + FILE *f, struct rtattr *xstats); +}; + +struct exec_util { + struct exec_util *next; + char id[FILTER_NAMESZ]; + int (*parse_eopt)(struct exec_util *eu, int argc, char **argv); +}; + +const char *get_tc_lib(void); + +struct qdisc_util *get_qdisc_kind(const char *str); +struct filter_util *get_filter_kind(const char *str); + +int get_qdisc_handle(__u32 *h, const char *str); +int get_percent_rate(unsigned int *rate, const char *str, const char *dev); +int get_percent_rate64(__u64 *rate, const char *str, const char *dev); +int get_size_and_cell(unsigned int *size, int *cell_log, char *str); +int get_linklayer(unsigned int *val, const char *arg); + +void tc_print_rate(enum output_type t, const char *key, const char *fmt, + unsigned long long rate); +void print_devname(enum output_type type, int ifindex); + +char *sprint_tc_classid(__u32 h, char *buf); +char *sprint_ticks(__u32 ticks, char *buf); +char *sprint_linklayer(unsigned int linklayer, char *buf); + +void print_tcstats_attr(FILE *fp, struct rtattr *tb[], + const char *prefix, struct rtattr **xstats); +void print_tcstats2_attr(FILE *fp, struct rtattr *rta, + const char *prefix, struct rtattr **xstats); + +int get_tc_classid(__u32 *h, const char *str); +int print_tc_classid(char *buf, int len, __u32 h); +char *sprint_tc_classid(__u32 h, char *buf); + +int tc_print_police(FILE *f, struct rtattr *tb); +int parse_percent(double *val, const char *str); +int parse_police(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n); + +int parse_action_control(int *argc_p, char ***argv_p, + int *result_p, bool allow_num); +void parse_action_control_dflt(int *argc_p, char ***argv_p, + int *result_p, bool allow_num, + int default_result); +int parse_action_control_slash(int *argc_p, char ***argv_p, + int *result1_p, int *result2_p, bool allow_num); +void print_action_control(FILE *f, const char *prefix, + int action, const char *suffix); +int police_print_xstats(struct action_util *a, FILE *f, struct rtattr *tb); +int tc_print_action(FILE *f, const struct rtattr *tb, unsigned short tot_acts); +int parse_action(int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n); +void print_tm(FILE *f, const struct tcf_t *tm); +int prio_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt); + +int cls_names_init(char *path); +void cls_names_uninit(void); + +#define CLOCKID_INVALID (-1) +int get_clockid(__s32 *val, const char *arg); +const char *get_clock_name(clockid_t clockid); + +int action_a2n(char *arg, int *result, bool allow_num); + +bool tc_qdisc_block_exists(__u32 block_index); + +void print_masked_u32(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline); +void print_masked_u16(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline); +void print_masked_u8(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline); +void print_masked_be16(const char *name, struct rtattr *attr, + struct rtattr *mask_attr, bool newline); + +void print_ext_msg(struct rtattr **tb); +#endif |