#define INCLUDE_UDP
#include "include.h"
#include "targets.h"
#include "inet6.h"
#include "ripng_internal.h"

static u_int ripng_mc_count;



void
ripng_send __PF6(tp, task *,
		 ifap, if_addr *,
		 flags, flag_t,
		 addr, sockaddr_un *,
		 msg, struct ripng *,
		 size, size_t)
{
    int rc;
    u_short port;
#ifdef INRIA
    struct msghdr m;
    struct iovec iov;
    struct in6_pktinfo pi;
    struct {
	struct cmsghdr cm;
	char cs[40];
    } c;
#endif /* INRIA */

    if (!(port = sock2port6(addr))) {
	sock2port6(addr) = htons(RIPNG_PORT);
    }

    switch(inet6_scope_of(addr)) {
      case INET6_SCOPE_NONE:
	rc = -1;
	break;

      case INET6_SCOPE_MULTICAST:
	/* Multicast sends fail if MSG_DONTROUTE is set */
	BIT_RESET(flags, MSG_DONTROUTE);

#ifndef INRIA
	(void) task_set_option(tp,
			       TASKOPTION_MULTI_IF,
			       ifap);
#endif /* !INRIA */
	/* fall through */
      default:

#ifdef INRIA
	bzero((caddr_t)&m, sizeof(struct msghdr));
	bzero((caddr_t)&iov, sizeof(struct iovec));
	bzero((caddr_t)&c, sizeof(c));
	if (addr)
		m.msg_name = (caddr_t)sock2unix(addr, &m.msg_namelen);
	m.msg_iov = &iov;
	m.msg_iovlen = 1;
	m.msg_iov->iov_base = (caddr_t)msg;
	m.msg_iov->iov_len = size;
	m.msg_control = (caddr_t)&c;
	m.msg_controllen = sizeof(c.cm) + sizeof(pi);
	c.cm.cmsg_len = m.msg_controllen;
	c.cm.cmsg_level = IPPROTO_IPV6;
	c.cm.cmsg_type = IPV6_PKTINFO;
	pi.ipi6_addr = sock2in6(ifap->ifa_addr_local);
	pi.ipi6_ifindex = ifap->ifa_link->ifl_index;
	bcopy((caddr_t)&pi, c.cs, sizeof(pi));
	
	rc = task_send_message(tp, (void_t) &m, size, flags);
#else /* INRIA */
	rc = task_send_packet(tp, (void_t) msg, size, flags, addr);
#endif /* INRIA */
	break;
    }

    ripng_trace(tp->task_trace,
		rc,
		ifap,
		addr,
		msg,
		size,
		TRUE);

    sock2port6(addr) = port;
}

/* Send RIPng updates to all targets on the list */
void
ripng_supply __PF5(tlp, target *,
		   dst, sockaddr_un *,
		   flags, flag_t,
		   send_flags, flag_t,
		   flash_update, int)
{
    u_int count = 0;
    u_int changes = 0;
    register td_entry *tdp;
    struct ripng *hdr = task_get_send_buffer(struct ripng *);
    struct ripng_netinfo *first = (struct ripng_netinfo *) ((void_t) (hdr + 1));
    struct ripng_netinfo *rte = first;
    struct ripng_netinfo *last = (struct ripng_netinfo *)((caddr_t)hdr + (tlp->target_ifap->ifa_mtu - sizeof(struct udphdr)));
    sockaddr_un *next_hop = sockdup(*tlp->target_src);

    bzero((caddr_t) hdr, sizeof(struct ripng));

    hdr->ripng_command = RIPNG_COMMAND_RESPONSE;
    hdr->ripng_version = RIPNG_VERSION_1;

    /* Open the routing table in case a holddown is over */
    rt_open(tlp->target_task);

    TD_LIST(tdp, &tlp->target_td) {
	int mask;

        if (BIT_TEST(tdp->td_flags, TDF_CHANGED) || !flash_update) {
 	    if (BIT_TEST(tdp->td_flags, TDF_CHANGED)) {
	        /* Reset the changed field */
	        BIT_RESET(tdp->td_flags, TDF_CHANGED);
	     }

	    if ((mask = inet6_prefix_mask(tdp->td_rt->rt_dest_mask)) < 0)
	        /* skip the contig mask */
	        continue;

	    if (((byte *)rte + sizeof(struct ripng_netinfo) >= (byte *)last)
                ||(((byte *)rte + 2*sizeof(struct ripng_netinfo) >= (byte *)last))
                  &&(!sockaddrcmp_in6(next_hop,RT_ROUTER(tdp->td_rt)))) {
	        /* Send the packet */

	        ripng_send(tlp->target_task,
		           tlp->target_ifap,
		           send_flags,
		           dst,
		           hdr,
		           (caddr_t)rte - (caddr_t)hdr);
	        count++;
	        rte = first;
	    }

#ifdef want_NH
            /* Add Next Hop RTE */
            if (!sockaddrcmp_in6(next_hop,RT_ROUTER(tdp->td_rt))) {
	        rte->ripng_tag = 0;
                rte->ripng_prefixlen = 0;
	        rte->ripng_metric = RIPNG_METRIC_NEXTHOP;
		if (sockaddrcmp_in6(*tlp->target_src,RT_ROUTER(tdp->td_rt))) {
                    sockfree(next_hop);
                    bzero(&rte->ripng_prefix, sizeof(struct in6_addr));
                    next_hop = sockdup(*tlp->target_src);
                } else {
                    /* Test if Link-Local address ??? */
                    sockfree(next_hop);
                    bcopy(&sock2in6(RT_ROUTER(tdp->td_rt)), &rte->ripng_prefix, sizeof(struct in6_addr));
                    next_hop = sockdup(RT_ROUTER(tdp->td_rt));
                }   
                rte++;                 
            }
#endif
	    rte->ripng_tag = 0;
	    /* struct copy */
            bcopy(&sock2in6(tdp->td_rt->rt_dest), &rte->ripng_prefix, sizeof(struct in6_addr));
            rte->ripng_prefixlen = (u_int8) mask;

            if (BIT_TEST(tdp->td_flags, TDF_HOLDDOWN|TDF_POISON)) {
	        rte->ripng_metric = RIPNG_METRIC_UNREACHABLE;
	    } else {
	        if (BIT_TEST(ripng_flags, RIPNGF_TERMINATE)) {
		    rte->ripng_metric = RIPNG_METRIC_SHUTDOWN;
	        } else {
		    rte->ripng_metric = tdp->td_metric;
	        }
	    }

	    rte++;
        }
    } TD_LIST_END(tdp, &tlp->target_td);

    if (rte > first) {
	ripng_send(tlp->target_task,
		   tlp->target_ifap,
		   send_flags,
		   dst,
		   hdr,
		   (int)rte - (int)hdr);
	count++;
    }

    rt_close(tlp->target_task, (gw_entry *) 0, changes, NULL);
}		   

