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