/*      $NetBSD: prune.c,v 1.18 2009/04/17 16:05:43 lukem Exp $ */

/*
* The mrouted program is covered by the license in the accompanying file
* named "LICENSE".  Use of the mrouted program represents acceptance of
* the terms and conditions listed in that file.
*
* The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
* Leland Stanford Junior University.
*/


#include "defs.h"

extern int cache_lifetime;
extern int max_prune_lifetime;
extern struct rtentry *routing_table;

extern int phys_vif;

/*
* dither cache lifetime to obtain a value between x and 2*x
*/
#define CACHE_LIFETIME(x) ((x) + (arc4random() % (x)))

#define CHK_GS(x, y) {  \
               switch(x) { \
                       case 2: \
                       case 4: \
                       case 8: \
                       case 16: \
                       case 32: \
                       case 64: \
                       case 128: \
                       /*case 256:*/ y = 1; \
                                 break; \
                       default:  y = 0; \
               } \
       }

struct gtable *kernel_table;            /* ptr to list of kernel grp entries*/
static struct gtable *kernel_no_route;  /* list of grp entries w/o routes   */
struct gtable *gtp;                     /* pointer for kernel rt entries    */
unsigned int kroutes;                   /* current number of cache entries  */

/****************************************************************************
                      Functions that are local to prune.c
****************************************************************************/
static void             prun_add_ttls(struct gtable *gt);
static int              pruning_neighbor(vifi_t vifi, u_int32_t addr);
static int              can_mtrace(vifi_t vifi, u_int32_t addr);
static struct ptable *  find_prune_entry(u_int32_t vr, struct ptable *pt);
static void             expire_prune(vifi_t vifi, struct gtable *gt);
static void             send_prune(struct gtable *gt);
static void             send_graft(struct gtable *gt);
static void             send_graft_ack(u_int32_t src, u_int32_t dst,
                                      u_int32_t origin, u_int32_t grp);
static void             update_kernel(struct gtable *g);
static const char *     scaletime(u_long t);

/*
* Updates the ttl values for each vif.
*/
static void
prun_add_ttls(struct gtable *gt)
{
   struct uvif *v;
   vifi_t vifi;

   for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) {
       if (VIFM_ISSET(vifi, gt->gt_grpmems))
           gt->gt_ttls[vifi] = v->uv_threshold;
       else
           gt->gt_ttls[vifi] = 0;
   }
}

/*
* checks for scoped multicast addresses
*/
#define GET_SCOPE(gt) { \
       vifi_t _i; \
       if ((ntohl((gt)->gt_mcastgrp) & 0xff000000) == 0xef000000) \
           for (_i = 0; _i < numvifs; _i++) \
               if (scoped_addr(_i, (gt)->gt_mcastgrp)) \
                   VIFM_SET(_i, (gt)->gt_scope); \
       }

int
scoped_addr(vifi_t vifi, u_int32_t addr)
{
   struct vif_acl *acl;

   for (acl = uvifs[vifi].uv_acl; acl; acl = acl->acl_next)
       if ((addr & acl->acl_mask) == acl->acl_addr)
           return 1;

   return 0;
}

/*
* Determine if mcastgrp has a listener on vifi
*/
int
grplst_mem(vifi_t vifi, u_int32_t mcastgrp)
{
   struct listaddr *g;
   struct uvif *v;

   v = &uvifs[vifi];

   for (g = v->uv_groups; g != NULL; g = g->al_next)
       if (mcastgrp == g->al_addr)
           return 1;

   return 0;
}

/*
* Finds the group entry with the specified source and netmask.
* If netmask is 0, it uses the route's netmask.
*
* Returns TRUE if found a match, and the global variable gtp is left
* pointing to entry before the found entry.
* Returns FALSE if no exact match found, gtp is left pointing to before
* the entry in question belongs, or is NULL if the it belongs at the
* head of the list.
*/
int
find_src_grp(u_int32_t src, u_int32_t mask, u_int32_t grp)
{
   struct gtable *gt;

   gtp = NULL;
   gt = kernel_table;
   while (gt != NULL) {
       if (grp == gt->gt_mcastgrp &&
           (mask ? (gt->gt_route->rt_origin == src &&
                    gt->gt_route->rt_originmask == mask) :
                   ((src & gt->gt_route->rt_originmask) ==
                    gt->gt_route->rt_origin)))
           return TRUE;
       if (ntohl(grp) > ntohl(gt->gt_mcastgrp) ||
           (grp == gt->gt_mcastgrp &&
            (ntohl(mask) < ntohl(gt->gt_route->rt_originmask) ||
             (mask == gt->gt_route->rt_originmask &&
              (ntohl(src) > ntohl(gt->gt_route->rt_origin)))))) {
           gtp = gt;
           gt = gt->gt_gnext;
       }
       else break;
   }
   return FALSE;
}

/*
* Check if the neighbor supports pruning
*/
static int
pruning_neighbor(vifi_t vifi, u_int32_t addr)
{
   struct listaddr *n = neighbor_info(vifi, addr);
   int vers;

   if (n == NULL)
       return 0;

   if (n->al_flags & NF_PRUNE)
       return 1;

   /*
    * Versions from 3.0 to 3.4 relied on the version number to identify
    * that they could handle pruning.
    */
   vers = NBR_VERS(n);
   return (vers >= 0x0300 && vers <= 0x0304);
}

/*
* Can the neighbor in question handle multicast traceroute?
*/
static int
can_mtrace(vifi_t vifi, u_int32_t addr)
{
   struct listaddr *n = neighbor_info(vifi, addr);
   int vers;

   if (n == NULL)
       return 0;

   if (n->al_flags & NF_MTRACE)
       return 1;

   /*
    * Versions 3.3 and 3.4 relied on the version number to identify
    * that they could handle traceroute.
    */
   vers = NBR_VERS(n);
   return (vers >= 0x0303 && vers <= 0x0304);
}

/*
* Returns the prune entry of the router, or NULL if none exists
*/
static struct ptable *
find_prune_entry(u_int32_t vr, struct ptable *pt)
{
   while (pt) {
       if (pt->pt_router == vr)
           return pt;
       pt = pt->pt_next;
   }

   return NULL;
}

/*
* Send a prune message to the dominant router for
* this source.
*
* Record an entry that a prune was sent for this group
*/
static void
send_prune(struct gtable *gt)
{
   struct ptable *pt;
   char *p;
   int i;
   int datalen;
   u_int32_t src;
   u_int32_t dst;
   u_int32_t tmp;

   /* Don't process any prunes if router is not pruning */
   if (pruning == 0)
       return;

   /* Can't process a prune if we don't have an associated route */
   if (gt->gt_route == NULL)
       return;

   /* Don't send a prune to a non-pruning router */
   if (!pruning_neighbor(gt->gt_route->rt_parent, gt->gt_route->rt_gateway))
       return;

   /*
    * sends a prune message to the router upstream.
    */
   src = uvifs[gt->gt_route->rt_parent].uv_lcl_addr;
   dst = gt->gt_route->rt_gateway;

   p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN;
   datalen = 0;

   /*
    * determine prune lifetime
    */
   gt->gt_prsent_timer = gt->gt_timer;
   for (pt = gt->gt_pruntbl; pt; pt = pt->pt_next)
       if (pt->pt_timer < gt->gt_prsent_timer)
           gt->gt_prsent_timer = pt->pt_timer;

   /*
    * If we have a graft pending, cancel graft retransmission
    */
   gt->gt_grftsnt = 0;

   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(gt->gt_route->rt_origin))[i];
   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(gt->gt_mcastgrp))[i];
   tmp = htonl(gt->gt_prsent_timer);
   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(tmp))[i];
   datalen += 12;

   send_igmp(src, dst, IGMP_DVMRP, DVMRP_PRUNE,
             htonl(MROUTED_LEVEL), datalen);

   logit(LOG_DEBUG, 0, "sent prune for (%s %s)/%d on vif %d to %s",
     inet_fmts(gt->gt_route->rt_origin, gt->gt_route->rt_originmask),
     inet_fmt(gt->gt_mcastgrp),
     gt->gt_prsent_timer, gt->gt_route->rt_parent,
     inet_fmt(gt->gt_route->rt_gateway));
}

/*
* a prune was sent upstream
* so, a graft has to be sent to annul the prune
* set up a graft timer so that if an ack is not
* heard within that time, another graft request
* is sent out.
*/
static void
send_graft(struct gtable *gt)
{
   char *p;
   int i;
   int datalen;
   u_int32_t src;
   u_int32_t dst;

   /* Can't send a graft without an associated route */
   if (gt->gt_route == NULL)
       return;

   src = uvifs[gt->gt_route->rt_parent].uv_lcl_addr;
   dst = gt->gt_route->rt_gateway;

   p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN;
   datalen = 0;

   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(gt->gt_route->rt_origin))[i];
   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(gt->gt_mcastgrp))[i];
   datalen += 8;

   if (datalen != 0) {
       send_igmp(src, dst, IGMP_DVMRP, DVMRP_GRAFT,
                 htonl(MROUTED_LEVEL), datalen);
   }
   logit(LOG_DEBUG, 0, "sent graft for (%s %s) to %s on vif %d",
       inet_fmts(gt->gt_route->rt_origin, gt->gt_route->rt_originmask),
       inet_fmt(gt->gt_mcastgrp),
       inet_fmt(gt->gt_route->rt_gateway),
       gt->gt_route->rt_parent);
}

