[pim] Initial pim 0.155
diff --git a/pimd/pim_ifchannel.c b/pimd/pim_ifchannel.c
new file mode 100644
index 0000000..7f946cf
--- /dev/null
+++ b/pimd/pim_ifchannel.c
@@ -0,0 +1,893 @@
+/*
+  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 "linklist.h"
+#include "thread.h"
+#include "memory.h"
+
+#include "pimd.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"
+
+void pim_ifchannel_free(struct pim_ifchannel *ch)
+{
+  zassert(!ch->t_ifjoin_expiry_timer);
+  zassert(!ch->t_ifjoin_prune_pending_timer);
+  zassert(!ch->t_ifassert_timer);
+
+  XFREE(MTYPE_PIM_IFCHANNEL, ch);
+}
+
+void pim_ifchannel_delete(struct pim_ifchannel *ch)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ch->interface->info;
+  zassert(pim_ifp);
+
+  if (ch->ifjoin_state != PIM_IFJOIN_NOINFO) {
+    pim_upstream_update_join_desired(ch->upstream);
+  }
+
+  pim_upstream_del(ch->upstream);
+
+  THREAD_OFF(ch->t_ifjoin_expiry_timer);
+  THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+  THREAD_OFF(ch->t_ifassert_timer);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into pim_ifchannel_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(pim_ifp->pim_ifchannel_list, ch);
+
+  pim_ifchannel_free(ch);
+}
+
+#define IFCHANNEL_NOINFO(ch)					\
+  (								\
+   ((ch)->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO)	\
+   &&								\
+   ((ch)->ifjoin_state == PIM_IFJOIN_NOINFO)			\
+   &&								\
+   ((ch)->ifassert_state == PIM_IFASSERT_NOINFO)		\
+   )
+   
+static void delete_on_noinfo(struct pim_ifchannel *ch)
+{
+  if (IFCHANNEL_NOINFO(ch)) {
+
+    /* In NOINFO state, timers should have been cleared */
+    zassert(!ch->t_ifjoin_expiry_timer);
+    zassert(!ch->t_ifjoin_prune_pending_timer);
+    zassert(!ch->t_ifassert_timer);
+    
+    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;
+
+  if (old_state == new_state) {
+    zlog_debug("%s calledby %s: non-transition on state %d (%s)",
+	       __PRETTY_FUNCTION__, caller, new_state,
+	       pim_ifchannel_ifjoin_name(new_state));
+    return;
+  }
+
+  zassert(old_state != new_state);
+
+  ch->ifjoin_state = new_state;
+
+  /* Transition to/from NOINFO ? */
+  if (
+      (old_state == PIM_IFJOIN_NOINFO)
+      ||
+      (new_state == PIM_IFJOIN_NOINFO)
+      ) {
+
+    if (PIM_DEBUG_PIM_EVENTS) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("PIM_IFCHANNEL_%s: (S,G)=(%s,%s) on interface %s",
+		 ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" : "UP"),
+		 src_str, grp_str, ch->interface->name);
+    }
+
+    /*
+      Record uptime of state transition to/from NOINFO
+    */
+    ch->ifjoin_creation = pim_time_monotonic_sec();
+
+    pim_upstream_update_join_desired(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)
+{
+  switch (ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:        return "NOINFO";
+  case PIM_IFJOIN_JOIN:          return "JOIN";
+  case PIM_IFJOIN_PRUNE_PENDING: return "PRUNEP";
+  }
+
+  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,
+			  qpim_inaddr_any,
+			  qpim_infinite_assert_metric);
+}
+
+static struct pim_ifchannel *pim_ifchannel_new(struct interface *ifp,
+					       struct in_addr source_addr,
+					       struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+  struct pim_upstream  *up;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  up = pim_upstream_add(source_addr, group_addr);
+  if (!up) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_err("%s: could not attach upstream (S,G)=(%s,%s) on interface %s",
+	     __PRETTY_FUNCTION__,
+	     src_str, grp_str, ifp->name);
+    return 0;
+  }
+
+  ch = XMALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch));
+  if (!ch) {
+    zlog_err("%s: PIM XMALLOC(%d) failure",
+	     __PRETTY_FUNCTION__, sizeof(*ch));
+    return 0;
+  }
+
+  ch->flags                        = 0;
+  ch->upstream                     = up;
+  ch->interface                    = ifp;
+  ch->source_addr                  = source_addr;
+  ch->group_addr                   = group_addr;
+  ch->local_ifmembership           = PIM_IFMEMBERSHIP_NOINFO;
+
+  ch->ifjoin_state                 = PIM_IFJOIN_NOINFO;
+  ch->t_ifjoin_expiry_timer        = 0;
+  ch->t_ifjoin_prune_pending_timer = 0;
+  ch->ifjoin_creation              = 0;
+
+  /* Assert state */
+  ch->t_ifassert_timer   = 0;
+  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);
+
+  ch->ifassert_my_metric = pim_macro_ch_my_assert_metric_eval(ch);
+
+  /* Attach to list */
+  listnode_add(pim_ifp->pim_ifchannel_list, ch);
+
+  zassert(IFCHANNEL_NOINFO(ch));
+
+  return ch;
+}
+
+struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp,
+					 struct in_addr source_addr,
+					 struct in_addr group_addr)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *ch_node;
+  struct pim_ifchannel *ch;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str,
+	      ifp->name);
+    return 0;
+  }
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    if (
+	(source_addr.s_addr == ch->source_addr.s_addr) &&
+	(group_addr.s_addr == ch->group_addr.s_addr)
+	) {
+      return ch;
+    }
+  }
+
+  return 0;
+}
+
+static void ifmembership_set(struct pim_ifchannel *ch,
+			     enum pim_ifmembership membership)
+{
+  if (ch->local_ifmembership == membership)
+    return;
+
+  {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+    zlog_info("%s: (S,G)=(%s,%s) membership now is %s on interface %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str,
+	      membership == PIM_IFMEMBERSHIP_INCLUDE ? "INCLUDE" : "NOINFO",
+	      ch->interface->name);
+  }
+  
+  ch->local_ifmembership = membership;
+
+  pim_upstream_update_join_desired(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 listnode      *ch_node;
+  struct pim_ifchannel *ch;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO);
+  }
+}
+
+void pim_ifchannel_delete_on_noinfo(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *node;
+  struct listnode      *next_node;
+  struct pim_ifchannel *ch;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+    delete_on_noinfo(ch);
+  }
+}
+
+struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  char src_str[100];
+  char grp_str[100];
+
+  ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+  if (ch)
+    return ch;
+
+  ch = pim_ifchannel_new(ifp, source_addr, group_addr);
+  if (ch)
+    return ch;
+    
+  pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+  pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+  zlog_warn("%s: pim_ifchannel_new() failure for (S,G)=(%s,%s) on interface %s",
+	    __PRETTY_FUNCTION__,
+	    src_str, grp_str, ifp->name);
+
+  return 0;
+}
+
+static void ifjoin_to_noinfo(struct pim_ifchannel *ch)
+{
+  pim_forward_stop(ch);
+  pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO);
+  delete_on_noinfo(ch);
+}
+
+static int on_ifjoin_expiry_timer(struct thread *t)
+{
+  struct pim_ifchannel *ch;
+
+  zassert(t);
+  ch = THREAD_ARG(t);
+  zassert(ch);
+
+  ch->t_ifjoin_expiry_timer = 0;
+
+  zassert(ch->ifjoin_state == PIM_IFJOIN_JOIN);
+
+  ifjoin_to_noinfo(ch);
+  /* ch may have been deleted */
+
+  return 0;
+}
+
+static void prune_echo(struct interface *ifp,
+		       struct in_addr source_addr,
+		       struct in_addr group_addr)
+{
+  struct pim_interface *pim_ifp;
+  struct in_addr neigh_dst_addr;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  neigh_dst_addr = pim_ifp->primary_address;
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char source_str[100];
+    char group_str[100];
+    char neigh_dst_str[100];
+    pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<neigh?>", neigh_dst_addr, neigh_dst_str, sizeof(neigh_dst_str));
+    zlog_debug("%s: sending PruneEcho(S,G)=(%s,%s) to upstream=%s on interface %s",
+	       __PRETTY_FUNCTION__, source_str, group_str, neigh_dst_str, ifp->name);
+  }
+
+  pim_joinprune_send(ifp, neigh_dst_addr, source_addr, group_addr,
+		     0 /* boolean: send_join=false (prune) */);
+}
+
+static int 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;
+  struct in_addr ch_source;
+  struct in_addr ch_group;
+
+  zassert(t);
+  ch = THREAD_ARG(t);
+  zassert(ch);
+
+  ch->t_ifjoin_prune_pending_timer = 0;
+
+  zassert(ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING);
+
+  /* Send PruneEcho(S,G) ? */
+  ifp = ch->interface;
+  pim_ifp = ifp->info;
+  send_prune_echo = (listcount(pim_ifp->pim_neighbor_list) > 1);
+
+  /* Save (S,G) */
+  ch_source = ch->source_addr;
+  ch_group = ch->group_addr;
+
+  ifjoin_to_noinfo(ch);
+  /* from here ch may have been deleted */
+
+  if (send_prune_echo)
+    prune_echo(ifp, ch_source, ch_group);
+
+  return 0;
+}
+
+static void check_recv_upstream(int is_join,
+				struct interface *recv_ifp,
+				struct in_addr upstream,
+				struct in_addr source_addr,
+				struct in_addr group_addr,
+				uint8_t source_flags,
+				int holdtime)
+{
+  struct pim_upstream *up;
+
+  /* Upstream (S,G) in Joined state ? */
+  up = pim_upstream_find(source_addr, group_addr);
+  if (!up)
+    return;
+  if (up->join_state != PIM_UPSTREAM_JOINED)
+    return;
+
+  /* Upstream (S,G) in Joined state */
+
+  if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+    /* RPF'(S,G) not found */
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s %s: RPF'(%s,%s) not found",
+	      __FILE__, __PRETTY_FUNCTION__, 
+	      src_str, grp_str);
+    return;
+  }
+
+  /* upstream directed to RPF'(S,G) ? */
+  if (upstream.s_addr != up->rpf.rpf_addr.s_addr) {
+    char src_str[100];
+    char grp_str[100];
+    char up_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<up?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_warn("%s %s: (S,G)=(%s,%s) upstream=%s not directed to RPF'(S,G)=%s on interface %s",
+	      __FILE__, __PRETTY_FUNCTION__, 
+	      src_str, grp_str,
+	      up_str, rpf_str, 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, up->rpf.rpf_addr);
+      return;
+    }
+
+    /* Prune(S,G,rpt) to RPF'(S,G) */
+    pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
+						   up, up->rpf.rpf_addr);
+    return;
+  }
+
+  /* Prune(S,G) to RPF'(S,G) */
+  pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up,
+						 up->rpf.rpf_addr);
+}
+
+static int nonlocal_upstream(int is_join,
+			     struct interface *recv_ifp,
+			     struct in_addr upstream,
+			     struct in_addr source_addr,
+			     struct in_addr group_addr,
+			     uint8_t source_flags,
+			     uint16_t holdtime)
+{
+  struct pim_interface *recv_pim_ifp;
+  int is_local; /* boolean */
+
+  recv_pim_ifp = recv_ifp->info;
+  zassert(recv_pim_ifp);
+
+  is_local = (upstream.s_addr == recv_pim_ifp->primary_address.s_addr);
+  
+  if (PIM_DEBUG_PIM_TRACE) {
+    char up_str[100];
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<upstream?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s: recv %s (S,G)=(%s,%s) to %s upstream=%s on %s",
+	      __PRETTY_FUNCTION__,
+	      is_join ? "join" : "prune",
+	      src_str, grp_str,
+	      is_local ? "local" : "non-local",
+	      up_str, recv_ifp->name);
+  }
+
+  if (is_local)
+    return 0;
+
+  /*
+    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, source_addr, group_addr,
+		      source_flags, holdtime);
+
+  return 1; /* non-local */
+}
+
+void pim_ifchannel_join_add(struct interface *ifp,
+			    struct in_addr neigh_addr,
+			    struct in_addr upstream,
+			    struct in_addr source_addr,
+			    struct in_addr group_addr,
+			    uint8_t source_flags,
+			    uint16_t holdtime)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  if (nonlocal_upstream(1 /* join */, ifp, upstream,
+			source_addr, group_addr, source_flags, holdtime)) {
+    return;
+  }
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  /*
+    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) {
+    char src_str[100];
+    char grp_str[100];
+    char neigh_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
+    zlog_warn("%s: Assert Loser recv Join(%s,%s) from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str, neigh_str, ifp->name);
+
+    assert_action_a5(ch);
+  }
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  switch (ch->ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:
+    pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+    if (pim_macro_chisin_oiflist(ch)) {
+      pim_forward_start(ch);
+    }
+    break;
+  case PIM_IFJOIN_JOIN:
+    zassert(!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_PENDING:
+    zassert(!ch->t_ifjoin_expiry_timer);
+    zassert(ch->t_ifjoin_prune_pending_timer);
+    THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+    pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+    break;
+  }
+
+  zassert(!IFCHANNEL_NOINFO(ch));
+
+  if (holdtime != 0xFFFF) {
+    THREAD_TIMER_ON(master, ch->t_ifjoin_expiry_timer,
+		    on_ifjoin_expiry_timer,
+		    ch, holdtime);
+  }
+}
+
+void pim_ifchannel_prune(struct interface *ifp,
+			 struct in_addr upstream,
+			 struct in_addr source_addr,
+			 struct in_addr group_addr,
+			 uint8_t source_flags,
+			 uint16_t holdtime)
+{
+  struct pim_ifchannel *ch;
+  int jp_override_interval_msec;
+
+  if (nonlocal_upstream(0 /* prune */, ifp, upstream,
+			source_addr, group_addr, source_flags, holdtime)) {
+    return;
+  }
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  switch (ch->ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:
+  case PIM_IFJOIN_PRUNE_PENDING:
+    /* nothing to do */
+    break;
+  case PIM_IFJOIN_JOIN:
+    {
+      struct pim_interface *pim_ifp;
+
+      pim_ifp = ifp->info;
+
+      zassert(ch->t_ifjoin_expiry_timer);
+      zassert(!ch->t_ifjoin_prune_pending_timer);
+
+      THREAD_OFF(ch->t_ifjoin_expiry_timer);
+      
+      pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, 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_TIMER_MSEC_ON(master, ch->t_ifjoin_prune_pending_timer,
+			   on_ifjoin_prune_pending_timer,
+			   ch, jp_override_interval_msec);
+      
+      zassert(!ch->t_ifjoin_expiry_timer);
+      zassert(ch->t_ifjoin_prune_pending_timer);
+    }
+    break;
+  }
+
+}
+
+void pim_ifchannel_local_membership_add(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+
+  /* PIM enabled on interface? */
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+  if (!PIM_IF_TEST_PIM(pim_ifp->options))
+    return;
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch) {
+    return;
+  }
+
+  ifmembership_set(ch, PIM_IFMEMBERSHIP_INCLUDE);
+
+  zassert(!IFCHANNEL_NOINFO(ch));
+}
+
+void pim_ifchannel_local_membership_del(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+
+  /* PIM enabled on interface? */
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+  if (!PIM_IF_TEST_PIM(pim_ifp->options))
+    return;
+
+  ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO);
+
+  delete_on_noinfo(ch);
+}
+
+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;
+
+  {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+    zlog_info("%s: CouldAssert(%s,%s,%s) changed from %d to %d",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str, 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;
+
+  {
+    char src_str[100];
+    char grp_str[100];
+    char old_addr_str[100];
+    char new_addr_str[100];
+    pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<old_addr?>", ch->ifassert_my_metric.ip_address, old_addr_str, sizeof(old_addr_str));
+    pim_inet4_dump("<new_addr?>", my_metric_new.ip_address, new_addr_str, sizeof(new_addr_str));
+    zlog_info("%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str, ch->interface->name,
+	      ch->ifassert_my_metric.rpt_bit_flag,
+	      ch->ifassert_my_metric.metric_preference,
+	      ch->ifassert_my_metric.route_metric,
+	      old_addr_str,
+	      my_metric_new.rpt_bit_flag,
+	      my_metric_new.metric_preference,
+	      my_metric_new.route_metric,
+	      new_addr_str);
+  }
+
+  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;
+
+  {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+    zlog_info("%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str, 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);
+    }
+  }
+}