| // 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; |
| } |