/*
* Send an ack that a graft was received
*/
static void
send_graft_ack(u_int32_t src, u_int32_t dst, u_int32_t origin, u_int32_t grp)
{
   char *p;
   int i;
   int datalen;

   p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN;
   datalen = 0;

   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(origin))[i];
   for (i = 0; i < 4; i++)
       *p++ = ((char *)&(grp))[i];
   datalen += 8;

   send_igmp(src, dst, IGMP_DVMRP, DVMRP_GRAFT_ACK,
             htonl(MROUTED_LEVEL), datalen);

   logit(LOG_DEBUG, 0, "sent graft ack for (%s, %s) to %s",
       inet_fmt(origin), inet_fmt(grp),
       inet_fmt(dst));
}

/*
* Update the kernel cache with all the routes hanging off the group entry
*/
static void
update_kernel(struct gtable *g)
{
   struct stable *st;

   for (st = g->gt_srctbl; st; st = st->st_next)
       k_add_rg(st->st_origin, g);
}

/****************************************************************************
                         Functions that are used externally
****************************************************************************/

#ifdef SNMP
#include <sys/types.h>
#include "snmp.h"

/*
* Find a specific group entry in the group table
*/
struct gtable *
find_grp(u_long grp)
{
  struct gtable *gt;

  for (gt = kernel_table; gt; gt = gt->gt_gnext) {
     if (ntohl(grp) < ntohl(gt->gt_mcastgrp))
        break;
     if (gt->gt_mcastgrp == grp)
        return gt;
  }
  return NULL;
}

/*
* Given a group entry and source, find the corresponding source table
* entry
*/
struct stable *
find_grp_src(struct gtable *gt, u_long src)
{
  struct stable *st;
  u_long grp = gt->gt_mcastgrp;
  struct gtable *gtcurr;

  for (gtcurr = gt; gtcurr->gt_mcastgrp == grp; gtcurr = gtcurr->gt_gnext) {
     for (st = gtcurr->gt_srctbl; st; st = st->st_next)
        if (st->st_origin == src)
           return st;
  }
  return NULL;
}

/*
* Find next entry > specification
*
* gtpp: ordered by group
* stpp: ordered by source
*/
int
next_grp_src_mask(struct gtable **gtpp, struct stable **stpp, u_long grp,
                 u_long src, u_long mask)
{
  struct gtable *gt, *gbest = NULL;
  struct stable *st, *sbest = NULL;

  /* Find first group entry >= grp spec */
  (*gtpp) = kernel_table;
  while ((*gtpp) && ntohl((*gtpp)->gt_mcastgrp) < ntohl(grp))
     (*gtpp)=(*gtpp)->gt_gnext;
  if (!(*gtpp))
     return 0; /* no more groups */

  for (gt = kernel_table; gt; gt=gt->gt_gnext) {
     /* Since grps are ordered, we can stop when group changes from gbest */
     if (gbest && gbest->gt_mcastgrp != gt->gt_mcastgrp)
        break;
     for (st = gt->gt_srctbl; st; st=st->st_next) {

        /* Among those entries > spec, find "lowest" one */
        if (((ntohl(gt->gt_mcastgrp)> ntohl(grp))
          || (ntohl(gt->gt_mcastgrp)==ntohl(grp)
             && ntohl(st->st_origin)> ntohl(src))
          || (ntohl(gt->gt_mcastgrp)==ntohl(grp)
             && ntohl(st->st_origin)==src && 0xFFFFFFFF>ntohl(mask)))
         && (!gbest
          || (ntohl(gt->gt_mcastgrp)< ntohl(gbest->gt_mcastgrp))
          || (ntohl(gt->gt_mcastgrp)==ntohl(gbest->gt_mcastgrp)
             && ntohl(st->st_origin)< ntohl(sbest->st_origin)))) {
              gbest = gt;
              sbest = st;
        }
     }
  }
  (*gtpp) = gbest;
  (*stpp) = sbest;
  return (*gtpp)!=0;
}

/*
* Ensure that sg contains current information for the given group,source.
* This is fetched from the kernel as a unit so that counts for the entry
* are consistent, i.e. packet and byte counts for the same entry are
* read at the same time.
*/
void
refresh_sg(struct sioc_sg_req *sg, struct gtable *gt, struct stable *st)
{
  static   int lastq = -1;

  if (quantum != lastq || sg->src.s_addr!=st->st_origin
   || sg->grp.s_addr!=gt->gt_mcastgrp) {
      lastq = quantum;
      sg->src.s_addr = st->st_origin;
      sg->grp.s_addr = gt->gt_mcastgrp;
      ioctl(igmp_socket, SIOCGETSGCNT, (char *)sg);
  }
}

/*
* Return pointer to a specific route entry.  This must be a separate
* function from find_route() which modifies rtp.
*/
struct rtentry *
snmp_find_route(u_long src, u_long mask)
{
   struct rtentry *rt;

  for (rt = routing_table; rt; rt = rt->rt_next) {
     if (src == rt->rt_origin && mask == rt->rt_originmask)
        return rt;
  }
  return NULL;
}

/*
* Find next route entry > specification
*/
int
next_route(struct rtentry **rtpp, u_long src, u_long mask)
{
  struct rtentry *rt, *rbest = NULL;

  /* Among all entries > spec, find "lowest" one in order */
  for (rt = routing_table; rt; rt=rt->rt_next) {
     if ((ntohl(rt->rt_origin) > ntohl(src)
         || (ntohl(rt->rt_origin) == ntohl(src)
            && ntohl(rt->rt_originmask) > ntohl(mask)))
      && (!rbest || (ntohl(rt->rt_origin) < ntohl(rbest->rt_origin))
         || (ntohl(rt->rt_origin) == ntohl(rbest->rt_origin)
            && ntohl(rt->rt_originmask) < ntohl(rbest->rt_originmask))))
              rbest = rt;
  }
  (*rtpp) = rbest;
  return (*rtpp)!=0;
}

/*
* Given a routing table entry, and a vifi, find the next vifi/entry
*
* vifi: vifi at which to start looking
*/
int
next_route_child(struct rtentry **rtpp, u_long src, u_long mask, vifi_t *vifi)
{
  struct rtentry *rt;

  /* Get (S,M) entry */
  if (!((*rtpp) = snmp_find_route(src,mask)))
     if (!next_route(rtpp, src, mask))
        return 0;

  /* Continue until we get one with a valid next vif */
  do {
     for (; (*rtpp)->rt_children && *vifi<numvifs; (*vifi)++)
        if (VIFM_ISSET(*vifi, (*rtpp)->rt_children))
           return 1;
     *vifi = 0;
  } while( next_route(rtpp, (*rtpp)->rt_origin, (*rtpp)->rt_originmask) );

  return 0;
}

/*
* Given a routing table entry, and a vifi, find the next entry
* equal to or greater than those
*
* vifi: vifi at which to start looking
*/
int
next_child(struct gtable **gtpp, struct stable **stpp, u_long grp, u_long src,
          u_long mask, vifi_t *vifi)
{
  struct stable *st;

  /* Get (G,S,M) entry */
  if (mask!=0xFFFFFFFF
   || !((*gtpp) = find_grp(grp))
   || !((*stpp) = find_grp_src((*gtpp),src)))
     if (!next_grp_src_mask(gtpp, stpp, grp, src, mask))
        return 0;

  /* Continue until we get one with a valid next vif */
  do {
     for (; (*gtpp)->gt_route->rt_children && *vifi<numvifs; (*vifi)++)
        if (VIFM_ISSET(*vifi, (*gtpp)->gt_route->rt_children))
           return 1;
     *vifi = 0;
  } while (next_grp_src_mask(gtpp, stpp, (*gtpp)->gt_mcastgrp,
               (*stpp)->st_origin, 0xFFFFFFFF) );

  return 0;
}
#endif /* SNMP */

/*
* Initialize the kernel table structure
*/
void
init_ktable(void)
{
   kernel_table        = NULL;
   kernel_no_route     = NULL;
   kroutes             = 0;
}