/* Leave the RIPng group */
static void
ripng_mc_reset __PF2(tp, task *,
		     tlp, target *)
{
    if (BIT_TEST(tlp->target_flags, RIPNGTF_MC)) {
	(void) task_set_option(tp,
			       TASKOPTION_GROUP_DROP,
			       tlp->target_ifap,
			       ripng_addr);
	BIT_RESET(tlp->target_flags, RIPNGTF_MC);
	tlp->target_dst = &inet6_addr_any;	/* ??? */
	if (!--ripng_mc_count) {
	    krt_multicast6_delete(ripng_addr);
	}
    }
}

/* Join the RIPng group */
int
ripng_mc_set __PF2(tp, task *,
		   tlp, target *)
{
    int ret;

    if (BIT_TEST(tlp->target_flags, RIPNGTF_MC)) {
	/* already set */
	ret = TRUE;
    } else if (!BIT_TEST(tlp->target_ifap->ifa_state, IFS_MULTICAST)) {
	/* interface is not MC available */
	trace_log_tp(tp,
		     0,
		     LOG_WARNING,
		     ("ripng_mc_set: interface %s is multicast off",
		      tlp->target_ifap->ifa_link->ifl_name));
	BIT_RESET(tlp->target_flags, RIPNGTF_MC);
	ret = FALSE;
    } else if ((task_set_option(tp,
				TASKOPTION_GROUP_ADD,
				tlp->target_ifap,
				ripng_addr) < 0)
	       && (errno != EADDRNOTAVAIL)
	       && (errno != EADDRINUSE)) {
	/* fail to join */
	trace_log_tp(tp,
		     0,
		     LOG_WARNING,
		     ("ripng_mc_set: %s can't join the RIPng group(%A): %m",
		      tlp->target_ifap->ifa_link->ifl_name,
		      ripng_addr));
	BIT_RESET(tlp->target_flags, RIPNGTF_MC);
	ret = FALSE;
    } else {
	/* successful joined */
	BIT_SET(tlp->target_flags, RIPNGTF_MC);
	tlp->target_reset = ripng_mc_reset;
	if (!ripng_mc_count++) {
	    krt_multicast6_add(ripng_addr);
	}

	/* Disable reception of our own packets */
	if (task_set_option(tp,
			    TASKOPTION_MULTI_LOOP,
			    FALSE) < 0) {
	    /* Warn */
	    trace_only_tp(tp,
			  0,
			  ("ripng_mc_set: fail to disable reception our own packet"));
	}
	ret = TRUE;
    }

    if (ret == TRUE) {
	tlp->target_dst = &ripng_addr;
    } else {
	/* ??? */
	tlp->target_dst = &inet6_addr_any;
    }

    return ret;
}				 

void
ripng_job __PF2(tip, task_timer *,
		 interval, time_t)
{
    target *tlp;

    /* send to each interface */
    TARGET_LIST(tlp, &ripng_targets) {
	if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
	    ripng_supply(tlp,
			 *tlp->target_dst,
			 tlp->target_flags,
			 MSG_DONTROUTE,
			 FALSE);
	}
    } TARGET_LIST_END(tlp, &ripng_targets);

    trace_only_tp(tip->task_timer_task,
		  0,
		  ("ripng_jobs:"));

    task_timer_set_interval(tip, RIPNG_T_UPDATE);
}
