mirror of
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git
synced 2026-01-26 14:13:24 +00:00
Dump the multicast querier state per vlan. This commit is almost identical to [1]. The querier state can be seen with: bridge -d vlan global The options for vlan filtering and vlan mcast snooping have to be enabled in order to see the output: ip link set [dev] type bridge mcast_vlan_snooping 1 vlan_filtering 1 The querier state shows the following information for IPv4 and IPv6 respectively: 1) The ip address of the current querier in the network. This could be ourselves or an external querier. 2) The port on which the querier was seen 3) Querier timeout in seconds [1] https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=16aa4494d7fc6543e5e92beb2ce01648b79f8fa2 Reviewed-by: Ido Schimmel <idosch@nvidia.com> Acked-by: Nikolay Aleksandrov <razor@blackwall.org> Signed-off-by: Fabian Pfitzner <f.pfitzner@pengutronix.de> Signed-off-by: David Ahern <dsahern@kernel.org>
1359 lines
36 KiB
C
1359 lines
36 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <linux/if_bridge.h>
|
|
#include <linux/if_ether.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "json_print.h"
|
|
#include "libnetlink.h"
|
|
#include "br_common.h"
|
|
#include "bridge.h"
|
|
#include "utils.h"
|
|
|
|
static unsigned int filter_index, filter_vlan;
|
|
static int vlan_rtm_cur_ifidx = -1;
|
|
static void print_vlan_info(struct rtattr *tb, int ifindex);
|
|
|
|
enum vlan_show_subject {
|
|
VLAN_SHOW_VLAN,
|
|
VLAN_SHOW_TUNNELINFO,
|
|
};
|
|
|
|
#define VLAN_ID_LEN 9
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ tunnel_info id TUNNEL_ID ]\n"
|
|
" [ pvid ] [ untagged ]\n"
|
|
" [ self ] [ master ]\n"
|
|
" bridge vlan { set } vid VLAN_ID dev DEV [ state STP_STATE ]\n"
|
|
" [ mcast_router MULTICAST_ROUTER ]\n"
|
|
" [ mcast_max_groups MAX_GROUPS ]\n"
|
|
" [ neigh_suppress {on | off} ]\n"
|
|
" bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n"
|
|
" bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n"
|
|
" bridge vlan global { set } vid VLAN_ID dev DEV\n"
|
|
" [ mcast_snooping MULTICAST_SNOOPING ]\n"
|
|
" [ mcast_querier MULTICAST_QUERIER ]\n"
|
|
" [ mcast_igmp_version IGMP_VERSION ]\n"
|
|
" [ mcast_mld_version MLD_VERSION ]\n"
|
|
" [ mcast_last_member_count LAST_MEMBER_COUNT ]\n"
|
|
" [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n"
|
|
" [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n"
|
|
" [ mcast_startup_query_interval STARTUP_QUERY_INTERVAL ]\n"
|
|
" [ mcast_membership_interval MEMBERSHIP_INTERVAL ]\n"
|
|
" [ mcast_querier_interval QUERIER_INTERVAL ]\n"
|
|
" [ mcast_query_interval QUERY_INTERVAL ]\n"
|
|
" [ mcast_query_response_interval QUERY_RESPONSE_INTERVAL ]\n"
|
|
" [ msti MSTI ]\n"
|
|
" bridge vlan global { show } [ dev DEV ] [ vid VLAN_ID ]\n");
|
|
exit(-1);
|
|
}
|
|
|
|
static int parse_tunnel_info(int *argcp, char ***argvp, __u32 *tun_id_start,
|
|
__u32 *tun_id_end)
|
|
{
|
|
char **argv = *argvp;
|
|
int argc = *argcp;
|
|
char *t;
|
|
|
|
NEXT_ARG();
|
|
if (!matches(*argv, "id")) {
|
|
NEXT_ARG();
|
|
t = strchr(*argv, '-');
|
|
if (t) {
|
|
*t = '\0';
|
|
if (get_u32(tun_id_start, *argv, 0) ||
|
|
*tun_id_start >= 1u << 24)
|
|
invarg("invalid tun id", *argv);
|
|
if (get_u32(tun_id_end, t + 1, 0) ||
|
|
*tun_id_end >= 1u << 24)
|
|
invarg("invalid tun id", *argv);
|
|
|
|
} else {
|
|
if (get_u32(tun_id_start, *argv, 0) ||
|
|
*tun_id_start >= 1u << 24)
|
|
invarg("invalid tun id", *argv);
|
|
}
|
|
} else {
|
|
invarg("tunnel id expected", *argv);
|
|
}
|
|
|
|
*argcp = argc;
|
|
*argvp = argv;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_tunnel_info(struct nlmsghdr *n, int reqsize,
|
|
__u16 vid, __u32 tun_id, __u16 flags)
|
|
{
|
|
struct rtattr *tinfo;
|
|
|
|
tinfo = addattr_nest(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
|
|
addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_ID, tun_id);
|
|
addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid);
|
|
addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags);
|
|
|
|
addattr_nest_end(n, tinfo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_tunnel_info_range(struct nlmsghdr *n, int reqsize,
|
|
__u16 vid_start, int16_t vid_end,
|
|
__u32 tun_id_start, __u32 tun_id_end)
|
|
{
|
|
if (vid_end != -1 && (vid_end - vid_start) > 0) {
|
|
add_tunnel_info(n, reqsize, vid_start, tun_id_start,
|
|
BRIDGE_VLAN_INFO_RANGE_BEGIN);
|
|
|
|
add_tunnel_info(n, reqsize, vid_end, tun_id_end,
|
|
BRIDGE_VLAN_INFO_RANGE_END);
|
|
} else {
|
|
add_tunnel_info(n, reqsize, vid_start, tun_id_start, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_vlan_info_range(struct nlmsghdr *n, int reqsize, __u16 vid_start,
|
|
int16_t vid_end, __u16 flags)
|
|
{
|
|
struct bridge_vlan_info vinfo = {};
|
|
|
|
vinfo.flags = flags;
|
|
vinfo.vid = vid_start;
|
|
if (vid_end != -1) {
|
|
/* send vlan range start */
|
|
addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
|
|
sizeof(vinfo));
|
|
vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
|
|
|
|
/* Now send the vlan range end */
|
|
vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
|
|
vinfo.vid = vid_end;
|
|
addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
|
|
sizeof(vinfo));
|
|
} else {
|
|
addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
|
|
sizeof(vinfo));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vlan_modify(int cmd, int argc, char **argv)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct ifinfomsg ifm;
|
|
char buf[1024];
|
|
} req = {
|
|
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
|
|
.n.nlmsg_flags = NLM_F_REQUEST,
|
|
.n.nlmsg_type = cmd,
|
|
.ifm.ifi_family = PF_BRIDGE,
|
|
};
|
|
char *d = NULL;
|
|
short vid = -1;
|
|
short vid_end = -1;
|
|
struct rtattr *afspec;
|
|
struct bridge_vlan_info vinfo = {};
|
|
bool tunnel_info_set = false;
|
|
unsigned short flags = 0;
|
|
__u32 tun_id_start = 0;
|
|
__u32 tun_id_end = 0;
|
|
|
|
while (argc > 0) {
|
|
if (strcmp(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
d = *argv;
|
|
} else if (strcmp(*argv, "vid") == 0) {
|
|
char *p;
|
|
|
|
NEXT_ARG();
|
|
p = strchr(*argv, '-');
|
|
if (p) {
|
|
*p = '\0';
|
|
p++;
|
|
vid = atoi(*argv);
|
|
vid_end = atoi(p);
|
|
vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
|
|
} else {
|
|
vid = atoi(*argv);
|
|
}
|
|
} else if (strcmp(*argv, "self") == 0) {
|
|
flags |= BRIDGE_FLAGS_SELF;
|
|
} else if (strcmp(*argv, "master") == 0) {
|
|
flags |= BRIDGE_FLAGS_MASTER;
|
|
} else if (strcmp(*argv, "pvid") == 0) {
|
|
vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
|
|
} else if (strcmp(*argv, "untagged") == 0) {
|
|
vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
|
|
} else if (strcmp(*argv, "tunnel_info") == 0) {
|
|
if (parse_tunnel_info(&argc, &argv,
|
|
&tun_id_start,
|
|
&tun_id_end))
|
|
return -1;
|
|
tunnel_info_set = true;
|
|
} else {
|
|
if (matches(*argv, "help") == 0)
|
|
NEXT_ARG();
|
|
}
|
|
argc--; argv++;
|
|
}
|
|
|
|
if (d == NULL || vid == -1) {
|
|
fprintf(stderr, "Device and VLAN ID are required arguments.\n");
|
|
return -1;
|
|
}
|
|
|
|
req.ifm.ifi_index = ll_name_to_index(d);
|
|
if (req.ifm.ifi_index == 0) {
|
|
fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
|
|
return -1;
|
|
}
|
|
|
|
if (vid >= 4096) {
|
|
fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
|
|
return -1;
|
|
}
|
|
|
|
if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
|
|
if (vid_end == -1 || vid_end >= 4096 || vid >= vid_end) {
|
|
fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
|
|
vid, vid_end);
|
|
return -1;
|
|
}
|
|
if (vinfo.flags & BRIDGE_VLAN_INFO_PVID) {
|
|
fprintf(stderr,
|
|
"pvid cannot be configured for a vlan range\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
|
|
|
|
if (flags)
|
|
addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
|
|
|
|
if (tunnel_info_set)
|
|
add_tunnel_info_range(&req.n, sizeof(req), vid, vid_end,
|
|
tun_id_start, tun_id_end);
|
|
else
|
|
add_vlan_info_range(&req.n, sizeof(req), vid, vid_end,
|
|
vinfo.flags);
|
|
|
|
addattr_nest_end(&req.n, afspec);
|
|
|
|
if (rtnl_talk(&rth, &req.n, NULL) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vlan_option_set(int argc, char **argv)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct br_vlan_msg bvm;
|
|
char buf[1024];
|
|
} req = {
|
|
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)),
|
|
.n.nlmsg_flags = NLM_F_REQUEST,
|
|
.n.nlmsg_type = RTM_NEWVLAN,
|
|
.bvm.family = PF_BRIDGE,
|
|
};
|
|
struct bridge_vlan_info vinfo = {};
|
|
struct rtattr *afspec;
|
|
char *d = NULL;
|
|
short vid = -1;
|
|
|
|
afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY);
|
|
afspec->rta_type |= NLA_F_NESTED;
|
|
while (argc > 0) {
|
|
if (strcmp(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
d = *argv;
|
|
req.bvm.ifindex = ll_name_to_index(d);
|
|
if (req.bvm.ifindex == 0) {
|
|
fprintf(stderr,
|
|
"Cannot find network device \"%s\"\n",
|
|
d);
|
|
return -1;
|
|
}
|
|
} else if (strcmp(*argv, "vid") == 0) {
|
|
short vid_end = -1;
|
|
char *p;
|
|
|
|
NEXT_ARG();
|
|
p = strchr(*argv, '-');
|
|
if (p) {
|
|
*p = '\0';
|
|
p++;
|
|
vid = atoi(*argv);
|
|
vid_end = atoi(p);
|
|
if (vid >= vid_end || vid_end >= 4096) {
|
|
fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
|
|
vid, vid_end);
|
|
return -1;
|
|
}
|
|
} else {
|
|
vid = atoi(*argv);
|
|
}
|
|
if (vid >= 4096) {
|
|
fprintf(stderr, "Invalid VLAN ID \"%hu\"\n",
|
|
vid);
|
|
return -1;
|
|
}
|
|
|
|
vinfo.flags = BRIDGE_VLAN_INFO_ONLY_OPTS;
|
|
vinfo.vid = vid;
|
|
addattr_l(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_INFO,
|
|
&vinfo, sizeof(vinfo));
|
|
if (vid_end != -1)
|
|
addattr16(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_ENTRY_RANGE, vid_end);
|
|
} else if (strcmp(*argv, "state") == 0) {
|
|
char *endptr;
|
|
int state;
|
|
|
|
NEXT_ARG();
|
|
state = strtol(*argv, &endptr, 10);
|
|
if (!(**argv != '\0' && *endptr == '\0'))
|
|
state = parse_stp_state(*argv);
|
|
if (state == -1) {
|
|
fprintf(stderr, "Error: invalid STP state\n");
|
|
return -1;
|
|
}
|
|
addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE,
|
|
state);
|
|
} else if (strcmp(*argv, "mcast_router") == 0) {
|
|
__u8 mcast_router;
|
|
|
|
NEXT_ARG();
|
|
if (get_u8(&mcast_router, *argv, 0))
|
|
invarg("invalid mcast_router", *argv);
|
|
addattr8(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
|
|
mcast_router);
|
|
} else if (strcmp(*argv, "mcast_max_groups") == 0) {
|
|
__u32 max_groups;
|
|
|
|
NEXT_ARG();
|
|
if (get_u32(&max_groups, *argv, 0))
|
|
invarg("invalid mcast_max_groups", *argv);
|
|
addattr32(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS,
|
|
max_groups);
|
|
} else if (strcmp(*argv, "neigh_suppress") == 0) {
|
|
bool neigh_suppress;
|
|
int ret;
|
|
|
|
NEXT_ARG();
|
|
neigh_suppress = parse_on_off("neigh_suppress", *argv,
|
|
&ret);
|
|
if (ret)
|
|
return ret;
|
|
addattr8(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS,
|
|
neigh_suppress);
|
|
} else {
|
|
if (matches(*argv, "help") == 0)
|
|
NEXT_ARG();
|
|
}
|
|
argc--; argv++;
|
|
}
|
|
addattr_nest_end(&req.n, afspec);
|
|
|
|
if (d == NULL || vid == -1) {
|
|
fprintf(stderr, "Device and VLAN ID are required arguments.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (rtnl_talk(&rth, &req.n, NULL) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vlan_global_option_set(int argc, char **argv)
|
|
{
|
|
struct {
|
|
struct nlmsghdr n;
|
|
struct br_vlan_msg bvm;
|
|
char buf[1024];
|
|
} req = {
|
|
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)),
|
|
.n.nlmsg_flags = NLM_F_REQUEST,
|
|
.n.nlmsg_type = RTM_NEWVLAN,
|
|
.bvm.family = PF_BRIDGE,
|
|
};
|
|
struct rtattr *afspec;
|
|
short vid_end = -1;
|
|
char *d = NULL;
|
|
short vid = -1;
|
|
__u64 val64;
|
|
__u32 val32;
|
|
__u16 val16;
|
|
__u8 val8;
|
|
|
|
afspec = addattr_nest(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_GLOBAL_OPTIONS);
|
|
afspec->rta_type |= NLA_F_NESTED;
|
|
while (argc > 0) {
|
|
if (strcmp(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
d = *argv;
|
|
req.bvm.ifindex = ll_name_to_index(d);
|
|
if (req.bvm.ifindex == 0) {
|
|
fprintf(stderr, "Cannot find network device \"%s\"\n",
|
|
d);
|
|
return -1;
|
|
}
|
|
} else if (strcmp(*argv, "vid") == 0) {
|
|
char *p;
|
|
|
|
NEXT_ARG();
|
|
p = strchr(*argv, '-');
|
|
if (p) {
|
|
*p = '\0';
|
|
p++;
|
|
vid = atoi(*argv);
|
|
vid_end = atoi(p);
|
|
if (vid >= vid_end || vid_end >= 4096) {
|
|
fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
|
|
vid, vid_end);
|
|
return -1;
|
|
}
|
|
} else {
|
|
vid = atoi(*argv);
|
|
}
|
|
if (vid >= 4096) {
|
|
fprintf(stderr, "Invalid VLAN ID \"%hu\"\n",
|
|
vid);
|
|
return -1;
|
|
}
|
|
addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_GOPTS_ID,
|
|
vid);
|
|
if (vid_end != -1)
|
|
addattr16(&req.n, sizeof(req),
|
|
BRIDGE_VLANDB_GOPTS_RANGE, vid_end);
|
|
} else if (strcmp(*argv, "mcast_snooping") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u8(&val8, *argv, 0))
|
|
invarg("invalid mcast_snooping", *argv);
|
|
addattr8(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING, val8);
|
|
} else if (strcmp(*argv, "mcast_querier") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u8(&val8, *argv, 0))
|
|
invarg("invalid mcast_querier", *argv);
|
|
addattr8(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_QUERIER, val8);
|
|
} else if (strcmp(*argv, "mcast_igmp_version") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u8(&val8, *argv, 0))
|
|
invarg("invalid mcast_igmp_version", *argv);
|
|
addattr8(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION, val8);
|
|
} else if (strcmp(*argv, "mcast_mld_version") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u8(&val8, *argv, 0))
|
|
invarg("invalid mcast_mld_version", *argv);
|
|
addattr8(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION, val8);
|
|
} else if (strcmp(*argv, "mcast_last_member_count") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u32(&val32, *argv, 0))
|
|
invarg("invalid mcast_last_member_count", *argv);
|
|
addattr32(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
|
|
val32);
|
|
} else if (strcmp(*argv, "mcast_startup_query_count") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u32(&val32, *argv, 0))
|
|
invarg("invalid mcast_startup_query_count",
|
|
*argv);
|
|
addattr32(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
|
|
val32);
|
|
} else if (strcmp(*argv, "mcast_last_member_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_last_member_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "mcast_membership_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_membership_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "mcast_querier_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_querier_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "mcast_query_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_query_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "mcast_query_response_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_query_response_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "mcast_startup_query_interval") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u64(&val64, *argv, 0))
|
|
invarg("invalid mcast_startup_query_interval",
|
|
*argv);
|
|
addattr64(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
|
|
val64);
|
|
} else if (strcmp(*argv, "msti") == 0) {
|
|
NEXT_ARG();
|
|
if (get_u16(&val16, *argv, 0))
|
|
invarg("invalid msti", *argv);
|
|
addattr16(&req.n, 1024,
|
|
BRIDGE_VLANDB_GOPTS_MSTI, val16);
|
|
} else {
|
|
if (strcmp(*argv, "help") == 0)
|
|
NEXT_ARG();
|
|
}
|
|
argc--; argv++;
|
|
}
|
|
addattr_nest_end(&req.n, afspec);
|
|
|
|
if (d == NULL || vid == -1) {
|
|
fprintf(stderr, "Device and VLAN ID are required arguments.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (rtnl_talk(&rth, &req.n, NULL) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* In order to use this function for both filtering and non-filtering cases
|
|
* we need to make it a tristate:
|
|
* return -1 - if filtering we've gone over so don't continue
|
|
* return 0 - skip entry and continue (applies to range start or to entries
|
|
* which are less than filter_vlan)
|
|
* return 1 - print the entry and continue
|
|
*/
|
|
static int filter_vlan_check(__u16 vid, __u16 flags)
|
|
{
|
|
/* if we're filtering we should stop on the first greater entry */
|
|
if (filter_vlan && vid > filter_vlan &&
|
|
!(flags & BRIDGE_VLAN_INFO_RANGE_END))
|
|
return -1;
|
|
if ((flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) ||
|
|
vid < filter_vlan)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void open_vlan_port(int ifi_index, enum vlan_show_subject subject)
|
|
{
|
|
open_json_object(NULL);
|
|
print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname",
|
|
"%-" textify(IFNAMSIZ) "s ",
|
|
ll_index_to_name(ifi_index));
|
|
open_json_array(PRINT_JSON,
|
|
subject == VLAN_SHOW_VLAN ? "vlans": "tunnels");
|
|
}
|
|
|
|
static void close_vlan_port(void)
|
|
{
|
|
close_json_array(PRINT_JSON, NULL);
|
|
close_json_object();
|
|
}
|
|
|
|
static void print_vlan_tunnel_info(struct rtattr *tb, int ifindex)
|
|
{
|
|
struct rtattr *i, *list = tb;
|
|
int rem = RTA_PAYLOAD(list);
|
|
__u16 last_vid_start = 0;
|
|
__u32 last_tunid_start = 0;
|
|
bool opened = false;
|
|
|
|
for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
|
|
struct rtattr *ttb[IFLA_BRIDGE_VLAN_TUNNEL_MAX+1];
|
|
__u32 tunnel_id = 0;
|
|
__u16 tunnel_vid = 0;
|
|
__u16 tunnel_flags = 0;
|
|
unsigned int width;
|
|
int vcheck_ret;
|
|
|
|
if (i->rta_type != IFLA_BRIDGE_VLAN_TUNNEL_INFO)
|
|
continue;
|
|
|
|
parse_rtattr(ttb, IFLA_BRIDGE_VLAN_TUNNEL_MAX,
|
|
RTA_DATA(i), RTA_PAYLOAD(i));
|
|
|
|
if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID])
|
|
tunnel_vid =
|
|
rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID]);
|
|
else
|
|
continue;
|
|
|
|
if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID])
|
|
tunnel_id =
|
|
rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID]);
|
|
|
|
if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS])
|
|
tunnel_flags =
|
|
rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]);
|
|
|
|
if (!(tunnel_flags & BRIDGE_VLAN_INFO_RANGE_END)) {
|
|
last_vid_start = tunnel_vid;
|
|
last_tunid_start = tunnel_id;
|
|
}
|
|
|
|
vcheck_ret = filter_vlan_check(tunnel_vid, tunnel_flags);
|
|
if (vcheck_ret == -1)
|
|
break;
|
|
else if (vcheck_ret == 0)
|
|
continue;
|
|
|
|
if (!opened) {
|
|
open_vlan_port(ifindex, VLAN_SHOW_TUNNELINFO);
|
|
opened = true;
|
|
} else {
|
|
print_string(PRINT_FP, NULL,
|
|
"%-" textify(IFNAMSIZ) "s ", "");
|
|
}
|
|
|
|
open_json_object(NULL);
|
|
width = print_range("vlan", last_vid_start, tunnel_vid);
|
|
if (!is_json_context())
|
|
printf("%-*s ", VLAN_ID_LEN - width, "");
|
|
print_range("tunid", last_tunid_start, tunnel_id);
|
|
close_json_object();
|
|
print_nl();
|
|
}
|
|
|
|
if (opened)
|
|
close_vlan_port();
|
|
}
|
|
|
|
static int print_vlan(struct nlmsghdr *n, void *arg)
|
|
{
|
|
enum vlan_show_subject *subject = arg;
|
|
struct ifinfomsg *ifm = NLMSG_DATA(n);
|
|
int len = n->nlmsg_len;
|
|
struct rtattr *tb[IFLA_MAX+1];
|
|
|
|
if (n->nlmsg_type != RTM_NEWLINK) {
|
|
fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n",
|
|
n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
|
|
return 0;
|
|
}
|
|
|
|
len -= NLMSG_LENGTH(sizeof(*ifm));
|
|
if (len < 0) {
|
|
fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
|
|
return -1;
|
|
}
|
|
|
|
if (ifm->ifi_family != AF_BRIDGE)
|
|
return 0;
|
|
|
|
if (filter_index && filter_index != ifm->ifi_index)
|
|
return 0;
|
|
|
|
parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len);
|
|
if (!tb[IFLA_AF_SPEC])
|
|
return 0;
|
|
|
|
switch (*subject) {
|
|
case VLAN_SHOW_VLAN:
|
|
print_vlan_info(tb[IFLA_AF_SPEC], ifm->ifi_index);
|
|
break;
|
|
case VLAN_SHOW_TUNNELINFO:
|
|
print_vlan_tunnel_info(tb[IFLA_AF_SPEC], ifm->ifi_index);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void print_vlan_stats_attr(struct rtattr *attr, int ifindex)
|
|
{
|
|
struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
|
|
struct rtattr *i, *list;
|
|
bool found_vlan = false;
|
|
int rem;
|
|
|
|
parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr),
|
|
RTA_PAYLOAD(attr));
|
|
if (!brtb[LINK_XSTATS_TYPE_BRIDGE])
|
|
return;
|
|
|
|
list = brtb[LINK_XSTATS_TYPE_BRIDGE];
|
|
rem = RTA_PAYLOAD(list);
|
|
|
|
for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
|
|
const struct bridge_vlan_xstats *vstats = RTA_DATA(i);
|
|
|
|
if (i->rta_type != BRIDGE_XSTATS_VLAN)
|
|
continue;
|
|
|
|
if (filter_vlan && filter_vlan != vstats->vid)
|
|
continue;
|
|
|
|
/* skip pure port entries, they'll be dumped via the slave stats call */
|
|
if ((vstats->flags & BRIDGE_VLAN_INFO_MASTER) &&
|
|
!(vstats->flags & BRIDGE_VLAN_INFO_BRENTRY))
|
|
continue;
|
|
|
|
/* found vlan stats, first time print the interface name */
|
|
if (!found_vlan) {
|
|
open_vlan_port(ifindex, VLAN_SHOW_VLAN);
|
|
found_vlan = true;
|
|
} else {
|
|
print_string(PRINT_FP, NULL,
|
|
"%-" textify(IFNAMSIZ) "s ", "");
|
|
}
|
|
bridge_print_vlan_stats(vstats);
|
|
}
|
|
|
|
/* vlan_port is opened only if there are any vlan stats */
|
|
if (found_vlan)
|
|
close_vlan_port();
|
|
}
|
|
|
|
static int print_vlan_stats(struct nlmsghdr *n, void *arg)
|
|
{
|
|
struct if_stats_msg *ifsm = NLMSG_DATA(n);
|
|
struct rtattr *tb[IFLA_STATS_MAX+1];
|
|
int len = n->nlmsg_len;
|
|
FILE *fp = arg;
|
|
|
|
len -= NLMSG_LENGTH(sizeof(*ifsm));
|
|
if (len < 0) {
|
|
fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
|
|
return -1;
|
|
}
|
|
|
|
if (filter_index && filter_index != ifsm->ifindex)
|
|
return 0;
|
|
|
|
parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len);
|
|
|
|
/* We have to check if any of the two attrs are usable */
|
|
if (tb[IFLA_STATS_LINK_XSTATS])
|
|
print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
|
|
ifsm->ifindex);
|
|
|
|
if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
|
|
print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
|
|
ifsm->ifindex);
|
|
|
|
fflush(fp);
|
|
return 0;
|
|
}
|
|
|
|
static void print_vlan_router_ports(struct rtattr *rattr)
|
|
{
|
|
int rem = RTA_PAYLOAD(rattr);
|
|
struct rtattr *i;
|
|
|
|
print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s ", "");
|
|
open_json_array(PRINT_ANY, is_json_context() ? "router_ports" :
|
|
"router ports: ");
|
|
for (i = RTA_DATA(rattr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
|
|
uint32_t *port_ifindex = RTA_DATA(i);
|
|
const char *port_ifname = ll_index_to_name(*port_ifindex);
|
|
|
|
open_json_object(NULL);
|
|
if (show_stats && i != RTA_DATA(rattr)) {
|
|
print_nl();
|
|
/* start: IFNAMSIZ + 4 + strlen("router ports: ") */
|
|
print_string(PRINT_FP, NULL,
|
|
"%-" textify(IFNAMSIZ) "s "
|
|
" ",
|
|
"");
|
|
}
|
|
print_string(PRINT_ANY, "port", "%s ", port_ifname);
|
|
if (show_stats)
|
|
br_print_router_port_stats(i);
|
|
close_json_object();
|
|
}
|
|
close_json_array(PRINT_JSON, NULL);
|
|
print_nl();
|
|
}
|
|
|
|
static void print_vlan_global_opts(struct rtattr *a, int ifindex)
|
|
{
|
|
struct rtattr *vtb[BRIDGE_VLANDB_GOPTS_MAX + 1], *vattr;
|
|
__u16 vid, vrange = 0;
|
|
|
|
if (rta_type(a) != BRIDGE_VLANDB_GLOBAL_OPTIONS)
|
|
return;
|
|
|
|
parse_rtattr_flags(vtb, BRIDGE_VLANDB_GOPTS_MAX, RTA_DATA(a),
|
|
RTA_PAYLOAD(a), NLA_F_NESTED);
|
|
vid = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_ID]);
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_RANGE])
|
|
vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_RANGE]);
|
|
else
|
|
vrange = vid;
|
|
|
|
if (filter_vlan && (filter_vlan < vid || filter_vlan > vrange))
|
|
return;
|
|
|
|
if (vlan_rtm_cur_ifidx != ifindex) {
|
|
open_vlan_port(ifindex, VLAN_SHOW_VLAN);
|
|
open_json_object(NULL);
|
|
vlan_rtm_cur_ifidx = ifindex;
|
|
} else {
|
|
open_json_object(NULL);
|
|
print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s ", "");
|
|
}
|
|
print_range("vlan", vid, vrange);
|
|
print_nl();
|
|
print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s ", "");
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING];
|
|
print_uint(PRINT_ANY, "mcast_snooping", "mcast_snooping %u ",
|
|
rta_getattr_u8(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER];
|
|
print_uint(PRINT_ANY, "mcast_querier", "mcast_querier %u ",
|
|
rta_getattr_u8(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE]) {
|
|
struct rtattr *attr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE];
|
|
|
|
bridge_print_mcast_querier_state(attr);
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION];
|
|
print_uint(PRINT_ANY, "mcast_igmp_version",
|
|
"mcast_igmp_version %u ", rta_getattr_u8(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION];
|
|
print_uint(PRINT_ANY, "mcast_mld_version",
|
|
"mcast_mld_version %u ", rta_getattr_u8(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT];
|
|
print_uint(PRINT_ANY, "mcast_last_member_count",
|
|
"mcast_last_member_count %u ",
|
|
rta_getattr_u32(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_last_member_interval",
|
|
"mcast_last_member_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT];
|
|
print_uint(PRINT_ANY, "mcast_startup_query_count",
|
|
"mcast_startup_query_count %u ",
|
|
rta_getattr_u32(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_startup_query_interval",
|
|
"mcast_startup_query_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_membership_interval",
|
|
"mcast_membership_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_querier_interval",
|
|
"mcast_querier_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_query_interval",
|
|
"mcast_query_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL];
|
|
print_lluint(PRINT_ANY, "mcast_query_response_interval",
|
|
"mcast_query_response_interval %llu ",
|
|
rta_getattr_u64(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MSTI]) {
|
|
vattr = vtb[BRIDGE_VLANDB_GOPTS_MSTI];
|
|
print_uint(PRINT_ANY, "msti", "msti %u ",
|
|
rta_getattr_u16(vattr));
|
|
}
|
|
print_nl();
|
|
if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]) {
|
|
vattr = RTA_DATA(vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]);
|
|
print_vlan_router_ports(vattr);
|
|
}
|
|
close_json_object();
|
|
}
|
|
|
|
static void print_vlan_opts(struct rtattr *a, int ifindex)
|
|
{
|
|
struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *vattr;
|
|
struct bridge_vlan_xstats vstats;
|
|
struct bridge_vlan_info *vinfo;
|
|
__u16 vrange = 0;
|
|
__u8 state = 0;
|
|
|
|
if (rta_type(a) != BRIDGE_VLANDB_ENTRY)
|
|
return;
|
|
|
|
parse_rtattr_flags(vtb, BRIDGE_VLANDB_ENTRY_MAX, RTA_DATA(a),
|
|
RTA_PAYLOAD(a), NLA_F_NESTED);
|
|
vinfo = RTA_DATA(vtb[BRIDGE_VLANDB_ENTRY_INFO]);
|
|
|
|
memset(&vstats, 0, sizeof(vstats));
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_RANGE])
|
|
vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_ENTRY_RANGE]);
|
|
else
|
|
vrange = vinfo->vid;
|
|
|
|
if (filter_vlan && (filter_vlan < vinfo->vid || filter_vlan > vrange))
|
|
return;
|
|
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_STATE])
|
|
state = rta_getattr_u8(vtb[BRIDGE_VLANDB_ENTRY_STATE]);
|
|
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_STATS]) {
|
|
struct rtattr *stb[BRIDGE_VLANDB_STATS_MAX+1];
|
|
struct rtattr *attr;
|
|
|
|
attr = vtb[BRIDGE_VLANDB_ENTRY_STATS];
|
|
parse_rtattr(stb, BRIDGE_VLANDB_STATS_MAX, RTA_DATA(attr),
|
|
RTA_PAYLOAD(attr));
|
|
|
|
if (stb[BRIDGE_VLANDB_STATS_RX_BYTES]) {
|
|
attr = stb[BRIDGE_VLANDB_STATS_RX_BYTES];
|
|
vstats.rx_bytes = rta_getattr_u64(attr);
|
|
}
|
|
if (stb[BRIDGE_VLANDB_STATS_RX_PACKETS]) {
|
|
attr = stb[BRIDGE_VLANDB_STATS_RX_PACKETS];
|
|
vstats.rx_packets = rta_getattr_u64(attr);
|
|
}
|
|
if (stb[BRIDGE_VLANDB_STATS_TX_PACKETS]) {
|
|
attr = stb[BRIDGE_VLANDB_STATS_TX_PACKETS];
|
|
vstats.tx_packets = rta_getattr_u64(attr);
|
|
}
|
|
if (stb[BRIDGE_VLANDB_STATS_TX_BYTES]) {
|
|
attr = stb[BRIDGE_VLANDB_STATS_TX_BYTES];
|
|
vstats.tx_bytes = rta_getattr_u64(attr);
|
|
}
|
|
}
|
|
|
|
if (vlan_rtm_cur_ifidx != ifindex) {
|
|
open_vlan_port(ifindex, VLAN_SHOW_VLAN);
|
|
open_json_object(NULL);
|
|
vlan_rtm_cur_ifidx = ifindex;
|
|
} else {
|
|
open_json_object(NULL);
|
|
print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s ", "");
|
|
}
|
|
print_range("vlan", vinfo->vid, vrange);
|
|
bridge_print_vlan_flags(vinfo->flags);
|
|
print_nl();
|
|
print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s ", "");
|
|
print_stp_state(state);
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]) {
|
|
vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER];
|
|
print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ",
|
|
rta_getattr_u8(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS]) {
|
|
vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS];
|
|
print_uint(PRINT_ANY, "mcast_n_groups", "mcast_n_groups %u ",
|
|
rta_getattr_u32(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]) {
|
|
vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS];
|
|
print_uint(PRINT_ANY, "mcast_max_groups", "mcast_max_groups %u ",
|
|
rta_getattr_u32(vattr));
|
|
}
|
|
if (vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]) {
|
|
vattr = vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS];
|
|
print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ",
|
|
rta_getattr_u8(vattr));
|
|
}
|
|
print_nl();
|
|
if (show_stats)
|
|
bridge_print_vlan_stats_only(&vstats);
|
|
close_json_object();
|
|
}
|
|
|
|
int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, bool global_only)
|
|
{
|
|
struct br_vlan_msg *bvm = NLMSG_DATA(n);
|
|
int len = n->nlmsg_len;
|
|
struct rtattr *a;
|
|
FILE *fp = arg;
|
|
int rem;
|
|
|
|
if (n->nlmsg_type != RTM_NEWVLAN && n->nlmsg_type != RTM_DELVLAN &&
|
|
n->nlmsg_type != RTM_GETVLAN) {
|
|
fprintf(stderr, "Unknown vlan rtm message: %08x %08x %08x\n",
|
|
n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
|
|
return 0;
|
|
}
|
|
|
|
len -= NLMSG_LENGTH(sizeof(*bvm));
|
|
if (len < 0) {
|
|
fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
|
|
return -1;
|
|
}
|
|
|
|
if (bvm->family != AF_BRIDGE)
|
|
return 0;
|
|
|
|
if (filter_index && filter_index != bvm->ifindex)
|
|
return 0;
|
|
|
|
print_headers(fp, "[VLAN]");
|
|
|
|
if (n->nlmsg_type == RTM_DELVLAN)
|
|
print_bool(PRINT_ANY, "deleted", "Deleted ", true);
|
|
|
|
if (monitor)
|
|
vlan_rtm_cur_ifidx = -1;
|
|
|
|
if (vlan_rtm_cur_ifidx != -1 && vlan_rtm_cur_ifidx != bvm->ifindex) {
|
|
close_vlan_port();
|
|
vlan_rtm_cur_ifidx = -1;
|
|
}
|
|
|
|
rem = len;
|
|
for (a = BRVLAN_RTA(bvm); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
|
|
unsigned short attr_type = rta_type(a);
|
|
|
|
/* skip unknown attributes */
|
|
if (attr_type > BRIDGE_VLANDB_MAX ||
|
|
(global_only && attr_type != BRIDGE_VLANDB_GLOBAL_OPTIONS))
|
|
continue;
|
|
|
|
switch (attr_type) {
|
|
case BRIDGE_VLANDB_ENTRY:
|
|
print_vlan_opts(a, bvm->ifindex);
|
|
break;
|
|
case BRIDGE_VLANDB_GLOBAL_OPTIONS:
|
|
print_vlan_global_opts(a, bvm->ifindex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg)
|
|
{
|
|
return print_vlan_rtm(n, arg, false, false);
|
|
}
|
|
|
|
static int print_vlan_rtm_global_filter(struct nlmsghdr *n, void *arg)
|
|
{
|
|
return print_vlan_rtm(n, arg, false, true);
|
|
}
|
|
|
|
static int vlan_show(int argc, char **argv, int subject)
|
|
{
|
|
char *filter_dev = NULL;
|
|
int ret = 0;
|
|
|
|
while (argc > 0) {
|
|
if (strcmp(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
if (filter_dev)
|
|
duparg("dev", *argv);
|
|
filter_dev = *argv;
|
|
} else if (strcmp(*argv, "vid") == 0) {
|
|
NEXT_ARG();
|
|
if (filter_vlan)
|
|
duparg("vid", *argv);
|
|
filter_vlan = atoi(*argv);
|
|
}
|
|
argc--; argv++;
|
|
}
|
|
|
|
if (filter_dev) {
|
|
filter_index = ll_name_to_index(filter_dev);
|
|
if (!filter_index)
|
|
return nodev(filter_dev);
|
|
}
|
|
|
|
new_json_obj(json);
|
|
|
|
/* if show_details is true then use the new bridge vlan dump format */
|
|
if (show_details && subject == VLAN_SHOW_VLAN) {
|
|
__u32 dump_flags = show_stats ? BRIDGE_VLANDB_DUMPF_STATS : 0;
|
|
|
|
if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) {
|
|
perror("Cannot send dump request");
|
|
exit(1);
|
|
}
|
|
|
|
if (!is_json_context()) {
|
|
printf("%-" textify(IFNAMSIZ) "s %-"
|
|
textify(VLAN_ID_LEN) "s", "port",
|
|
"vlan-id");
|
|
printf("\n");
|
|
}
|
|
|
|
ret = rtnl_dump_filter(&rth, print_vlan_rtm_filter, &subject);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Dump terminated\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (vlan_rtm_cur_ifidx != -1)
|
|
close_vlan_port();
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (!show_stats) {
|
|
if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE,
|
|
(compress_vlans ?
|
|
RTEXT_FILTER_BRVLAN_COMPRESSED :
|
|
RTEXT_FILTER_BRVLAN)) < 0) {
|
|
perror("Cannot send dump request");
|
|
exit(1);
|
|
}
|
|
|
|
if (!is_json_context()) {
|
|
printf("%-" textify(IFNAMSIZ) "s %-"
|
|
textify(VLAN_ID_LEN) "s", "port",
|
|
"vlan-id");
|
|
if (subject == VLAN_SHOW_TUNNELINFO)
|
|
printf(" tunnel-id");
|
|
printf("\n");
|
|
}
|
|
|
|
ret = rtnl_dump_filter(&rth, print_vlan, &subject);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Dump terminated\n");
|
|
exit(1);
|
|
}
|
|
} else {
|
|
__u32 filt_mask;
|
|
|
|
filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
|
|
if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask,
|
|
NULL, NULL) < 0) {
|
|
perror("Cannot send dump request");
|
|
exit(1);
|
|
}
|
|
|
|
if (!is_json_context())
|
|
printf("%-" textify(IFNAMSIZ) "s vlan-id\n",
|
|
"port");
|
|
|
|
if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
|
|
fprintf(stderr, "Dump terminated\n");
|
|
exit(1);
|
|
}
|
|
|
|
filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
|
|
if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask,
|
|
NULL, NULL) < 0) {
|
|
perror("Cannot send slave dump request");
|
|
exit(1);
|
|
}
|
|
|
|
if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
|
|
fprintf(stderr, "Dump terminated\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
out:
|
|
delete_json_obj();
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
|
|
static int vlan_global_show(int argc, char **argv)
|
|
{
|
|
__u32 dump_flags = BRIDGE_VLANDB_DUMPF_GLOBAL;
|
|
int ret = 0, subject = VLAN_SHOW_VLAN;
|
|
char *filter_dev = NULL;
|
|
|
|
while (argc > 0) {
|
|
if (strcmp(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
if (filter_dev)
|
|
duparg("dev", *argv);
|
|
filter_dev = *argv;
|
|
} else if (strcmp(*argv, "vid") == 0) {
|
|
NEXT_ARG();
|
|
if (filter_vlan)
|
|
duparg("vid", *argv);
|
|
filter_vlan = atoi(*argv);
|
|
}
|
|
argc--; argv++;
|
|
}
|
|
|
|
if (filter_dev) {
|
|
filter_index = ll_name_to_index(filter_dev);
|
|
if (!filter_index)
|
|
return nodev(filter_dev);
|
|
}
|
|
|
|
new_json_obj(json);
|
|
|
|
if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) {
|
|
perror("Cannot send dump request");
|
|
exit(1);
|
|
}
|
|
|
|
if (!is_json_context()) {
|
|
printf("%-" textify(IFNAMSIZ) "s %-"
|
|
textify(VLAN_ID_LEN) "s", "port",
|
|
"vlan-id");
|
|
printf("\n");
|
|
}
|
|
|
|
ret = rtnl_dump_filter(&rth, print_vlan_rtm_global_filter, &subject);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Dump terminated\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (vlan_rtm_cur_ifidx != -1)
|
|
close_vlan_port();
|
|
|
|
delete_json_obj();
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
|
|
static void print_vlan_info(struct rtattr *tb, int ifindex)
|
|
{
|
|
struct rtattr *i, *list = tb;
|
|
int rem = RTA_PAYLOAD(list);
|
|
__u16 last_vid_start = 0;
|
|
bool opened = false;
|
|
|
|
for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
|
|
struct bridge_vlan_info *vinfo;
|
|
int vcheck_ret;
|
|
|
|
if (i->rta_type != IFLA_BRIDGE_VLAN_INFO)
|
|
continue;
|
|
|
|
vinfo = RTA_DATA(i);
|
|
|
|
if (!(vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END))
|
|
last_vid_start = vinfo->vid;
|
|
vcheck_ret = filter_vlan_check(vinfo->vid, vinfo->flags);
|
|
if (vcheck_ret == -1)
|
|
break;
|
|
else if (vcheck_ret == 0)
|
|
continue;
|
|
|
|
if (!opened) {
|
|
open_vlan_port(ifindex, VLAN_SHOW_VLAN);
|
|
opened = true;
|
|
} else {
|
|
print_string(PRINT_FP, NULL, "%-"
|
|
textify(IFNAMSIZ) "s ", "");
|
|
}
|
|
|
|
open_json_object(NULL);
|
|
print_range("vlan", last_vid_start, vinfo->vid);
|
|
|
|
bridge_print_vlan_flags(vinfo->flags);
|
|
close_json_object();
|
|
print_nl();
|
|
}
|
|
|
|
if (opened)
|
|
close_vlan_port();
|
|
}
|
|
|
|
static int vlan_global(int argc, char **argv)
|
|
{
|
|
if (argc > 0) {
|
|
if (strcmp(*argv, "show") == 0 ||
|
|
strcmp(*argv, "lst") == 0 ||
|
|
strcmp(*argv, "list") == 0)
|
|
return vlan_global_show(argc-1, argv+1);
|
|
else if (strcmp(*argv, "set") == 0)
|
|
return vlan_global_option_set(argc-1, argv+1);
|
|
else
|
|
usage();
|
|
} else {
|
|
return vlan_global_show(0, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_vlan(int argc, char **argv)
|
|
{
|
|
ll_init_map(&rth);
|
|
timestamp = 0;
|
|
|
|
if (argc > 0) {
|
|
if (matches(*argv, "add") == 0)
|
|
return vlan_modify(RTM_SETLINK, argc-1, argv+1);
|
|
if (matches(*argv, "delete") == 0)
|
|
return vlan_modify(RTM_DELLINK, argc-1, argv+1);
|
|
if (matches(*argv, "show") == 0 ||
|
|
matches(*argv, "lst") == 0 ||
|
|
matches(*argv, "list") == 0)
|
|
return vlan_show(argc-1, argv+1, VLAN_SHOW_VLAN);
|
|
if (matches(*argv, "tunnelshow") == 0) {
|
|
return vlan_show(argc-1, argv+1, VLAN_SHOW_TUNNELINFO);
|
|
}
|
|
if (matches(*argv, "set") == 0)
|
|
return vlan_option_set(argc-1, argv+1);
|
|
if (strcmp(*argv, "global") == 0)
|
|
return vlan_global(argc-1, argv+1);
|
|
if (matches(*argv, "help") == 0)
|
|
usage();
|
|
} else {
|
|
return vlan_show(0, NULL, VLAN_SHOW_VLAN);
|
|
}
|
|
|
|
fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv);
|
|
exit(-1);
|
|
}
|