#define	INCLUDE_IF
#define	INCLUDE_IOCTL
#define	INCLUDE_ROUTE
#define INCLUDE_KINFO
#include "include.h"

#ifdef	IPV6_MULTICAST

#include "inet6.h"
#include "inet6_multi.h"
#include "krt.h"
#include "krt_var.h"
#include "krt_ipv6multi.h"


/*
 * krt_generate_mfc()
 *
 * Process a request to generate a Multicast Forwarding Cache entry.
 * Each multicast routing protocol that has registered a receive routine
 * will be asked to contribute to the outgoing interface list.
 *
 */

void
krt_generate_mfc __PF4(error, int,
		       dst, sockaddr_un *,
		       ifap, if_addr *,
		       src, sockaddr_un *)
{
    int		call = 0;
    mfc		*mfcp = (mfc *) 0;
    struct	krt_mfc_recv *recv_list;
    upstream	*up;


    switch (error) {
	case EADDRINUSE:

	    mfcp = mfc_locate_mfc_v6(dst, src);
	    if (mfcp) {
		    /*
		     * This should never happen
		     */
		if (mfcp->upstream_ifap == ifap) {
		    trace_tp(krt_task,
			TR_KRT_REQUEST,
			0,
			("krt_generate_mfc: Kernel and Gated disagree on correct interface for packet from %A to group %A arriving on interface %A",
			  src,
			  dst,
			  ifap->ifa_addr));
		} else {
		    trace_tp(krt_task,
			 TR_KRT_REQUEST,
			 0,
			 ("krt_generate_mfc: Duplicate packet arrived for group %A source %A via interface %A. Correct Interface was %A",
			  dst,
			  src,
			  ifap->ifa_addr,
			  mfcp->upstream_ifap->ifa_addr));
		    call++;
		}
	    } else {
		trace_tp(krt_task,
		     TR_KRT_REQUEST,
		     0,
		     ("krt_generate_mfc: Can't locate mfc entry for group %A source %A",
		      dst,
		      src));
	    }
	    break;

	case EADDRNOTAVAIL:

	    trace_tp(krt_task,
		     /*TR_KRT_REQUEST, */ TR_NORMAL,
		     0,
		     ("krt_generate_mfc: Multicast Forwarding Cache entry requested for group %A source %A",
		      dst,
		      src));
	    up = krt_locate_upstream(src, icmpv6_group_get_ifproto(ifap));
	    if (!up) {
			trace_tp(krt_task,
				 /*TR_KRT_REQUEST,*/ TR_NORMAL,
				 0,
				 ("krt_generate_mfc: No Multicast routing protocol configured on interface %A(%s). Deleting forwarding cache entry for group %A source %A",
				  ifap->ifa_addr,
				  ifap->ifa_link->ifl_name,
				  dst,
				  src));
			(void) krt_delete_cache(dst, src);

	    } else if (up->ifap) {
			/*
			 * need to check for point to point case
			 */
			if (up->ifap != ifap) {
				trace_tp(krt_task,
				/*TR_KRT_REQUEST,*/ TR_NORMAL,
				0,
				("krt_generate_mfc: Ignoring packet from %A to group %A arriving on incorrect interface %A expected interface %A(%s)",
				  src,
				  dst,
				  ifap->ifa_addr,
				  up->ifap->ifa_addr,
				  up->ifap->ifa_link->ifl_name));
		} else {
		    mfcp = mfc_alloc_node_v6();
		    mfcp->upstream_ifap = up->ifap;
		    bcopy(src, &mfcp->mfc_src, sizeof(sockaddr_un));
		    mfcp->mfc_pfx = INET6_NODE_NOMASK;
		    mfcp->mfc_ctime = time_sec;
		    mfcp->mfc_rtime = time_sec;
		    mfcp->mfc_use = 1;
		    mfcp->mfc_lastuse = 0;
		    mfcp->mfc_proto = 0;
		    mfcp->mfc_callback = (void *) 0;
		    mfcp->prune_up = (prune_list *) 0;
		    mfcp->prune_down.if_forw = &mfcp->prune_down;
		    mfcp->prune_down.if_back = &mfcp->prune_down;
		    mfcp->pimv6_assert = (assert_list *) 0;
		    mfcp->msl = (mfc_src_list *) 0;
		    bzero(&mfcp->rpf_addr, sizeof(struct in6_addr));
		    mfcp->ds_count = 0;
		    mfcp->ds = mfc_alloc_downstream_v6();
		    mfcp->ds->forw = mfcp->ds->back = mfcp->ds;
			/*
			 * Fill in the downstream interface list by merging
			 * the list from all multicast routing protocols below
			 */

		    call++;
		}
	    } else {
			trace_tp(krt_task,
				 TR_KRT_REQUEST,
				 0,
				 ("krt_generate_mfc: No unicast route back to source. Deleting forwarding cache entry for group %A source %A",
				  dst,
				  src));
			(void) krt_delete_cache(dst, src);
	    }
	    break;
	default:
	    trace_tp(krt_task,
		     /*TR_KRT_REQUEST,*/ TR_NORMAL,
		     0,
		     ("krt_generate_mfc: Unknown error type: %s",
		      strerror(error)));
	    break;
    }

    if (call) {
		if (error == EADDRNOTAVAIL) {
			/*
			 * insert cache entry in multicast tree of trees
			 */
			if (mfc_add_node_v6(dst, mfcp)) {
				mfc_free_downstream_v6(mfcp->ds);
				return;
			}
		}
		MFC_RECV_SCAN(recv_list, &krt_mfc_recv_head) {
			if (recv_list->errno == error) {
				(*recv_list->recv_routine)(error, ifap, mfcp);
			}
		} MFC_RECV_SCAN_END(recv_list, &krt_mfc_recv_head);
		if (error == EADDRNOTAVAIL) {
			/*
			 * resolve kernel forwarding cache entry
			 * And we really get useful MFC in hand
			 * If Daemon can not resolve the request, 
			 * we leave the kernel MFC untouched.
			 */
			krt_resolve_cache(dst, src, mfcp);
		}
    }
}

