diff options
Diffstat (limited to 'pimd/pim_ifchannel.c')
-rw-r--r-- | pimd/pim_ifchannel.c | 1505 |
1 files changed, 1505 insertions, 0 deletions
diff --git a/pimd/pim_ifchannel.c b/pimd/pim_ifchannel.c new file mode 100644 index 0000000..3310009 --- /dev/null +++ b/pimd/pim_ifchannel.c @@ -0,0 +1,1505 @@ +/* + * 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 "linklist.h" +#include "thread.h" +#include "memory.h" +#include "if.h" +#include "vrf.h" +#include "hash.h" +#include "jhash.h" +#include "prefix.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_ifchannel.h" +#include "pim_zebra.h" +#include "pim_time.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_join.h" +#include "pim_rpf.h" +#include "pim_macro.h" +#include "pim_oil.h" +#include "pim_upstream.h" +#include "pim_ssm.h" +#include "pim_rp.h" +#include "pim_mlag.h" + +RB_GENERATE(pim_ifchannel_rb, pim_ifchannel, pim_ifp_rb, pim_ifchannel_compare); + +int pim_ifchannel_compare(const struct pim_ifchannel *ch1, + const struct pim_ifchannel *ch2) +{ + struct pim_interface *pim_ifp1; + struct pim_interface *pim_ifp2; + + pim_ifp1 = ch1->interface->info; + pim_ifp2 = ch2->interface->info; + + if (pim_ifp1->mroute_vif_index < pim_ifp2->mroute_vif_index) + return -1; + + if (pim_ifp1->mroute_vif_index > pim_ifp2->mroute_vif_index) + return 1; + + return pim_sgaddr_cmp(ch1->sg, ch2->sg); +} + +/* + * A (*,G) or a (*,*) is going away + * remove the parent pointer from + * those pointing at us + */ +static void pim_ifchannel_remove_children(struct pim_ifchannel *ch) +{ + struct pim_ifchannel *child; + + if (!ch->sources) + return; + + while (!list_isempty(ch->sources)) { + child = listnode_head(ch->sources); + child->parent = NULL; + listnode_delete(ch->sources, child); + } +} + +/* + * A (*,G) or a (*,*) is being created + * find all the children that would point + * at us. + */ +static void pim_ifchannel_find_new_children(struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp = ch->interface->info; + struct pim_ifchannel *child; + + // Basic Sanity that we are not being silly + if (!pim_addr_is_any(ch->sg.src) && !pim_addr_is_any(ch->sg.grp)) + return; + + if (pim_addr_is_any(ch->sg.src) && pim_addr_is_any(ch->sg.grp)) + return; + + RB_FOREACH (child, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + if (!pim_addr_is_any(ch->sg.grp) && + !pim_addr_cmp(child->sg.grp, ch->sg.grp) && (child != ch)) { + child->parent = ch; + listnode_add_sort(ch->sources, child); + } + } +} + +void pim_ifchannel_delete(struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp; + struct pim_upstream *up; + + pim_ifp = ch->interface->info; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel entry %s(%s) del start", __func__, + ch->sg_str, ch->interface->name); + + if (PIM_I_am_DualActive(pim_ifp)) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: if-chnanel-%s is deleted from a Dual active Interface", + __func__, ch->sg_str); + /* Post Delete only if it is the last Dual-active Interface */ + if (ch->upstream->dualactive_ifchannel_count == 1) { + pim_mlag_up_local_del(pim_ifp->pim, ch->upstream); + PIM_UPSTREAM_FLAG_UNSET_MLAG_INTERFACE( + ch->upstream->flags); + } + ch->upstream->dualactive_ifchannel_count--; + } + + if (ch->upstream->channel_oil) { + uint32_t mask = PIM_OIF_FLAG_PROTO_PIM; + if (ch->upstream->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + mask |= PIM_OIF_FLAG_PROTO_GM; + + /* + * A S,G RPT channel can have an empty oil, we also + * need to take into account the fact that a ifchannel + * might have been suppressing a *,G ifchannel from + * being inherited. So let's figure out what + * needs to be done here + */ + if (!pim_addr_is_any(ch->sg.src) && + pim_upstream_evaluate_join_desired_interface( + ch->upstream, ch, ch->parent)) + pim_channel_add_oif(ch->upstream->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + + pim_channel_del_oif(ch->upstream->channel_oil, + ch->interface, mask, __func__); + /* + * Do we have any S,G's that are inheriting? + * Nuke from on high too. + */ + if (ch->upstream->sources) { + struct pim_upstream *child; + struct listnode *up_node; + + for (ALL_LIST_ELEMENTS_RO(ch->upstream->sources, + up_node, child)) + pim_channel_del_inherited_oif( + child->channel_oil, + ch->interface, + __func__); + } + } + + /* + * When this channel is removed + * we need to find all our children + * and make sure our pointers are fixed + */ + pim_ifchannel_remove_children(ch); + + if (ch->sources) + list_delete(&ch->sources); + + listnode_delete(ch->upstream->ifchannels, ch); + + up = ch->upstream; + + /* upstream is common across ifchannels, check if upstream's + ifchannel list is empty before deleting upstream_del + ref count will take care of it. + */ + if (ch->upstream->ref_count > 0) + up = pim_upstream_del(pim_ifp->pim, ch->upstream, __func__); + + else { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Avoiding deletion of upstream with ref_count %d from ifchannel(%s): %s", + __func__, ch->upstream->ref_count, + ch->interface->name, ch->sg_str); + } + + ch->upstream = NULL; + + THREAD_OFF(ch->t_ifjoin_expiry_timer); + THREAD_OFF(ch->t_ifjoin_prune_pending_timer); + THREAD_OFF(ch->t_ifassert_timer); + + if (ch->parent) { + listnode_delete(ch->parent->sources, ch); + ch->parent = NULL; + } + + RB_REMOVE(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel entry %s(%s) is deleted ", __func__, + ch->sg_str, ch->interface->name); + + XFREE(MTYPE_PIM_IFCHANNEL, ch); + + if (up) + pim_upstream_update_join_desired(pim_ifp->pim, up); +} + +void pim_ifchannel_delete_all(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { + ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); + + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_NOINFO); + pim_ifchannel_delete(ch); + } +} + +void delete_on_noinfo(struct pim_ifchannel *ch) +{ + if (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO + && ch->ifjoin_state == PIM_IFJOIN_NOINFO + && ch->t_ifjoin_expiry_timer == NULL) + pim_ifchannel_delete(ch); +} + +void pim_ifchannel_ifjoin_switch(const char *caller, struct pim_ifchannel *ch, + enum pim_ifjoin_state new_state) +{ + enum pim_ifjoin_state old_state = ch->ifjoin_state; + struct pim_interface *pim_ifp = ch->interface->info; + struct pim_ifchannel *child_ch; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "PIM_IFCHANNEL(%s): %s is switching from %s to %s", + ch->interface->name, ch->sg_str, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), + pim_ifchannel_ifjoin_name(new_state, 0)); + + + if (old_state == new_state) { + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug( + "%s called by %s: non-transition on state %d (%s)", + __func__, caller, new_state, + pim_ifchannel_ifjoin_name(new_state, 0)); + } + return; + } + + ch->ifjoin_state = new_state; + + if (pim_addr_is_any(ch->sg.src)) { + struct pim_upstream *up = ch->upstream; + struct pim_upstream *child; + struct listnode *up_node; + + if (up) { + if (ch->ifjoin_state == PIM_IFJOIN_NOINFO) { + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, + child)) { + struct channel_oil *c_oil = + child->channel_oil; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s %s: Prune(S,G)=%s from %s", + __FILE__, __func__, + child->sg_str, + up->sg_str); + if (!c_oil) + continue; + + /* + * If the S,G has no if channel and the + * c_oil still + * has output here then the *,G was + * supplying the implied + * if channel. So remove it. + */ + if (oil_if_has(c_oil, + pim_ifp->mroute_vif_index)) + pim_channel_del_inherited_oif( + c_oil, ch->interface, + __func__); + } + } + if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, + child)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s %s: Join(S,G)=%s from %s", + __FILE__, __func__, + child->sg_str, + up->sg_str); + + /* check if the channel can be + * inherited into the SG's OIL + */ + child_ch = pim_ifchannel_find( + ch->interface, + &child->sg); + if (pim_upstream_eval_inherit_if( + child, child_ch, ch)) { + pim_channel_add_oif( + child->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired( + pim_ifp->pim, child); + } + } + } + } + } + /* Transition to/from NOINFO ? */ + if ((old_state == PIM_IFJOIN_NOINFO) + || (new_state == PIM_IFJOIN_NOINFO)) { + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s", + ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" + : "UP"), + ch->sg_str, ch->interface->name); + } + + /* + Record uptime of state transition to/from NOINFO + */ + ch->ifjoin_creation = pim_time_monotonic_sec(); + + pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); + pim_ifchannel_update_could_assert(ch); + pim_ifchannel_update_assert_tracking_desired(ch); + } +} + +const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state, + int flags) +{ + switch (ifjoin_state) { + case PIM_IFJOIN_NOINFO: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(NI)"; + else + return "NOINFO"; + case PIM_IFJOIN_JOIN: + return "JOIN"; + case PIM_IFJOIN_PRUNE: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(P)"; + else + return "PRUNE"; + case PIM_IFJOIN_PRUNE_PENDING: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(PP)"; + else + return "PRUNEP"; + case PIM_IFJOIN_PRUNE_TMP: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(P')"; + else + return "PRUNET"; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(PP')"; + else + return "PRUNEPT"; + } + + return "ifjoin_bad_state"; +} + +const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state) +{ + switch (ifassert_state) { + case PIM_IFASSERT_NOINFO: + return "NOINFO"; + case PIM_IFASSERT_I_AM_WINNER: + return "WINNER"; + case PIM_IFASSERT_I_AM_LOSER: + return "LOSER"; + } + + return "ifassert_bad_state"; +} + +/* + RFC 4601: 4.6.5. Assert State Macros + + AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I) + defaults to Infinity when in the NoInfo state. +*/ +void reset_ifassert_state(struct pim_ifchannel *ch) +{ + THREAD_OFF(ch->t_ifassert_timer); + + pim_ifassert_winner_set(ch, PIM_IFASSERT_NOINFO, PIMADDR_ANY, + router->infinite_assert_metric); +} + +struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp, pim_sgaddr *sg) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct pim_ifchannel lookup; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%pSG: multicast not enabled on interface %s", + __func__, sg, ifp->name); + return NULL; + } + + lookup.sg = *sg; + lookup.interface = ifp; + ch = RB_FIND(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, &lookup); + + return ch; +} + +static void ifmembership_set(struct pim_ifchannel *ch, + enum pim_ifmembership membership) +{ + struct pim_interface *pim_ifp = ch->interface->info; + + if (ch->local_ifmembership == membership) + return; + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("%s: (S,G)=%s membership now is %s on interface %s", + __func__, ch->sg_str, + membership == PIM_IFMEMBERSHIP_INCLUDE ? "INCLUDE" + : "NOINFO", + ch->interface->name); + } + + ch->local_ifmembership = membership; + + pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); + pim_ifchannel_update_could_assert(ch); + pim_ifchannel_update_assert_tracking_desired(ch); +} + + +void pim_ifchannel_membership_clear(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) + ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); +} + +void pim_ifchannel_delete_on_noinfo(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch, *ch_tmp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch_tmp) + delete_on_noinfo(ch); +} + +/* + * For a given Interface, if we are given a S,G + * Find the *,G (If we have it). + * If we are passed a *,G, find the *,* ifchannel + * if we have it. + */ +static struct pim_ifchannel *pim_ifchannel_find_parent(struct pim_ifchannel *ch) +{ + pim_sgaddr parent_sg = ch->sg; + struct pim_ifchannel *parent = NULL; + + // (S,G) + if (!pim_addr_is_any(parent_sg.src) && + !pim_addr_is_any(parent_sg.grp)) { + parent_sg.src = PIMADDR_ANY; + parent = pim_ifchannel_find(ch->interface, &parent_sg); + + if (parent) + listnode_add(parent->sources, ch); + return parent; + } + + return NULL; +} + +struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp, pim_sgaddr *sg, + uint8_t source_flags, int up_flags) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct pim_upstream *up; + + ch = pim_ifchannel_find(ifp, sg); + if (ch) { + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_PIM) + PIM_IF_FLAG_SET_PROTO_PIM(ch->flags); + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + PIM_IF_FLAG_SET_PROTO_IGMP(ch->flags); + + ch->upstream->flags |= up_flags; + + return ch; + } + + pim_ifp = ifp->info; + + ch = XCALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch)); + + ch->flags = 0; + if ((source_flags & PIM_ENCODE_RPT_BIT) + && !(source_flags & PIM_ENCODE_WC_BIT)) + PIM_IF_FLAG_SET_S_G_RPT(ch->flags); + + ch->interface = ifp; + ch->sg = *sg; + snprintfrr(ch->sg_str, sizeof(ch->sg_str), "%pSG", sg); + ch->parent = pim_ifchannel_find_parent(ch); + if (pim_addr_is_any(ch->sg.src)) { + ch->sources = list_new(); + ch->sources->cmp = + (int (*)(void *, void *))pim_ifchannel_compare; + } else + ch->sources = NULL; + + pim_ifchannel_find_new_children(ch); + ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO; + + ch->ifjoin_state = PIM_IFJOIN_NOINFO; + ch->t_ifjoin_expiry_timer = NULL; + ch->t_ifjoin_prune_pending_timer = NULL; + ch->ifjoin_creation = 0; + + RB_INSERT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); + + up = pim_upstream_add(pim_ifp->pim, sg, NULL, up_flags, __func__, ch); + + ch->upstream = up; + + listnode_add_sort(up->ifchannels, ch); + + ch->ifassert_my_metric = pim_macro_ch_my_assert_metric_eval(ch); + ch->ifassert_winner_metric = pim_macro_ch_my_assert_metric_eval(ch); + + ch->ifassert_winner = PIMADDR_ANY; + + /* Assert state */ + ch->t_ifassert_timer = NULL; + ch->ifassert_state = PIM_IFASSERT_NOINFO; + reset_ifassert_state(ch); + if (pim_macro_ch_could_assert_eval(ch)) + PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); + else + PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); + + if (pim_macro_assert_tracking_desired_eval(ch)) + PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); + else + PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); + + /* + * advertise MLAG Data to MLAG peer + */ + if (PIM_I_am_DualActive(pim_ifp)) { + up->dualactive_ifchannel_count++; + /* Sync once for upstream */ + if (up->dualactive_ifchannel_count == 1) { + PIM_UPSTREAM_FLAG_SET_MLAG_INTERFACE(up->flags); + pim_mlag_up_local_add(pim_ifp->pim, up); + } + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: New Dual active if-chnanel is added to upstream:%s count:%d, flags:0x%x", + __func__, up->sg_str, + up->dualactive_ifchannel_count, up->flags); + } + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_PIM) + PIM_IF_FLAG_SET_PROTO_PIM(ch->flags); + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + PIM_IF_FLAG_SET_PROTO_IGMP(ch->flags); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel %s(%s) is created ", __func__, + ch->sg_str, ch->interface->name); + + return ch; +} + +static void ifjoin_to_noinfo(struct pim_ifchannel *ch) +{ + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_NOINFO); + pim_forward_stop(ch); + + PIM_UPSTREAM_FLAG_UNSET_SRC_PIM(ch->upstream->flags); + + PIM_IF_FLAG_UNSET_PROTO_PIM(ch->flags); + + delete_on_noinfo(ch); +} + +static void on_ifjoin_expiry_timer(struct thread *t) +{ + struct pim_ifchannel *ch; + + ch = THREAD_ARG(t); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel %s expiry timer", __func__, + ch->sg_str); + + ifjoin_to_noinfo(ch); + /* ch may have been deleted */ +} + +static void on_ifjoin_prune_pending_timer(struct thread *t) +{ + struct pim_ifchannel *ch; + int send_prune_echo; /* boolean */ + struct interface *ifp; + struct pim_interface *pim_ifp; + + ch = THREAD_ARG(t); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: IFCHANNEL%pSG %s Prune Pending Timer Popped", + __func__, &ch->sg, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags)); + + if (ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING) { + ifp = ch->interface; + pim_ifp = ifp->info; + if (!PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) { + /* Send PruneEcho(S,G) ? */ + send_prune_echo = + (listcount(pim_ifp->pim_neighbor_list) > 1); + + if (send_prune_echo) { + struct pim_rpf rpf; + + rpf.source_nexthop.interface = ifp; + rpf.rpf_addr = pim_ifp->primary_address; + pim_jp_agg_single_upstream_send( + &rpf, ch->upstream, 0); + } + + ifjoin_to_noinfo(ch); + } else { + /* If SGRpt flag is set on ifchannel, Trigger SGRpt + * message on RP path upon prune timer expiry. + */ + ch->ifjoin_state = PIM_IFJOIN_PRUNE; + struct pim_upstream *parent = + ch->upstream->parent; + + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + + pim_jp_agg_single_upstream_send(&parent->rpf, + parent, true); + /* + * SGRpt prune pending expiry has to install + * SG entry with empty olist to drop the SG + * traffic incase no other intf exists. + * On that scenario, SG entry wouldn't have + * got installed until Prune pending timer + * expired. So install now. + */ + pim_channel_del_oif( + ch->upstream->channel_oil, ifp, + PIM_OIF_FLAG_PROTO_STAR, __func__); + if (!ch->upstream->channel_oil->installed) + pim_upstream_mroute_add( + ch->upstream->channel_oil, + __func__); + } + /* from here ch may have been deleted */ + } +} + +static void check_recv_upstream(int is_join, struct interface *recv_ifp, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, int holdtime) +{ + struct pim_upstream *up; + struct pim_interface *pim_ifp = recv_ifp->info; + pim_addr rpf_addr; + + /* Upstream (S,G) in Joined state ? */ + up = pim_upstream_find(pim_ifp->pim, sg); + if (!up) + return; + if (up->join_state != PIM_UPSTREAM_JOINED) + return; + + /* Upstream (S,G) in Joined state */ + + if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { + /* RPF'(S,G) not found */ + zlog_warn("%s %s: RPF'%s not found", __FILE__, __func__, + up->sg_str); + return; + } + + rpf_addr = up->rpf.rpf_addr; + + /* upstream directed to RPF'(S,G) ? */ + if (pim_addr_cmp(upstream, rpf_addr)) { + zlog_warn( + "%s %s: (S,G)=%s upstream=%pPAs not directed to RPF'(S,G)=%pPAs on interface %s", + __FILE__, __func__, up->sg_str, &upstream, &rpf_addr, + recv_ifp->name); + return; + } + /* upstream directed to RPF'(S,G) */ + + if (is_join) { + /* Join(S,G) to RPF'(S,G) */ + pim_upstream_join_suppress(up, up->rpf.rpf_addr, holdtime); + return; + } + + /* Prune to RPF'(S,G) */ + + if (source_flags & PIM_RPT_BIT_MASK) { + if (source_flags & PIM_WILDCARD_BIT_MASK) { + /* Prune(*,G) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override( + "Prune(*,G)", up); + return; + } + + /* Prune(S,G,rpt) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)", + up); + return; + } + + /* Prune(S,G) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up); +} + +static int nonlocal_upstream(int is_join, struct interface *recv_ifp, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, uint16_t holdtime) +{ + struct pim_interface *recv_pim_ifp; + int is_local; /* boolean */ + + recv_pim_ifp = recv_ifp->info; + assert(recv_pim_ifp); + + is_local = !pim_addr_cmp(upstream, recv_pim_ifp->primary_address); + + if (is_local) + return 0; + + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_warn( + "%s: recv %s (S,G)=%pSG to non-local upstream=%pPAs on %s", + __func__, is_join ? "join" : "prune", sg, &upstream, + recv_ifp->name); + + /* + * Since recv upstream addr was not directed to our primary + * address, check if we should react to it in any way. + */ + check_recv_upstream(is_join, recv_ifp, upstream, sg, source_flags, + holdtime); + + return 1; /* non-local */ +} + +static void pim_ifchannel_ifjoin_handler(struct pim_ifchannel *ch, + struct pim_interface *pim_ifp) +{ + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_JOIN); + PIM_IF_FLAG_UNSET_S_G_RPT(ch->flags); + /* check if the interface qualifies as an immediate + * OIF + */ + if (pim_upstream_evaluate_join_desired_interface( + ch->upstream, ch, + NULL /*starch*/)) { + pim_channel_add_oif(ch->upstream->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_PIM, + __func__); + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + } +} + + +void pim_ifchannel_join_add(struct interface *ifp, pim_addr neigh_addr, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, uint16_t holdtime) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + if (nonlocal_upstream(1 /* join */, ifp, upstream, sg, source_flags, + holdtime)) { + return; + } + + ch = pim_ifchannel_add(ifp, sg, source_flags, + PIM_UPSTREAM_FLAG_MASK_SRC_PIM); + + /* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + Transitions from "I am Assert Loser" State + + Receive Join(S,G) on Interface I + + We receive a Join(S,G) that has the Upstream Neighbor Address + field set to my primary IP address on interface I. The action is + to transition to NoInfo state, delete this (S,G) assert state + (Actions A5 below), and allow the normal PIM Join/Prune mechanisms + to operate. + + Notice: The nonlocal_upstream() test above ensures the upstream + address of the join message is our primary address. + */ + if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + zlog_warn("%s: Assert Loser recv Join%s from %pPA on %s", + __func__, ch->sg_str, &neigh_addr, ifp->name); + + assert_action_a5(ch); + } + + pim_ifp = ifp->info; + assert(pim_ifp); + + switch (ch->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_JOIN); + if (pim_macro_chisin_oiflist(ch)) { + pim_upstream_inherited_olist(pim_ifp->pim, + ch->upstream); + pim_forward_start(ch); + } + /* + * If we are going to be a LHR, we need to note it + */ + if (ch->upstream->parent && + (PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR( + ch->upstream->parent->flags)) + && !(ch->upstream->flags + & PIM_UPSTREAM_FLAG_MASK_SRC_LHR)) { + pim_upstream_ref(ch->upstream, + PIM_UPSTREAM_FLAG_MASK_SRC_LHR, + __func__); + pim_upstream_keep_alive_timer_start( + ch->upstream, pim_ifp->pim->keep_alive_time); + } + break; + case PIM_IFJOIN_JOIN: + assert(!ch->t_ifjoin_prune_pending_timer); + + /* + In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to + a + previously received join message with holdtime=0xFFFF. + */ + if (ch->t_ifjoin_expiry_timer) { + unsigned long remain = thread_timer_remain_second( + ch->t_ifjoin_expiry_timer); + if (remain > holdtime) { + /* + RFC 4601: 4.5.3. Receiving (S,G) Join/Prune + Messages + + Transitions from Join State + + The (S,G) downstream state machine on + interface I remains in + Join state, and the Expiry Timer (ET) is + restarted, set to + maximum of its current value and the HoldTime + from the + triggering Join/Prune message. + + Conclusion: Do not change the ET if the + current value is + higher than the received join holdtime. + */ + return; + } + } + THREAD_OFF(ch->t_ifjoin_expiry_timer); + break; + case PIM_IFJOIN_PRUNE: + if (source_flags & PIM_ENCODE_RPT_BIT) { + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_NOINFO); + THREAD_OFF(ch->t_ifjoin_expiry_timer); + delete_on_noinfo(ch); + return; + } else + pim_ifchannel_ifjoin_handler(ch, pim_ifp); + break; + case PIM_IFJOIN_PRUNE_PENDING: + /* + * Transitions from Prune-Pending State (Receive Join) + * RFC 7761 Sec 4.5.2: + * The (S,G) downstream state machine on interface I + * transitions to the Join state. The Prune-Pending Timer is + * canceled (without triggering an expiry event). The + * Expiry Timer (ET) is restarted and is then set to the + * maximum of its current value and the HoldTime from the + * triggering Join/Prune message. + */ + THREAD_OFF(ch->t_ifjoin_prune_pending_timer); + + /* Check if SGRpt join Received */ + if ((source_flags & PIM_ENCODE_RPT_BIT) && + !pim_addr_is_any(sg->src)) { + /* + * Transitions from Prune-Pending State (Rcv SGRpt Join) + * RFC 7761 Sec 4.5.3: + * The (S,G,rpt) downstream state machine on interface + * I transitions to the NoInfo state.The ET and PPT are + * cancelled. + */ + THREAD_OFF(ch->t_ifjoin_expiry_timer); + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_NOINFO); + return; + } + + pim_ifchannel_ifjoin_handler(ch, pim_ifp); + + if (ch->t_ifjoin_expiry_timer) { + unsigned long remain = thread_timer_remain_second( + ch->t_ifjoin_expiry_timer); + + if (remain > holdtime) + return; + } + THREAD_OFF(ch->t_ifjoin_expiry_timer); + + break; + case PIM_IFJOIN_PRUNE_TMP: + break; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + break; + } + + if (holdtime != 0xFFFF) { + thread_add_timer(router->master, on_ifjoin_expiry_timer, ch, + holdtime, &ch->t_ifjoin_expiry_timer); + } +} + +void pim_ifchannel_prune(struct interface *ifp, pim_addr upstream, + pim_sgaddr *sg, uint8_t source_flags, + uint16_t holdtime) +{ + struct pim_ifchannel *ch; + struct pim_interface *pim_ifp; + int jp_override_interval_msec; + + if (nonlocal_upstream(0 /* prune */, ifp, upstream, sg, source_flags, + holdtime)) { + return; + } + + ch = pim_ifchannel_find(ifp, sg); + if (!ch && !(source_flags & PIM_ENCODE_RPT_BIT)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: Received prune with no relevant ifchannel %s%pSG state: %d", + __func__, ifp->name, sg, + source_flags); + return; + } + + ch = pim_ifchannel_add(ifp, sg, source_flags, + PIM_UPSTREAM_FLAG_MASK_SRC_PIM); + + pim_ifp = ifp->info; + + switch (ch->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + if (source_flags & PIM_ENCODE_RPT_BIT) { + if (!(source_flags & PIM_ENCODE_WC_BIT)) + PIM_IF_FLAG_SET_S_G_RPT(ch->flags); + + ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; + if (listcount(pim_ifp->pim_neighbor_list) > 1) + jp_override_interval_msec = + pim_if_jp_override_interval_msec(ifp); + else + jp_override_interval_msec = + 0; /* schedule to expire immediately */ + /* If we called ifjoin_prune() directly instead, care + should + be taken not to use "ch" afterwards since it would be + deleted. */ + + THREAD_OFF(ch->t_ifjoin_prune_pending_timer); + THREAD_OFF(ch->t_ifjoin_expiry_timer); + thread_add_timer_msec( + router->master, on_ifjoin_prune_pending_timer, + ch, jp_override_interval_msec, + &ch->t_ifjoin_prune_pending_timer); + thread_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + } + break; + case PIM_IFJOIN_PRUNE_PENDING: + /* nothing to do */ + break; + case PIM_IFJOIN_JOIN: + /* + * The (S,G) downstream state machine on interface I + * transitions to the Prune-Pending state. The + * Prune-Pending Timer is started. It is set to the + * J/P_Override_Interval(I) if the router has more than one + * neighbor on that interface; otherwise, it is set to zero, + * causing it to expire immediately. + */ + + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_PRUNE_PENDING); + + if (listcount(pim_ifp->pim_neighbor_list) > 1) + jp_override_interval_msec = + pim_if_jp_override_interval_msec(ifp); + else + jp_override_interval_msec = + 0; /* schedule to expire immediately */ + /* If we called ifjoin_prune() directly instead, care should + be taken not to use "ch" afterwards since it would be + deleted. */ + THREAD_OFF(ch->t_ifjoin_prune_pending_timer); + thread_add_timer_msec(router->master, + on_ifjoin_prune_pending_timer, ch, + jp_override_interval_msec, + &ch->t_ifjoin_prune_pending_timer); + break; + case PIM_IFJOIN_PRUNE: + if (source_flags & PIM_ENCODE_RPT_BIT) { + THREAD_OFF(ch->t_ifjoin_prune_pending_timer); + /* + * While in Prune State, Receive SGRpt Prune. + * RFC 7761 Sec 4.5.3: + * The (S,G,rpt) downstream state machine on interface I + * remains in Prune state. The Expiry Timer (ET) is + * restarted and is then set to the maximum of its + * current value and the HoldTime from the triggering + * Join/Prune message. + */ + if (ch->t_ifjoin_expiry_timer) { + unsigned long rem = thread_timer_remain_second( + ch->t_ifjoin_expiry_timer); + + if (rem > holdtime) + return; + THREAD_OFF(ch->t_ifjoin_expiry_timer); + } + + thread_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + case PIM_IFJOIN_PRUNE_TMP: + if (source_flags & PIM_ENCODE_RPT_BIT) { + ch->ifjoin_state = PIM_IFJOIN_PRUNE; + THREAD_OFF(ch->t_ifjoin_expiry_timer); + thread_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (source_flags & PIM_ENCODE_RPT_BIT) { + ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; + THREAD_OFF(ch->t_ifjoin_expiry_timer); + thread_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + } +} + +int pim_ifchannel_local_membership_add(struct interface *ifp, pim_sgaddr *sg, + bool is_vxlan) +{ + struct pim_ifchannel *ch, *starch; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + int up_flags; + + /* PIM enabled on interface? */ + pim_ifp = ifp->info; + if (!pim_ifp) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s:%pSG Expected pim interface setup for %s", + __func__, sg, ifp->name); + return 0; + } + + if (!pim_ifp->pim_enable) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s:%pSG PIM is not configured on this interface %s", + __func__, sg, ifp->name); + return 0; + } + + pim = pim_ifp->pim; + + /* skip (*,G) ch creation if G is of type SSM */ + if (pim_addr_is_any(sg->src)) { + if (pim_is_grp_ssm(pim, sg->grp)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: local membership (S,G)=%pSG ignored as group is SSM", + __func__, sg); + return 1; + } + } + + /* vxlan term mroutes use ipmr-lo as local member to + * pull down multicast vxlan tunnel traffic + */ + up_flags = is_vxlan ? PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM : + PIM_UPSTREAM_FLAG_MASK_SRC_IGMP; + ch = pim_ifchannel_add(ifp, sg, 0, up_flags); + + ifmembership_set(ch, PIM_IFMEMBERSHIP_INCLUDE); + + if (pim_addr_is_any(sg->src)) { + struct pim_upstream *up = pim_upstream_find(pim, sg); + struct pim_upstream *child; + struct listnode *up_node; + + starch = ch; + + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s", + __FILE__, __func__, child->sg_str, + ifp->name, up->sg_str); + + if (!child->rpf.source_nexthop.interface) { + /* when iif unknown, do not inherit */ + if (PIM_DEBUG_EVENTS) + zlog_debug( + "Skipped (S,G)=%s(%s) from %s: no iif", + child->sg_str, ifp->name, + up->sg_str); + continue; + } + + ch = pim_ifchannel_find(ifp, &child->sg); + if (pim_upstream_evaluate_join_desired_interface( + child, ch, starch)) { + pim_channel_add_oif(child->channel_oil, ifp, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired(pim, child); + } + } + + if (pim->spt.switchover == PIM_SPT_INFINITY) { + if (pim->spt.plist) { + struct prefix_list *plist = prefix_list_lookup( + AFI_IP, pim->spt.plist); + struct prefix g; + + pim_addr_to_prefix(&g, up->sg.grp); + if (prefix_list_apply_ext(plist, NULL, &g, + true) == + PREFIX_DENY) { + pim_channel_add_oif( + up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, + __func__); + } + } + } else + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + } + + return 1; +} + +void pim_ifchannel_local_membership_del(struct interface *ifp, pim_sgaddr *sg) +{ + struct pim_ifchannel *starch, *ch, *orig; + struct pim_interface *pim_ifp; + + /* PIM enabled on interface? */ + pim_ifp = ifp->info; + if (!pim_ifp) + return; + if (!pim_ifp->pim_enable) + return; + + orig = ch = pim_ifchannel_find(ifp, sg); + if (!ch) + return; + ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); + + if (pim_addr_is_any(sg->src)) { + struct pim_upstream *up = pim_upstream_find(pim_ifp->pim, sg); + struct pim_upstream *child; + struct listnode *up_node, *up_nnode; + + starch = ch; + + for (ALL_LIST_ELEMENTS(up->sources, up_node, up_nnode, child)) { + struct channel_oil *c_oil = child->channel_oil; + struct pim_ifchannel *chchannel = + pim_ifchannel_find(ifp, &child->sg); + + pim_ifp = ifp->info; + + if (PIM_DEBUG_EVENTS) + zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s", + __FILE__, __func__, up->sg_str, + ifp->name, child->sg_str); + + ch = pim_ifchannel_find(ifp, &child->sg); + /* + * If the S,G has no if channel and the c_oil still + * has output here then the *,G was supplying the + * implied + * if channel. So remove it. + */ + if (!pim_upstream_evaluate_join_desired_interface( + child, ch, starch) || + (!chchannel && + oil_if_has(c_oil, pim_ifp->mroute_vif_index))) { + pim_channel_del_inherited_oif(c_oil, ifp, + __func__); + } + + /* Child node removal/ref count-- will happen as part of + * parent' delete_no_info */ + } + } + + /* Resettng the IGMP flags here */ + if (orig->upstream) + PIM_UPSTREAM_FLAG_UNSET_SRC_IGMP(orig->upstream->flags); + + PIM_IF_FLAG_UNSET_PROTO_IGMP(orig->flags); + + delete_on_noinfo(orig); +} + +void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch) +{ + int old_couldassert = + PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)); + int new_couldassert = + PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch)); + + if (new_couldassert == old_couldassert) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: CouldAssert(%pPAs,%pPAs,%s) changed from %d to %d", + __func__, &ch->sg.src, &ch->sg.grp, + ch->interface->name, old_couldassert, + new_couldassert); + + if (new_couldassert) { + /* CouldAssert(S,G,I) switched from false to true */ + PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); + } else { + /* CouldAssert(S,G,I) switched from true to false */ + PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); + + if (ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER) { + assert_action_a4(ch); + } + } + + pim_ifchannel_update_my_assert_metric(ch); +} + +/* + my_assert_metric may be affected by: + + CouldAssert(S,G) + pim_ifp->primary_address + rpf->source_nexthop.mrib_metric_preference; + rpf->source_nexthop.mrib_route_metric; + */ +void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch) +{ + struct pim_assert_metric my_metric_new = + pim_macro_ch_my_assert_metric_eval(ch); + + if (pim_assert_metric_match(&my_metric_new, &ch->ifassert_my_metric)) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "%s: my_assert_metric(%pPAs,%pPAs,%s) changed from %u,%u,%u,%pPAs to %u,%u,%u,%pPAs", + __func__, &ch->sg.src, &ch->sg.grp, ch->interface->name, + ch->ifassert_my_metric.rpt_bit_flag, + ch->ifassert_my_metric.metric_preference, + ch->ifassert_my_metric.route_metric, + &ch->ifassert_my_metric.ip_address, + my_metric_new.rpt_bit_flag, + my_metric_new.metric_preference, + my_metric_new.route_metric, &my_metric_new.ip_address); + + ch->ifassert_my_metric = my_metric_new; + + if (pim_assert_metric_better(&ch->ifassert_my_metric, + &ch->ifassert_winner_metric)) { + assert_action_a5(ch); + } +} + +void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch) +{ + int old_atd = PIM_FORCE_BOOLEAN( + PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags)); + int new_atd = + PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch)); + + if (new_atd == old_atd) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "%s: AssertTrackingDesired(%pPAs,%pPAs,%s) changed from %d to %d", + __func__, &ch->sg.src, &ch->sg.grp, ch->interface->name, + old_atd, new_atd); + + if (new_atd) { + /* AssertTrackingDesired(S,G,I) switched from false to true */ + PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); + } else { + /* AssertTrackingDesired(S,G,I) switched from true to false */ + PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); + + if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + assert_action_a5(ch); + } + } +} + +/* + * If we have a new pim interface, check to + * see if any of the pre-existing channels have + * their upstream out that way and turn on forwarding + * for that ifchannel then. + */ +void pim_ifchannel_scan_forward_start(struct interface *new_ifp) +{ + struct pim_interface *new_pim_ifp = new_ifp->info; + struct pim_instance *pim = new_pim_ifp->pim; + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *loop_pim_ifp = ifp->info; + struct pim_ifchannel *ch; + + if (!loop_pim_ifp) + continue; + + if (new_pim_ifp == loop_pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &loop_pim_ifp->ifchannel_rb) { + if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { + struct pim_upstream *up = ch->upstream; + if ((!up->channel_oil) + && (up->rpf.source_nexthop + .interface == new_ifp)) + pim_forward_start(ch); + } + } + } +} + +/* + * Downstream per-interface (S,G,rpt) state machine + * states that we need to move (S,G,rpt) items + * into different states at the start of the + * reception of a *,G join as well, when + * we get End of Message + */ +void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel *ch, int eom, + uint8_t join) +{ + bool send_upstream_starg = false; + struct pim_ifchannel *child; + struct listnode *ch_node, *nch_node; + struct pim_instance *pim = + ((struct pim_interface *)ch->interface->info)->pim; + struct pim_upstream *starup = ch->upstream; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: %s %s eom: %d join %u", __func__, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), + ch->sg_str, eom, join); + if (!ch->sources) + return; + + for (ALL_LIST_ELEMENTS(ch->sources, ch_node, nch_node, child)) { + if (!PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) + continue; + + switch (child->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + case PIM_IFJOIN_JOIN: + break; + case PIM_IFJOIN_PRUNE: + if (!eom) + child->ifjoin_state = PIM_IFJOIN_PRUNE_TMP; + break; + case PIM_IFJOIN_PRUNE_PENDING: + if (!eom) + child->ifjoin_state = + PIM_IFJOIN_PRUNE_PENDING_TMP; + break; + case PIM_IFJOIN_PRUNE_TMP: + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (!eom) + break; + + 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; + + if ((I_am_RP(pim, child->sg.grp)) && + (!pim_upstream_empty_inherited_olist( + child->upstream))) { + pim_channel_add_oif( + child->upstream->channel_oil, + ch->interface, PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired(pim, + child->upstream); + } + send_upstream_starg = true; + + delete_on_noinfo(child); + break; + } + } + + if (send_upstream_starg) + pim_jp_agg_single_upstream_send(&starup->rpf, starup, true); +} |