/*
* Add a new table entry for (origin, mcastgrp)
*/
void
add_table_entry(u_int32_t origin, u_int32_t mcastgrp)
{
   struct rtentry *r;
   struct gtable *gt,**gtnp,*prev_gt;
   struct stable *st,**stnp;
   vifi_t i;

#ifdef DEBUG_MFC
   md_log(MD_MISS, origin, mcastgrp);
#endif

   r = determine_route(origin);
   prev_gt = NULL;
   if (r == NULL) {
       /*
        * Look for it on the no_route table; if it is found then
        * it will be detected as a duplicate below.
        */
       for (gt = kernel_no_route; gt; gt = gt->gt_next)
           if (mcastgrp == gt->gt_mcastgrp &&
               gt->gt_srctbl && gt->gt_srctbl->st_origin == origin)
                       break;
       gtnp = &kernel_no_route;
   } else {
       gtnp = &r->rt_groups;
       while ((gt = *gtnp) != NULL) {
           if (gt->gt_mcastgrp >= mcastgrp)
               break;
           gtnp = &gt->gt_next;
           prev_gt = gt;
       }
   }

   if (gt == NULL || gt->gt_mcastgrp != mcastgrp) {
       gt = (struct gtable *)malloc(sizeof(struct gtable));
       if (gt == NULL) {
           logit(LOG_ERR, 0, "ran out of memory");
           return;
       }

       gt->gt_mcastgrp     = mcastgrp;
       gt->gt_timer        = CACHE_LIFETIME(cache_lifetime);
       time(&gt->gt_ctime);
       gt->gt_grpmems      = 0;
       gt->gt_scope        = 0;
       gt->gt_prsent_timer = 0;
       gt->gt_grftsnt      = 0;
       gt->gt_srctbl       = NULL;
       gt->gt_pruntbl      = NULL;
       gt->gt_route        = r;
#ifdef RSRR
       gt->gt_rsrr_cache   = NULL;
#endif

       if (r != NULL) {
           /* obtain the multicast group membership list */
           for (i = 0; i < numvifs; i++) {
               if (VIFM_ISSET(i, r->rt_children) &&
                   !(VIFM_ISSET(i, r->rt_leaves)))
                   VIFM_SET(i, gt->gt_grpmems);

               if (VIFM_ISSET(i, r->rt_leaves) && grplst_mem(i, mcastgrp))
                   VIFM_SET(i, gt->gt_grpmems);
           }
           GET_SCOPE(gt);
           if (VIFM_ISSET(r->rt_parent, gt->gt_scope))
               gt->gt_scope = -1;
           gt->gt_grpmems &= ~gt->gt_scope;
       } else {
           gt->gt_scope = -1;
           gt->gt_grpmems = 0;
       }

       /* update ttls */
       prun_add_ttls(gt);

       gt->gt_next = *gtnp;
       *gtnp = gt;
       if (gt->gt_next)
           gt->gt_next->gt_prev = gt;
       gt->gt_prev = prev_gt;

       if (r) {
           if (find_src_grp(r->rt_origin, r->rt_originmask, gt->gt_mcastgrp)) {
               struct gtable *g;

               g = gtp ? gtp->gt_gnext : kernel_table;
               logit(LOG_WARNING, 0, "Entry for (%s %s) (rt:%p) exists (rt:%p)",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp),
                   r, g->gt_route);
           } else {
               if (gtp) {
                   gt->gt_gnext = gtp->gt_gnext;
                   gt->gt_gprev = gtp;
                   gtp->gt_gnext = gt;
               } else {
                   gt->gt_gnext = kernel_table;
                   gt->gt_gprev = NULL;
                   kernel_table = gt;
               }
               if (gt->gt_gnext)
                   gt->gt_gnext->gt_gprev = gt;
           }
       } else {
           gt->gt_gnext = gt->gt_gprev = NULL;
       }
   }

   stnp = &gt->gt_srctbl;
   while ((st = *stnp) != NULL) {
       if (ntohl(st->st_origin) >= ntohl(origin))
           break;
       stnp = &st->st_next;
   }

   if (st == NULL || st->st_origin != origin) {
       st = (struct stable *)malloc(sizeof(struct stable));
       if (st == NULL)
           logit(LOG_ERR, 0, "ran out of memory");

       st->st_origin = origin;
       st->st_pktcnt = 0;
       st->st_next = *stnp;
       *stnp = st;
   } else {
#ifdef DEBUG_MFC
       md_log(MD_DUPE, origin, mcastgrp);
#endif
       logit(LOG_WARNING, 0, "kernel entry already exists for (%s %s)",
               inet_fmt(origin),
               inet_fmt(mcastgrp));
       /* XXX Doing this should cause no harm, and may ensure
        * kernel<>mrouted synchronization */
       k_add_rg(origin, gt);
       return;
   }

   kroutes++;
   k_add_rg(origin, gt);

   logit(LOG_DEBUG, 0, "add cache entry (%s %s) gm:%x, parent-vif:%d",
       inet_fmt(origin),
       inet_fmt(mcastgrp),
       gt->gt_grpmems, r ? r->rt_parent : -1);

   /* If there are no leaf vifs
    * which have this group, then
    * mark this src-grp as a prune candidate.
    */
   if (!gt->gt_prsent_timer && !gt->gt_grpmems && r && r->rt_gateway)
       send_prune(gt);
}

/*
* An mrouter has gone down and come up on an interface
* Forward on that interface immediately
*/
void
reset_neighbor_state(vifi_t vifi, u_int32_t addr)
{
   struct rtentry *r;
   struct gtable *g;
   struct ptable *pt, **ptnp;
   struct stable *st;

   for (g = kernel_table; g; g = g->gt_gnext) {
       r = g->gt_route;

       /*
        * If neighbor was the parent, remove the prune sent state
        * and all of the source cache info so that prunes get
        * regenerated.
        */
       if (vifi == r->rt_parent) {
           if (addr == r->rt_gateway) {
               logit(LOG_DEBUG, 0, "reset_neighbor_state parent reset (%s %s)",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp));

               g->gt_prsent_timer = 0;
               g->gt_grftsnt = 0;
               while ((st = g->gt_srctbl) != NULL) {
                   g->gt_srctbl = st->st_next;
                   k_del_rg(st->st_origin, g);
                   kroutes--;
                   free(st);
               }
           }
       } else {
           /*
            * Neighbor was not the parent, send grafts to join the groups
            */
           if (g->gt_prsent_timer) {
               g->gt_grftsnt = 1;
               send_graft(g);
               g->gt_prsent_timer = 0;
           }

           /*
            * Remove any prunes that this router has sent us.
            */
           ptnp = &g->gt_pruntbl;
           while ((pt = *ptnp) != NULL) {
               if (pt->pt_vifi == vifi && pt->pt_router == addr) {
                   *ptnp = pt->pt_next;
                   free(pt);
               } else
                   ptnp = &pt->pt_next;
           }

           /*
            * And see if we want to forward again.
            */
           if (!VIFM_ISSET(vifi, g->gt_grpmems)) {
               if (VIFM_ISSET(vifi, r->rt_children) &&
                   !(VIFM_ISSET(vifi, r->rt_leaves)))
                   VIFM_SET(vifi, g->gt_grpmems);

               if (VIFM_ISSET(vifi, r->rt_leaves) &&
                   grplst_mem(vifi, g->gt_mcastgrp))
                   VIFM_SET(vifi, g->gt_grpmems);

               g->gt_grpmems &= ~g->gt_scope;
               prun_add_ttls(g);

               /* Update kernel state */
               update_kernel(g);
#ifdef RSRR
               /* Send route change notification to reservation protocol. */
               rsrr_cache_send(g,1);
#endif /* RSRR */

               logit(LOG_DEBUG, 0, "reset member state (%s %s) gm:%x",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp), g->gt_grpmems);
           }
       }
   }
}

