]> git.proxmox.com Git - mirror_frr.git/blob - pimd/pim_join.c
*: auto-convert to SPDX License IDs
[mirror_frr.git] / pimd / pim_join.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3
4 /*
5 * PIM for Quagga
6 * Copyright (C) 2008 Everton da Silva Marques
7 */
8
9 #include <zebra.h>
10
11 #include "log.h"
12 #include "prefix.h"
13 #include "if.h"
14 #include "vty.h"
15 #include "plist.h"
16
17 #include "pimd.h"
18 #include "pim_instance.h"
19 #include "pim_str.h"
20 #include "pim_tlv.h"
21 #include "pim_msg.h"
22 #include "pim_pim.h"
23 #include "pim_join.h"
24 #include "pim_oil.h"
25 #include "pim_iface.h"
26 #include "pim_hello.h"
27 #include "pim_ifchannel.h"
28 #include "pim_rpf.h"
29 #include "pim_rp.h"
30 #include "pim_jp_agg.h"
31 #include "pim_util.h"
32 #include "pim_ssm.h"
33
34 static void on_trace(const char *label, struct interface *ifp, pim_addr src)
35 {
36 if (PIM_DEBUG_PIM_TRACE)
37 zlog_debug("%s: from %pPA on %s", label, &src, ifp->name);
38 }
39
40 static void recv_join(struct interface *ifp, struct pim_neighbor *neigh,
41 uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg,
42 uint8_t source_flags)
43 {
44 struct pim_interface *pim_ifp = NULL;
45
46 if (PIM_DEBUG_PIM_TRACE)
47 zlog_debug(
48 "%s: join (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s",
49 __func__, sg, !!(source_flags & PIM_RPT_BIT_MASK),
50 !!(source_flags & PIM_WILDCARD_BIT_MASK), &upstream,
51 holdtime, &neigh->source_addr, ifp->name);
52
53 pim_ifp = ifp->info;
54 assert(pim_ifp);
55
56 ++pim_ifp->pim_ifstat_join_recv;
57
58 /*
59 * If the RPT and WC are set it's a (*,G)
60 * and the source is the RP
61 */
62 if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) {
63 /* As per RFC 7761 Section 4.9.1:
64 * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for
65 * use with PIM Join/Prune messages (see Section 4.9.5.1). If
66 * the WC bit is 1, the RPT bit MUST be 1.
67 */
68 if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) {
69 if (PIM_DEBUG_PIM_J_P)
70 zlog_debug(
71 "Discarding (*,G)=%pSG join since WC bit is set but RPT bit is unset",
72 sg);
73
74 return;
75 }
76
77 struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp);
78 pim_addr rpf_addr;
79
80 if (!rp) {
81 zlog_warn("%s: Lookup of RP failed for %pSG", __func__,
82 sg);
83 return;
84 }
85 /*
86 * If the RP sent in the message is not
87 * our RP for the group, drop the message
88 */
89 rpf_addr = rp->rpf_addr;
90 if (pim_addr_cmp(sg->src, rpf_addr)) {
91 zlog_warn(
92 "%s: Specified RP(%pPAs) in join is different than our configured RP(%pPAs)",
93 __func__, &sg->src, &rpf_addr);
94 return;
95 }
96
97 if (pim_is_grp_ssm(pim_ifp->pim, sg->grp)) {
98 zlog_warn(
99 "%s: Specified Group(%pPA) in join is now in SSM, not allowed to create PIM state",
100 __func__, &sg->grp);
101 return;
102 }
103
104 sg->src = PIMADDR_ANY;
105 }
106
107 /* Restart join expiry timer */
108 pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, sg,
109 source_flags, holdtime);
110 }
111
112 static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh,
113 uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg,
114 uint8_t source_flags)
115 {
116 struct pim_interface *pim_ifp = NULL;
117
118 if (PIM_DEBUG_PIM_TRACE)
119 zlog_debug(
120 "%s: prune (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s",
121 __func__, sg, source_flags & PIM_RPT_BIT_MASK,
122 source_flags & PIM_WILDCARD_BIT_MASK, &upstream,
123 holdtime, &neigh->source_addr, ifp->name);
124
125 pim_ifp = ifp->info;
126 assert(pim_ifp);
127
128 ++pim_ifp->pim_ifstat_prune_recv;
129
130 if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) {
131 /* As per RFC 7761 Section 4.9.1:
132 * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for
133 * use with PIM Join/Prune messages (see Section 4.9.5.1). If
134 * the WC bit is 1, the RPT bit MUST be 1.
135 */
136 if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) {
137 if (PIM_DEBUG_PIM_J_P)
138 zlog_debug(
139 "Discarding (*,G)=%pSG prune since WC bit is set but RPT bit is unset",
140 sg);
141
142 return;
143 }
144
145 /*
146 * RFC 4601 Section 4.5.2:
147 * Received Prune(*,G) messages are processed even if the
148 * RP in the message does not match RP(G).
149 */
150 if (PIM_DEBUG_PIM_TRACE)
151 zlog_debug("%s: Prune received with RP(%pPAs) for %pSG",
152 __func__, &sg->src, sg);
153
154 sg->src = PIMADDR_ANY;
155 }
156
157 pim_ifchannel_prune(ifp, upstream, sg, source_flags, holdtime);
158 }
159
160 int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh,
161 pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size)
162 {
163 pim_addr msg_upstream_addr;
164 bool wrong_af = false;
165 struct pim_interface *pim_ifp;
166 uint8_t msg_num_groups;
167 uint16_t msg_holdtime;
168 int addr_offset;
169 uint8_t *buf;
170 uint8_t *pastend;
171 int remain;
172 int group;
173 struct pim_ifchannel *child = NULL;
174 struct listnode *ch_node, *nch_node;
175
176 buf = tlv_buf;
177 pastend = tlv_buf + tlv_buf_size;
178 pim_ifp = ifp->info;
179
180 if (pim_ifp->pim_passive_enable) {
181 if (PIM_DEBUG_PIM_PACKETS)
182 zlog_debug(
183 "skip receiving PIM message on passive interface %s",
184 ifp->name);
185 return 0;
186 }
187
188 /*
189 Parse ucast addr
190 */
191 addr_offset = pim_parse_addr_ucast(&msg_upstream_addr, buf,
192 pastend - buf, &wrong_af);
193 if (addr_offset < 1) {
194 zlog_warn("%s: pim_parse_addr_ucast() failure: from %pPA on %s",
195 __func__, &src_addr, ifp->name);
196 return -1;
197 }
198 buf += addr_offset;
199
200 /*
201 Check upstream address family
202 */
203 if (wrong_af) {
204 zlog_warn(
205 "%s: ignoring join/prune directed to unexpected addr family from %pPA on %s",
206 __func__, &src_addr, ifp->name);
207 return -2;
208 }
209
210 remain = pastend - buf;
211 if (remain < 4) {
212 zlog_warn(
213 "%s: short join/prune message buffer for group list: size=%d minimum=%d from %pPA on %s",
214 __func__, remain, 4, &src_addr, ifp->name);
215 return -4;
216 }
217
218 ++buf; /* skip reserved byte */
219 msg_num_groups = *(const uint8_t *)buf;
220 ++buf;
221 msg_holdtime = ntohs(*(const uint16_t *)buf);
222 ++buf;
223 ++buf;
224
225 if (PIM_DEBUG_PIM_J_P)
226 zlog_debug(
227 "%s: join/prune upstream=%pPAs groups=%d holdtime=%d from %pPA on %s",
228 __func__, &msg_upstream_addr, msg_num_groups,
229 msg_holdtime, &src_addr, ifp->name);
230
231 /* Scan groups */
232 for (group = 0; group < msg_num_groups; ++group) {
233 pim_sgaddr sg;
234 uint8_t msg_source_flags;
235 uint16_t msg_num_joined_sources;
236 uint16_t msg_num_pruned_sources;
237 int source;
238 struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL;
239 bool filtered = false;
240
241 memset(&sg, 0, sizeof(sg));
242 addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf);
243 if (addr_offset < 1) {
244 return -5;
245 }
246 buf += addr_offset;
247
248 remain = pastend - buf;
249 if (remain < 4) {
250 zlog_warn(
251 "%s: short join/prune buffer for source list: size=%d minimum=%d from %pPA on %s",
252 __func__, remain, 4, &src_addr, ifp->name);
253 return -6;
254 }
255
256 msg_num_joined_sources = ntohs(*(const uint16_t *)buf);
257 buf += 2;
258 msg_num_pruned_sources = ntohs(*(const uint16_t *)buf);
259 buf += 2;
260
261 if (PIM_DEBUG_PIM_J_P)
262 zlog_debug(
263 "%s: join/prune upstream=%pPAs group=%pPA/32 join_src=%d prune_src=%d from %pPA on %s",
264 __func__, &msg_upstream_addr, &sg.grp,
265 msg_num_joined_sources, msg_num_pruned_sources,
266 &src_addr, ifp->name);
267
268 /* boundary check */
269 filtered = pim_is_group_filtered(pim_ifp, &sg.grp);
270
271 /* Scan joined sources */
272 for (source = 0; source < msg_num_joined_sources; ++source) {
273 addr_offset = pim_parse_addr_source(
274 &sg, &msg_source_flags, buf, pastend - buf);
275 if (addr_offset < 1) {
276 return -7;
277 }
278
279 buf += addr_offset;
280
281 /* if we are filtering this group, skip the join */
282 if (filtered)
283 continue;
284
285 recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr,
286 &sg, msg_source_flags);
287
288 if (pim_addr_is_any(sg.src)) {
289 starg_ch = pim_ifchannel_find(ifp, &sg);
290 if (starg_ch)
291 pim_ifchannel_set_star_g_join_state(
292 starg_ch, 0, 1);
293 }
294 }
295
296 /* Scan pruned sources */
297 for (source = 0; source < msg_num_pruned_sources; ++source) {
298 addr_offset = pim_parse_addr_source(
299 &sg, &msg_source_flags, buf, pastend - buf);
300 if (addr_offset < 1) {
301 return -8;
302 }
303
304 buf += addr_offset;
305
306 /* if we are filtering this group, skip the prune */
307 if (filtered)
308 continue;
309
310 recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr,
311 &sg, msg_source_flags);
312 /*
313 * So if we are receiving a S,G,RPT prune
314 * before we have any data for that S,G
315 * We need to retrieve the sg_ch after
316 * we parse the prune.
317 */
318 sg_ch = pim_ifchannel_find(ifp, &sg);
319
320 if (!sg_ch)
321 continue;
322
323 /* (*,G) prune received */
324 for (ALL_LIST_ELEMENTS(sg_ch->sources, ch_node,
325 nch_node, child)) {
326 if (PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) {
327 if (child->ifjoin_state
328 == PIM_IFJOIN_PRUNE_PENDING_TMP)
329 THREAD_OFF(
330 child->t_ifjoin_prune_pending_timer);
331 THREAD_OFF(
332 child->t_ifjoin_expiry_timer);
333 PIM_IF_FLAG_UNSET_S_G_RPT(child->flags);
334 child->ifjoin_state = PIM_IFJOIN_NOINFO;
335 delete_on_noinfo(child);
336 }
337 }
338
339 /* Received SG-RPT Prune delete oif from specific S,G */
340 if (starg_ch && (msg_source_flags & PIM_RPT_BIT_MASK)
341 && !(msg_source_flags & PIM_WILDCARD_BIT_MASK)) {
342 struct pim_upstream *up = sg_ch->upstream;
343 PIM_IF_FLAG_SET_S_G_RPT(sg_ch->flags);
344 if (up) {
345 if (PIM_DEBUG_PIM_TRACE)
346 zlog_debug(
347 "%s: SGRpt flag is set, del inherit oif from up %s",
348 __func__, up->sg_str);
349 pim_channel_del_inherited_oif(
350 up->channel_oil,
351 starg_ch->interface,
352 __func__);
353 }
354 }
355 }
356 if (starg_ch && !filtered)
357 pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0);
358 starg_ch = NULL;
359 } /* scan groups */
360
361 return 0;
362 }
363
364 /*
365 * J/P Message Format
366 *
367 * While the RFC clearly states that this is 32 bits wide, it
368 * is cheating. These fields:
369 * Encoded-Unicast format (6 bytes MIN)
370 * Encoded-Group format (8 bytes MIN)
371 * Encoded-Source format (8 bytes MIN)
372 * are *not* 32 bits wide.
373 *
374 * Nor does the RFC explicitly call out the size for:
375 * Reserved (1 byte)
376 * Num Groups (1 byte)
377 * Holdtime (2 bytes)
378 * Number of Joined Sources (2 bytes)
379 * Number of Pruned Sources (2 bytes)
380 *
381 * This leads to a missleading representation from casual
382 * reading and making assumptions. Be careful!
383 *
384 * 0 1 2 3
385 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
386 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
387 * |PIM Ver| Type | Reserved | Checksum |
388 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
389 * | Upstream Neighbor Address (Encoded-Unicast format) |
390 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
391 * | Reserved | Num groups | Holdtime |
392 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
393 * | Multicast Group Address 1 (Encoded-Group format) |
394 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
395 * | Number of Joined Sources | Number of Pruned Sources |
396 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
397 * | Joined Source Address 1 (Encoded-Source format) |
398 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
399 * | . |
400 * | . |
401 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
402 * | Joined Source Address n (Encoded-Source format) |
403 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
404 * | Pruned Source Address 1 (Encoded-Source format) |
405 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
406 * | . |
407 * | . |
408 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
409 * | Pruned Source Address n (Encoded-Source format) |
410 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
411 * | Multicast Group Address m (Encoded-Group format) |
412 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
413 * | Number of Joined Sources | Number of Pruned Sources |
414 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
415 * | Joined Source Address 1 (Encoded-Source format) |
416 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
417 * | . |
418 * | . |
419 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
420 * | Joined Source Address n (Encoded-Source format) |
421 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
422 * | Pruned Source Address 1 (Encoded-Source format) |
423 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
424 * | . |
425 * | . |
426 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
427 * | Pruned Source Address n (Encoded-Source format) |
428 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
429 */
430 int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
431 {
432 struct pim_jp_agg_group *group;
433 struct pim_interface *pim_ifp = NULL;
434 struct pim_jp_groups *grp = NULL;
435 struct pim_jp *msg = NULL;
436 struct listnode *node, *nnode;
437 uint8_t pim_msg[10000];
438 uint8_t *curr_ptr = pim_msg;
439 bool new_packet = true;
440 size_t packet_left = 0;
441 size_t packet_size = 0;
442 size_t group_size = 0;
443
444 if (rpf->source_nexthop.interface)
445 pim_ifp = rpf->source_nexthop.interface->info;
446 else {
447 zlog_warn("%s: RPF interface is not present", __func__);
448 return -1;
449 }
450
451
452 on_trace(__func__, rpf->source_nexthop.interface, rpf->rpf_addr);
453
454 if (!pim_ifp) {
455 zlog_warn("%s: multicast not enabled on interface %s", __func__,
456 rpf->source_nexthop.interface->name);
457 return -1;
458 }
459
460 if (pim_addr_is_any(rpf->rpf_addr)) {
461 if (PIM_DEBUG_PIM_J_P)
462 zlog_debug(
463 "%s: upstream=%pPA is myself on interface %s",
464 __func__, &rpf->rpf_addr,
465 rpf->source_nexthop.interface->name);
466 return 0;
467 }
468
469 /*
470 RFC 4601: 4.3.1. Sending Hello Messages
471
472 Thus, if a router needs to send a Join/Prune or Assert message on
473 an interface on which it has not yet sent a Hello message with the
474 currently configured IP address, then it MUST immediately send the
475 relevant Hello message without waiting for the Hello Timer to
476 expire, followed by the Join/Prune or Assert message.
477 */
478 pim_hello_require(rpf->source_nexthop.interface);
479
480 for (ALL_LIST_ELEMENTS(groups, node, nnode, group)) {
481 if (new_packet) {
482 msg = (struct pim_jp *)pim_msg;
483
484 memset(msg, 0, sizeof(*msg));
485
486 pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
487 rpf->rpf_addr);
488 msg->reserved = 0;
489 msg->holdtime = htons(PIM_JP_HOLDTIME);
490
491 new_packet = false;
492
493 grp = &msg->groups[0];
494 curr_ptr = (uint8_t *)grp;
495 packet_size = sizeof(struct pim_msg_header);
496 packet_size += sizeof(pim_encoded_unicast);
497 packet_size +=
498 4; // reserved (1) + groups (1) + holdtime (2)
499
500 packet_left = rpf->source_nexthop.interface->mtu - 24;
501 packet_left -= packet_size;
502 }
503 if (PIM_DEBUG_PIM_J_P)
504 zlog_debug(
505 "%s: sending (G)=%pPAs to upstream=%pPA on interface %s",
506 __func__, &group->group, &rpf->rpf_addr,
507 rpf->source_nexthop.interface->name);
508
509 group_size = pim_msg_get_jp_group_size(group->sources);
510 if (group_size > packet_left) {
511 pim_msg_build_header(pim_ifp->primary_address,
512 qpim_all_pim_routers_addr, pim_msg,
513 packet_size,
514 PIM_MSG_TYPE_JOIN_PRUNE, false);
515 if (pim_msg_send(pim_ifp->pim_sock_fd,
516 pim_ifp->primary_address,
517 qpim_all_pim_routers_addr, pim_msg,
518 packet_size,
519 rpf->source_nexthop.interface)) {
520 zlog_warn(
521 "%s: could not send PIM message on interface %s",
522 __func__,
523 rpf->source_nexthop.interface->name);
524 }
525
526 msg = (struct pim_jp *)pim_msg;
527 memset(msg, 0, sizeof(*msg));
528
529 pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
530 rpf->rpf_addr);
531 msg->reserved = 0;
532 msg->holdtime = htons(PIM_JP_HOLDTIME);
533
534 new_packet = false;
535
536 grp = &msg->groups[0];
537 curr_ptr = (uint8_t *)grp;
538 packet_size = sizeof(struct pim_msg_header);
539 packet_size += sizeof(pim_encoded_unicast);
540 packet_size +=
541 4; // reserved (1) + groups (1) + holdtime (2)
542
543 packet_left = rpf->source_nexthop.interface->mtu - 24;
544 packet_left -= packet_size;
545 }
546
547 msg->num_groups++;
548 /*
549 Build PIM message
550 */
551
552 curr_ptr += group_size;
553 packet_left -= group_size;
554 packet_size += group_size;
555 pim_msg_build_jp_groups(grp, group, group_size);
556
557 if (!pim_ifp->pim_passive_enable) {
558 pim_ifp->pim_ifstat_join_send += ntohs(grp->joins);
559 pim_ifp->pim_ifstat_prune_send += ntohs(grp->prunes);
560 }
561
562 if (PIM_DEBUG_PIM_TRACE)
563 zlog_debug(
564 "%s: interface %s num_joins %u num_prunes %u",
565 __func__, rpf->source_nexthop.interface->name,
566 ntohs(grp->joins), ntohs(grp->prunes));
567
568 grp = (struct pim_jp_groups *)curr_ptr;
569 if (packet_left < sizeof(struct pim_jp_groups)
570 || msg->num_groups == 255) {
571 pim_msg_build_header(pim_ifp->primary_address,
572 qpim_all_pim_routers_addr, pim_msg,
573 packet_size,
574 PIM_MSG_TYPE_JOIN_PRUNE, false);
575 if (pim_msg_send(pim_ifp->pim_sock_fd,
576 pim_ifp->primary_address,
577 qpim_all_pim_routers_addr, pim_msg,
578 packet_size,
579 rpf->source_nexthop.interface)) {
580 zlog_warn(
581 "%s: could not send PIM message on interface %s",
582 __func__,
583 rpf->source_nexthop.interface->name);
584 }
585
586 new_packet = true;
587 }
588 }
589
590
591 if (!new_packet) {
592 // msg->num_groups = htons (msg->num_groups);
593 pim_msg_build_header(
594 pim_ifp->primary_address, qpim_all_pim_routers_addr,
595 pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false);
596 if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address,
597 qpim_all_pim_routers_addr, pim_msg,
598 packet_size, rpf->source_nexthop.interface)) {
599 zlog_warn(
600 "%s: could not send PIM message on interface %s",
601 __func__, rpf->source_nexthop.interface->name);
602 }
603 }
604 return 0;
605 }