pim6d: add some show commands for MLD

Just some basic show commands to get going.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2022-04-05 15:35:32 +02:00
parent 5afe22f5bc
commit d2951219a1

View File

@ -2237,6 +2237,9 @@ void gm_ifp_update(struct interface *ifp)
}
}
/*
* CLI (show commands only)
*/
#include "lib/command.h"
@ -2244,6 +2247,548 @@ void gm_ifp_update(struct interface *ifp)
#include "pimd/pim6_mld_clippy.c"
#endif
#define MLD_STR "Multicast Listener Discovery\n"
static struct vrf *gm_cmd_vrf_lookup(struct vty *vty, const char *vrf_str,
int *err)
{
struct vrf *ret;
if (!vrf_str)
return vrf_lookup_by_id(VRF_DEFAULT);
if (!strcmp(vrf_str, "all"))
return NULL;
ret = vrf_lookup_by_name(vrf_str);
if (ret)
return ret;
vty_out(vty, "%% VRF %pSQq does not exist\n", vrf_str);
*err = CMD_WARNING;
return NULL;
}
static void gm_show_if_one_detail(struct vty *vty, struct interface *ifp)
{
struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info;
struct gm_if *gm_ifp;
bool querier;
size_t i;
if (!pim_ifp) {
vty_out(vty, "Interface %s: no PIM/MLD config\n\n", ifp->name);
return;
}
gm_ifp = pim_ifp->mld;
if (!gm_ifp) {
vty_out(vty, "Interface %s: MLD not running\n\n", ifp->name);
return;
}
querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest);
vty_out(vty, "Interface %s: MLD running\n", ifp->name);
vty_out(vty, " Uptime: %pTVMs\n", &gm_ifp->started);
vty_out(vty, " MLD version: %d\n", gm_ifp->cur_version);
vty_out(vty, " Querier: %pPA%s\n", &gm_ifp->querier,
querier ? " (this system)" : "");
vty_out(vty, " Query timer: %pTH\n", gm_ifp->t_query);
vty_out(vty, " Other querier timer: %pTH\n",
gm_ifp->t_other_querier);
vty_out(vty, " Robustness value: %u\n", gm_ifp->cur_qrv);
vty_out(vty, " Query interval: %ums\n",
gm_ifp->cur_query_intv);
vty_out(vty, " Query response timer: %ums\n", gm_ifp->cur_max_resp);
vty_out(vty, " Last member query intv.: %ums\n",
gm_ifp->cur_query_intv_trig);
vty_out(vty, " %u expiry timers from general queries:\n",
gm_ifp->n_pending);
for (i = 0; i < gm_ifp->n_pending; i++) {
struct gm_general_pending *p = &gm_ifp->pending[i];
vty_out(vty, " %9pTVMs ago (query) -> %9pTVMu (expiry)\n",
&p->query, &p->expiry);
}
vty_out(vty, " %zu expiry timers from *,G queries\n",
gm_grp_pends_count(gm_ifp->grp_pends));
vty_out(vty, " %zu expiry timers from S,G queries\n",
gm_gsq_pends_count(gm_ifp->gsq_pends));
vty_out(vty, " %zu total *,G/S,G from %zu hosts in %zu bundles\n",
gm_sgs_count(gm_ifp->sgs),
gm_subscribers_count(gm_ifp->subscribers),
gm_packet_expires_count(gm_ifp->expires));
vty_out(vty, "\n");
}
static void gm_show_if_one(struct vty *vty, struct interface *ifp,
json_object *js_if)
{
struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info;
struct gm_if *gm_ifp = pim_ifp->mld;
bool querier;
if (!gm_ifp) {
if (js_if)
json_object_string_add(js_if, "state", "down");
else
vty_out(vty, "%-16s %5s\n", ifp->name, "down");
return;
}
querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest);
if (js_if) {
json_object_string_add(js_if, "state", "up");
json_object_string_addf(js_if, "version", "%d",
gm_ifp->cur_version);
json_object_string_addf(js_if, "upTime", "%pTVMs",
&gm_ifp->started);
json_object_boolean_add(js_if, "querier", querier);
json_object_string_addf(js_if, "querierIp", "%pPA",
&gm_ifp->querier);
if (querier)
json_object_string_addf(js_if, "queryTimer", "%pTH",
gm_ifp->t_query);
else
json_object_string_addf(js_if, "otherQuerierTimer",
"%pTH",
gm_ifp->t_other_querier);
} else {
vty_out(vty, "%-16s %-5s %d %-25pPA %-5s %11pTH %pTVMs\n",
ifp->name, "up", gm_ifp->cur_version, &gm_ifp->querier,
querier ? "query" : "other",
querier ? gm_ifp->t_query : gm_ifp->t_other_querier,
&gm_ifp->started);
}
}
static void gm_show_if_vrf(struct vty *vty, struct vrf *vrf, const char *ifname,
bool detail, json_object *js)
{
struct interface *ifp;
json_object *js_vrf;
if (js) {
js_vrf = json_object_new_object();
json_object_object_add(js, vrf->name, js_vrf);
}
FOR_ALL_INTERFACES (vrf, ifp) {
json_object *js_if = NULL;
if (ifname && strcmp(ifp->name, ifname))
continue;
if (detail && !js) {
gm_show_if_one_detail(vty, ifp);
continue;
}
if (!ifp->info)
continue;
if (js) {
js_if = json_object_new_object();
json_object_object_add(js_vrf, ifp->name, js_if);
}
gm_show_if_one(vty, ifp, js_if);
}
}
static void gm_show_if(struct vty *vty, struct vrf *vrf, const char *ifname,
bool detail, json_object *js)
{
if (!js && !detail)
vty_out(vty, "%-16s %-5s V %-25s %-18s %s\n", "Interface",
"State", "Querier", "Timer", "Uptime");
if (vrf)
gm_show_if_vrf(vty, vrf, ifname, detail, js);
else
RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
gm_show_if_vrf(vty, vrf, ifname, detail, js);
}
DEFPY(gm_show_interface,
gm_show_interface_cmd,
"show ipv6 mld [vrf <VRF|all>$vrf_str] interface [IFNAME] [detail$detail|json$json]",
DEBUG_STR
SHOW_STR
IPV6_STR
MLD_STR
VRF_FULL_CMD_HELP_STR
"MLD interface information\n"
"Detailed output\n"
JSON_STR)
{
int ret = CMD_SUCCESS;
struct vrf *vrf;
json_object *js = NULL;
vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
if (ret != CMD_SUCCESS)
return ret;
if (json)
js = json_object_new_object();
gm_show_if(vty, vrf, ifname, !!detail, js);
return vty_json(vty, js);
}
static void gm_show_stats_one(struct vty *vty, struct gm_if *gm_ifp,
json_object *js_if)
{
struct gm_if_stats *stats = &gm_ifp->stats;
/* clang-format off */
struct {
const char *text;
const char *js_key;
uint64_t *val;
} *item, items[] = {
{ "v2 reports received", "rxV2Reports", &stats->rx_new_report },
{ "v1 reports received", "rxV1Reports", &stats->rx_old_report },
{ "v1 done received", "rxV1Done", &stats->rx_old_leave },
{ "v2 *,* queries received", "rxV2QueryGeneral", &stats->rx_query_new_general },
{ "v2 *,G queries received", "rxV2QueryGroup", &stats->rx_query_new_group },
{ "v2 S,G queries received", "rxV2QueryGroupSource", &stats->rx_query_new_groupsrc },
{ "v2 S-bit queries received", "rxV2QuerySBit", &stats->rx_query_new_sbit },
{ "v1 *,* queries received", "rxV1QueryGeneral", &stats->rx_query_old_general },
{ "v1 *,G queries received", "rxV1QueryGroup", &stats->rx_query_old_group },
{ "v2 *,* queries sent", "txV2QueryGeneral", &stats->tx_query_new_general },
{ "v2 *,G queries sent", "txV2QueryGroup", &stats->tx_query_new_group },
{ "v2 S,G queries sent", "txV2QueryGroupSource", &stats->tx_query_new_groupsrc },
{ "v1 *,* queries sent", "txV1QueryGeneral", &stats->tx_query_old_general },
{ "v1 *,G queries sent", "txV1QueryGroup", &stats->tx_query_old_group },
{ "TX errors", "txErrors", &stats->tx_query_fail },
{ "RX system errors", "rxErrorSys", &stats->rx_drop_sys },
{ "RX dropped (checksum error)", "rxDropChecksum", &stats->rx_drop_csum },
{ "RX dropped (invalid source)", "rxDropSrcAddr", &stats->rx_drop_srcaddr },
{ "RX dropped (invalid dest.)", "rxDropDstAddr", &stats->rx_drop_dstaddr },
{ "RX dropped (missing alert)", "rxDropRtrAlert", &stats->rx_drop_ra },
{ "RX dropped (malformed pkt.)", "rxDropMalformed", &stats->rx_drop_malformed },
{ "RX truncated reports", "rxTruncatedRep", &stats->rx_trunc_report },
};
/* clang-format on */
for (item = items; item < items + array_size(items); item++) {
if (js_if)
json_object_int_add(js_if, item->js_key, *item->val);
else
vty_out(vty, " %-30s %" PRIu64 "\n", item->text,
*item->val);
}
}
static void gm_show_stats_vrf(struct vty *vty, struct vrf *vrf,
const char *ifname, json_object *js)
{
struct interface *ifp;
json_object *js_vrf;
if (js) {
js_vrf = json_object_new_object();
json_object_object_add(js, vrf->name, js_vrf);
}
FOR_ALL_INTERFACES (vrf, ifp) {
struct pim_interface *pim_ifp;
struct gm_if *gm_ifp;
json_object *js_if = NULL;
if (ifname && strcmp(ifp->name, ifname))
continue;
if (!ifp->info)
continue;
pim_ifp = ifp->info;
if (!pim_ifp->mld)
continue;
gm_ifp = pim_ifp->mld;
if (js) {
js_if = json_object_new_object();
json_object_object_add(js_vrf, ifp->name, js_if);
} else {
vty_out(vty, "Interface: %s\n", ifp->name);
}
gm_show_stats_one(vty, gm_ifp, js_if);
if (!js)
vty_out(vty, "\n");
}
}
DEFPY(gm_show_interface_stats,
gm_show_interface_stats_cmd,
"show ipv6 mld [vrf <VRF|all>$vrf_str] statistics [interface IFNAME] [json$json]",
SHOW_STR
IPV6_STR
MLD_STR
VRF_FULL_CMD_HELP_STR
"MLD statistics\n"
INTERFACE_STR
"Interface name\n"
JSON_STR)
{
int ret = CMD_SUCCESS;
struct vrf *vrf;
json_object *js = NULL;
vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
if (ret != CMD_SUCCESS)
return ret;
if (json)
js = json_object_new_object();
if (vrf)
gm_show_stats_vrf(vty, vrf, ifname, js);
else
RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
gm_show_stats_vrf(vty, vrf, ifname, js);
return vty_json(vty, js);
}
static void gm_show_joins_one(struct vty *vty, struct gm_if *gm_ifp,
const struct prefix_ipv6 *groups,
const struct prefix_ipv6 *sources, bool detail,
json_object *js_if)
{
struct gm_sg *sg, *sg_start;
json_object *js_group = NULL;
pim_addr js_grpaddr = PIMADDR_ANY;
struct gm_subscriber sub_ref = {}, *sub_untracked;
if (groups) {
struct gm_sg sg_ref = {};
sg_ref.sgaddr.grp = pim_addr_from_prefix(groups);
sg_start = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref);
} else
sg_start = gm_sgs_first(gm_ifp->sgs);
sub_ref.addr = gm_dummy_untracked;
sub_untracked = gm_subscribers_find(gm_ifp->subscribers, &sub_ref);
/* NB: sub_untracked may be NULL if no untracked joins exist */
frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) {
struct timeval *recent = NULL, *untracked = NULL;
json_object *js_src;
if (groups) {
struct prefix grp_p;
pim_addr_to_prefix(&grp_p, sg->sgaddr.grp);
if (!prefix_match(groups, &grp_p))
break;
}
if (sources) {
struct prefix src_p;
pim_addr_to_prefix(&src_p, sg->sgaddr.src);
if (!prefix_match(sources, &src_p))
continue;
}
if (sg->most_recent) {
struct gm_packet_state *packet;
packet = gm_packet_sg2state(sg->most_recent);
recent = &packet->received;
}
if (sub_untracked) {
struct gm_packet_state *packet;
struct gm_packet_sg *item;
item = gm_packet_sg_find(sg, GM_SUB_POS, sub_untracked);
if (item) {
packet = gm_packet_sg2state(item);
untracked = &packet->received;
}
}
if (!js_if) {
FMT_NSTD_BEGIN; /* %.0p */
vty_out(vty,
"%-30pPA %-30pPAs %-16s %10.0pTVMs %10.0pTVMs %10.0pTVMs\n",
&sg->sgaddr.grp, &sg->sgaddr.src,
gm_states[sg->state], recent, untracked,
&sg->created);
if (!detail)
continue;
struct gm_packet_sg *item;
struct gm_packet_state *packet;
frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
packet = gm_packet_sg2state(item);
if (packet->subscriber == sub_untracked)
continue;
vty_out(vty, " %-58pPA %-16s %10.0pTVMs\n",
&packet->subscriber->addr, "(JOIN)",
&packet->received);
}
frr_each (gm_packet_sg_subs, sg->subs_negative, item) {
packet = gm_packet_sg2state(item);
if (packet->subscriber == sub_untracked)
continue;
vty_out(vty, " %-58pPA %-16s %10.0pTVMs\n",
&packet->subscriber->addr, "(PRUNE)",
&packet->received);
}
FMT_NSTD_END; /* %.0p */
continue;
}
/* if (js_if) */
if (!js_group || pim_addr_cmp(js_grpaddr, sg->sgaddr.grp)) {
js_group = json_object_new_object();
json_object_object_addf(js_if, js_group, "%pPA",
&sg->sgaddr.grp);
js_grpaddr = sg->sgaddr.grp;
}
js_src = json_object_new_object();
json_object_object_addf(js_group, js_src, "%pPA",
&sg->sgaddr.src);
json_object_string_add(js_src, "state", gm_states[sg->state]);
json_object_string_addf(js_src, "created", "%pTVMs",
&sg->created);
json_object_string_addf(js_src, "lastSeen", "%pTVMs", recent);
if (untracked)
json_object_string_addf(js_src, "untrackedLastSeen",
"%pTVMs", untracked);
if (!detail)
continue;
json_object *js_subs;
struct gm_packet_sg *item;
struct gm_packet_state *packet;
js_subs = json_object_new_object();
json_object_object_add(js_src, "joinedBy", js_subs);
frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
packet = gm_packet_sg2state(item);
if (packet->subscriber == sub_untracked)
continue;
json_object *js_sub;
js_sub = json_object_new_object();
json_object_object_addf(js_subs, js_sub, "%pPA",
&packet->subscriber->addr);
json_object_string_addf(js_sub, "lastSeen", "%pTVMs",
&packet->received);
}
js_subs = json_object_new_object();
json_object_object_add(js_src, "prunedBy", js_subs);
frr_each (gm_packet_sg_subs, sg->subs_negative, item) {
packet = gm_packet_sg2state(item);
if (packet->subscriber == sub_untracked)
continue;
json_object *js_sub;
js_sub = json_object_new_object();
json_object_object_addf(js_subs, js_sub, "%pPA",
&packet->subscriber->addr);
json_object_string_addf(js_sub, "lastSeen", "%pTVMs",
&packet->received);
}
}
}
static void gm_show_joins_vrf(struct vty *vty, struct vrf *vrf,
const char *ifname,
const struct prefix_ipv6 *groups,
const struct prefix_ipv6 *sources, bool detail,
json_object *js)
{
struct interface *ifp;
json_object *js_vrf;
if (js) {
js_vrf = json_object_new_object();
json_object_object_add(js, vrf->name, js_vrf);
}
FOR_ALL_INTERFACES (vrf, ifp) {
struct pim_interface *pim_ifp;
struct gm_if *gm_ifp;
json_object *js_if = NULL;
if (ifname && strcmp(ifp->name, ifname))
continue;
if (!ifp->info)
continue;
pim_ifp = ifp->info;
if (!pim_ifp->mld)
continue;
gm_ifp = pim_ifp->mld;
if (js) {
js_if = json_object_new_object();
json_object_object_add(js_vrf, ifp->name, js_if);
}
if (!js && !ifname)
vty_out(vty, "\nOn interface %s:\n", ifp->name);
gm_show_joins_one(vty, gm_ifp, groups, sources, detail, js_if);
}
}
DEFPY(gm_show_interface_joins,
gm_show_interface_joins_cmd,
"show ipv6 mld [vrf <VRF|all>$vrf_str] joins [{interface IFNAME|groups X:X::X:X/M|sources X:X::X:X/M|detail$detail}] [json$json]",
SHOW_STR
IPV6_STR
MLD_STR
VRF_FULL_CMD_HELP_STR
"MLD joined groups & sources\n"
INTERFACE_STR
"Interface name\n"
"Limit output to group range\n"
"Show groups covered by this prefix\n"
"Limit output to source range\n"
"Show sources covered by this prefix\n"
"Show details, including tracked receivers\n"
JSON_STR)
{
int ret = CMD_SUCCESS;
struct vrf *vrf;
json_object *js = NULL;
vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
if (ret != CMD_SUCCESS)
return ret;
if (json)
js = json_object_new_object();
else
vty_out(vty, "%-30s %-30s %-16s %10s %10s %10s\n", "Group",
"Source", "State", "LastSeen", "NonTrkSeen", "Created");
if (vrf)
gm_show_joins_vrf(vty, vrf, ifname, groups, sources, !!detail,
js);
else
RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
gm_show_joins_vrf(vty, vrf, ifname, groups, sources,
!!detail, js);
return vty_json(vty, js);
}
DEFPY(gm_debug_show,
gm_debug_show_cmd,
"debug show mld interface IFNAME",
@ -2416,6 +2961,10 @@ void gm_cli_init(void);
void gm_cli_init(void)
{
install_element(VIEW_NODE, &gm_show_interface_cmd);
install_element(VIEW_NODE, &gm_show_interface_stats_cmd);
install_element(VIEW_NODE, &gm_show_interface_joins_cmd);
install_element(VIEW_NODE, &gm_debug_show_cmd);
install_element(INTERFACE_NODE, &gm_debug_iface_cfg_cmd);
}