/*
* Delete table entry from the kernel
* del_flag determines how many entries to delete
*/
void
del_table_entry(struct rtentry *r, u_int32_t mcastgrp, u_int del_flag)
{
   struct gtable *g, *prev_g;
   struct stable *st, *prev_st;
   struct ptable *pt, *prev_pt;

   if (del_flag == DEL_ALL_ROUTES) {
       g = r->rt_groups;
       while (g) {
           logit(LOG_DEBUG, 0, "del_table_entry deleting (%s %s)",
               inet_fmts(r->rt_origin, r->rt_originmask),
               inet_fmt(g->gt_mcastgrp));
           st = g->gt_srctbl;
           while (st) {
               if (k_del_rg(st->st_origin, g) < 0) {
                   logit(LOG_WARNING, errno,
                       "del_table_entry trying to delete (%s, %s)",
                       inet_fmt(st->st_origin),
                       inet_fmt(g->gt_mcastgrp));
               }
               kroutes--;
               prev_st = st;
               st = st->st_next;
               free(prev_st);
           }
           g->gt_srctbl = NULL;

           pt = g->gt_pruntbl;
           while (pt) {
               prev_pt = pt;
               pt = pt->pt_next;
               free(prev_pt);
           }
           g->gt_pruntbl = NULL;

           if (g->gt_gnext)
               g->gt_gnext->gt_gprev = g->gt_gprev;
           if (g->gt_gprev)
               g->gt_gprev->gt_gnext = g->gt_gnext;
           else
               kernel_table = g->gt_gnext;

#ifdef RSRR
           /* Send route change notification to reservation protocol. */
           rsrr_cache_send(g,0);
           rsrr_cache_clean(g);
#endif /* RSRR */
           prev_g = g;
           g = g->gt_next;
           free(prev_g);
       }
       r->rt_groups = NULL;
   }

   /*
    * Dummy routine - someday this may be needed, so it is just there
    */
   if (del_flag == DEL_RTE_GROUP) {
       prev_g = (struct gtable *)&r->rt_groups;
       for (g = r->rt_groups; g; g = g->gt_next) {
           if (g->gt_mcastgrp == mcastgrp) {
               logit(LOG_DEBUG, 0, "del_table_entry deleting (%s %s)",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp));
               st = g->gt_srctbl;
               while (st) {
                   if (k_del_rg(st->st_origin, g) < 0) {
                       logit(LOG_WARNING, errno,
                           "del_table_entry trying to delete (%s, %s)",
                           inet_fmt(st->st_origin),
                           inet_fmt(g->gt_mcastgrp));
                   }
                   kroutes--;
                   prev_st = st;
                   st = st->st_next;
                   free(prev_st);
               }
               g->gt_srctbl = NULL;

               pt = g->gt_pruntbl;
               while (pt) {
                   prev_pt = pt;
                   pt = pt->pt_next;
                   free(prev_pt);
               }
               g->gt_pruntbl = NULL;

               if (g->gt_gnext)
                   g->gt_gnext->gt_gprev = g->gt_gprev;
               if (g->gt_gprev)
                   g->gt_gprev->gt_gnext = g->gt_gnext;
               else
                   kernel_table = g->gt_gnext;

               if (prev_g != (struct gtable *)&r->rt_groups)
                   g->gt_next->gt_prev = prev_g;
               else
                   g->gt_next->gt_prev = NULL;
               prev_g->gt_next = g->gt_next;

#ifdef RSRR
               /* Send route change notification to reservation protocol. */
               rsrr_cache_send(g,0);
               rsrr_cache_clean(g);
#endif /* RSRR */
               free(g);
               g = prev_g;
           } else {
               prev_g = g;
           }
       }
   }
}

/*
* update kernel table entry when a route entry changes
*/
void
update_table_entry(struct rtentry *r)
{
   struct gtable *g;
   struct ptable *pt, *prev_pt;
   vifi_t i;

   for (g = r->rt_groups; g; g = g->gt_next) {
       pt = g->gt_pruntbl;
       while (pt) {
           prev_pt = pt->pt_next;
           free(pt);
           pt = prev_pt;
       }
       g->gt_pruntbl = NULL;

       g->gt_grpmems = 0;

       /* obtain the multicast group membership list */
       for (i = 0; i < numvifs; i++) {
           if (VIFM_ISSET(i, r->rt_children) &&
               !(VIFM_ISSET(i, r->rt_leaves)))
               VIFM_SET(i, g->gt_grpmems);

           if (VIFM_ISSET(i, r->rt_leaves) && grplst_mem(i, g->gt_mcastgrp))
               VIFM_SET(i, g->gt_grpmems);
       }
       if (VIFM_ISSET(r->rt_parent, g->gt_scope))
           g->gt_scope = -1;
       g->gt_grpmems &= ~g->gt_scope;

       logit(LOG_DEBUG, 0, "updating cache entries (%s %s) gm:%x",
           inet_fmts(r->rt_origin, r->rt_originmask),
           inet_fmt(g->gt_mcastgrp),
           g->gt_grpmems);

       if (g->gt_grpmems && g->gt_prsent_timer) {
           g->gt_grftsnt = 1;
           send_graft(g);
           g->gt_prsent_timer = 0;
       }

       /* update ttls and add entry into kernel */
       prun_add_ttls(g);
       update_kernel(g);
#ifdef RSRR
       /* Send route change notification to reservation protocol. */
       rsrr_cache_send(g,1);
#endif /* RSRR */

       /* Check if we want to prune this group */
       if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway) {
           g->gt_timer = CACHE_LIFETIME(cache_lifetime);
           send_prune(g);
       }
   }
}

/*
* set the forwarding flag for all mcastgrps on this vifi
*/
void
update_lclgrp(vifi_t vifi, u_int32_t mcastgrp)
{
   struct rtentry *r;
   struct gtable *g;

   logit(LOG_DEBUG, 0, "group %s joined on vif %d",
       inet_fmt(mcastgrp), vifi);

   for (g = kernel_table; g; g = g->gt_gnext) {
       if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp))
           break;

       r = g->gt_route;
       if (g->gt_mcastgrp == mcastgrp &&
           VIFM_ISSET(vifi, r->rt_children)) {

           VIFM_SET(vifi, g->gt_grpmems);
           g->gt_grpmems &= ~g->gt_scope;
           if (g->gt_grpmems == 0)
               continue;

           prun_add_ttls(g);
           logit(LOG_DEBUG, 0, "update lclgrp (%s %s) gm:%x",
               inet_fmts(r->rt_origin, r->rt_originmask),
               inet_fmt(g->gt_mcastgrp), g->gt_grpmems);

           update_kernel(g);
#ifdef RSRR
           /* Send route change notification to reservation protocol. */
           rsrr_cache_send(g,1);
#endif /* RSRR */
       }
   }
}

/*
* reset forwarding flag for all mcastgrps on this vifi
*/
void
delete_lclgrp(vifi_t vifi, u_int32_t mcastgrp)
{
   struct rtentry *r;
   struct gtable *g;

   logit(LOG_DEBUG, 0, "group %s left on vif %d",
       inet_fmt(mcastgrp), vifi);

   for (g = kernel_table; g; g = g->gt_gnext) {
       if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp))
           break;

       if (g->gt_mcastgrp == mcastgrp) {
           int stop_sending = 1;

           r = g->gt_route;
           /*
            * If this is not a leaf, then we have router neighbors on this
            * vif.  Only turn off forwarding if they have all pruned.
            */
           if (!VIFM_ISSET(vifi, r->rt_leaves)) {
               struct listaddr *vr;

               for (vr = uvifs[vifi].uv_neighbors; vr; vr = vr->al_next)
                 if (find_prune_entry(vr->al_addr, g->gt_pruntbl) == NULL) {
                     stop_sending = 0;
                     break;
                 }
           }

           if (stop_sending) {
               VIFM_CLR(vifi, g->gt_grpmems);
               logit(LOG_DEBUG, 0, "delete lclgrp (%s %s) gm:%x",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp), g->gt_grpmems);

               prun_add_ttls(g);
               update_kernel(g);
#ifdef RSRR
               /* Send route change notification to reservation protocol. */
               rsrr_cache_send(g,1);
#endif /* RSRR */

               /*
                * If there are no more members of this particular group,
                *  send prune upstream
                */
               if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway)
                   send_prune(g);
           }
       }
   }
}

