/*
 * Public Release 3
 * 
 * $Id: inet6.c,v 1.1.2.3 1998/10/20 19:29:47 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_IF
#include "include.h"
#include "inet6.h"

int inet6_ipforwarding = TRUE;		/* IP forwarding engine enabled */

static task *inet6_task;

const bits inet6_proto_bits[] = {
#ifdef	IPPROTO_ICMPV6
    { IPPROTO_ICMPV6,	"icmpv6" },
#endif	/* IPPROTO_ICMP */
#ifdef	IPPROTO_TCP    
    { IPPROTO_TCP,	"tcp" },
#endif	/* IPPROTO_TCP */
#ifdef	IPPROTO_UDP    
    { IPPROTO_UDP,	"udp" },
#endif	/* IPPROTO_UDP */
#ifdef	IPPROTO_OSPF    
    { IPPROTO_OSPF,	"ospf" },
#endif	/* IPPROTO_OSPF */
    { 0,		NULL }
} ;


#define	INET6_IFPS_ALLROUTERS	IFPS_KEEP1	/* We joined the all-routers group on this interface */

sockaddr_un *inet6_addr_allnodes;	/* All nodes multicast address */
sockaddr_un *inet6_addr_allrouters;	/* All routers multicast address */

static const bits inet6_if_bits[] = {
    { INET6_IFPS_ALLROUTERS, "AllRouters" },
    { 0 }
};

sockaddr_un *inet6_masks[130] = { 0 };
byte inet6_mask_list[SOCKADDR_IN6_LEN * (sizeof (struct in6_addr) * NBBY + 1)];

sockaddr_un *inet6_addr_default = 0;
sockaddr_un *inet6_addr_loopback = 0;
sockaddr_un *inet6_addr_any = 0;
sockaddr_un *inet6_addr_reject = 0;		/* Where reject routes need to point */
sockaddr_un *inet6_addr_blackhole = 0;		/* Where unreachable routes need to point */

struct martian6 {
	const char *dest6;
	const char *mask6;
	flag_t adv_flags;
	flag_t dm_flags;
};