/*
 * This routine will request the kernel version of the multicast
 * forwarding cache entry. It is useful when getting the use count
 * from the kernel.
 */

int
krt_request_cache(mfcp, callback)
mfc *mfcp;
_PROTOTYPE(callback,
	   void,
	   (mfc *));
{
    int rc = 0;
    struct sockaddr *ap;
    sockaddr_un dst, src;
    struct rt_msghdr *rtp;
    size_t size = sizeof *rtp;

    sockcopy(&mfcp->mfc_group->group_key, &dst);
    sockcopy(&mfcp->mfc_src, &src);

    if (TRACE_TP(krt_task, TR_KRT_REQUEST)) {
		tracef("KERNEL %-6s group %-15A source %-15A",
		   trace_state(rtm_type_bits, RTM_GET - 1),
		   &dst,
	       &src);
    }

    rtp = (struct rt_msghdr *) task_mem_malloc(krt_task, size);
    bzero((caddr_t) rtp, size);

    rtp->rtm_addrs = 0;
    rtp->rtm_type = RTM_GET;
    rtp->rtm_version = RTM_VERSION;
    rtp->rtm_flags = RTF_MULTICAST | RTF_HOST;

    size += dst.in6.gin6_len;
    size += src.in6.gin6_len;
    ap = (struct sockaddr *) (rtp + 1);

    bcopy((caddr_t) &dst, (caddr_t) ap, (size_t) dst.in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_DST);

    bcopy((caddr_t) &src, (caddr_t) ap, (size_t) src.in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_AUTHOR);

    rtp->rtm_msglen = (caddr_t) ap - (caddr_t) rtp;

    mfcp->mfc_callback = callback;

    /* If this is the first entry on the queue, run the queue */
    trace_tp(krt_task,
	     TR_KRT_REQUEST,
	     0,
	     (NULL));
    if (task_set_option(krt_task,
			TASKOPTION_USELOOPBACK,
			TRUE) < 0) {
		task_quit(errno);
    }
    rc =  krt_action(krt_task, rtp);
    if (task_set_option(krt_task,
			TASKOPTION_USELOOPBACK,
			FALSE) < 0) {
		task_quit(errno);
    }
    task_mem_free(krt_task, rtp);

    return (rc);
}