/*
* Takes the prune message received and then strips it to
* determine the (src, grp) pair to be pruned.
*
* Adds the router to the (src, grp) entry then.
*
* Determines if further packets have to be sent down that vif
*
* Determines if a corresponding prune message has to be generated
*/
void
accept_prune(u_int32_t src, u_int32_t dst, char *p, int datalen)
{
   u_int32_t prun_src;
   u_int32_t prun_grp;
   u_int32_t prun_tmr;
   vifi_t vifi;
   int i;
   int stop_sending;
   struct rtentry *r;
   struct gtable *g;
   struct ptable *pt;
   struct listaddr *vr;

   /* Don't process any prunes if router is not pruning */
   if (pruning == 0)
       return;

   if ((vifi = find_vif(src, dst)) == NO_VIF) {
       logit(LOG_INFO, 0,
           "ignoring prune report from non-neighbor %s",
           inet_fmt(src));
       return;
   }

   /* Check if enough data is present */
   if (datalen < 12)
       {
           logit(LOG_WARNING, 0,
               "non-decipherable prune from %s",
               inet_fmt(src));
           return;
       }

   for (i = 0; i< 4; i++)
       ((char *)&prun_src)[i] = *p++;
   for (i = 0; i< 4; i++)
       ((char *)&prun_grp)[i] = *p++;
   for (i = 0; i< 4; i++)
       ((char *)&prun_tmr)[i] = *p++;
   prun_tmr = ntohl(prun_tmr);

   logit(LOG_DEBUG, 0, "%s on vif %d prunes (%s %s)/%d",
       inet_fmt(src), vifi,
       inet_fmt(prun_src), inet_fmt(prun_grp), prun_tmr);

   /*
    * Find the subnet for the prune
    */
   if (find_src_grp(prun_src, 0, prun_grp)) {
       g = gtp ? gtp->gt_gnext : kernel_table;
       r = g->gt_route;

       if (!VIFM_ISSET(vifi, r->rt_children)) {
           logit(LOG_WARNING, 0, "prune received from non-child %s for (%s %s)",
               inet_fmt(src), inet_fmt(prun_src),
               inet_fmt(prun_grp));
           return;
       }
       if (VIFM_ISSET(vifi, g->gt_scope)) {
           logit(LOG_WARNING, 0, "prune received from %s on scoped grp (%s %s)",
               inet_fmt(src), inet_fmt(prun_src),
               inet_fmt(prun_grp));
           return;
       }
       if ((pt = find_prune_entry(src, g->gt_pruntbl)) != NULL) {
           /*
            * If it's about to expire, then it's only still around because
            * of timer granularity, so don't warn about it.
            */
           if (pt->pt_timer > 10) {
               logit(LOG_WARNING, 0, "%s %d from %s for (%s %s)/%d %s %d %s %x",
                   "duplicate prune received on vif",
                   vifi, inet_fmt(src), inet_fmt(prun_src),
                   inet_fmt(prun_grp), prun_tmr,
                   "old timer:", pt->pt_timer, "cur gm:", g->gt_grpmems);
           }
           pt->pt_timer = prun_tmr;
       } else {
           /* allocate space for the prune structure */
           pt = (struct ptable *)(malloc(sizeof(struct ptable)));
           if (pt == NULL) {
             logit(LOG_ERR, 0, "pt: ran out of memory");
             return;
           }

           pt->pt_vifi = vifi;
           pt->pt_router = src;
           pt->pt_timer = prun_tmr;

           pt->pt_next = g->gt_pruntbl;
           g->gt_pruntbl = pt;
       }

       /* Refresh the group's lifetime */
       g->gt_timer = CACHE_LIFETIME(cache_lifetime);
       if ((u_int32_t)g->gt_timer < prun_tmr)
           g->gt_timer = prun_tmr;

       /*
        * check if any more packets need to be sent on the
        * vif which sent this message
        */
       stop_sending = 1;
       for (vr = uvifs[vifi].uv_neighbors; vr; vr = vr->al_next)
         if (find_prune_entry(vr->al_addr, g->gt_pruntbl) == NULL)  {
             stop_sending = 0;
             break;
         }

       if (stop_sending && !grplst_mem(vifi, prun_grp)) {
           VIFM_CLR(vifi, g->gt_grpmems);
           logit(LOG_DEBUG, 0, "prune (%s %s), stop sending on vif %d, gm:%x",
               inet_fmts(r->rt_origin, r->rt_originmask),
               inet_fmt(g->gt_mcastgrp), vifi, g->gt_grpmems);

           prun_add_ttls(g);
           update_kernel(g);
#ifdef RSRR
           /* Send route change notification to reservation protocol. */
           rsrr_cache_send(g,1);
#endif /* RSRR */
       }

       /*
        * check if all the child routers have expressed no interest
        * in this group and if this group does not exist in the
        * interface
        * Send a prune message then upstream
        */
       if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway) {
           send_prune(g);
       }
   } else {
       /*
        * There is no kernel entry for this group.  Therefore, we can
        * simply ignore the prune, as we are not forwarding this traffic
        * downstream.
        */
       logit(LOG_DEBUG, 0, "%s (%s %s)/%d from %s",
           "prune message received with no kernel entry for",
           inet_fmt(prun_src), inet_fmt(prun_grp),
           prun_tmr, inet_fmt(src));
       return;
   }
}

/*
* Checks if this mcastgrp is present in the kernel table
* If so and if a prune was sent, it sends a graft upwards
*/
void
chkgrp_graft(vifi_t vifi, u_int32_t mcastgrp)
{
   struct rtentry *r;
   struct gtable *g;

   for (g = kernel_table; g; g = g->gt_gnext) {
       if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp))
           break;

       r = g->gt_route;
       if (g->gt_mcastgrp == mcastgrp && VIFM_ISSET(vifi, r->rt_children))
           if (g->gt_prsent_timer) {
               VIFM_SET(vifi, g->gt_grpmems);

               /*
                * If the vif that was joined was a scoped vif,
                * ignore it ; don't graft back
                */
               g->gt_grpmems &= ~g->gt_scope;
               if (g->gt_grpmems == 0)
                   continue;

               /* set the flag for graft retransmission */
               g->gt_grftsnt = 1;

               /* send graft upwards */
               send_graft(g);

               /* reset the prune timer and update cache timer*/
               g->gt_prsent_timer = 0;
               g->gt_timer = max_prune_lifetime;

               logit(LOG_DEBUG, 0, "chkgrp graft (%s %s) gm:%x",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp), g->gt_grpmems);

               prun_add_ttls(g);
               update_kernel(g);
#ifdef RSRR
               /* Send route change notification to reservation protocol. */
               rsrr_cache_send(g,1);
#endif /* RSRR */
           }
   }
}

/* determine the multicast group and src
*
* if it does, then determine if a prune was sent
* upstream.
* if prune sent upstream, send graft upstream and send
* ack downstream.
*
* if no prune sent upstream, change the forwarding bit
* for this interface and send ack downstream.
*
* if no entry exists for this group send ack downstream.
*/
void
accept_graft(u_int32_t src, u_int32_t dst, char *p, int datalen)
{
   vifi_t      vifi;
   u_int32_t   graft_src;
   u_int32_t   graft_grp;
   int         i;
   struct rtentry *r;
   struct gtable *g;
   struct ptable *pt, **ptnp;

   if ((vifi = find_vif(src, dst)) == NO_VIF) {
       logit(LOG_INFO, 0,
           "ignoring graft from non-neighbor %s",
           inet_fmt(src));
       return;
   }

   if (datalen < 8) {
       logit(LOG_WARNING, 0,
           "received non-decipherable graft from %s",
           inet_fmt(src));
       return;
   }

   for (i = 0; i< 4; i++)
       ((char *)&graft_src)[i] = *p++;
   for (i = 0; i< 4; i++)
       ((char *)&graft_grp)[i] = *p++;

   logit(LOG_DEBUG, 0, "%s on vif %d grafts (%s %s)",
       inet_fmt(src), vifi,
       inet_fmt(graft_src), inet_fmt(graft_grp));

   /*
    * Find the subnet for the graft
    */
   if (find_src_grp(graft_src, 0, graft_grp)) {
       g = gtp ? gtp->gt_gnext : kernel_table;
       r = g->gt_route;

       if (VIFM_ISSET(vifi, g->gt_scope)) {
           logit(LOG_WARNING, 0, "graft received from %s on scoped grp (%s %s)",
               inet_fmt(src), inet_fmt(graft_src),
               inet_fmt(graft_grp));
           return;
       }

       ptnp = &g->gt_pruntbl;
       while ((pt = *ptnp) != NULL) {
           if ((pt->pt_vifi == vifi) && (pt->pt_router == src)) {
               *ptnp = pt->pt_next;
               free(pt);

               VIFM_SET(vifi, g->gt_grpmems);
               logit(LOG_DEBUG, 0, "accept graft (%s %s) gm:%x",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(g->gt_mcastgrp), g->gt_grpmems);

               prun_add_ttls(g);
               update_kernel(g);
#ifdef RSRR
               /* Send route change notification to reservation protocol. */
               rsrr_cache_send(g,1);
#endif /* RSRR */
               break;
           } else {
               ptnp = &pt->pt_next;
           }
       }

       /* send ack downstream */
       send_graft_ack(dst, src, graft_src, graft_grp);
       g->gt_timer = max_prune_lifetime;

       if (g->gt_prsent_timer) {
           /* set the flag for graft retransmission */
           g->gt_grftsnt = 1;

           /* send graft upwards */
           send_graft(g);

           /* reset the prune sent timer */
           g->gt_prsent_timer = 0;
       }
   } else {
       /*
        * We have no state for the source and group in question.
        * We can simply acknowledge the graft, since we know
        * that we have no prune state, and grafts are requests
        * to remove prune state.
        */
       send_graft_ack(dst, src, graft_src, graft_grp);
       logit(LOG_DEBUG, 0, "%s (%s %s) from %s",
           "graft received with no kernel entry for",
           inet_fmt(graft_src), inet_fmt(graft_grp),
           inet_fmt(src));
       return;
   }
}

/*
* find out which group is involved first of all
* then determine if a graft was sent.
* if no graft sent, ignore the message
* if graft was sent and the ack is from the right
* source, remove the graft timer so that we don't
* have send a graft again
*/
void
accept_g_ack(u_int32_t src, u_int32_t dst, char *p, int datalen)
{
   struct gtable *g;
   vifi_t      vifi;
   u_int32_t   grft_src;
   u_int32_t   grft_grp;
   int         i;

   if ((vifi = find_vif(src, dst)) == NO_VIF) {
       logit(LOG_INFO, 0,
           "ignoring graft ack from non-neighbor %s",
           inet_fmt(src));
       return;
   }

   if (datalen < 0  || datalen > 8) {
       logit(LOG_WARNING, 0,
           "received non-decipherable graft ack from %s",
           inet_fmt(src));
       return;
   }

   for (i = 0; i< 4; i++)
       ((char *)&grft_src)[i] = *p++;
   for (i = 0; i< 4; i++)
       ((char *)&grft_grp)[i] = *p++;

   logit(LOG_DEBUG, 0, "%s on vif %d acks graft (%s, %s)",
       inet_fmt(src), vifi,
       inet_fmt(grft_src), inet_fmt(grft_grp));

   /*
    * Find the subnet for the graft ack
    */
   if (find_src_grp(grft_src, 0, grft_grp)) {
       g = gtp ? gtp->gt_gnext : kernel_table;
       g->gt_grftsnt = 0;
   } else {
       logit(LOG_WARNING, 0, "%s (%s, %s) from %s",
           "rcvd graft ack with no kernel entry for",
           inet_fmt(grft_src), inet_fmt(grft_grp),
           inet_fmt(src));
       return;
   }
}


