/*
 * Public Release 3
 * 
 * $Id: krt_ipv6multi_rtsock.c,v 1.1.2.1 1999/01/28 17:46:04 wfs Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997 The Regents of the University of Michigan
 * All Rights Reserved
 *  
 * Royalty-free licenses to redistribute GateD Release
 * 3 in whole or in part may be obtained by writing to:
 * 
 * 	Merit GateDaemon Project
 * 	4251 Plymouth Road, Suite C
 * 	Ann Arbor, MI 48105
 *  
 * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE REGENTS OF THE
 * UNIVERSITY OF MICHIGAN AND MERIT DO NOT WARRANT THAT THE
 * FUNCTIONS CONTAINED IN THE SOFTWARE WILL MEET LICENSEE'S REQUIREMENTS OR
 * THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. The Regents of the
 * University of Michigan and Merit shall not be liable for
 * any special, indirect, incidental or consequential damages with respect
 * to any claim by Licensee or any third party arising from use of the
 * software. GateDaemon was originated and developed through release 3.0
 * by Cornell University and its collaborators.
 * 
 * Please forward bug fixes, enhancements and questions to the
 * gated mailing list: gated-people@gated.merit.edu.
 * 
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1990,1991,1992,1993,1994,1995 by Cornell University.
 *     All rights reserved.
 * 
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
 * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * GateD is based on Kirton's EGP, UC Berkeley's routing
 * daemon	 (routed), and DCN's HELLO routing Protocol.
 * Development of GateD has been supported in part by the
 * National Science Foundation.
 * 
 * ------------------------------------------------------------------------
 * 
 * Portions of this software may fall under the following
 * copyrights:
 * 
 * Copyright (c) 1988 Regents of the University of California.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms are
 * permitted provided that the above copyright notice and
 * this paragraph are duplicated in all such forms and that
 * any documentation, advertising materials, and other
 * materials related to such distribution and use
 * acknowledge that the software was developed by the
 * University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote
 * products derived from this software without specific
 * prior written permission.  THIS SOFTWARE IS PROVIDED
 * ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#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,
		 0,
		 ("krt_generate_mfc: Multicast Forwarding Cache entry requested for group %A source %A",
		  dst,
		  src));
	up = krt_locate_upstream(src, mld6_get_ifproto(ifap));
	if (!up) {
	    trace_tp(krt_task,
		     TR_KRT_REQUEST,
		     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,
			 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;
		if(up->nbr) {
		    mfcp->rpf_addr = sock2in6(up->nbr);
		}
		else {
		    mfcp->rpf_addr = in6addr_any;
		}
		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,
		 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 will return with the kernel information.
 * Unlike krt_request_cache, which will wait another cycle to get the 
 * information.
 */
void
krt_request_mfc(mfcp)
     mfc *mfcp;
{
    int rc = 0;
    int sock;
    struct sockaddr *ap;
    sockaddr_un dst, src;
    struct {
	struct  rt_msghdr m_rtm;
	char    m_space[512];
    }   m_rtmsg;
    struct rt_msghdr    *rtp = &m_rtmsg.m_rtm;
    size_t size = sizeof *rtp;

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

    mfcp->mfc_callback = (void *)0;
    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);
    }

    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 += socksize(&dst);
    size += socksize(&src);
    ap = (struct sockaddr *) (rtp + 1);

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

    bcopy((caddr_t) &src, (caddr_t) ap, (size_t) socksize(&src));
    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));
    if (task_set_option(krt_task,
			TASKOPTION_USELOOPBACK,
			TRUE) < 0) {
        task_quit(errno);
    }

    rtp->rtm_seq = 5555; 	/* Any number works */
    rtp->rtm_pid = task_pid;   
    rtp->rtm_errno = 0;  
                 
    sock = socket(PF_ROUTE, SOCK_RAW, 0);
    if (sock < 0) {
	tracef("krt_request_mfc: socket can not be created\n");
	task_quit(0); 
    }
    if (!BIT_TEST(task_state, TASKS_TEST)
	&& !BIT_TEST(krt_options, KRT_OPT_NOINSTALL)) {
	errno = 0;
	if (write(sock,
		  (caddr_t) rtp,
		  (size_t) rtp->rtm_msglen) < 0) {
	    rtp->rtm_errno = errno;
	}
    }
    do {
	rc = read(sock, (char *)&m_rtmsg, sizeof(m_rtmsg));
    } while (rc > 0 && rtp->rtm_seq != 5555);
    close(sock);
    mfcp->mfc_use = rtp->rtm_use;
}

/*
 * 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 += socksize(&dst);
    size += socksize(&src);
    ap = (struct sockaddr *) (rtp + 1);

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

    bcopy((caddr_t) &src, (caddr_t) ap, (size_t) socksize(&src));
    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};
    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 += socksize(dst);
    size += handle->dl.gdl_len;
    size += socksize(src);
    
    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) socksize(dst));
    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) socksize(src));
    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;
}

void 
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;

    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)
	    continue;
        rtm->rtm_type = RTM_DELETE;
        rtm->rtm_seq = seqno;
	krt_action(krt_task, rtm);
	seqno++;
    }
    task_mem_free(krt_task, buf);
}

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 += socksize(dst);
    size += socksize(src);
    
    /* 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) socksize(dst));
    RTM_ADDR(ap);
    BIT_SET(rtp->rtm_addrs, RTA_DST);

    bcopy((caddr_t) src, (caddr_t) ap, (size_t) socksize(src));
    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 */
