summaryrefslogtreecommitdiffstats
path: root/pimd/pim_join.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_join.c')
-rw-r--r--pimd/pim_join.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/pimd/pim_join.c b/pimd/pim_join.c
new file mode 100644
index 0000000..8c7ae80
--- /dev/null
+++ b/pimd/pim_join.c
@@ -0,0 +1,592 @@
+
+
+/*
+ * PIM for Quagga
+ * Copyright (C) 2008 Everton da Silva Marques
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+#include "if.h"
+#include "vty.h"
+#include "plist.h"
+
+#include "pimd.h"
+#include "pim_instance.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_join.h"
+#include "pim_oil.h"
+#include "pim_iface.h"
+#include "pim_hello.h"
+#include "pim_ifchannel.h"
+#include "pim_rpf.h"
+#include "pim_rp.h"
+#include "pim_jp_agg.h"
+#include "pim_util.h"
+#include "pim_ssm.h"
+
+static void on_trace(const char *label, struct interface *ifp, pim_addr src)
+{
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug("%s: from %pPA on %s", label, &src, ifp->name);
+}
+
+static void recv_join(struct interface *ifp, struct pim_neighbor *neigh,
+ uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg,
+ uint8_t source_flags)
+{
+ struct pim_interface *pim_ifp = NULL;
+
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug(
+ "%s: join (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s",
+ __func__, sg, !!(source_flags & PIM_RPT_BIT_MASK),
+ !!(source_flags & PIM_WILDCARD_BIT_MASK), &upstream,
+ holdtime, &neigh->source_addr, ifp->name);
+
+ pim_ifp = ifp->info;
+ assert(pim_ifp);
+
+ ++pim_ifp->pim_ifstat_join_recv;
+
+ /*
+ * If the RPT and WC are set it's a (*,G)
+ * and the source is the RP
+ */
+ if ((source_flags & PIM_RPT_BIT_MASK)
+ && (source_flags & PIM_WILDCARD_BIT_MASK)) {
+ struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp);
+ pim_addr rpf_addr;
+
+ if (!rp) {
+ zlog_warn("%s: Lookup of RP failed for %pSG", __func__,
+ sg);
+ return;
+ }
+ /*
+ * If the RP sent in the message is not
+ * our RP for the group, drop the message
+ */
+ rpf_addr = rp->rpf_addr;
+ if (pim_addr_cmp(sg->src, rpf_addr)) {
+ zlog_warn(
+ "%s: Specified RP(%pPAs) in join is different than our configured RP(%pPAs)",
+ __func__, &sg->src, &rpf_addr);
+ return;
+ }
+
+ if (pim_is_grp_ssm(pim_ifp->pim, sg->grp)) {
+ zlog_warn(
+ "%s: Specified Group(%pPA) in join is now in SSM, not allowed to create PIM state",
+ __func__, &sg->grp);
+ return;
+ }
+
+ sg->src = PIMADDR_ANY;
+ }
+
+ /* Restart join expiry timer */
+ pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, sg,
+ source_flags, holdtime);
+}
+
+static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh,
+ uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg,
+ uint8_t source_flags)
+{
+ struct pim_interface *pim_ifp = NULL;
+
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug(
+ "%s: prune (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s",
+ __func__, sg, source_flags & PIM_RPT_BIT_MASK,
+ source_flags & PIM_WILDCARD_BIT_MASK, &upstream,
+ holdtime, &neigh->source_addr, ifp->name);
+
+ pim_ifp = ifp->info;
+ assert(pim_ifp);
+
+ ++pim_ifp->pim_ifstat_prune_recv;
+
+ if ((source_flags & PIM_RPT_BIT_MASK)
+ && (source_flags & PIM_WILDCARD_BIT_MASK)) {
+ /*
+ * RFC 4601 Section 4.5.2:
+ * Received Prune(*,G) messages are processed even if the
+ * RP in the message does not match RP(G).
+ */
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug("%s: Prune received with RP(%pPAs) for %pSG",
+ __func__, &sg->src, sg);
+
+ sg->src = PIMADDR_ANY;
+ }
+
+ pim_ifchannel_prune(ifp, upstream, sg, source_flags, holdtime);
+}
+
+int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh,
+ pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size)
+{
+ pim_addr msg_upstream_addr;
+ bool wrong_af = false;
+ struct pim_interface *pim_ifp;
+ uint8_t msg_num_groups;
+ uint16_t msg_holdtime;
+ int addr_offset;
+ uint8_t *buf;
+ uint8_t *pastend;
+ int remain;
+ int group;
+ struct pim_ifchannel *child = NULL;
+ struct listnode *ch_node, *nch_node;
+
+ buf = tlv_buf;
+ pastend = tlv_buf + tlv_buf_size;
+ pim_ifp = ifp->info;
+
+ if (pim_ifp->pim_passive_enable) {
+ if (PIM_DEBUG_PIM_PACKETS)
+ zlog_debug(
+ "skip receiving PIM message on passive interface %s",
+ ifp->name);
+ return 0;
+ }
+
+ /*
+ Parse ucast addr
+ */
+ addr_offset = pim_parse_addr_ucast(&msg_upstream_addr, buf,
+ pastend - buf, &wrong_af);
+ if (addr_offset < 1) {
+ zlog_warn("%s: pim_parse_addr_ucast() failure: from %pPA on %s",
+ __func__, &src_addr, ifp->name);
+ return -1;
+ }
+ buf += addr_offset;
+
+ /*
+ Check upstream address family
+ */
+ if (wrong_af) {
+ zlog_warn(
+ "%s: ignoring join/prune directed to unexpected addr family from %pPA on %s",
+ __func__, &src_addr, ifp->name);
+ return -2;
+ }
+
+ remain = pastend - buf;
+ if (remain < 4) {
+ zlog_warn(
+ "%s: short join/prune message buffer for group list: size=%d minimum=%d from %pPA on %s",
+ __func__, remain, 4, &src_addr, ifp->name);
+ return -4;
+ }
+
+ ++buf; /* skip reserved byte */
+ msg_num_groups = *(const uint8_t *)buf;
+ ++buf;
+ msg_holdtime = ntohs(*(const uint16_t *)buf);
+ ++buf;
+ ++buf;
+
+ if (PIM_DEBUG_PIM_J_P)
+ zlog_debug(
+ "%s: join/prune upstream=%pPAs groups=%d holdtime=%d from %pPA on %s",
+ __func__, &msg_upstream_addr, msg_num_groups,
+ msg_holdtime, &src_addr, ifp->name);
+
+ /* Scan groups */
+ for (group = 0; group < msg_num_groups; ++group) {
+ pim_sgaddr sg;
+ uint8_t msg_source_flags;
+ uint16_t msg_num_joined_sources;
+ uint16_t msg_num_pruned_sources;
+ int source;
+ struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL;
+ bool filtered = false;
+
+ memset(&sg, 0, sizeof(sg));
+ addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf);
+ if (addr_offset < 1) {
+ return -5;
+ }
+ buf += addr_offset;
+
+ remain = pastend - buf;
+ if (remain < 4) {
+ zlog_warn(
+ "%s: short join/prune buffer for source list: size=%d minimum=%d from %pPA on %s",
+ __func__, remain, 4, &src_addr, ifp->name);
+ return -6;
+ }
+
+ msg_num_joined_sources = ntohs(*(const uint16_t *)buf);
+ buf += 2;
+ msg_num_pruned_sources = ntohs(*(const uint16_t *)buf);
+ buf += 2;
+
+ if (PIM_DEBUG_PIM_J_P)
+ zlog_debug(
+ "%s: join/prune upstream=%pPAs group=%pPA/32 join_src=%d prune_src=%d from %pPA on %s",
+ __func__, &msg_upstream_addr, &sg.grp,
+ msg_num_joined_sources, msg_num_pruned_sources,
+ &src_addr, ifp->name);
+
+ /* boundary check */
+ filtered = pim_is_group_filtered(pim_ifp, &sg.grp);
+
+ /* Scan joined sources */
+ for (source = 0; source < msg_num_joined_sources; ++source) {
+ addr_offset = pim_parse_addr_source(
+ &sg, &msg_source_flags, buf, pastend - buf);
+ if (addr_offset < 1) {
+ return -7;
+ }
+
+ buf += addr_offset;
+
+ /* if we are filtering this group, skip the join */
+ if (filtered)
+ continue;
+
+ recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr,
+ &sg, msg_source_flags);
+
+ if (pim_addr_is_any(sg.src)) {
+ starg_ch = pim_ifchannel_find(ifp, &sg);
+ if (starg_ch)
+ pim_ifchannel_set_star_g_join_state(
+ starg_ch, 0, 1);
+ }
+ }
+
+ /* Scan pruned sources */
+ for (source = 0; source < msg_num_pruned_sources; ++source) {
+ addr_offset = pim_parse_addr_source(
+ &sg, &msg_source_flags, buf, pastend - buf);
+ if (addr_offset < 1) {
+ return -8;
+ }
+
+ buf += addr_offset;
+
+ /* if we are filtering this group, skip the prune */
+ if (filtered)
+ continue;
+
+ recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr,
+ &sg, msg_source_flags);
+ /*
+ * So if we are receiving a S,G,RPT prune
+ * before we have any data for that S,G
+ * We need to retrieve the sg_ch after
+ * we parse the prune.
+ */
+ sg_ch = pim_ifchannel_find(ifp, &sg);
+
+ if (!sg_ch)
+ continue;
+
+ /* (*,G) prune received */
+ for (ALL_LIST_ELEMENTS(sg_ch->sources, ch_node,
+ nch_node, child)) {
+ if (PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) {
+ if (child->ifjoin_state
+ == PIM_IFJOIN_PRUNE_PENDING_TMP)
+ THREAD_OFF(
+ child->t_ifjoin_prune_pending_timer);
+ THREAD_OFF(
+ child->t_ifjoin_expiry_timer);
+ PIM_IF_FLAG_UNSET_S_G_RPT(child->flags);
+ child->ifjoin_state = PIM_IFJOIN_NOINFO;
+ delete_on_noinfo(child);
+ }
+ }
+
+ /* Received SG-RPT Prune delete oif from specific S,G */
+ if (starg_ch && (msg_source_flags & PIM_RPT_BIT_MASK)
+ && !(msg_source_flags & PIM_WILDCARD_BIT_MASK)) {
+ struct pim_upstream *up = sg_ch->upstream;
+ PIM_IF_FLAG_SET_S_G_RPT(sg_ch->flags);
+ if (up) {
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug(
+ "%s: SGRpt flag is set, del inherit oif from up %s",
+ __func__, up->sg_str);
+ pim_channel_del_inherited_oif(
+ up->channel_oil,
+ starg_ch->interface,
+ __func__);
+ }
+ }
+ }
+ if (starg_ch && !filtered)
+ pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0);
+ starg_ch = NULL;
+ } /* scan groups */
+
+ return 0;
+}
+
+/*
+ * J/P Message Format
+ *
+ * While the RFC clearly states that this is 32 bits wide, it
+ * is cheating. These fields:
+ * Encoded-Unicast format (6 bytes MIN)
+ * Encoded-Group format (8 bytes MIN)
+ * Encoded-Source format (8 bytes MIN)
+ * are *not* 32 bits wide.
+ *
+ * Nor does the RFC explicitly call out the size for:
+ * Reserved (1 byte)
+ * Num Groups (1 byte)
+ * Holdtime (2 bytes)
+ * Number of Joined Sources (2 bytes)
+ * Number of Pruned Sources (2 bytes)
+ *
+ * This leads to a missleading representation from casual
+ * reading and making assumptions. Be careful!
+ *
+ * 0 1 2 3
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |PIM Ver| Type | Reserved | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Upstream Neighbor Address (Encoded-Unicast format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved | Num groups | Holdtime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Multicast Group Address 1 (Encoded-Group format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Number of Joined Sources | Number of Pruned Sources |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Joined Source Address 1 (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | . |
+ * | . |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Joined Source Address n (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Pruned Source Address 1 (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | . |
+ * | . |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Pruned Source Address n (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Multicast Group Address m (Encoded-Group format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Number of Joined Sources | Number of Pruned Sources |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Joined Source Address 1 (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | . |
+ * | . |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Joined Source Address n (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Pruned Source Address 1 (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | . |
+ * | . |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Pruned Source Address n (Encoded-Source format) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups)
+{
+ struct pim_jp_agg_group *group;
+ struct pim_interface *pim_ifp = NULL;
+ struct pim_jp_groups *grp = NULL;
+ struct pim_jp *msg = NULL;
+ struct listnode *node, *nnode;
+ uint8_t pim_msg[10000];
+ uint8_t *curr_ptr = pim_msg;
+ bool new_packet = true;
+ size_t packet_left = 0;
+ size_t packet_size = 0;
+ size_t group_size = 0;
+
+ if (rpf->source_nexthop.interface)
+ pim_ifp = rpf->source_nexthop.interface->info;
+ else {
+ zlog_warn("%s: RPF interface is not present", __func__);
+ return -1;
+ }
+
+
+ on_trace(__func__, rpf->source_nexthop.interface, rpf->rpf_addr);
+
+ if (!pim_ifp) {
+ zlog_warn("%s: multicast not enabled on interface %s", __func__,
+ rpf->source_nexthop.interface->name);
+ return -1;
+ }
+
+ if (pim_addr_is_any(rpf->rpf_addr)) {
+ if (PIM_DEBUG_PIM_J_P)
+ zlog_debug(
+ "%s: upstream=%pPA is myself on interface %s",
+ __func__, &rpf->rpf_addr,
+ rpf->source_nexthop.interface->name);
+ return 0;
+ }
+
+ /*
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ Thus, if a router needs to send a Join/Prune or Assert message on
+ an interface on which it has not yet sent a Hello message with the
+ currently configured IP address, then it MUST immediately send the
+ relevant Hello message without waiting for the Hello Timer to
+ expire, followed by the Join/Prune or Assert message.
+ */
+ pim_hello_require(rpf->source_nexthop.interface);
+
+ for (ALL_LIST_ELEMENTS(groups, node, nnode, group)) {
+ if (new_packet) {
+ msg = (struct pim_jp *)pim_msg;
+
+ memset(msg, 0, sizeof(*msg));
+
+ pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
+ rpf->rpf_addr);
+ msg->reserved = 0;
+ msg->holdtime = htons(PIM_JP_HOLDTIME);
+
+ new_packet = false;
+
+ grp = &msg->groups[0];
+ curr_ptr = (uint8_t *)grp;
+ packet_size = sizeof(struct pim_msg_header);
+ packet_size += sizeof(pim_encoded_unicast);
+ packet_size +=
+ 4; // reserved (1) + groups (1) + holdtime (2)
+
+ packet_left = rpf->source_nexthop.interface->mtu - 24;
+ packet_left -= packet_size;
+ }
+ if (PIM_DEBUG_PIM_J_P)
+ zlog_debug(
+ "%s: sending (G)=%pPAs to upstream=%pPA on interface %s",
+ __func__, &group->group, &rpf->rpf_addr,
+ rpf->source_nexthop.interface->name);
+
+ group_size = pim_msg_get_jp_group_size(group->sources);
+ if (group_size > packet_left) {
+ pim_msg_build_header(pim_ifp->primary_address,
+ qpim_all_pim_routers_addr, pim_msg,
+ packet_size,
+ PIM_MSG_TYPE_JOIN_PRUNE, false);
+ if (pim_msg_send(pim_ifp->pim_sock_fd,
+ pim_ifp->primary_address,
+ qpim_all_pim_routers_addr, pim_msg,
+ packet_size,
+ rpf->source_nexthop.interface)) {
+ zlog_warn(
+ "%s: could not send PIM message on interface %s",
+ __func__,
+ rpf->source_nexthop.interface->name);
+ }
+
+ msg = (struct pim_jp *)pim_msg;
+ memset(msg, 0, sizeof(*msg));
+
+ pim_msg_addr_encode_ucast((uint8_t *)&msg->addr,
+ rpf->rpf_addr);
+ msg->reserved = 0;
+ msg->holdtime = htons(PIM_JP_HOLDTIME);
+
+ new_packet = false;
+
+ grp = &msg->groups[0];
+ curr_ptr = (uint8_t *)grp;
+ packet_size = sizeof(struct pim_msg_header);
+ packet_size += sizeof(pim_encoded_unicast);
+ packet_size +=
+ 4; // reserved (1) + groups (1) + holdtime (2)
+
+ packet_left = rpf->source_nexthop.interface->mtu - 24;
+ packet_left -= packet_size;
+ }
+
+ msg->num_groups++;
+ /*
+ Build PIM message
+ */
+
+ curr_ptr += group_size;
+ packet_left -= group_size;
+ packet_size += group_size;
+ pim_msg_build_jp_groups(grp, group, group_size);
+
+ if (!pim_ifp->pim_passive_enable) {
+ pim_ifp->pim_ifstat_join_send += ntohs(grp->joins);
+ pim_ifp->pim_ifstat_prune_send += ntohs(grp->prunes);
+ }
+
+ if (PIM_DEBUG_PIM_TRACE)
+ zlog_debug(
+ "%s: interface %s num_joins %u num_prunes %u",
+ __func__, rpf->source_nexthop.interface->name,
+ ntohs(grp->joins), ntohs(grp->prunes));
+
+ grp = (struct pim_jp_groups *)curr_ptr;
+ if (packet_left < sizeof(struct pim_jp_groups)
+ || msg->num_groups == 255) {
+ pim_msg_build_header(pim_ifp->primary_address,
+ qpim_all_pim_routers_addr, pim_msg,
+ packet_size,
+ PIM_MSG_TYPE_JOIN_PRUNE, false);
+ if (pim_msg_send(pim_ifp->pim_sock_fd,
+ pim_ifp->primary_address,
+ qpim_all_pim_routers_addr, pim_msg,
+ packet_size,
+ rpf->source_nexthop.interface)) {
+ zlog_warn(
+ "%s: could not send PIM message on interface %s",
+ __func__,
+ rpf->source_nexthop.interface->name);
+ }
+
+ new_packet = true;
+ }
+ }
+
+
+ if (!new_packet) {
+ // msg->num_groups = htons (msg->num_groups);
+ pim_msg_build_header(
+ pim_ifp->primary_address, qpim_all_pim_routers_addr,
+ pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false);
+ if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address,
+ qpim_all_pim_routers_addr, pim_msg,
+ packet_size, rpf->source_nexthop.interface)) {
+ zlog_warn(
+ "%s: could not send PIM message on interface %s",
+ __func__, rpf->source_nexthop.interface->name);
+ }
+ }
+ return 0;
+}