static const struct martian6 inet6_martians[] = {
	{ "::1", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", ADVF_NO, 0 },
	{ "FE80::", "ffc0::", ADVF_NO, 0 },
	{ NULL }
};

/* Optimized routine to locate an IPv6 mask */
sockaddr_un *
inet6_mask_locate __PF1(mask, sockaddr_un *)
{
    u_int i;
    byte *cp = (byte *) mask + socksize(mask);
    byte *lp = (byte *) &mask->in6.gin6_addr;

    /* Find the last non-zero byte */
    while (--cp > lp) {
	if (*cp) {
	    break;
	}
    }

    /* A rough guess from the size */
    i = (cp - lp) * NBBY;

    /* And a fine tune */
    i += NBBY + 1 - ffs(*cp);

    return inet6_masks[i];
}

/*
 * Find interface on whole network of a possibly subnetted address.
 */
if_addr *
inet6_ifwithnet __PF1(dstaddr, sockaddr_un *)
{
    register if_addr *ifap = (if_addr *) 0;

    IF_ADDR(ifap) {
	if (socktype(ifap->ifa_addr) == AF_INET6
	    && ifap->ifa_net
	    && BIT_TEST(ifap->ifa_state, IFS_UP)
	    && sockaddrcmp_mask(dstaddr, ifap->ifa_net, ifap->ifa_netmask)) {
	    return ifap;
	}
    } IF_ADDR_END(ifap) ;

    return ifap;
}


/**/

/* Drop the all routers multicast group when we are not functioning as a router */
void
inet6_allrouters_drop __PF1(ifap, if_addr *)
{
    if (BIT_TEST(ifap->ifa_ps[RTPROTO_INET6].ips_state, INET6_IFPS_ALLROUTERS)) {

	if (!BIT_TEST(task_state, TASKS_TEST)) {
	    /* Not in test mode, drop group membership */

	    (void) task_set_option(inet6_task,
				   TASKOPTION_GROUP_DROP,
				   ifap,
				   inet6_addr_allrouters);
	}

	/* Indicate now dropped */
	BIT_RESET(ifap->ifa_ps[RTPROTO_INET6].ips_state, INET6_IFPS_ALLROUTERS);
    }
}


/* Join the all routers multicast group when we are functioning as a router */
void
inet6_allrouters_join __PF1(ifap, if_addr *)
{
    if (socktype(ifap->ifa_addr) == AF_INET6 &&
	BIT_TEST(ifap->ifa_state, IFS_MULTICAST)) {

	if (ifap->ifa_rtactive) {
	    if (!BIT_TEST(ifap->ifa_ps[RTPROTO_INET6].ips_state, INET6_IFPS_ALLROUTERS)) {
		/* Try to add to this multicast interface */

		if (BIT_TEST(task_state, TASKS_TEST)
		    || (!task_set_option(inet6_task,
					 TASKOPTION_GROUP_ADD,
					 ifap,
					 inet6_addr_allrouters)
			|| (errno == EADDRNOTAVAIL)
			|| (errno == EADDRINUSE))) {
		    /* Indicate we enabled it on this interface */

		    BIT_SET(ifap->ifa_ps[RTPROTO_INET6].ips_state, INET6_IFPS_ALLROUTERS);
		}
	    }
	} else {
	    /* Make sure we are not added on this interface */

	    inet6_allrouters_drop(ifap);
	}
    }
}

#if	defined(V6L1) || defined(HITACHI_INET6) || defined(PROTO_INET6)
byte
inet6_scope_of __PF1(addr, sockaddr_un *)
{
    byte ret = INET6_SCOPE_GLOBAL;

    if (socktype(addr) != AF_INET6) {
	ret = INET6_SCOPE_NONE;
    } else {
#ifdef	SUNOS5_0
	switch (sock2in6(addr).s6_addr[0]) {
#else
	switch (sock2in6(addr).s6_addr8[0]) {
#endif	/* SUNOS5_0 */
	  case 0xff:
	    ret = INET6_SCOPE_MULTICAST;
	    break;

	  case 0xfe:
#ifdef	SUNOS5_0
	    switch(sock2in6(addr).s6_addr[1] & 0xc0) {
#else
	    switch(sock2in6(addr).s6_addr8[1] & 0xc0) {
#endif	/* SUNOS5_0 */
	      case 0x80:
		ret = INET6_SCOPE_LINKLOCAL;
		break;

	      case 0xc0:
		ret = INET6_SCOPE_SITELOCAL;
		break;
	    }
	  case 0:
#ifdef	SUNOS5_0
	    if ((sock2in6(addr).s6_addr[0] == 0)
		&& (sock2in6(addr).s6_addr[1] == 0)
		&& (sock2in6(addr).s6_addr[2] == 0)
		&& (sock2in6(addr).s6_addr[3] != 0)) {
#else
	    if ((sock2in6(addr).s6_addr32[0] == 0)
		&& (sock2in6(addr).s6_addr32[1] == 0)
		&& (sock2in6(addr).s6_addr32[2] == 0)
		&& (sock2in6(addr).s6_addr32[3] != 0)) {
#endif	/* SUNOS5_0 */
		ret = INET6_SCOPE_V4COMPAT;
		break;
	    }
	}
    }

    return ret;
}
#endif /* V6L1 */

/*
 *	Reinit
 */
static void
inet6_reinit __PF1(tp, task *)
{
	SI_MARTIANS(AF_INET6) = adv_destmask_finish(SI_MARTIANS(AF_INET6));
}

static void
inet6_dump __PF2(tp, task *,
		fp, FILE *)
{
    (void) fprintf(fp, "\tIP forwarding: %d\n", inet6_ipforwarding);

    if (inet6_addr_reject || inet6_addr_blackhole) {
	if (inet6_addr_reject) {
	    (void) fprintf(fp, "\tReject address: %A",
			   inet6_addr_reject);
	}
	if (inet6_addr_blackhole) {
	    (void) fprintf(fp, "\tBlackhole address: %A",
			   inet6_addr_blackhole);
	}
	(void) fprintf(fp, "\n\n");
    }
	/* Martians */
	(void) fprintf(fp, "\tMartians:\n");
	control_dmlist_dump(fp,2,SI_MARTIANS(AF_INET6),
	                    (adv_entry *)0,(adv_entry *)0);
	(void) fprintf(fp,"\n");
}


/*
 *	Cleanup
 */
static void
inet6_cleanup __PF1(tp, task *)
{
	if(SI_MARTIANS(AF_INET6)) {
		adv_free_list(SI_MARTIANS(AF_INET6));
	}
    trace_freeup(tp->task_trace);
}


void
inet6_var_init __PF0(void)
{
}


static void
inet6_terminate __PF1(tp, task *)    
{
    if_addr *ifap;
	
    /* Leave the all routers multicast group */
    IF_ADDR(ifap) {
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    inet6_allrouters_drop(ifap);
	}
    } IF_ADDR_END(ifap) ;

    inet6_cleanup(tp);
    
    task_delete(tp);
}


void
inet6_init __PF0(void)
{
    if (inet6_task) {
	inet6_task->task_trace = trace_set_global((bits *) 0, (flag_t) 0);
    } else {
	inet6_task = task_alloc("INET6",
			       TASKPRI_FAMILY,
			       trace_set_global((bits *) 0, (flag_t) 0));
	task_set_cleanup(inet6_task, inet6_cleanup);
	task_set_reinit(inet6_task, inet6_reinit);
	task_set_terminate(inet6_task, inet6_terminate);
	inet6_task->task_rtproto = RTPROTO_INET6;
	inet6_task->task_socket = task_get_socket(inet6_task, PF_INET6, SOCK_DGRAM, 0);
	task_set_dump(inet6_task, inet6_dump);
	if (!task_create(inet6_task)) {
	    task_quit(EINVAL);
	}
    }

    /* Verify the validity of some kernel variables */
    if (!BIT_TEST(task_state, TASKS_TEST)) {
	if (!inet6_ipforwarding) {
	    trace_log_tp(inet6_task,
			 0,
			 LOG_WARNING,
			 ("inet6_init: *WARNING* IPv6 forwarding disabled!"));
	}
    }

	/* Martians */
	{
		const struct martian6 *ptr = inet6_martians;

		do{
			struct in6_addr mask6;
			struct in6_addr dest6;

			if(inet_pton(AF_INET6, ptr->dest6, &dest6) <= 0 ||
			   inet_pton(AF_INET6, ptr->mask6, &mask6) <= 0 ) {
				trace_log_tp(inet6_task,0,LOG_WARNING,
				             ("inet6_init: Invalid martian entry at %s/%s",
				              ptr->dest6, ptr->mask6 )
				            );
				continue;
			}
			martian_add(sockbuild_in6(0, dest6.s6_addr),
			            mask_locate(sockbuild_in6(0,mask6.s6_addr)),
			            ptr->adv_flags, ptr->dm_flags );
		}while((++ptr)->dest6);
	}
}


/*
 *	Init all kinds of IPv6 structures
 */
void
inet6_family_init __PF0(void)
{
    int plen = 0;
    struct in6_addr ia;
    sockaddr_un *addr;
    sockaddr_un **mp = inet6_masks;
    sockaddr_un *mpp = (sockaddr_un *) ((void_t) inet6_mask_list);
    byte *cp, *lp;

    /* Get an address to work with */
    bzero((void_t) &ia, sizeof(ia));
    addr = sockbuild_in6(0, (byte *) &ia);

    /* Build all possible contiguous masks */
    /* Add null mask */
    sockcopy(addr, mpp);
    mask_insert(*mp++ = mpp);
    mpp = (sockaddr_un *) ((void_t) ((byte *) mpp + SOCKADDR_IN6_LEN));

    for (cp = (byte *) &addr->in6.gin6_addr, lp = cp + sizeof(ia);
	 cp < lp; cp++) {
	int bit = NBBY;

	*cp = (byte) 0;

	while (bit--) {
	    *cp |= 1 << bit;
	    sockcopy(addr, mpp);
	    mask_insert(*mp++ = mpp);
	    mpp = (sockaddr_un *) ((void_t) ((byte *) mpp + SOCKADDR_IN6_LEN));
	}
    }
    
    sock_init_family(AF_INET6,
		     (SOCKADDR_IN6_LEN - sizeof sock2in6(inet6_addr_default)),
		     SOCKADDR_IN6_LEN,
		     inet6_mask_list,
		     sizeof inet6_mask_list,
		     "sockaddr_un.in6");

    /* Build useful addresses */
    bzero((void_t) &ia, sizeof(ia));
    inet6_addr_default = sockdup(sockbuild_in6(0, (byte *) &ia));
    inet6_addr_any = sockdup(sockbuild_in6(0, (byte *) &ia));
    ia.s6_addr[15] = 1;
    inet6_addr_loopback = sockdup(sockbuild_in6(0, (byte *) &ia));

    rt_table_init_family(AF_INET6);
    rt_static_init_family(AF_INET6);

    bzero((void_t) &ia, sizeof(ia));
    ia.s6_addr[0] = 0xff;
    ia.s6_addr[1] = 2;
    ia.s6_addr[15] = 1;
    inet6_addr_allnodes = sockdup(sockbuild_in6(0, (byte *) &ia));
    ia.s6_addr[15] = 2;
    inet6_addr_allrouters = sockdup(sockbuild_in6(0, (byte *) &ia));

    /* Set up interface bits to be printed */
    int_ps_bits[RTPROTO_INET6] = inet6_if_bits;
}


