#define INCLUDE_IOCTL
#define INCLUDE_IF
#define INCLUDE_IF_TYPES
#define INCLUDE_MROUTE

#include "include.h"
#include "inet6.h"
#include "inet6_multi.h"
#include "krt_ipv6multi.h"
#include "icmpv6.h"
#include "icmpv6_group.h"

PROTOTYPE(icmpv6_group_job, static void, (task_timer *, time_t));
PROTOTYPE(icmpv6_group_timeout, static void, (task_timer *, time_t));
PROTOTYPE(icmpv6_group_control_set, static void, (task *, if_addr *));
PROTOTYPE(icmpv6_group_control_reset, static void, (task *, if_addr *));

#define ICMPV6_QUERY_OFFSET	1	/* don't send initial query for 1 sec */

const bits 	icmpv6_group_proto_bits[] = 
{
	{IPV6MULTI_PROTO_MOSPF, "MOSPF(v6)"},
	{IPV6MULTI_PROTO_DVMRP, "DVMRP(v6)"},
	{IPV6MULTI_PROTO_PIMV6, "PIMV6"},
	{IPV6MULTI_PROTO_CBT, "CBT(v6)"},
	{0}
};

/*
 * protocol defaults
 */
int	icmpv6_group_default_queryinterval;	/* default query interval */
int 	icmpv6_group_default_timeoutinterval;	/* default timeout interval */
static 	task_timer *icmpv6_group_timer_query = NULL; 	/* To send Host membership query */

/* 
 * local group database
 */

static block_t group_block_index_v6;

/*
 * configuration interface lists 
 */

adv_entry 	*icmpv6_group_int_policy = 0;	/* ICMPv6 group control info */

/* 
 * tracing details
 */

trace		*icmpv6_group_trace_options = {0}; 	/* trace flags */

const bits	icmpv6_group_trace_types[] = {
	{TR_DETAIL, "detail packets"},
        {TR_DETAIL_SEND, "detail send packets"},
        {TR_DETAIL_RECV, "detail recv packets"},
        {TR_PACKET, "packets"},
        {TR_PACKET_SEND, "send packets"},
        {TR_PACKET_RECV, "recv packets"},
        {TR_DETAIL_1, "detail query"},
        {TR_DETAIL_SEND_1, "detail send query"},
        {TR_DETAIL_RECV_1, "detail recv query"},
        {TR_PACKET_1, "query"},
        {TR_PACKET_SEND_1, "send query"},
        {TR_PACKET_RECV_1, "recv query"},
        {TR_DETAIL_2, "detail report"},
        {TR_DETAIL_SEND_2, "detail send report"},
        {TR_DETAIL_RECV_2, "detail recv report"},
        {TR_PACKET_2, "report"},
        {TR_PACKET_SEND_2, "send report"},
        {TR_PACKET_RECV_2, "recv report"},
        {TR_DETAIL_3, "detail leave"},
        {TR_DETAIL_SEND_3, "detail send leave"},
        {TR_DETAIL_RECV_3, "detail recv leave"},
        {TR_PACKET_3, "leave"},
        {TR_PACKET_SEND_3, "send leave"},
        {TR_PACKET_RECV_3, "recv leave"},
        {TR_DETAIL_4, "detail mtrace"},
        {TR_DETAIL_SEND_4, "detail send mtrace"},
        {TR_DETAIL_RECV_4, "detail recv mtrace"},
        {TR_PACKET_4, "mtrace"},
        {TR_PACKET_SEND_4, "send mtrace"},
        {TR_PACKET_RECV_4, "recv mtrace"},
        {0, NULL}
};     

/*
 * Create  callback list of routines to be called when an ICMPv6 group packet 
 * of a particular type is requested. Useful since DVMRP and PIM piggy back on
 * ICMPv6 group messages.
 */     

struct _icmpv6_group_recv_types {
	void	(*recv_routine) ();
};

static struct _icmpv6_group_recv_types icmpv6_group_recv_types[] = { 
        {0},                    /* ICMPV6_GROUPMEM_QUERY */
        {0},                    /* ICMPV6_GROUPMEM_REPORT */
        {0},                    /* ICMPV6_GROUPMEM_TERM */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
        {0},                    /* UNUSED */
	{0}
};

