From d2951219a12fb98a7b6c8e249165a11b1004fe1a Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Tue, 5 Apr 2022 15:35:32 +0200 Subject: [PATCH] pim6d: add some show commands for MLD Just some basic show commands to get going. Signed-off-by: David Lamparter --- pimd/pim6_mld.c | 549 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 549 insertions(+) diff --git a/pimd/pim6_mld.c b/pimd/pim6_mld.c index 24514df9bb..7209989cb4 100644 --- a/pimd/pim6_mld.c +++ b/pimd/pim6_mld.c @@ -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_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_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_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); }