[pim] Initial pim 0.155
diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c
new file mode 100644
index 0000000..b9cf1e5
--- /dev/null
+++ b/pimd/pim_upstream.c
@@ -0,0 +1,686 @@
+/*
+  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
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "zebra/rib.h"
+
+#include "log.h"
+#include "zclient.h"
+#include "memory.h"
+#include "thread.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_join.h"
+#include "pim_zlookup.h"
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+#include "pim_neighbor.h"
+#include "pim_rpf.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+#include "pim_macro.h"
+
+static void join_timer_start(struct pim_upstream *up);
+static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up);
+
+void pim_upstream_free(struct pim_upstream *up)
+{
+  XFREE(MTYPE_PIM_UPSTREAM, up);
+}
+
+static void upstream_channel_oil_detach(struct pim_upstream *up)
+{
+  if (up->channel_oil) {
+    pim_channel_oil_del(up->channel_oil);
+    up->channel_oil = 0;
+  }
+}
+
+void pim_upstream_delete(struct pim_upstream *up)
+{
+  THREAD_OFF(up->t_join_timer);
+
+  upstream_channel_oil_detach(up);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into pim_upstream_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(qpim_upstream_list, up);
+
+  pim_upstream_free(up);
+}
+
+static void send_join(struct pim_upstream *up)
+{
+  zassert(up->join_state == PIM_UPSTREAM_JOINED);
+
+  
+  if (PIM_DEBUG_PIM_TRACE) {
+    if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+      char src_str[100];
+      char grp_str[100];
+      char rpf_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+      zlog_warn("%s: can't send join upstream: RPF'(%s,%s)=%s",
+		__PRETTY_FUNCTION__,
+		src_str, grp_str, rpf_str);
+      /* warning only */
+    }
+  }
+  
+  /* send Join(S,G) to the current upstream neighbor */
+  pim_joinprune_send(up->rpf.source_nexthop.interface,
+  		     up->rpf.rpf_addr,
+		     up->source_addr,
+		     up->group_addr,
+		     1 /* join */);
+}
+
+static int on_join_timer(struct thread *t)
+{
+  struct pim_upstream *up;
+
+  zassert(t);
+  up = THREAD_ARG(t);
+  zassert(up);
+
+  send_join(up);
+
+  up->t_join_timer = 0;
+  join_timer_start(up);
+
+  return 0;
+}
+
+static void join_timer_start(struct pim_upstream *up)
+{
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: starting %d sec timer for upstream (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       qpim_t_periodic,
+	       src_str, grp_str);
+  }
+
+  zassert(!up->t_join_timer);
+
+  THREAD_TIMER_ON(master, up->t_join_timer,
+		  on_join_timer,
+		  up, qpim_t_periodic);
+}
+
+void pim_upstream_join_timer_restart(struct pim_upstream *up)
+{
+  THREAD_OFF(up->t_join_timer);
+  join_timer_start(up);
+}
+
+static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up,
+						 int interval_msec)
+{
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: restarting %d msec timer for upstream (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       interval_msec,
+	       src_str, grp_str);
+  }
+
+  THREAD_OFF(up->t_join_timer);
+  THREAD_TIMER_MSEC_ON(master, up->t_join_timer,
+		       on_join_timer,
+		       up, interval_msec);
+}
+
+void pim_upstream_join_suppress(struct pim_upstream *up,
+				struct in_addr rpf_addr,
+				int holdtime)
+{
+  long t_joinsuppress_msec;
+  long join_timer_remain_msec;
+
+  t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface),
+			    1000 * holdtime);
+
+  join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_debug("%s %s: detected Join(%s,%s) to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec",
+	       __FILE__, __PRETTY_FUNCTION__, 
+	       src_str, grp_str,
+	       rpf_str,
+	       join_timer_remain_msec, t_joinsuppress_msec);
+  }
+
+  if (join_timer_remain_msec < t_joinsuppress_msec) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("%s %s: suppressing Join(S,G)=(%s,%s) for %ld msec",
+		 __FILE__, __PRETTY_FUNCTION__, 
+		 src_str, grp_str, t_joinsuppress_msec);
+    }
+
+    pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec);
+  }
+}
+
+void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
+						    struct pim_upstream *up,
+						    struct in_addr rpf_addr)
+{
+  long join_timer_remain_msec;
+  int t_override_msec;
+
+  join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+  t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_debug("%s: to RPF'(%s,%s)=%s: join_timer=%ld msec t_override=%d msec",
+	       debug_label,
+	       src_str, grp_str, rpf_str,
+	       join_timer_remain_msec, t_override_msec);
+  }
+    
+  if (join_timer_remain_msec > t_override_msec) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("%s: decreasing (S,G)=(%s,%s) join timer to t_override=%d msec",
+		 debug_label,
+		 src_str, grp_str,
+		 t_override_msec);
+    }
+
+    pim_upstream_join_timer_restart_msec(up, t_override_msec);
+  }
+}
+
+static void forward_on(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+  struct in_addr        ifaddr;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      if (pim_macro_chisin_oiflist(ch))
+	pim_forward_start(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+static void forward_off(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      pim_forward_stop(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+static void pim_upstream_switch(struct pim_upstream *up,
+				enum pim_upstream_state new_state)
+{
+  enum pim_upstream_state old_state = up->join_state;
+
+  zassert(old_state != new_state);
+  
+  up->join_state       = new_state;
+  up->state_transition = pim_time_monotonic_sec();
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: PIM_UPSTREAM_%s: (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       ((new_state == PIM_UPSTREAM_JOINED) ? "JOINED" : "NOTJOINED"),
+	       src_str, grp_str);
+  }
+
+  pim_upstream_update_assert_tracking_desired(up);
+
+  if (new_state == PIM_UPSTREAM_JOINED) {
+    forward_on(up);
+    send_join(up);
+    join_timer_start(up);
+  }
+  else {
+    forward_off(up);
+    pim_joinprune_send(up->rpf.source_nexthop.interface,
+		       up->rpf.rpf_addr,
+		       up->source_addr,
+		       up->group_addr,
+		       0 /* prune */);
+    zassert(up->t_join_timer);
+    THREAD_OFF(up->t_join_timer);
+  }
+
+}
+
+static struct pim_upstream *pim_upstream_new(struct in_addr source_addr,
+					     struct in_addr group_addr)
+{
+  struct pim_upstream *up;
+
+  up = XMALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up));
+  if (!up) {
+    zlog_err("%s: PIM XMALLOC(%d) failure",
+	     __PRETTY_FUNCTION__, sizeof(*up));
+    return 0;
+  }
+  
+  up->source_addr                = source_addr;
+  up->group_addr                 = group_addr;
+  up->flags                      = 0;
+  up->ref_count                  = 1;
+  up->t_join_timer               = 0;
+  up->join_state                 = 0;
+  up->state_transition           = pim_time_monotonic_sec();
+  up->channel_oil                = 0;
+
+  up->rpf.source_nexthop.interface                = 0;
+  up->rpf.source_nexthop.mrib_nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
+  up->rpf.source_nexthop.mrib_metric_preference   = qpim_infinite_assert_metric.metric_preference;
+  up->rpf.source_nexthop.mrib_route_metric        = qpim_infinite_assert_metric.route_metric;
+  up->rpf.rpf_addr.s_addr                         = PIM_NET_INADDR_ANY;
+
+  pim_rpf_update(up, 0);
+
+  listnode_add(qpim_upstream_list, up);
+
+  return up;
+}
+
+struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
+				       struct in_addr group_addr)
+{
+  struct listnode     *up_node;
+  struct pim_upstream *up;
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
+    if (
+	(source_addr.s_addr == up->source_addr.s_addr) &&
+	(group_addr.s_addr == up->group_addr.s_addr)
+	) {
+      return up;
+    }
+  }
+
+  return 0;
+}
+
+struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
+				      struct in_addr group_addr)
+{
+  struct pim_upstream *up;
+
+  up = pim_upstream_find(source_addr, group_addr);
+  if (up) {
+    ++up->ref_count;
+  }
+  else {
+    up = pim_upstream_new(source_addr, group_addr);
+  }
+
+  return up;
+}
+
+void pim_upstream_del(struct pim_upstream *up)
+{
+  --up->ref_count;
+
+  if (up->ref_count < 1) {
+    pim_upstream_delete(up);
+  }
+}
+
+/*
+  Evaluate JoinDesired(S,G):
+
+  JoinDesired(S,G) is true if there is a downstream (S,G) interface I
+  in the set:
+
+  inherited_olist(S,G) =
+  joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+  JoinDesired(S,G) may be affected by changes in the following:
+
+  pim_ifp->primary_address
+  pim_ifp->pim_dr_addr
+  ch->ifassert_winner_metric
+  ch->ifassert_winner
+  ch->local_ifmembership 
+  ch->ifjoin_state
+  ch->upstream->rpf.source_nexthop.mrib_metric_preference
+  ch->upstream->rpf.source_nexthop.mrib_route_metric
+  ch->upstream->rpf.source_nexthop.interface
+
+  See also pim_upstream_update_join_desired() below.
+ */
+int pim_upstream_evaluate_join_desired(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+      if (ch->upstream != up)
+	continue;
+
+      if (pim_macro_ch_lost_assert(ch))
+	continue; /* keep searching */
+
+      if (pim_macro_chisin_joins_or_include(ch))
+	return 1; /* true */
+    } /* scan iface channel list */
+  } /* scan iflist */
+
+  return 0; /* false */
+}
+
+/*
+  See also pim_upstream_evaluate_join_desired() above.
+*/
+void pim_upstream_update_join_desired(struct pim_upstream *up)
+{
+  int was_join_desired; /* boolean */
+  int is_join_desired; /* boolean */
+
+  was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags);
+
+  is_join_desired = pim_upstream_evaluate_join_desired(up);
+  if (is_join_desired)
+    PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags);
+  else
+    PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags);
+
+  /* switched from false to true */
+  if (is_join_desired && !was_join_desired) {
+    zassert(up->join_state == PIM_UPSTREAM_NOTJOINED);
+    pim_upstream_switch(up, PIM_UPSTREAM_JOINED);
+    return;
+  }
+      
+  /* switched from true to false */
+  if (!is_join_desired && was_join_desired) {
+    zassert(up->join_state == PIM_UPSTREAM_JOINED);
+    pim_upstream_switch(up, PIM_UPSTREAM_NOTJOINED);
+    return;
+  }
+}
+
+/*
+  RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
+  Transitions from Joined State
+  RPF'(S,G) GenID changes
+
+  The upstream (S,G) state machine remains in Joined state.  If the
+  Join Timer is set to expire in more than t_override seconds, reset
+  it so that it expires after t_override seconds.
+*/
+void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr)
+{
+  struct listnode     *up_node;
+  struct listnode     *up_nextnode;
+  struct pim_upstream *up;
+
+  /*
+    Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr
+  */
+  for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
+
+    if (PIM_DEBUG_PIM_TRACE) {
+      char neigh_str[100];
+      char src_str[100];
+      char grp_str[100];
+      char rpf_addr_str[100];
+      pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+      zlog_debug("%s: matching neigh=%s against upstream (S,G)=(%s,%s) joined=%d rpf_addr=%s",
+		 __PRETTY_FUNCTION__,
+		 neigh_str, src_str, grp_str,
+		 up->join_state == PIM_UPSTREAM_JOINED,
+		 rpf_addr_str);
+    }
+
+    /* consider only (S,G) upstream in Joined state */
+    if (up->join_state != PIM_UPSTREAM_JOINED)
+      continue;
+
+    /* match RPF'(S,G)=neigh_addr */
+    if (up->rpf.rpf_addr.s_addr != neigh_addr.s_addr)
+      continue;
+
+    pim_upstream_join_timer_decrease_to_t_override("RPF'(S,G) GenID change",
+						   up, neigh_addr);
+  }
+}
+
+
+void pim_upstream_rpf_interface_changed(struct pim_upstream *up,
+					struct interface *old_rpf_ifp)
+{
+  struct listnode  *ifnode;
+  struct listnode  *ifnextnode;
+  struct interface *ifp;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    struct listnode      *chnode;
+    struct listnode      *chnextnode;
+    struct pim_ifchannel *ch;
+    struct pim_interface *pim_ifp;
+
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* search all ifchannels */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+      if (ch->upstream != up)
+	continue;
+
+      if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+	if (
+	    /* RPF_interface(S) was NOT I */
+	    (old_rpf_ifp == ch->interface)
+	    &&
+	    /* RPF_interface(S) stopped being I */
+	    (ch->upstream->rpf.source_nexthop.interface != ch->interface)
+	    ) {
+	  assert_action_a5(ch);
+	}
+      } /* PIM_IFASSERT_I_AM_LOSER */
+
+      pim_ifchannel_update_assert_tracking_desired(ch);
+    }
+  }
+}
+
+void pim_upstream_update_could_assert(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      pim_ifchannel_update_could_assert(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+void pim_upstream_update_my_assert_metric(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      pim_ifchannel_update_my_assert_metric(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      pim_ifchannel_update_assert_tracking_desired(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}