| /* 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); |
| } |
| |
| int get_size64_and_cell(__u64 *size, int *cell_log, char *str) |
| { |
| char *slash = strchr(str, '/'); |
| |
| if (slash) |
| *slash = 0; |
| |
| if (get_size64(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; |
| } |
| |
| int get_size_and_cell(unsigned int *size, int *cell_log, char *str) |
| { |
| __u64 size64; |
| int rv; |
| |
| rv = get_size64_and_cell(&size64, cell_log, str); |
| if (rv) |
| return rv; |
| |
| if (size64 > UINT32_MAX) |
| return -1; |
| |
| *size = size64; |
| |
| 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(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(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, |
| __u64 packets64, __u64 packets64_hw) |
| { |
| 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))); |
| packets64_hw = packets64_hw ? : bs_hw.packets; |
| |
| if (bs_hw.bytes == 0 && packets64_hw == 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))); |
| packets64 = packets64 ? : bs.packets; |
| |
| if (bs.bytes >= bs_hw.bytes && packets64 >= packets64_hw) { |
| 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_lluint(PRINT_ANY, "sw_packets", " %llu pkt", |
| packets64 - packets64_hw); |
| } |
| } |
| |
| print_nl(); |
| print_string(PRINT_FP, NULL, "%s", prefix); |
| print_lluint(PRINT_ANY, "hw_bytes", "Sent hardware %llu bytes", |
| bs_hw.bytes); |
| print_lluint(PRINT_ANY, "hw_packets", " %llu pkt", packets64_hw); |
| } |
| |
| static void parse_packets64(const struct rtattr *nest, __u64 *p_packets64, |
| __u64 *p_packets64_hw) |
| { |
| unsigned short prev_type = __TCA_STATS_MAX; |
| const struct rtattr *pos; |
| |
| /* 'TCA_STATS_PKT64' can appear twice in the 'TCA_ACT_STATS' nest. |
| * Whether the attribute carries the combined or hardware only |
| * statistics depends on the attribute that precedes it in the nest. |
| */ |
| rtattr_for_each_nested(pos, nest) { |
| if (pos->rta_type == TCA_STATS_PKT64 && |
| prev_type == TCA_STATS_BASIC) |
| *p_packets64 = rta_getattr_u64(pos); |
| else if (pos->rta_type == TCA_STATS_PKT64 && |
| prev_type == TCA_STATS_BASIC_HW) |
| *p_packets64_hw = rta_getattr_u64(pos); |
| prev_type = pos->rta_type; |
| } |
| } |
| |
| void print_tcstats2_attr(struct rtattr *rta, const char *prefix, struct rtattr **xstats) |
| { |
| struct rtattr *tbs[TCA_STATS_MAX + 1]; |
| __u64 packets64 = 0, packets64_hw = 0; |
| |
| parse_rtattr_nested(tbs, TCA_STATS_MAX, rta); |
| parse_packets64(rta, &packets64, &packets64_hw); |
| |
| if (tbs[TCA_STATS_BASIC]) { |
| struct gnet_stats_basic bs = {0}; |
| |
| 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, packets64, packets64_hw); |
| |
| 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(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))); |
| |
| print_string(PRINT_FP, NULL, "%s", prefix); |
| print_lluint(PRINT_ANY, "bytes", "Sent %llu bytes", |
| (unsigned long long)st.bytes); |
| print_uint(PRINT_ANY, "packets", " %u pkts", st.packets); |
| print_uint(PRINT_ANY, "dropped", " (dropped %u,", st.drops); |
| print_uint(PRINT_ANY, "overlimits", " overlimits %u) ", st.overlimits); |
| |
| if (st.bps || st.pps || st.qlen || st.backlog) { |
| print_nl(); |
| print_string(PRINT_FP, NULL, "%s", prefix); |
| |
| if (st.bps || st.pps) { |
| print_string(PRINT_FP, NULL, "rate ", NULL); |
| if (st.bps) |
| tc_print_rate(PRINT_ANY, "rate", "%s ", st.bps); |
| if (st.pps) |
| print_uint(PRINT_ANY, "pps", "%upps ", st.pps); |
| } |
| if (st.qlen || st.backlog) { |
| print_string(PRINT_FP, NULL, "backlog ", NULL); |
| if (st.backlog) |
| print_size(PRINT_ANY, "backlog", "%s ", st.backlog); |
| if (st.qlen) |
| print_uint(PRINT_ANY, "qlen", "%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(); |
| } |