/*
* free all prune entries and kernel routes
* normally, this should inform the kernel that all of its routes
* are going away, but this is only called by restart(), which is
* about to call MRT_DONE which does that anyway.
*/
void
free_all_prunes(void)
{
   struct rtentry *r;
   struct gtable *g, *prev_g;
   struct stable *s, *prev_s;
   struct ptable *p, *prev_p;

   for (r = routing_table; r; r = r->rt_next) {
       g = r->rt_groups;
       while (g) {
           s = g->gt_srctbl;
           while (s) {
               prev_s = s;
               s = s->st_next;
               free(prev_s);
           }

           p = g->gt_pruntbl;
           while (p) {
               prev_p = p;
               p = p->pt_next;
               free(prev_p);
           }

           prev_g = g;
           g = g->gt_next;
           free(prev_g);
       }
       r->rt_groups = NULL;
   }
   kernel_table = NULL;

   g = kernel_no_route;
   while (g) {
       if (g->gt_srctbl)
           free(g->gt_srctbl);

       prev_g = g;
       g = g->gt_next;
       free(prev_g);
   }
   kernel_no_route = NULL;
}

/*
* When a new route is created, search
* a) The less-specific part of the routing table
* b) The route-less kernel table
* for sources that the new route might want to handle.
*
* "Inheriting" these sources might be cleanest, but simply deleting
* them is easier, and letting the kernel re-request them.
*/
void
steal_sources(struct rtentry *rt)
{
   struct rtentry *rp;
   struct gtable *gt, **gtnp;
   struct stable *st, **stnp;

   for (rp = rt->rt_next; rp; rp = rp->rt_next) {
       if ((rt->rt_origin & rp->rt_originmask) == rp->rt_origin) {
           logit(LOG_DEBUG, 0, "Route for %s stealing sources from %s",
               inet_fmts(rt->rt_origin, rt->rt_originmask),
               inet_fmts(rp->rt_origin, rp->rt_originmask));
           for (gt = rp->rt_groups; gt; gt = gt->gt_next) {
               stnp = &gt->gt_srctbl;
               while ((st = *stnp) != NULL) {
                   if ((st->st_origin & rt->rt_originmask) == rt->rt_origin) {
                       logit(LOG_DEBUG, 0, "%s stealing (%s %s) from %s",
                           inet_fmts(rt->rt_origin, rt->rt_originmask),
                           inet_fmt(st->st_origin),
                           inet_fmt(gt->gt_mcastgrp),
                           inet_fmts(rp->rt_origin, rp->rt_originmask));
                       if (k_del_rg(st->st_origin, gt) < 0) {
                           logit(LOG_WARNING, errno, "%s (%s, %s)",
                               "steal_sources trying to delete",
                               inet_fmt(st->st_origin),
                               inet_fmt(gt->gt_mcastgrp));
                       }
                       *stnp = st->st_next;
                       kroutes--;
                       free(st);
                   } else {
                       stnp = &st->st_next;
                   }
               }
           }
       }
   }

   gtnp = &kernel_no_route;
   while ((gt = *gtnp) != NULL) {
       if (gt->gt_srctbl && ((gt->gt_srctbl->st_origin & rt->rt_originmask)
                                   == rt->rt_origin)) {
           logit(LOG_DEBUG, 0, "%s stealing (%s %s) from %s",
               inet_fmts(rt->rt_origin, rt->rt_originmask),
               inet_fmt(gt->gt_srctbl->st_origin),
               inet_fmt(gt->gt_mcastgrp),
               "no_route table");
           if (k_del_rg(gt->gt_srctbl->st_origin, gt) < 0) {
               logit(LOG_WARNING, errno, "%s (%s %s)",
                   "steal_sources trying to delete",
                   inet_fmt(gt->gt_srctbl->st_origin),
                   inet_fmt(gt->gt_mcastgrp));
           }
           kroutes--;
           free(gt->gt_srctbl);
           *gtnp = gt->gt_next;
           if (gt->gt_next)
               gt->gt_next->gt_prev = gt->gt_prev;
           free(gt);
       } else {
           gtnp = &gt->gt_next;
       }
   }
}

/*
* Advance the timers on all the cache entries.
* If there are any entries whose timers have expired,
* remove these entries from the kernel cache.
*/
void
age_table_entry(void)
{
   struct rtentry *r;
   struct gtable *gt, **gtnptr;
   struct stable *st, **stnp;
   struct ptable *pt, **ptnp;
   struct sioc_sg_req sg_req;

   logit(LOG_DEBUG, 0, "ageing entries");

   gtnptr = &kernel_table;
   while ((gt = *gtnptr) != NULL) {
       r = gt->gt_route;

       /* advance the timer for the kernel entry */
       gt->gt_timer -= ROUTE_MAX_REPORT_DELAY;

       /* decrement prune timer if need be */
       if (gt->gt_prsent_timer > 0) {
           gt->gt_prsent_timer -= ROUTE_MAX_REPORT_DELAY;
           if (gt->gt_prsent_timer <= 0) {
               logit(LOG_DEBUG, 0, "upstream prune tmo (%s %s)",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(gt->gt_mcastgrp));
               gt->gt_prsent_timer = -1;
           }
       }

       /* retransmit graft if graft sent flag is still set */
       if (gt->gt_grftsnt) {
           int y;
           CHK_GS(gt->gt_grftsnt++, y);
           if (y)
               send_graft(gt);
       }

       /*
        * Age prunes
        *
        * If a prune expires, forward again on that vif.
        */
       ptnp = &gt->gt_pruntbl;
       while ((pt = *ptnp) != NULL) {
           if ((pt->pt_timer -= ROUTE_MAX_REPORT_DELAY) <= 0) {
               logit(LOG_DEBUG, 0, "expire prune (%s %s) from %s on vif %d",
                   inet_fmts(r->rt_origin, r->rt_originmask),
                   inet_fmt(gt->gt_mcastgrp),
                   inet_fmt(pt->pt_router),
                   pt->pt_vifi);

               expire_prune(pt->pt_vifi, gt);

               /* remove the router's prune entry and await new one */
               *ptnp = pt->pt_next;
               free(pt);
           } else {
               ptnp = &pt->pt_next;
           }
       }

       /*
        * If the cache entry has expired, delete source table entries for
        * silent sources.  If there are no source entries left, and there
        * are no downstream prunes, then the entry is deleted.
        * Otherwise, the cache entry's timer is refreshed.
        */
       if (gt->gt_timer <= 0) {
           /* Check for traffic before deleting source entries */
           sg_req.grp.s_addr = gt->gt_mcastgrp;
           stnp = &gt->gt_srctbl;
           while ((st = *stnp) != NULL) {
               sg_req.src.s_addr = st->st_origin;
               if (ioctl(igmp_socket, SIOCGETSGCNT, (char *)&sg_req) < 0) {
                   logit(LOG_WARNING, errno, "%s (%s %s)",
                       "age_table_entry: SIOCGETSGCNT failing for",
                       inet_fmt(st->st_origin),
                       inet_fmt(gt->gt_mcastgrp));
                   /* Make sure it gets deleted below */
                   sg_req.pktcnt = st->st_pktcnt;
               }
               if (sg_req.pktcnt == st->st_pktcnt) {
                   *stnp = st->st_next;
                   logit(LOG_DEBUG, 0, "age_table_entry deleting (%s %s)",
                       inet_fmt(st->st_origin),
                       inet_fmt(gt->gt_mcastgrp));
                   if (k_del_rg(st->st_origin, gt) < 0) {
                       logit(LOG_WARNING, errno,
                           "age_table_entry trying to delete (%s %s)",
                           inet_fmt(st->st_origin),
                           inet_fmt(gt->gt_mcastgrp));
                   }
                   kroutes--;
                   free(st);
               } else {
                   st->st_pktcnt = sg_req.pktcnt;
                   stnp = &st->st_next;
               }
           }

           /*
            * Retain the group entry if we have downstream prunes or if
            * there is at least one source in the list that still has
            * traffic, or if our upstream prune timer is running.
            */
           if (gt->gt_pruntbl != NULL || gt->gt_srctbl != NULL ||
               gt->gt_prsent_timer > 0) {
               gt->gt_timer = CACHE_LIFETIME(cache_lifetime);
               if (gt->gt_prsent_timer == -1) {
                   if (gt->gt_grpmems == 0)
                       send_prune(gt);
                   else
                       gt->gt_prsent_timer = 0;
               }
               gtnptr = &gt->gt_gnext;
               continue;
           }

           logit(LOG_DEBUG, 0, "timeout cache entry (%s, %s)",
               inet_fmts(r->rt_origin, r->rt_originmask),
               inet_fmt(gt->gt_mcastgrp));

           if (gt->gt_prev)
               gt->gt_prev->gt_next = gt->gt_next;
           else
               gt->gt_route->rt_groups = gt->gt_next;
           if (gt->gt_next)
               gt->gt_next->gt_prev = gt->gt_prev;

           if (gt->gt_gprev) {
               gt->gt_gprev->gt_gnext = gt->gt_gnext;
               gtnptr = &gt->gt_gprev->gt_gnext;
           } else {
               kernel_table = gt->gt_gnext;
               gtnptr = &kernel_table;
           }
           if (gt->gt_gnext)
               gt->gt_gnext->gt_gprev = gt->gt_gprev;

#ifdef RSRR
           /* Send route change notification to reservation protocol. */
           rsrr_cache_send(gt,0);
           rsrr_cache_clean(gt);
#endif /* RSRR */
           free((char *)gt);
       } else {
           if (gt->gt_prsent_timer == -1) {
               if (gt->gt_grpmems == 0)
                   send_prune(gt);
               else
                   gt->gt_prsent_timer = 0;
           }
           gtnptr = &gt->gt_gnext;
       }
   }

   /*
    * When traversing the no_route table, the decision is much easier.
    * Just delete it if it has timed out.
    */
   gtnptr = &kernel_no_route;
   while ((gt = *gtnptr) != NULL) {
       /* advance the timer for the kernel entry */
       gt->gt_timer -= ROUTE_MAX_REPORT_DELAY;

       if (gt->gt_timer < 0) {
           if (gt->gt_srctbl) {
               if (k_del_rg(gt->gt_srctbl->st_origin, gt) < 0) {
                   logit(LOG_WARNING, errno, "%s (%s %s)",
                       "age_table_entry trying to delete no-route",
                       inet_fmt(gt->gt_srctbl->st_origin),
                       inet_fmt(gt->gt_mcastgrp));
               }
               free(gt->gt_srctbl);
           }
           *gtnptr = gt->gt_next;
           if (gt->gt_next)
               gt->gt_next->gt_prev = gt->gt_prev;

           free((char *)gt);
       } else {
           gtnptr = &gt->gt_next;
       }
   }
}