/*
 * Request ICMPv6 group packet types. This routine registers a callback function
 * to receive an ICMPv6 group packet of a particular type. The type should match
 * the group_type field in the packet header. The common group_type's are
 * enumerated above.
 *
 * Assumptions: Only one routine protocol needs a particular group_type. May
 * not be true for ICMPV6_GROUPMEM_QUERY messages which are used by multicast
 * routing protocol for router discovery.
 */
int
icmpv6_group_register_recv(code, callback)
	int	code;
_PROTOTYPE(callback, 
	   void,
	   (if_addr *,
	    struct icmpv6 *,
	    int));
{
	/*
	 * ICMPv6 must be running before any protocol can register receive
 	 * routines
	 */
	
	assert(icmpv6_task);

	trace_tp(icmpv6_task,
	 	TR_NORMAL,
		0,
		("icmpv6_group_register_recv: registering 0x%0x for packet
		 type 0x%0x", callback, code));

	if (icmpv6_group_recv_types[code - ICMPV6_GROUP_BASE_TYPE].recv_routine)
	{
		trace_only_tf(icmpv6_trace_options,
				0,
				("icmpv6_group_register_recv: packet type
				0x%0x already being received", 
				code));
		return -1;
	} else {
		icmpv6_group_recv_types[code - ICMPV6_GROUP_BASE_TYPE].recv_routine = callback;
		return 0;
	}
}

/*
 * No longer need ICMPv6 group packet types. The routing protocol no longer 
 * wished to receive packets of this particular type. Protocol may have been 
 * disabled on this interface.
 */
int
icmpv6_group_unregister_recv
__PF1(code, int)
{
        /*
         * ICMPv6 must be running before any protocol can register receive
         * routines
         */

	assert(icmpv6_task);

	trace_tp(icmpv6_task, 
		TR_NORMAL,
		0,
		("icmpv6_group_unregister_recv: removing receive routine for 
		type 0x%0x", code));

	if (icmpv6_group_recv_types[code - ICMPV6_GROUP_BASE_TYPE].recv_routine)
	{
		icmpv6_group_recv_types[code - ICMPV6_GROUP_BASE_TYPE].recv_routine = (void_t) 0;
	} else {
		trace_only_tf(icmpv6_trace_options,
				0,
				("icmpv6_group_unregister_recv: packet type
				0x%0x not being received", code ));
		return -1;
	}
	return 0;
}

/*
 * Create callback list of routines to be called when a new group is added or
 * an existing group is timed out.
 */
  
typedef struct _icmpv6_group_change_notify {
        struct _icmpv6_group_change_notify *forw, *back;
        void            (*change_routine) ();
}               icmpv6_group_change_notify;

static icmpv6_group_change_notify icmpv6_group_change_head =
{&icmpv6_group_change_head, &icmpv6_group_change_head};

static block_t  change_block_index_v6;

/*
 * Caller requests notification when a new group is added or an existing
 * group times out.
 */
  
#define CHANGE_LIST(cp, list)   { for (cp = (list)->forw; cp != list; cp = cp->forw)
#define CHANGE_LIST_END(cp, list)       if (cp == list) cp = (icmpv6_group_change_notify *) 0; }

int
icmpv6_register_group_change(callback)
_PROTOTYPE(callback,
           void,
           (int,
            if_addr *,
            u_int32));
{
        icmpv6_group_change_notify *cp;

        /*
         * ICMPv6 must be running before any protocol can register group change
         * routines
         */

        assert(icmpv6_task);

        trace_tp(icmpv6_task,
                 TR_NORMAL,
                 0,
                 ("icmpv6_register_group_change: registering address 0x%0x",
                  callback));

        CHANGE_LIST(cp, &icmpv6_group_change_head) {
                if (cp->change_routine == callback) {
                        /*
                         * already registered
                         */
                        trace_tp(icmpv6_task,
                                 TR_NORMAL,
                                 0,
                                 ("icmpv6_register_group_change: address 0x%0x 
				already registered",
                                  callback));
                        return -1;
                }
        } CHANGE_LIST_END(cp, &icmpv6_group_change_head);

        cp = (icmpv6_group_change_notify *) 
		task_block_alloc(change_block_index_v6);
        cp->change_routine = callback;

        INSQUE(cp, icmpv6_group_change_head.back);
        return 0;
}

/*
 * The routing protocol no longer want notified of changes to group
 * membership. Protocol may have been disabled on this interface.
 */
int
icmpv6_unregister_group_change(callback)
_PROTOTYPE(callback,
           void,
           (int,
            if_addr *,
            u_int32));
{
        icmpv6_group_change_notify *cp;

        /*
         * ICMPv6 must be running before any protocol can register group change
         * routines
         */

        assert(icmpv6_task);
        trace_tp(icmpv6_task,
                 TR_NORMAL,
                 0,
               ("icmpv6_unregister_group_change: unregistering address 0x%0x",
                callback));

        CHANGE_LIST(cp, &icmpv6_group_change_head) {
                if (cp->change_routine == callback) {
                        /* Found it! */

                        REMQUE(cp);

                        return 0;
                }
        } CHANGE_LIST_END(cp, &icmpv6_group_change_head);

        trace_tp(icmpv6_task,
                 TR_NORMAL,
                 0,
              ("icmpv6_unregister_group_change: address 0x%0x not registered",
               callback));
        return -1;
}

/*
 * Send an ICMPv6 Group Query to the specified interface.
 * 
 * Called when the query timer expires for all interfaces that this router
 * is the DR for.
 */

static void 
icmpv6_group_query
__PF2(ifap, if_addr *,
     dst, sockaddr_un *)
{
	int		rc;
	struct ipv6	*ipv6 = task_get_send_buffer(struct ipv6 *);
	struct icmpv6   *pkt = (struct icmpv6 *)(ipv6 + 1);
	
	pkt->icmp6_type = ICMP6_GROUPMEM_QUERY;
	/*
	 * the icmp6_code is useless so far
 	 */
	pkt->icmp6_code = 0;

	pkt->icmp6_mrd = ICMPV6_MAX_HOST_REPORT_DELAY;

	bzero(&pkt->icmp6_grp, sizeof(struct in6_addr));

	pkt->icmp6_cksum = 0;
	/* icmpv6_send() will compute the checksum */
	rc = icmpv6_send(pkt, sizeof(struct icmpv6), ifap->ifa_addr_local, 
				dst, ifap, 0);
}

/* ARGSUSED */
static void
                icmpv6_group_timeout
__PF2(tip, task_timer *,
      interval, time_t)
{
        if_addr        *ifap = (if_addr *) (tip->task_timer_data);
        struct v6_group_list *gp, *aged;
        struct v6_group_list *list = (struct v6_group_list *) ifap->icmpv6_group_if_group_list;
        icmpv6_group_change_notify *cp;

        /*
         * search for groups that aged out will all be at beginning of list
         */

        gp = list->forw;
        while (gp != list &&
            (time_sec - gp->refresh_time) >= icmpv6_group_default_timeoutinterval) {
                aged = gp;
                gp = gp->forw;
                REMQUE(aged);
                trace_tp(icmpv6_task,
                         TR_NORMAL,
                         0,
                   ("icmpv6_group_timeout: Group %A on interface %A(%s) timed out.",
                    sockbuild_in6(0, (byte*)&aged->group_addr),
                    ifap->ifa_addr_local,
                    ifap->ifa_link->ifl_name)); 
                /*
                 * Notify other protocols of removed group for pruning
                 */
                if (MADDR6_SCOPE(aged->group_addr) > MADDR6_SCP_LINK) {
                        CHANGE_LIST(cp, &icmpv6_group_change_head) {
                                if (cp->change_routine)
                                   (*cp->change_routine) (ICMPV6_GROUP_REMOVED,
                                                          ifap,
                                                          &aged->group_addr);
                        } CHANGE_LIST_END(cp, &icmpv6_group_change_head);
                }
                task_block_free(group_block_index_v6, (void_t) aged);
        }
        /*
         * if there's more entries in the list, reset the timer
         */
        if (list != list->forw) {
                task_timer_set((task_timer *) ifap->icmpv6_group_if_timer_timeout,
                               (time_t) 0,
                               icmpv6_group_default_timeoutinterval -
                               (time_sec - list->forw->refresh_time));
        }
}

/* ARGSUSED */
static void
                icmpv6_group_job
__PF2(tip, task_timer *,
      interval, time_t)
{
        int             stop_timer = 1;
        register if_addr *ifap;

        IF_ADDR(ifap) {
                struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
                if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
                        stop_timer = 0;
                        icmpv6_group_query(ifap,inet6_addr_allnodes);
                }
        } IF_ADDR_END(ifap);

        if (stop_timer) {
                task_timer_reset(icmpv6_group_timer_query);
        }
}

/*
 * Called when an ICMPv6 packet is available for reading.
 */
void
                icmpv6_group_recv
__PF2(tp, task *,
      ipv6, struct ipv6 *)
{
	register struct icmpv6 *icmpv6;
	if_addr *ifap;
	size_t	icmp6len;

	icmpv6 = (struct icmpv6 *) (ipv6 + 1);

	/* Remove IP header from length */
	icmp6len = ipv6->ip6_len;

	/* verify checksum */
	if (inet6_cksum((void_t) ipv6, icmp6len+sizeof(struct ipv6))) {
               trace_log_tf(icmpv6_trace_options,
                            0,
                            LOG_ERR,
                           ("icmpv6_group_recv: bad ICMPV6 checksum 
							from %A length %d",
                            task_recv_srcaddr, icmp6len));
               return;
	 }
	 ifap = if_withdst(task_recv_srcaddr);
	 if (ifap == 0)
		ifap = if_withlcladdr(task_recv_srcaddr, FALSE);

	 if (!ifap) {
		   trace_log_tf(icmpv6_trace_options,
						0,
						LOG_ERR,
         ("icmpv6_group_recv: ignoring icmpv6 group msg from remote source: %A",
						task_recv_srcaddr));
		   return;
	  }
	  if (!IS_LINKLADDR6(ifap->ifa_addr_local->in6.gin6_addr)) {
			/* In case the host doesn't use LL address to do report */
			if_addr	*ifap2;

			IF_ADDR(ifap2) {
                if ((ifap2->ifa_link == ifap->ifa_link)
				   && (ifap2->ifa_addr_local->in6.gin6_family == AF_INET6)
				   && (IS_LINKLADDR6(ifap2->ifa_addr_local->in6.gin6_addr))) {
						ifap = ifap2;
				   break;
                }
            } IF_ADDR_END(ifap2) ;
	  }
tracef("ICMPv6 GROUP recv from interface %A\n", ifap->ifa_addr_local);
	  /*
	   * If someone has registered for this packet type, call their
	   * receive routine.
	   */
	  if (icmpv6_group_recv_types[icmpv6->icmp6_type - ICMPV6_GROUP_BASE_TYPE].recv_routine)
			(*icmpv6_group_recv_types[icmpv6->icmp6_type - ICMPV6_GROUP_BASE_TYPE].recv_routine) (ifap, icmpv6, icmp6len);
}

/*
 * Receive Membership Query Packets
 */
static void
                icmpv6_recv_membership_query
__PF3(ifap, if_addr *,
      icmpv6, struct icmpv6 *,
      icmp6len, int)
{
	/* Don't know why it does this */
/*	icmpv6_recv_trace(ifap, icmpv6, icmp6len); */
}

/*
 * Receive Membership Report Packets
 */
static void
                icmpv6_recv_membership_report
__PF3(ifap, if_addr *,
      icmpv6, struct icmpv6 *,
      icmp6len, int)
{
        time_t          timeout;
        struct v6_group_list *gp;
        struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
        struct v6_group_list *list = (struct v6_group_list *) ifap->icmpv6_group_if_group_list;     
        icmpv6_group_change_notify *cp;
 
        if (task_recv_dstaddr && 
	!SAME_ADDR6(sock2in6(task_recv_dstaddr), icmpv6->icmp6_grp))
	 {
                trace_log_tf(icmpv6_group_trace_options,
                             0,
                             LOG_WARNING,
                             ("icmpv6_recv_membership_report: group %A doesn't match dest ipv6 %A", 
                              sockbuild_in6(0, (byte*)&icmpv6->icmp6_grp),
                              task_recv_dstaddr));
        }
        /*
         * If we are DR on this interface (set by the multicast routing
         * protocol running on this interface) then we will listen to Host
         * Membership Reports. Otherwise, we ignore them.
         */
        if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

                /* Search if group is already in database */
                GROUP_LIST(gp, list) {
                        if (SAME_ADDR6(gp->group_addr, icmpv6->icmp6_grp)) {
                                /* Found it! */

                                REMQUE(gp);
                                goto Found;
                        }
                } GROUP_LIST_END(gp, list);
        
                /* If not, Allocate a block to store this packet */
                gp = (struct v6_group_list *) task_block_alloc(group_block_index_v6); 
                gp->group_addr = icmpv6->icmp6_grp;
        
                /*
                 * Notify other protocols of new group for grafting
                 */
                if (MADDR6_SCOPE(gp->group_addr) > MADDR6_SCP_LINK) {
                        CHANGE_LIST(cp, &icmpv6_group_change_head) {
                                if (cp->change_routine)
                                        (*cp->change_routine) (ICMPV6_GROUP_ADDED,
                                                               ifap,
                                                     &gp->group_addr);
                        } CHANGE_LIST_END(cp, &icmpv6_group_change_head);
                }
Found:
                /* Store as sorted list with oldest first */
                INSQUE(gp, list->back);
                                         
                gp->last_addr = sock2in6(task_recv_srcaddr);
                gp->refresh_time = time_sec;    
         
                timeout = icmpv6_group_default_timeoutinterval - (time_sec - list->forw->refresh_time);
                /* if no timer running, create one */
                if (!ifap->icmpv6_group_if_timer_timeout) {
                        ifap->icmpv6_group_if_timer_timeout =  
                                (void_t) task_timer_create(icmpv6_task,
                                                           "Timeout",
                                                           (flag_t) 0,
                                                           (time_t) 0,
                                                           timeout,
                                                           icmpv6_group_timeout,
                                                           (void_t) ifap);
                }               
                /* set existing timer to new time */
                else {  
                        /*
                         * No way to reset data without deleting timer and
                         * re-creating it. So for now, just cheat  
                         */
                        ((task_timer *) ifap->icmpv6_group_if_timer_timeout)->task_timer_data = (void_t) ifap;
                        task_timer_set((task_timer *) ifap->icmpv6_group_if_timer_timeout,               
                                       (time_t) 0,
                                       timeout); 
                }       
        }
}

/*      
 * Receive Membership Leave Packets
 */
static void              
                icmpv6_recv_membership_leave
__PF3(ifap, if_addr *,  
      icmpv6, struct icmpv6 *,
      icmp6len, int)
{                                      
        struct v6_group_list *gp, *ggp;
        struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
        struct v6_group_list *list = (struct v6_group_list *) ifap->icmpv6_group_if_group_list;

        if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

                /* Search if group is already in database */

                GROUP_LIST(gp, list) {
                        if (SAME_ADDR6(gp->group_addr, icmpv6->icmp6_grp)) {
                                /* Found it! */

                                goto Found;
                        }
                } GROUP_LIST_END(gp, list);

                return;
Found:
     
                REMQUE(gp);

                /*
                 * set timer to go off in ICMPV6_MAX_HOST_REPORT_DELAY
                 * seconds to delete group unless we receive another
                 * membership report before that.
                 */

                gp->refresh_time = time_sec - icmpv6_group_default_timeoutinterval + ICMPV6_MAX_HOST_REPORT_DELAY;
                ggp = list->forw;
                while (ggp != list) {
                        if (gp->refresh_time < ggp->refresh_time) {
                                INSQUE(gp, ggp->back);
                                break;
                        }
                        ggp = ggp->forw;
                }
                /*
                 * reached end without inserting group, stick it at
                 * end
                 */
                if (ggp == list) {
                      INSQUE(gp, list->back);
                }
                /*
                 * send a group specific query
                 */
                icmpv6_group_query(ifap, sockbuild_in6(0, (byte*)&gp->group_addr));

                /*
                 * reset the timer
                 */
                task_timer_set((task_timer *) ifap->icmpv6_group_if_timer_timeout,   
                               (time_t) 0,
                               list->forw->refresh_time +
			icmpv6_group_default_timeoutinterval - time_sec);
        }
}

/*              
 * Enable a group to be received on the specified interface
 */
                
int             
                icmpv6_group_add
__PF2(ifap, if_addr *, 
      group, sockaddr_un *)
{
        krt_multicast6_add(group);
        return task_set_option(icmpv6_task, TASKOPTION_GROUP_ADD, ifap, group);
}

/* 
 * Disable a group being received on the specified interface
 */

int
                icmpv6_group_drop
__PF2(ifap, if_addr *,
      group, sockaddr_un *)
{
        krt_multicast6_delete(group); 
        return task_set_option(icmpv6_task, TASKOPTION_GROUP_DROP, ifap, group);
}

/*
 * Enable DR status on interface. This routine is called by the multicast
 * routing protocol running on this interface. A DR election is performed
 * with other multicast routers on shared lans and if elected, this routine
 * is called to begin sending Host Membership Queries. It also starts
 * maintaining a Local Group Database of Host Membership Reports.
 */
void
                icmpv6_group_enable_dr_status
__PF1(ifap, if_addr *)
{
        struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
        struct v6_group_list **listp = (struct v6_group_list **) & ifap->icmpv6_group_if_group_list;
        struct v6_group_list *list;

        if (!BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

                trace_tp(icmpv6_task,
/*                         TR_STATE,  */ TR_ALL, /* DEBUG, yixin */
                         0,
                   ("icmpv6_group_enable_dr_status: Elected DR on interface %A(%s)",
                    ifap->ifa_addr_local,
                    ifap->ifa_link->ifl_name));
                BIT_SET(ips->ips_state, IFPS_DR_STATUS);
                list = (struct v6_group_list *) task_block_alloc(group_block_index_v6);
                list->forw = list;
                list->back = list;

                *listp = list;

                /* if no timer running, create one */
                if (!icmpv6_group_timer_query) {
                        icmpv6_group_timer_query = task_timer_create(icmpv6_task,
                                                             "Query",
                                                             (flag_t) 0,
                                                 icmpv6_group_default_queryinterval,
                                                          ICMPV6_QUERY_OFFSET,
                                                             icmpv6_group_job,
                                                             (void_t) 0);
                } 
                /* if timer currently inactive, reactivate */
                else if (BIT_TEST(icmpv6_group_timer_query->task_timer_flags, TIMERF_INACTIVE)) {
                        task_timer_set(icmpv6_group_timer_query,
                                       icmpv6_group_default_queryinterval,
                                       ICMPV6_QUERY_OFFSET);
                } 
                krt_multicast6_add(inet6_addr_allnodes); 
        }
} 

/*                        
 * Disable DR status on interface. If the routing protocol determines this
 * multicast router is no longer DR on this interface, it disables DR status
 * and stops sending Host Membership Queries on this interface.
 */
void
                icmpv6_group_disable_dr_status
__PF1(ifap, if_addr *)
{
        struct v6_group_list *gp, *delete;
        struct ifa_ps  *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
        struct v6_group_list *list = (struct v6_group_list *) ifap->icmpv6_group_if_group_list;

        if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {

                trace_tp(icmpv6_task,
                         TR_STATE, 
                         0,
                         ("icmpv6_group_disable_dr_status: Relinquishing DR status on in
terface %A(%s)",
                          ifap->ifa_addr_local,
                          ifap->ifa_link->ifl_name));
 
                BIT_RESET(ips->ips_state, IFPS_DR_STATUS);
   
                /*
                 * stop timer from running
                 */ 
                if (ifap->icmpv6_group_if_timer_timeout) {
                        task_timer_reset((task_timer *) ifap->icmpv6_group_if_timer_timeout);   
                }
                assert(list);

                gp = list->forw;
                while (gp != list) {
                        delete = gp;
                        gp = gp->forw;
                        REMQUE(delete);
                        task_block_free(group_block_index_v6, (void_t) delete);
                }
                task_block_free(group_block_index_v6, (void_t) list);
                ifap->icmpv6_group_if_group_list = (void_t) 0;

                krt_multicast6_delete(inet6_addr_allnodes);
        }
}

/*
 * Since IPv6 Multicast is based on RPF, it is difficult to have more than a
 * single multicast routing protocol running on the same interface at the
 * same time. Therefore, the first protocol to grab the interface wins.
 */

int
icmpv6_group_set_ifproto
__PF2(ifap, if_addr *,
      proto, int)
{
        if (!ifap->icmpv6_group_if_proto) {
                ifap->icmpv6_group_if_proto = (void_t) proto;
                return TRUE;
        }
        trace_log_tf(icmpv6_group_trace_options,
                     0,
                     LOG_WARNING,
          ("icmpv6_group_set_ifproto: can't enable proto %s, %s already configured",
           icmpv6_group_proto_bits[proto].t_name,
           icmpv6_group_proto_bits[(int) ifap->icmpv6_group_if_proto].t_name));
        return FALSE;
}


void 
icmpv6_group_reset_ifproto
__PF2(ifap, if_addr *,
      proto, int)
{
	if ((int) ifap->icmpv6_group_if_proto == proto) {
		ifap->icmpv6_group_if_proto = (void_t) 0;
	}
}

int
icmpv6_group_get_ifproto
__PF1(ifap, if_addr *)
{
        /*
         * If the incoming interface is not specified, we assume DVMRP. This
         * is necessary for 3.3 and earlier kernels. It doesn't hurt anything
         * since if it came in on the wrong interface, it will be tossed
         * since the interface won't match.
         */
        if (!ifap)
                return IPV6MULTI_PROTO_DVMRP;
        else
                return (int) ifap->icmpv6_group_if_proto;
}

/* 
 * Initialize static variables 
 */
void
icmpv6_group_var_init()
{
	icmpv6_group_default_queryinterval = 125;
	icmpv6_group_default_timeoutinterval = 270; 

}

/* 
 * Initialize ICMPv6 Group Management
 */

static int	icmpv6_group_mrouting_protos = 0;
static int	icmpv6_group_assert_count = 0;

/* ARGSUSED */
void
icmpv6_group_init()
{
	icmpv6_group_register_recv(ICMP6_GROUPMEM_QUERY,
				icmpv6_recv_membership_query);
	icmpv6_group_register_recv(ICMP6_GROUPMEM_REPORT,
				icmpv6_recv_membership_report);
	icmpv6_group_register_recv(ICMP6_GROUPMEM_TERM,
				icmpv6_recv_membership_leave);
	group_block_index_v6 = task_block_init(sizeof(struct v6_group_list), "icmpv6_group_list");
	change_block_index_v6 = task_block_init(sizeof(icmpv6_group_change_notify), "icmpv6_group_change_notify");
}

/*
 * Deal with an interface status change
 */
static void 
icmpv6_group_ifachange
__PF2(tp, task *,
     ifap, if_addr *)
{
	struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];

	if (socktype(ifap->ifa_addr_local) != AF_INET6) {
		return;
	}
	switch (ifap->ifa_change) {
	case IFC_NOCHANGE:
	case IFC_ADD:
		if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
			icmpv6_group_control_set(tp, ifap);
			if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK) ||
			    BIT_TEST(ifap->ifa_state, IFS_POINTOPOINT)) {
				BIT_SET(ips->ips_state, IFPS_NOIN);
				BIT_SET(ips->ips_state, IFPS_NOOUT);
			}
		}
		break;

	case IFC_DELETE:
	case IFC_DELETE | IFC_UPDOWN:

		icmpv6_group_control_reset(tp, ifap);
		icmpv6_group_disable_dr_status(ifap);
		break;

	default:
		/* Something has changed */

		if (BIT_TEST(ifap->ifa_change, IFC_UPDOWN)) {
			if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
				/* Down to Up transition */

				icmpv6_group_control_set(tp, ifap);
				if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
					BIT_SET(ips->ips_state, IFPS_NOIN);
					BIT_SET(ips->ips_state, IFPS_NOOUT);
				}
			} else {
				/* UP to DOWN transition */

				icmpv6_group_control_reset(tp, ifap);
				icmpv6_group_disable_dr_status(ifap);
			}
		}
		break;
	}
}


