aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPo Liu <Po.Liu@nxp.com>2020-05-08 15:02:46 +0800
committerDavid Ahern <dsahern@gmail.com>2020-05-13 02:19:46 +0000
commit07d5ee70b5b3d1777ef6569bc5a94bf5d0ea5c0a (patch)
treea778a211ede9710305e9082230ba2f774183f357
parent0e9b227e2d9ea0874c2438c733e6d1e9de925563 (diff)
downloadiproute2-07d5ee70b5b3d1777ef6569bc5a94bf5d0ea5c0a.tar.gz
iproute2-next:tc:action: add a gate control action
Introduce a ingress frame gate control flow action. Tc gate action does the work like this: Assume there is a gate allow specified ingress frames can pass at specific time slot, and also drop at specific time slot. Tc filter chooses the ingress frames, and tc gate action would specify what slot does these frames can be passed to device and what time slot would be dropped. Tc gate action would provide an entry list to tell how much time gate keep open and how much time gate keep state close. Gate action also assign a start time to tell when the entry list start. Then driver would repeat the gate entry list cyclically. For the software simulation, gate action require the user assign a time clock type. Below is the setting example in user space. Tc filter a stream source ip address is 192.168.0.20 and gate action own two time slots. One is last 200ms gate open let frame pass another is last 100ms gate close let frames dropped. # tc qdisc add dev eth0 ingress # tc filter add dev eth0 parent ffff: protocol ip \ flower src_ip 192.168.0.20 \ action gate index 2 clockid CLOCK_TAI \ sched-entry open 200000000ns -1 8000000b \ sched-entry close 100000000ns # tc chain del dev eth0 ingress chain 0 "sched-entry" follow the name taprio style. Gate state is "open"/"close". Follow the period nanosecond. Then next -1 is internal priority value means which ingress queue should put to. "-1" means wildcard. The last value optional specifies the maximum number of MSDU octets that are permitted to pass the gate during the specified time interval, the overlimit frames would be dropped. Below example shows filtering a stream with destination mac address is 10:00:80:00:00:00 and ip type is ICMP, follow the action gate. The gate action would run with one close time slot which means always keep close. The time cycle is total 200000000ns. The base-time would calculate by: 1357000000000 + (N + 1) * cycletime When the total value is the future time, it will be the start time. The cycletime here would be 200000000ns for this case. #tc filter add dev eth0 parent ffff: protocol ip \ flower skip_hw ip_proto icmp dst_mac 10:00:80:00:00:00 \ action gate index 12 base-time 1357000000000ns \ sched-entry CLOSE 200000000ns \ clockid CLOCK_TAI Signed-off-by: Po Liu <Po.Liu@nxp.com> Signed-off-by: David Ahern <dsahern@gmail.com>
-rw-r--r--tc/Makefile1
-rw-r--r--tc/m_gate.c580
2 files changed, 581 insertions, 0 deletions
diff --git a/tc/Makefile b/tc/Makefile
index e31cbc12e..79c9c1dd9 100644
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -54,6 +54,7 @@ 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
diff --git a/tc/m_gate.c b/tc/m_gate.c
new file mode 100644
index 000000000..327df7eb2
--- /dev/null
+++ b/tc/m_gate.c
@@ -0,0 +1,580 @@
+// 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;
+};
+
+#define CLOCKID_INVALID (-1)
+static const struct clockid_table {
+ const char *name;
+ clockid_t clockid;
+} clockt_map[] = {
+ { "REALTIME", CLOCK_REALTIME },
+ { "TAI", CLOCK_TAI },
+ { "BOOTTIME", CLOCK_BOOTTIME },
+ { "MONOTONIC", CLOCK_MONOTONIC },
+ { NULL }
+};
+
+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_clockid(__s32 *val, const char *arg)
+{
+ const struct clockid_table *c;
+
+ 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;
+}
+
+static 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";
+}
+
+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 imcomplete\n");
+ free_entries(&gate_entries);
+ return -1;
+ }
+
+ NEXT_ARG();
+
+ if (get_gate_state(&gate_state, *argv)) {
+ explain_entry_format();
+ fprintf(stderr, "\"sched-entry\" is imcomplete\n");
+ free_entries(&gate_entries);
+ return -1;
+ }
+
+ if (!NEXT_ARG_OK()) {
+ explain_entry_format();
+ fprintf(stderr, "\"sched-entry\" is imcomplete\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 imcomplete\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;
+ char buf[22];
+
+ 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) {
+ memset(buf, 0, sizeof(buf));
+ print_uint(PRINT_JSON, "max_octets", NULL, maxoctets);
+ print_string(PRINT_FP, NULL, "\t max-octets %s",
+ sprint_size(maxoctets, buf));
+ } 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;
+ char buf[22];
+ 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;
+}