void
krt_resolve_cache  __PF3(dst, sockaddr_un *,
			 src, sockaddr_un *,
			 mfcp, mfc *)
{
    int count = 0;
    struct sockaddr *ap;
    struct rt_msghdr *rtp;
    flag_t flags = 0;
    size_t size = sizeof *rtp;
    sockaddr_un *handle = mfcp->upstream_ifap->ifa_addr;
    struct in6_addr last_addr = {IN6ADDR_ANY_INIT}; /* Change me, Yixin */
    downstream *dsp;
    caddr_t cp;

    if (!mfcp->upstream_ifap) {
	trace_tp(krt_task,
		 TR_KRT_REQUEST,
		 0,
		 ("krt_resolve_cache: upstream interface not specified"));
	return;
    }

    if (TRACE_TP(krt_task, TR_KRT_REQUEST)) {
	tracef("KERNEL %-6s group %-15A source %-15A",
	       trace_state(rtm_type_bits, RTM_CHANGE - 1),
	       dst,
	       src);
    }

    if (BIT_TEST(krt_options, KRT_OPT_NOINSTALL)) {
	trace_tp(krt_task,
		 TR_KRT_REQUEST,
		 0,
		 (NULL));

	return;
    }
    BIT_SET(flags, RTF_MULTICAST);
    BIT_SET(flags, RTF_HOST);

	/*
	 * count number of unique downstream interfaces
	 */
    DOWNSTREAM_LIST(dsp, mfcp->ds) {
	if (!SAME_ADDR6(sock2in6(dsp->ds_addr), last_addr))
	    count++;
    } DOWNSTREAM_LIST_END(dsp, mfcp->ds);

    if (count) {
	size += count * sizeof(struct ds_in6addr) +
		sizeof(struct sockaddr_inds);
    }

    size += dst->in6.gin6_len;
    size += handle->dl.gdl_len;
    size += src->in6.gin6_len;
    
    rtp = (struct rt_msghdr *) task_mem_malloc(krt_task, size);
    bzero((caddr_t) rtp, size);

    rtp->rtm_addrs = 0;
    rtp->rtm_type = RTM_CHANGE;
    rtp->rtm_version = RTM_VERSION;
    rtp->rtm_flags = flags;
    if (BIT_TEST(mfcp->upstream_ifap->ifa_state, IFS_UP)) {
	BIT_SET(rtp->rtm_flags, RTF_UP);
    }

    ap = (struct sockaddr *) (rtp + 1);

    bcopy((caddr_t) dst, (caddr_t) ap, (size_t) dst->in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_DST);

    bcopy((caddr_t) handle, (caddr_t) ap, (size_t) handle->dl.gdl_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_IFP);

    bcopy((caddr_t) src, (caddr_t) ap, (size_t) src->in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_AUTHOR);

    if (count) {
	struct sockaddr_inds *inds = (struct sockaddr_inds *) ap;
	struct ds_in6addr *ds_ia = (struct ds_in6addr *) inds->sin_data;

	inds->sin_len = count * sizeof(struct ds_in6addr) +
			 2 * sizeof(u_char) + sizeof(u_short);
	inds->sin_family = AF_INET6;
	inds->sin_num = count;

	bzero(&last_addr, sizeof(struct in6_addr));
	DOWNSTREAM_LIST(dsp, mfcp->ds) {
	    if (!SAME_ADDR6(sock2in6(dsp->ds_addr), last_addr)) {
		ds_ia->sin6_addr = sock2in6(dsp->ds_addr);
		ds_ia->hoplimit = dsp->ds_hoplimit;
		ds_ia->flags = 0;
		ds_ia++;
	    }
	} DOWNSTREAM_LIST_END(dsp, mfcp->ds);

	RTM_ADDR(ap);
	BIT_SET(rtp->rtm_addrs, RTA_DOWNSTREAM);
    }

    rtp->rtm_msglen = (caddr_t) ap - (caddr_t) rtp;

    /* If this is the first entry on the queue, run the queue */
    trace_tp(krt_task,
	     TR_KRT_REQUEST,
	     0,
	     (NULL));
    (void) krt_action(krt_task, rtp);
    task_mem_free(krt_task, rtp);

    return;
}