static void
icmpv6_group_dump
__PF2(tp, task *,
     fd, FILE *)
{
	register if_addr *ifap;

/*	if(icmpv6_group_int_policy) {
		(void)fprintf(fd, "\tInterface policy:\n");
		control_interface_dump(fd, 2, icmpv6_group_inet_policy, icmpv6_group_int_dump);
	} */  /* Yixin, it may be useful */
	IF_ADDR(ifap) {
		struct v6_group_list *gp;
		struct ifa_ps *ips = &ifap->ifa_ps[RTPROTO_ICMPV6];
		struct v6_group_list *list = (struct v6_group_list *) ifap->icmpv6_group_if_group_list;
		if (BIT_TEST(ips->ips_state, IFPS_DR_STATUS)) {
			(void)fprintf(fd, "\n\tLocal Group Database for Interface %A(%s)\n",
				ifap->ifa_addr_local,
				ifap->ifa_link->ifl_name);
			fprintf(fd, "\t\tGroup           Last Reported By Refreshed\n");
			GROUP_LIST(gp, list) {
				fprintf(fd, "\t\t%-15A %-15A   %u seconds ago\n", 
				sockbuild_in6(0, (byte*)&gp->group_addr),
				sockbuild_in6(0, (byte*)&gp->last_addr),
				time_sec - gp->refresh_time);
			} GROUP_LIST_END(gp, list);
		}
	} IF_ADDR_END(ifap);

	(void)fprintf(fd, "\n");
}

