From 906ac5437ab8e1e5f8514afe62bf01e1ff3906af Mon Sep 17 00:00:00 2001 From: "John W. Linville" Date: Thu, 24 Sep 2015 14:39:39 -0400 Subject: [PATCH 01/16] geneve: add support for IPv6 link partners Signed-off-by: John W. Linville --- ip/iplink_geneve.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ip/iplink_geneve.c b/ip/iplink_geneve.c index 331240a6..13454795 100644 --- a/ip/iplink_geneve.c +++ b/ip/iplink_geneve.c @@ -55,7 +55,7 @@ static int geneve_parse_opt(struct link_util *lu, int argc, char **argv, fprintf(stderr, "Invalid address \"%s\"\n", *argv); return -1; } - if (IN_MULTICAST(ntohl(daddr))) + if (IN6_IS_ADDR_MULTICAST(&daddr6) || IN_MULTICAST(ntohl(daddr))) invarg("invalid remote address", *argv); } else if (!matches(*argv, "ttl") || !matches(*argv, "hoplimit")) { @@ -96,18 +96,16 @@ static int geneve_parse_opt(struct link_util *lu, int argc, char **argv, return -1; } - if (!daddr) { - fprintf(stderr, "geneve: remove link partner not specified\n"); - return -1; - } - if (memcmp(&daddr6, &in6addr_any, sizeof(daddr6)) != 0) { - fprintf(stderr, "geneve: remove link over IPv6 not supported\n"); + if (!daddr && memcmp(&daddr6, &in6addr_any, sizeof(daddr6)) == 0) { + fprintf(stderr, "geneve: remote link partner not specified\n"); return -1; } addattr32(n, 1024, IFLA_GENEVE_ID, vni); if (daddr) addattr_l(n, 1024, IFLA_GENEVE_REMOTE, &daddr, 4); + if (memcmp(&daddr6, &in6addr_any, sizeof(daddr6)) != 0) + addattr_l(n, 1024, IFLA_GENEVE_REMOTE6, &daddr6, sizeof(struct in6_addr)); addattr8(n, 1024, IFLA_GENEVE_TTL, ttl); addattr8(n, 1024, IFLA_GENEVE_TOS, tos); @@ -135,6 +133,14 @@ static void geneve_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) if (addr) fprintf(f, "remote %s ", format_host(AF_INET, 4, &addr, s1, sizeof(s1))); + } else if (tb[IFLA_GENEVE_REMOTE6]) { + struct in6_addr addr; + memcpy(&addr, RTA_DATA(tb[IFLA_GENEVE_REMOTE6]), sizeof(struct in6_addr)); + if (memcmp(&addr, &in6addr_any, sizeof(addr)) != 0) { + if (IN6_IS_ADDR_MULTICAST(&addr)) + fprintf(f, "remote %s ", + format_host(AF_INET6, sizeof(struct in6_addr), &addr, s1, sizeof(s1))); + } } if (tb[IFLA_GENEVE_TTL]) { From 13ada95da4f05df5993ca332358fa3f2f3a21047 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Tue, 24 Nov 2015 13:20:01 -0800 Subject: [PATCH 02/16] Add support for rt_tables.d Add support for reading table id/name mappings from rt_tables.d directory. Suggested-by: Roopa Prabhu Signed-off-by: David Ahern --- etc/iproute2/rt_tables.d/README | 3 +++ lib/rt_names.c | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 etc/iproute2/rt_tables.d/README diff --git a/etc/iproute2/rt_tables.d/README b/etc/iproute2/rt_tables.d/README new file mode 100644 index 00000000..79386f89 --- /dev/null +++ b/etc/iproute2/rt_tables.d/README @@ -0,0 +1,3 @@ +Each file in this directory is an rt_tables configuration file. iproute2 +commands scan this directory processing all files that end in '.conf'. + diff --git a/lib/rt_names.c b/lib/rt_names.c index e87c65da..f68e91d6 100644 --- a/lib/rt_names.c +++ b/lib/rt_names.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -339,6 +340,8 @@ static int rtnl_rttable_init; static void rtnl_rttable_initialize(void) { + struct dirent *de; + DIR *d; int i; rtnl_rttable_init = 1; @@ -348,6 +351,29 @@ static void rtnl_rttable_initialize(void) } rtnl_hash_initialize(CONFDIR "/rt_tables", rtnl_rttable_hash, 256); + + d = opendir(CONFDIR "/rt_tables.d"); + if (!d) + return; + + while ((de = readdir(d)) != NULL) { + char path[PATH_MAX]; + size_t len; + + if (*de->d_name == '.') + continue; + + /* only consider filenames ending in '.conf' */ + len = strlen(de->d_name); + if (len <= 5) + continue; + if (strcmp(de->d_name + len - 5, ".conf")) + continue; + + snprintf(path, sizeof(path), CONFDIR "/rt_tables.d/%s", de->d_name); + rtnl_hash_initialize(path, rtnl_rttable_hash, 256); + } + closedir(d); } const char * rtnl_rttable_n2a(__u32 id, char *buf, int len) From 68ef50724914e17ef47871e432618d73cce0c6c9 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sun, 29 Nov 2015 11:41:23 -0800 Subject: [PATCH 03/16] rt_names: style cleanup Cleanup all checkpatch complaints about whitespace in rt_names. --- lib/rt_names.c | 112 ++++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/lib/rt_names.c b/lib/rt_names.c index f68e91d6..1071a938 100644 --- a/lib/rt_names.c +++ b/lib/rt_names.c @@ -31,8 +31,8 @@ #define NAME_MAX_LEN 512 struct rtnl_hash_entry { - struct rtnl_hash_entry *next; - const char * name; + struct rtnl_hash_entry *next; + const char *name; unsigned int id; }; @@ -82,7 +82,7 @@ rtnl_hash_initialize(const char *file, struct rtnl_hash_entry **hash, int size) return; } - if (id<0) + if (id < 0) continue; entry = malloc(sizeof(*entry)); @@ -112,7 +112,7 @@ static void rtnl_tab_initialize(const char *file, char **tab, int size) fclose(fp); return; } - if (id<0 || id>size) + if (id < 0 || id > size) continue; tab[id] = strdup(namebuf); @@ -120,23 +120,23 @@ static void rtnl_tab_initialize(const char *file, char **tab, int size) fclose(fp); } -static char * rtnl_rtprot_tab[256] = { - [RTPROT_UNSPEC] = "none", - [RTPROT_REDIRECT] ="redirect", - [RTPROT_KERNEL] = "kernel", - [RTPROT_BOOT] = "boot", - [RTPROT_STATIC] = "static", +static char *rtnl_rtprot_tab[256] = { + [RTPROT_UNSPEC] = "none", + [RTPROT_REDIRECT] = "redirect", + [RTPROT_KERNEL] = "kernel", + [RTPROT_BOOT] = "boot", + [RTPROT_STATIC] = "static", - [RTPROT_GATED] = "gated", - [RTPROT_RA] = "ra", - [RTPROT_MRT] = "mrt", - [RTPROT_ZEBRA] ="zebra", - [RTPROT_BIRD] = "bird", - [RTPROT_BABEL] = "babel", + [RTPROT_GATED] = "gated", + [RTPROT_RA] = "ra", + [RTPROT_MRT] = "mrt", + [RTPROT_ZEBRA] = "zebra", + [RTPROT_BIRD] = "bird", + [RTPROT_BABEL] = "babel", [RTPROT_DNROUTED] = "dnrouted", - [RTPROT_XORP] = "xorp", - [RTPROT_NTK] = "ntk", - [RTPROT_DHCP] = "dhcp", + [RTPROT_XORP] = "xorp", + [RTPROT_NTK] = "ntk", + [RTPROT_DHCP] = "dhcp", }; @@ -149,9 +149,9 @@ static void rtnl_rtprot_initialize(void) rtnl_rtprot_tab, 256); } -const char * rtnl_rtprot_n2a(int id, char *buf, int len) +const char *rtnl_rtprot_n2a(int id, char *buf, int len) { - if (id<0 || id>=256) { + if (id < 0 || id >= 256) { snprintf(buf, len, "%u", id); return buf; } @@ -167,7 +167,7 @@ const char * rtnl_rtprot_n2a(int id, char *buf, int len) int rtnl_rtprot_a2n(__u32 *id, const char *arg) { - static char *cache = NULL; + static char *cache; static unsigned long res; char *end; int i; @@ -180,7 +180,7 @@ int rtnl_rtprot_a2n(__u32 *id, const char *arg) if (!rtnl_rtprot_init) rtnl_rtprot_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { if (rtnl_rtprot_tab[i] && strcmp(rtnl_rtprot_tab[i], arg) == 0) { cache = rtnl_rtprot_tab[i]; @@ -197,8 +197,13 @@ int rtnl_rtprot_a2n(__u32 *id, const char *arg) return 0; } -static char * rtnl_rtscope_tab[256] = { - "global", + +static char *rtnl_rtscope_tab[256] = { + [RT_SCOPE_UNIVERSE] = "global", + [RT_SCOPE_NOWHERE] = "nowhere", + [RT_SCOPE_HOST] = "host", + [RT_SCOPE_LINK] = "link", + [RT_SCOPE_SITE] = "site", }; static int rtnl_rtscope_init; @@ -206,33 +211,32 @@ static int rtnl_rtscope_init; static void rtnl_rtscope_initialize(void) { rtnl_rtscope_init = 1; - rtnl_rtscope_tab[RT_SCOPE_NOWHERE] = "nowhere"; - rtnl_rtscope_tab[RT_SCOPE_HOST] = "host"; - rtnl_rtscope_tab[RT_SCOPE_LINK] = "link"; - rtnl_rtscope_tab[RT_SCOPE_SITE] = "site"; rtnl_tab_initialize(CONFDIR "/rt_scopes", rtnl_rtscope_tab, 256); } const char *rtnl_rtscope_n2a(int id, char *buf, int len) { - if (id<0 || id>=256) { + if (id < 0 || id >= 256) { snprintf(buf, len, "%d", id); return buf; } + if (!rtnl_rtscope_tab[id]) { if (!rtnl_rtscope_init) rtnl_rtscope_initialize(); } + if (rtnl_rtscope_tab[id]) return rtnl_rtscope_tab[id]; + snprintf(buf, len, "%d", id); return buf; } int rtnl_rtscope_a2n(__u32 *id, const char *arg) { - static const char *cache = NULL; + static const char *cache; static unsigned long res; char *end; int i; @@ -245,7 +249,7 @@ int rtnl_rtscope_a2n(__u32 *id, const char *arg) if (!rtnl_rtscope_init) rtnl_rtscope_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { if (rtnl_rtscope_tab[i] && strcmp(rtnl_rtscope_tab[i], arg) == 0) { cache = rtnl_rtscope_tab[i]; @@ -263,7 +267,7 @@ int rtnl_rtscope_a2n(__u32 *id, const char *arg) } -static char * rtnl_rtrealm_tab[256] = { +static char *rtnl_rtrealm_tab[256] = { "unknown", }; @@ -278,7 +282,7 @@ static void rtnl_rtrealm_initialize(void) const char *rtnl_rtrealm_n2a(int id, char *buf, int len) { - if (id<0 || id>=256) { + if (id < 0 || id >= 256) { snprintf(buf, len, "%d", id); return buf; } @@ -295,7 +299,7 @@ const char *rtnl_rtrealm_n2a(int id, char *buf, int len) int rtnl_rtrealm_a2n(__u32 *id, const char *arg) { - static char *cache = NULL; + static char *cache; static unsigned long res; char *end; int i; @@ -308,7 +312,7 @@ int rtnl_rtrealm_a2n(__u32 *id, const char *arg) if (!rtnl_rtrealm_init) rtnl_rtrealm_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { if (rtnl_rtrealm_tab[i] && strcmp(rtnl_rtrealm_tab[i], arg) == 0) { cache = rtnl_rtrealm_tab[i]; @@ -330,7 +334,7 @@ static struct rtnl_hash_entry dflt_table_entry = { .name = "default" }; static struct rtnl_hash_entry main_table_entry = { .name = "main" }; static struct rtnl_hash_entry local_table_entry = { .name = "local" }; -static struct rtnl_hash_entry * rtnl_rttable_hash[256] = { +static struct rtnl_hash_entry *rtnl_rttable_hash[256] = { [RT_TABLE_DEFAULT] = &dflt_table_entry, [RT_TABLE_MAIN] = &main_table_entry, [RT_TABLE_LOCAL] = &local_table_entry, @@ -370,13 +374,14 @@ static void rtnl_rttable_initialize(void) if (strcmp(de->d_name + len - 5, ".conf")) continue; - snprintf(path, sizeof(path), CONFDIR "/rt_tables.d/%s", de->d_name); + snprintf(path, sizeof(path), + CONFDIR "/rt_tables.d/%s", de->d_name); rtnl_hash_initialize(path, rtnl_rttable_hash, 256); } closedir(d); } -const char * rtnl_rttable_n2a(__u32 id, char *buf, int len) +const char *rtnl_rttable_n2a(__u32 id, char *buf, int len) { struct rtnl_hash_entry *entry; @@ -397,7 +402,7 @@ const char * rtnl_rttable_n2a(__u32 id, char *buf, int len) int rtnl_rttable_a2n(__u32 *id, const char *arg) { - static const char *cache = NULL; + static const char *cache; static unsigned long res; struct rtnl_hash_entry *entry; char *end; @@ -411,7 +416,7 @@ int rtnl_rttable_a2n(__u32 *id, const char *arg) if (!rtnl_rttable_init) rtnl_rttable_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { entry = rtnl_rttable_hash[i]; while (entry && strcmp(entry->name, arg)) entry = entry->next; @@ -431,7 +436,7 @@ int rtnl_rttable_a2n(__u32 *id, const char *arg) } -static char * rtnl_rtdsfield_tab[256] = { +static char *rtnl_rtdsfield_tab[256] = { "0", }; @@ -446,7 +451,7 @@ static void rtnl_rtdsfield_initialize(void) const char *rtnl_dsfield_n2a(int id, char *buf, int len) { - if (id<0 || id>=256) { + if (id < 0 || id >= 256) { snprintf(buf, len, "%d", id); return buf; } @@ -463,7 +468,7 @@ const char *rtnl_dsfield_n2a(int id, char *buf, int len) int rtnl_dsfield_a2n(__u32 *id, const char *arg) { - static char *cache = NULL; + static char *cache; static unsigned long res; char *end; int i; @@ -476,7 +481,7 @@ int rtnl_dsfield_a2n(__u32 *id, const char *arg) if (!rtnl_rtdsfield_init) rtnl_rtdsfield_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { if (rtnl_rtdsfield_tab[i] && strcmp(rtnl_rtdsfield_tab[i], arg) == 0) { cache = rtnl_rtdsfield_tab[i]; @@ -494,9 +499,11 @@ int rtnl_dsfield_a2n(__u32 *id, const char *arg) } -static struct rtnl_hash_entry dflt_group_entry = { .id = 0, .name = "default" }; +static struct rtnl_hash_entry dflt_group_entry = { + .id = 0, .name = "default" +}; -static struct rtnl_hash_entry * rtnl_group_hash[256] = { +static struct rtnl_hash_entry *rtnl_group_hash[256] = { [0] = &dflt_group_entry, }; @@ -511,7 +518,7 @@ static void rtnl_group_initialize(void) int rtnl_group_a2n(int *id, const char *arg) { - static const char *cache = NULL; + static const char *cache; static unsigned long res; struct rtnl_hash_entry *entry; char *end; @@ -525,7 +532,7 @@ int rtnl_group_a2n(int *id, const char *arg) if (!rtnl_group_init) rtnl_group_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { entry = rtnl_group_hash[i]; while (entry && strcmp(entry->name, arg)) entry = entry->next; @@ -552,11 +559,10 @@ const char *rtnl_group_n2a(int id, char *buf, int len) if (!rtnl_group_init) rtnl_group_initialize(); - for (i=0; i<256; i++) { + for (i = 0; i < 256; i++) { entry = rtnl_group_hash[i]; - if (entry && entry->id == id) { + if (entry && entry->id == id) return entry->name; - } } snprintf(buf, len, "%d", id); @@ -615,7 +621,7 @@ const char *nl_proto_n2a(int id, char *buf, int len) int nl_proto_a2n(__u32 *id, const char *arg) { - static char *cache = NULL; + static char *cache; static unsigned long res; char *end; int i; From c6995c48025233902a5b0c5fe88654e17ea934f6 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:31:00 +0100 Subject: [PATCH 04/16] ipaddress: simplify ipaddr_flush() Since it's no longer relevant whether an IP address is primary or secondary when flushing, ipaddr_flush() can be simplified a bit. Signed-off-by: Phil Sutter --- ip/ipaddress.c | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/ip/ipaddress.c b/ip/ipaddress.c index 05358c97..26e91c9b 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -1148,28 +1148,6 @@ brief_exit: return 0; } -static int print_addrinfo_primary(const struct sockaddr_nl *who, - struct nlmsghdr *n, void *arg) -{ - struct ifaddrmsg *ifa = NLMSG_DATA(n); - - if (ifa->ifa_flags & IFA_F_SECONDARY) - return 0; - - return print_addrinfo(who, n, arg); -} - -static int print_addrinfo_secondary(const struct sockaddr_nl *who, - struct nlmsghdr *n, void *arg) -{ - struct ifaddrmsg *ifa = NLMSG_DATA(n); - - if (!(ifa->ifa_flags & IFA_F_SECONDARY)) - return 0; - - return print_addrinfo(who, n, arg); -} - struct nlmsg_list { struct nlmsg_list *next; @@ -1420,26 +1398,12 @@ static int ipaddr_flush(void) filter.flushe = sizeof(flushb); while ((max_flush_loops == 0) || (round < max_flush_loops)) { - const struct rtnl_dump_filter_arg a[3] = { - { - .filter = print_addrinfo_secondary, - .arg1 = stdout, - }, - { - .filter = print_addrinfo_primary, - .arg1 = stdout, - }, - { - .filter = NULL, - .arg1 = NULL, - }, - }; if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { perror("Cannot send dump request"); exit(1); } filter.flushed = 0; - if (rtnl_dump_filter_l(&rth, a) < 0) { + if (rtnl_dump_filter(&rth, print_addrinfo, stdout) < 0) { fprintf(stderr, "Flush terminated\n"); exit(1); } From 8e72880f6bfa39f439b9c4a88eb84b635b991687 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:31:01 +0100 Subject: [PATCH 05/16] libnetlink: introduce nc_flags Allow for a filter to ignore certain nlmsg_flags. Signed-off-by: Phil Sutter --- include/libnetlink.h | 7 ++++++- lib/libnetlink.c | 10 ++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/libnetlink.h b/include/libnetlink.h index 2280c39c..431189e2 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -60,11 +60,16 @@ struct rtnl_dump_filter_arg { rtnl_filter_t filter; void *arg1; + __u16 nc_flags; }; int rtnl_dump_filter_l(struct rtnl_handle *rth, const struct rtnl_dump_filter_arg *arg); -int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg); +int rtnl_dump_filter_nc(struct rtnl_handle *rth, + rtnl_filter_t filter, + void *arg, __u16 nc_flags); +#define rtnl_dump_filter(rth, filter, arg) \ + rtnl_dump_filter_nc(rth, filter, arg, 0) int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, struct nlmsghdr *answer, size_t len) __attribute__((warn_unused_result)); diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 09b0e911..922ec2d9 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -259,6 +259,8 @@ int rtnl_dump_filter_l(struct rtnl_handle *rth, while (NLMSG_OK(h, msglen)) { int err = 0; + h->nlmsg_flags &= ~a->nc_flags; + if (nladdr.nl_pid != 0 || h->nlmsg_pid != rth->local.nl_pid || h->nlmsg_seq != rth->dump) @@ -317,13 +319,13 @@ skip_it: } } -int rtnl_dump_filter(struct rtnl_handle *rth, +int rtnl_dump_filter_nc(struct rtnl_handle *rth, rtnl_filter_t filter, - void *arg1) + void *arg1, __u16 nc_flags) { const struct rtnl_dump_filter_arg a[2] = { - { .filter = filter, .arg1 = arg1, }, - { .filter = NULL, .arg1 = NULL, }, + { .filter = filter, .arg1 = arg1, .nc_flags = nc_flags, }, + { .filter = NULL, .arg1 = NULL, .nc_flags = 0, }, }; return rtnl_dump_filter_l(rth, a); From d25ec03e1dce4cf22093a9f7106e9401ab5bf066 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:31:02 +0100 Subject: [PATCH 06/16] ipaddress: fix ipaddr_flush for Linux >= 3.1 Linux version 3.1 introduced a consistency check for netlink dumps in commit 670dc28 ("netlink: advertise incomplete dumps"). This bites iproute2 when flushing more addresses than can fit into a single RTM_GETADDR response. To silence the spurious error message "Dump was interrupted and may be inconsistent.", advise rtnl_dump_filter_l() to not care about NLM_F_DUMP_INTR. Signed-off-by: Phil Sutter --- ip/ipaddress.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ip/ipaddress.c b/ip/ipaddress.c index 26e91c9b..9811eb4c 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -1403,7 +1403,8 @@ static int ipaddr_flush(void) exit(1); } filter.flushed = 0; - if (rtnl_dump_filter(&rth, print_addrinfo, stdout) < 0) { + if (rtnl_dump_filter_nc(&rth, print_addrinfo, + stdout, NLM_F_DUMP_INTR) < 0) { fprintf(stderr, "Flush terminated\n"); exit(1); } From 906dfe4887672be87b0656a2034f950883f036f6 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:31:03 +0100 Subject: [PATCH 07/16] ipaddress: drop unnecessary check in ipaddr_list_flush_or_save() Right after ipaddr_reset_filter(), filter.family is always AF_UNSPEC. Signed-off-by: Phil Sutter --- ip/ipaddress.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ip/ipaddress.c b/ip/ipaddress.c index 9811eb4c..bc8359eb 100644 --- a/ip/ipaddress.c +++ b/ip/ipaddress.c @@ -1451,10 +1451,7 @@ static int ipaddr_list_flush_or_save(int argc, char **argv, int action) ipaddr_reset_filter(oneline, 0); filter.showqueue = 1; - - if (filter.family == AF_UNSPEC) - filter.family = preferred_family; - + filter.family = preferred_family; filter.group = -1; if (action == IPADD_FLUSH) { From d81f54d5999cb78f4d062a21693ddd50165df0ec Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:31:04 +0100 Subject: [PATCH 08/16] iptoken: simplify iptoken_list a bit Since it uses only a single filter, rtnl_dump_filter() can be used. Signed-off-by: Phil Sutter --- ip/iptoken.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ip/iptoken.c b/ip/iptoken.c index a38194c9..428f1332 100644 --- a/ip/iptoken.c +++ b/ip/iptoken.c @@ -95,10 +95,6 @@ static int iptoken_list(int argc, char **argv) { int af = AF_INET6; struct rtnl_dump_args da; - const struct rtnl_dump_filter_arg a[2] = { - { .filter = print_token, .arg1 = &da, }, - { .filter = NULL, .arg1 = NULL, }, - }; memset(&da, 0, sizeof(da)); da.fp = stdout; @@ -118,7 +114,7 @@ static int iptoken_list(int argc, char **argv) return -1; } - if (rtnl_dump_filter_l(&rth, a) < 0) { + if (rtnl_dump_filter(&rth, print_token, &da) < 0) { fprintf(stderr, "Dump terminated\n"); return -1; } From ea6cbab792f7bb8813f1b24cc1f4bd4caad8ccbe Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:45:31 +0100 Subject: [PATCH 09/16] iproute: restrict hoplimit values to be in range [0; 255] Technically, the range of possible hoplimit values are defined by IPv4 and IPv6 header formats. Both define the field to be eight bits in size, which leads to a value range of [0;255]. Setting a packet's hoplimit field to 0 though makes not much sense, as the next hop would immediately drop the packet. Therefore Linux uses 0 as a special value indicating to use the system's default hoplimit (configurable via sysctl). In iproute, setting the hoplimit of a route to 0 is equivalent to omitting the hoplimit parameter alltogether, so it is actually not necessary to allow that value to be specified, but keep it anyway for backwards compatibility. Signed-off-by: Phil Sutter --- ip/iproute.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ip/iproute.c b/ip/iproute.c index c0ef7bfe..aed1038e 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -931,7 +931,7 @@ static int iproute_modify(int cmd, unsigned flags, int argc, char **argv) mxlock |= (1< 255) invarg("\"hoplimit\" value is invalid\n", *argv); rta_addattr32(mxrta, sizeof(mxbuf), RTAX_HOPLIMIT, hoplimit); } else if (strcmp(*argv, "advmss") == 0) { From fc31817d1f2ee8098b875a8b122f136a7564e339 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Tue, 24 Nov 2015 15:50:00 +0100 Subject: [PATCH 10/16] bridge.8: minor formatting cleanup - Replace commas at end of subsection with dots. - Replace double whitespace by single one. Signed-off-by: Phil Sutter --- man/man8/bridge.8 | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index 98a92eb8..0ec6f174 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -25,14 +25,14 @@ bridge \- show / manipulate bridge addresses and devices .ti -8 .BR "bridge link set" -.B dev +.B dev .IR DEV .IR " [ " -.B cost +.B cost .IR COST " ] [ " -.B priority -.IR PRIO " ] [ " -.B state +.B priority +.IR PRIO " ] [ " +.B state .IR STATE "] [" .BR guard " { " on " | " off " } ] [ " .BR hairpin " { " on " | " off " } ] [ " @@ -42,21 +42,21 @@ bridge \- show / manipulate bridge addresses and devices .BR learning_sync " { " on " | " off " } ] [ " .BR flood " { " on " | " off " } ] [ " .BR hwmode " { " vepa " | " veb " } ] [ " -.BR self " ] [ " master " ] " +.BR self " ] [ " master " ] " .ti -8 .BR "bridge link" " [ " show " ] [ " -.B dev +.B dev .IR DEV " ]" .ti -8 .BR "bridge fdb" " { " add " | " append " | " del " | " replace " } " .I LLADDR -.B dev +.B dev .IR DEV " { " .BR local " | " temp " } [ " .BR self " ] [ " master " ] [ " router " ] [ " use " ] [ " -.B dst +.B dst .IR IPADDR " ] [ " .B vni .IR VNI " ] [" @@ -67,12 +67,12 @@ bridge \- show / manipulate bridge addresses and devices .ti -8 .BR "bridge fdb" " [ " show " ] [ " -.B dev +.B dev .IR DEV " ]" .ti -8 .BR "bridge mdb" " { " add " | " del " } " -.B dev +.B dev .IR DEV .B port .IR PORT @@ -84,21 +84,21 @@ bridge \- show / manipulate bridge addresses and devices .ti -8 .BR "bridge mdb show " [ " -.B dev +.B dev .IR DEV " ]" .ti -8 .BR "bridge vlan" " { " add " | " del " } " -.B dev +.B dev .IR DEV -.B vid +.B vid .IR VID " [ " -.BR pvid " ] [ " untagged " ] [ " -.BR self " ] [ " master " ] " +.BR pvid " ] [ " untagged " ] [ " +.BR self " ] [ " master " ] " .ti -8 .BR "bridge vlan" " [ " show " ] [ " -.B dev +.B dev .IR DEV " ]" .ti -8 @@ -319,7 +319,7 @@ This command displays the current bridge port configuration and flags. .SH bridge fdb - forwarding database management .B fdb -objects contain known Ethernet addresses on a link. +objects contain known Ethernet addresses on a link. .P The corresponding commands display fdb entries, add new entries, @@ -398,21 +398,21 @@ sends a copy of the data packet to each entry found. .PP The arguments are the same as with -.BR "bridge fdb add" , +.BR "bridge fdb add" . .SS bridge fdb delete - delete a forwarding database entry This command removes an existing fdb entry. .PP The arguments are the same as with -.BR "bridge fdb add" , +.BR "bridge fdb add" . .SS bridge fdb replace - replace a forwarding database entry If no matching entry is found, a new one will be created instead. .PP The arguments are the same as with -.BR "bridge fdb add" , +.BR "bridge fdb add" . .SS bridge fdb show - list forwarding entries. @@ -548,7 +548,7 @@ This command displays the current VLAN filter table. The .B bridge -utility can monitor the state of devices and addresses +utility can monitor the state of devices and addresses continuously. This option has a slightly different format. Namely, the .B monitor @@ -560,7 +560,7 @@ command is the first in the command line and then the object list follows: .I OBJECT-LIST is the list of object types that we want to monitor. It may contain -.BR link ", " fdb ", and " mdb "." +.BR link ", " fdb ", and " mdb "." If no .B file argument is given, From db3ef44c5433aa50bae6f88736ad350fb14fc2cf Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sat, 28 Nov 2015 01:00:01 +0100 Subject: [PATCH 11/16] lnstat: review lnstat_update() Instead of calling rewind() and fgets() before every call to scan_lines(), move them into scan_lines() itself. This should also fix compat mode, as before the second call to scan_lines() the first line was skipped unconditionally. Signed-off-by: Phil Sutter --- misc/lnstat_util.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/misc/lnstat_util.c b/misc/lnstat_util.c index 6dde7c49..433e9929 100644 --- a/misc/lnstat_util.c +++ b/misc/lnstat_util.c @@ -38,18 +38,22 @@ /* Read (and summarize for SMP) the different stats vars. */ static int scan_lines(struct lnstat_file *lf, int i) { + char buf[FGETS_BUF_SIZE]; int j, num_lines = 0; for (j = 0; j < lf->num_fields; j++) lf->fields[j].values[i] = 0; - while(!feof(lf->fp)) { - char buf[FGETS_BUF_SIZE]; + rewind(lf->fp); + /* skip first line */ + if (!lf->compat && !fgets(buf, sizeof(buf)-1, lf->fp)) + return -1; + + while(!feof(lf->fp) && fgets(buf, sizeof(buf)-1, lf->fp)) { char *ptr = buf; num_lines++; - fgets(buf, sizeof(buf)-1, lf->fp); gettimeofday(&lf->last_read, NULL); for (j = 0; j < lf->num_fields; j++) { @@ -81,7 +85,6 @@ static int time_after(struct timeval *last, int lnstat_update(struct lnstat_file *lnstat_files) { struct lnstat_file *lf; - char buf[FGETS_BUF_SIZE]; struct timeval tv; gettimeofday(&tv, NULL); @@ -91,11 +94,6 @@ int lnstat_update(struct lnstat_file *lnstat_files) int i; struct lnstat_field *lfi; - rewind(lf->fp); - if (!lf->compat) { - /* skip first line */ - fgets(buf, sizeof(buf)-1, lf->fp); - } scan_lines(lf, 1); for (i = 0, lfi = &lf->fields[i]; @@ -107,8 +105,6 @@ int lnstat_update(struct lnstat_file *lnstat_files) / lf->interval.tv_sec; } - rewind(lf->fp); - fgets(buf, sizeof(buf)-1, lf->fp); scan_lines(lf, 0); } } From 596307ea3d638ea037017b0d6f31f2d04fddd296 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sat, 28 Nov 2015 01:00:02 +0100 Subject: [PATCH 12/16] ss: reduce max indentation level in init_service_resolver() Exit early or continue on error instead of putting conditional into conditional to make reading the code a bit easier. Also, the call to memcpy() can be skipped by initialising prog with the desired prefix. Signed-off-by: Phil Sutter --- misc/ss.c | 53 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/misc/ss.c b/misc/ss.c index a9ae85ec..4988d34e 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -870,31 +870,38 @@ static void init_service_resolver(void) { char buf[128]; FILE *fp = popen("/usr/sbin/rpcinfo -p 2>/dev/null", "r"); - if (fp) { - fgets(buf, sizeof(buf), fp); - while (fgets(buf, sizeof(buf), fp) != NULL) { - unsigned int progn, port; - char proto[128], prog[128]; - if (sscanf(buf, "%u %*d %s %u %s", &progn, proto, - &port, prog+4) == 4) { - struct scache *c = malloc(sizeof(*c)); - if (c) { - c->port = port; - memcpy(prog, "rpc.", 4); - c->name = strdup(prog); - if (strcmp(proto, TCP_PROTO) == 0) - c->proto = TCP_PROTO; - else if (strcmp(proto, UDP_PROTO) == 0) - c->proto = UDP_PROTO; - else - c->proto = NULL; - c->next = rlist; - rlist = c; - } - } - } + + if (!fp) + return; + + if (!fgets(buf, sizeof(buf), fp)) { pclose(fp); + return; } + while (fgets(buf, sizeof(buf), fp) != NULL) { + unsigned int progn, port; + char proto[128], prog[128] = "rpc."; + struct scache *c; + + if (sscanf(buf, "%u %*d %s %u %s", + &progn, proto, &port, prog+4) != 4) + continue; + + if (!(c = malloc(sizeof(*c)))) + continue; + + c->port = port; + c->name = strdup(prog); + if (strcmp(proto, TCP_PROTO) == 0) + c->proto = TCP_PROTO; + else if (strcmp(proto, UDP_PROTO) == 0) + c->proto = UDP_PROTO; + else + c->proto = NULL; + c->next = rlist; + rlist = c; + } + pclose(fp); } static int ip_local_port_min, ip_local_port_max; From c29d37925aa479a6c0545fee87329ab1d248680a Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sat, 28 Nov 2015 01:00:03 +0100 Subject: [PATCH 13/16] ss: review is_ephemeral() No need to keep static port boundaries global, they are not used directly. Keeping them local also allows to safely reduce their names to the minimum. Assign hardcoded fallback values also if fscanf() fails. Get rid of unnecessary braces around return parameter. Instead of more or less duplicating is_ephemeral() in run_ssfilter(), simply call the function instead. Signed-off-by: Phil Sutter --- misc/ss.c | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/misc/ss.c b/misc/ss.c index 4988d34e..79285732 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -904,8 +904,6 @@ static void init_service_resolver(void) pclose(fp); } -static int ip_local_port_min, ip_local_port_max; - /* Even do not try default linux ephemeral port ranges: * default /etc/services contains so much of useless crap * wouldbe "allocated" to this area that resolution @@ -914,19 +912,18 @@ static int ip_local_port_min, ip_local_port_max; */ static int is_ephemeral(int port) { - if (!ip_local_port_min) { - FILE *f = ephemeral_ports_open(); - if (f) { - fscanf(f, "%d %d", - &ip_local_port_min, &ip_local_port_max); - fclose(f); - } else { - ip_local_port_min = 1024; - ip_local_port_max = 4999; - } - } + static int min = 0, max = 0; - return (port >= ip_local_port_min && port<= ip_local_port_max); + if (!min) { + FILE *f = ephemeral_ports_open(); + if (!f || fscanf(f, "%d %d", &min, &max) < 2) { + min = 1024; + max = 4999; + } + if (f) + fclose(f); + } + return port >= min && port <= max; } @@ -1081,8 +1078,6 @@ static int run_ssfilter(struct ssfilter *f, struct sockstat *s) switch (f->type) { case SSF_S_AUTO: { - static int low, high=65535; - if (s->local.family == AF_UNIX) { char *p; memcpy(&p, s->local.data, sizeof(p)); @@ -1094,14 +1089,7 @@ static int run_ssfilter(struct ssfilter *f, struct sockstat *s) if (s->local.family == AF_NETLINK) return s->lport < 0; - if (!low) { - FILE *fp = ephemeral_ports_open(); - if (fp) { - fscanf(fp, "%d%d", &low, &high); - fclose(fp); - } - } - return s->lport >= low && s->lport <= high; + return is_ephemeral(s->lport); } case SSF_DCOND: { From d572ed4d0af79eb597469d3f1a84456782c64f24 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sat, 28 Nov 2015 01:00:04 +0100 Subject: [PATCH 14/16] get rid of remaining -Wunused-result warnings Although not fundamentally necessary to check return codes in these spots, preventing the warnings will put new ones into focus. Signed-off-by: Phil Sutter --- misc/ifstat.c | 6 ++++-- misc/lnstat_util.c | 3 ++- misc/nstat.c | 6 ++++-- misc/ss.c | 18 ++++++++++++++---- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/misc/ifstat.c b/misc/ifstat.c index 20a8db45..ac5c29c8 100644 --- a/misc/ifstat.c +++ b/misc/ifstat.c @@ -819,7 +819,8 @@ int main(int argc, char *argv[]) } if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) { fprintf(stderr, "ifstat: history is aged out, resetting\n"); - ftruncate(fileno(hist_fp), 0); + if (ftruncate(fileno(hist_fp), 0)) + perror("ifstat: ftruncate"); } } @@ -862,7 +863,8 @@ int main(int argc, char *argv[]) } if (!no_update) { - ftruncate(fileno(hist_fp), 0); + if (ftruncate(fileno(hist_fp), 0)) + perror("ifstat: ftruncate"); rewind(hist_fp); json_output = 0; diff --git a/misc/lnstat_util.c b/misc/lnstat_util.c index 433e9929..70a77c56 100644 --- a/misc/lnstat_util.c +++ b/misc/lnstat_util.c @@ -138,7 +138,8 @@ static int lnstat_scan_fields(struct lnstat_file *lf) char buf[FGETS_BUF_SIZE]; rewind(lf->fp); - fgets(buf, sizeof(buf)-1, lf->fp); + if (!fgets(buf, sizeof(buf)-1, lf->fp)) + return -1; return __lnstat_scan_fields(lf, buf); } diff --git a/misc/nstat.c b/misc/nstat.c index 267e515f..99705286 100644 --- a/misc/nstat.c +++ b/misc/nstat.c @@ -649,7 +649,8 @@ int main(int argc, char *argv[]) } if (uptime >= 0 && time(NULL) >= stb.st_mtime+uptime) { fprintf(stderr, "nstat: history is aged out, resetting\n"); - ftruncate(fileno(hist_fp), 0); + if (ftruncate(fileno(hist_fp), 0) < 0) + perror("nstat: ftruncate"); } } @@ -693,7 +694,8 @@ int main(int argc, char *argv[]) dump_incr_db(stdout); } if (!no_update) { - ftruncate(fileno(hist_fp), 0); + if (ftruncate(fileno(hist_fp), 0) < 0) + perror("nstat: ftruncate"); rewind(hist_fp); json_output = 0; diff --git a/misc/ss.c b/misc/ss.c index 79285732..d5090094 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -528,7 +528,8 @@ static void user_ent_hash_build(void) snprintf(tmp, sizeof(tmp), "%s/%d/stat", root, pid); if ((fp = fopen(tmp, "r")) != NULL) { - fscanf(fp, "%*d (%[^)])", p); + if (fscanf(fp, "%*d (%[^)])", p) < 1) + ; /* ignore */ fclose(fp); } } @@ -660,7 +661,10 @@ static int get_slabstat(struct slabstat *s) cnt = sizeof(*s)/sizeof(int); - fgets(buf, sizeof(buf), fp); + if (!fgets(buf, sizeof(buf), fp)) { + fclose(fp); + return -1; + } while(fgets(buf, sizeof(buf), fp) != NULL) { int i; for (i=0; i Date: Sat, 28 Nov 2015 01:00:05 +0100 Subject: [PATCH 15/16] get rid of unnecessary fgets() buffer size limitation fgets() will read at most size-1 bytes into the buffer and add a terminating null-char at the end. Therefore it is not necessary to pass a reduced buffer size when calling it. This change was generated using the following semantic patch: @@ identifier buf, fp; @@ - fgets(buf, sizeof(buf) - 1, fp) + fgets(buf, sizeof(buf), fp) Signed-off-by: Phil Sutter --- misc/arpd.c | 2 +- misc/ss.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/arpd.c b/misc/arpd.c index 7919eb8b..6bb9bd16 100644 --- a/misc/arpd.c +++ b/misc/arpd.c @@ -703,7 +703,7 @@ int main(int argc, char **argv) } buf[sizeof(buf)-1] = 0; - while (fgets(buf, sizeof(buf)-1, fp)) { + while (fgets(buf, sizeof(buf), fp)) { __u8 b1[6]; char ipbuf[128]; char macbuf[128]; diff --git a/misc/ss.c b/misc/ss.c index d5090094..0dab32ce 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -2729,7 +2729,7 @@ static int unix_show(struct filter *f) if ((fp = net_unix_open()) == NULL) return -1; - if (!fgets(buf, sizeof(buf)-1, fp)) { + if (!fgets(buf, sizeof(buf), fp)) { fclose(fp); return -1; } @@ -2738,7 +2738,7 @@ static int unix_show(struct filter *f) newformat = 1; cnt = 0; - while (fgets(buf, sizeof(buf)-1, fp)) { + while (fgets(buf, sizeof(buf), fp)) { struct sockstat *u, **insp; int flags; @@ -3217,12 +3217,12 @@ static int netlink_show(struct filter *f) if ((fp = net_netlink_open()) == NULL) return -1; - if (!fgets(buf, sizeof(buf)-1, fp)) { + if (!fgets(buf, sizeof(buf), fp)) { fclose(fp); return -1; } - while (fgets(buf, sizeof(buf)-1, fp)) { + while (fgets(buf, sizeof(buf), fp)) { sscanf(buf, "%llx %d %d %x %d %d %llx %d", &sk, &prot, &pid, &groups, &rq, &wq, &cb, &rc); From 35f59d862fc9dec1e4af675c5ce776ba44be7eb7 Mon Sep 17 00:00:00 2001 From: Tom Herbert Date: Fri, 27 Nov 2015 10:23:43 -0800 Subject: [PATCH 16/16] vxlan: Add support for remote checksum offload This patch adds support to remote checksum checksum offload to VXLAN. This patch adds remcsumtx and remcsumrx to ip vxlan configuration to enable remote checksum offload for transmit and receive on the VXLAN tunnel. https://tools.ietf.org/html/draft-herbert-vxlan-rco-00 Example: ip link add name vxlan0 type vxlan id 42 group 239.1.1.1 dev eth0 \ udpcsum remcsumtx remcsumrx Testing: Ran single netperf over mlnx4 to illustrate the effest: - Without RCO (UDP csum set to zero) 4335.99 Mbps - With RCO enabled 7661.81 Mbps Signed-off-by: Tom Herbert --- ip/iplink_vxlan.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c index 473ff97a..db29bf03 100644 --- a/ip/iplink_vxlan.c +++ b/ip/iplink_vxlan.c @@ -30,6 +30,7 @@ static void print_explain(FILE *f) fprintf(f, " [ [no]l2miss ] [ [no]l3miss ]\n"); fprintf(f, " [ ageing SECONDS ] [ maxaddress NUMBER ]\n"); fprintf(f, " [ [no]udpcsum ] [ [no]udp6zerocsumtx ] [ [no]udp6zerocsumrx ]\n"); + fprintf(f, " [ [no]remcsumtx ] [ [no]remcsumrx ]\n"); fprintf(f, " [ gbp ]\n"); fprintf(f, "\n"); fprintf(f, "Where: VNI := 0-16777215\n"); @@ -69,6 +70,8 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, __u8 udpcsum = 0; __u8 udp6zerocsumtx = 0; __u8 udp6zerocsumrx = 0; + __u8 remcsumtx = 0; + __u8 remcsumrx = 0; __u8 gbp = 0; int dst_port_set = 0; struct ifla_vxlan_port_range range = { 0, 0 }; @@ -199,6 +202,14 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, udp6zerocsumrx = 1; } else if (!matches(*argv, "noudp6zerocsumrx")) { udp6zerocsumrx = 0; + } else if (!matches(*argv, "remcsumtx")) { + remcsumtx = 1; + } else if (!matches(*argv, "noremcsumtx")) { + remcsumtx = 0; + } else if (!matches(*argv, "remcsumrx")) { + remcsumrx = 1; + } else if (!matches(*argv, "noremcsumrx")) { + remcsumrx = 0; } else if (!matches(*argv, "gbp")) { gbp = 1; } else if (matches(*argv, "help") == 0) { @@ -259,6 +270,8 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv, addattr8(n, 1024, IFLA_VXLAN_UDP_CSUM, udpcsum); addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, udp6zerocsumtx); addattr8(n, 1024, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, udp6zerocsumrx); + addattr8(n, 1024, IFLA_VXLAN_REMCSUM_TX, remcsumtx); + addattr8(n, 1024, IFLA_VXLAN_REMCSUM_RX, remcsumrx); if (noage) addattr32(n, 1024, IFLA_VXLAN_AGEING, 0); @@ -407,6 +420,14 @@ static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) rta_getattr_u8(tb[IFLA_VXLAN_UDP_ZERO_CSUM6_RX])) fputs("udp6zerocsumrx ", f); + if (tb[IFLA_VXLAN_REMCSUM_TX] && + rta_getattr_u8(tb[IFLA_VXLAN_REMCSUM_TX])) + fputs("remcsumtx ", f); + + if (tb[IFLA_VXLAN_REMCSUM_RX] && + rta_getattr_u8(tb[IFLA_VXLAN_REMCSUM_RX])) + fputs("remcsumrx ", f); + if (tb[IFLA_VXLAN_GBP]) fputs("gbp ", f); }