/*
* Modify the kernel to forward packets when one or multiple prunes that
* were received on the vif given by vifi, for the group given by gt,
* have expired.
*/
static void
expire_prune(vifi_t vifi, struct gtable *gt)
{
   /*
    * No need to send a graft, any prunes that we sent
    * will expire before any prunes that we have received.
    */
   if (gt->gt_prsent_timer > 0) {
       logit(LOG_DEBUG, 0, "prune expired with %d left on %s",
               gt->gt_prsent_timer, "prsent_timer");
       gt->gt_prsent_timer = 0;
   }

   /* modify the kernel entry to forward packets */
   if (!VIFM_ISSET(vifi, gt->gt_grpmems)) {
       struct rtentry *rt = gt->gt_route;
       VIFM_SET(vifi, gt->gt_grpmems);
       logit(LOG_DEBUG, 0, "forw again (%s %s) gm:%x vif:%d",
       inet_fmts(rt->rt_origin, rt->rt_originmask),
       inet_fmt(gt->gt_mcastgrp), gt->gt_grpmems, vifi);

       prun_add_ttls(gt);
       update_kernel(gt);
#ifdef RSRR
       /* Send route change notification to reservation protocol. */
       rsrr_cache_send(gt,1);
#endif /* RSRR */
   }
}


static const char *
scaletime(u_long t)
{
   static char buf1[5];
   static char buf2[5];
   static char *buf=buf1;
   char s;
   char *p;

   p = buf;
   if (buf == buf1)
       buf = buf2;
   else
       buf = buf1;

   if (t < 120) {
       s = 's';
   } else if (t < 3600) {
       t /= 60;
       s = 'm';
   } else if (t < 86400) {
       t /= 3600;
       s = 'h';
   } else if (t < 864000) {
       t /= 86400;
       s = 'd';
   } else {
       t /= 604800;
       s = 'w';
   }
   if (t > 999)
       return "*** ";

   snprintf(p, 5, "%3d%c", (int)t, s);

   return p;
}

/*
* Print the contents of the cache table on file 'fp2'.
*/
void
dump_cache(FILE *fp2)
{
   struct rtentry *r;
   struct gtable *gt;
   struct stable *st;
   vifi_t i;
   time_t thyme = time(0);

   fprintf(fp2,
           "Multicast Routing Cache Table (%d entries)\n%s", kroutes,
   " Origin             Mcast-group     CTmr  Age Ptmr IVif Forwvifs\n");

   for (gt = kernel_no_route; gt; gt = gt->gt_next) {
       if (gt->gt_srctbl) {
           fprintf(fp2, " %-18s %-15s %-4s %-4s    - -1\n",
               inet_fmts(gt->gt_srctbl->st_origin, 0xffffffff),
               inet_fmt(gt->gt_mcastgrp), scaletime(gt->gt_timer),
               scaletime(thyme - gt->gt_ctime));
           fprintf(fp2, ">%s\n", inet_fmt(gt->gt_srctbl->st_origin));
       }
   }

   for (gt = kernel_table; gt; gt = gt->gt_gnext) {
       r = gt->gt_route;
       fprintf(fp2, " %-18s %-15s",
           inet_fmts(r->rt_origin, r->rt_originmask),
           inet_fmt(gt->gt_mcastgrp));

       fprintf(fp2, " %-4s", scaletime(gt->gt_timer));

       fprintf(fp2, " %-4s %-4s ", scaletime(thyme - gt->gt_ctime),
                       gt->gt_prsent_timer ? scaletime(gt->gt_prsent_timer) :
                                             "   -");

       fprintf(fp2, "%2u%c%c ", r->rt_parent,
           gt->gt_prsent_timer ? 'P' : ' ',
           VIFM_ISSET(r->rt_parent, gt->gt_scope) ? 'B' : ' ');

       for (i = 0; i < numvifs; ++i) {
           if (VIFM_ISSET(i, gt->gt_grpmems))
               fprintf(fp2, " %u ", i);
           else if (VIFM_ISSET(i, r->rt_children) &&
                    !VIFM_ISSET(i, r->rt_leaves))
               fprintf(fp2, " %u%c", i,
                       VIFM_ISSET(i, gt->gt_scope) ? 'b' : 'p');
       }
       fprintf(fp2, "\n");
       for (st = gt->gt_srctbl; st; st = st->st_next) {
           fprintf(fp2, ">%s\n", inet_fmt(st->st_origin));
       }
#ifdef DEBUG_PRUNES
       for (pt = gt->gt_pruntbl; pt; pt = pt->pt_next) {
           fprintf(fp2, "<r:%s v:%d t:%d\n", inet_fmt(pt->pt_router),
               pt->pt_vifi, pt->pt_timer);
       }
#endif
   }
}