static void
                icmpv6_group_control_set
__PF2(tp, task *,
      ifap, if_addr *)
{
        struct ifa_ps  *ips = &ifap->ifa_ps[tp->task_rtproto];
        config_entry  **list = config_resolv_ifa(icmpv6_group_int_policy,
                                                 ifap,
                                                 ICMPV6_GROUP_CONFIG_MAX);
   
        /* Init */
        icmpv6_group_control_reset(tp, ifap);

        if (list) {
                int             type = ICMPV6_GROUP_CONFIG_MAX;
                config_entry   *cp;

                /* Fill in the parameters */
                while (--type) {
                        if ((cp = list[type])) {
                                switch (type) {
                                case ICMPV6_GROUP_CONFIG_ENABLE:
                                        if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
                                                trace_only_tf(icmpv6_trace_options,
                                                              0,
                                                              ("icmpv6_group_control_set: can't enable icmpv6 group on loopback"));
                                        }
                                        if (BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {
                                                BIT_RESET(ips->ips_state, IFPS_NOIN);
                                                BIT_RESET(ips->ips_state, IFPS_NOOUT);
                                        } else {
                                                trace_only_tf(icmpv6_trace_options ,
                                                              0,
                                                              ("icmpv6_group_control_se: interface %A(%s) not multicast capable",
                                                             ifap->ifa_addr_local,
                                                 ifap->ifa_link->ifl_name));
                                        }
        
                                        break;   
                       
                                case ICMPV6_GROUP_CONFIG_DISABLE:
                                        BIT_SET(ips->ips_state, IFPS_NOIN);
                                        BIT_SET(ips->ips_state, IFPS_NOOUT);
                                        break;
                                }
                        }       
                }

                config_resolv_free(list, ICMPV6_GROUP_CONFIG_MAX);
        }       
}                       

/*      
 * Deal with interface policy
 */
static void
                icmpv6_group_control_reset
__PF2(tp, task *,
      ifap, if_addr *)  
{               
        struct ifa_ps  *ips = &ifap->ifa_ps[tp->task_rtproto];
                
        BIT_RESET(ips->ips_state, IFPS_RESET);
}        
