From 547b31976215e596a3661f3a1b86cd109afd6f0e Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:19 +0300 Subject: [PATCH 1/6] bridge: mdb: add support for source address This patch adds the user-space control and dump of mdb entry source address. When setting the new MDBA_SET_ENTRY_ATTRS nested attribute is used and inside is added MDBE_ATTR_SOURCE based on the address family. When dumping we look for MDBA_MDB_EATTR_SOURCE and if present we add the "src x.x.x.x" output. The source address will be always shown as it's needed to match the entry to modify it from user-space. Example: $ bridge mdb add dev bridge port ens13 grp 239.0.0.1 src 1.2.3.4 permanent vid 100 $ bridge mdb show dev bridge port ens13 grp 239.0.0.1 src 1.2.3.4 permanent vid 100 Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 38 ++++++++++++++++++++++++++++++++------ man/man8/bridge.8 | 8 ++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/bridge/mdb.c b/bridge/mdb.c index 928ae56d..01c8a6e3 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -31,7 +31,7 @@ static unsigned int filter_index, filter_vlan; static void usage(void) { fprintf(stderr, - "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [permanent | temp] [vid VID]\n" + "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n" " bridge mdb {show} [ dev DEV ] [ vid VID ]\n"); exit(-1); } @@ -118,16 +118,16 @@ static void br_print_router_ports(FILE *f, struct rtattr *attr, static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, struct nlmsghdr *n, struct rtattr **tb) { + const void *grp, *src; SPRINT_BUF(abuf); const char *dev; - const void *src; int af; if (filter_vlan && e->vid != filter_vlan) return; af = e->addr.proto == htons(ETH_P_IP) ? AF_INET : AF_INET6; - src = af == AF_INET ? (const void *)&e->addr.u.ip4 : + grp = af == AF_INET ? (const void *)&e->addr.u.ip4 : (const void *)&e->addr.u.ip6; dev = ll_index_to_name(ifindex); @@ -140,8 +140,13 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, print_color_string(PRINT_ANY, ifa_family_color(af), "grp", " grp %s", - inet_ntop(af, src, abuf, sizeof(abuf))); - + inet_ntop(af, grp, abuf, sizeof(abuf))); + if (tb && tb[MDBA_MDB_EATTR_SOURCE]) { + src = (const void *)RTA_DATA(tb[MDBA_MDB_EATTR_SOURCE]); + print_color_string(PRINT_ANY, ifa_family_color(af), + "src", " src %s", + inet_ntop(af, src, abuf, sizeof(abuf))); + } print_string(PRINT_ANY, "state", " %s", (e->state & MDB_PERMANENT) ? "permanent" : "temp"); @@ -378,8 +383,8 @@ static int mdb_modify(int cmd, int flags, int argc, char **argv) .n.nlmsg_type = cmd, .bpm.family = PF_BRIDGE, }; + char *d = NULL, *p = NULL, *grp = NULL, *src = NULL; struct br_mdb_entry entry = {}; - char *d = NULL, *p = NULL, *grp = NULL; short vid = 0; while (argc > 0) { @@ -400,6 +405,9 @@ static int mdb_modify(int cmd, int flags, int argc, char **argv) } else if (strcmp(*argv, "vid") == 0) { NEXT_ARG(); vid = atoi(*argv); + } else if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + src = *argv; } else { if (matches(*argv, "help") == 0) usage(); @@ -431,6 +439,24 @@ static int mdb_modify(int cmd, int flags, int argc, char **argv) entry.vid = vid; addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (src) { + struct rtattr *nest = addattr_nest(&req.n, sizeof(req), + MDBA_SET_ENTRY_ATTRS); + struct in6_addr src_ip6; + __be32 src_ip4; + + nest->rta_type |= NLA_F_NESTED; + if (!inet_pton(AF_INET, src, &src_ip4)) { + if (!inet_pton(AF_INET6, src, &src_ip6)) { + fprintf(stderr, "Invalid source address \"%s\"\n", src); + return -1; + } + addattr_l(&req.n, sizeof(req), MDBE_ATTR_SOURCE, &src_ip6, sizeof(src_ip6)); + } else { + addattr32(&req.n, sizeof(req), MDBE_ATTR_SOURCE, src_ip4); + } + addattr_nest_end(&req.n, nest); + } if (rtnl_talk(&rth, &req.n, NULL) < 0) return -1; diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index b0600576..84b9b70c 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -116,6 +116,8 @@ bridge \- show / manipulate bridge addresses and devices .I PORT .B grp .IR GROUP " [ " +.B src +.IR SOURCE " ] [ " .BR permanent " | " temp " ] [ " .B vid .IR VID " ] " @@ -694,6 +696,12 @@ the port. - the mdb entry is temporary (default) .sp +.TP +.BI src " SOURCE" +optional source IP address of a sender for this multicast group. If IGMPv3 for IPv4, or +MLDv2 for IPv6 respectively, are enabled it will be included in the lookup when +forwarding multicast traffic. + .TP .BI vid " VID" the VLAN ID which is known to have members of this multicast group. From f94e8b07498ab2d283b212535e868a801a55b445 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:20 +0300 Subject: [PATCH 2/6] bridge: mdb: print fast_leave flag We're not showing the fast_leave flag when it's set. Currently that can be only when an mdb entry is being deleted due to fast leave, so it will only affect mdb monitor. Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bridge/mdb.c b/bridge/mdb.c index 01c8a6e3..94cd3c3b 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -153,6 +153,8 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, open_json_array(PRINT_JSON, "flags"); if (e->flags & MDB_FLAGS_OFFLOAD) print_string(PRINT_ANY, NULL, " %s", "offload"); + if (e->flags & MDB_FLAGS_FAST_LEAVE) + print_string(PRINT_ANY, NULL, " %s", "fast_leave"); close_json_array(PRINT_JSON, NULL); if (e->vid) From e331677ea2bb85f45dcb8d589c512900a84c7374 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:21 +0300 Subject: [PATCH 3/6] bridge: mdb: show igmpv3/mldv2 flags With IGMPv3/MLDv2 support we have 2 new flags: - added_by_star_ex: set when the S,G entry was automatically created because of a *,G entry in EXCLUDE mode - blocked: set when traffic for the S,G entry for that port has to be blocked Both flags are used only on the new S,G entries and are currently kernel managed, i.e. similar to other flags which can't be set from user-space. Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bridge/mdb.c b/bridge/mdb.c index 94cd3c3b..d33bd5d5 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -155,6 +155,10 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, print_string(PRINT_ANY, NULL, " %s", "offload"); if (e->flags & MDB_FLAGS_FAST_LEAVE) print_string(PRINT_ANY, NULL, " %s", "fast_leave"); + if (e->flags & MDB_FLAGS_STAR_EXCL) + print_string(PRINT_ANY, NULL, " %s", "added_by_star_ex"); + if (e->flags & MDB_FLAGS_BLOCKED) + print_string(PRINT_ANY, NULL, " %s", "blocked"); close_json_array(PRINT_JSON, NULL); if (e->vid) From 1d28c4804610f8fe0aa8afc47cc09c2496d4a31f Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:22 +0300 Subject: [PATCH 4/6] bridge: mdb: print filter mode when available Print the mdb entry's filter mode when it's available if the user requested to show details (-d). It can be either include or exclude. Currently it's kernel controlled and can't be changed by user-space. Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bridge/mdb.c b/bridge/mdb.c index d33bd5d5..c0cb4fd1 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -150,6 +150,13 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, print_string(PRINT_ANY, "state", " %s", (e->state & MDB_PERMANENT) ? "permanent" : "temp"); + if (show_details && tb && tb[MDBA_MDB_EATTR_GROUP_MODE]) { + __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]); + + print_string(PRINT_ANY, "filter_mode", " filter_mode %s", + mode == MCAST_INCLUDE ? "include" : "exclude"); + } + open_json_array(PRINT_JSON, "flags"); if (e->flags & MDB_FLAGS_OFFLOAD) print_string(PRINT_ANY, NULL, " %s", "offload"); From 2de81d1eff2a7f588541d567dd9536584aa939ed Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:23 +0300 Subject: [PATCH 5/6] bridge: mdb: print source list when available Print the mdb entry's source list when it's available if the user requested to show details (-d). Each source has an associated timer which controls if traffic should be forwarded to that S,G entry (if the timer is non-zero traffic is forwarded, otherwise it's not). Currently the source list is kernel controlled and can't be changed by user-space. Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 73 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/bridge/mdb.c b/bridge/mdb.c index c0cb4fd1..b3b58a23 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -41,15 +41,20 @@ static bool is_temp_mcast_rtr(__u8 type) return type == MDB_RTR_TYPE_TEMP_QUERY || type == MDB_RTR_TYPE_TEMP; } -static const char *format_timer(__u32 ticks) +static const char *format_timer(__u32 ticks, int align) { struct timeval tv; static char tbuf[32]; __jiffies_to_tv(&tv, ticks); - snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu", - (unsigned long)tv.tv_sec, - (unsigned long)tv.tv_usec / 10000); + if (align) + snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); + else + snprintf(tbuf, sizeof(tbuf), "%lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); return tbuf; } @@ -65,7 +70,7 @@ static void __print_router_port_stats(FILE *f, struct rtattr *pattr) __u32 timer = rta_getattr_u32(tb[MDBA_ROUTER_PATTR_TIMER]); print_string(PRINT_ANY, "timer", " %s", - format_timer(timer)); + format_timer(timer, 1)); } if (tb[MDBA_ROUTER_PATTR_TYPE]) { @@ -115,6 +120,31 @@ static void br_print_router_ports(FILE *f, struct rtattr *attr, close_json_array(PRINT_JSON, NULL); } +static void print_src_entry(struct rtattr *src_attr, int af, const char *sep) +{ + struct rtattr *stb[MDBA_MDB_SRCATTR_MAX + 1]; + SPRINT_BUF(abuf); + const char *addr; + __u32 timer_val; + + parse_rtattr_nested(stb, MDBA_MDB_SRCATTR_MAX, src_attr); + if (!stb[MDBA_MDB_SRCATTR_ADDRESS] || !stb[MDBA_MDB_SRCATTR_TIMER]) + return; + + addr = inet_ntop(af, RTA_DATA(stb[MDBA_MDB_SRCATTR_ADDRESS]), abuf, + sizeof(abuf)); + if (!addr) + return; + timer_val = rta_getattr_u32(stb[MDBA_MDB_SRCATTR_TIMER]); + + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%s", sep); + print_color_string(PRINT_ANY, ifa_family_color(af), + "address", "%s", addr); + print_string(PRINT_ANY, "timer", "/%s", format_timer(timer_val, 0)); + close_json_object(); +} + static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, struct nlmsghdr *n, struct rtattr **tb) { @@ -149,12 +179,30 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, } print_string(PRINT_ANY, "state", " %s", (e->state & MDB_PERMANENT) ? "permanent" : "temp"); + if (show_details && tb) { + if (tb[MDBA_MDB_EATTR_GROUP_MODE]) { + __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]); - if (show_details && tb && tb[MDBA_MDB_EATTR_GROUP_MODE]) { - __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]); + print_string(PRINT_ANY, "filter_mode", " filter_mode %s", + mode == MCAST_INCLUDE ? "include" : + "exclude"); + } + if (tb[MDBA_MDB_EATTR_SRC_LIST]) { + struct rtattr *i, *attr = tb[MDBA_MDB_EATTR_SRC_LIST]; + const char *sep = " "; + int rem; - print_string(PRINT_ANY, "filter_mode", " filter_mode %s", - mode == MCAST_INCLUDE ? "include" : "exclude"); + open_json_array(PRINT_ANY, is_json_context() ? + "source_list" : + " source_list"); + rem = RTA_PAYLOAD(attr); + for (i = RTA_DATA(attr); RTA_OK(i, rem); + i = RTA_NEXT(i, rem)) { + print_src_entry(i, af, sep); + sep = ","; + } + close_json_array(PRINT_JSON, NULL); + } } open_json_array(PRINT_JSON, "flags"); @@ -175,7 +223,7 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, __u32 timer = rta_getattr_u32(tb[MDBA_MDB_EATTR_TIMER]); print_string(PRINT_ANY, "timer", " %s", - format_timer(timer)); + format_timer(timer, 1)); } print_nl(); @@ -193,8 +241,9 @@ static void br_print_mdb_entry(FILE *f, int ifindex, struct rtattr *attr, rem = RTA_PAYLOAD(attr); for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { e = RTA_DATA(i); - parse_rtattr(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)), - RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e))); + parse_rtattr_flags(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)), + RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e)), + NLA_F_NESTED); print_mdb_entry(f, ifindex, e, n, etb); } } From 86588450c54e68e4351899d7139f20013f0f2e09 Mon Sep 17 00:00:00 2001 From: Nikolay Aleksandrov Date: Thu, 8 Oct 2020 16:50:24 +0300 Subject: [PATCH 6/6] bridge: mdb: print protocol when available Print the mdb entry's protocol (i.e. who added it) when it's available if the user requested to show details (-d). Currently the only possible values are RTPROT_STATIC (user-space added) or RTPROT_KERNEL (automatically added by kernel). The value is kernel controlled. Signed-off-by: Nikolay Aleksandrov Signed-off-by: David Ahern --- bridge/mdb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bridge/mdb.c b/bridge/mdb.c index b3b58a23..4cd7ca76 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -203,6 +203,13 @@ static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, } close_json_array(PRINT_JSON, NULL); } + if (tb[MDBA_MDB_EATTR_RTPROT]) { + __u8 rtprot = rta_getattr_u8(tb[MDBA_MDB_EATTR_RTPROT]); + SPRINT_BUF(rtb); + + print_string(PRINT_ANY, "protocol", " proto %s ", + rtnl_rtprot_n2a(rtprot, rtb, sizeof(rtb))); + } } open_json_array(PRINT_JSON, "flags");