/*
* Traceroute function which returns traceroute replies to the requesting
* router. Also forwards the request to downstream routers.
*
* no: promoted u_char
*/
void
accept_mtrace(u_int32_t src, u_int32_t dst, u_int32_t group, char *data,
             u_int no, int datalen)
{
   u_char type;
   struct rtentry *rt;
   struct gtable *gt;
   struct tr_query *qry;
   struct tr_resp  *resp;
   int vifi;
   char *p;
   int rcount;
   int errcode = TR_NO_ERR;
   int resptype;
   struct timeval tp;
   struct sioc_vif_req v_req;
   struct sioc_sg_req sg_req;

   /* Remember qid across invocations */
   static u_int32_t oqid = 0;

   /* timestamp the request/response */
   gettimeofday(&tp, 0);

   /*
    * Check if it is a query or a response
    */
   if (datalen == QLEN) {
       type = QUERY;
       logit(LOG_DEBUG, 0, "Initial traceroute query rcvd from %s to %s",
           inet_fmt(src), inet_fmt(dst));
   }
   else if ((datalen - QLEN) % RLEN == 0) {
       type = RESP;
       logit(LOG_DEBUG, 0, "In-transit traceroute query rcvd from %s to %s",
           inet_fmt(src), inet_fmt(dst));
       if (IN_MULTICAST(ntohl(dst))) {
           logit(LOG_DEBUG, 0, "Dropping multicast response");
           return;
       }
   }
   else {
       logit(LOG_WARNING, 0, "%s from %s to %s",
           "Non decipherable traceroute request received",
           inet_fmt(src), inet_fmt(dst));
       return;
   }

   qry = (struct tr_query *)data;

   /*
    * if it is a packet with all reports filled, drop it
    */
   if ((u_int)(rcount = (datalen - QLEN)/RLEN) == no) {
       logit(LOG_DEBUG, 0, "packet with all reports filled in");
       return;
   }

   logit(LOG_DEBUG, 0, "s: %s g: %s d: %s ",
           inet_fmt(qry->tr_src),
           inet_fmt(group),
           inet_fmt(qry->tr_dst));
   logit(LOG_DEBUG, 0, "rttl: %d rd: %s", qry->tr_rttl,
           inet_fmt(qry->tr_raddr));
   logit(LOG_DEBUG, 0, "rcount:%d, qid:%06x", rcount, qry->tr_qid);

   /* determine the routing table entry for this traceroute */
   rt = determine_route(qry->tr_src);
   if (rt) {
       logit(LOG_DEBUG, 0, "rt parent vif: %d rtr: %s metric: %d",
               rt->rt_parent, inet_fmt(rt->rt_gateway),
               rt->rt_metric);
       logit(LOG_DEBUG, 0, "rt origin %s",
               inet_fmts(rt->rt_origin, rt->rt_originmask));
   } else
       logit(LOG_DEBUG, 0, "...no route");

   /*
    * Query type packet - check if rte exists
    * Check if the query destination is a vif connected to me.
    * and if so, whether I should start response back
    */
   if (type == QUERY) {
       if (oqid == qry->tr_qid) {
           /*
            * If the multicast router is a member of the group being
            * queried, and the query is multicasted, then the router can
            * receive multiple copies of the same query.  If we have already
            * replied to this traceroute, just ignore it this time.
            *
            * This is not a total solution, but since if this fails you
            * only get N copies, N <= the number of interfaces on the router,
            * it is not fatal.
            */
           logit(LOG_DEBUG, 0, "ignoring duplicate traceroute packet");
           return;
       }

       if (rt == NULL) {
           logit(LOG_DEBUG, 0, "Mcast traceroute: no route entry %s",
                  inet_fmt(qry->tr_src));
           if (IN_MULTICAST(ntohl(dst)))
               return;
       }
       vifi = find_vif(qry->tr_dst, 0);

       if (vifi == NO_VIF) {
           /* The traceroute destination is not on one of my subnet vifs. */
           logit(LOG_DEBUG, 0, "Destination %s not an interface",
                  inet_fmt(qry->tr_dst));
           if (IN_MULTICAST(ntohl(dst)))
               return;
           errcode = TR_WRONG_IF;
       } else if (rt != NULL && !VIFM_ISSET(vifi, rt->rt_children)) {
           logit(LOG_DEBUG, 0,
                   "Destination %s not on forwarding tree for src %s",
                  inet_fmt(qry->tr_dst),
                  inet_fmt(qry->tr_src));
           if (IN_MULTICAST(ntohl(dst)))
               return;
           errcode = TR_WRONG_IF;
       }
   }
   else {
       /*
        * determine which interface the packet came in on
        * RESP packets travel hop-by-hop so this either traversed
        * a tunnel or came from a directly attached mrouter.
        */
       if ((vifi = find_vif(src, dst)) == NO_VIF) {
           logit(LOG_DEBUG, 0, "Wrong interface for packet");
           errcode = TR_WRONG_IF;
       }
   }

   /* Now that we've decided to send a response, save the qid */
   oqid = qry->tr_qid;

   logit(LOG_DEBUG, 0, "Sending traceroute response");

   /* copy the packet to the sending buffer */
   p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN;

   bcopy(data, p, datalen);

   p += datalen;

   /*
    * If there is no room to insert our reply, coopt the previous hop
    * error indication to relay this fact.
    */
   if (p + sizeof(struct tr_resp) > send_buf + RECV_BUF_SIZE) {
       resp = (struct tr_resp *)p - 1;
       resp->tr_rflags = TR_NO_SPACE;
       rt = NULL;
       goto sendit;
   }

   /*
    * fill in initial response fields
    */
   resp = (struct tr_resp *)p;
   bzero(resp, sizeof(struct tr_resp));
   datalen += RLEN;

   resp->tr_qarr    = htonl((tp.tv_sec + JAN_1970) << 16) +
                               ((tp.tv_usec >> 4) & 0xffff);

   resp->tr_rproto  = PROTO_DVMRP;
   if (errcode != TR_NO_ERR) {
       resp->tr_rflags  = errcode;
       rt = NULL;      /* hack to enforce send straight to requestor */
       goto sendit;
   }
   resp->tr_outaddr = uvifs[vifi].uv_lcl_addr;
   resp->tr_fttl    = uvifs[vifi].uv_threshold;
   resp->tr_rflags  = TR_NO_ERR;

   /*
    * obtain # of packets out on interface
    */
   v_req.vifi = vifi;
   if (ioctl(igmp_socket, SIOCGETVIFCNT, (char *)&v_req) >= 0)
       resp->tr_vifout  =  htonl(v_req.ocount);

   /*
    * fill in scoping & pruning information
    */
   if (rt)
       for (gt = rt->rt_groups; gt; gt = gt->gt_next) {
           if (gt->gt_mcastgrp >= group)
               break;
       }
   else
       gt = NULL;

   if (gt && gt->gt_mcastgrp == group) {
       sg_req.src.s_addr = qry->tr_src;
       sg_req.grp.s_addr = group;
       if (ioctl(igmp_socket, SIOCGETSGCNT, (char *)&sg_req) >= 0)
           resp->tr_pktcnt = htonl(sg_req.pktcnt);

       if (VIFM_ISSET(vifi, gt->gt_scope))
           resp->tr_rflags = TR_SCOPED;
       else if (gt->gt_prsent_timer)
           resp->tr_rflags = TR_PRUNED;
       else if (!VIFM_ISSET(vifi, gt->gt_grpmems)) {
           if (VIFM_ISSET(vifi, rt->rt_children) &&
               !VIFM_ISSET(vifi, rt->rt_leaves))
               resp->tr_rflags = TR_OPRUNED;
           else
               resp->tr_rflags = TR_NO_FWD;
       }
   } else {
       if (scoped_addr(vifi, group))
           resp->tr_rflags = TR_SCOPED;
       else if (rt && !VIFM_ISSET(vifi, rt->rt_children))
           resp->tr_rflags = TR_NO_FWD;
   }

   /*
    *  if no rte exists, set NO_RTE error
    */
   if (rt == NULL) {
       src = dst;              /* the dst address of resp. pkt */
       resp->tr_inaddr   = 0;
       resp->tr_rflags   = TR_NO_RTE;
       resp->tr_rmtaddr  = 0;
   } else {
       /* get # of packets in on interface */
       v_req.vifi = rt->rt_parent;
       if (ioctl(igmp_socket, SIOCGETVIFCNT, (char *)&v_req) >= 0)
           resp->tr_vifin = htonl(v_req.icount);

       MASK_TO_VAL(rt->rt_originmask, resp->tr_smask);
       src = uvifs[rt->rt_parent].uv_lcl_addr;
       resp->tr_inaddr = src;
       resp->tr_rmtaddr = rt->rt_gateway;
       if (!VIFM_ISSET(vifi, rt->rt_children)) {
           logit(LOG_DEBUG, 0,
                   "Destination %s not on forwarding tree for src %s",
                  inet_fmt(qry->tr_dst),
                  inet_fmt(qry->tr_src));
           resp->tr_rflags = TR_WRONG_IF;
       }
       if (rt->rt_metric >= UNREACHABLE) {
           resp->tr_rflags = TR_NO_RTE;
           /* Hack to send reply directly */
           rt = NULL;
       }
   }

sendit:
   /*
    * if metric is 1 or no. of reports is 1, send response to requestor
    * else send to upstream router.  If the upstream router can't handle
    * mtrace, set an error code and send to requestor anyway.
    */
   logit(LOG_DEBUG, 0, "rcount:%d, no:%d", rcount, no);

   if (((u_int)(rcount + 1) == no) || (rt == NULL) || (rt->rt_metric == 1)) {
       resptype = IGMP_MTRACE_REPLY;
       dst = qry->tr_raddr;
   } else
       if (!can_mtrace(rt->rt_parent, rt->rt_gateway)) {
           dst = qry->tr_raddr;
           resp->tr_rflags = TR_OLD_ROUTER;
           resptype = IGMP_MTRACE_REPLY;
       } else {
           dst = rt->rt_gateway;
           resptype = IGMP_MTRACE_QUERY;
       }

   if (IN_MULTICAST(ntohl(dst))) {
       /*
        * Send the reply on a known multicast capable vif.
        * If we don't have one, we can't source any multicasts anyway.
        */
       if (phys_vif != -1) {
           logit(LOG_DEBUG, 0, "Sending reply to %s from %s",
               inet_fmt(dst), inet_fmt(uvifs[phys_vif].uv_lcl_addr));
           k_set_ttl(qry->tr_rttl);
           send_igmp(uvifs[phys_vif].uv_lcl_addr, dst,
                     resptype, no, group,
                     datalen);
           k_set_ttl(1);
       } else
           logit(LOG_INFO, 0, "No enabled phyints -- %s",
                       "dropping traceroute reply");
   } else {
       logit(LOG_DEBUG, 0, "Sending %s to %s from %s",
           resptype == IGMP_MTRACE_REPLY ?  "reply" : "request on",
           inet_fmt(dst), inet_fmt(src));

       send_igmp(src, dst,
                 resptype, no, group,
                 datalen);
   }
   return;
}