int 
krt_flush_cache __PF0(void)
{
    int flags = 0;
    size_t needed;
    int mib[6] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_DUMP, RTF_MULTICAST};
    int rlen, seqno = 0;
    char *buf, *next, *lim;
    register struct rt_msghdr *rtm;

    /* Yixin, stupid but easy way to turn off multicast forwarding */
    system("sysctl -w net.inet6.ipv6.mforwarding=0");
    sleep(1);

    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
	task_quit(EINVAL);
    if ((buf = (char*)task_mem_malloc(krt_task, needed)) == NULL)
	task_quit(EINVAL);
    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
        task_quit(EINVAL);
    lim = buf + needed;
    for (next = buf; next < lim; next += rtm->rtm_msglen) {
        rtm = (struct rt_msghdr *)next;
        if ((rtm->rtm_flags & (RTF_GATEWAY | RTF_MULTICAST)) == 0)
	    tracef("Wrong!!\n");
	/* continue; */
        rtm->rtm_type = RTM_DELETE;
        rtm->rtm_seq = seqno;
	krt_action(krt_task, rtm);
	seqno++;
    }
    task_mem_free(krt_task, buf);

    /* Yixin, stupid but easy way to turn on multicast forwarding */ 
    sleep(1);
    system("sysctl -w net.inet6.ipv6.mforwarding=1");
    return(0);
}

int
krt_delete_cache  __PF2(dst, sockaddr_un *,
			src, sockaddr_un *)
{
    int rc = 0;
    struct sockaddr *ap;
    static union {
	struct rt_msghdr msg;
	u_char buf[sizeof (struct rt_msghdr) + 8 * sizeof(struct in6_addr)];
    } u;
    struct rt_msghdr *rtp = &u.msg;
    flag_t flags = 0;
    size_t size = sizeof *rtp;

    if (TRACE_TP(krt_task, TR_KRT_REQUEST)) { 
	tracef("KERNEL %-6s group %-15A source %-15A",
	       trace_state(rtm_type_bits, RTM_DELETE - 1),
	       dst,
	       src);
    } 

    if (BIT_TEST(krt_options, KRT_OPT_NOINSTALL)) {
	trace_tp(krt_task,
		 TR_KRT_REQUEST,
		 0,
		 (NULL));

	return rc;
    }
    BIT_SET(flags, RTF_MULTICAST);
    BIT_SET(flags, RTF_HOST);

    size += dst->in6.gin6_len;
    size += src->in6.gin6_len;
    
    /* Allocate a block and clear it */
    assert(size < sizeof u);

    rtp->rtm_addrs = 0;
    rtp->rtm_type = RTM_DELETE;
    rtp->rtm_version = RTM_VERSION;
    rtp->rtm_flags = flags;

    ap = (struct sockaddr *) (rtp + 1);

    bcopy((caddr_t) dst, (caddr_t) ap, (size_t) dst->in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_DST);

    bcopy((caddr_t) src, (caddr_t) ap, (size_t) src->in6.gin6_len);
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_AUTHOR);

    rtp->rtm_msglen = (caddr_t) ap - (caddr_t) rtp;

    /* If this is the first entry on the queue, run the queue */
    trace_tp(krt_task,
	     TR_KRT_REQUEST,
	     0,
	     (NULL));
	return krt_action(krt_task, rtp);
}

void
krt_add_vif  __PF3(ifap, if_addr *,
                   threshold, u_int32,
                   ratelimit, u_int32)
{
        /*
         * This kernel only needs to add a vif if its a tunnel
         */

        /*
         * Warn that this kernel doesn't support rate limiting
         */
    if (ratelimit) {
        trace_tp(krt_task,
            TR_KRT_IFLIST,
            0,
            ("krt_add_vif: Kernel doesn't support rate limiting"));
    }
  
    return;
}

void
krt_del_vif  __PF1(ifap, if_addr *)   
{
}


#endif	/* IPV6_MULTICAST */
