/*
 * Public Release 3
 * 
 * $Id: ripng_send.c,v 1.1.2.3 1998/10/20 19:31:40 swright Exp $
 */

/*
 * ------------------------------------------------------------------------
 * 
 * Copyright (c) 1996, 1997, 1998 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 and UC Berkeley's routing
 * daemon	 (routed).
 * 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_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;

    if (!(port = sock2port6(addr))) {
	sock2port6(addr) = 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 HITACHI_INET6
	(void) task_set_option(tp,
			       TASKOPTION_MULTI_IF,
			       ifap);
#endif /* HITACHI_INET6 */

	/* fall through */
      default:
#if defined(IPV6_PKTINFO) && defined(HITACHI_INET6)
        rc = task_send_message(tp, (void_t) msg, size, flags, addr, ifap);
#else  /* !(IPV6_PKTINFO && HITACHI_INET6) */
	rc = task_send_packet(tp, (void_t) msg, size, flags, addr);
#endif /* IPV6_PKTINFO && HITACHI_INET6 */
	break;

#ifdef IPV6_PKTINFO
    case INET6_SCOPE_LINKLOCAL:
        rc = task_send_message(tp, (void_t) msg, size, flags, addr, ifap);
	break;
#endif /* IPV6_PKTINFO */
    }

    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 = *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;
	    }

            /* Add Next Hop RTE */
#if 0
            if (tdp->td_rt->rt_routers[0]){
	        if (!sockaddrcmp_in6(next_hop, RT_ROUTER(tdp->td_rt))) {
		    rte->ripng_tag = 0;
		    rte->ripng_prefixlen = (u_int8) mask;
		    rte->ripng_metric = RIPNG_METRIC_NEXTHOP;
		    if (sockaddrcmp_in6(*tlp->target_src, RT_ROUTER(tdp->td_rt))) {
		        bzero(&rte->ripng_prefix, sizeof(struct in6_addr));
			next_hop = *tlp->target_src;
		    } else {
		        /* Test if Link-Local address ??? */
		        bcopy(&sock2in6(*tlp->target_src), &rte->ripng_prefix, sizeof(struct in6_addr));
			next_hop = *tlp->target_src;
		    }
		    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);
}
