From 40443f49b398f7a907f5fa808f25eb45d8aa163f Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 15 Aug 2018 14:29:41 -0700 Subject: [PATCH 01/30] ip: convert monitor to switch The decoding of netlink message types is natural for a C switch statement. Signed-off-by: Stephen Hemminger --- ip/ipmonitor.c | 63 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c index 8b017341..5552d98e 100644 --- a/ip/ipmonitor.c +++ b/ip/ipmonitor.c @@ -58,7 +58,9 @@ static int accept_msg(const struct sockaddr_nl *who, { FILE *fp = (FILE *)arg; - if (n->nlmsg_type == RTM_NEWROUTE || n->nlmsg_type == RTM_DELROUTE) { + switch (n->nlmsg_type) { + case RTM_NEWROUTE: + case RTM_DELROUTE: { struct rtmsg *r = NLMSG_DATA(n); int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)); @@ -82,24 +84,28 @@ static int accept_msg(const struct sockaddr_nl *who, } } - if (n->nlmsg_type == RTM_NEWLINK || n->nlmsg_type == RTM_DELLINK) { + case RTM_NEWLINK: + case RTM_DELLINK: ll_remember_index(who, n, NULL); print_headers(fp, "[LINK]", ctrl); print_linkinfo(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) { + + case RTM_NEWADDR: + case RTM_DELADDR: print_headers(fp, "[ADDR]", ctrl); print_addrinfo(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWADDRLABEL || n->nlmsg_type == RTM_DELADDRLABEL) { + + case RTM_NEWADDRLABEL: + case RTM_DELADDRLABEL: print_headers(fp, "[ADDRLABEL]", ctrl); print_addrlabel(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWNEIGH || n->nlmsg_type == RTM_DELNEIGH || - n->nlmsg_type == RTM_GETNEIGH) { + + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + case RTM_GETNEIGH: if (preferred_family) { struct ndmsg *r = NLMSG_DATA(n); @@ -110,34 +116,41 @@ static int accept_msg(const struct sockaddr_nl *who, print_headers(fp, "[NEIGH]", ctrl); print_neigh(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWPREFIX) { + + case RTM_NEWPREFIX: print_headers(fp, "[PREFIX]", ctrl); print_prefix(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWRULE || n->nlmsg_type == RTM_DELRULE) { + + case RTM_NEWRULE: + case RTM_DELRULE: print_headers(fp, "[RULE]", ctrl); print_rule(who, n, arg); return 0; - } - if (n->nlmsg_type == RTM_NEWNETCONF) { + + case NLMSG_TSTAMP: + print_nlmsg_timestamp(fp, n); + return 0; + + case RTM_NEWNETCONF: print_headers(fp, "[NETCONF]", ctrl); print_netconf(who, ctrl, n, arg); return 0; - } - if (n->nlmsg_type == NLMSG_TSTAMP) { - print_nlmsg_timestamp(fp, n); - return 0; - } - if (n->nlmsg_type == RTM_NEWNSID || n->nlmsg_type == RTM_DELNSID) { + + case RTM_DELNSID: + case RTM_NEWNSID: print_headers(fp, "[NSID]", ctrl); print_nsid(who, n, arg); return 0; - } - if (n->nlmsg_type != NLMSG_ERROR && n->nlmsg_type != NLMSG_NOOP && - n->nlmsg_type != NLMSG_DONE) { - fprintf(fp, "Unknown message: type=0x%08x(%d) flags=0x%08x(%d)len=0x%08x(%d)\n", + + case NLMSG_ERROR: + case NLMSG_NOOP: + case NLMSG_DONE: + break; /* ignore */ + + default: + fprintf(stderr, + "Unknown message: type=0x%08x(%d) flags=0x%08x(%d) len=0x%08x(%d)\n", n->nlmsg_type, n->nlmsg_type, n->nlmsg_flags, n->nlmsg_flags, n->nlmsg_len, n->nlmsg_len); From e09d21f6752a02d1bec19d109e93b8e759627079 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 15 Aug 2018 14:29:42 -0700 Subject: [PATCH 02/30] ipmonitor: decode DELNETCONF message When device is deleted DELNETCONF is sent, but ipmonitor was unable to decode it. Signed-off-by: Stephen Hemminger --- ip/ipmonitor.c | 1 + ip/ipnetconf.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c index 5552d98e..a93b62cd 100644 --- a/ip/ipmonitor.c +++ b/ip/ipmonitor.c @@ -133,6 +133,7 @@ static int accept_msg(const struct sockaddr_nl *who, return 0; case RTM_NEWNETCONF: + case RTM_DELNETCONF: print_headers(fp, "[NETCONF]", ctrl); print_netconf(who, ctrl, n, arg); return 0; diff --git a/ip/ipnetconf.c b/ip/ipnetconf.c index 03f98ace..04c4d608 100644 --- a/ip/ipnetconf.c +++ b/ip/ipnetconf.c @@ -66,8 +66,10 @@ int print_netconf(const struct sockaddr_nl *who, struct rtnl_ctrl_data *ctrl, if (n->nlmsg_type == NLMSG_ERROR) return -1; - if (n->nlmsg_type != RTM_NEWNETCONF) { - fprintf(stderr, "Not RTM_NEWNETCONF: %08x %08x %08x\n", + + if (n->nlmsg_type != RTM_NEWNETCONF && + n->nlmsg_type != RTM_DELNETCONF) { + fprintf(stderr, "Not a netconf message: %08x %08x %08x\n", n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); return -1; @@ -91,6 +93,9 @@ int print_netconf(const struct sockaddr_nl *who, struct rtnl_ctrl_data *ctrl, return 0; open_json_object(NULL); + if (n->nlmsg_type == RTM_DELNETCONF) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + print_string(PRINT_ANY, "family", "%s ", family_name(ncm->ncm_family)); From 05758f5c7b357c53b53e16604bcdfb269fc68a13 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:53 +0200 Subject: [PATCH 03/30] man: bridge.8: Document -oneline option Copied the description from ip.8. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- man/man8/bridge.8 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index f6d228c5..1d10cb2b 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -24,7 +24,8 @@ bridge \- show / manipulate bridge addresses and devices \fB\-b\fR[\fIatch\fR] filename | \fB\-c\fR[\folor\fR] | \fB\-p\fR[\fIretty\fR] | -\fB\-j\fR[\fIson\fR] } +\fB\-j\fR[\fIson\fR] | +\fB\-o\fR[\fIneline\fr] } .ti -8 .BR "bridge link set" @@ -182,6 +183,18 @@ Output results in JavaScript Object Notation (JSON). .BR "\-p", " \-pretty" When combined with -j generate a pretty JSON output. +.TP +.BR "\-o", " \-oneline" +output each record on a single line, replacing line feeds +with the +.B '\e' +character. This is convenient when you want to count records +with +.BR wc (1) +or to +.BR grep (1) +the output. + .SH BRIDGE - COMMAND SYNTAX From f9ff0cd69c4514bc78657d33459655248c9e1357 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:54 +0200 Subject: [PATCH 04/30] bridge: trivial: Make help text consistent Change curly braces into brackets for -json option in help text to be consistent with the rest. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- bridge/bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/bridge.c b/bridge/bridge.c index 451d684e..b3cab717 100644 --- a/bridge/bridge.c +++ b/bridge/bridge.c @@ -42,7 +42,7 @@ static void usage(void) "where OBJECT := { link | fdb | mdb | vlan | monitor }\n" " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n" " -o[neline] | -t[imestamp] | -n[etns] name |\n" -" -c[ompressvlans] -color -p[retty] -j{son} }\n"); +" -c[ompressvlans] -color -p[retty] -j[son] }\n"); exit(-1); } From bb75b9bf2f3fa392807bf09ed41eef25b89e1e82 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:55 +0200 Subject: [PATCH 05/30] devlink: trivial: Make help text consistent Typically the part of the flag in brackets completes the leading part instead of repeating it. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- devlink/devlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devlink/devlink.c b/devlink/devlink.c index 784bb84b..519ee257 100644 --- a/devlink/devlink.c +++ b/devlink/devlink.c @@ -5426,7 +5426,7 @@ static void help(void) pr_err("Usage: devlink [ OPTIONS ] OBJECT { COMMAND | help }\n" " devlink [ -f[orce] ] -b[atch] filename\n" "where OBJECT := { dev | port | sb | monitor | dpipe | resource | region }\n" - " OPTIONS := { -V[ersion] | -n[no-nice-names] | -j[json] | -p[pretty] | -v[verbose] }\n"); + " OPTIONS := { -V[ersion] | -n[o-nice-names] | -j[son] | -p[retty] | -v[erbose] }\n"); } static int dl_cmd(struct dl *dl, int argc, char **argv) From 29b1430ba9aa9bceb42b07232f5138e511034a5a Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:56 +0200 Subject: [PATCH 06/30] man: devlink.8: Document -verbose option This was the only bit missing in comparison to devlink help text. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- man/man8/devlink.8 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/man/man8/devlink.8 b/man/man8/devlink.8 index ac61b6ad..360031f7 100644 --- a/man/man8/devlink.8 +++ b/man/man8/devlink.8 @@ -47,6 +47,10 @@ Generate JSON output. .BR "\-p" , " --pretty" When combined with -j generate a pretty JSON output. +.TP +.BR "\-v" , " --verbose" +Turn on verbose output. + .SS .I OBJECT From d94974bc91eb2aa2df4592140e1aa3ea0d3cddba Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:57 +0200 Subject: [PATCH 07/30] genl: Fix help text The '| help' part was misleading: In fact, 'genl help' does not work but 'genl help' does. Fix the help text to make that clear. In addition to that, list -Version and -help flags as well. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- genl/genl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/genl/genl.c b/genl/genl.c index 20ecb8b6..1940a23c 100644 --- a/genl/genl.c +++ b/genl/genl.c @@ -98,9 +98,9 @@ static void usage(void) __attribute__((noreturn)); static void usage(void) { - fprintf(stderr, "Usage: genl [ OPTIONS ] OBJECT | help }\n" + fprintf(stderr, "Usage: genl [ OPTIONS ] OBJECT [help] }\n" "where OBJECT := { ctrl etc }\n" - " OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] }\n"); + " OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -V[ersion] | -h[elp] }\n"); exit(-1); } From a486d25b9cbfc3469e30297495333d95a5576bdc Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:58 +0200 Subject: [PATCH 08/30] man: ifstat.8: Document --json and --pretty options Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- man/man8/ifstat.8 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/man/man8/ifstat.8 b/man/man8/ifstat.8 index 3ba0088d..8cd164dd 100644 --- a/man/man8/ifstat.8 +++ b/man/man8/ifstat.8 @@ -48,6 +48,14 @@ Report average over the last SECS seconds. .B \-z, \-\-zeros Show entries with zero activity. .TP +.B \-j, \-\-json +Display results in JSON format +.TP +.B \-p, \-\-pretty +If combined with +.BR \-\-json , +pretty print the output. +.TP .B \-x, \-\-extended=TYPE Show extended stats of TYPE. Supported types are: From 71170d854e96d2e7dff338f482cb23dfadbbd702 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:28:00 +0200 Subject: [PATCH 09/30] man: rtacct.8: Fix nstat options Add missing --pretty and --json options, correct --zero to --zeros and correct the mess around --scan/--interval including broken man page formatting. Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- man/man8/rtacct.8 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/man/man8/rtacct.8 b/man/man8/rtacct.8 index 01321e6d..ccdbf6ca 100644 --- a/man/man8/rtacct.8 +++ b/man/man8/rtacct.8 @@ -4,7 +4,7 @@ nstat, rtacct - network statistics tools. .SH SYNOPSIS -Usage: nstat [ -h?vVzrnasd:t: ] [ PATTERN [ PATTERN ] ] +Usage: nstat [ -h?vVzrnasd:t:jp ] [ PATTERN [ PATTERN ] ] .br Usage: rtacct [ -h?vVzrnasd:t: ] [ ListOfRealms ] @@ -21,7 +21,7 @@ Print help .B \-V, \-\-version Print version .TP -.B \-z, \-\-zero +.B \-z, \-\-zeros Dump zero counters too. By default they are not shown. .TP .B \-r, \-\-reset @@ -39,12 +39,16 @@ Do not update history, so that the next time you will see counters including val .B \-j, \-\-json Display results in JSON format. .TP -.B \-d, \-\-interval +.B \-p, \-\-pretty +When combined with +.BR \-\-json , +pretty print the output. +.TP +.B \-d, \-\-scan Run in daemon mode collecting statistics. is interval between measurements in seconds. .TP - +.B \-t, \-\-interval Time interval to average rates. Default value is 60 seconds. -.TP .SH SEE ALSO lnstat(8) From 6417c06b59b0731bcff4c0999661b256be6f52cb Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:28:01 +0200 Subject: [PATCH 10/30] rtmon: List options in help text Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- ip/rtmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ip/rtmon.c b/ip/rtmon.c index acc11df4..0e795f74 100644 --- a/ip/rtmon.c +++ b/ip/rtmon.c @@ -63,7 +63,9 @@ static int dump_msg2(const struct sockaddr_nl *who, static void usage(void) { - fprintf(stderr, "Usage: rtmon file FILE [ all | LISTofOBJECTS]\n"); + fprintf(stderr, "Usage: rtmon [ OPTIONS ] file FILE [ all | LISTofOBJECTS ]\n"); + fprintf(stderr, "OPTIONS := { -f[amily] { inet | inet6 | link | help } |\n" + " -4 | -6 | -0 | -V[ersion] }\n"); fprintf(stderr, "LISTofOBJECTS := [ link ] [ address ] [ route ]\n"); exit(-1); } From d559db725cfe033718d7bcfff01285c194a6e92d Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:28:02 +0200 Subject: [PATCH 11/30] man: ss.8: Describe --events option Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- man/man8/ss.8 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/man8/ss.8 b/man/man8/ss.8 index 28033d8f..7a6572b1 100644 --- a/man/man8/ss.8 +++ b/man/man8/ss.8 @@ -242,6 +242,9 @@ Print summary statistics. This option does not parse socket lists obtaining summary from various sources. It is useful when amount of sockets is so huge that parsing /proc/net/tcp is painful. .TP +.B \-E, \-\-events +Continually display sockets as they are destroyed +.TP .B \-Z, \-\-context As the .B \-p From 3e5f8e0ab6c1e49aae8249809394b3c40074848e Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Thu, 16 Aug 2018 10:28:13 -0700 Subject: [PATCH 12/30] genl: code cleanup Run through checkpatch and cleanup line wraps etc. Signed-off-by: Stephen Hemminger --- genl/genl.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/genl/genl.c b/genl/genl.c index 1940a23c..253c4450 100644 --- a/genl/genl.c +++ b/genl/genl.c @@ -26,12 +26,12 @@ #include "utils.h" #include "genl_utils.h" -int show_stats = 0; -int show_details = 0; -int show_raw = 0; +int show_stats; +int show_details; +int show_raw; static void *BODY; -static struct genl_util * genl_list; +static struct genl_util *genl_list; static int print_nofopt(const struct sockaddr_nl *who, struct nlmsghdr *n, @@ -44,8 +44,9 @@ static int print_nofopt(const struct sockaddr_nl *who, struct nlmsghdr *n, static int parse_nofopt(struct genl_util *f, int argc, char **argv) { if (argc) { - fprintf(stderr, "Unknown genl \"%s\", hence option \"%s\" " - "is unparsable\n", f->name, *argv); + fprintf(stderr, + "Unknown genl \"%s\", hence option \"%s\" is unparsable\n", + f->name, *argv); return -1; } @@ -98,9 +99,10 @@ static void usage(void) __attribute__((noreturn)); static void usage(void) { - fprintf(stderr, "Usage: genl [ OPTIONS ] OBJECT [help] }\n" - "where OBJECT := { ctrl etc }\n" - " OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -V[ersion] | -h[elp] }\n"); + fprintf(stderr, + "Usage: genl [ OPTIONS ] OBJECT [help] }\n" + "where OBJECT := { ctrl etc }\n" + " OPTIONS := { -s[tatistics] | -d[etails] | -r[aw] | -V[ersion] | -h[elp] }\n"); exit(-1); } @@ -122,19 +124,21 @@ int main(int argc, char **argv) } else if (matches(argv[1], "-help") == 0) { usage(); } else { - fprintf(stderr, "Option \"%s\" is unknown, try " - "\"genl -help\".\n", argv[1]); + fprintf(stderr, + "Option \"%s\" is unknown, try \"genl -help\".\n", + argv[1]); exit(-1); } argc--; argv++; } if (argc > 1) { + struct genl_util *a; int ret; - struct genl_util *a = NULL; + a = get_genl_kind(argv[1]); if (!a) { - fprintf(stderr,"bad genl %s\n", argv[1]); + fprintf(stderr, "bad genl %s\n", argv[1]); exit(-1); } From 644b9c238c2dde8b0b931d153fc6719e00ebfc6b Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 16 Aug 2018 12:27:59 +0200 Subject: [PATCH 13/30] ip: Add missing -M flag to help text Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- ip/ip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ip/ip.c b/ip/ip.c index 38eac5ec..72e858ee 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -55,7 +55,7 @@ static void usage(void) " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n" " -h[uman-readable] | -iec | -j[son] | -p[retty] |\n" " -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n" -" -4 | -6 | -I | -D | -B | -0 |\n" +" -4 | -6 | -I | -D | -M | -B | -0 |\n" " -l[oops] { maximum-addr-flush-attempts } | -br[ief] |\n" " -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |\n" " -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}\n"); From ff1ab8edf827f55b86c6487c90b7454975d52236 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Fri, 17 Aug 2018 18:38:45 +0200 Subject: [PATCH 14/30] Make colored output configurable Allow for -color={never,auto,always} to have colored output disabled, enabled only if stdout is a terminal or enabled regardless of stdout state. Signed-off-by: Phil Sutter Reviewed-by: David Ahern Signed-off-by: Stephen Hemminger --- bridge/bridge.c | 3 +-- include/color.h | 9 +++++++++ ip/ip.c | 3 +-- lib/color.c | 33 ++++++++++++++++++++++++++++++++- man/man8/bridge.8 | 13 +++++++++++-- man/man8/ip.8 | 13 +++++++++++-- man/man8/tc.8 | 13 +++++++++++-- tc/tc.c | 3 +-- 8 files changed, 77 insertions(+), 13 deletions(-) diff --git a/bridge/bridge.c b/bridge/bridge.c index b3cab717..663a35b2 100644 --- a/bridge/bridge.c +++ b/bridge/bridge.c @@ -173,8 +173,7 @@ main(int argc, char **argv) NEXT_ARG(); if (netns_switch(argv[1])) exit(-1); - } else if (matches(opt, "-color") == 0) { - ++color; + } else if (matches_color(opt, &color)) { } else if (matches(opt, "-compressvlans") == 0) { ++compress_vlans; } else if (matches(opt, "-force") == 0) { diff --git a/include/color.h b/include/color.h index 4f2c918d..a22a00c2 100644 --- a/include/color.h +++ b/include/color.h @@ -2,6 +2,8 @@ #ifndef __COLOR_H__ #define __COLOR_H__ 1 +#include + enum color_attr { COLOR_IFNAME, COLOR_MAC, @@ -12,8 +14,15 @@ enum color_attr { COLOR_NONE }; +enum color_opt { + COLOR_OPT_NEVER = 0, + COLOR_OPT_AUTO = 1, + COLOR_OPT_ALWAYS = 2 +}; + void enable_color(void); int check_enable_color(int color, int json); +bool matches_color(const char *arg, int *val); void set_color_palette(void); int color_fprintf(FILE *fp, enum color_attr attr, const char *fmt, ...); enum color_attr ifa_family_color(__u8 ifa_family); diff --git a/ip/ip.c b/ip/ip.c index 72e858ee..58c643df 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -283,8 +283,7 @@ int main(int argc, char **argv) exit(-1); } rcvbuf = size; - } else if (matches(opt, "-color") == 0) { - ++color; + } else if (matches_color(opt, &color)) { } else if (matches(opt, "-help") == 0) { usage(); } else if (matches(opt, "-netns") == 0) { diff --git a/lib/color.c b/lib/color.c index edf96e5c..9c902358 100644 --- a/lib/color.c +++ b/lib/color.c @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include "color.h" +#include "utils.h" enum color { C_RED, @@ -79,13 +81,42 @@ void enable_color(void) int check_enable_color(int color, int json) { - if (color && !json) { + if (json || color == COLOR_OPT_NEVER) + return 1; + + if (color == COLOR_OPT_ALWAYS || isatty(fileno(stdout))) { enable_color(); return 0; } return 1; } +bool matches_color(const char *arg, int *val) +{ + char *dup, *p; + + if (!val) + return false; + + dup = strdupa(arg); + p = strchrnul(dup, '='); + if (*p) + *(p++) = '\0'; + + if (matches(dup, "-color")) + return false; + + if (*p == '\0' || !strcmp(p, "always")) + *val = COLOR_OPT_ALWAYS; + else if (!strcmp(p, "auto")) + *val = COLOR_OPT_AUTO; + else if (!strcmp(p, "never")) + *val = COLOR_OPT_NEVER; + else + return false; + return true; +} + void set_color_palette(void) { char *p = getenv("COLORFGBG"); diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index 1d10cb2b..53cd3d0a 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -172,8 +172,17 @@ If there were any errors during execution of the commands, the application return code will be non zero. .TP -.BR "\-c" , " -color" -Use color output. +.BR \-c [ color ][ = { always | auto | never } +Configure color output. If parameter is omitted or +.BR always , +color output is enabled regardless of stdout state. If parameter is +.BR auto , +stdout is checked to be a terminal before enabling color output. If parameter is +.BR never , +color output is disabled. If specified multiple times, the last one takes +precedence. This flag is ignored if +.B \-json +is also given. .TP .BR "\-j", " \-json" diff --git a/man/man8/ip.8 b/man/man8/ip.8 index 0087d18b..1d358879 100644 --- a/man/man8/ip.8 +++ b/man/man8/ip.8 @@ -187,8 +187,17 @@ to executes specified command over all objects, it depends if command supports this option. .TP -.BR "\-c" , " -color" -Use color output. +.BR \-c [ color ][ = { always | auto | never } +Configure color output. If parameter is omitted or +.BR always , +color output is enabled regardless of stdout state. If parameter is +.BR auto , +stdout is checked to be a terminal before enabling color output. If parameter is +.BR never , +color output is disabled. If specified multiple times, the last one takes +precedence. This flag is ignored if +.B \-json +is also given. .TP .BR "\-t" , " \-timestamp" diff --git a/man/man8/tc.8 b/man/man8/tc.8 index fd33f9b2..f98398a3 100644 --- a/man/man8/tc.8 +++ b/man/man8/tc.8 @@ -755,8 +755,17 @@ option was specified. Classes can be filtered only by option. .TP -.BR "\ -color" -Use color output. +.BR \-c [ color ][ = { always | auto | never } +Configure color output. If parameter is omitted or +.BR always , +color output is enabled regardless of stdout state. If parameter is +.BR auto , +stdout is checked to be a terminal before enabling color output. If parameter is +.BR never , +color output is disabled. If specified multiple times, the last one takes +precedence. This flag is ignored if +.B \-json +is also given. .TP .BR "\-j", " \-json" diff --git a/tc/tc.c b/tc/tc.c index 4c7a128c..4b28e9b1 100644 --- a/tc/tc.c +++ b/tc/tc.c @@ -496,8 +496,7 @@ int main(int argc, char **argv) matches(argv[1], "-conf") == 0) { NEXT_ARG(); conf_file = argv[1]; - } else if (matches(argv[1], "-color") == 0) { - ++color; + } else if (matches_color(argv[1], &color)) { } else if (matches(argv[1], "-timestamp") == 0) { timestamp++; } else if (matches(argv[1], "-tshort") == 0) { From 515a766cd29bbed8a180733e93bc8590b001cad7 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Fri, 17 Aug 2018 18:38:46 +0200 Subject: [PATCH 15/30] lib: Make check_enable_color() return boolean As suggested, turn return code into true/false although it's not checked anywhere yet. Fixes: 4d82962cccc6a ("Merge common code for conditionally colored output") Signed-off-by: Phil Sutter Signed-off-by: Stephen Hemminger --- include/color.h | 2 +- lib/color.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/color.h b/include/color.h index a22a00c2..e30f28c5 100644 --- a/include/color.h +++ b/include/color.h @@ -21,7 +21,7 @@ enum color_opt { }; void enable_color(void); -int check_enable_color(int color, int json); +bool check_enable_color(int color, int json); bool matches_color(const char *arg, int *val); void set_color_palette(void); int color_fprintf(FILE *fp, enum color_attr attr, const char *fmt, ...); diff --git a/lib/color.c b/lib/color.c index 9c902358..eaf69e74 100644 --- a/lib/color.c +++ b/lib/color.c @@ -79,16 +79,16 @@ void enable_color(void) set_color_palette(); } -int check_enable_color(int color, int json) +bool check_enable_color(int color, int json) { if (json || color == COLOR_OPT_NEVER) - return 1; + return false; if (color == COLOR_OPT_ALWAYS || isatty(fileno(stdout))) { enable_color(); - return 0; + return true; } - return 1; + return false; } bool matches_color(const char *arg, int *val) From 84fb55ede10940938d8a4b06e9c98204ddec3c6f Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Mon, 20 Aug 2018 15:58:50 -0700 Subject: [PATCH 16/30] ip: drop extern from function prototype Signed-off-by: Stephen Hemminger --- ip/ip_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ip/ip_common.h b/ip/ip_common.h index 4d3227cb..200be5e2 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -82,7 +82,7 @@ int do_netns(int argc, char **argv); int do_xfrm(int argc, char **argv); int do_ipl2tp(int argc, char **argv); int do_ipfou(int argc, char **argv); -extern int do_ipila(int argc, char **argv); +int do_ipila(int argc, char **argv); int do_tcp_metrics(int argc, char **argv); int do_ipnetconf(int argc, char **argv); int do_iptoken(int argc, char **argv); From cf7fe23859cd53740febce137d29f080efefd64c Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Mon, 20 Aug 2018 16:00:38 -0700 Subject: [PATCH 17/30] bridge: drop extern from function prototypes Signed-off-by: Stephen Hemminger --- bridge/br_common.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bridge/br_common.h b/bridge/br_common.h index 2f1cb8fd..7bf15e95 100644 --- a/bridge/br_common.h +++ b/bridge/br_common.h @@ -6,20 +6,20 @@ #define MDB_RTR_RTA(r) \ ((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(__u32)))) -extern void print_vlan_info(FILE *fp, struct rtattr *tb); -extern int print_linkinfo(const struct sockaddr_nl *who, +void print_vlan_info(FILE *fp, struct rtattr *tb); +int print_linkinfo(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int print_fdb(const struct sockaddr_nl *who, +int print_fdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int print_mdb(const struct sockaddr_nl *who, +int print_mdb(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int do_fdb(int argc, char **argv); -extern int do_mdb(int argc, char **argv); -extern int do_monitor(int argc, char **argv); -extern int do_vlan(int argc, char **argv); -extern int do_link(int argc, char **argv); +int do_fdb(int argc, char **argv); +int do_mdb(int argc, char **argv); +int do_monitor(int argc, char **argv); +int do_vlan(int argc, char **argv); +int do_link(int argc, char **argv); extern int preferred_family; extern int show_stats; From 51070e8f18771bec12e3702e1ce6f6eb6503a503 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Mon, 20 Aug 2018 16:01:01 -0700 Subject: [PATCH 18/30] genl: drop extern from function prototypes Signed-off-by: Stephen Hemminger --- genl/genl_utils.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/genl/genl_utils.h b/genl/genl_utils.h index 6e6f4450..3de0da34 100644 --- a/genl/genl_utils.h +++ b/genl/genl_utils.h @@ -10,9 +10,10 @@ struct genl_util struct genl_util *next; char name[16]; int (*parse_genlopt)(struct genl_util *fu, int argc, char **argv); - int (*print_genlopt)(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); + int (*print_genlopt)(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg); }; -extern int genl_ctrl_resolve_family(const char *family); +int genl_ctrl_resolve_family(const char *family); #endif From a8e9f4ae14eac7d0faffcdd508a38d5051f0e857 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Mon, 20 Aug 2018 16:01:31 -0700 Subject: [PATCH 19/30] tc: drop extern from function prototypes Signed-off-by: Stephen Hemminger --- tc/m_ematch.h | 35 +++++++++++++++-------------------- tc/m_pedit.h | 31 +++++++++++++++---------------- tc/tc_common.h | 30 +++++++++++++++--------------- tc/tc_red.h | 7 ++++--- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/tc/m_ematch.h b/tc/m_ematch.h index f634f191..ff02d7ac 100644 --- a/tc/m_ematch.h +++ b/tc/m_ematch.h @@ -12,17 +12,16 @@ #define EMATCHKINDSIZ 16 -struct bstr -{ +struct bstr { char *data; unsigned int len; int quoted; struct bstr *next; }; -extern struct bstr * bstr_alloc(const char *text); +struct bstr *bstr_alloc(const char *text); -static inline struct bstr * bstr_new(char *data, unsigned int len) +static inline struct bstr *bstr_new(char *data, unsigned int len) { struct bstr *b = calloc(1, sizeof(*b)); @@ -35,7 +34,7 @@ static inline struct bstr * bstr_new(char *data, unsigned int len) return b; } -static inline int bstrcmp(struct bstr *b, const char *text) +static inline int bstrcmp(const struct bstr *b, const char *text) { int len = strlen(text); int d = b->len - len; @@ -51,12 +50,10 @@ static inline struct bstr *bstr_next(struct bstr *b) return b->next; } -extern unsigned long bstrtoul(const struct bstr *b); -extern void bstr_print(FILE *fd, const struct bstr *b, int ascii); +unsigned long bstrtoul(const struct bstr *b); +void bstr_print(FILE *fd, const struct bstr *b, int ascii); - -struct ematch -{ +struct ematch { struct bstr *args; int index; int inverted; @@ -66,7 +63,7 @@ struct ematch struct ematch *next; }; -static inline struct ematch * new_ematch(struct bstr *args, int inverted) +static inline struct ematch *new_ematch(struct bstr *args, int inverted) { struct ematch *e = calloc(1, sizeof(*e)); @@ -79,14 +76,12 @@ static inline struct ematch * new_ematch(struct bstr *args, int inverted) return e; } -extern void print_ematch_tree(const struct ematch *tree); +void print_ematch_tree(const struct ematch *tree); - -struct ematch_util -{ +struct ematch_util { char kind[EMATCHKINDSIZ]; int kind_num; - int (*parse_eopt)(struct nlmsghdr *,struct tcf_ematch_hdr *, + int (*parse_eopt)(struct nlmsghdr *, struct tcf_ematch_hdr *, struct bstr *); int (*parse_eopt_argv)(struct nlmsghdr *, struct tcf_ematch_hdr *, int, char **); @@ -95,7 +90,7 @@ struct ematch_util struct ematch_util *next; }; -static inline int parse_layer(struct bstr *b) +static inline int parse_layer(const struct bstr *b) { if (*((char *) b->data) == 'l') return TCF_LAYER_LINK; @@ -107,9 +102,9 @@ static inline int parse_layer(struct bstr *b) return INT_MAX; } -extern int em_parse_error(int err, struct bstr *args, struct bstr *carg, +int em_parse_error(int err, struct bstr *args, struct bstr *carg, struct ematch_util *, char *fmt, ...); -extern int print_ematch(FILE *, const struct rtattr *); -extern int parse_ematch(int *, char ***, int, struct nlmsghdr *); +int print_ematch(FILE *, const struct rtattr *); +int parse_ematch(int *, char ***, int, struct nlmsghdr *); #endif diff --git a/tc/m_pedit.h b/tc/m_pedit.h index a8b06958..b6b274bd 100644 --- a/tc/m_pedit.h +++ b/tc/m_pedit.h @@ -71,23 +71,22 @@ struct m_pedit_util { struct m_pedit_key *tkey); }; -extern int pack_key(struct m_pedit_sel *sel, struct m_pedit_key *tkey); -extern int pack_key32(__u32 retain, struct m_pedit_sel *sel, - struct m_pedit_key *tkey); -extern int pack_key16(__u32 retain, struct m_pedit_sel *sel, - struct m_pedit_key *tkey); -extern int pack_key8(__u32 retain, struct m_pedit_sel *sel, +int pack_key(struct m_pedit_sel *sel, struct m_pedit_key *tkey); +int pack_key32(__u32 retain, struct m_pedit_sel *sel, + struct m_pedit_key *tkey); +int pack_key16(__u32 retain, struct m_pedit_sel *sel, + struct m_pedit_key *tkey); +int pack_key8(__u32 retain, struct m_pedit_sel *sel, struct m_pedit_key *tkey); -extern int parse_val(int *argc_p, char ***argv_p, __u32 *val, int type); -extern 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); -extern int parse_offset(int *argc_p, char ***argv_p, - struct m_pedit_sel *sel, struct m_pedit_key *tkey); +int parse_val(int *argc_p, char ***argv_p, __u32 *val, int type); +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 parse_offset(int *argc_p, char ***argv_p, + struct m_pedit_sel *sel, struct m_pedit_key *tkey); int parse_pedit(struct action_util *a, int *argc_p, char ***argv_p, int tca_id, struct nlmsghdr *n); -extern int print_pedit(struct action_util *au, FILE *f, struct rtattr *arg); -extern int pedit_print_xstats(struct action_util *au, FILE *f, - struct rtattr *xstats); - +int print_pedit(struct action_util *au, FILE *f, struct rtattr *arg); +int pedit_print_xstats(struct action_util *au, FILE *f, + struct rtattr *xstats); #endif diff --git a/tc/tc_common.h b/tc/tc_common.h index 272d1727..371ca7d0 100644 --- a/tc/tc_common.h +++ b/tc/tc_common.h @@ -5,26 +5,26 @@ extern struct rtnl_handle rth; -extern int do_qdisc(int argc, char **argv); -extern int do_class(int argc, char **argv); -extern int do_filter(int argc, char **argv, void *buf, size_t buflen); -extern int do_chain(int argc, char **argv, void *buf, size_t buflen); -extern int do_action(int argc, char **argv, void *buf, size_t buflen); -extern int do_tcmonitor(int argc, char **argv); -extern int do_exec(int argc, char **argv); +int do_qdisc(int argc, char **argv); +int do_class(int argc, char **argv); +int do_filter(int argc, char **argv, void *buf, size_t buflen); +int do_chain(int argc, char **argv, void *buf, size_t buflen); +int do_action(int argc, char **argv, void *buf, size_t buflen); +int do_tcmonitor(int argc, char **argv); +int do_exec(int argc, char **argv); -extern int print_action(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int print_filter(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int print_qdisc(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern int print_class(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); -extern void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta); +int print_action(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +int print_filter(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +int print_qdisc(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +int print_class(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +void print_size_table(FILE *fp, const char *prefix, struct rtattr *rta); struct tc_estimator; -extern int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est); +int parse_estimator(int *p_argc, char ***p_argv, struct tc_estimator *est); struct tc_sizespec; -extern int parse_size_table(int *p_argc, char ***p_argv, struct tc_sizespec *s); -extern int check_size_table_opts(struct tc_sizespec *s); +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; diff --git a/tc/tc_red.h b/tc/tc_red.h index 88fba58b..6c6e6b03 100644 --- a/tc/tc_red.h +++ b/tc/tc_red.h @@ -2,8 +2,9 @@ #ifndef _TC_RED_H_ #define _TC_RED_H_ 1 -extern int tc_red_eval_P(unsigned qmin, unsigned qmax, double prob); -extern int tc_red_eval_ewma(unsigned qmin, unsigned burst, unsigned avpkt); -extern int tc_red_eval_idle_damping(int wlog, unsigned avpkt, unsigned bandwidth, __u8 *sbuf); +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); #endif From 10193649642ad549796c870fa2a252c3a4fee69f Mon Sep 17 00:00:00 2001 From: Stefan Bader Date: Wed, 22 Aug 2018 10:31:38 +0200 Subject: [PATCH 20/30] testsuite: Handle large number of kernel options Once there are more than a certain number of kernel config options set (this happened for us with kernel 4.17), the method of passing those as command line arguments exceeds the maximum number of arguments the shell supports. This causes the whole testsuite to fail. Instead, create a temporary file and modify its contents so that the config option variables are exported. Then this file can be sourced in before running the tests. Signed-off-by: Stefan Bader Acked-by: Luca Boccassi Signed-off-by: Stephen Hemminger --- testsuite/Makefile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/testsuite/Makefile b/testsuite/Makefile index 8fcbc557..f9f3b198 100644 --- a/testsuite/Makefile +++ b/testsuite/Makefile @@ -14,15 +14,13 @@ TESTS_DIR := $(dir $(TESTS)) IPVERS := $(filter-out iproute2/Makefile,$(wildcard iproute2/*)) +KENVFN := $(shell mktemp /tmp/tc_testkenv.XXXXXX) ifneq (,$(wildcard /proc/config.gz)) - KENV := $(shell cat /proc/config.gz | gunzip | grep ^CONFIG) + KCPATH := /proc/config.gz else KVER := $(shell uname -r) KCPATHS := /lib/modules/$(KVER)/config /boot/config-$(KVER) KCPATH := $(firstword $(wildcard $(KCPATHS))) -ifneq (,$(KCPATH)) - KENV := $(shell cat ${KCPATH} | grep ^CONFIG) -endif endif .PHONY: compile listtests alltests configure $(TESTS) @@ -59,14 +57,22 @@ endif mkdir -p $(RESULTS_DIR)/$$d; \ done + @if [ "$(KCPATH)" = "/proc/config.gz" ]; then \ + gunzip -c $(KCPATH) >$(KENVFN); \ + elif [ "$(KCPATH)" != "" ]; then \ + cat $(KCPATH) >$(KENVFN); \ + fi + @sed -i -e 's/^CONFIG_/export CONFIG_/' $(KENVFN) + @for i in $(IPVERS); do \ o=`echo $$i | sed -e 's/iproute2\///'`; \ echo -n "Running $@ [$$o/`uname -r`]: "; \ TMP_ERR=`mktemp /tmp/tc_testsuite.XXXXXX`; \ TMP_OUT=`mktemp /tmp/tc_testsuite.XXXXXX`; \ + . $(KENVFN); \ STD_ERR="$$TMP_ERR" STD_OUT="$$TMP_OUT" \ TC="$$i/tc/tc" IP="$$i/ip/ip" SS=$$i/misc/ss DEV="$(DEV)" IPVER="$@" SNAME="$$i" \ - ERRF="$(RESULTS_DIR)/$@.$$o.err" $(KENV) $(PREFIX) tests/$@ > $(RESULTS_DIR)/$@.$$o.out; \ + ERRF="$(RESULTS_DIR)/$@.$$o.err" $(PREFIX) tests/$@ > $(RESULTS_DIR)/$@.$$o.out; \ if [ "$$?" = "127" ]; then \ echo "SKIPPED"; \ elif [ -e "$(RESULTS_DIR)/$@.$$o.err" ]; then \ From ad23e152b85a92b30a944cb70f4fbc8d4933ac29 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Aug 2018 19:09:01 +0100 Subject: [PATCH 21/30] testsuite: remove all temp files and implement make clean Some generated test files were not removed, including one executable in the testsuite/tools directory. Ensure make clean from the top level directory works for the testsuite subdirs too, and that all the files are removed. Signed-off-by: Luca Boccassi Signed-off-by: Stephen Hemminger --- Makefile | 2 +- testsuite/Makefile | 3 +++ testsuite/tools/Makefile | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 651d2a50..ea2f797c 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ snapshot: > include/SNAPSHOT.h clean: - @for i in $(SUBDIRS); \ + @for i in $(SUBDIRS) testsuite; \ do $(MAKE) $(MFLAGS) -C $$i clean; done clobber: diff --git a/testsuite/Makefile b/testsuite/Makefile index f9f3b198..fa4849c7 100644 --- a/testsuite/Makefile +++ b/testsuite/Makefile @@ -41,6 +41,9 @@ alltests: $(TESTS) clean: @echo "Removing $(RESULTS_DIR) dir ..." @rm -rf $(RESULTS_DIR) + @rm -f iproute2/iproute2-this + @rm -f tests/ip/link/dev_wo_vf_rate.nl + $(MAKE) -C tools clean distclean: clean echo "Entering iproute2" && cd iproute2 && $(MAKE) distclean && cd ..; diff --git a/testsuite/tools/Makefile b/testsuite/tools/Makefile index f2cdc980..f0ce4ee2 100644 --- a/testsuite/tools/Makefile +++ b/testsuite/tools/Makefile @@ -1,3 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 generate_nlmsg: generate_nlmsg.c ../../lib/libnetlink.c $(CC) -o $@ $^ + +clean: + rm -f generate_nlmsg From 012895ce4eda26a65d2d7cced8cbb0f433365927 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Aug 2018 19:09:02 +0100 Subject: [PATCH 22/30] testsuite: let make compile build the netlink helper The generate_nlmsg binary is required but make -C testsuite compile does not build it. Add the necessary includes and C*FLAGS to the tools Makefile and have the compile target build it. Signed-off-by: Luca Boccassi Signed-off-by: Stephen Hemminger --- testsuite/Makefile | 1 + testsuite/tools/Makefile | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/testsuite/Makefile b/testsuite/Makefile index fa4849c7..39bf1d4e 100644 --- a/testsuite/Makefile +++ b/testsuite/Makefile @@ -30,6 +30,7 @@ configure: compile: configure echo "Entering iproute2" && cd iproute2 && $(MAKE) && cd ..; + $(MAKE) -C tools listtests: @for t in $(TESTS); do \ diff --git a/testsuite/tools/Makefile b/testsuite/tools/Makefile index f0ce4ee2..c936af71 100644 --- a/testsuite/tools/Makefile +++ b/testsuite/tools/Makefile @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 +include ../../config.mk + generate_nlmsg: generate_nlmsg.c ../../lib/libnetlink.c - $(CC) -o $@ $^ + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDLIBS) $(EXTRA_CFLAGS) -I../../include -include../../include/uapi/linux/netlink.h -o $@ $^ clean: rm -f generate_nlmsg From 88ecd4873b8fabf84006f8122f8533f39bc79ba4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 22 Aug 2018 19:09:03 +0100 Subject: [PATCH 23/30] testsuite: run dmesg with sudo Some distributions like Debian nowadays restrict the dmesg command to root-only. Run it with sudo in the testsuite. Signed-off-by: Luca Boccassi Signed-off-by: Stephen Hemminger --- testsuite/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/Makefile b/testsuite/Makefile index 39bf1d4e..d1ac997d 100644 --- a/testsuite/Makefile +++ b/testsuite/Makefile @@ -85,5 +85,5 @@ endif echo "PASS"; \ fi; \ rm "$$TMP_ERR" "$$TMP_OUT"; \ - dmesg > $(RESULTS_DIR)/$@.$$o.dmesg; \ + sudo dmesg > $(RESULTS_DIR)/$@.$$o.dmesg; \ done From 6526e604cfe48b93b78f331b3bdd4d672a3d6e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toke=20H=C3=B8iland-J=C3=B8rgensen?= Date: Thu, 23 Aug 2018 12:05:05 +0200 Subject: [PATCH 24/30] q_cake: Add description of the tc filter override mechanism to man page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since CAKE now has three different settings that can be overridden by tc filters (priority and host and flow hashes), documenting how they work is probably a good idea. Signed-off-by: Toke Høiland-Jørgensen Signed-off-by: Stephen Hemminger --- man/man8/tc-cake.8 | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/man/man8/tc-cake.8 b/man/man8/tc-cake.8 index c1e751d6..c62e5547 100644 --- a/man/man8/tc-cake.8 +++ b/man/man8/tc-cake.8 @@ -569,6 +569,61 @@ possible latency. At link speeds higher than 10 Gbps, setting the no-split-gso parameter can increase the maximum achievable throughput by retaining the full GSO packets. +.SH OVERRIDING CLASSIFICATION WITH TC FILTERS + +CAKE supports overriding of its internal classification of packets through the +tc filter mechanism. Packets can be assigned to different priority tins by +setting the +.B priority +field on the skb, and the flow hashing can be overridden by setting the +.B classid +parameter. + +.PP +.B Tin override + +.br + To assign a priority tin, the major number of the priority field needs +to match the qdisc handle of the cake instance; if it does, the minor number +will be interpreted as the tin index. For example, to classify all ICMP packets +as 'bulk', the following filter can be used: + +.br + # tc qdisc replace dev eth0 handle 1: root cake diffserv3 + # tc filter add dev eth0 parent 1: protocol ip prio 1 \\ + u32 match icmp type 0 0 action skbedit priority 1:1 + +.PP +.B Flow hash override + +.br + To override flow hashing, the classid can be set. CAKE will interpret +the major number of the classid as the host hash used in host isolation mode, +and the minor number as the flow hash used for flow-based queueing. One or both +of those can be set, and will be used if the relevant flow isolation parameter +is set (i.e., the major number will be ignored if CAKE is not configured in +hosts mode, and the minor number will be ignored if CAKE is not configured in +flows mode). + +.br +This example will assign all ICMP packets to the first queue: + +.br + # tc qdisc replace dev eth0 handle 1: root cake + # tc filter add dev eth0 parent 1: protocol ip prio 1 \\ + u32 match icmp type 0 0 classid 0:1 + +.br +If only one of the host and flow overrides is set, CAKE will compute the other +hash from the packet as normal. Note, however, that the host isolation mode +works by assigning a host ID to the flow queue; so if overriding both host and +flow, the same flow cannot have more than one host assigned. In addition, it is +not possible to assign different source and destination host IDs through the +override mechanism; if a host ID is assigned, it will be used as both source and +destination host. + + + .SH EXAMPLES # tc qdisc delete root dev eth0 .br From 1a75322c5a8dee6e1ed0017d76c87ef85c505b98 Mon Sep 17 00:00:00 2001 From: Stefan Bader Date: Tue, 28 Aug 2018 16:27:29 +0200 Subject: [PATCH 25/30] iprule: Fix destination prefix output When adding support for JSON output the new code for printing the destination prefix adds a stray blank character before the bitmask. This causes some user-space parsing to fail. Current output: ...: from x.x.x.x/l to y.y.y.y /l Previous output: ...: from x.x.x.x/l to y.y.y.y/l Fixes: 0dd4ccc5 "iprule: add json support" Signed-off-by: Stefan Bader Acked-by: Luca Boccassi Signed-off-by: Stephen Hemminger --- ip/iprule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ip/iprule.c b/ip/iprule.c index 8b942143..744d6d88 100644 --- a/ip/iprule.c +++ b/ip/iprule.c @@ -239,7 +239,7 @@ int print_rule(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) print_string(PRINT_FP, NULL, "to ", NULL); print_color_string(PRINT_ANY, ifa_family_color(frh->family), - "dst", "%s ", dst); + "dst", "%s", dst); if (frh->dst_len != host_len) print_uint(PRINT_ANY, "dstlen", "/%u ", frh->dst_len); else From 0bab7630e38863d3d2a5ddaeabf8745c4258a1a9 Mon Sep 17 00:00:00 2001 From: Cong Wang Date: Wed, 29 Aug 2018 10:09:27 -0700 Subject: [PATCH 26/30] ss: add UNIX_DIAG_VFS and UNIX_DIAG_ICONS for unix sockets UNIX_DIAG_VFS and UNIX_DIAG_ICONS are never used by ss, make them available in ss -e output. Cc: Stephen Hemminger Signed-off-by: Cong Wang Signed-off-by: Stephen Hemminger --- misc/ss.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/misc/ss.c b/misc/ss.c index 41e7762b..b2c634c8 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -3604,6 +3605,21 @@ static int unix_show_sock(const struct sockaddr_nl *addr, struct nlmsghdr *nlh, out(" %c-%c", mask & 1 ? '-' : '<', mask & 2 ? '-' : '>'); } + if (tb[UNIX_DIAG_VFS]) { + struct unix_diag_vfs *uv = RTA_DATA(tb[UNIX_DIAG_VFS]); + + out(" ino:%u dev:%u/%u", uv->udiag_vfs_ino, major(uv->udiag_vfs_dev), + minor(uv->udiag_vfs_dev)); + } + if (tb[UNIX_DIAG_ICONS]) { + int len = RTA_PAYLOAD(tb[UNIX_DIAG_ICONS]); + __u32 *peers = RTA_DATA(tb[UNIX_DIAG_ICONS]); + int i; + + out(" peers:"); + for (i = 0; i < len / sizeof(__u32); i++) + out(" %u", peers[i]); + } } return 0; @@ -3641,6 +3657,8 @@ static int unix_show_netlink(struct filter *f) req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN; if (show_mem) req.r.udiag_show |= UDIAG_SHOW_MEMINFO; + if (show_details) + req.r.udiag_show |= UDIAG_SHOW_VFS | UDIAG_SHOW_ICONS; return handle_netlink_request(f, &req.nlh, sizeof(req), unix_show_sock); } From 0ebb420929dd18562a1f76e9d09015719b913f75 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Thu, 30 Aug 2018 07:55:49 -0700 Subject: [PATCH 27/30] uapi: update bpf headers Signed-off-by: Stephen Hemminger --- include/uapi/linux/bpf.h | 56 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 4bbe7e5d..8eb284d2 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -126,6 +126,7 @@ enum bpf_map_type { BPF_MAP_TYPE_XSKMAP, BPF_MAP_TYPE_SOCKHASH, BPF_MAP_TYPE_CGROUP_STORAGE, + BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, }; enum bpf_prog_type { @@ -150,6 +151,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_PROG_TYPE_LIRC_MODE2, + BPF_PROG_TYPE_SK_REUSEPORT, }; enum bpf_attach_type { @@ -2091,6 +2093,24 @@ union bpf_attr { * Return * The id is returned or 0 in case the id could not be retrieved. * + * u64 bpf_skb_ancestor_cgroup_id(struct sk_buff *skb, int ancestor_level) + * Description + * Return id of cgroup v2 that is ancestor of cgroup associated + * with the *skb* at the *ancestor_level*. The root cgroup is at + * *ancestor_level* zero and each step down the hierarchy + * increments the level. If *ancestor_level* == level of cgroup + * associated with *skb*, then return value will be same as that + * of **bpf_skb_cgroup_id**\ (). + * + * The helper is useful to implement policies based on cgroups + * that are upper in hierarchy than immediate cgroup associated + * with *skb*. + * + * The format of returned id and helper limitations are same as in + * **bpf_skb_cgroup_id**\ (). + * Return + * The id is returned or 0 in case the id could not be retrieved. + * * u64 bpf_get_current_cgroup_id(void) * Return * A 64-bit integer containing the current cgroup id based @@ -2113,6 +2133,14 @@ union bpf_attr { * the shared data. * Return * Pointer to the local storage area. + * + * int bpf_sk_select_reuseport(struct sk_reuseport_md *reuse, struct bpf_map *map, void *key, u64 flags) + * Description + * Select a SO_REUSEPORT sk from a BPF_MAP_TYPE_REUSEPORT_ARRAY map + * It checks the selected sk is matching the incoming + * request in the skb. + * Return + * 0 on success, or a negative error in case of failure. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -2196,7 +2224,9 @@ union bpf_attr { FN(rc_keydown), \ FN(skb_cgroup_id), \ FN(get_current_cgroup_id), \ - FN(get_local_storage), + FN(get_local_storage), \ + FN(sk_select_reuseport), \ + FN(skb_ancestor_cgroup_id), /* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call @@ -2413,6 +2443,30 @@ struct sk_msg_md { __u32 local_port; /* stored in host byte order */ }; +struct sk_reuseport_md { + /* + * Start of directly accessible data. It begins from + * the tcp/udp header. + */ + void *data; + void *data_end; /* End of directly accessible data */ + /* + * Total length of packet (starting from the tcp/udp header). + * Note that the directly accessible bytes (data_end - data) + * could be less than this "len". Those bytes could be + * indirectly read by a helper "bpf_skb_load_bytes()". + */ + __u32 len; + /* + * Eth protocol in the mac header (network byte order). e.g. + * ETH_P_IP(0x0800) and ETH_P_IPV6(0x86DD) + */ + __u32 eth_protocol; + __u32 ip_protocol; /* IP protocol. e.g. IPPROTO_TCP, IPPROTO_UDP */ + __u32 bind_inany; /* Is sock bound to an INANY address? */ + __u32 hash; /* A hash of the packet 4 tuples */ +}; + #define BPF_TAG_SIZE 8 struct bpf_prog_info { From a5aaca9be223ed9e548820da6374a7208adcf558 Mon Sep 17 00:00:00 2001 From: Mahesh Bandewar Date: Wed, 22 Aug 2018 18:01:34 -0700 Subject: [PATCH 28/30] ipmaddr: use preferred_family when given When creating socket() AF_INET is used irrespective of the family that is given at the command-line (with -4, -6, or -0). This change will open the socket with the preferred family. Signed-off-by: Mahesh Bandewar Signed-off-by: Stephen Hemminger --- ip/ipmaddr.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ip/ipmaddr.c b/ip/ipmaddr.c index a4849902..abf83784 100644 --- a/ip/ipmaddr.c +++ b/ip/ipmaddr.c @@ -289,6 +289,7 @@ static int multiaddr_list(int argc, char **argv) static int multiaddr_modify(int cmd, int argc, char **argv) { struct ifreq ifr = {}; + int family; int fd; if (cmd == RTM_NEWADDR) @@ -324,7 +325,17 @@ static int multiaddr_modify(int cmd, int argc, char **argv) exit(-1); } - fd = socket(AF_INET, SOCK_DGRAM, 0); + switch (preferred_family) { + case AF_INET6: + case AF_PACKET: + case AF_INET: + family = preferred_family; + break; + default: + family = AF_INET; + } + + fd = socket(family, SOCK_DGRAM, 0); if (fd < 0) { perror("Cannot create socket"); exit(1); From 5d5586b0588969c9e9ff28da6cd558bfb5f68d9b Mon Sep 17 00:00:00 2001 From: Mahesh Bandewar Date: Wed, 22 Aug 2018 18:01:37 -0700 Subject: [PATCH 29/30] iproute: make clang happy These are primarily fixes for "string is not string literal" warnings / errors (with -Werror -Wformat-nonliteral). This should be a no-op change. I had to replace couple of print helper functions with the code they call as it was becoming harder to eliminate these warnings, however these helpers were used only at couple of places, so no major change as such. Signed-off-by: Mahesh Bandewar Signed-off-by: Stephen Hemminger --- include/json_writer.h | 3 +-- ip/iplink_can.c | 19 ++++++++++++------- lib/color.c | 1 + lib/json_print.c | 1 + lib/json_writer.c | 15 +-------------- misc/ss.c | 3 ++- tc/m_ematch.c | 1 + tc/m_ematch.h | 1 + 8 files changed, 20 insertions(+), 24 deletions(-) diff --git a/include/json_writer.h b/include/json_writer.h index 9ab88e1d..0c8831c1 100644 --- a/include/json_writer.h +++ b/include/json_writer.h @@ -29,6 +29,7 @@ void jsonw_pretty(json_writer_t *self, bool on); void jsonw_name(json_writer_t *self, const char *name); /* Add value */ +__attribute__((format(printf, 2, 3))) void jsonw_printf(json_writer_t *self, const char *fmt, ...); void jsonw_string(json_writer_t *self, const char *value); void jsonw_bool(json_writer_t *self, bool value); @@ -59,8 +60,6 @@ void jsonw_luint_field(json_writer_t *self, const char *prop, unsigned long int num); void jsonw_lluint_field(json_writer_t *self, const char *prop, unsigned long long int num); -void jsonw_float_field_fmt(json_writer_t *self, const char *prop, - const char *fmt, double val); /* Collections */ void jsonw_start_object(json_writer_t *self); diff --git a/ip/iplink_can.c b/ip/iplink_can.c index 587413da..c0deeb1f 100644 --- a/ip/iplink_can.c +++ b/ip/iplink_can.c @@ -316,11 +316,14 @@ static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) struct can_bittiming *bt = RTA_DATA(tb[IFLA_CAN_BITTIMING]); if (is_json_context()) { + json_writer_t *jw; + open_json_object("bittiming"); print_int(PRINT_ANY, "bitrate", NULL, bt->bitrate); - jsonw_float_field_fmt(get_json_writer(), - "sample_point", "%.3f", - (float) bt->sample_point / 1000.); + jw = get_json_writer(); + jsonw_name(jw, "sample_point"); + jsonw_printf(jw, "%.3f", + (float) bt->sample_point / 1000); print_int(PRINT_ANY, "tq", NULL, bt->tq); print_int(PRINT_ANY, "prop_seg", NULL, bt->prop_seg); print_int(PRINT_ANY, "phase_seg1", @@ -415,12 +418,14 @@ static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) RTA_DATA(tb[IFLA_CAN_DATA_BITTIMING]); if (is_json_context()) { + json_writer_t *jw; + open_json_object("data_bittiming"); print_int(PRINT_JSON, "bitrate", NULL, dbt->bitrate); - jsonw_float_field_fmt(get_json_writer(), - "sample_point", - "%.3f", - (float) dbt->sample_point / 1000.); + jw = get_json_writer(); + jsonw_name(jw, "sample_point"); + jsonw_printf(jw, "%.3f", + (float) dbt->sample_point / 1000.); print_int(PRINT_JSON, "tq", NULL, dbt->tq); print_int(PRINT_JSON, "prop_seg", NULL, dbt->prop_seg); print_int(PRINT_JSON, "phase_seg1", diff --git a/lib/color.c b/lib/color.c index eaf69e74..e5406294 100644 --- a/lib/color.c +++ b/lib/color.c @@ -132,6 +132,7 @@ void set_color_palette(void) is_dark_bg = 1; } +__attribute__((format(printf, 3, 4))) int color_fprintf(FILE *fp, enum color_attr attr, const char *fmt, ...) { int ret = 0; diff --git a/lib/json_print.c b/lib/json_print.c index 5dc41bfa..77902824 100644 --- a/lib/json_print.c +++ b/lib/json_print.c @@ -100,6 +100,7 @@ void close_json_array(enum output_type type, const char *str) * functions handling different types */ #define _PRINT_FUNC(type_name, type) \ + __attribute__((format(printf, 4, 0))) \ void print_color_##type_name(enum output_type t, \ enum color_attr color, \ const char *key, \ diff --git a/lib/json_writer.c b/lib/json_writer.c index aa9ce1c6..68890b34 100644 --- a/lib/json_writer.c +++ b/lib/json_writer.c @@ -152,6 +152,7 @@ void jsonw_name(json_writer_t *self, const char *name) putc(' ', self->out); } +__attribute__((format(printf, 2, 3))) void jsonw_printf(json_writer_t *self, const char *fmt, ...) { va_list ap; @@ -205,11 +206,6 @@ void jsonw_null(json_writer_t *self) jsonw_printf(self, "null"); } -void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num) -{ - jsonw_printf(self, fmt, num); -} - void jsonw_float(json_writer_t *self, double num) { jsonw_printf(self, "%g", num); @@ -274,15 +270,6 @@ void jsonw_float_field(json_writer_t *self, const char *prop, double val) jsonw_float(self, val); } -void jsonw_float_field_fmt(json_writer_t *self, - const char *prop, - const char *fmt, - double val) -{ - jsonw_name(self, prop); - jsonw_float_fmt(self, fmt, val); -} - void jsonw_uint_field(json_writer_t *self, const char *prop, unsigned int num) { jsonw_name(self, prop); diff --git a/misc/ss.c b/misc/ss.c index b2c634c8..f99b6874 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -977,6 +977,7 @@ static int buf_update(int len) } /* Append content to buffer as part of the current field */ +__attribute__((format(printf, 1, 2))) static void out(const char *fmt, ...) { struct column *f = current_field; @@ -1094,7 +1095,7 @@ static void print_header(void) { while (!field_is_last(current_field)) { if (!current_field->disabled) - out(current_field->header); + out("%s", current_field->header); field_next(); } } diff --git a/tc/m_ematch.c b/tc/m_ematch.c index ace4b3dd..a524b520 100644 --- a/tc/m_ematch.c +++ b/tc/m_ematch.c @@ -277,6 +277,7 @@ static int flatten_tree(struct ematch *head, struct ematch *tree) 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, ...) { diff --git a/tc/m_ematch.h b/tc/m_ematch.h index ff02d7ac..356f2ede 100644 --- a/tc/m_ematch.h +++ b/tc/m_ematch.h @@ -102,6 +102,7 @@ static inline int parse_layer(const struct bstr *b) 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 *); From 2bfe28710e69959a787681fb579853a08cd91502 Mon Sep 17 00:00:00 2001 From: Florent Fourcot Date: Thu, 30 Aug 2018 16:38:54 +0200 Subject: [PATCH 30/30] tc/htb: remove unused variable Since introduction of htb module, this variable has never been used. Signed-off-by: Florent Fourcot Signed-off-by: Stephen Hemminger --- tc/q_htb.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tc/q_htb.c b/tc/q_htb.c index b93d31d4..c8b2941d 100644 --- a/tc/q_htb.c +++ b/tc/q_htb.c @@ -109,7 +109,6 @@ static int htb_parse_opt(struct qdisc_util *qu, int argc, static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, struct nlmsghdr *n, const char *dev) { - int ok = 0; struct tc_htb_opt opt = {}; __u32 rtab[256], ctab[256]; unsigned buffer = 0, cbuffer = 0; @@ -127,7 +126,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str if (get_u32(&opt.prio, *argv, 10)) { explain1("prio"); return -1; } - ok++; } else if (matches(*argv, "mtu") == 0) { NEXT_ARG(); if (get_u32(&mtu, *argv, 10)) { @@ -161,7 +159,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str explain1("buffer"); return -1; } - ok++; } else if (matches(*argv, "cburst") == 0 || strcmp(*argv, "cbuffer") == 0 || strcmp(*argv, "cmaxburst") == 0) { @@ -170,7 +167,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str explain1("cbuffer"); return -1; } - ok++; } else if (strcmp(*argv, "ceil") == 0) { NEXT_ARG(); if (ceil64) { @@ -186,7 +182,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str explain1("ceil"); return -1; } - ok++; } else if (strcmp(*argv, "rate") == 0) { NEXT_ARG(); if (rate64) { @@ -202,7 +197,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str explain1("rate"); return -1; } - ok++; } else if (strcmp(*argv, "help") == 0) { explain(); return -1; @@ -214,9 +208,6 @@ static int htb_parse_class_opt(struct qdisc_util *qu, int argc, char **argv, str argc--; argv++; } - /* if (!ok) - return 0;*/ - if (!rate64) { fprintf(stderr, "\"rate\" is required.\n"); return -1;