1 /* SPDX-License-Identifier: GPL-2.0 */
3 * Get mdb table with netlink
10 #include <sys/socket.h>
12 #include <netinet/in.h>
13 #include <linux/if_bridge.h>
14 #include <linux/if_ether.h>
16 #include <arpa/inet.h>
18 #include "libnetlink.h"
19 #include "br_common.h"
22 #include "json_print.h"
26 ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg))))
29 static unsigned int filter_index
, filter_vlan
;
31 static void usage(void)
34 "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n"
35 " bridge mdb {show} [ dev DEV ] [ vid VID ]\n");
39 static bool is_temp_mcast_rtr(__u8 type
)
41 return type
== MDB_RTR_TYPE_TEMP_QUERY
|| type
== MDB_RTR_TYPE_TEMP
;
44 static const char *format_timer(__u32 ticks
, int align
)
49 __jiffies_to_tv(&tv
, ticks
);
51 snprintf(tbuf
, sizeof(tbuf
), "%4lu.%.2lu",
52 (unsigned long)tv
.tv_sec
,
53 (unsigned long)tv
.tv_usec
/ 10000);
55 snprintf(tbuf
, sizeof(tbuf
), "%lu.%.2lu",
56 (unsigned long)tv
.tv_sec
,
57 (unsigned long)tv
.tv_usec
/ 10000);
62 static void __print_router_port_stats(FILE *f
, struct rtattr
*pattr
)
64 struct rtattr
*tb
[MDBA_ROUTER_PATTR_MAX
+ 1];
66 parse_rtattr(tb
, MDBA_ROUTER_PATTR_MAX
, MDB_RTR_RTA(RTA_DATA(pattr
)),
67 RTA_PAYLOAD(pattr
) - RTA_ALIGN(sizeof(uint32_t)));
69 if (tb
[MDBA_ROUTER_PATTR_TIMER
]) {
70 __u32 timer
= rta_getattr_u32(tb
[MDBA_ROUTER_PATTR_TIMER
]);
72 print_string(PRINT_ANY
, "timer", " %s",
73 format_timer(timer
, 1));
76 if (tb
[MDBA_ROUTER_PATTR_TYPE
]) {
77 __u8 type
= rta_getattr_u8(tb
[MDBA_ROUTER_PATTR_TYPE
]);
79 print_string(PRINT_ANY
, "type", " %s",
80 is_temp_mcast_rtr(type
) ? "temp" : "permanent");
84 static void br_print_router_ports(FILE *f
, struct rtattr
*attr
,
87 int rem
= RTA_PAYLOAD(attr
);
90 if (is_json_context())
91 open_json_array(PRINT_JSON
, brifname
);
93 fprintf(f
, "router ports on %s: ", brifname
);
95 for (i
= RTA_DATA(attr
); RTA_OK(i
, rem
); i
= RTA_NEXT(i
, rem
)) {
96 uint32_t *port_ifindex
= RTA_DATA(i
);
97 const char *port_ifname
= ll_index_to_name(*port_ifindex
);
99 if (is_json_context()) {
100 open_json_object(NULL
);
101 print_string(PRINT_JSON
, "port", NULL
, port_ifname
);
104 __print_router_port_stats(f
, i
);
106 } else if (show_stats
) {
107 fprintf(f
, "router ports on %s: %s",
108 brifname
, port_ifname
);
110 __print_router_port_stats(f
, i
);
113 fprintf(f
, "%s ", port_ifname
);
120 close_json_array(PRINT_JSON
, NULL
);
123 static void print_src_entry(struct rtattr
*src_attr
, int af
, const char *sep
)
125 struct rtattr
*stb
[MDBA_MDB_SRCATTR_MAX
+ 1];
130 parse_rtattr_nested(stb
, MDBA_MDB_SRCATTR_MAX
, src_attr
);
131 if (!stb
[MDBA_MDB_SRCATTR_ADDRESS
] || !stb
[MDBA_MDB_SRCATTR_TIMER
])
134 addr
= inet_ntop(af
, RTA_DATA(stb
[MDBA_MDB_SRCATTR_ADDRESS
]), abuf
,
138 timer_val
= rta_getattr_u32(stb
[MDBA_MDB_SRCATTR_TIMER
]);
140 open_json_object(NULL
);
141 print_string(PRINT_FP
, NULL
, "%s", sep
);
142 print_color_string(PRINT_ANY
, ifa_family_color(af
),
143 "address", "%s", addr
);
144 print_string(PRINT_ANY
, "timer", "/%s", format_timer(timer_val
, 0));
148 static void print_mdb_entry(FILE *f
, int ifindex
, const struct br_mdb_entry
*e
,
149 struct nlmsghdr
*n
, struct rtattr
**tb
)
151 const void *grp
, *src
;
157 if (filter_vlan
&& e
->vid
!= filter_vlan
)
160 if (!e
->addr
.proto
) {
162 grp
= &e
->addr
.u
.mac_addr
;
163 } else if (e
->addr
.proto
== htons(ETH_P_IP
)) {
165 grp
= &e
->addr
.u
.ip4
;
168 grp
= &e
->addr
.u
.ip6
;
170 dev
= ll_index_to_name(ifindex
);
172 open_json_object(NULL
);
174 print_int(PRINT_JSON
, "index", NULL
, ifindex
);
175 print_color_string(PRINT_ANY
, COLOR_IFNAME
, "dev", "dev %s", dev
);
176 print_string(PRINT_ANY
, "port", " port %s",
177 ll_index_to_name(e
->ifindex
));
179 /* The ETH_ALEN argument is ignored for all cases but AF_PACKET */
180 addr
= rt_addr_n2a_r(af
, ETH_ALEN
, grp
, abuf
, sizeof(abuf
));
184 print_color_string(PRINT_ANY
, ifa_family_color(af
),
185 "grp", " grp %s", addr
);
187 if (tb
&& tb
[MDBA_MDB_EATTR_SOURCE
]) {
188 src
= (const void *)RTA_DATA(tb
[MDBA_MDB_EATTR_SOURCE
]);
189 print_color_string(PRINT_ANY
, ifa_family_color(af
),
191 inet_ntop(af
, src
, abuf
, sizeof(abuf
)));
193 print_string(PRINT_ANY
, "state", " %s",
194 (e
->state
& MDB_PERMANENT
) ? "permanent" : "temp");
195 if (show_details
&& tb
) {
196 if (tb
[MDBA_MDB_EATTR_GROUP_MODE
]) {
197 __u8 mode
= rta_getattr_u8(tb
[MDBA_MDB_EATTR_GROUP_MODE
]);
199 print_string(PRINT_ANY
, "filter_mode", " filter_mode %s",
200 mode
== MCAST_INCLUDE
? "include" :
203 if (tb
[MDBA_MDB_EATTR_SRC_LIST
]) {
204 struct rtattr
*i
, *attr
= tb
[MDBA_MDB_EATTR_SRC_LIST
];
205 const char *sep
= " ";
208 open_json_array(PRINT_ANY
, is_json_context() ?
211 rem
= RTA_PAYLOAD(attr
);
212 for (i
= RTA_DATA(attr
); RTA_OK(i
, rem
);
213 i
= RTA_NEXT(i
, rem
)) {
214 print_src_entry(i
, af
, sep
);
217 close_json_array(PRINT_JSON
, NULL
);
219 if (tb
[MDBA_MDB_EATTR_RTPROT
]) {
220 __u8 rtprot
= rta_getattr_u8(tb
[MDBA_MDB_EATTR_RTPROT
]);
223 print_string(PRINT_ANY
, "protocol", " proto %s ",
224 rtnl_rtprot_n2a(rtprot
, rtb
, sizeof(rtb
)));
228 open_json_array(PRINT_JSON
, "flags");
229 if (e
->flags
& MDB_FLAGS_OFFLOAD
)
230 print_string(PRINT_ANY
, NULL
, " %s", "offload");
231 if (e
->flags
& MDB_FLAGS_FAST_LEAVE
)
232 print_string(PRINT_ANY
, NULL
, " %s", "fast_leave");
233 if (e
->flags
& MDB_FLAGS_STAR_EXCL
)
234 print_string(PRINT_ANY
, NULL
, " %s", "added_by_star_ex");
235 if (e
->flags
& MDB_FLAGS_BLOCKED
)
236 print_string(PRINT_ANY
, NULL
, " %s", "blocked");
237 close_json_array(PRINT_JSON
, NULL
);
240 print_uint(PRINT_ANY
, "vid", " vid %u", e
->vid
);
242 if (show_stats
&& tb
&& tb
[MDBA_MDB_EATTR_TIMER
]) {
243 __u32 timer
= rta_getattr_u32(tb
[MDBA_MDB_EATTR_TIMER
]);
245 print_string(PRINT_ANY
, "timer", " %s",
246 format_timer(timer
, 1));
253 static void br_print_mdb_entry(FILE *f
, int ifindex
, struct rtattr
*attr
,
256 struct rtattr
*etb
[MDBA_MDB_EATTR_MAX
+ 1];
257 struct br_mdb_entry
*e
;
261 rem
= RTA_PAYLOAD(attr
);
262 for (i
= RTA_DATA(attr
); RTA_OK(i
, rem
); i
= RTA_NEXT(i
, rem
)) {
264 parse_rtattr_flags(etb
, MDBA_MDB_EATTR_MAX
, MDB_RTA(RTA_DATA(i
)),
265 RTA_PAYLOAD(i
) - RTA_ALIGN(sizeof(*e
)),
267 print_mdb_entry(f
, ifindex
, e
, n
, etb
);
271 static void print_mdb_entries(FILE *fp
, struct nlmsghdr
*n
,
272 int ifindex
, struct rtattr
*mdb
)
274 int rem
= RTA_PAYLOAD(mdb
);
277 for (i
= RTA_DATA(mdb
); RTA_OK(i
, rem
); i
= RTA_NEXT(i
, rem
))
278 br_print_mdb_entry(fp
, ifindex
, i
, n
);
281 static void print_router_entries(FILE *fp
, struct nlmsghdr
*n
,
282 int ifindex
, struct rtattr
*router
)
284 const char *brifname
= ll_index_to_name(ifindex
);
286 if (n
->nlmsg_type
== RTM_GETMDB
) {
288 br_print_router_ports(fp
, router
, brifname
);
290 struct rtattr
*i
= RTA_DATA(router
);
291 uint32_t *port_ifindex
= RTA_DATA(i
);
292 const char *port_name
= ll_index_to_name(*port_ifindex
);
294 if (is_json_context()) {
295 open_json_array(PRINT_JSON
, brifname
);
296 open_json_object(NULL
);
298 print_string(PRINT_JSON
, "port", NULL
,
301 close_json_array(PRINT_JSON
, NULL
);
303 fprintf(fp
, "router port dev %s master %s\n",
304 port_name
, brifname
);
309 static int __parse_mdb_nlmsg(struct nlmsghdr
*n
, struct rtattr
**tb
)
311 struct br_port_msg
*r
= NLMSG_DATA(n
);
312 int len
= n
->nlmsg_len
;
314 if (n
->nlmsg_type
!= RTM_GETMDB
&&
315 n
->nlmsg_type
!= RTM_NEWMDB
&&
316 n
->nlmsg_type
!= RTM_DELMDB
) {
318 "Not RTM_GETMDB, RTM_NEWMDB or RTM_DELMDB: %08x %08x %08x\n",
319 n
->nlmsg_len
, n
->nlmsg_type
, n
->nlmsg_flags
);
324 len
-= NLMSG_LENGTH(sizeof(*r
));
326 fprintf(stderr
, "BUG: wrong nlmsg len %d\n", len
);
330 if (filter_index
&& filter_index
!= r
->ifindex
)
333 parse_rtattr(tb
, MDBA_MAX
, MDBA_RTA(r
), n
->nlmsg_len
- NLMSG_LENGTH(sizeof(*r
)));
338 static int print_mdbs(struct nlmsghdr
*n
, void *arg
)
340 struct br_port_msg
*r
= NLMSG_DATA(n
);
341 struct rtattr
*tb
[MDBA_MAX
+1];
345 ret
= __parse_mdb_nlmsg(n
, tb
);
350 print_mdb_entries(fp
, n
, r
->ifindex
, tb
[MDBA_MDB
]);
355 static int print_rtrs(struct nlmsghdr
*n
, void *arg
)
357 struct br_port_msg
*r
= NLMSG_DATA(n
);
358 struct rtattr
*tb
[MDBA_MAX
+1];
362 ret
= __parse_mdb_nlmsg(n
, tb
);
367 print_router_entries(fp
, n
, r
->ifindex
, tb
[MDBA_ROUTER
]);
372 int print_mdb_mon(struct nlmsghdr
*n
, void *arg
)
374 struct br_port_msg
*r
= NLMSG_DATA(n
);
375 struct rtattr
*tb
[MDBA_MAX
+1];
379 ret
= __parse_mdb_nlmsg(n
, tb
);
383 if (n
->nlmsg_type
== RTM_DELMDB
)
384 print_bool(PRINT_ANY
, "deleted", "Deleted ", true);
387 print_mdb_entries(fp
, n
, r
->ifindex
, tb
[MDBA_MDB
]);
390 print_router_entries(fp
, n
, r
->ifindex
, tb
[MDBA_ROUTER
]);
395 static int mdb_show(int argc
, char **argv
)
397 char *filter_dev
= NULL
;
400 if (strcmp(*argv
, "dev") == 0) {
403 duparg("dev", *argv
);
405 } else if (strcmp(*argv
, "vid") == 0) {
408 duparg("vid", *argv
);
409 filter_vlan
= atoi(*argv
);
415 filter_index
= ll_name_to_index(filter_dev
);
417 return nodev(filter_dev
);
421 open_json_object(NULL
);
423 /* get mdb entries */
424 if (rtnl_mdbdump_req(&rth
, PF_BRIDGE
) < 0) {
425 perror("Cannot send dump request");
429 open_json_array(PRINT_JSON
, "mdb");
430 if (rtnl_dump_filter(&rth
, print_mdbs
, stdout
) < 0) {
431 fprintf(stderr
, "Dump terminated\n");
434 close_json_array(PRINT_JSON
, NULL
);
436 /* get router ports */
437 if (rtnl_mdbdump_req(&rth
, PF_BRIDGE
) < 0) {
438 perror("Cannot send dump request");
442 open_json_object("router");
443 if (rtnl_dump_filter(&rth
, print_rtrs
, stdout
) < 0) {
444 fprintf(stderr
, "Dump terminated\n");
456 static int mdb_parse_grp(const char *grp
, struct br_mdb_entry
*e
)
458 if (inet_pton(AF_INET
, grp
, &e
->addr
.u
.ip4
)) {
459 e
->addr
.proto
= htons(ETH_P_IP
);
462 if (inet_pton(AF_INET6
, grp
, &e
->addr
.u
.ip6
)) {
463 e
->addr
.proto
= htons(ETH_P_IPV6
);
466 if (ll_addr_a2n((char *)e
->addr
.u
.mac_addr
, sizeof(e
->addr
.u
.mac_addr
),
475 static int mdb_modify(int cmd
, int flags
, int argc
, char **argv
)
479 struct br_port_msg bpm
;
482 .n
.nlmsg_len
= NLMSG_LENGTH(sizeof(struct br_port_msg
)),
483 .n
.nlmsg_flags
= NLM_F_REQUEST
| flags
,
485 .bpm
.family
= PF_BRIDGE
,
487 char *d
= NULL
, *p
= NULL
, *grp
= NULL
, *src
= NULL
;
488 struct br_mdb_entry entry
= {};
492 if (strcmp(*argv
, "dev") == 0) {
495 } else if (strcmp(*argv
, "grp") == 0) {
498 } else if (strcmp(*argv
, "port") == 0) {
501 } else if (strcmp(*argv
, "permanent") == 0) {
502 if (cmd
== RTM_NEWMDB
)
503 entry
.state
|= MDB_PERMANENT
;
504 } else if (strcmp(*argv
, "temp") == 0) {
506 } else if (strcmp(*argv
, "vid") == 0) {
509 } else if (strcmp(*argv
, "src") == 0) {
513 if (matches(*argv
, "help") == 0)
519 if (d
== NULL
|| grp
== NULL
|| p
== NULL
) {
520 fprintf(stderr
, "Device, group address and port name are required arguments.\n");
524 req
.bpm
.ifindex
= ll_name_to_index(d
);
525 if (!req
.bpm
.ifindex
)
528 entry
.ifindex
= ll_name_to_index(p
);
532 if (mdb_parse_grp(grp
, &entry
)) {
533 fprintf(stderr
, "Invalid address \"%s\"\n", grp
);
538 addattr_l(&req
.n
, sizeof(req
), MDBA_SET_ENTRY
, &entry
, sizeof(entry
));
540 struct rtattr
*nest
= addattr_nest(&req
.n
, sizeof(req
),
541 MDBA_SET_ENTRY_ATTRS
);
542 struct in6_addr src_ip6
;
545 nest
->rta_type
|= NLA_F_NESTED
;
546 if (!inet_pton(AF_INET
, src
, &src_ip4
)) {
547 if (!inet_pton(AF_INET6
, src
, &src_ip6
)) {
548 fprintf(stderr
, "Invalid source address \"%s\"\n", src
);
551 addattr_l(&req
.n
, sizeof(req
), MDBE_ATTR_SOURCE
, &src_ip6
, sizeof(src_ip6
));
553 addattr32(&req
.n
, sizeof(req
), MDBE_ATTR_SOURCE
, src_ip4
);
555 addattr_nest_end(&req
.n
, nest
);
558 if (rtnl_talk(&rth
, &req
.n
, NULL
) < 0)
564 int do_mdb(int argc
, char **argv
)
569 if (matches(*argv
, "add") == 0)
570 return mdb_modify(RTM_NEWMDB
, NLM_F_CREATE
|NLM_F_EXCL
, argc
-1, argv
+1);
571 if (matches(*argv
, "delete") == 0)
572 return mdb_modify(RTM_DELMDB
, 0, argc
-1, argv
+1);
574 if (matches(*argv
, "show") == 0 ||
575 matches(*argv
, "lst") == 0 ||
576 matches(*argv
, "list") == 0)
577 return mdb_show(argc
-1, argv
+1);
578 if (matches(*argv
, "help") == 0)
581 return mdb_show(0, NULL
);
583 fprintf(stderr
, "Command \"%s\" is unknown, try \"bridge mdb help\".\n", *argv
);