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 1432 : static inline uint8_t in6_multicast_scope(const pim_addr *addr)
111 : {
112 1432 : return addr->s6_addr[1] & 0xf;
113 : }
114 :
115 1432 : static inline bool in6_multicast_nofwd(const pim_addr *addr)
116 : {
117 1432 : return in6_multicast_scope(addr) <= IPV6_MULTICAST_SCOPE_LINK;
118 : }
119 :
120 : /*
121 : * (S,G) -> subscriber,(S,G)
122 : */
123 :
124 3647 : static int gm_packet_sg_cmp(const struct gm_packet_sg *a,
125 : const struct gm_packet_sg *b)
126 : {
127 3647 : const struct gm_packet_state *s_a, *s_b;
128 :
129 3647 : s_a = gm_packet_sg2state(a);
130 3647 : s_b = gm_packet_sg2state(b);
131 3647 : return IPV6_ADDR_CMP(&s_a->subscriber->addr, &s_b->subscriber->addr);
132 : }
133 :
134 8744 : DECLARE_RBTREE_UNIQ(gm_packet_sg_subs, struct gm_packet_sg, subs_itm,
135 : gm_packet_sg_cmp);
136 :
137 2461 : 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 2461 : struct {
142 : struct gm_packet_state hdr;
143 : struct gm_packet_sg item;
144 2461 : } ref = {
145 : /* clang-format off */
146 : .hdr = {
147 : .subscriber = sub,
148 : },
149 : .item = {
150 : .offset = 0,
151 : },
152 : /* clang-format on */
153 : };
154 :
155 2461 : 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 90 : 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 72 : 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 3206 : static int gm_sg_cmp(const struct gm_sg *a, const struct gm_sg *b)
199 : {
200 3206 : return pim_sgaddr_cmp(a->sgaddr, b->sgaddr);
201 : }
202 :
203 4770 : DECLARE_RBTREE_UNIQ(gm_sgs, struct gm_sg, itm, gm_sg_cmp);
204 :
205 1337 : static struct gm_sg *gm_sg_find(struct gm_if *gm_ifp, pim_addr grp,
206 : pim_addr src)
207 : {
208 1337 : struct gm_sg ref = {};
209 :
210 1337 : ref.sgaddr.grp = grp;
211 1337 : ref.sgaddr.src = src;
212 1337 : return gm_sgs_find(gm_ifp->sgs, &ref);
213 : }
214 :
215 90 : static struct gm_sg *gm_sg_make(struct gm_if *gm_ifp, pim_addr grp,
216 : pim_addr src)
217 : {
218 90 : struct gm_sg *ret, *prev;
219 :
220 90 : ret = XCALLOC(MTYPE_GM_SG, sizeof(*ret));
221 90 : ret->sgaddr.grp = grp;
222 90 : ret->sgaddr.src = src;
223 90 : ret->iface = gm_ifp;
224 90 : prev = gm_sgs_add(gm_ifp->sgs, ret);
225 :
226 90 : if (prev) {
227 0 : XFREE(MTYPE_GM_SG, ret);
228 0 : ret = prev;
229 : } else {
230 90 : monotime(&ret->created);
231 90 : gm_packet_sg_subs_init(ret->subs_positive);
232 90 : gm_packet_sg_subs_init(ret->subs_negative);
233 : }
234 90 : return ret;
235 : }
236 :
237 : /*
238 : * interface -> packets, sorted by expiry (because add_tail insert order)
239 : */
240 :
241 1289 : DECLARE_DLIST(gm_packet_expires, struct gm_packet_state, exp_itm);
242 :
243 : /*
244 : * subscriber -> packets
245 : */
246 :
247 6 : DECLARE_DLIST(gm_packets, struct gm_packet_state, pkt_itm);
248 :
249 : /*
250 : * interface -> subscriber
251 : */
252 :
253 256 : static int gm_subscriber_cmp(const struct gm_subscriber *a,
254 : const struct gm_subscriber *b)
255 : {
256 256 : return IPV6_ADDR_CMP(&a->addr, &b->addr);
257 : }
258 :
259 316 : static uint32_t gm_subscriber_hash(const struct gm_subscriber *a)
260 : {
261 316 : return jhash(&a->addr, sizeof(a->addr), 0xd0e94ad4);
262 : }
263 :
264 1197 : DECLARE_HASH(gm_subscribers, struct gm_subscriber, itm, gm_subscriber_cmp,
265 : gm_subscriber_hash);
266 :
267 286 : static struct gm_subscriber *gm_subscriber_findref(struct gm_if *gm_ifp,
268 : pim_addr addr)
269 : {
270 286 : struct gm_subscriber ref = {}, *ret;
271 :
272 286 : ref.addr = addr;
273 286 : ret = gm_subscribers_find(gm_ifp->subscribers, &ref);
274 286 : if (ret)
275 256 : ret->refcount++;
276 286 : 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 286 : static void gm_subscriber_drop(struct gm_subscriber **subp)
301 : {
302 286 : struct gm_subscriber *sub = *subp;
303 286 : struct gm_if *gm_ifp;
304 :
305 286 : if (!sub)
306 : return;
307 286 : gm_ifp = sub->iface;
308 :
309 286 : *subp = NULL;
310 286 : sub->refcount--;
311 :
312 286 : 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 109 : static void gm_expiry_calc(struct gm_query_timers *timers)
332 : {
333 109 : unsigned int expire =
334 109 : (timers->qrv - 1) * timers->qqic_ms + timers->max_resp_ms;
335 109 : ldiv_t exp_div = ldiv(expire, 1000);
336 :
337 109 : timers->expire_wait.tv_sec = exp_div.quot;
338 109 : timers->expire_wait.tv_usec = exp_div.rem * 1000;
339 109 : timeradd(&timers->expire_wait, &timers->fuzz, &timers->expire_wait);
340 109 : }
341 :
342 90 : static void gm_sg_free(struct gm_sg *sg)
343 : {
344 : /* t_sg_expiry is handled before this is reached */
345 90 : THREAD_OFF(sg->t_sg_query);
346 90 : gm_packet_sg_subs_fini(sg->subs_negative);
347 90 : gm_packet_sg_subs_fini(sg->subs_positive);
348 90 : XFREE(MTYPE_GM_SG, sg);
349 90 : }
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 1432 : static void gm_sg_update(struct gm_sg *sg, bool has_expired)
370 : {
371 1432 : struct gm_if *gm_ifp = sg->iface;
372 1432 : enum gm_sg_state prev, desired;
373 1432 : bool new_join;
374 1432 : struct gm_sg *grp = NULL;
375 :
376 1432 : if (!pim_addr_is_any(sg->sgaddr.src))
377 18 : grp = gm_sg_find(gm_ifp, sg->sgaddr.grp, PIMADDR_ANY);
378 : else
379 1414 : assert(sg->state != GM_SG_PRUNE);
380 :
381 1432 : if (gm_packet_sg_subs_count(sg->subs_positive)) {
382 1341 : desired = GM_SG_JOIN;
383 1341 : assert(!sg->t_sg_expire);
384 91 : } else if ((sg->state == GM_SG_JOIN ||
385 91 : sg->state == GM_SG_JOIN_EXPIRING) &&
386 : !has_expired)
387 : desired = GM_SG_JOIN_EXPIRING;
388 90 : 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 90 : desired = GM_SG_NOINFO;
402 :
403 1432 : if (desired != sg->state && !gm_ifp->stopping) {
404 92 : if (PIM_DEBUG_GM_EVENTS)
405 19 : zlog_debug(log_sg(sg, "%s => %s"), gm_states[sg->state],
406 : gm_states[desired]);
407 :
408 92 : if (desired == GM_SG_JOIN_EXPIRING ||
409 92 : 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 1432 : prev = sg->state;
427 1432 : sg->state = desired;
428 :
429 1432 : 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 1428 : } 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 1432 : if (desired == GM_SG_NOINFO) {
454 90 : 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 90 : if (PIM_DEBUG_GM_TRACE)
464 19 : zlog_debug(log_sg(sg, "dropping"));
465 :
466 90 : gm_sgs_del(gm_ifp->sgs, sg);
467 90 : gm_sg_free(sg);
468 : }
469 1432 : }
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 279 : static void gm_packet_free(struct gm_packet_state *pkt)
480 : {
481 279 : gm_packet_expires_del(pkt->iface->expires, pkt);
482 279 : gm_packets_del(pkt->subscriber->packets, pkt);
483 279 : gm_subscriber_drop(&pkt->subscriber);
484 279 : XFREE(MTYPE_GM_STATE, pkt);
485 279 : }
486 :
487 1291 : 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 1291 : struct gm_packet_sg *item;
492 :
493 1291 : assert(pkt->n_active < pkt->n_sg);
494 :
495 1291 : item = &pkt->items[pkt->n_active];
496 1291 : item->sg = sg;
497 1291 : item->is_excl = is_excl;
498 1291 : item->is_src = is_src;
499 1291 : item->offset = pkt->n_active;
500 :
501 1291 : pkt->n_active++;
502 1291 : return item;
503 : }
504 :
505 1291 : static bool gm_packet_sg_drop(struct gm_packet_sg *item)
506 : {
507 1291 : struct gm_packet_state *pkt;
508 1291 : size_t i;
509 :
510 1291 : assert(item->sg);
511 :
512 1291 : pkt = gm_packet_sg2state(item);
513 1291 : if (item->sg->most_recent == item)
514 623 : item->sg->most_recent = NULL;
515 :
516 1291 : 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 1291 : if (item->is_excl && item->is_src)
531 0 : gm_packet_sg_subs_del(item->sg->subs_negative, item);
532 : else
533 1291 : gm_packet_sg_subs_del(item->sg->subs_positive, item);
534 1291 : item->sg = NULL;
535 1291 : pkt->n_active--;
536 :
537 1291 : if (!pkt->n_active) {
538 279 : gm_packet_free(pkt);
539 279 : return true;
540 : }
541 : return false;
542 : }
543 :
544 31 : static void gm_packet_drop(struct gm_packet_state *pkt, bool trace)
545 : {
546 139 : for (size_t i = 0; i < pkt->n_sg; i++) {
547 139 : struct gm_sg *sg = pkt->items[i].sg;
548 139 : bool deleted;
549 :
550 139 : if (!sg)
551 8 : continue;
552 :
553 131 : if (trace && PIM_DEBUG_GM_TRACE)
554 0 : zlog_debug(log_sg(sg, "general-dropping from %pPA"),
555 : &pkt->subscriber->addr);
556 131 : deleted = gm_packet_sg_drop(&pkt->items[i]);
557 :
558 131 : gm_sg_update(sg, true);
559 131 : if (deleted)
560 : break;
561 : }
562 31 : }
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 1291 : static void gm_sg_expiry_cancel(struct gm_sg *sg)
588 : {
589 1291 : if (sg->t_sg_expire && PIM_DEBUG_GM_TRACE)
590 0 : zlog_debug(log_sg(sg, "alive, cancelling expiry timer"));
591 1291 : THREAD_OFF(sg->t_sg_expire);
592 1291 : sg->query_sbit = true;
593 1291 : }
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 1301 : 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 1301 : struct gm_subscriber *subscriber = pkt->subscriber;
611 1301 : struct gm_sg *grp;
612 1301 : struct gm_packet_sg *old_grp = NULL;
613 1301 : struct gm_packet_sg *item;
614 1301 : size_t n_src = ntohs(rechdr->n_src);
615 1301 : size_t j;
616 1301 : bool is_excl = false;
617 :
618 1301 : grp = gm_sg_find(pkt->iface, rechdr->grp, PIMADDR_ANY);
619 1301 : if (grp && subscriber)
620 1154 : old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber);
621 :
622 1301 : assert(old_grp == NULL || old_grp->is_excl);
623 :
624 1301 : switch (rechdr->type) {
625 1277 : case MLD_RECTYPE_IS_EXCLUDE:
626 : case MLD_RECTYPE_CHANGE_TO_EXCLUDE:
627 : /* this always replaces or creates state */
628 1277 : is_excl = true;
629 1277 : if (!grp)
630 87 : grp = gm_sg_make(pkt->iface, rechdr->grp, PIMADDR_ANY);
631 :
632 1277 : item = gm_packet_sg_setup(pkt, grp, is_excl, false);
633 1277 : 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 1277 : 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 1313 : 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 1277 : static void gm_handle_v2_pass2_excl(struct gm_packet_state *pkt, size_t offs)
729 : {
730 1277 : struct gm_packet_sg *item = &pkt->items[offs];
731 1277 : struct gm_packet_sg *old_grp, *item_dup;
732 1277 : struct gm_sg *sg_grp = item->sg;
733 1277 : size_t i;
734 :
735 1277 : old_grp = gm_packet_sg_find(sg_grp, GM_SUB_POS, pkt->subscriber);
736 1277 : if (old_grp) {
737 1140 : 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 1140 : 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 1140 : gm_packet_sg_drop(old_grp);
775 : }
776 :
777 1277 : item_dup = gm_packet_sg_subs_add(sg_grp->subs_positive, item);
778 1277 : assert(!item_dup);
779 1277 : pkt->n_active++;
780 :
781 1277 : sg_grp->most_recent = item;
782 1277 : gm_sg_expiry_cancel(sg_grp);
783 :
784 2554 : 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 1277 : gm_sg_update(sg_grp, false);
803 1277 : }
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 286 : 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 286 : struct mld_v2_report_hdr *hdr;
816 286 : size_t i, n_records, max_entries;
817 286 : struct gm_packet_state *pkt;
818 :
819 286 : 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 286 : gm_ifp->stats.rx_new_report++;
829 :
830 286 : hdr = (struct mld_v2_report_hdr *)data;
831 286 : data += sizeof(*hdr);
832 286 : 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 286 : max_entries = len / sizeof(pim_addr);
838 286 : pkt = XCALLOC(MTYPE_GM_STATE,
839 : offsetof(struct gm_packet_state, items[max_entries]));
840 286 : pkt->n_sg = max_entries;
841 286 : pkt->iface = gm_ifp;
842 286 : pkt->subscriber = gm_subscriber_findref(gm_ifp, pkt_src->sin6_addr);
843 :
844 286 : n_records = ntohs(hdr->n_records);
845 :
846 : /* validate & remove state in v2_pass1() */
847 1587 : for (i = 0; i < n_records; i++) {
848 1302 : struct mld_v2_rec_hdr *rechdr;
849 1302 : size_t n_src, record_size;
850 :
851 1302 : 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 1302 : rechdr = (struct mld_v2_rec_hdr *)data;
859 1302 : data += sizeof(*rechdr);
860 1302 : len -= sizeof(*rechdr);
861 :
862 1302 : n_src = ntohs(rechdr->n_src);
863 1302 : record_size = n_src * sizeof(pim_addr) + rechdr->aux_len * 4;
864 :
865 1302 : 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 1302 : 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 1301 : data += record_size;
881 1301 : len -= record_size;
882 :
883 1301 : gm_handle_v2_pass1(pkt, rechdr);
884 : }
885 :
886 286 : if (!pkt->n_active) {
887 7 : gm_subscriber_drop(&pkt->subscriber);
888 7 : XFREE(MTYPE_GM_STATE, pkt);
889 7 : return;
890 : }
891 :
892 279 : pkt = XREALLOC(MTYPE_GM_STATE, pkt,
893 : offsetof(struct gm_packet_state, items[pkt->n_active]));
894 279 : pkt->n_sg = pkt->n_active;
895 279 : pkt->n_active = 0;
896 :
897 279 : monotime(&pkt->received);
898 279 : if (!pkt->subscriber)
899 30 : pkt->subscriber = gm_subscriber_get(gm_ifp, pkt_src->sin6_addr);
900 279 : gm_packets_add_tail(pkt->subscriber->packets, pkt);
901 279 : gm_packet_expires_add_tail(gm_ifp->expires, pkt);
902 :
903 1570 : for (i = 0; i < pkt->n_sg; i++)
904 1291 : if (!pkt->items[i].is_excl)
905 14 : gm_handle_v2_pass2_incl(pkt, i);
906 : else {
907 1277 : gm_handle_v2_pass2_excl(pkt, i);
908 1277 : i += pkt->items[i].n_exclude;
909 : }
910 :
911 279 : 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 90 : static void gm_t_expire(struct thread *t)
1039 : {
1040 90 : struct gm_if *gm_ifp = THREAD_ARG(t);
1041 90 : struct gm_packet_state *pkt;
1042 :
1043 90 : zlog_info(log_ifp("general expiry timer"));
1044 :
1045 180 : while (gm_ifp->n_pending) {
1046 180 : struct gm_general_pending *pend = gm_ifp->pending;
1047 180 : struct timeval remain;
1048 180 : int64_t remain_ms;
1049 :
1050 180 : remain_ms = monotime_until(&pend->expiry, &remain);
1051 180 : if (remain_ms > 0) {
1052 90 : if (PIM_DEBUG_GM_EVENTS)
1053 0 : zlog_debug(
1054 : log_ifp("next general expiry in %" PRId64 "ms"),
1055 : remain_ms / 1000);
1056 :
1057 90 : thread_add_timer_tv(router->master, gm_t_expire, gm_ifp,
1058 : &remain, &gm_ifp->t_expire);
1059 90 : return;
1060 : }
1061 :
1062 90 : while ((pkt = gm_packet_expires_first(gm_ifp->expires))) {
1063 90 : 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 90 : gm_ifp->n_pending--;
1072 90 : memmove(gm_ifp->pending, gm_ifp->pending + 1,
1073 90 : 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 106 : static void gm_handle_q_general(struct gm_if *gm_ifp,
1084 : struct gm_query_timers *timers)
1085 : {
1086 106 : struct timeval now, expiry;
1087 106 : struct gm_general_pending *pend;
1088 :
1089 106 : monotime(&now);
1090 106 : timeradd(&now, &timers->expire_wait, &expiry);
1091 :
1092 106 : while (gm_ifp->n_pending) {
1093 91 : pend = &gm_ifp->pending[gm_ifp->n_pending - 1];
1094 :
1095 91 : 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 106 : THREAD_OFF(gm_ifp->t_expire);
1113 : }
1114 :
1115 : /* people might be messing with their configs or something */
1116 106 : if (gm_ifp->n_pending == array_size(gm_ifp->pending))
1117 0 : return;
1118 :
1119 106 : pend = &gm_ifp->pending[gm_ifp->n_pending];
1120 106 : pend->query = now;
1121 106 : pend->expiry = expiry;
1122 :
1123 106 : if (!gm_ifp->n_pending++) {
1124 15 : if (PIM_DEBUG_GM_TRACE)
1125 3 : 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 91 : } else if (PIM_DEBUG_GM_TRACE)
1131 106 : 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 0 : 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 38 : static void gm_bump_querier(struct gm_if *gm_ifp)
1347 : {
1348 38 : struct pim_interface *pim_ifp = gm_ifp->ifp->info;
1349 :
1350 38 : THREAD_OFF(gm_ifp->t_query);
1351 :
1352 38 : 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 108 : 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 108 : struct mld_v2_query_hdr *hdr;
1380 108 : struct pim_interface *pim_ifp = gm_ifp->ifp->info;
1381 108 : struct gm_query_timers timers;
1382 108 : bool general_query;
1383 :
1384 108 : if (len < sizeof(struct mld_v2_query_hdr) &&
1385 108 : 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 108 : hdr = (struct mld_v2_query_hdr *)data;
1392 108 : general_query = pim_addr_is_any(hdr->grp);
1393 :
1394 108 : 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 108 : if (len >= sizeof(struct mld_v2_query_hdr)) {
1403 108 : size_t src_space = ntohs(hdr->n_src) * sizeof(pim_addr);
1404 :
1405 108 : 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 108 : 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 108 : 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 108 : if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &gm_ifp->querier) < 0) {
1445 3 : if (PIM_DEBUG_GM_EVENTS)
1446 0 : 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 108 : 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 108 : timers.qrv = (hdr->flags & 0x7) ?: 8;
1459 108 : timers.max_resp_ms = mld_max_resp_decode(hdr->max_resp_code);
1460 108 : timers.qqic_ms = igmp_msg_decode8to16(hdr->qqic) * 1000;
1461 : }
1462 108 : timers.fuzz = gm_ifp->cfg_timing_fuzz;
1463 :
1464 108 : gm_expiry_calc(&timers);
1465 :
1466 108 : 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 108 : if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &pim_ifp->ll_lowest) < 0) {
1473 33 : unsigned int other_ms;
1474 :
1475 33 : THREAD_OFF(gm_ifp->t_query);
1476 33 : THREAD_OFF(gm_ifp->t_other_querier);
1477 :
1478 33 : other_ms = timers.qrv * timers.qqic_ms + timers.max_resp_ms / 2;
1479 33 : thread_add_timer_msec(router->master, gm_t_other_querier,
1480 : gm_ifp, other_ms,
1481 : &gm_ifp->t_other_querier);
1482 : }
1483 :
1484 108 : 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 108 : if (hdr->flags & 0x8) {
1497 0 : gm_ifp->stats.rx_query_new_sbit++;
1498 0 : return;
1499 : }
1500 :
1501 108 : if (general_query) {
1502 106 : gm_handle_q_general(gm_ifp, &timers);
1503 106 : 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 394 : 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 394 : struct icmp6_plain_hdr *icmp6 = data;
1519 394 : uint16_t pkt_csum, ref_csum;
1520 394 : struct ipv6_ph ph6 = {
1521 : .src = pkt_src->sin6_addr,
1522 : .dst = *pkt_dst,
1523 394 : .ulpl = htons(pktlen),
1524 : .next_hdr = IPPROTO_ICMPV6,
1525 : };
1526 :
1527 394 : pkt_csum = icmp6->icmp6_cksum;
1528 394 : icmp6->icmp6_cksum = 0;
1529 394 : ref_csum = in_cksum_with_ph6(&ph6, data, pktlen);
1530 :
1531 394 : 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 394 : data = (icmp6 + 1);
1541 394 : pktlen -= sizeof(*icmp6);
1542 :
1543 394 : switch (icmp6->icmp6_type) {
1544 108 : case ICMP6_MLD_QUERY:
1545 108 : gm_handle_query(gm_ifp, pkt_src, pkt_dst, data, pktlen);
1546 108 : 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 286 : case ICMP6_MLD_V2_REPORT:
1554 286 : gm_handle_v2_report(gm_ifp, pkt_src, data, pktlen);
1555 286 : break;
1556 : }
1557 : }
1558 :
1559 395 : static bool ip6_check_hopopts_ra(uint8_t *hopopts, size_t hopopt_len,
1560 : uint16_t alert_type)
1561 : {
1562 395 : uint8_t *hopopt_end;
1563 :
1564 395 : if (hopopt_len < 8)
1565 : return false;
1566 394 : if (hopopt_len < (hopopts[1] + 1U) * 8U)
1567 : return false;
1568 :
1569 394 : hopopt_end = hopopts + (hopopts[1] + 1) * 8;
1570 394 : hopopts += 2;
1571 :
1572 394 : while (hopopts < hopopt_end) {
1573 394 : if (hopopts[0] == IP6OPT_PAD1) {
1574 0 : hopopts++;
1575 0 : continue;
1576 : }
1577 :
1578 394 : if (hopopts > hopopt_end - 2)
1579 : break;
1580 394 : if (hopopts > hopopt_end - 2 - hopopts[1])
1581 : break;
1582 :
1583 394 : if (hopopts[0] == IP6OPT_ROUTER_ALERT && hopopts[1] == 2) {
1584 394 : uint16_t have_type = (hopopts[2] << 8) | hopopts[3];
1585 :
1586 394 : if (have_type == alert_type)
1587 : return true;
1588 : }
1589 :
1590 0 : hopopts += 2 + hopopts[1];
1591 : }
1592 : return false;
1593 : }
1594 :
1595 421 : static void gm_t_recv(struct thread *t)
1596 : {
1597 421 : struct pim_instance *pim = THREAD_ARG(t);
1598 421 : 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 421 : struct cmsghdr *cmsg;
1605 421 : struct in6_pktinfo *pktinfo = NULL;
1606 421 : uint8_t *hopopts = NULL;
1607 421 : size_t hopopt_len = 0;
1608 421 : int *hoplimit = NULL;
1609 421 : char rxbuf[2048];
1610 421 : struct msghdr mh[1] = {};
1611 421 : struct iovec iov[1];
1612 421 : struct sockaddr_in6 pkt_src[1] = {};
1613 421 : ssize_t nread;
1614 421 : size_t pktlen;
1615 :
1616 421 : thread_add_read(router->master, gm_t_recv, pim, pim->gm_socket,
1617 : &pim->t_gm_recv);
1618 :
1619 421 : iov->iov_base = rxbuf;
1620 421 : iov->iov_len = sizeof(rxbuf);
1621 :
1622 421 : mh->msg_name = pkt_src;
1623 421 : mh->msg_namelen = sizeof(pkt_src);
1624 421 : mh->msg_control = cmsgbuf.buf;
1625 421 : mh->msg_controllen = sizeof(cmsgbuf.buf);
1626 421 : mh->msg_iov = iov;
1627 421 : mh->msg_iovlen = array_size(iov);
1628 421 : mh->msg_flags = 0;
1629 :
1630 421 : nread = recvmsg(pim->gm_socket, mh, MSG_PEEK | MSG_TRUNC);
1631 421 : 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 421 : if ((size_t)nread > sizeof(rxbuf)) {
1638 0 : iov->iov_base = XMALLOC(MTYPE_GM_PACKET, nread);
1639 0 : iov->iov_len = nread;
1640 : }
1641 421 : nread = recvmsg(pim->gm_socket, mh, 0);
1642 421 : 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 421 : struct interface *ifp;
1649 :
1650 421 : ifp = if_lookup_by_index(pkt_src->sin6_scope_id, pim->vrf->vrf_id);
1651 421 : if (!ifp || !ifp->info)
1652 18 : goto out_free;
1653 :
1654 403 : struct pim_interface *pim_ifp = ifp->info;
1655 403 : struct gm_if *gm_ifp = pim_ifp->mld;
1656 :
1657 403 : if (!gm_ifp)
1658 8 : goto out_free;
1659 :
1660 3158 : for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) {
1661 1184 : if (cmsg->cmsg_level != SOL_IPV6)
1662 0 : continue;
1663 :
1664 1184 : switch (cmsg->cmsg_type) {
1665 395 : case IPV6_PKTINFO:
1666 395 : pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
1667 395 : break;
1668 394 : case IPV6_HOPOPTS:
1669 394 : hopopts = CMSG_DATA(cmsg);
1670 394 : hopopt_len = cmsg->cmsg_len - sizeof(*cmsg);
1671 394 : break;
1672 395 : case IPV6_HOPLIMIT:
1673 395 : hoplimit = (int *)CMSG_DATA(cmsg);
1674 395 : break;
1675 : }
1676 : }
1677 :
1678 395 : 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 395 : 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 395 : 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 394 : 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 394 : 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 394 : pktlen = nread;
1712 394 : 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 394 : gm_rx_process(gm_ifp, pkt_src, &pktinfo->ipi6_addr, iov->iov_base,
1719 : pktlen);
1720 :
1721 421 : out_free:
1722 421 : if (iov->iov_base != rxbuf)
1723 421 : XFREE(MTYPE_GM_PACKET, iov->iov_base);
1724 : }
1725 :
1726 72 : 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 72 : struct pim_interface *pim_ifp = gm_ifp->ifp->info;
1730 72 : struct sockaddr_in6 dstaddr = {
1731 : .sin6_family = AF_INET6,
1732 72 : .sin6_scope_id = gm_ifp->ifp->ifindex,
1733 : };
1734 72 : struct {
1735 : struct icmp6_plain_hdr hdr;
1736 : struct mld_v2_query_hdr v2_query;
1737 72 : } 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 72 : struct ipv6_ph ph6 = {
1749 : .src = pim_ifp->ll_lowest,
1750 72 : .ulpl = htons(sizeof(query)),
1751 : .next_hdr = IPPROTO_ICMPV6,
1752 : };
1753 72 : union {
1754 : char buf[CMSG_SPACE(8) /* hop options */ +
1755 : CMSG_SPACE(sizeof(struct in6_pktinfo))];
1756 : struct cmsghdr align;
1757 72 : } cmsg = {};
1758 72 : struct cmsghdr *cmh;
1759 72 : struct msghdr mh[1] = {};
1760 72 : struct iovec iov[3];
1761 72 : size_t iov_len;
1762 72 : ssize_t ret, expect_ret;
1763 72 : uint8_t *dp;
1764 72 : struct in6_pktinfo *pktinfo;
1765 :
1766 72 : 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 72 : } else if (pim_addr_is_any(grp))
1771 70 : dstaddr.sin6_addr = gm_all_hosts;
1772 : else
1773 2 : dstaddr.sin6_addr = grp;
1774 :
1775 72 : query.v2_query.max_resp_code =
1776 72 : mld_max_resp_encode(gm_ifp->cur_max_resp);
1777 72 : query.v2_query.flags = (gm_ifp->cur_qrv < 8) ? gm_ifp->cur_qrv : 0;
1778 72 : if (s_bit)
1779 0 : query.v2_query.flags |= 0x08;
1780 144 : query.v2_query.qqic =
1781 72 : igmp_msg_encode16to8(gm_ifp->cur_query_intv / 1000);
1782 72 : query.v2_query.n_src = htons(n_srcs);
1783 :
1784 72 : ph6.dst = dstaddr.sin6_addr;
1785 :
1786 : /* ph6 not included in sendmsg */
1787 72 : iov[0].iov_base = &ph6;
1788 72 : iov[0].iov_len = sizeof(ph6);
1789 72 : iov[1].iov_base = &query;
1790 72 : 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 72 : } else if (!n_srcs) {
1794 70 : iov_len = 2;
1795 70 : 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 72 : query.hdr.icmp6_cksum = in_cksumv(iov, iov_len);
1804 :
1805 72 : if (PIM_DEBUG_GM_PACKETS)
1806 3 : 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 72 : mh->msg_name = &dstaddr;
1811 72 : mh->msg_namelen = sizeof(dstaddr);
1812 72 : mh->msg_iov = iov + 1;
1813 72 : mh->msg_iovlen = iov_len - 1;
1814 72 : mh->msg_control = &cmsg;
1815 72 : mh->msg_controllen = sizeof(cmsg.buf);
1816 :
1817 72 : cmh = CMSG_FIRSTHDR(mh);
1818 72 : cmh->cmsg_level = IPPROTO_IPV6;
1819 72 : cmh->cmsg_type = IPV6_HOPOPTS;
1820 72 : cmh->cmsg_len = CMSG_LEN(8);
1821 72 : dp = CMSG_DATA(cmh);
1822 72 : *dp++ = 0; /* next header */
1823 72 : *dp++ = 0; /* length (8-byte blocks, minus 1) */
1824 72 : *dp++ = IP6OPT_ROUTER_ALERT; /* router alert */
1825 72 : *dp++ = 2; /* length */
1826 72 : *dp++ = 0; /* value (2 bytes) */
1827 72 : *dp++ = 0; /* value (2 bytes) (0 = MLD) */
1828 72 : *dp++ = 0; /* pad0 */
1829 72 : *dp++ = 0; /* pad0 */
1830 :
1831 72 : cmh = CMSG_NXTHDR(mh, cmh);
1832 72 : cmh->cmsg_level = IPPROTO_IPV6;
1833 72 : cmh->cmsg_type = IPV6_PKTINFO;
1834 72 : cmh->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
1835 72 : pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmh);
1836 72 : pktinfo->ipi6_ifindex = gm_ifp->ifp->ifindex;
1837 72 : pktinfo->ipi6_addr = gm_ifp->cur_ll_lowest;
1838 :
1839 72 : expect_ret = iov[1].iov_len;
1840 72 : if (iov_len == 3)
1841 2 : expect_ret += iov[2].iov_len;
1842 :
1843 72 : frr_with_privs (&pimd_privs) {
1844 72 : ret = sendmsg(gm_ifp->pim->gm_socket, mh, 0);
1845 : }
1846 :
1847 72 : 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 72 : 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 72 : if (pim_addr_is_any(grp))
1858 70 : 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 72 : }
1866 :
1867 70 : static void gm_t_query(struct thread *t)
1868 : {
1869 70 : struct gm_if *gm_ifp = THREAD_ARG(t);
1870 70 : unsigned int timer_ms = gm_ifp->cur_query_intv;
1871 :
1872 70 : if (gm_ifp->n_startup) {
1873 20 : timer_ms /= 4;
1874 20 : gm_ifp->n_startup--;
1875 : }
1876 :
1877 70 : thread_add_timer_msec(router->master, gm_t_query, gm_ifp, timer_ms,
1878 : &gm_ifp->t_query);
1879 :
1880 70 : gm_send_query(gm_ifp, PIMADDR_ANY, NULL, 0, false);
1881 70 : }
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 0 : 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 30 : static void gm_vrf_socket_incref(struct pim_instance *pim)
1965 : {
1966 30 : struct vrf *vrf = pim->vrf;
1967 30 : int ret, intval;
1968 30 : struct icmp6_filter filter[1];
1969 :
1970 30 : if (pim->gm_socket_if_count++ && pim->gm_socket != -1)
1971 16 : return;
1972 :
1973 14 : ICMP6_FILTER_SETBLOCKALL(filter);
1974 14 : ICMP6_FILTER_SETPASS(ICMP6_MLD_QUERY, filter);
1975 14 : ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_REPORT, filter);
1976 14 : ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_DONE, filter);
1977 14 : ICMP6_FILTER_SETPASS(ICMP6_MLD_V2_REPORT, filter);
1978 :
1979 14 : frr_with_privs (&pimd_privs) {
1980 28 : pim->gm_socket = vrf_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6,
1981 14 : vrf->vrf_id, vrf->name);
1982 14 : 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 14 : ret = setsockopt(pim->gm_socket, SOL_ICMPV6, ICMP6_FILTER,
1989 : filter, sizeof(filter));
1990 14 : if (ret)
1991 0 : zlog_err("(VRF %s) failed to set ICMP6_FILTER: %m",
1992 : vrf->name);
1993 :
1994 14 : intval = 1;
1995 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVPKTINFO,
1996 : &intval, sizeof(intval));
1997 14 : if (ret)
1998 0 : zlog_err("(VRF %s) failed to set IPV6_RECVPKTINFO: %m",
1999 : vrf->name);
2000 :
2001 14 : intval = 1;
2002 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPOPTS,
2003 : &intval, sizeof(intval));
2004 14 : if (ret)
2005 0 : zlog_err("(VRF %s) failed to set IPV6_HOPOPTS: %m",
2006 : vrf->name);
2007 :
2008 14 : intval = 1;
2009 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPLIMIT,
2010 : &intval, sizeof(intval));
2011 14 : if (ret)
2012 0 : zlog_err("(VRF %s) failed to set IPV6_HOPLIMIT: %m",
2013 : vrf->name);
2014 :
2015 14 : intval = 1;
2016 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_LOOP,
2017 : &intval, sizeof(intval));
2018 14 : if (ret)
2019 0 : zlog_err(
2020 : "(VRF %s) failed to disable IPV6_MULTICAST_LOOP: %m",
2021 : vrf->name);
2022 :
2023 14 : intval = 1;
2024 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_HOPS,
2025 : &intval, sizeof(intval));
2026 14 : 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 14 : intval = 1;
2047 14 : ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_ALL,
2048 : &intval, sizeof(intval));
2049 14 : if (ret)
2050 14 : zlog_info(
2051 : "(VRF %s) failed to set IPV6_MULTICAST_ALL: %m (OK on old kernels)",
2052 : vrf->name);
2053 : }
2054 :
2055 14 : thread_add_read(router->master, gm_t_recv, pim, pim->gm_socket,
2056 : &pim->t_gm_recv);
2057 : }
2058 :
2059 30 : static void gm_vrf_socket_decref(struct pim_instance *pim)
2060 : {
2061 30 : if (--pim->gm_socket_if_count)
2062 : return;
2063 :
2064 14 : THREAD_OFF(pim->t_gm_recv);
2065 14 : close(pim->gm_socket);
2066 14 : pim->gm_socket = -1;
2067 : }
2068 :
2069 30 : static void gm_start(struct interface *ifp)
2070 : {
2071 30 : struct pim_interface *pim_ifp = ifp->info;
2072 30 : struct gm_if *gm_ifp;
2073 :
2074 30 : assert(pim_ifp);
2075 30 : assert(pim_ifp->pim);
2076 30 : assert(pim_ifp->mroute_vif_index >= 0);
2077 30 : assert(!pim_ifp->mld);
2078 :
2079 30 : gm_vrf_socket_incref(pim_ifp->pim);
2080 :
2081 30 : gm_ifp = XCALLOC(MTYPE_GM_IFACE, sizeof(*gm_ifp));
2082 30 : gm_ifp->ifp = ifp;
2083 30 : pim_ifp->mld = gm_ifp;
2084 30 : gm_ifp->pim = pim_ifp->pim;
2085 30 : monotime(&gm_ifp->started);
2086 :
2087 30 : zlog_info(log_ifp("starting MLD"));
2088 :
2089 30 : if (pim_ifp->mld_version == 1)
2090 0 : gm_ifp->cur_version = GM_MLDV1;
2091 : else
2092 30 : gm_ifp->cur_version = GM_MLDV2;
2093 :
2094 30 : gm_ifp->cur_qrv = pim_ifp->gm_default_robustness_variable;
2095 30 : gm_ifp->cur_query_intv = pim_ifp->gm_default_query_interval * 1000;
2096 30 : gm_ifp->cur_query_intv_trig =
2097 30 : pim_ifp->gm_specific_query_max_response_time_dsec * 100;
2098 30 : gm_ifp->cur_max_resp = pim_ifp->gm_query_max_response_time_dsec * 100;
2099 30 : gm_ifp->cur_lmqc = pim_ifp->gm_last_member_query_count;
2100 :
2101 30 : gm_ifp->cfg_timing_fuzz.tv_sec = 0;
2102 30 : gm_ifp->cfg_timing_fuzz.tv_usec = 10 * 1000;
2103 :
2104 30 : gm_sgs_init(gm_ifp->sgs);
2105 30 : gm_subscribers_init(gm_ifp->subscribers);
2106 30 : gm_packet_expires_init(gm_ifp->expires);
2107 30 : gm_grp_pends_init(gm_ifp->grp_pends);
2108 30 : gm_gsq_pends_init(gm_ifp->gsq_pends);
2109 :
2110 30 : frr_with_privs (&pimd_privs) {
2111 30 : struct ipv6_mreq mreq;
2112 30 : int ret;
2113 :
2114 : /* all-MLDv2 group */
2115 30 : mreq.ipv6mr_multiaddr = gm_all_routers;
2116 30 : mreq.ipv6mr_interface = ifp->ifindex;
2117 30 : ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6,
2118 : IPV6_JOIN_GROUP, &mreq, sizeof(mreq));
2119 30 : if (ret)
2120 30 : zlog_err("(%s) failed to join ff02::16 (all-MLDv2): %m",
2121 : ifp->name);
2122 : }
2123 30 : }
2124 :
2125 30 : void gm_group_delete(struct gm_if *gm_ifp)
2126 : {
2127 30 : struct gm_sg *sg;
2128 30 : struct gm_packet_state *pkt;
2129 30 : struct gm_grp_pending *pend_grp;
2130 30 : struct gm_gsq_pending *pend_gsq;
2131 30 : struct gm_subscriber *subscriber;
2132 :
2133 61 : while ((pkt = gm_packet_expires_first(gm_ifp->expires)))
2134 31 : gm_packet_drop(pkt, false);
2135 :
2136 30 : while ((pend_grp = gm_grp_pends_pop(gm_ifp->grp_pends))) {
2137 0 : THREAD_OFF(pend_grp->t_expire);
2138 30 : XFREE(MTYPE_GM_GRP_PENDING, pend_grp);
2139 : }
2140 :
2141 30 : while ((pend_gsq = gm_gsq_pends_pop(gm_ifp->gsq_pends))) {
2142 0 : THREAD_OFF(pend_gsq->t_send);
2143 30 : XFREE(MTYPE_GM_GSQ_PENDING, pend_gsq);
2144 : }
2145 :
2146 30 : 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 30 : while ((subscriber = gm_subscribers_pop(gm_ifp->subscribers))) {
2156 0 : assertf(!gm_packets_count(subscriber->packets), "%pPA",
2157 : &subscriber->addr);
2158 30 : XFREE(MTYPE_GM_SUBSCRIBER, subscriber);
2159 : }
2160 30 : }
2161 :
2162 35 : void gm_ifp_teardown(struct interface *ifp)
2163 : {
2164 35 : struct pim_interface *pim_ifp = ifp->info;
2165 35 : struct gm_if *gm_ifp;
2166 :
2167 35 : if (!pim_ifp || !pim_ifp->mld)
2168 : return;
2169 :
2170 30 : gm_ifp = pim_ifp->mld;
2171 30 : gm_ifp->stopping = true;
2172 30 : if (PIM_DEBUG_GM_EVENTS)
2173 6 : zlog_debug(log_ifp("MLD stop"));
2174 :
2175 30 : THREAD_OFF(gm_ifp->t_query);
2176 30 : THREAD_OFF(gm_ifp->t_other_querier);
2177 30 : THREAD_OFF(gm_ifp->t_expire);
2178 :
2179 30 : frr_with_privs (&pimd_privs) {
2180 30 : struct ipv6_mreq mreq;
2181 30 : int ret;
2182 :
2183 : /* all-MLDv2 group */
2184 30 : mreq.ipv6mr_multiaddr = gm_all_routers;
2185 30 : mreq.ipv6mr_interface = ifp->ifindex;
2186 30 : ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6,
2187 : IPV6_LEAVE_GROUP, &mreq, sizeof(mreq));
2188 30 : if (ret)
2189 30 : zlog_err(
2190 : "(%s) failed to leave ff02::16 (all-MLDv2): %m",
2191 : ifp->name);
2192 : }
2193 :
2194 30 : gm_vrf_socket_decref(gm_ifp->pim);
2195 :
2196 30 : gm_group_delete(gm_ifp);
2197 :
2198 30 : gm_grp_pends_fini(gm_ifp->grp_pends);
2199 30 : gm_packet_expires_fini(gm_ifp->expires);
2200 30 : gm_subscribers_fini(gm_ifp->subscribers);
2201 30 : gm_sgs_fini(gm_ifp->sgs);
2202 :
2203 30 : XFREE(MTYPE_GM_IFACE, gm_ifp);
2204 30 : 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 178 : void gm_ifp_update(struct interface *ifp)
2245 : {
2246 178 : struct pim_interface *pim_ifp = ifp->info;
2247 178 : struct gm_if *gm_ifp;
2248 178 : bool changed = false;
2249 :
2250 178 : if (!pim_ifp)
2251 : return;
2252 178 : if (!if_is_operative(ifp) || !pim_ifp->pim ||
2253 178 : pim_ifp->mroute_vif_index < 0) {
2254 0 : gm_ifp_teardown(ifp);
2255 0 : return;
2256 : }
2257 :
2258 : /*
2259 : * If ipv6 mld is not enabled on interface, do not start mld activites.
2260 : */
2261 178 : if (!pim_ifp->gm_enable)
2262 : return;
2263 :
2264 99 : if (!pim_ifp->mld) {
2265 30 : changed = true;
2266 30 : gm_start(ifp);
2267 : }
2268 :
2269 99 : gm_ifp = pim_ifp->mld;
2270 99 : if (IPV6_ADDR_CMP(&pim_ifp->ll_lowest, &gm_ifp->cur_ll_lowest))
2271 15 : gm_update_ll(ifp);
2272 :
2273 99 : unsigned int cfg_query_intv = pim_ifp->gm_default_query_interval * 1000;
2274 :
2275 99 : if (gm_ifp->cur_query_intv != cfg_query_intv) {
2276 8 : gm_ifp->cur_query_intv = cfg_query_intv;
2277 8 : changed = true;
2278 : }
2279 :
2280 99 : unsigned int cfg_query_intv_trig =
2281 99 : pim_ifp->gm_specific_query_max_response_time_dsec * 100;
2282 :
2283 99 : 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 99 : unsigned int cfg_max_response =
2289 99 : pim_ifp->gm_query_max_response_time_dsec * 100;
2290 :
2291 99 : if (gm_ifp->cur_max_resp != cfg_max_response)
2292 8 : gm_ifp->cur_max_resp = cfg_max_response;
2293 :
2294 99 : 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 99 : enum gm_version cfg_version;
2298 :
2299 99 : if (pim_ifp->mld_version == 1)
2300 : cfg_version = GM_MLDV1;
2301 : else
2302 99 : cfg_version = GM_MLDV2;
2303 99 : if (gm_ifp->cur_version != cfg_version) {
2304 0 : gm_ifp->cur_version = cfg_version;
2305 0 : changed = true;
2306 : }
2307 :
2308 99 : if (changed) {
2309 38 : if (PIM_DEBUG_GM_TRACE)
2310 6 : zlog_debug(log_ifp(
2311 : "MLD querier config changed, querying"));
2312 38 : 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 34 : frr_each (gm_sgs, gm_ifp->sgs, sg) {
3048 15 : vty_out(vty, "\t%pSG t_expire=%pTHD\n", &sg->sgaddr,
3049 : sg->t_sg_expire);
3050 :
3051 15 : vty_out(vty, "\t @pos:%zu\n",
3052 15 : gm_packet_sg_subs_count(sg->subs_positive));
3053 68 : frr_each (gm_packet_sg_subs, sg->subs_positive, item) {
3054 19 : pkt = gm_packet_sg2state(item);
3055 :
3056 19 : vty_out(vty, "\t\t+%s%s [%pPAs %p] %p+%u\n",
3057 19 : item->is_src ? "S" : "",
3058 19 : item->is_excl ? "E" : "",
3059 : &pkt->subscriber->addr, pkt->subscriber, pkt,
3060 : item->offset);
3061 :
3062 19 : assert(item->sg == sg);
3063 : }
3064 15 : vty_out(vty, "\t @neg:%zu\n",
3065 15 : gm_packet_sg_subs_count(sg->subs_negative));
3066 30 : 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 20 : frr_each (gm_packets, subscriber->packets, pkt) {
3086 12 : vty_out(vty, "\t\t%p %.3fs ago %u of %u items active\n",
3087 : pkt,
3088 12 : monotime_since(&pkt->received, NULL) *
3089 : 0.000001f,
3090 6 : pkt->n_active, pkt->n_sg);
3091 :
3092 25 : for (size_t i = 0; i < pkt->n_sg; i++) {
3093 19 : item = pkt->items + i;
3094 :
3095 19 : vty_out(vty, "\t\t[%zu]", i);
3096 :
3097 19 : if (!item->sg) {
3098 0 : vty_out(vty, " inactive\n");
3099 0 : continue;
3100 : }
3101 :
3102 19 : vty_out(vty, " %s%s %pSG nE=%u\n",
3103 19 : item->is_src ? "S" : "",
3104 19 : item->is_excl ? "E" : "",
3105 19 : &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 : }
|