back to topotato report
topotato coverage report
Current view: top level - pimd - pim6_mld.c (source / functions) Hit Total Coverage
Test: aggregated run ( view descriptions ) Lines: 916 1654 55.4 %
Date: 2023-02-24 19:38:44 Functions: 86 110 78.2 %

          Line data    Source code
       1             : /*
       2             :  * PIMv6 MLD querier
       3             :  * Copyright (C) 2021-2022  David Lamparter for NetDEF, Inc.
       4             :  *
       5             :  * This program is free software; you can redistribute it and/or modify it
       6             :  * under the terms of the GNU General Public License as published by the Free
       7             :  * Software Foundation; either version 2 of the License, or (at your option)
       8             :  * any later version.
       9             :  *
      10             :  * This program is distributed in the hope that it will be useful, but WITHOUT
      11             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      12             :  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
      13             :  * more details.
      14             :  *
      15             :  * You should have received a copy of the GNU General Public License along
      16             :  * with this program; see the file COPYING; if not, write to the Free Software
      17             :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
      18             :  */
      19             : 
      20             : /*
      21             :  * keep pim6_mld.h open when working on this code.  Most data structures are
      22             :  * commented in the header.
      23             :  *
      24             :  * IPv4 support is pre-planned but hasn't been tackled yet.  It is intended
      25             :  * that this code will replace the old IGMP querier at some point.
      26             :  */
      27             : 
      28             : #include <zebra.h>
      29             : #include <netinet/ip6.h>
      30             : 
      31             : #include "lib/memory.h"
      32             : #include "lib/jhash.h"
      33             : #include "lib/prefix.h"
      34             : #include "lib/checksum.h"
      35             : #include "lib/thread.h"
      36             : #include "termtable.h"
      37             : 
      38             : #include "pimd/pim6_mld.h"
      39             : #include "pimd/pim6_mld_protocol.h"
      40             : #include "pimd/pim_memory.h"
      41             : #include "pimd/pim_instance.h"
      42             : #include "pimd/pim_iface.h"
      43             : #include "pimd/pim6_cmd.h"
      44             : #include "pimd/pim_cmd_common.h"
      45             : #include "pimd/pim_util.h"
      46             : #include "pimd/pim_tib.h"
      47             : #include "pimd/pimd.h"
      48             : 
      49             : #ifndef IPV6_MULTICAST_ALL
      50             : #define IPV6_MULTICAST_ALL 29
      51             : #endif
      52             : 
      53          24 : DEFINE_MTYPE_STATIC(PIMD, GM_IFACE, "MLD interface");
      54          24 : DEFINE_MTYPE_STATIC(PIMD, GM_PACKET, "MLD packet");
      55          24 : DEFINE_MTYPE_STATIC(PIMD, GM_SUBSCRIBER, "MLD subscriber");
      56          24 : DEFINE_MTYPE_STATIC(PIMD, GM_STATE, "MLD subscription state");
      57          24 : DEFINE_MTYPE_STATIC(PIMD, GM_SG, "MLD (S,G)");
      58          24 : DEFINE_MTYPE_STATIC(PIMD, GM_GRP_PENDING, "MLD group query state");
      59          24 : DEFINE_MTYPE_STATIC(PIMD, GM_GSQ_PENDING, "MLD group/source query aggregate");
      60             : 
      61             : static void gm_t_query(struct thread *t);
      62             : static void gm_trigger_specific(struct gm_sg *sg);
      63             : static void gm_sg_timer_start(struct gm_if *gm_ifp, struct gm_sg *sg,
      64             :                               struct timeval expire_wait);
      65             : 
      66             : /* shorthand for log messages */
      67             : #define log_ifp(msg)                                                           \
      68             :         "[MLD %s:%s] " msg, gm_ifp->ifp->vrf->name, gm_ifp->ifp->name
      69             : #define log_pkt_src(msg)                                                       \
      70             :         "[MLD %s:%s %pI6] " msg, gm_ifp->ifp->vrf->name, gm_ifp->ifp->name,    \
      71             :                 &pkt_src->sin6_addr
      72             : #define log_sg(sg, msg)                                                        \
      73             :         "[MLD %s:%s %pSG] " msg, sg->iface->ifp->vrf->name,                    \
      74             :                 sg->iface->ifp->name, &sg->sgaddr
      75             : 
      76             : /* clang-format off */
      77             : #if PIM_IPV == 6
      78             : static const pim_addr gm_all_hosts = {
      79             :         .s6_addr = {
      80             :                 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      81             :                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
      82             :         },
      83             : };
      84             : static const pim_addr gm_all_routers = {
      85             :         .s6_addr = {
      86             :                 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      87             :                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16,
      88             :         },
      89             : };
      90             : /* MLDv1 does not allow subscriber tracking due to report suppression
      91             :  * hence, the source address is replaced with ffff:...:ffff
      92             :  */
      93             : static const pim_addr gm_dummy_untracked = {
      94             :         .s6_addr = {
      95             :                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      96             :                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      97             :         },
      98             : };
      99             : #else
     100             : /* 224.0.0.1 */
     101             : static const pim_addr gm_all_hosts = { .s_addr = htonl(0xe0000001), };
     102             : /* 224.0.0.22 */
     103             : static const pim_addr gm_all_routers = { .s_addr = htonl(0xe0000016), };
     104             : static const pim_addr gm_dummy_untracked = { .s_addr = 0xffffffff, };
     105             : #endif
     106             : /* clang-format on */
     107             : 
     108             : #define IPV6_MULTICAST_SCOPE_LINK 2
     109             : 
     110        1308 : static inline uint8_t in6_multicast_scope(const pim_addr *addr)
     111             : {
     112        1308 :         return addr->s6_addr[1] & 0xf;
     113             : }
     114             : 
     115        1308 : static inline bool in6_multicast_nofwd(const pim_addr *addr)
     116             : {
     117        1308 :         return in6_multicast_scope(addr) <= IPV6_MULTICAST_SCOPE_LINK;
     118             : }
     119             : 
     120             : /*
     121             :  * (S,G) -> subscriber,(S,G)
     122             :  */
     123             : 
     124        3372 : static int gm_packet_sg_cmp(const struct gm_packet_sg *a,
     125             :                             const struct gm_packet_sg *b)
     126             : {
     127        3372 :         const struct gm_packet_state *s_a, *s_b;
     128             : 
     129        3372 :         s_a = gm_packet_sg2state(a);
     130        3372 :         s_b = gm_packet_sg2state(b);
     131        3372 :         return IPV6_ADDR_CMP(&s_a->subscriber->addr, &s_b->subscriber->addr);
     132             : }
     133             : 
     134        7978 : DECLARE_RBTREE_UNIQ(gm_packet_sg_subs, struct gm_packet_sg, subs_itm,
     135             :                     gm_packet_sg_cmp);
     136             : 
     137        2222 : static struct gm_packet_sg *gm_packet_sg_find(struct gm_sg *sg,
     138             :                                               enum gm_sub_sense sense,
     139             :                                               struct gm_subscriber *sub)
     140             : {
     141        2222 :         struct {
     142             :                 struct gm_packet_state hdr;
     143             :                 struct gm_packet_sg item;
     144        2222 :         } ref = {
     145             :                 /* clang-format off */
     146             :                 .hdr = {
     147             :                         .subscriber = sub,
     148             :                 },
     149             :                 .item = {
     150             :                         .offset = 0,
     151             :                 },
     152             :                 /* clang-format on */
     153             :         };
     154             : 
     155        2222 :         return gm_packet_sg_subs_find(&sg->subs[sense], &ref.item);
     156             : }
     157             : 
     158             : /*
     159             :  * interface -> (*,G),pending
     160             :  */
     161             : 
     162           0 : static int gm_grp_pending_cmp(const struct gm_grp_pending *a,
     163             :                               const struct gm_grp_pending *b)
     164             : {
     165           0 :         return IPV6_ADDR_CMP(&a->grp, &b->grp);
     166             : }
     167             : 
     168          69 : DECLARE_RBTREE_UNIQ(gm_grp_pends, struct gm_grp_pending, itm,
     169             :                     gm_grp_pending_cmp);
     170             : 
     171             : /*
     172             :  * interface -> ([S1,S2,...],G),pending
     173             :  */
     174             : 
     175           0 : static int gm_gsq_pending_cmp(const struct gm_gsq_pending *a,
     176             :                               const struct gm_gsq_pending *b)
     177             : {
     178           0 :         if (a->s_bit != b->s_bit)
     179           0 :                 return numcmp(a->s_bit, b->s_bit);
     180             : 
     181           0 :         return IPV6_ADDR_CMP(&a->grp, &b->grp);
     182             : }
     183             : 
     184           2 : static uint32_t gm_gsq_pending_hash(const struct gm_gsq_pending *a)
     185             : {
     186           2 :         uint32_t seed = a->s_bit ? 0x68f0eb5e : 0x156b7f19;
     187             : 
     188           2 :         return jhash(&a->grp, sizeof(a->grp), seed);
     189             : }
     190             : 
     191          58 : DECLARE_HASH(gm_gsq_pends, struct gm_gsq_pending, itm, gm_gsq_pending_cmp,
     192             :              gm_gsq_pending_hash);
     193             : 
     194             : /*
     195             :  * interface -> (S,G)
     196             :  */
     197             : 
     198        2918 : static int gm_sg_cmp(const struct gm_sg *a, const struct gm_sg *b)
     199             : {
     200        2918 :         return pim_sgaddr_cmp(a->sgaddr, b->sgaddr);
     201             : }
     202             : 
     203        4350 : DECLARE_RBTREE_UNIQ(gm_sgs, struct gm_sg, itm, gm_sg_cmp);
     204             : 
     205        1216 : static struct gm_sg *gm_sg_find(struct gm_if *gm_ifp, pim_addr grp,
     206             :                                 pim_addr src)
     207             : {
     208        1216 :         struct gm_sg ref = {};
     209             : 
     210        1216 :         ref.sgaddr.grp = grp;
     211        1216 :         ref.sgaddr.src = src;
     212        1216 :         return gm_sgs_find(gm_ifp->sgs, &ref);
     213             : }
     214             : 
     215          89 : static struct gm_sg *gm_sg_make(struct gm_if *gm_ifp, pim_addr grp,
     216             :                                 pim_addr src)
     217             : {
     218          89 :         struct gm_sg *ret, *prev;
     219             : 
     220          89 :         ret = XCALLOC(MTYPE_GM_SG, sizeof(*ret));
     221          89 :         ret->sgaddr.grp = grp;
     222          89 :         ret->sgaddr.src = src;
     223          89 :         ret->iface = gm_ifp;
     224          89 :         prev = gm_sgs_add(gm_ifp->sgs, ret);
     225             : 
     226          89 :         if (prev) {
     227           0 :                 XFREE(MTYPE_GM_SG, ret);
     228           0 :                 ret = prev;
     229             :         } else {
     230          89 :                 monotime(&ret->created);
     231          89 :                 gm_packet_sg_subs_init(ret->subs_positive);
     232          89 :                 gm_packet_sg_subs_init(ret->subs_negative);
     233             :         }
     234          89 :         return ret;
     235             : }
     236             : 
     237             : /*
     238             :  * interface -> packets, sorted by expiry (because add_tail insert order)
     239             :  */
     240             : 
     241        1127 : DECLARE_DLIST(gm_packet_expires, struct gm_packet_state, exp_itm);
     242             : 
     243             : /*
     244             :  * subscriber -> packets
     245             :  */
     246             : 
     247           4 : DECLARE_DLIST(gm_packets, struct gm_packet_state, pkt_itm);
     248             : 
     249             : /*
     250             :  * interface -> subscriber
     251             :  */
     252             : 
     253         228 : static int gm_subscriber_cmp(const struct gm_subscriber *a,
     254             :                              const struct gm_subscriber *b)
     255             : {
     256         228 :         return IPV6_ADDR_CMP(&a->addr, &b->addr);
     257             : }
     258             : 
     259         288 : static uint32_t gm_subscriber_hash(const struct gm_subscriber *a)
     260             : {
     261         288 :         return jhash(&a->addr, sizeof(a->addr), 0xd0e94ad4);
     262             : }
     263             : 
     264        1088 : DECLARE_HASH(gm_subscribers, struct gm_subscriber, itm, gm_subscriber_cmp,
     265             :              gm_subscriber_hash);
     266             : 
     267         258 : static struct gm_subscriber *gm_subscriber_findref(struct gm_if *gm_ifp,
     268             :                                                    pim_addr addr)
     269             : {
     270         258 :         struct gm_subscriber ref = {}, *ret;
     271             : 
     272         258 :         ref.addr = addr;
     273         258 :         ret = gm_subscribers_find(gm_ifp->subscribers, &ref);
     274         258 :         if (ret)
     275         228 :                 ret->refcount++;
     276         258 :         return ret;
     277             : }
     278             : 
     279          30 : static struct gm_subscriber *gm_subscriber_get(struct gm_if *gm_ifp,
     280             :                                                pim_addr addr)
     281             : {
     282          30 :         struct gm_subscriber ref = {}, *ret;
     283             : 
     284          30 :         ref.addr = addr;
     285          30 :         ret = gm_subscribers_find(gm_ifp->subscribers, &ref);
     286             : 
     287          30 :         if (!ret) {
     288          30 :                 ret = XCALLOC(MTYPE_GM_SUBSCRIBER, sizeof(*ret));
     289          30 :                 ret->iface = gm_ifp;
     290          30 :                 ret->addr = addr;
     291          30 :                 ret->refcount = 1;
     292          30 :                 monotime(&ret->created);
     293          30 :                 gm_packets_init(ret->packets);
     294             : 
     295          30 :                 gm_subscribers_add(gm_ifp->subscribers, ret);
     296             :         }
     297          30 :         return ret;
     298             : }
     299             : 
     300         258 : static void gm_subscriber_drop(struct gm_subscriber **subp)
     301             : {
     302         258 :         struct gm_subscriber *sub = *subp;
     303         258 :         struct gm_if *gm_ifp;
     304             : 
     305         258 :         if (!sub)
     306             :                 return;
     307         258 :         gm_ifp = sub->iface;
     308             : 
     309         258 :         *subp = NULL;
     310         258 :         sub->refcount--;
     311             : 
     312         258 :         if (sub->refcount)
     313             :                 return;
     314             : 
     315          30 :         gm_subscribers_del(gm_ifp->subscribers, sub);
     316          30 :         XFREE(MTYPE_GM_SUBSCRIBER, sub);
     317             : }
     318             : 
     319             : /****************************************************************************/
     320             : 
     321             : /* bundle query timer values for combined v1/v2 handling */
     322             : struct gm_query_timers {
     323             :         unsigned int qrv;
     324             :         unsigned int max_resp_ms;
     325             :         unsigned int qqic_ms;
     326             : 
     327             :         struct timeval fuzz;
     328             :         struct timeval expire_wait;
     329             : };
     330             : 
     331          93 : static void gm_expiry_calc(struct gm_query_timers *timers)
     332             : {
     333          93 :         unsigned int expire =
     334          93 :                 (timers->qrv - 1) * timers->qqic_ms + timers->max_resp_ms;
     335          93 :         ldiv_t exp_div = ldiv(expire, 1000);
     336             : 
     337          93 :         timers->expire_wait.tv_sec = exp_div.quot;
     338          93 :         timers->expire_wait.tv_usec = exp_div.rem * 1000;
     339          93 :         timeradd(&timers->expire_wait, &timers->fuzz, &timers->expire_wait);
     340          93 : }
     341             : 
     342          89 : static void gm_sg_free(struct gm_sg *sg)
     343             : {
     344             :         /* t_sg_expiry is handled before this is reached */
     345          89 :         THREAD_OFF(sg->t_sg_query);
     346          89 :         gm_packet_sg_subs_fini(sg->subs_negative);
     347          89 :         gm_packet_sg_subs_fini(sg->subs_positive);
     348          89 :         XFREE(MTYPE_GM_SG, sg);
     349          89 : }
     350             : 
     351             : /* clang-format off */
     352             : static const char *const gm_states[] = {
     353             :         [GM_SG_NOINFO]                  = "NOINFO",
     354             :         [GM_SG_JOIN]                    = "JOIN",
     355             :         [GM_SG_JOIN_EXPIRING]           = "JOIN_EXPIRING",
     356             :         [GM_SG_PRUNE]                   = "PRUNE",
     357             :         [GM_SG_NOPRUNE]                 = "NOPRUNE",
     358             :         [GM_SG_NOPRUNE_EXPIRING]        = "NOPRUNE_EXPIRING",
     359             : };
     360             : /* clang-format on */
     361             : 
     362             : CPP_NOTICE("TODO: S,G entries in EXCLUDE (i.e. prune) unsupported");
     363             : /* tib_sg_gm_prune() below is an "un-join", it doesn't prune S,G when *,G is
     364             :  * joined.  Whether we actually want/need to support this is a separate
     365             :  * question - it is almost never used.  In fact this is exactly what RFC5790
     366             :  * ("lightweight" MLDv2) does:  it removes S,G EXCLUDE support.
     367             :  */
     368             : 
     369        1308 : static void gm_sg_update(struct gm_sg *sg, bool has_expired)
     370             : {
     371        1308 :         struct gm_if *gm_ifp = sg->iface;
     372        1308 :         enum gm_sg_state prev, desired;
     373        1308 :         bool new_join;
     374        1308 :         struct gm_sg *grp = NULL;
     375             : 
     376        1308 :         if (!pim_addr_is_any(sg->sgaddr.src))
     377          18 :                 grp = gm_sg_find(gm_ifp, sg->sgaddr.grp, PIMADDR_ANY);
     378             :         else
     379        1290 :                 assert(sg->state != GM_SG_PRUNE);
     380             : 
     381        1308 :         if (gm_packet_sg_subs_count(sg->subs_positive)) {
     382        1218 :                 desired = GM_SG_JOIN;
     383        1218 :                 assert(!sg->t_sg_expire);
     384          90 :         } else if ((sg->state == GM_SG_JOIN ||
     385          90 :                     sg->state == GM_SG_JOIN_EXPIRING) &&
     386             :                    !has_expired)
     387             :                 desired = GM_SG_JOIN_EXPIRING;
     388          89 :         else if (!grp || !gm_packet_sg_subs_count(grp->subs_positive))
     389             :                 desired = GM_SG_NOINFO;
     390           0 :         else if (gm_packet_sg_subs_count(grp->subs_positive) ==
     391           0 :                  gm_packet_sg_subs_count(sg->subs_negative)) {
     392           0 :                 if ((sg->state == GM_SG_NOPRUNE ||
     393           0 :                      sg->state == GM_SG_NOPRUNE_EXPIRING) &&
     394             :                     !has_expired)
     395             :                         desired = GM_SG_NOPRUNE_EXPIRING;
     396             :                 else
     397           0 :                         desired = GM_SG_PRUNE;
     398           0 :         } else if (gm_packet_sg_subs_count(sg->subs_negative))
     399             :                 desired = GM_SG_NOPRUNE;
     400             :         else
     401          89 :                 desired = GM_SG_NOINFO;
     402             : 
     403        1308 :         if (desired != sg->state && !gm_ifp->stopping) {
     404          91 :                 if (PIM_DEBUG_GM_EVENTS)
     405          91 :                         zlog_debug(log_sg(sg, "%s => %s"), gm_states[sg->state],
     406             :                                    gm_states[desired]);
     407             : 
     408          91 :                 if (desired == GM_SG_JOIN_EXPIRING ||
     409          91 :                     desired == GM_SG_NOPRUNE_EXPIRING) {
     410           1 :                         struct gm_query_timers timers;
     411             : 
     412           1 :                         timers.qrv = gm_ifp->cur_qrv;
     413           1 :                         timers.max_resp_ms = gm_ifp->cur_max_resp;
     414           1 :                         timers.qqic_ms = gm_ifp->cur_query_intv_trig;
     415           1 :                         timers.fuzz = gm_ifp->cfg_timing_fuzz;
     416             : 
     417           1 :                         gm_expiry_calc(&timers);
     418           1 :                         gm_sg_timer_start(gm_ifp, sg, timers.expire_wait);
     419             : 
     420           1 :                         THREAD_OFF(sg->t_sg_query);
     421           1 :                         sg->n_query = gm_ifp->cur_lmqc;
     422           1 :                         sg->query_sbit = false;
     423           1 :                         gm_trigger_specific(sg);
     424             :                 }
     425             :         }
     426        1308 :         prev = sg->state;
     427        1308 :         sg->state = desired;
     428             : 
     429        1308 :         if (in6_multicast_nofwd(&sg->sgaddr.grp) || gm_ifp->stopping)
     430             :                 new_join = false;
     431             :         else
     432          17 :                 new_join = gm_sg_state_want_join(desired);
     433             : 
     434          17 :         if (new_join && !sg->tib_joined) {
     435             :                 /* this will retry if join previously failed */
     436           4 :                 sg->tib_joined = tib_sg_gm_join(gm_ifp->pim, sg->sgaddr,
     437             :                                                 gm_ifp->ifp, &sg->oil);
     438           4 :                 if (!sg->tib_joined)
     439           0 :                         zlog_warn(
     440             :                                 "MLD join for %pSG%%%s not propagated into TIB",
     441             :                                 &sg->sgaddr, gm_ifp->ifp->name);
     442             :                 else
     443           4 :                         zlog_info(log_ifp("%pSG%%%s TIB joined"), &sg->sgaddr,
     444             :                                   gm_ifp->ifp->name);
     445             : 
     446        1304 :         } else if (sg->tib_joined && !new_join) {
     447           4 :                 tib_sg_gm_prune(gm_ifp->pim, sg->sgaddr, gm_ifp->ifp, &sg->oil);
     448             : 
     449           4 :                 sg->oil = NULL;
     450           4 :                 sg->tib_joined = false;
     451             :         }
     452             : 
     453        1308 :         if (desired == GM_SG_NOINFO) {
     454          89 :                 assertf((!sg->t_sg_expire &&
     455             :                          !gm_packet_sg_subs_count(sg->subs_positive) &&
     456             :                          !gm_packet_sg_subs_count(sg->subs_negative)),
     457             :                         "%pSG%%%s hx=%u exp=%pTHD state=%s->%s pos=%zu neg=%zu grp=%p",
     458             :                         &sg->sgaddr, gm_ifp->ifp->name, has_expired,
     459             :                         sg->t_sg_expire, gm_states[prev], gm_states[desired],
     460             :                         gm_packet_sg_subs_count(sg->subs_positive),
     461             :                         gm_packet_sg_subs_count(sg->subs_negative), grp);
     462             : 
     463          89 :                 if (PIM_DEBUG_GM_TRACE)
     464          89 :                         zlog_debug(log_sg(sg, "dropping"));
     465             : 
     466          89 :                 gm_sgs_del(gm_ifp->sgs, sg);
     467          89 :                 gm_sg_free(sg);
     468             :         }
     469        1308 : }
     470             : 
     471             : /****************************************************************************/
     472             : 
     473             : /* the following bunch of functions deals with transferring state from
     474             :  * received packets into gm_packet_state.  As a reminder, the querier is
     475             :  * structured to keep all items received in one packet together, since they
     476             :  * will share expiry timers and thus allows efficient handling.
     477             :  */
     478             : 
     479         251 : static void gm_packet_free(struct gm_packet_state *pkt)
     480             : {
     481         251 :         gm_packet_expires_del(pkt->iface->expires, pkt);
     482         251 :         gm_packets_del(pkt->subscriber->packets, pkt);
     483         251 :         gm_subscriber_drop(&pkt->subscriber);
     484         251 :         XFREE(MTYPE_GM_STATE, pkt);
     485         251 : }
     486             : 
     487        1170 : static struct gm_packet_sg *gm_packet_sg_setup(struct gm_packet_state *pkt,
     488             :                                                struct gm_sg *sg, bool is_excl,
     489             :                                                bool is_src)
     490             : {
     491        1170 :         struct gm_packet_sg *item;
     492             : 
     493        1170 :         assert(pkt->n_active < pkt->n_sg);
     494             : 
     495        1170 :         item = &pkt->items[pkt->n_active];
     496        1170 :         item->sg = sg;
     497        1170 :         item->is_excl = is_excl;
     498        1170 :         item->is_src = is_src;
     499        1170 :         item->offset = pkt->n_active;
     500             : 
     501        1170 :         pkt->n_active++;
     502        1170 :         return item;
     503             : }
     504             : 
     505        1170 : static bool gm_packet_sg_drop(struct gm_packet_sg *item)
     506             : {
     507        1170 :         struct gm_packet_state *pkt;
     508        1170 :         size_t i;
     509             : 
     510        1170 :         assert(item->sg);
     511             : 
     512        1170 :         pkt = gm_packet_sg2state(item);
     513        1170 :         if (item->sg->most_recent == item)
     514         577 :                 item->sg->most_recent = NULL;
     515             : 
     516        1170 :         for (i = 0; i < item->n_exclude; i++) {
     517           0 :                 struct gm_packet_sg *excl_item;
     518             : 
     519           0 :                 excl_item = item + 1 + i;
     520           0 :                 if (!excl_item->sg)
     521           0 :                         continue;
     522             : 
     523           0 :                 gm_packet_sg_subs_del(excl_item->sg->subs_negative, excl_item);
     524           0 :                 excl_item->sg = NULL;
     525           0 :                 pkt->n_active--;
     526             : 
     527           0 :                 assert(pkt->n_active > 0);
     528             :         }
     529             : 
     530        1170 :         if (item->is_excl && item->is_src)
     531           0 :                 gm_packet_sg_subs_del(item->sg->subs_negative, item);
     532             :         else
     533        1170 :                 gm_packet_sg_subs_del(item->sg->subs_positive, item);
     534        1170 :         item->sg = NULL;
     535        1170 :         pkt->n_active--;
     536             : 
     537        1170 :         if (!pkt->n_active) {
     538         251 :                 gm_packet_free(pkt);
     539         251 :                 return true;
     540             :         }
     541             :         return false;
     542             : }
     543             : 
     544          30 : static void gm_packet_drop(struct gm_packet_state *pkt, bool trace)
     545             : {
     546         136 :         for (size_t i = 0; i < pkt->n_sg; i++) {
     547         136 :                 struct gm_sg *sg = pkt->items[i].sg;
     548         136 :                 bool deleted;
     549             : 
     550         136 :                 if (!sg)
     551           8 :                         continue;
     552             : 
     553         128 :                 if (trace && PIM_DEBUG_GM_TRACE)
     554           0 :                         zlog_debug(log_sg(sg, "general-dropping from %pPA"),
     555             :                                    &pkt->subscriber->addr);
     556         128 :                 deleted = gm_packet_sg_drop(&pkt->items[i]);
     557             : 
     558         128 :                 gm_sg_update(sg, true);
     559         128 :                 if (deleted)
     560             :                         break;
     561             :         }
     562          30 : }
     563             : 
     564           2 : static void gm_packet_sg_remove_sources(struct gm_if *gm_ifp,
     565             :                                         struct gm_subscriber *subscriber,
     566             :                                         pim_addr grp, pim_addr *srcs,
     567             :                                         size_t n_src, enum gm_sub_sense sense)
     568             : {
     569           2 :         struct gm_sg *sg;
     570           2 :         struct gm_packet_sg *old_src;
     571           2 :         size_t i;
     572             : 
     573           4 :         for (i = 0; i < n_src; i++) {
     574           2 :                 sg = gm_sg_find(gm_ifp, grp, srcs[i]);
     575           2 :                 if (!sg)
     576           0 :                         continue;
     577             : 
     578           2 :                 old_src = gm_packet_sg_find(sg, sense, subscriber);
     579           2 :                 if (!old_src)
     580           1 :                         continue;
     581             : 
     582           1 :                 gm_packet_sg_drop(old_src);
     583           1 :                 gm_sg_update(sg, false);
     584             :         }
     585           2 : }
     586             : 
     587        1170 : static void gm_sg_expiry_cancel(struct gm_sg *sg)
     588             : {
     589        1170 :         if (sg->t_sg_expire && PIM_DEBUG_GM_TRACE)
     590           0 :                 zlog_debug(log_sg(sg, "alive, cancelling expiry timer"));
     591        1170 :         THREAD_OFF(sg->t_sg_expire);
     592        1170 :         sg->query_sbit = true;
     593        1170 : }
     594             : 
     595             : /* first pass: process all changes resulting in removal of state:
     596             :  *  - {TO,IS}_INCLUDE removes *,G EXCLUDE state (and S,G)
     597             :  *  - ALLOW_NEW_SOURCES, if *,G in EXCLUDE removes S,G state
     598             :  *  - BLOCK_OLD_SOURCES, if *,G in INCLUDE removes S,G state
     599             :  *  - {TO,IS}_EXCLUDE,   if *,G in INCLUDE removes S,G state
     600             :  * note *replacing* state is NOT considered *removing* state here
     601             :  *
     602             :  * everything else is thrown into pkt for creation of state in pass 2
     603             :  */
     604        1180 : static void gm_handle_v2_pass1(struct gm_packet_state *pkt,
     605             :                                struct mld_v2_rec_hdr *rechdr)
     606             : {
     607             :         /* NB: pkt->subscriber can be NULL here if the subscriber was not
     608             :          * previously seen!
     609             :          */
     610        1180 :         struct gm_subscriber *subscriber = pkt->subscriber;
     611        1180 :         struct gm_sg *grp;
     612        1180 :         struct gm_packet_sg *old_grp = NULL;
     613        1180 :         struct gm_packet_sg *item;
     614        1180 :         size_t n_src = ntohs(rechdr->n_src);
     615        1180 :         size_t j;
     616        1180 :         bool is_excl = false;
     617             : 
     618        1180 :         grp = gm_sg_find(pkt->iface, rechdr->grp, PIMADDR_ANY);
     619        1180 :         if (grp && subscriber)
     620        1036 :                 old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber);
     621             : 
     622        1180 :         assert(old_grp == NULL || old_grp->is_excl);
     623             : 
     624        1180 :         switch (rechdr->type) {
     625        1156 :         case MLD_RECTYPE_IS_EXCLUDE:
     626             :         case MLD_RECTYPE_CHANGE_TO_EXCLUDE:
     627             :                 /* this always replaces or creates state */
     628        1156 :                 is_excl = true;
     629        1156 :                 if (!grp)
     630          86 :                         grp = gm_sg_make(pkt->iface, rechdr->grp, PIMADDR_ANY);
     631             : 
     632        1156 :                 item = gm_packet_sg_setup(pkt, grp, is_excl, false);
     633        1156 :                 item->n_exclude = n_src;
     634             : 
     635             :                 /* [EXCL_INCL_SG_NOTE] referenced below
     636             :                  *
     637             :                  * in theory, we should drop any S,G that the host may have
     638             :                  * previously added in INCLUDE mode.  In practice, this is both
     639             :                  * incredibly rare and entirely irrelevant.  It only makes any
     640             :                  * difference if an S,G that the host previously had on the
     641             :                  * INCLUDE list is now on the blocked list for EXCLUDE, which
     642             :                  * we can cover in processing the S,G list in pass2_excl().
     643             :                  *
     644             :                  * Other S,G from the host are simply left to expire
     645             :                  * "naturally" through general expiry.
     646             :                  */
     647        1156 :                 break;
     648             : 
     649          16 :         case MLD_RECTYPE_IS_INCLUDE:
     650             :         case MLD_RECTYPE_CHANGE_TO_INCLUDE:
     651          16 :                 if (old_grp) {
     652             :                         /* INCLUDE has no *,G state, so old_grp here refers to
     653             :                          * previous EXCLUDE => delete it
     654             :                          */
     655           8 :                         gm_packet_sg_drop(old_grp);
     656           8 :                         gm_sg_update(grp, false);
     657             :                         CPP_NOTICE("need S,G PRUNE => NO_INFO transition here");
     658             :                 }
     659             :                 break;
     660             : 
     661           6 :         case MLD_RECTYPE_ALLOW_NEW_SOURCES:
     662           6 :                 if (old_grp) {
     663             :                         /* remove S,Gs from EXCLUDE, and then we're done */
     664           0 :                         gm_packet_sg_remove_sources(pkt->iface, subscriber,
     665           0 :                                                     rechdr->grp, rechdr->srcs,
     666             :                                                     n_src, GM_SUB_NEG);
     667           2 :                         return;
     668             :                 }
     669             :                 /* in INCLUDE mode => ALLOW_NEW_SOURCES is functionally
     670             :                  * idential to IS_INCLUDE (because the list of sources in
     671             :                  * IS_INCLUDE is not exhaustive)
     672             :                  */
     673             :                 break;
     674             : 
     675           2 :         case MLD_RECTYPE_BLOCK_OLD_SOURCES:
     676           2 :                 if (old_grp) {
     677             :                         /* this is intentionally not implemented because it
     678             :                          * would be complicated as hell.  we only take the list
     679             :                          * of blocked sources from full group state records
     680             :                          */
     681             :                         return;
     682             :                 }
     683             : 
     684           2 :                 if (subscriber)
     685           2 :                         gm_packet_sg_remove_sources(pkt->iface, subscriber,
     686           2 :                                                     rechdr->grp, rechdr->srcs,
     687             :                                                     n_src, GM_SUB_POS);
     688             :                 return;
     689             :         }
     690             : 
     691        1192 :         for (j = 0; j < n_src; j++) {
     692          14 :                 struct gm_sg *sg;
     693             : 
     694          14 :                 sg = gm_sg_find(pkt->iface, rechdr->grp, rechdr->srcs[j]);
     695          14 :                 if (!sg)
     696           3 :                         sg = gm_sg_make(pkt->iface, rechdr->grp,
     697             :                                         rechdr->srcs[j]);
     698             : 
     699          14 :                 gm_packet_sg_setup(pkt, sg, is_excl, true);
     700             :         }
     701             : }
     702             : 
     703             : /* second pass: creating/updating/refreshing state.  All the items from the
     704             :  * received packet have already been thrown into gm_packet_state.
     705             :  */
     706             : 
     707          14 : static void gm_handle_v2_pass2_incl(struct gm_packet_state *pkt, size_t i)
     708             : {
     709          14 :         struct gm_packet_sg *item = &pkt->items[i];
     710          14 :         struct gm_packet_sg *old = NULL;
     711          14 :         struct gm_sg *sg = item->sg;
     712             : 
     713             :         /* EXCLUDE state was already dropped in pass1 */
     714          14 :         assert(!gm_packet_sg_find(sg, GM_SUB_NEG, pkt->subscriber));
     715             : 
     716          14 :         old = gm_packet_sg_find(sg, GM_SUB_POS, pkt->subscriber);
     717          14 :         if (old)
     718          11 :                 gm_packet_sg_drop(old);
     719             : 
     720          14 :         pkt->n_active++;
     721          14 :         gm_packet_sg_subs_add(sg->subs_positive, item);
     722             : 
     723          14 :         sg->most_recent = item;
     724          14 :         gm_sg_expiry_cancel(sg);
     725          14 :         gm_sg_update(sg, false);
     726          14 : }
     727             : 
     728        1156 : static void gm_handle_v2_pass2_excl(struct gm_packet_state *pkt, size_t offs)
     729             : {
     730        1156 :         struct gm_packet_sg *item = &pkt->items[offs];
     731        1156 :         struct gm_packet_sg *old_grp, *item_dup;
     732        1156 :         struct gm_sg *sg_grp = item->sg;
     733        1156 :         size_t i;
     734             : 
     735        1156 :         old_grp = gm_packet_sg_find(sg_grp, GM_SUB_POS, pkt->subscriber);
     736        1156 :         if (old_grp) {
     737        1022 :                 for (i = 0; i < item->n_exclude; i++) {
     738           0 :                         struct gm_packet_sg *item_src, *old_src;
     739             : 
     740           0 :                         item_src = &pkt->items[offs + 1 + i];
     741           0 :                         old_src = gm_packet_sg_find(item_src->sg, GM_SUB_NEG,
     742             :                                                     pkt->subscriber);
     743           0 :                         if (old_src)
     744           0 :                                 gm_packet_sg_drop(old_src);
     745             : 
     746             :                         /* See [EXCL_INCL_SG_NOTE] above - we can have old S,G
     747             :                          * items left over if the host previously had INCLUDE
     748             :                          * mode going.  Remove them here if we find any.
     749             :                          */
     750           0 :                         old_src = gm_packet_sg_find(item_src->sg, GM_SUB_POS,
     751             :                                                     pkt->subscriber);
     752           0 :                         if (old_src)
     753           0 :                                 gm_packet_sg_drop(old_src);
     754             :                 }
     755             : 
     756             :                 /* the previous loop has removed the S,G entries which are
     757             :                  * still excluded after this update.  So anything left on the
     758             :                  * old item was previously excluded but is now included
     759             :                  * => need to trigger update on S,G
     760             :                  */
     761        1022 :                 for (i = 0; i < old_grp->n_exclude; i++) {
     762           0 :                         struct gm_packet_sg *old_src;
     763           0 :                         struct gm_sg *old_sg_src;
     764             : 
     765           0 :                         old_src = old_grp + 1 + i;
     766           0 :                         old_sg_src = old_src->sg;
     767           0 :                         if (!old_sg_src)
     768           0 :                                 continue;
     769             : 
     770           0 :                         gm_packet_sg_drop(old_src);
     771           0 :                         gm_sg_update(old_sg_src, false);
     772             :                 }
     773             : 
     774        1022 :                 gm_packet_sg_drop(old_grp);
     775             :         }
     776             : 
     777        1156 :         item_dup = gm_packet_sg_subs_add(sg_grp->subs_positive, item);
     778        1156 :         assert(!item_dup);
     779        1156 :         pkt->n_active++;
     780             : 
     781        1156 :         sg_grp->most_recent = item;
     782        1156 :         gm_sg_expiry_cancel(sg_grp);
     783             : 
     784        2312 :         for (i = 0; i < item->n_exclude; i++) {
     785           0 :                 struct gm_packet_sg *item_src;
     786             : 
     787           0 :                 item_src = &pkt->items[offs + 1 + i];
     788           0 :                 item_dup = gm_packet_sg_subs_add(item_src->sg->subs_negative,
     789             :                                                  item_src);
     790             : 
     791           0 :                 if (item_dup)
     792           0 :                         item_src->sg = NULL;
     793             :                 else {
     794           0 :                         pkt->n_active++;
     795           0 :                         gm_sg_update(item_src->sg, false);
     796             :                 }
     797             :         }
     798             : 
     799             :         /* TODO: determine best ordering between gm_sg_update(S,G) and (*,G)
     800             :          * to get lower PIM churn/flapping
     801             :          */
     802        1156 :         gm_sg_update(sg_grp, false);
     803        1156 : }
     804             : 
     805             : CPP_NOTICE("TODO: QRV/QQIC are not copied from queries to local state");
     806             : /* on receiving a query, we need to update our robustness/query interval to
     807             :  * match, so we correctly process group/source specific queries after last
     808             :  * member leaves
     809             :  */
     810             : 
     811         258 : static void gm_handle_v2_report(struct gm_if *gm_ifp,
     812             :                                 const struct sockaddr_in6 *pkt_src, char *data,
     813             :                                 size_t len)
     814             : {
     815         258 :         struct mld_v2_report_hdr *hdr;
     816         258 :         size_t i, n_records, max_entries;
     817         258 :         struct gm_packet_state *pkt;
     818             : 
     819         258 :         if (len < sizeof(*hdr)) {
     820           0 :                 if (PIM_DEBUG_GM_PACKETS)
     821           0 :                         zlog_debug(log_pkt_src(
     822             :                                 "malformed MLDv2 report (truncated header)"));
     823           0 :                 gm_ifp->stats.rx_drop_malformed++;
     824           0 :                 return;
     825             :         }
     826             : 
     827             :         /* errors after this may at least partially process the packet */
     828         258 :         gm_ifp->stats.rx_new_report++;
     829             : 
     830         258 :         hdr = (struct mld_v2_report_hdr *)data;
     831         258 :         data += sizeof(*hdr);
     832         258 :         len -= sizeof(*hdr);
     833             : 
     834             :         /* can't have more *,G and S,G items than there is space for ipv6
     835             :          * addresses, so just use this to allocate temporary buffer
     836             :          */
     837         258 :         max_entries = len / sizeof(pim_addr);
     838         258 :         pkt = XCALLOC(MTYPE_GM_STATE,
     839             :                       offsetof(struct gm_packet_state, items[max_entries]));
     840         258 :         pkt->n_sg = max_entries;
     841         258 :         pkt->iface = gm_ifp;
     842         258 :         pkt->subscriber = gm_subscriber_findref(gm_ifp, pkt_src->sin6_addr);
     843             : 
     844         258 :         n_records = ntohs(hdr->n_records);
     845             : 
     846             :         /* validate & remove state in v2_pass1() */
     847        1438 :         for (i = 0; i < n_records; i++) {
     848        1181 :                 struct mld_v2_rec_hdr *rechdr;
     849        1181 :                 size_t n_src, record_size;
     850             : 
     851        1181 :                 if (len < sizeof(*rechdr)) {
     852           0 :                         zlog_warn(log_pkt_src(
     853             :                                 "malformed MLDv2 report (truncated record header)"));
     854           0 :                         gm_ifp->stats.rx_trunc_report++;
     855           0 :                         break;
     856             :                 }
     857             : 
     858        1181 :                 rechdr = (struct mld_v2_rec_hdr *)data;
     859        1181 :                 data += sizeof(*rechdr);
     860        1181 :                 len -= sizeof(*rechdr);
     861             : 
     862        1181 :                 n_src = ntohs(rechdr->n_src);
     863        1181 :                 record_size = n_src * sizeof(pim_addr) + rechdr->aux_len * 4;
     864             : 
     865        1181 :                 if (len < record_size) {
     866           0 :                         zlog_warn(log_pkt_src(
     867             :                                 "malformed MLDv2 report (truncated source list)"));
     868           0 :                         gm_ifp->stats.rx_trunc_report++;
     869           0 :                         break;
     870             :                 }
     871        1181 :                 if (!IN6_IS_ADDR_MULTICAST(&rechdr->grp)) {
     872           1 :                         zlog_warn(
     873             :                                 log_pkt_src(
     874             :                                         "malformed MLDv2 report (invalid group %pI6)"),
     875             :                                 &rechdr->grp);
     876           1 :                         gm_ifp->stats.rx_trunc_report++;
     877           1 :                         break;
     878             :                 }
     879             : 
     880        1180 :                 data += record_size;
     881        1180 :                 len -= record_size;
     882             : 
     883        1180 :                 gm_handle_v2_pass1(pkt, rechdr);
     884             :         }
     885             : 
     886         258 :         if (!pkt->n_active) {
     887           7 :                 gm_subscriber_drop(&pkt->subscriber);
     888           7 :                 XFREE(MTYPE_GM_STATE, pkt);
     889           7 :                 return;
     890             :         }
     891             : 
     892         251 :         pkt = XREALLOC(MTYPE_GM_STATE, pkt,
     893             :                        offsetof(struct gm_packet_state, items[pkt->n_active]));
     894         251 :         pkt->n_sg = pkt->n_active;
     895         251 :         pkt->n_active = 0;
     896             : 
     897         251 :         monotime(&pkt->received);
     898         251 :         if (!pkt->subscriber)
     899          30 :                 pkt->subscriber = gm_subscriber_get(gm_ifp, pkt_src->sin6_addr);
     900         251 :         gm_packets_add_tail(pkt->subscriber->packets, pkt);
     901         251 :         gm_packet_expires_add_tail(gm_ifp->expires, pkt);
     902             : 
     903        1421 :         for (i = 0; i < pkt->n_sg; i++)
     904        1170 :                 if (!pkt->items[i].is_excl)
     905          14 :                         gm_handle_v2_pass2_incl(pkt, i);
     906             :                 else {
     907        1156 :                         gm_handle_v2_pass2_excl(pkt, i);
     908        1156 :                         i += pkt->items[i].n_exclude;
     909             :                 }
     910             : 
     911         251 :         if (pkt->n_active == 0)
     912           0 :                 gm_packet_free(pkt);
     913             : }
     914             : 
     915           0 : static void gm_handle_v1_report(struct gm_if *gm_ifp,
     916             :                                 const struct sockaddr_in6 *pkt_src, char *data,
     917             :                                 size_t len)
     918             : {
     919           0 :         struct mld_v1_pkt *hdr;
     920           0 :         struct gm_packet_state *pkt;
     921           0 :         struct gm_sg *grp;
     922           0 :         struct gm_packet_sg *item;
     923           0 :         size_t max_entries;
     924             : 
     925           0 :         if (len < sizeof(*hdr)) {
     926           0 :                 if (PIM_DEBUG_GM_PACKETS)
     927           0 :                         zlog_debug(log_pkt_src(
     928             :                                 "malformed MLDv1 report (truncated)"));
     929           0 :                 gm_ifp->stats.rx_drop_malformed++;
     930           0 :                 return;
     931             :         }
     932             : 
     933           0 :         gm_ifp->stats.rx_old_report++;
     934             : 
     935           0 :         hdr = (struct mld_v1_pkt *)data;
     936             : 
     937           0 :         max_entries = 1;
     938           0 :         pkt = XCALLOC(MTYPE_GM_STATE,
     939             :                       offsetof(struct gm_packet_state, items[max_entries]));
     940           0 :         pkt->n_sg = max_entries;
     941           0 :         pkt->iface = gm_ifp;
     942           0 :         pkt->subscriber = gm_subscriber_findref(gm_ifp, gm_dummy_untracked);
     943             : 
     944             :         /* { equivalent of gm_handle_v2_pass1() with IS_EXCLUDE */
     945             : 
     946           0 :         grp = gm_sg_find(pkt->iface, hdr->grp, PIMADDR_ANY);
     947           0 :         if (!grp)
     948           0 :                 grp = gm_sg_make(pkt->iface, hdr->grp, PIMADDR_ANY);
     949             : 
     950           0 :         item = gm_packet_sg_setup(pkt, grp, true, false);
     951           0 :         item->n_exclude = 0;
     952           0 :         CPP_NOTICE("set v1-seen timer on grp here");
     953             : 
     954             :         /* } */
     955             : 
     956             :         /* pass2 will count n_active back up to 1.  Also since a v1 report
     957             :          * has exactly 1 group, we can skip the realloc() that v2 needs here.
     958             :          */
     959           0 :         assert(pkt->n_active == 1);
     960           0 :         pkt->n_sg = pkt->n_active;
     961           0 :         pkt->n_active = 0;
     962             : 
     963           0 :         monotime(&pkt->received);
     964           0 :         if (!pkt->subscriber)
     965           0 :                 pkt->subscriber = gm_subscriber_get(gm_ifp, gm_dummy_untracked);
     966           0 :         gm_packets_add_tail(pkt->subscriber->packets, pkt);
     967           0 :         gm_packet_expires_add_tail(gm_ifp->expires, pkt);
     968             : 
     969             :         /* pass2 covers installing state & removing old state;  all the v1
     970             :          * compat is handled at this point.
     971             :          *
     972             :          * Note that "old state" may be v2;  subscribers will switch from v2
     973             :          * reports to v1 reports when the querier changes from v2 to v1.  So,
     974             :          * limiting this to v1 would be wrong.
     975             :          */
     976           0 :         gm_handle_v2_pass2_excl(pkt, 0);
     977             : 
     978           0 :         if (pkt->n_active == 0)
     979           0 :                 gm_packet_free(pkt);
     980             : }
     981             : 
     982           0 : static void gm_handle_v1_leave(struct gm_if *gm_ifp,
     983             :                                const struct sockaddr_in6 *pkt_src, char *data,
     984             :                                size_t len)
     985             : {
     986           0 :         struct mld_v1_pkt *hdr;
     987           0 :         struct gm_subscriber *subscriber;
     988           0 :         struct gm_sg *grp;
     989           0 :         struct gm_packet_sg *old_grp;
     990             : 
     991           0 :         if (len < sizeof(*hdr)) {
     992           0 :                 if (PIM_DEBUG_GM_PACKETS)
     993           0 :                         zlog_debug(log_pkt_src(
     994             :                                 "malformed MLDv1 leave (truncated)"));
     995           0 :                 gm_ifp->stats.rx_drop_malformed++;
     996           0 :                 return;
     997             :         }
     998             : 
     999           0 :         gm_ifp->stats.rx_old_leave++;
    1000             : 
    1001           0 :         hdr = (struct mld_v1_pkt *)data;
    1002             : 
    1003           0 :         subscriber = gm_subscriber_findref(gm_ifp, gm_dummy_untracked);
    1004           0 :         if (!subscriber)
    1005             :                 return;
    1006             : 
    1007             :         /* { equivalent of gm_handle_v2_pass1() with IS_INCLUDE */
    1008             : 
    1009           0 :         grp = gm_sg_find(gm_ifp, hdr->grp, PIMADDR_ANY);
    1010           0 :         if (grp) {
    1011           0 :                 old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber);
    1012           0 :                 if (old_grp) {
    1013           0 :                         gm_packet_sg_drop(old_grp);
    1014           0 :                         gm_sg_update(grp, false);
    1015           0 :                         CPP_NOTICE("need S,G PRUNE => NO_INFO transition here");
    1016             :                 }
    1017             :         }
    1018             : 
    1019             :         /* } */
    1020             : 
    1021             :         /* nothing more to do here, pass2 is no-op for leaves */
    1022           0 :         gm_subscriber_drop(&subscriber);
    1023             : }
    1024             : 
    1025             : /* for each general query received (or sent), a timer is started to expire
    1026             :  * _everything_ at the appropriate time (including robustness multiplier).
    1027             :  *
    1028             :  * So when this timer hits, all packets - with all of their items - that were
    1029             :  * received *before* the query are aged out, and state updated accordingly.
    1030             :  * Note that when we receive a refresh/update, the previous/old packet is
    1031             :  * already dropped and replaced with a new one, so in normal steady-state
    1032             :  * operation, this timer won't be doing anything.
    1033             :  *
    1034             :  * Additionally, if a subscriber actively leaves a group, that goes through
    1035             :  * its own path too and won't hit this.  This is really only triggered when a
    1036             :  * host straight up disappears.
    1037             :  */
    1038          74 : static void gm_t_expire(struct thread *t)
    1039             : {
    1040          74 :         struct gm_if *gm_ifp = THREAD_ARG(t);
    1041          74 :         struct gm_packet_state *pkt;
    1042             : 
    1043          74 :         zlog_info(log_ifp("general expiry timer"));
    1044             : 
    1045         148 :         while (gm_ifp->n_pending) {
    1046         148 :                 struct gm_general_pending *pend = gm_ifp->pending;
    1047         148 :                 struct timeval remain;
    1048         148 :                 int64_t remain_ms;
    1049             : 
    1050         148 :                 remain_ms = monotime_until(&pend->expiry, &remain);
    1051         148 :                 if (remain_ms > 0) {
    1052          74 :                         if (PIM_DEBUG_GM_EVENTS)
    1053          74 :                                 zlog_debug(
    1054             :                                         log_ifp("next general expiry in %" PRId64 "ms"),
    1055             :                                         remain_ms / 1000);
    1056             : 
    1057          74 :                         thread_add_timer_tv(router->master, gm_t_expire, gm_ifp,
    1058             :                                             &remain, &gm_ifp->t_expire);
    1059          74 :                         return;
    1060             :                 }
    1061             : 
    1062          74 :                 while ((pkt = gm_packet_expires_first(gm_ifp->expires))) {
    1063          74 :                         if (timercmp(&pkt->received, &pend->query, >=))
    1064             :                                 break;
    1065             : 
    1066           0 :                         if (PIM_DEBUG_GM_PACKETS)
    1067           0 :                                 zlog_debug(log_ifp("expire packet %p"), pkt);
    1068           0 :                         gm_packet_drop(pkt, true);
    1069             :                 }
    1070             : 
    1071          74 :                 gm_ifp->n_pending--;
    1072          74 :                 memmove(gm_ifp->pending, gm_ifp->pending + 1,
    1073          74 :                         gm_ifp->n_pending * sizeof(gm_ifp->pending[0]));
    1074             :         }
    1075             : 
    1076           0 :         if (PIM_DEBUG_GM_EVENTS)
    1077           0 :                 zlog_debug(log_ifp("next general expiry waiting for query"));
    1078             : }
    1079             : 
    1080             : /* NB: the receive handlers will also run when sending packets, since we
    1081             :  * receive our own packets back in.
    1082             :  */
    1083          90 : static void gm_handle_q_general(struct gm_if *gm_ifp,
    1084             :                                 struct gm_query_timers *timers)
    1085             : {
    1086          90 :         struct timeval now, expiry;
    1087          90 :         struct gm_general_pending *pend;
    1088             : 
    1089          90 :         monotime(&now);
    1090          90 :         timeradd(&now, &timers->expire_wait, &expiry);
    1091             : 
    1092          90 :         while (gm_ifp->n_pending) {
    1093          75 :                 pend = &gm_ifp->pending[gm_ifp->n_pending - 1];
    1094             : 
    1095          75 :                 if (timercmp(&pend->expiry, &expiry, <))
    1096             :                         break;
    1097             : 
    1098             :                 /* if we end up here, the last item in pending[] has an expiry
    1099             :                  * later than the expiry for this query.  But our query time
    1100             :                  * (now) is later than that of the item (because, well, that's
    1101             :                  * how time works.)  This makes this query meaningless since
    1102             :                  * it's "supersetted" within the preexisting query
    1103             :                  */
    1104             : 
    1105           0 :                 if (PIM_DEBUG_GM_TRACE_DETAIL)
    1106           0 :                         zlog_debug(
    1107             :                                 log_ifp("zapping supersetted general timer %pTVMu"),
    1108             :                                 &pend->expiry);
    1109             : 
    1110           0 :                 gm_ifp->n_pending--;
    1111           0 :                 if (!gm_ifp->n_pending)
    1112          90 :                         THREAD_OFF(gm_ifp->t_expire);
    1113             :         }
    1114             : 
    1115             :         /* people might be messing with their configs or something */
    1116          90 :         if (gm_ifp->n_pending == array_size(gm_ifp->pending))
    1117           0 :                 return;
    1118             : 
    1119          90 :         pend = &gm_ifp->pending[gm_ifp->n_pending];
    1120          90 :         pend->query = now;
    1121          90 :         pend->expiry = expiry;
    1122             : 
    1123          90 :         if (!gm_ifp->n_pending++) {
    1124          15 :                 if (PIM_DEBUG_GM_TRACE)
    1125          15 :                         zlog_debug(
    1126             :                                 log_ifp("starting general timer @ 0: %pTVMu"),
    1127             :                                 &pend->expiry);
    1128          15 :                 thread_add_timer_tv(router->master, gm_t_expire, gm_ifp,
    1129             :                                     &timers->expire_wait, &gm_ifp->t_expire);
    1130          75 :         } else if (PIM_DEBUG_GM_TRACE)
    1131          90 :                 zlog_debug(log_ifp("appending general timer @ %u: %pTVMu"),
    1132             :                            gm_ifp->n_pending, &pend->expiry);
    1133             : }
    1134             : 
    1135           1 : static void gm_t_sg_expire(struct thread *t)
    1136             : {
    1137           1 :         struct gm_sg *sg = THREAD_ARG(t);
    1138           1 :         struct gm_if *gm_ifp = sg->iface;
    1139           1 :         struct gm_packet_sg *item;
    1140             : 
    1141           1 :         assertf(sg->state == GM_SG_JOIN_EXPIRING ||
    1142             :                         sg->state == GM_SG_NOPRUNE_EXPIRING,
    1143             :                 "%pSG%%%s %pTHD", &sg->sgaddr, gm_ifp->ifp->name, t);
    1144             : 
    1145           2 :         frr_each_safe (gm_packet_sg_subs, sg->subs_positive, item)
    1146             :                 /* this will also drop EXCLUDE mode S,G lists together with
    1147             :                  * the *,G entry
    1148             :                  */
    1149           0 :                 gm_packet_sg_drop(item);
    1150             : 
    1151             :         /* subs_negative items are only timed out together with the *,G entry
    1152             :          * since we won't get any reports for a group-and-source query
    1153             :          */
    1154           1 :         gm_sg_update(sg, true);
    1155           1 : }
    1156             : 
    1157           3 : static bool gm_sg_check_recent(struct gm_if *gm_ifp, struct gm_sg *sg,
    1158             :                                struct timeval ref)
    1159             : {
    1160           3 :         struct gm_packet_state *pkt;
    1161             : 
    1162           3 :         if (!sg->most_recent) {
    1163           3 :                 struct gm_packet_state *best_pkt = NULL;
    1164           3 :                 struct gm_packet_sg *item;
    1165             : 
    1166           6 :                 frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
    1167           0 :                         pkt = gm_packet_sg2state(item);
    1168             : 
    1169           0 :                         if (!best_pkt ||
    1170           0 :                             timercmp(&pkt->received, &best_pkt->received, >)) {
    1171           0 :                                 best_pkt = pkt;
    1172           0 :                                 sg->most_recent = item;
    1173             :                         }
    1174             :                 }
    1175             :         }
    1176           3 :         if (sg->most_recent) {
    1177           0 :                 struct timeval fuzz;
    1178             : 
    1179           0 :                 pkt = gm_packet_sg2state(sg->most_recent);
    1180             : 
    1181             :                 /* this shouldn't happen on plain old real ethernet segment,
    1182             :                  * but on something like a VXLAN or VPLS it is very possible
    1183             :                  * that we get a report before the query that triggered it.
    1184             :                  * (imagine a triangle scenario with 3 datacenters, it's very
    1185             :                  * possible A->B + B->C is faster than A->C due to odd routing)
    1186             :                  *
    1187             :                  * This makes a little tolerance allowance to handle that case.
    1188             :                  */
    1189           0 :                 timeradd(&pkt->received, &gm_ifp->cfg_timing_fuzz, &fuzz);
    1190             : 
    1191           0 :                 if (timercmp(&fuzz, &ref, >))
    1192           0 :                         return true;
    1193             :         }
    1194             :         return false;
    1195             : }
    1196             : 
    1197           3 : static void gm_sg_timer_start(struct gm_if *gm_ifp, struct gm_sg *sg,
    1198             :                               struct timeval expire_wait)
    1199             : {
    1200           3 :         struct timeval now;
    1201             : 
    1202           3 :         if (!sg)
    1203           2 :                 return;
    1204           3 :         if (sg->state == GM_SG_PRUNE)
    1205             :                 return;
    1206             : 
    1207           3 :         monotime(&now);
    1208           3 :         if (gm_sg_check_recent(gm_ifp, sg, now))
    1209             :                 return;
    1210             : 
    1211           3 :         if (PIM_DEBUG_GM_TRACE)
    1212           3 :                 zlog_debug(log_sg(sg, "expiring in %pTVI"), &expire_wait);
    1213             : 
    1214           3 :         if (sg->t_sg_expire) {
    1215           2 :                 struct timeval remain;
    1216             : 
    1217           2 :                 remain = thread_timer_remain(sg->t_sg_expire);
    1218           2 :                 if (timercmp(&remain, &expire_wait, <=))
    1219           2 :                         return;
    1220             : 
    1221           0 :                 THREAD_OFF(sg->t_sg_expire);
    1222             :         }
    1223             : 
    1224           1 :         thread_add_timer_tv(router->master, gm_t_sg_expire, sg, &expire_wait,
    1225             :                             &sg->t_sg_expire);
    1226             : }
    1227             : 
    1228           2 : static void gm_handle_q_groupsrc(struct gm_if *gm_ifp,
    1229             :                                  struct gm_query_timers *timers, pim_addr grp,
    1230             :                                  const pim_addr *srcs, size_t n_src)
    1231             : {
    1232           2 :         struct gm_sg *sg;
    1233           2 :         size_t i;
    1234             : 
    1235           4 :         for (i = 0; i < n_src; i++) {
    1236           2 :                 sg = gm_sg_find(gm_ifp, grp, srcs[i]);
    1237           2 :                 gm_sg_timer_start(gm_ifp, sg, timers->expire_wait);
    1238             :         }
    1239           2 : }
    1240             : 
    1241           0 : static void gm_t_grp_expire(struct thread *t)
    1242             : {
    1243             :         /* if we're here, that means when we received the group-specific query
    1244             :          * there was one or more active S,G for this group.  For *,G the timer
    1245             :          * in sg->t_sg_expire is running separately and gets cancelled when we
    1246             :          * receive a report, so that work is left to gm_t_sg_expire and we
    1247             :          * shouldn't worry about it here.
    1248             :          */
    1249           0 :         struct gm_grp_pending *pend = THREAD_ARG(t);
    1250           0 :         struct gm_if *gm_ifp = pend->iface;
    1251           0 :         struct gm_sg *sg, *sg_start, sg_ref = {};
    1252             : 
    1253           0 :         if (PIM_DEBUG_GM_EVENTS)
    1254           0 :                 zlog_debug(log_ifp("*,%pPAs S,G timer expired"), &pend->grp);
    1255             : 
    1256             :         /* gteq lookup - try to find *,G or S,G  (S,G is > *,G)
    1257             :          * could technically be gt to skip a possible *,G
    1258             :          */
    1259           0 :         sg_ref.sgaddr.grp = pend->grp;
    1260           0 :         sg_ref.sgaddr.src = PIMADDR_ANY;
    1261           0 :         sg_start = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref);
    1262             : 
    1263           0 :         frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) {
    1264           0 :                 struct gm_packet_sg *item;
    1265             : 
    1266           0 :                 if (pim_addr_cmp(sg->sgaddr.grp, pend->grp))
    1267             :                         break;
    1268           0 :                 if (pim_addr_is_any(sg->sgaddr.src))
    1269             :                         /* handled by gm_t_sg_expire / sg->t_sg_expire */
    1270           0 :                         continue;
    1271           0 :                 if (gm_sg_check_recent(gm_ifp, sg, pend->query))
    1272           0 :                         continue;
    1273             : 
    1274             :                 /* we may also have a group-source-specific query going on in
    1275             :                  * parallel.  But if we received nothing for the *,G query,
    1276             :                  * the S,G query is kinda irrelevant.
    1277             :                  */
    1278           0 :                 THREAD_OFF(sg->t_sg_expire);
    1279             : 
    1280           0 :                 frr_each_safe (gm_packet_sg_subs, sg->subs_positive, item)
    1281             :                         /* this will also drop the EXCLUDE S,G lists */
    1282           0 :                         gm_packet_sg_drop(item);
    1283             : 
    1284           0 :                 gm_sg_update(sg, true);
    1285             :         }
    1286             : 
    1287           0 :         gm_grp_pends_del(gm_ifp->grp_pends, pend);
    1288           0 :         XFREE(MTYPE_GM_GRP_PENDING, pend);
    1289           0 : }
    1290             : 
    1291           0 : static void gm_handle_q_group(struct gm_if *gm_ifp,
    1292             :                               struct gm_query_timers *timers, pim_addr grp)
    1293             : {
    1294           0 :         struct gm_sg *sg, sg_ref = {};
    1295           0 :         struct gm_grp_pending *pend, pend_ref = {};
    1296             : 
    1297           0 :         sg_ref.sgaddr.grp = grp;
    1298           0 :         sg_ref.sgaddr.src = PIMADDR_ANY;
    1299             :         /* gteq lookup - try to find *,G or S,G  (S,G is > *,G) */
    1300           0 :         sg = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref);
    1301             : 
    1302           0 :         if (!sg || pim_addr_cmp(sg->sgaddr.grp, grp))
    1303             :                 /* we have nothing at all for this group - don't waste RAM */
    1304           0 :                 return;
    1305             : 
    1306           0 :         if (pim_addr_is_any(sg->sgaddr.src)) {
    1307             :                 /* actually found *,G entry here */
    1308           0 :                 if (PIM_DEBUG_GM_TRACE)
    1309           0 :                         zlog_debug(log_ifp("*,%pPAs expiry timer starting"),
    1310             :                                    &grp);
    1311           0 :                 gm_sg_timer_start(gm_ifp, sg, timers->expire_wait);
    1312             : 
    1313           0 :                 sg = gm_sgs_next(gm_ifp->sgs, sg);
    1314           0 :                 if (!sg || pim_addr_cmp(sg->sgaddr.grp, grp))
    1315             :                         /* no S,G for this group */
    1316           0 :                         return;
    1317             :         }
    1318             : 
    1319           0 :         pend_ref.grp = grp;
    1320           0 :         pend = gm_grp_pends_find(gm_ifp->grp_pends, &pend_ref);
    1321             : 
    1322           0 :         if (pend) {
    1323           0 :                 struct timeval remain;
    1324             : 
    1325           0 :                 remain = thread_timer_remain(pend->t_expire);
    1326           0 :                 if (timercmp(&remain, &timers->expire_wait, <=))
    1327           0 :                         return;
    1328             : 
    1329           0 :                 THREAD_OFF(pend->t_expire);
    1330             :         } else {
    1331           0 :                 pend = XCALLOC(MTYPE_GM_GRP_PENDING, sizeof(*pend));
    1332           0 :                 pend->grp = grp;
    1333           0 :                 pend->iface = gm_ifp;
    1334           0 :                 gm_grp_pends_add(gm_ifp->grp_pends, pend);
    1335             :         }
    1336             : 
    1337           0 :         monotime(&pend->query);
    1338           0 :         thread_add_timer_tv(router->master, gm_t_grp_expire, pend,
    1339             :                             &timers->expire_wait, &pend->t_expire);
    1340             : 
    1341           0 :         if (PIM_DEBUG_GM_TRACE)
    1342           0 :                 zlog_debug(log_ifp("*,%pPAs S,G timer started: %pTHD"), &grp,
    1343             :                            pend->t_expire);
    1344             : }
    1345             : 
    1346          27 : static void gm_bump_querier(struct gm_if *gm_ifp)
    1347             : {
    1348          27 :         struct pim_interface *pim_ifp = gm_ifp->ifp->info;
    1349             : 
    1350          27 :         THREAD_OFF(gm_ifp->t_query);
    1351             : 
    1352          27 :         if (pim_addr_is_any(pim_ifp->ll_lowest))
    1353             :                 return;
    1354           0 :         if (!IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest))
    1355             :                 return;
    1356             : 
    1357           0 :         gm_ifp->n_startup = gm_ifp->cur_qrv;
    1358             : 
    1359           0 :         thread_execute(router->master, gm_t_query, gm_ifp, 0);
    1360             : }
    1361             : 
    1362           0 : static void gm_t_other_querier(struct thread *t)
    1363             : {
    1364           0 :         struct gm_if *gm_ifp = THREAD_ARG(t);
    1365           0 :         struct pim_interface *pim_ifp = gm_ifp->ifp->info;
    1366             : 
    1367           0 :         zlog_info(log_ifp("other querier timer expired"));
    1368             : 
    1369           0 :         gm_ifp->querier = pim_ifp->ll_lowest;
    1370           0 :         gm_ifp->n_startup = gm_ifp->cur_qrv;
    1371             : 
    1372           0 :         thread_execute(router->master, gm_t_query, gm_ifp, 0);
    1373           0 : }
    1374             : 
    1375          92 : static void gm_handle_query(struct gm_if *gm_ifp,
    1376             :                             const struct sockaddr_in6 *pkt_src,
    1377             :                             pim_addr *pkt_dst, char *data, size_t len)
    1378             : {
    1379          92 :         struct mld_v2_query_hdr *hdr;
    1380          92 :         struct pim_interface *pim_ifp = gm_ifp->ifp->info;
    1381          92 :         struct gm_query_timers timers;
    1382          92 :         bool general_query;
    1383             : 
    1384          92 :         if (len < sizeof(struct mld_v2_query_hdr) &&
    1385          92 :             len != sizeof(struct mld_v1_pkt)) {
    1386           0 :                 zlog_warn(log_pkt_src("invalid query size"));
    1387           0 :                 gm_ifp->stats.rx_drop_malformed++;
    1388           0 :                 return;
    1389             :         }
    1390             : 
    1391          92 :         hdr = (struct mld_v2_query_hdr *)data;
    1392          92 :         general_query = pim_addr_is_any(hdr->grp);
    1393             : 
    1394          92 :         if (!general_query && !IN6_IS_ADDR_MULTICAST(&hdr->grp)) {
    1395           0 :                 zlog_warn(log_pkt_src(
    1396             :                                   "malformed MLDv2 query (invalid group %pI6)"),
    1397             :                           &hdr->grp);
    1398           0 :                 gm_ifp->stats.rx_drop_malformed++;
    1399           0 :                 return;
    1400             :         }
    1401             : 
    1402          92 :         if (len >= sizeof(struct mld_v2_query_hdr)) {
    1403          92 :                 size_t src_space = ntohs(hdr->n_src) * sizeof(pim_addr);
    1404             : 
    1405          92 :                 if (len < sizeof(struct mld_v2_query_hdr) + src_space) {
    1406           0 :                         zlog_warn(log_pkt_src(
    1407             :                                 "malformed MLDv2 query (truncated source list)"));
    1408           0 :                         gm_ifp->stats.rx_drop_malformed++;
    1409           0 :                         return;
    1410             :                 }
    1411             : 
    1412          92 :                 if (general_query && src_space) {
    1413           0 :                         zlog_warn(log_pkt_src(
    1414             :                                 "malformed MLDv2 query (general query with non-empty source list)"));
    1415           0 :                         gm_ifp->stats.rx_drop_malformed++;
    1416           0 :                         return;
    1417             :                 }
    1418             :         }
    1419             : 
    1420             :         /* accepting queries unicast to us (or addressed to a wrong group)
    1421             :          * can mess up querier election as well as cause us to terminate
    1422             :          * traffic (since after a unicast query no reports will be coming in)
    1423             :          */
    1424          92 :         if (!IPV6_ADDR_SAME(pkt_dst, &gm_all_hosts)) {
    1425           2 :                 if (pim_addr_is_any(hdr->grp)) {
    1426           0 :                         zlog_warn(
    1427             :                                 log_pkt_src(
    1428             :                                         "wrong destination %pPA for general query"),
    1429             :                                 pkt_dst);
    1430           0 :                         gm_ifp->stats.rx_drop_dstaddr++;
    1431           0 :                         return;
    1432             :                 }
    1433             : 
    1434           2 :                 if (!IPV6_ADDR_SAME(&hdr->grp, pkt_dst)) {
    1435           0 :                         gm_ifp->stats.rx_drop_dstaddr++;
    1436           0 :                         zlog_warn(
    1437             :                                 log_pkt_src(
    1438             :                                         "wrong destination %pPA for group specific query"),
    1439             :                                 pkt_dst);
    1440           0 :                         return;
    1441             :                 }
    1442             :         }
    1443             : 
    1444          92 :         if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &gm_ifp->querier) < 0) {
    1445           3 :                 if (PIM_DEBUG_GM_EVENTS)
    1446           3 :                         zlog_debug(
    1447             :                                 log_pkt_src("replacing elected querier %pPA"),
    1448             :                                 &gm_ifp->querier);
    1449             : 
    1450           3 :                 gm_ifp->querier = pkt_src->sin6_addr;
    1451             :         }
    1452             : 
    1453          92 :         if (len == sizeof(struct mld_v1_pkt)) {
    1454           0 :                 timers.qrv = gm_ifp->cur_qrv;
    1455           0 :                 timers.max_resp_ms = hdr->max_resp_code;
    1456           0 :                 timers.qqic_ms = gm_ifp->cur_query_intv;
    1457             :         } else {
    1458          92 :                 timers.qrv = (hdr->flags & 0x7) ?: 8;
    1459          92 :                 timers.max_resp_ms = mld_max_resp_decode(hdr->max_resp_code);
    1460          92 :                 timers.qqic_ms = igmp_msg_decode8to16(hdr->qqic) * 1000;
    1461             :         }
    1462          92 :         timers.fuzz = gm_ifp->cfg_timing_fuzz;
    1463             : 
    1464          92 :         gm_expiry_calc(&timers);
    1465             : 
    1466          92 :         if (PIM_DEBUG_GM_TRACE_DETAIL)
    1467           0 :                 zlog_debug(
    1468             :                         log_ifp("query timers: QRV=%u max_resp=%ums qqic=%ums expire_wait=%pTVI"),
    1469             :                         timers.qrv, timers.max_resp_ms, timers.qqic_ms,
    1470             :                         &timers.expire_wait);
    1471             : 
    1472          92 :         if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &pim_ifp->ll_lowest) < 0) {
    1473          27 :                 unsigned int other_ms;
    1474             : 
    1475          27 :                 THREAD_OFF(gm_ifp->t_query);
    1476          27 :                 THREAD_OFF(gm_ifp->t_other_querier);
    1477             : 
    1478          27 :                 other_ms = timers.qrv * timers.qqic_ms + timers.max_resp_ms / 2;
    1479          27 :                 thread_add_timer_msec(router->master, gm_t_other_querier,
    1480             :                                       gm_ifp, other_ms,
    1481             :                                       &gm_ifp->t_other_querier);
    1482             :         }
    1483             : 
    1484          92 :         if (len == sizeof(struct mld_v1_pkt)) {
    1485           0 :                 if (general_query) {
    1486           0 :                         gm_handle_q_general(gm_ifp, &timers);
    1487           0 :                         gm_ifp->stats.rx_query_old_general++;
    1488             :                 } else {
    1489           0 :                         gm_handle_q_group(gm_ifp, &timers, hdr->grp);
    1490           0 :                         gm_ifp->stats.rx_query_old_group++;
    1491             :                 }
    1492           0 :                 return;
    1493             :         }
    1494             : 
    1495             :         /* v2 query - [S]uppress bit */
    1496          92 :         if (hdr->flags & 0x8) {
    1497           0 :                 gm_ifp->stats.rx_query_new_sbit++;
    1498           0 :                 return;
    1499             :         }
    1500             : 
    1501          92 :         if (general_query) {
    1502          90 :                 gm_handle_q_general(gm_ifp, &timers);
    1503          90 :                 gm_ifp->stats.rx_query_new_general++;
    1504           2 :         } else if (!ntohs(hdr->n_src)) {
    1505           0 :                 gm_handle_q_group(gm_ifp, &timers, hdr->grp);
    1506           0 :                 gm_ifp->stats.rx_query_new_group++;
    1507             :         } else {
    1508           2 :                 gm_handle_q_groupsrc(gm_ifp, &timers, hdr->grp, hdr->srcs,
    1509           2 :                                      ntohs(hdr->n_src));
    1510           2 :                 gm_ifp->stats.rx_query_new_groupsrc++;
    1511             :         }
    1512             : }
    1513             : 
    1514         350 : static void gm_rx_process(struct gm_if *gm_ifp,
    1515             :                           const struct sockaddr_in6 *pkt_src, pim_addr *pkt_dst,
    1516             :                           void *data, size_t pktlen)
    1517             : {
    1518         350 :         struct icmp6_plain_hdr *icmp6 = data;
    1519         350 :         uint16_t pkt_csum, ref_csum;
    1520         350 :         struct ipv6_ph ph6 = {
    1521             :                 .src = pkt_src->sin6_addr,
    1522             :                 .dst = *pkt_dst,
    1523         350 :                 .ulpl = htons(pktlen),
    1524             :                 .next_hdr = IPPROTO_ICMPV6,
    1525             :         };
    1526             : 
    1527         350 :         pkt_csum = icmp6->icmp6_cksum;
    1528         350 :         icmp6->icmp6_cksum = 0;
    1529         350 :         ref_csum = in_cksum_with_ph6(&ph6, data, pktlen);
    1530             : 
    1531         350 :         if (pkt_csum != ref_csum) {
    1532           0 :                 zlog_warn(
    1533             :                         log_pkt_src(
    1534             :                                 "(dst %pPA) packet RX checksum failure, expected %04hx, got %04hx"),
    1535             :                         pkt_dst, pkt_csum, ref_csum);
    1536           0 :                 gm_ifp->stats.rx_drop_csum++;
    1537           0 :                 return;
    1538             :         }
    1539             : 
    1540         350 :         data = (icmp6 + 1);
    1541         350 :         pktlen -= sizeof(*icmp6);
    1542             : 
    1543         350 :         switch (icmp6->icmp6_type) {
    1544          92 :         case ICMP6_MLD_QUERY:
    1545          92 :                 gm_handle_query(gm_ifp, pkt_src, pkt_dst, data, pktlen);
    1546          92 :                 break;
    1547           0 :         case ICMP6_MLD_V1_REPORT:
    1548           0 :                 gm_handle_v1_report(gm_ifp, pkt_src, data, pktlen);
    1549           0 :                 break;
    1550           0 :         case ICMP6_MLD_V1_DONE:
    1551           0 :                 gm_handle_v1_leave(gm_ifp, pkt_src, data, pktlen);
    1552           0 :                 break;
    1553         258 :         case ICMP6_MLD_V2_REPORT:
    1554         258 :                 gm_handle_v2_report(gm_ifp, pkt_src, data, pktlen);
    1555         258 :                 break;
    1556             :         }
    1557             : }
    1558             : 
    1559         351 : static bool ip6_check_hopopts_ra(uint8_t *hopopts, size_t hopopt_len,
    1560             :                                  uint16_t alert_type)
    1561             : {
    1562         351 :         uint8_t *hopopt_end;
    1563             : 
    1564         351 :         if (hopopt_len < 8)
    1565             :                 return false;
    1566         350 :         if (hopopt_len < (hopopts[1] + 1U) * 8U)
    1567             :                 return false;
    1568             : 
    1569         350 :         hopopt_end = hopopts + (hopopts[1] + 1) * 8;
    1570         350 :         hopopts += 2;
    1571             : 
    1572         350 :         while (hopopts < hopopt_end) {
    1573         350 :                 if (hopopts[0] == IP6OPT_PAD1) {
    1574           0 :                         hopopts++;
    1575           0 :                         continue;
    1576             :                 }
    1577             : 
    1578         350 :                 if (hopopts > hopopt_end - 2)
    1579             :                         break;
    1580         350 :                 if (hopopts > hopopt_end - 2 - hopopts[1])
    1581             :                         break;
    1582             : 
    1583         350 :                 if (hopopts[0] == IP6OPT_ROUTER_ALERT && hopopts[1] == 2) {
    1584         350 :                         uint16_t have_type = (hopopts[2] << 8) | hopopts[3];
    1585             : 
    1586         350 :                         if (have_type == alert_type)
    1587             :                                 return true;
    1588             :                 }
    1589             : 
    1590           0 :                 hopopts += 2 + hopopts[1];
    1591             :         }
    1592             :         return false;
    1593             : }
    1594             : 
    1595         369 : static void gm_t_recv(struct thread *t)
    1596             : {
    1597         369 :         struct pim_instance *pim = THREAD_ARG(t);
    1598         369 :         union {
    1599             :                 char buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) +
    1600             :                          CMSG_SPACE(256) /* hop options */ +
    1601             :                          CMSG_SPACE(sizeof(int)) /* hopcount */];
    1602             :                 struct cmsghdr align;
    1603             :         } cmsgbuf;
    1604         369 :         struct cmsghdr *cmsg;
    1605         369 :         struct in6_pktinfo *pktinfo = NULL;
    1606         369 :         uint8_t *hopopts = NULL;
    1607         369 :         size_t hopopt_len = 0;
    1608         369 :         int *hoplimit = NULL;
    1609         369 :         char rxbuf[2048];
    1610         369 :         struct msghdr mh[1] = {};
    1611         369 :         struct iovec iov[1];
    1612         369 :         struct sockaddr_in6 pkt_src[1] = {};
    1613         369 :         ssize_t nread;
    1614         369 :         size_t pktlen;
    1615             : 
    1616         369 :         thread_add_read(router->master, gm_t_recv, pim, pim->gm_socket,
    1617             :                         &pim->t_gm_recv);
    1618             : 
    1619         369 :         iov->iov_base = rxbuf;
    1620         369 :         iov->iov_len = sizeof(rxbuf);
    1621             : 
    1622         369 :         mh->msg_name = pkt_src;
    1623         369 :         mh->msg_namelen = sizeof(pkt_src);
    1624         369 :         mh->msg_control = cmsgbuf.buf;
    1625         369 :         mh->msg_controllen = sizeof(cmsgbuf.buf);
    1626         369 :         mh->msg_iov = iov;
    1627         369 :         mh->msg_iovlen = array_size(iov);
    1628         369 :         mh->msg_flags = 0;
    1629             : 
    1630         369 :         nread = recvmsg(pim->gm_socket, mh, MSG_PEEK | MSG_TRUNC);
    1631         369 :         if (nread <= 0) {
    1632           0 :                 zlog_err("(VRF %s) RX error: %m", pim->vrf->name);
    1633           0 :                 pim->gm_rx_drop_sys++;
    1634           0 :                 return;
    1635             :         }
    1636             : 
    1637         369 :         if ((size_t)nread > sizeof(rxbuf)) {
    1638           0 :                 iov->iov_base = XMALLOC(MTYPE_GM_PACKET, nread);
    1639           0 :                 iov->iov_len = nread;
    1640             :         }
    1641         369 :         nread = recvmsg(pim->gm_socket, mh, 0);
    1642         369 :         if (nread <= 0) {
    1643           0 :                 zlog_err("(VRF %s) RX error: %m", pim->vrf->name);
    1644           0 :                 pim->gm_rx_drop_sys++;
    1645           0 :                 goto out_free;
    1646             :         }
    1647             : 
    1648         369 :         struct interface *ifp;
    1649             : 
    1650         369 :         ifp = if_lookup_by_index(pkt_src->sin6_scope_id, pim->vrf->vrf_id);
    1651         369 :         if (!ifp || !ifp->info)
    1652          10 :                 goto out_free;
    1653             : 
    1654         359 :         struct pim_interface *pim_ifp = ifp->info;
    1655         359 :         struct gm_if *gm_ifp = pim_ifp->mld;
    1656             : 
    1657         359 :         if (!gm_ifp)
    1658           8 :                 goto out_free;
    1659             : 
    1660        2806 :         for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) {
    1661        1052 :                 if (cmsg->cmsg_level != SOL_IPV6)
    1662           0 :                         continue;
    1663             : 
    1664        1052 :                 switch (cmsg->cmsg_type) {
    1665         351 :                 case IPV6_PKTINFO:
    1666         351 :                         pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
    1667         351 :                         break;
    1668         350 :                 case IPV6_HOPOPTS:
    1669         350 :                         hopopts = CMSG_DATA(cmsg);
    1670         350 :                         hopopt_len = cmsg->cmsg_len - sizeof(*cmsg);
    1671         350 :                         break;
    1672         351 :                 case IPV6_HOPLIMIT:
    1673         351 :                         hoplimit = (int *)CMSG_DATA(cmsg);
    1674         351 :                         break;
    1675             :                 }
    1676             :         }
    1677             : 
    1678         351 :         if (!pktinfo || !hoplimit) {
    1679           0 :                 zlog_err(log_ifp(
    1680             :                         "BUG: packet without IPV6_PKTINFO or IPV6_HOPLIMIT"));
    1681           0 :                 pim->gm_rx_drop_sys++;
    1682           0 :                 goto out_free;
    1683             :         }
    1684             : 
    1685         351 :         if (*hoplimit != 1) {
    1686           0 :                 zlog_err(log_pkt_src("packet with hop limit != 1"));
    1687             :                 /* spoofing attempt => count on srcaddr counter */
    1688           0 :                 gm_ifp->stats.rx_drop_srcaddr++;
    1689           0 :                 goto out_free;
    1690             :         }
    1691             : 
    1692         351 :         if (!ip6_check_hopopts_ra(hopopts, hopopt_len, IP6_ALERT_MLD)) {
    1693           1 :                 zlog_err(log_pkt_src(
    1694             :                         "packet without IPv6 Router Alert MLD option"));
    1695           1 :                 gm_ifp->stats.rx_drop_ra++;
    1696           1 :                 goto out_free;
    1697             :         }
    1698             : 
    1699         350 :         if (IN6_IS_ADDR_UNSPECIFIED(&pkt_src->sin6_addr))
    1700             :                 /* reports from :: happen in normal operation for DAD, so
    1701             :                  * don't spam log messages about this
    1702             :                  */
    1703           0 :                 goto out_free;
    1704             : 
    1705         350 :         if (!IN6_IS_ADDR_LINKLOCAL(&pkt_src->sin6_addr)) {
    1706           0 :                 zlog_warn(log_pkt_src("packet from invalid source address"));
    1707           0 :                 gm_ifp->stats.rx_drop_srcaddr++;
    1708           0 :                 goto out_free;
    1709             :         }
    1710             : 
    1711         350 :         pktlen = nread;
    1712         350 :         if (pktlen < sizeof(struct icmp6_plain_hdr)) {
    1713           0 :                 zlog_warn(log_pkt_src("truncated packet"));
    1714           0 :                 gm_ifp->stats.rx_drop_malformed++;
    1715           0 :                 goto out_free;
    1716             :         }
    1717             : 
    1718         350 :         gm_rx_process(gm_ifp, pkt_src, &pktinfo->ipi6_addr, iov->iov_base,
    1719             :                       pktlen);
    1720             : 
    1721         369 : out_free:
    1722         369 :         if (iov->iov_base != rxbuf)
    1723         369 :                 XFREE(MTYPE_GM_PACKET, iov->iov_base);
    1724             : }
    1725             : 
    1726          62 : static void gm_send_query(struct gm_if *gm_ifp, pim_addr grp,
    1727             :                           const pim_addr *srcs, size_t n_srcs, bool s_bit)
    1728             : {
    1729          62 :         struct pim_interface *pim_ifp = gm_ifp->ifp->info;
    1730          62 :         struct sockaddr_in6 dstaddr = {
    1731             :                 .sin6_family = AF_INET6,
    1732          62 :                 .sin6_scope_id = gm_ifp->ifp->ifindex,
    1733             :         };
    1734          62 :         struct {
    1735             :                 struct icmp6_plain_hdr hdr;
    1736             :                 struct mld_v2_query_hdr v2_query;
    1737          62 :         } query = {
    1738             :                 /* clang-format off */
    1739             :                 .hdr = {
    1740             :                         .icmp6_type = ICMP6_MLD_QUERY,
    1741             :                         .icmp6_code = 0,
    1742             :                 },
    1743             :                 .v2_query = {
    1744             :                         .grp = grp,
    1745             :                 },
    1746             :                 /* clang-format on */
    1747             :         };
    1748          62 :         struct ipv6_ph ph6 = {
    1749             :                 .src = pim_ifp->ll_lowest,
    1750          62 :                 .ulpl = htons(sizeof(query)),
    1751             :                 .next_hdr = IPPROTO_ICMPV6,
    1752             :         };
    1753          62 :         union {
    1754             :                 char buf[CMSG_SPACE(8) /* hop options */ +
    1755             :                          CMSG_SPACE(sizeof(struct in6_pktinfo))];
    1756             :                 struct cmsghdr align;
    1757          62 :         } cmsg = {};
    1758          62 :         struct cmsghdr *cmh;
    1759          62 :         struct msghdr mh[1] = {};
    1760          62 :         struct iovec iov[3];
    1761          62 :         size_t iov_len;
    1762          62 :         ssize_t ret, expect_ret;
    1763          62 :         uint8_t *dp;
    1764          62 :         struct in6_pktinfo *pktinfo;
    1765             : 
    1766          62 :         if (if_is_loopback(gm_ifp->ifp)) {
    1767             :                 /* Linux is a bit odd with multicast on loopback */
    1768           0 :                 ph6.src = in6addr_loopback;
    1769           0 :                 dstaddr.sin6_addr = in6addr_loopback;
    1770          62 :         } else if (pim_addr_is_any(grp))
    1771          60 :                 dstaddr.sin6_addr = gm_all_hosts;
    1772             :         else
    1773           2 :                 dstaddr.sin6_addr = grp;
    1774             : 
    1775          62 :         query.v2_query.max_resp_code =
    1776          62 :                 mld_max_resp_encode(gm_ifp->cur_max_resp);
    1777          62 :         query.v2_query.flags = (gm_ifp->cur_qrv < 8) ? gm_ifp->cur_qrv : 0;
    1778          62 :         if (s_bit)
    1779           0 :                 query.v2_query.flags |= 0x08;
    1780         124 :         query.v2_query.qqic =
    1781          62 :                 igmp_msg_encode16to8(gm_ifp->cur_query_intv / 1000);
    1782          62 :         query.v2_query.n_src = htons(n_srcs);
    1783             : 
    1784          62 :         ph6.dst = dstaddr.sin6_addr;
    1785             : 
    1786             :         /* ph6 not included in sendmsg */
    1787          62 :         iov[0].iov_base = &ph6;
    1788          62 :         iov[0].iov_len = sizeof(ph6);
    1789          62 :         iov[1].iov_base = &query;
    1790          62 :         if (gm_ifp->cur_version == GM_MLDV1) {
    1791           0 :                 iov_len = 2;
    1792           0 :                 iov[1].iov_len = sizeof(query.hdr) + sizeof(struct mld_v1_pkt);
    1793          62 :         } else if (!n_srcs) {
    1794          60 :                 iov_len = 2;
    1795          60 :                 iov[1].iov_len = sizeof(query);
    1796             :         } else {
    1797           2 :                 iov[1].iov_len = sizeof(query);
    1798           2 :                 iov[2].iov_base = (void *)srcs;
    1799           2 :                 iov[2].iov_len = n_srcs * sizeof(srcs[0]);
    1800           2 :                 iov_len = 3;
    1801             :         }
    1802             : 
    1803          62 :         query.hdr.icmp6_cksum = in_cksumv(iov, iov_len);
    1804             : 
    1805          62 :         if (PIM_DEBUG_GM_PACKETS)
    1806          62 :                 zlog_debug(
    1807             :                         log_ifp("MLD query %pPA -> %pI6 (grp=%pPA, %zu srcs)"),
    1808             :                         &pim_ifp->ll_lowest, &dstaddr.sin6_addr, &grp, n_srcs);
    1809             : 
    1810          62 :         mh->msg_name = &dstaddr;
    1811          62 :         mh->msg_namelen = sizeof(dstaddr);
    1812          62 :         mh->msg_iov = iov + 1;
    1813          62 :         mh->msg_iovlen = iov_len - 1;
    1814          62 :         mh->msg_control = &cmsg;
    1815          62 :         mh->msg_controllen = sizeof(cmsg.buf);
    1816             : 
    1817          62 :         cmh = CMSG_FIRSTHDR(mh);
    1818          62 :         cmh->cmsg_level = IPPROTO_IPV6;
    1819          62 :         cmh->cmsg_type = IPV6_HOPOPTS;
    1820          62 :         cmh->cmsg_len = CMSG_LEN(8);
    1821          62 :         dp = CMSG_DATA(cmh);
    1822          62 :         *dp++ = 0;                   /* next header */
    1823          62 :         *dp++ = 0;                   /* length (8-byte blocks, minus 1) */
    1824          62 :         *dp++ = IP6OPT_ROUTER_ALERT; /* router alert */
    1825          62 :         *dp++ = 2;                   /* length */
    1826          62 :         *dp++ = 0;                   /* value (2 bytes) */
    1827          62 :         *dp++ = 0;                   /* value (2 bytes) (0 = MLD) */
    1828          62 :         *dp++ = 0;                   /* pad0 */
    1829          62 :         *dp++ = 0;                   /* pad0 */
    1830             : 
    1831          62 :         cmh = CMSG_NXTHDR(mh, cmh);
    1832          62 :         cmh->cmsg_level = IPPROTO_IPV6;
    1833          62 :         cmh->cmsg_type = IPV6_PKTINFO;
    1834          62 :         cmh->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
    1835          62 :         pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmh);
    1836          62 :         pktinfo->ipi6_ifindex = gm_ifp->ifp->ifindex;
    1837          62 :         pktinfo->ipi6_addr = gm_ifp->cur_ll_lowest;
    1838             : 
    1839          62 :         expect_ret = iov[1].iov_len;
    1840          62 :         if (iov_len == 3)
    1841           2 :                 expect_ret += iov[2].iov_len;
    1842             : 
    1843          62 :         frr_with_privs (&pimd_privs) {
    1844          62 :                 ret = sendmsg(gm_ifp->pim->gm_socket, mh, 0);
    1845             :         }
    1846             : 
    1847          62 :         if (ret != expect_ret) {
    1848           0 :                 zlog_warn(log_ifp("failed to send query: %m"));
    1849           0 :                 gm_ifp->stats.tx_query_fail++;
    1850             :         } else {
    1851          62 :                 if (gm_ifp->cur_version == GM_MLDV1) {
    1852           0 :                         if (pim_addr_is_any(grp))
    1853           0 :                                 gm_ifp->stats.tx_query_old_general++;
    1854             :                         else
    1855           0 :                                 gm_ifp->stats.tx_query_old_group++;
    1856             :                 } else {
    1857          62 :                         if (pim_addr_is_any(grp))
    1858          60 :                                 gm_ifp->stats.tx_query_new_general++;
    1859           2 :                         else if (!n_srcs)
    1860           0 :                                 gm_ifp->stats.tx_query_new_group++;
    1861             :                         else
    1862           2 :                                 gm_ifp->stats.tx_query_new_groupsrc++;
    1863             :                 }
    1864             :         }
    1865          62 : }
    1866             : 
    1867          60 : static void gm_t_query(struct thread *t)
    1868             : {
    1869          60 :         struct gm_if *gm_ifp = THREAD_ARG(t);
    1870          60 :         unsigned int timer_ms = gm_ifp->cur_query_intv;
    1871             : 
    1872          60 :         if (gm_ifp->n_startup) {
    1873          20 :                 timer_ms /= 4;
    1874          20 :                 gm_ifp->n_startup--;
    1875             :         }
    1876             : 
    1877          60 :         thread_add_timer_msec(router->master, gm_t_query, gm_ifp, timer_ms,
    1878             :                               &gm_ifp->t_query);
    1879             : 
    1880          60 :         gm_send_query(gm_ifp, PIMADDR_ANY, NULL, 0, false);
    1881          60 : }
    1882             : 
    1883           1 : static void gm_t_sg_query(struct thread *t)
    1884             : {
    1885           1 :         struct gm_sg *sg = THREAD_ARG(t);
    1886             : 
    1887           1 :         gm_trigger_specific(sg);
    1888           1 : }
    1889             : 
    1890             : /* S,G specific queries (triggered by a member leaving) get a little slack
    1891             :  * time so we can bundle queries for [S1,S2,S3,...],G into the same query
    1892             :  */
    1893           2 : static void gm_send_specific(struct gm_gsq_pending *pend_gsq)
    1894             : {
    1895           2 :         struct gm_if *gm_ifp = pend_gsq->iface;
    1896             : 
    1897           2 :         gm_send_query(gm_ifp, pend_gsq->grp, pend_gsq->srcs, pend_gsq->n_src,
    1898           2 :                       pend_gsq->s_bit);
    1899             : 
    1900           2 :         gm_gsq_pends_del(gm_ifp->gsq_pends, pend_gsq);
    1901           2 :         XFREE(MTYPE_GM_GSQ_PENDING, pend_gsq);
    1902           2 : }
    1903             : 
    1904           2 : static void gm_t_gsq_pend(struct thread *t)
    1905             : {
    1906           2 :         struct gm_gsq_pending *pend_gsq = THREAD_ARG(t);
    1907             : 
    1908           2 :         gm_send_specific(pend_gsq);
    1909           2 : }
    1910             : 
    1911           2 : static void gm_trigger_specific(struct gm_sg *sg)
    1912             : {
    1913           2 :         struct gm_if *gm_ifp = sg->iface;
    1914           2 :         struct pim_interface *pim_ifp = gm_ifp->ifp->info;
    1915           2 :         struct gm_gsq_pending *pend_gsq, ref = {};
    1916             : 
    1917           2 :         sg->n_query--;
    1918           2 :         if (sg->n_query)
    1919           1 :                 thread_add_timer_msec(router->master, gm_t_sg_query, sg,
    1920             :                                       gm_ifp->cur_query_intv_trig,
    1921             :                                       &sg->t_sg_query);
    1922             : 
    1923           2 :         if (!IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest))
    1924           0 :                 return;
    1925           2 :         if (gm_ifp->pim->gm_socket == -1)
    1926             :                 return;
    1927             : 
    1928           2 :         if (PIM_DEBUG_GM_TRACE)
    1929           2 :                 zlog_debug(log_sg(sg, "triggered query"));
    1930             : 
    1931           2 :         if (pim_addr_is_any(sg->sgaddr.src)) {
    1932           0 :                 gm_send_query(gm_ifp, sg->sgaddr.grp, NULL, 0, sg->query_sbit);
    1933           0 :                 return;
    1934             :         }
    1935             : 
    1936           2 :         ref.grp = sg->sgaddr.grp;
    1937           2 :         ref.s_bit = sg->query_sbit;
    1938             : 
    1939           2 :         pend_gsq = gm_gsq_pends_find(gm_ifp->gsq_pends, &ref);
    1940           2 :         if (!pend_gsq) {
    1941           2 :                 pend_gsq = XCALLOC(MTYPE_GM_GSQ_PENDING, sizeof(*pend_gsq));
    1942           2 :                 pend_gsq->grp = sg->sgaddr.grp;
    1943           2 :                 pend_gsq->s_bit = sg->query_sbit;
    1944           2 :                 pend_gsq->iface = gm_ifp;
    1945           2 :                 gm_gsq_pends_add(gm_ifp->gsq_pends, pend_gsq);
    1946             : 
    1947           2 :                 thread_add_timer_tv(router->master, gm_t_gsq_pend, pend_gsq,
    1948             :                                     &gm_ifp->cfg_timing_fuzz,
    1949             :                                     &pend_gsq->t_send);
    1950             :         }
    1951             : 
    1952           2 :         assert(pend_gsq->n_src < array_size(pend_gsq->srcs));
    1953             : 
    1954           2 :         pend_gsq->srcs[pend_gsq->n_src] = sg->sgaddr.src;
    1955           2 :         pend_gsq->n_src++;
    1956             : 
    1957           2 :         if (pend_gsq->n_src == array_size(pend_gsq->srcs)) {
    1958           0 :                 THREAD_OFF(pend_gsq->t_send);
    1959           0 :                 gm_send_specific(pend_gsq);
    1960           0 :                 pend_gsq = NULL;
    1961             :         }
    1962             : }
    1963             : 
    1964          23 : static void gm_vrf_socket_incref(struct pim_instance *pim)
    1965             : {
    1966          23 :         struct vrf *vrf = pim->vrf;
    1967          23 :         int ret, intval;
    1968          23 :         struct icmp6_filter filter[1];
    1969             : 
    1970          23 :         if (pim->gm_socket_if_count++ && pim->gm_socket != -1)
    1971          12 :                 return;
    1972             : 
    1973          11 :         ICMP6_FILTER_SETBLOCKALL(filter);
    1974          11 :         ICMP6_FILTER_SETPASS(ICMP6_MLD_QUERY, filter);
    1975          11 :         ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_REPORT, filter);
    1976          11 :         ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_DONE, filter);
    1977          11 :         ICMP6_FILTER_SETPASS(ICMP6_MLD_V2_REPORT, filter);
    1978             : 
    1979          11 :         frr_with_privs (&pimd_privs) {
    1980          22 :                 pim->gm_socket = vrf_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6,
    1981          11 :                                             vrf->vrf_id, vrf->name);
    1982          11 :                 if (pim->gm_socket < 0) {
    1983           0 :                         zlog_err("(VRF %s) could not create MLD socket: %m",
    1984             :                                  vrf->name);
    1985           0 :                         return;
    1986             :                 }
    1987             : 
    1988          11 :                 ret = setsockopt(pim->gm_socket, SOL_ICMPV6, ICMP6_FILTER,
    1989             :                                  filter, sizeof(filter));
    1990          11 :                 if (ret)
    1991           0 :                         zlog_err("(VRF %s) failed to set ICMP6_FILTER: %m",
    1992             :                                  vrf->name);
    1993             : 
    1994          11 :                 intval = 1;
    1995          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVPKTINFO,
    1996             :                                  &intval, sizeof(intval));
    1997          11 :                 if (ret)
    1998           0 :                         zlog_err("(VRF %s) failed to set IPV6_RECVPKTINFO: %m",
    1999             :                                  vrf->name);
    2000             : 
    2001          11 :                 intval = 1;
    2002          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPOPTS,
    2003             :                                  &intval, sizeof(intval));
    2004          11 :                 if (ret)
    2005           0 :                         zlog_err("(VRF %s) failed to set IPV6_HOPOPTS: %m",
    2006             :                                  vrf->name);
    2007             : 
    2008          11 :                 intval = 1;
    2009          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPLIMIT,
    2010             :                                  &intval, sizeof(intval));
    2011          11 :                 if (ret)
    2012           0 :                         zlog_err("(VRF %s) failed to set IPV6_HOPLIMIT: %m",
    2013             :                                  vrf->name);
    2014             : 
    2015          11 :                 intval = 1;
    2016          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_LOOP,
    2017             :                                  &intval, sizeof(intval));
    2018          11 :                 if (ret)
    2019           0 :                         zlog_err(
    2020             :                                 "(VRF %s) failed to disable IPV6_MULTICAST_LOOP: %m",
    2021             :                                 vrf->name);
    2022             : 
    2023          11 :                 intval = 1;
    2024          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_HOPS,
    2025             :                                  &intval, sizeof(intval));
    2026          11 :                 if (ret)
    2027           0 :                         zlog_err(
    2028             :                                 "(VRF %s) failed to set IPV6_MULTICAST_HOPS: %m",
    2029             :                                 vrf->name);
    2030             : 
    2031             :                 /* NB: IPV6_MULTICAST_ALL does not completely bypass multicast
    2032             :                  * RX filtering in Linux.  It only means "receive all groups
    2033             :                  * that something on the system has joined".  To actually
    2034             :                  * receive *all* MLD packets - which is what we need -
    2035             :                  * multicast routing must be enabled on the interface.  And
    2036             :                  * this only works for MLD packets specifically.
    2037             :                  *
    2038             :                  * For reference, check ip6_mc_input() in net/ipv6/ip6_input.c
    2039             :                  * and in particular the #ifdef CONFIG_IPV6_MROUTE block there.
    2040             :                  *
    2041             :                  * Also note that the code there explicitly checks for the IPv6
    2042             :                  * router alert MLD option (which is required by the RFC to be
    2043             :                  * on MLD packets.)  That implies trying to support hosts which
    2044             :                  * erroneously don't add that option is just not possible.
    2045             :                  */
    2046          11 :                 intval = 1;
    2047          11 :                 ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_ALL,
    2048             :                                  &intval, sizeof(intval));
    2049          11 :                 if (ret)
    2050          11 :                         zlog_info(
    2051             :                                 "(VRF %s) failed to set IPV6_MULTICAST_ALL: %m (OK on old kernels)",
    2052             :                                 vrf->name);
    2053             :         }
    2054             : 
    2055          11 :         thread_add_read(router->master, gm_t_recv, pim, pim->gm_socket,
    2056             :                         &pim->t_gm_recv);
    2057             : }
    2058             : 
    2059          23 : static void gm_vrf_socket_decref(struct pim_instance *pim)
    2060             : {
    2061          23 :         if (--pim->gm_socket_if_count)
    2062             :                 return;
    2063             : 
    2064          11 :         THREAD_OFF(pim->t_gm_recv);
    2065          11 :         close(pim->gm_socket);
    2066          11 :         pim->gm_socket = -1;
    2067             : }
    2068             : 
    2069          23 : static void gm_start(struct interface *ifp)
    2070             : {
    2071          23 :         struct pim_interface *pim_ifp = ifp->info;
    2072          23 :         struct gm_if *gm_ifp;
    2073             : 
    2074          23 :         assert(pim_ifp);
    2075          23 :         assert(pim_ifp->pim);
    2076          23 :         assert(pim_ifp->mroute_vif_index >= 0);
    2077          23 :         assert(!pim_ifp->mld);
    2078             : 
    2079          23 :         gm_vrf_socket_incref(pim_ifp->pim);
    2080             : 
    2081          23 :         gm_ifp = XCALLOC(MTYPE_GM_IFACE, sizeof(*gm_ifp));
    2082          23 :         gm_ifp->ifp = ifp;
    2083          23 :         pim_ifp->mld = gm_ifp;
    2084          23 :         gm_ifp->pim = pim_ifp->pim;
    2085          23 :         monotime(&gm_ifp->started);
    2086             : 
    2087          23 :         zlog_info(log_ifp("starting MLD"));
    2088             : 
    2089          23 :         if (pim_ifp->mld_version == 1)
    2090           0 :                 gm_ifp->cur_version = GM_MLDV1;
    2091             :         else
    2092          23 :                 gm_ifp->cur_version = GM_MLDV2;
    2093             : 
    2094          23 :         gm_ifp->cur_qrv = pim_ifp->gm_default_robustness_variable;
    2095          23 :         gm_ifp->cur_query_intv = pim_ifp->gm_default_query_interval * 1000;
    2096          23 :         gm_ifp->cur_query_intv_trig =
    2097          23 :                 pim_ifp->gm_specific_query_max_response_time_dsec * 100;
    2098          23 :         gm_ifp->cur_max_resp = pim_ifp->gm_query_max_response_time_dsec * 100;
    2099          23 :         gm_ifp->cur_lmqc = pim_ifp->gm_last_member_query_count;
    2100             : 
    2101          23 :         gm_ifp->cfg_timing_fuzz.tv_sec = 0;
    2102          23 :         gm_ifp->cfg_timing_fuzz.tv_usec = 10 * 1000;
    2103             : 
    2104          23 :         gm_sgs_init(gm_ifp->sgs);
    2105          23 :         gm_subscribers_init(gm_ifp->subscribers);
    2106          23 :         gm_packet_expires_init(gm_ifp->expires);
    2107          23 :         gm_grp_pends_init(gm_ifp->grp_pends);
    2108          23 :         gm_gsq_pends_init(gm_ifp->gsq_pends);
    2109             : 
    2110          23 :         frr_with_privs (&pimd_privs) {
    2111          23 :                 struct ipv6_mreq mreq;
    2112          23 :                 int ret;
    2113             : 
    2114             :                 /* all-MLDv2 group */
    2115          23 :                 mreq.ipv6mr_multiaddr = gm_all_routers;
    2116          23 :                 mreq.ipv6mr_interface = ifp->ifindex;
    2117          23 :                 ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6,
    2118             :                                  IPV6_JOIN_GROUP, &mreq, sizeof(mreq));
    2119          23 :                 if (ret)
    2120          23 :                         zlog_err("(%s) failed to join ff02::16 (all-MLDv2): %m",
    2121             :                                  ifp->name);
    2122             :         }
    2123          23 : }
    2124             : 
    2125          23 : void gm_group_delete(struct gm_if *gm_ifp)
    2126             : {
    2127          23 :         struct gm_sg *sg;
    2128          23 :         struct gm_packet_state *pkt;
    2129          23 :         struct gm_grp_pending *pend_grp;
    2130          23 :         struct gm_gsq_pending *pend_gsq;
    2131          23 :         struct gm_subscriber *subscriber;
    2132             : 
    2133          53 :         while ((pkt = gm_packet_expires_first(gm_ifp->expires)))
    2134          30 :                 gm_packet_drop(pkt, false);
    2135             : 
    2136          23 :         while ((pend_grp = gm_grp_pends_pop(gm_ifp->grp_pends))) {
    2137           0 :                 THREAD_OFF(pend_grp->t_expire);
    2138          23 :                 XFREE(MTYPE_GM_GRP_PENDING, pend_grp);
    2139             :         }
    2140             : 
    2141          23 :         while ((pend_gsq = gm_gsq_pends_pop(gm_ifp->gsq_pends))) {
    2142           0 :                 THREAD_OFF(pend_gsq->t_send);
    2143          23 :                 XFREE(MTYPE_GM_GSQ_PENDING, pend_gsq);
    2144             :         }
    2145             : 
    2146          23 :         while ((sg = gm_sgs_pop(gm_ifp->sgs))) {
    2147           0 :                 THREAD_OFF(sg->t_sg_expire);
    2148           0 :                 assertf(!gm_packet_sg_subs_count(sg->subs_negative), "%pSG",
    2149             :                         &sg->sgaddr);
    2150           0 :                 assertf(!gm_packet_sg_subs_count(sg->subs_positive), "%pSG",
    2151             :                         &sg->sgaddr);
    2152             : 
    2153           0 :                 gm_sg_free(sg);
    2154             :         }
    2155          23 :         while ((subscriber = gm_subscribers_pop(gm_ifp->subscribers))) {
    2156           0 :                 assertf(!gm_packets_count(subscriber->packets), "%pPA",
    2157             :                         &subscriber->addr);
    2158          23 :                 XFREE(MTYPE_GM_SUBSCRIBER, subscriber);
    2159             :         }
    2160          23 : }
    2161             : 
    2162          57 : void gm_ifp_teardown(struct interface *ifp)
    2163             : {
    2164          57 :         struct pim_interface *pim_ifp = ifp->info;
    2165          57 :         struct gm_if *gm_ifp;
    2166             : 
    2167          57 :         if (!pim_ifp || !pim_ifp->mld)
    2168             :                 return;
    2169             : 
    2170          23 :         gm_ifp = pim_ifp->mld;
    2171          23 :         gm_ifp->stopping = true;
    2172          23 :         if (PIM_DEBUG_GM_EVENTS)
    2173          23 :                 zlog_debug(log_ifp("MLD stop"));
    2174             : 
    2175          23 :         THREAD_OFF(gm_ifp->t_query);
    2176          23 :         THREAD_OFF(gm_ifp->t_other_querier);
    2177          23 :         THREAD_OFF(gm_ifp->t_expire);
    2178             : 
    2179          23 :         frr_with_privs (&pimd_privs) {
    2180          23 :                 struct ipv6_mreq mreq;
    2181          23 :                 int ret;
    2182             : 
    2183             :                 /* all-MLDv2 group */
    2184          23 :                 mreq.ipv6mr_multiaddr = gm_all_routers;
    2185          23 :                 mreq.ipv6mr_interface = ifp->ifindex;
    2186          23 :                 ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6,
    2187             :                                  IPV6_LEAVE_GROUP, &mreq, sizeof(mreq));
    2188          23 :                 if (ret)
    2189          23 :                         zlog_err(
    2190             :                                 "(%s) failed to leave ff02::16 (all-MLDv2): %m",
    2191             :                                 ifp->name);
    2192             :         }
    2193             : 
    2194          23 :         gm_vrf_socket_decref(gm_ifp->pim);
    2195             : 
    2196          23 :         gm_group_delete(gm_ifp);
    2197             : 
    2198          23 :         gm_grp_pends_fini(gm_ifp->grp_pends);
    2199          23 :         gm_packet_expires_fini(gm_ifp->expires);
    2200          23 :         gm_subscribers_fini(gm_ifp->subscribers);
    2201          23 :         gm_sgs_fini(gm_ifp->sgs);
    2202             : 
    2203          23 :         XFREE(MTYPE_GM_IFACE, gm_ifp);
    2204          23 :         pim_ifp->mld = NULL;
    2205             : }
    2206             : 
    2207          15 : static void gm_update_ll(struct interface *ifp)
    2208             : {
    2209          15 :         struct pim_interface *pim_ifp = ifp->info;
    2210          15 :         struct gm_if *gm_ifp = pim_ifp->mld;
    2211          15 :         bool was_querier;
    2212             : 
    2213          30 :         was_querier =
    2214          15 :                 !IPV6_ADDR_CMP(&gm_ifp->cur_ll_lowest, &gm_ifp->querier) &&
    2215          15 :                 !pim_addr_is_any(gm_ifp->querier);
    2216             : 
    2217          15 :         gm_ifp->cur_ll_lowest = pim_ifp->ll_lowest;
    2218          15 :         if (was_querier)
    2219           0 :                 gm_ifp->querier = pim_ifp->ll_lowest;
    2220          15 :         THREAD_OFF(gm_ifp->t_query);
    2221             : 
    2222          15 :         if (pim_addr_is_any(gm_ifp->cur_ll_lowest)) {
    2223           0 :                 if (was_querier)
    2224           0 :                         zlog_info(log_ifp(
    2225             :                                 "lost link-local address, stopping querier"));
    2226           0 :                 return;
    2227             :         }
    2228             : 
    2229          15 :         if (was_querier)
    2230           0 :                 zlog_info(log_ifp("new link-local %pPA while querier"),
    2231             :                           &gm_ifp->cur_ll_lowest);
    2232          15 :         else if (IPV6_ADDR_CMP(&gm_ifp->cur_ll_lowest, &gm_ifp->querier) < 0 ||
    2233          15 :                  pim_addr_is_any(gm_ifp->querier)) {
    2234          15 :                 zlog_info(log_ifp("new link-local %pPA, becoming querier"),
    2235             :                           &gm_ifp->cur_ll_lowest);
    2236          15 :                 gm_ifp->querier = gm_ifp->cur_ll_lowest;
    2237             :         } else
    2238             :                 return;
    2239             : 
    2240          15 :         gm_ifp->n_startup = gm_ifp->cur_qrv;
    2241          15 :         thread_execute(router->master, gm_t_query, gm_ifp, 0);
    2242             : }
    2243             : 
    2244         161 : void gm_ifp_update(struct interface *ifp)
    2245             : {
    2246         161 :         struct pim_interface *pim_ifp = ifp->info;
    2247         161 :         struct gm_if *gm_ifp;
    2248         161 :         bool changed = false;
    2249             : 
    2250         161 :         if (!pim_ifp)
    2251             :                 return;
    2252         161 :         if (!if_is_operative(ifp) || !pim_ifp->pim ||
    2253         132 :             pim_ifp->mroute_vif_index < 0) {
    2254          29 :                 gm_ifp_teardown(ifp);
    2255          29 :                 return;
    2256             :         }
    2257             : 
    2258             :         /*
    2259             :          * If ipv6 mld is not enabled on interface, do not start mld activites.
    2260             :          */
    2261         132 :         if (!pim_ifp->gm_enable)
    2262             :                 return;
    2263             : 
    2264          77 :         if (!pim_ifp->mld) {
    2265          23 :                 changed = true;
    2266          23 :                 gm_start(ifp);
    2267             :         }
    2268             : 
    2269          77 :         gm_ifp = pim_ifp->mld;
    2270          77 :         if (IPV6_ADDR_CMP(&pim_ifp->ll_lowest, &gm_ifp->cur_ll_lowest))
    2271          15 :                 gm_update_ll(ifp);
    2272             : 
    2273          77 :         unsigned int cfg_query_intv = pim_ifp->gm_default_query_interval * 1000;
    2274             : 
    2275          77 :         if (gm_ifp->cur_query_intv != cfg_query_intv) {
    2276           4 :                 gm_ifp->cur_query_intv = cfg_query_intv;
    2277           4 :                 changed = true;
    2278             :         }
    2279             : 
    2280          77 :         unsigned int cfg_query_intv_trig =
    2281          77 :                 pim_ifp->gm_specific_query_max_response_time_dsec * 100;
    2282             : 
    2283          77 :         if (gm_ifp->cur_query_intv_trig != cfg_query_intv_trig) {
    2284           0 :                 gm_ifp->cur_query_intv_trig = cfg_query_intv_trig;
    2285           0 :                 changed = true;
    2286             :         }
    2287             : 
    2288          77 :         unsigned int cfg_max_response =
    2289          77 :                 pim_ifp->gm_query_max_response_time_dsec * 100;
    2290             : 
    2291          77 :         if (gm_ifp->cur_max_resp != cfg_max_response)
    2292           4 :                 gm_ifp->cur_max_resp = cfg_max_response;
    2293             : 
    2294          77 :         if (gm_ifp->cur_lmqc != pim_ifp->gm_last_member_query_count)
    2295           0 :                 gm_ifp->cur_lmqc = pim_ifp->gm_last_member_query_count;
    2296             : 
    2297          77 :         enum gm_version cfg_version;
    2298             : 
    2299          77 :         if (pim_ifp->mld_version == 1)
    2300             :                 cfg_version = GM_MLDV1;
    2301             :         else
    2302          77 :                 cfg_version = GM_MLDV2;
    2303          77 :         if (gm_ifp->cur_version != cfg_version) {
    2304           0 :                 gm_ifp->cur_version = cfg_version;
    2305           0 :                 changed = true;
    2306             :         }
    2307             : 
    2308          77 :         if (changed) {
    2309          27 :                 if (PIM_DEBUG_GM_TRACE)
    2310          27 :                         zlog_debug(log_ifp(
    2311             :                                 "MLD querier config changed, querying"));
    2312          27 :                 gm_bump_querier(gm_ifp);
    2313             :         }
    2314             : }
    2315             : 
    2316             : /*
    2317             :  * CLI (show commands only)
    2318             :  */
    2319             : 
    2320             : #include "lib/command.h"
    2321             : 
    2322             : #include "pimd/pim6_mld_clippy.c"
    2323             : 
    2324           0 : static struct vrf *gm_cmd_vrf_lookup(struct vty *vty, const char *vrf_str,
    2325             :                                      int *err)
    2326             : {
    2327           0 :         struct vrf *ret;
    2328             : 
    2329           0 :         if (!vrf_str)
    2330           0 :                 return vrf_lookup_by_id(VRF_DEFAULT);
    2331           0 :         if (!strcmp(vrf_str, "all"))
    2332             :                 return NULL;
    2333           0 :         ret = vrf_lookup_by_name(vrf_str);
    2334           0 :         if (ret)
    2335             :                 return ret;
    2336             : 
    2337           0 :         vty_out(vty, "%% VRF %pSQq does not exist\n", vrf_str);
    2338           0 :         *err = CMD_WARNING;
    2339           0 :         return NULL;
    2340             : }
    2341             : 
    2342           0 : static void gm_show_if_one_detail(struct vty *vty, struct interface *ifp)
    2343             : {
    2344           0 :         struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info;
    2345           0 :         struct gm_if *gm_ifp;
    2346           0 :         bool querier;
    2347           0 :         size_t i;
    2348             : 
    2349           0 :         if (!pim_ifp) {
    2350           0 :                 vty_out(vty, "Interface %s: no PIM/MLD config\n\n", ifp->name);
    2351           0 :                 return;
    2352             :         }
    2353             : 
    2354           0 :         gm_ifp = pim_ifp->mld;
    2355           0 :         if (!gm_ifp) {
    2356           0 :                 vty_out(vty, "Interface %s: MLD not running\n\n", ifp->name);
    2357           0 :                 return;
    2358             :         }
    2359             : 
    2360           0 :         querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest);
    2361             : 
    2362           0 :         vty_out(vty, "Interface %s: MLD running\n", ifp->name);
    2363           0 :         vty_out(vty, "  Uptime:                  %pTVMs\n", &gm_ifp->started);
    2364           0 :         vty_out(vty, "  MLD version:             %d\n", gm_ifp->cur_version);
    2365           0 :         vty_out(vty, "  Querier:                 %pPA%s\n", &gm_ifp->querier,
    2366             :                 querier ? " (this system)" : "");
    2367           0 :         vty_out(vty, "  Query timer:             %pTH\n", gm_ifp->t_query);
    2368           0 :         vty_out(vty, "  Other querier timer:     %pTH\n",
    2369             :                 gm_ifp->t_other_querier);
    2370           0 :         vty_out(vty, "  Robustness value:        %u\n", gm_ifp->cur_qrv);
    2371           0 :         vty_out(vty, "  Query interval:          %ums\n",
    2372             :                 gm_ifp->cur_query_intv);
    2373           0 :         vty_out(vty, "  Query response timer:    %ums\n", gm_ifp->cur_max_resp);
    2374           0 :         vty_out(vty, "  Last member query intv.: %ums\n",
    2375             :                 gm_ifp->cur_query_intv_trig);
    2376           0 :         vty_out(vty, "  %u expiry timers from general queries:\n",
    2377           0 :                 gm_ifp->n_pending);
    2378           0 :         for (i = 0; i < gm_ifp->n_pending; i++) {
    2379           0 :                 struct gm_general_pending *p = &gm_ifp->pending[i];
    2380             : 
    2381           0 :                 vty_out(vty, "    %9pTVMs ago (query) -> %9pTVMu (expiry)\n",
    2382             :                         &p->query, &p->expiry);
    2383             :         }
    2384           0 :         vty_out(vty, "  %zu expiry timers from *,G queries\n",
    2385           0 :                 gm_grp_pends_count(gm_ifp->grp_pends));
    2386           0 :         vty_out(vty, "  %zu expiry timers from S,G queries\n",
    2387           0 :                 gm_gsq_pends_count(gm_ifp->gsq_pends));
    2388           0 :         vty_out(vty, "  %zu total *,G/S,G from %zu hosts in %zu bundles\n",
    2389           0 :                 gm_sgs_count(gm_ifp->sgs),
    2390           0 :                 gm_subscribers_count(gm_ifp->subscribers),
    2391           0 :                 gm_packet_expires_count(gm_ifp->expires));
    2392           0 :         vty_out(vty, "\n");
    2393             : }
    2394             : 
    2395           0 : static void gm_show_if_one(struct vty *vty, struct interface *ifp,
    2396             :                            json_object *js_if)
    2397             : {
    2398           0 :         struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info;
    2399           0 :         struct gm_if *gm_ifp = pim_ifp->mld;
    2400           0 :         bool querier;
    2401             : 
    2402           0 :         if (!gm_ifp) {
    2403           0 :                 if (js_if)
    2404           0 :                         json_object_string_add(js_if, "state", "down");
    2405             :                 else
    2406           0 :                         vty_out(vty, "%-16s  %5s\n", ifp->name, "down");
    2407           0 :                 return;
    2408             :         }
    2409             : 
    2410           0 :         querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest);
    2411             : 
    2412           0 :         if (js_if) {
    2413           0 :                 json_object_string_add(js_if, "name", ifp->name);
    2414           0 :                 json_object_string_add(js_if, "state", "up");
    2415           0 :                 json_object_string_addf(js_if, "version", "%d",
    2416           0 :                                         gm_ifp->cur_version);
    2417           0 :                 json_object_string_addf(js_if, "upTime", "%pTVMs",
    2418             :                                         &gm_ifp->started);
    2419           0 :                 json_object_boolean_add(js_if, "querier", querier);
    2420           0 :                 json_object_string_addf(js_if, "querierIp", "%pPA",
    2421             :                                         &gm_ifp->querier);
    2422           0 :                 if (querier)
    2423           0 :                         json_object_string_addf(js_if, "queryTimer", "%pTH",
    2424             :                                                 gm_ifp->t_query);
    2425             :                 else
    2426           0 :                         json_object_string_addf(js_if, "otherQuerierTimer",
    2427             :                                                 "%pTH",
    2428             :                                                 gm_ifp->t_other_querier);
    2429           0 :                 json_object_int_add(js_if, "timerRobustnessValue",
    2430           0 :                                     gm_ifp->cur_qrv);
    2431           0 :                 json_object_int_add(js_if, "lastMemberQueryCount",
    2432           0 :                                     gm_ifp->cur_lmqc);
    2433           0 :                 json_object_int_add(js_if, "timerQueryIntervalMsec",
    2434           0 :                                     gm_ifp->cur_query_intv);
    2435           0 :                 json_object_int_add(js_if, "timerQueryResponseTimerMsec",
    2436           0 :                                     gm_ifp->cur_max_resp);
    2437           0 :                 json_object_int_add(js_if, "timerLastMemberQueryIntervalMsec",
    2438           0 :                                     gm_ifp->cur_query_intv_trig);
    2439             :         } else {
    2440           0 :                 vty_out(vty, "%-16s  %-5s  %d  %-25pPA  %-5s %11pTH  %pTVMs\n",
    2441           0 :                         ifp->name, "up", gm_ifp->cur_version, &gm_ifp->querier,
    2442             :                         querier ? "query" : "other",
    2443             :                         querier ? gm_ifp->t_query : gm_ifp->t_other_querier,
    2444             :                         &gm_ifp->started);
    2445             :         }
    2446             : }
    2447             : 
    2448           0 : static void gm_show_if_vrf(struct vty *vty, struct vrf *vrf, const char *ifname,
    2449             :                            bool detail, json_object *js)
    2450             : {
    2451           0 :         struct interface *ifp;
    2452           0 :         json_object *js_vrf;
    2453             : 
    2454           0 :         if (js) {
    2455           0 :                 js_vrf = json_object_new_object();
    2456           0 :                 json_object_object_add(js, vrf->name, js_vrf);
    2457             :         }
    2458             : 
    2459           0 :         FOR_ALL_INTERFACES (vrf, ifp) {
    2460           0 :                 json_object *js_if = NULL;
    2461             : 
    2462           0 :                 if (ifname && strcmp(ifp->name, ifname))
    2463           0 :                         continue;
    2464           0 :                 if (detail && !js) {
    2465           0 :                         gm_show_if_one_detail(vty, ifp);
    2466           0 :                         continue;
    2467             :                 }
    2468             : 
    2469           0 :                 if (!ifp->info)
    2470           0 :                         continue;
    2471           0 :                 if (js) {
    2472           0 :                         js_if = json_object_new_object();
    2473           0 :                         json_object_object_add(js_vrf, ifp->name, js_if);
    2474             :                 }
    2475             : 
    2476           0 :                 gm_show_if_one(vty, ifp, js_if);
    2477             :         }
    2478           0 : }
    2479             : 
    2480           0 : static void gm_show_if(struct vty *vty, struct vrf *vrf, const char *ifname,
    2481             :                        bool detail, json_object *js)
    2482             : {
    2483           0 :         if (!js && !detail)
    2484           0 :                 vty_out(vty, "%-16s  %-5s  V  %-25s  %-18s  %s\n", "Interface",
    2485             :                         "State", "Querier", "Timer", "Uptime");
    2486             : 
    2487           0 :         if (vrf)
    2488           0 :                 gm_show_if_vrf(vty, vrf, ifname, detail, js);
    2489             :         else
    2490           0 :                 RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
    2491           0 :                         gm_show_if_vrf(vty, vrf, ifname, detail, js);
    2492           0 : }
    2493             : 
    2494           0 : DEFPY(gm_show_interface,
    2495             :       gm_show_interface_cmd,
    2496             :       "show ipv6 mld [vrf <VRF|all>$vrf_str] interface [IFNAME | detail$detail] [json$json]",
    2497             :       SHOW_STR
    2498             :       IPV6_STR
    2499             :       MLD_STR
    2500             :       VRF_FULL_CMD_HELP_STR
    2501             :       "MLD interface information\n"
    2502             :       "Interface name\n"
    2503             :       "Detailed output\n"
    2504             :       JSON_STR)
    2505             : {
    2506           0 :         int ret = CMD_SUCCESS;
    2507           0 :         struct vrf *vrf;
    2508           0 :         json_object *js = NULL;
    2509             : 
    2510           0 :         vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
    2511           0 :         if (ret != CMD_SUCCESS)
    2512             :                 return ret;
    2513             : 
    2514           0 :         if (json)
    2515           0 :                 js = json_object_new_object();
    2516           0 :         gm_show_if(vty, vrf, ifname, !!detail, js);
    2517           0 :         return vty_json(vty, js);
    2518             : }
    2519             : 
    2520           0 : static void gm_show_stats_one(struct vty *vty, struct gm_if *gm_ifp,
    2521             :                               json_object *js_if)
    2522             : {
    2523           0 :         struct gm_if_stats *stats = &gm_ifp->stats;
    2524             :         /* clang-format off */
    2525           0 :         struct {
    2526             :                 const char *text;
    2527             :                 const char *js_key;
    2528             :                 uint64_t *val;
    2529           0 :         } *item, items[] = {
    2530           0 :                 { "v2 reports received", "rxV2Reports", &stats->rx_new_report },
    2531           0 :                 { "v1 reports received", "rxV1Reports", &stats->rx_old_report },
    2532           0 :                 { "v1 done received",    "rxV1Done",    &stats->rx_old_leave },
    2533             : 
    2534           0 :                 { "v2 *,* queries received",   "rxV2QueryGeneral",     &stats->rx_query_new_general },
    2535           0 :                 { "v2 *,G queries received",   "rxV2QueryGroup",       &stats->rx_query_new_group },
    2536           0 :                 { "v2 S,G queries received",   "rxV2QueryGroupSource", &stats->rx_query_new_groupsrc },
    2537           0 :                 { "v2 S-bit queries received", "rxV2QuerySBit",        &stats->rx_query_new_sbit },
    2538           0 :                 { "v1 *,* queries received",   "rxV1QueryGeneral",     &stats->rx_query_old_general },
    2539           0 :                 { "v1 *,G queries received",   "rxV1QueryGroup",       &stats->rx_query_old_group },
    2540             : 
    2541           0 :                 { "v2 *,* queries sent", "txV2QueryGeneral",     &stats->tx_query_new_general },
    2542           0 :                 { "v2 *,G queries sent", "txV2QueryGroup",       &stats->tx_query_new_group },
    2543           0 :                 { "v2 S,G queries sent", "txV2QueryGroupSource", &stats->tx_query_new_groupsrc },
    2544           0 :                 { "v1 *,* queries sent", "txV1QueryGeneral",     &stats->tx_query_old_general },
    2545           0 :                 { "v1 *,G queries sent", "txV1QueryGroup",       &stats->tx_query_old_group },
    2546           0 :                 { "TX errors",           "txErrors",             &stats->tx_query_fail },
    2547             : 
    2548           0 :                 { "RX dropped (checksum error)", "rxDropChecksum",  &stats->rx_drop_csum },
    2549           0 :                 { "RX dropped (invalid source)", "rxDropSrcAddr",   &stats->rx_drop_srcaddr },
    2550           0 :                 { "RX dropped (invalid dest.)",  "rxDropDstAddr",   &stats->rx_drop_dstaddr },
    2551           0 :                 { "RX dropped (missing alert)",  "rxDropRtrAlert",  &stats->rx_drop_ra },
    2552           0 :                 { "RX dropped (malformed pkt.)", "rxDropMalformed", &stats->rx_drop_malformed },
    2553           0 :                 { "RX truncated reports",        "rxTruncatedRep",  &stats->rx_trunc_report },
    2554             :         };
    2555             :         /* clang-format on */
    2556             : 
    2557           0 :         for (item = items; item < items + array_size(items); item++) {
    2558           0 :                 if (js_if)
    2559           0 :                         json_object_int_add(js_if, item->js_key, *item->val);
    2560             :                 else
    2561           0 :                         vty_out(vty, "  %-30s  %" PRIu64 "\n", item->text,
    2562           0 :                                 *item->val);
    2563             :         }
    2564           0 : }
    2565             : 
    2566           0 : static void gm_show_stats_vrf(struct vty *vty, struct vrf *vrf,
    2567             :                               const char *ifname, json_object *js)
    2568             : {
    2569           0 :         struct interface *ifp;
    2570           0 :         json_object *js_vrf;
    2571             : 
    2572           0 :         if (js) {
    2573           0 :                 js_vrf = json_object_new_object();
    2574           0 :                 json_object_object_add(js, vrf->name, js_vrf);
    2575             :         }
    2576             : 
    2577           0 :         FOR_ALL_INTERFACES (vrf, ifp) {
    2578           0 :                 struct pim_interface *pim_ifp;
    2579           0 :                 struct gm_if *gm_ifp;
    2580           0 :                 json_object *js_if = NULL;
    2581             : 
    2582           0 :                 if (ifname && strcmp(ifp->name, ifname))
    2583           0 :                         continue;
    2584             : 
    2585           0 :                 if (!ifp->info)
    2586           0 :                         continue;
    2587           0 :                 pim_ifp = ifp->info;
    2588           0 :                 if (!pim_ifp->mld)
    2589           0 :                         continue;
    2590           0 :                 gm_ifp = pim_ifp->mld;
    2591             : 
    2592           0 :                 if (js) {
    2593           0 :                         js_if = json_object_new_object();
    2594           0 :                         json_object_object_add(js_vrf, ifp->name, js_if);
    2595             :                 } else {
    2596           0 :                         vty_out(vty, "Interface: %s\n", ifp->name);
    2597             :                 }
    2598           0 :                 gm_show_stats_one(vty, gm_ifp, js_if);
    2599           0 :                 if (!js)
    2600           0 :                         vty_out(vty, "\n");
    2601             :         }
    2602           0 : }
    2603             : 
    2604           0 : DEFPY(gm_show_interface_stats,
    2605             :       gm_show_interface_stats_cmd,
    2606             :       "show ipv6 mld [vrf <VRF|all>$vrf_str] statistics [interface IFNAME] [json$json]",
    2607             :       SHOW_STR
    2608             :       IPV6_STR
    2609             :       MLD_STR
    2610             :       VRF_FULL_CMD_HELP_STR
    2611             :       "MLD statistics\n"
    2612             :       INTERFACE_STR
    2613             :       "Interface name\n"
    2614             :       JSON_STR)
    2615             : {
    2616           0 :         int ret = CMD_SUCCESS;
    2617           0 :         struct vrf *vrf;
    2618           0 :         json_object *js = NULL;
    2619             : 
    2620           0 :         vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
    2621           0 :         if (ret != CMD_SUCCESS)
    2622             :                 return ret;
    2623             : 
    2624           0 :         if (json)
    2625           0 :                 js = json_object_new_object();
    2626             : 
    2627           0 :         if (vrf)
    2628           0 :                 gm_show_stats_vrf(vty, vrf, ifname, js);
    2629             :         else
    2630           0 :                 RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
    2631           0 :                         gm_show_stats_vrf(vty, vrf, ifname, js);
    2632           0 :         return vty_json(vty, js);
    2633             : }
    2634             : 
    2635           0 : static void gm_show_joins_one(struct vty *vty, struct gm_if *gm_ifp,
    2636             :                               const struct prefix_ipv6 *groups,
    2637             :                               const struct prefix_ipv6 *sources, bool detail,
    2638             :                               json_object *js_if)
    2639             : {
    2640           0 :         struct gm_sg *sg, *sg_start;
    2641           0 :         json_object *js_group = NULL;
    2642           0 :         pim_addr js_grpaddr = PIMADDR_ANY;
    2643           0 :         struct gm_subscriber sub_ref = {}, *sub_untracked;
    2644             : 
    2645           0 :         if (groups) {
    2646           0 :                 struct gm_sg sg_ref = {};
    2647             : 
    2648           0 :                 sg_ref.sgaddr.grp = pim_addr_from_prefix(groups);
    2649           0 :                 sg_start = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref);
    2650             :         } else
    2651           0 :                 sg_start = gm_sgs_first(gm_ifp->sgs);
    2652             : 
    2653           0 :         sub_ref.addr = gm_dummy_untracked;
    2654           0 :         sub_untracked = gm_subscribers_find(gm_ifp->subscribers, &sub_ref);
    2655             :         /* NB: sub_untracked may be NULL if no untracked joins exist */
    2656             : 
    2657           0 :         frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) {
    2658           0 :                 struct timeval *recent = NULL, *untracked = NULL;
    2659           0 :                 json_object *js_src;
    2660             : 
    2661           0 :                 if (groups) {
    2662           0 :                         struct prefix grp_p;
    2663             : 
    2664           0 :                         pim_addr_to_prefix(&grp_p, sg->sgaddr.grp);
    2665           0 :                         if (!prefix_match(groups, &grp_p))
    2666             :                                 break;
    2667             :                 }
    2668             : 
    2669           0 :                 if (sources) {
    2670           0 :                         struct prefix src_p;
    2671             : 
    2672           0 :                         pim_addr_to_prefix(&src_p, sg->sgaddr.src);
    2673           0 :                         if (!prefix_match(sources, &src_p))
    2674           0 :                                 continue;
    2675             :                 }
    2676             : 
    2677           0 :                 if (sg->most_recent) {
    2678           0 :                         struct gm_packet_state *packet;
    2679             : 
    2680           0 :                         packet = gm_packet_sg2state(sg->most_recent);
    2681           0 :                         recent = &packet->received;
    2682             :                 }
    2683             : 
    2684           0 :                 if (sub_untracked) {
    2685           0 :                         struct gm_packet_state *packet;
    2686           0 :                         struct gm_packet_sg *item;
    2687             : 
    2688           0 :                         item = gm_packet_sg_find(sg, GM_SUB_POS, sub_untracked);
    2689           0 :                         if (item) {
    2690           0 :                                 packet = gm_packet_sg2state(item);
    2691           0 :                                 untracked = &packet->received;
    2692             :                         }
    2693             :                 }
    2694             : 
    2695           0 :                 if (!js_if) {
    2696           0 :                         FMT_NSTD_BEGIN; /* %.0p */
    2697           0 :                         vty_out(vty,
    2698             :                                 "%-30pPA  %-30pPAs  %-16s  %10.0pTVMs  %10.0pTVMs  %10.0pTVMs\n",
    2699             :                                 &sg->sgaddr.grp, &sg->sgaddr.src,
    2700           0 :                                 gm_states[sg->state], recent, untracked,
    2701             :                                 &sg->created);
    2702             : 
    2703           0 :                         if (!detail)
    2704           0 :                                 continue;
    2705             : 
    2706           0 :                         struct gm_packet_sg *item;
    2707           0 :                         struct gm_packet_state *packet;
    2708             : 
    2709           0 :                         frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
    2710           0 :                                 packet = gm_packet_sg2state(item);
    2711             : 
    2712           0 :                                 if (packet->subscriber == sub_untracked)
    2713           0 :                                         continue;
    2714           0 :                                 vty_out(vty, "    %-58pPA  %-16s  %10.0pTVMs\n",
    2715             :                                         &packet->subscriber->addr, "(JOIN)",
    2716             :                                         &packet->received);
    2717             :                         }
    2718           0 :                         frr_each (gm_packet_sg_subs, sg->subs_negative, item) {
    2719           0 :                                 packet = gm_packet_sg2state(item);
    2720             : 
    2721           0 :                                 if (packet->subscriber == sub_untracked)
    2722           0 :                                         continue;
    2723           0 :                                 vty_out(vty, "    %-58pPA  %-16s  %10.0pTVMs\n",
    2724             :                                         &packet->subscriber->addr, "(PRUNE)",
    2725             :                                         &packet->received);
    2726             :                         }
    2727           0 :                         FMT_NSTD_END; /* %.0p */
    2728           0 :                         continue;
    2729             :                 }
    2730             :                 /* if (js_if) */
    2731             : 
    2732           0 :                 if (!js_group || pim_addr_cmp(js_grpaddr, sg->sgaddr.grp)) {
    2733           0 :                         js_group = json_object_new_object();
    2734           0 :                         json_object_object_addf(js_if, js_group, "%pPA",
    2735             :                                                 &sg->sgaddr.grp);
    2736           0 :                         js_grpaddr = sg->sgaddr.grp;
    2737             :                 }
    2738             : 
    2739           0 :                 js_src = json_object_new_object();
    2740           0 :                 json_object_object_addf(js_group, js_src, "%pPA",
    2741             :                                         &sg->sgaddr.src);
    2742             : 
    2743           0 :                 json_object_string_add(js_src, "state", gm_states[sg->state]);
    2744           0 :                 json_object_string_addf(js_src, "created", "%pTVMs",
    2745             :                                         &sg->created);
    2746           0 :                 json_object_string_addf(js_src, "lastSeen", "%pTVMs", recent);
    2747             : 
    2748           0 :                 if (untracked)
    2749           0 :                         json_object_string_addf(js_src, "untrackedLastSeen",
    2750             :                                                 "%pTVMs", untracked);
    2751           0 :                 if (!detail)
    2752           0 :                         continue;
    2753             : 
    2754           0 :                 json_object *js_subs;
    2755           0 :                 struct gm_packet_sg *item;
    2756           0 :                 struct gm_packet_state *packet;
    2757             : 
    2758           0 :                 js_subs = json_object_new_object();
    2759           0 :                 json_object_object_add(js_src, "joinedBy", js_subs);
    2760           0 :                 frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
    2761           0 :                         packet = gm_packet_sg2state(item);
    2762           0 :                         if (packet->subscriber == sub_untracked)
    2763           0 :                                 continue;
    2764             : 
    2765           0 :                         json_object *js_sub;
    2766             : 
    2767           0 :                         js_sub = json_object_new_object();
    2768           0 :                         json_object_object_addf(js_subs, js_sub, "%pPA",
    2769           0 :                                                 &packet->subscriber->addr);
    2770           0 :                         json_object_string_addf(js_sub, "lastSeen", "%pTVMs",
    2771             :                                                 &packet->received);
    2772             :                 }
    2773             : 
    2774           0 :                 js_subs = json_object_new_object();
    2775           0 :                 json_object_object_add(js_src, "prunedBy", js_subs);
    2776           0 :                 frr_each (gm_packet_sg_subs, sg->subs_negative, item) {
    2777           0 :                         packet = gm_packet_sg2state(item);
    2778           0 :                         if (packet->subscriber == sub_untracked)
    2779           0 :                                 continue;
    2780             : 
    2781           0 :                         json_object *js_sub;
    2782             : 
    2783           0 :                         js_sub = json_object_new_object();
    2784           0 :                         json_object_object_addf(js_subs, js_sub, "%pPA",
    2785           0 :                                                 &packet->subscriber->addr);
    2786           0 :                         json_object_string_addf(js_sub, "lastSeen", "%pTVMs",
    2787             :                                                 &packet->received);
    2788             :                 }
    2789             :         }
    2790           0 : }
    2791             : 
    2792           0 : static void gm_show_joins_vrf(struct vty *vty, struct vrf *vrf,
    2793             :                               const char *ifname,
    2794             :                               const struct prefix_ipv6 *groups,
    2795             :                               const struct prefix_ipv6 *sources, bool detail,
    2796             :                               json_object *js)
    2797             : {
    2798           0 :         struct interface *ifp;
    2799           0 :         json_object *js_vrf;
    2800             : 
    2801           0 :         if (js) {
    2802           0 :                 js_vrf = json_object_new_object();
    2803           0 :                 json_object_object_add(js, vrf->name, js_vrf);
    2804             :         }
    2805             : 
    2806           0 :         FOR_ALL_INTERFACES (vrf, ifp) {
    2807           0 :                 struct pim_interface *pim_ifp;
    2808           0 :                 struct gm_if *gm_ifp;
    2809           0 :                 json_object *js_if = NULL;
    2810             : 
    2811           0 :                 if (ifname && strcmp(ifp->name, ifname))
    2812           0 :                         continue;
    2813             : 
    2814           0 :                 if (!ifp->info)
    2815           0 :                         continue;
    2816           0 :                 pim_ifp = ifp->info;
    2817           0 :                 if (!pim_ifp->mld)
    2818           0 :                         continue;
    2819           0 :                 gm_ifp = pim_ifp->mld;
    2820             : 
    2821           0 :                 if (js) {
    2822           0 :                         js_if = json_object_new_object();
    2823           0 :                         json_object_object_add(js_vrf, ifp->name, js_if);
    2824             :                 }
    2825             : 
    2826           0 :                 if (!js && !ifname)
    2827           0 :                         vty_out(vty, "\nOn interface %s:\n", ifp->name);
    2828             : 
    2829           0 :                 gm_show_joins_one(vty, gm_ifp, groups, sources, detail, js_if);
    2830             :         }
    2831           0 : }
    2832             : 
    2833           0 : DEFPY(gm_show_interface_joins,
    2834             :       gm_show_interface_joins_cmd,
    2835             :       "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]",
    2836             :       SHOW_STR
    2837             :       IPV6_STR
    2838             :       MLD_STR
    2839             :       VRF_FULL_CMD_HELP_STR
    2840             :       "MLD joined groups & sources\n"
    2841             :       INTERFACE_STR
    2842             :       "Interface name\n"
    2843             :       "Limit output to group range\n"
    2844             :       "Show groups covered by this prefix\n"
    2845             :       "Limit output to source range\n"
    2846             :       "Show sources covered by this prefix\n"
    2847             :       "Show details, including tracked receivers\n"
    2848             :       JSON_STR)
    2849             : {
    2850           0 :         int ret = CMD_SUCCESS;
    2851           0 :         struct vrf *vrf;
    2852           0 :         json_object *js = NULL;
    2853             : 
    2854           0 :         vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
    2855           0 :         if (ret != CMD_SUCCESS)
    2856             :                 return ret;
    2857             : 
    2858           0 :         if (json)
    2859           0 :                 js = json_object_new_object();
    2860             :         else
    2861           0 :                 vty_out(vty, "%-30s  %-30s  %-16s  %10s  %10s  %10s\n", "Group",
    2862             :                         "Source", "State", "LastSeen", "NonTrkSeen", "Created");
    2863             : 
    2864           0 :         if (vrf)
    2865           0 :                 gm_show_joins_vrf(vty, vrf, ifname, groups, sources, !!detail,
    2866             :                                   js);
    2867             :         else
    2868           0 :                 RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
    2869           0 :                         gm_show_joins_vrf(vty, vrf, ifname, groups, sources,
    2870             :                                           !!detail, js);
    2871           0 :         return vty_json(vty, js);
    2872             : }
    2873             : 
    2874           0 : static void gm_show_groups(struct vty *vty, struct vrf *vrf, bool uj)
    2875             : {
    2876           0 :         struct interface *ifp;
    2877           0 :         struct ttable *tt = NULL;
    2878           0 :         char *table;
    2879           0 :         json_object *json = NULL;
    2880           0 :         json_object *json_iface = NULL;
    2881           0 :         json_object *json_group = NULL;
    2882           0 :         json_object *json_groups = NULL;
    2883           0 :         struct pim_instance *pim = vrf->info;
    2884             : 
    2885           0 :         if (uj) {
    2886           0 :                 json = json_object_new_object();
    2887           0 :                 json_object_int_add(json, "totalGroups", pim->gm_group_count);
    2888           0 :                 json_object_int_add(json, "watermarkLimit",
    2889           0 :                                     pim->gm_watermark_limit);
    2890             :         } else {
    2891             :                 /* Prepare table. */
    2892           0 :                 tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
    2893           0 :                 ttable_add_row(tt, "Interface|Group|Version|Uptime");
    2894           0 :                 tt->style.cell.rpad = 2;
    2895           0 :                 tt->style.corner = '+';
    2896           0 :                 ttable_restyle(tt);
    2897             : 
    2898           0 :                 vty_out(vty, "Total MLD groups: %u\n", pim->gm_group_count);
    2899           0 :                 vty_out(vty, "Watermark warn limit(%s): %u\n",
    2900             :                         pim->gm_watermark_limit ? "Set" : "Not Set",
    2901             :                         pim->gm_watermark_limit);
    2902             :         }
    2903             : 
    2904             :         /* scan interfaces */
    2905           0 :         FOR_ALL_INTERFACES (vrf, ifp) {
    2906             : 
    2907           0 :                 struct pim_interface *pim_ifp = ifp->info;
    2908           0 :                 struct gm_if *gm_ifp;
    2909           0 :                 struct gm_sg *sg;
    2910             : 
    2911           0 :                 if (!pim_ifp)
    2912           0 :                         continue;
    2913             : 
    2914           0 :                 gm_ifp = pim_ifp->mld;
    2915           0 :                 if (!gm_ifp)
    2916           0 :                         continue;
    2917             : 
    2918             :                 /* scan mld groups */
    2919           0 :                 frr_each (gm_sgs, gm_ifp->sgs, sg) {
    2920             : 
    2921           0 :                         if (uj) {
    2922           0 :                                 json_object_object_get_ex(json, ifp->name,
    2923             :                                                           &json_iface);
    2924             : 
    2925           0 :                                 if (!json_iface) {
    2926           0 :                                         json_iface = json_object_new_object();
    2927           0 :                                         json_object_pim_ifp_add(json_iface,
    2928             :                                                                 ifp);
    2929           0 :                                         json_object_object_add(json, ifp->name,
    2930             :                                                                json_iface);
    2931           0 :                                         json_groups = json_object_new_array();
    2932           0 :                                         json_object_object_add(json_iface,
    2933             :                                                                "groups",
    2934             :                                                                json_groups);
    2935             :                                 }
    2936             : 
    2937           0 :                                 json_group = json_object_new_object();
    2938           0 :                                 json_object_string_addf(json_group, "group",
    2939             :                                                         "%pPAs",
    2940             :                                                         &sg->sgaddr.grp);
    2941             : 
    2942           0 :                                 json_object_int_add(json_group, "version",
    2943           0 :                                                     pim_ifp->mld_version);
    2944           0 :                                 json_object_string_addf(json_group, "uptime",
    2945             :                                                         "%pTVMs", &sg->created);
    2946           0 :                                 json_object_array_add(json_groups, json_group);
    2947             :                         } else {
    2948           0 :                                 ttable_add_row(tt, "%s|%pPAs|%d|%pTVMs",
    2949           0 :                                                ifp->name, &sg->sgaddr.grp,
    2950             :                                                pim_ifp->mld_version,
    2951             :                                                &sg->created);
    2952             :                         }
    2953             :                 } /* scan gm groups */
    2954             :         }        /* scan interfaces */
    2955             : 
    2956           0 :         if (uj)
    2957           0 :                 vty_json(vty, json);
    2958             :         else {
    2959             :                 /* Dump the generated table. */
    2960           0 :                 table = ttable_dump(tt, "\n");
    2961           0 :                 vty_out(vty, "%s\n", table);
    2962           0 :                 XFREE(MTYPE_TMP, table);
    2963           0 :                 ttable_del(tt);
    2964             :         }
    2965           0 : }
    2966             : 
    2967           0 : DEFPY(gm_show_mld_groups,
    2968             :       gm_show_mld_groups_cmd,
    2969             :       "show ipv6 mld [vrf <VRF|all>$vrf_str] groups [json$json]",
    2970             :       SHOW_STR
    2971             :       IPV6_STR
    2972             :       MLD_STR
    2973             :       VRF_FULL_CMD_HELP_STR
    2974             :       MLD_GROUP_STR
    2975             :       JSON_STR)
    2976             : {
    2977           0 :         int ret = CMD_SUCCESS;
    2978           0 :         struct vrf *vrf;
    2979             : 
    2980           0 :         vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret);
    2981           0 :         if (ret != CMD_SUCCESS)
    2982             :                 return ret;
    2983             : 
    2984           0 :         if (vrf)
    2985           0 :                 gm_show_groups(vty, vrf, !!json);
    2986             :         else
    2987           0 :                 RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
    2988           0 :                         gm_show_groups(vty, vrf, !!json);
    2989             : 
    2990             :         return CMD_SUCCESS;
    2991             : }
    2992             : 
    2993           2 : DEFPY(gm_debug_show,
    2994             :       gm_debug_show_cmd,
    2995             :       "debug show mld interface IFNAME",
    2996             :       DEBUG_STR
    2997             :       SHOW_STR
    2998             :       MLD_STR
    2999             :       INTERFACE_STR
    3000             :       "interface name\n")
    3001             : {
    3002           2 :         struct interface *ifp;
    3003           2 :         struct pim_interface *pim_ifp;
    3004           2 :         struct gm_if *gm_ifp;
    3005             : 
    3006           2 :         ifp = if_lookup_by_name(ifname, VRF_DEFAULT);
    3007           2 :         if (!ifp) {
    3008           0 :                 vty_out(vty, "%% no such interface: %pSQq\n", ifname);
    3009           0 :                 return CMD_WARNING;
    3010             :         }
    3011             : 
    3012           2 :         pim_ifp = ifp->info;
    3013           2 :         if (!pim_ifp) {
    3014           0 :                 vty_out(vty, "%% no PIM state for interface %pSQq\n", ifname);
    3015           0 :                 return CMD_WARNING;
    3016             :         }
    3017             : 
    3018           2 :         gm_ifp = pim_ifp->mld;
    3019           2 :         if (!gm_ifp) {
    3020           0 :                 vty_out(vty, "%% no MLD state for interface %pSQq\n", ifname);
    3021           0 :                 return CMD_WARNING;
    3022             :         }
    3023             : 
    3024           2 :         vty_out(vty, "querier:         %pPA\n", &gm_ifp->querier);
    3025           2 :         vty_out(vty, "ll_lowest:       %pPA\n\n", &pim_ifp->ll_lowest);
    3026           2 :         vty_out(vty, "t_query:         %pTHD\n", gm_ifp->t_query);
    3027           2 :         vty_out(vty, "t_other_querier: %pTHD\n", gm_ifp->t_other_querier);
    3028           2 :         vty_out(vty, "t_expire:        %pTHD\n", gm_ifp->t_expire);
    3029             : 
    3030           2 :         vty_out(vty, "\nn_pending: %u\n", gm_ifp->n_pending);
    3031           4 :         for (size_t i = 0; i < gm_ifp->n_pending; i++) {
    3032           2 :                 int64_t query, expiry;
    3033             : 
    3034           2 :                 query = monotime_since(&gm_ifp->pending[i].query, NULL);
    3035           2 :                 expiry = monotime_until(&gm_ifp->pending[i].expiry, NULL);
    3036             : 
    3037           2 :                 vty_out(vty, "[%zu]: query %"PRId64"ms ago, expiry in %"PRId64"ms\n",
    3038             :                         i, query / 1000, expiry / 1000);
    3039             :         }
    3040             : 
    3041           2 :         struct gm_sg *sg;
    3042           2 :         struct gm_packet_state *pkt;
    3043           2 :         struct gm_packet_sg *item;
    3044           2 :         struct gm_subscriber *subscriber;
    3045             : 
    3046           2 :         vty_out(vty, "\n%zu S,G entries:\n", gm_sgs_count(gm_ifp->sgs));
    3047          30 :         frr_each (gm_sgs, gm_ifp->sgs, sg) {
    3048          13 :                 vty_out(vty, "\t%pSG    t_expire=%pTHD\n", &sg->sgaddr,
    3049             :                         sg->t_sg_expire);
    3050             : 
    3051          13 :                 vty_out(vty, "\t     @pos:%zu\n",
    3052          13 :                         gm_packet_sg_subs_count(sg->subs_positive));
    3053          52 :                 frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
    3054          13 :                         pkt = gm_packet_sg2state(item);
    3055             : 
    3056          13 :                         vty_out(vty, "\t\t+%s%s [%pPAs %p] %p+%u\n",
    3057          13 :                                 item->is_src ? "S" : "",
    3058          13 :                                 item->is_excl ? "E" : "",
    3059             :                                 &pkt->subscriber->addr, pkt->subscriber, pkt,
    3060             :                                 item->offset);
    3061             : 
    3062          13 :                         assert(item->sg == sg);
    3063             :                 }
    3064          13 :                 vty_out(vty, "\t     @neg:%zu\n",
    3065          13 :                         gm_packet_sg_subs_count(sg->subs_negative));
    3066          26 :                 frr_each (gm_packet_sg_subs, sg->subs_negative, item) {
    3067           0 :                         pkt = gm_packet_sg2state(item);
    3068             : 
    3069           0 :                         vty_out(vty, "\t\t-%s%s [%pPAs %p] %p+%u\n",
    3070           0 :                                 item->is_src ? "S" : "",
    3071           0 :                                 item->is_excl ? "E" : "",
    3072             :                                 &pkt->subscriber->addr, pkt->subscriber, pkt,
    3073             :                                 item->offset);
    3074             : 
    3075           0 :                         assert(item->sg == sg);
    3076             :                 }
    3077             :         }
    3078             : 
    3079           2 :         vty_out(vty, "\n%zu subscribers:\n",
    3080           2 :                 gm_subscribers_count(gm_ifp->subscribers));
    3081           6 :         frr_each (gm_subscribers, gm_ifp->subscribers, subscriber) {
    3082           4 :                 vty_out(vty, "\t%pPA %p %zu packets\n", &subscriber->addr,
    3083           4 :                         subscriber, gm_packets_count(subscriber->packets));
    3084             : 
    3085          16 :                 frr_each (gm_packets, subscriber->packets, pkt) {
    3086           8 :                         vty_out(vty, "\t\t%p %.3fs ago %u of %u items active\n",
    3087             :                                 pkt,
    3088           8 :                                 monotime_since(&pkt->received, NULL) *
    3089             :                                         0.000001f,
    3090           4 :                                 pkt->n_active, pkt->n_sg);
    3091             : 
    3092          17 :                         for (size_t i = 0; i < pkt->n_sg; i++) {
    3093          13 :                                 item = pkt->items + i;
    3094             : 
    3095          13 :                                 vty_out(vty, "\t\t[%zu]", i);
    3096             : 
    3097          13 :                                 if (!item->sg) {
    3098           0 :                                         vty_out(vty, " inactive\n");
    3099           0 :                                         continue;
    3100             :                                 }
    3101             : 
    3102          13 :                                 vty_out(vty, " %s%s %pSG nE=%u\n",
    3103          13 :                                         item->is_src ? "S" : "",
    3104          13 :                                         item->is_excl ? "E" : "",
    3105          13 :                                         &item->sg->sgaddr, item->n_exclude);
    3106             :                         }
    3107             :                 }
    3108             :         }
    3109             : 
    3110             :         return CMD_SUCCESS;
    3111             : }
    3112             : 
    3113           0 : DEFPY(gm_debug_iface_cfg,
    3114             :       gm_debug_iface_cfg_cmd,
    3115             :       "debug ipv6 mld {"
    3116             :         "robustness (0-7)|"
    3117             :         "query-max-response-time (1-8387584)"
    3118             :       "}",
    3119             :       DEBUG_STR
    3120             :       IPV6_STR
    3121             :       "Multicast Listener Discovery\n"
    3122             :       "QRV\nQRV\n"
    3123             :       "maxresp\nmaxresp\n")
    3124             : {
    3125           0 :         VTY_DECLVAR_CONTEXT(interface, ifp);
    3126           0 :         struct pim_interface *pim_ifp;
    3127           0 :         struct gm_if *gm_ifp;
    3128           0 :         bool changed = false;
    3129             : 
    3130           0 :         pim_ifp = ifp->info;
    3131           0 :         if (!pim_ifp) {
    3132           0 :                 vty_out(vty, "%% no PIM state for interface %pSQq\n",
    3133           0 :                         ifp->name);
    3134           0 :                 return CMD_WARNING;
    3135             :         }
    3136           0 :         gm_ifp = pim_ifp->mld;
    3137           0 :         if (!gm_ifp) {
    3138           0 :                 vty_out(vty, "%% no MLD state for interface %pSQq\n",
    3139           0 :                         ifp->name);
    3140           0 :                 return CMD_WARNING;
    3141             :         }
    3142             : 
    3143           0 :         if (robustness_str && gm_ifp->cur_qrv != robustness) {
    3144           0 :                 gm_ifp->cur_qrv = robustness;
    3145           0 :                 changed = true;
    3146             :         }
    3147           0 :         if (query_max_response_time_str &&
    3148           0 :             gm_ifp->cur_max_resp != (unsigned int)query_max_response_time) {
    3149           0 :                 gm_ifp->cur_max_resp = query_max_response_time;
    3150           0 :                 changed = true;
    3151             :         }
    3152             : 
    3153           0 :         if (changed) {
    3154           0 :                 vty_out(vty, "%% MLD querier config changed, bumping\n");
    3155           0 :                 gm_bump_querier(gm_ifp);
    3156             :         }
    3157             :         return CMD_SUCCESS;
    3158             : }
    3159             : 
    3160             : void gm_cli_init(void);
    3161             : 
    3162           8 : void gm_cli_init(void)
    3163             : {
    3164           8 :         install_element(VIEW_NODE, &gm_show_interface_cmd);
    3165           8 :         install_element(VIEW_NODE, &gm_show_interface_stats_cmd);
    3166           8 :         install_element(VIEW_NODE, &gm_show_interface_joins_cmd);
    3167           8 :         install_element(VIEW_NODE, &gm_show_mld_groups_cmd);
    3168             : 
    3169           8 :         install_element(VIEW_NODE, &gm_debug_show_cmd);
    3170           8 :         install_element(INTERFACE_NODE, &gm_debug_iface_cfg_cmd);
    3171           8 : }

Generated by: LCOV version v1.16-topotato