/*
 *      Router List Operations
 *
 *      Authors:
 *      Henrik Petander                <lpetande@cc.hut.fi>
 *     
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 *
 *      $Id: router.c,v 1.15 2001/02/09 13:02:56 antti Exp $
 *
 *      Contains functions for handling the default router list, which 
 *      movement detection uses
 *      for avoiding loops etc. 
 * 
 */

#include <linux/kernel.h>
#include <linux/version.h>

#include <linux/autoconf.h>

#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/route.h>
#include <linux/init.h>

#include <linux/if_arp.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
#include <linux/init.h>

#include <net/sock.h>
#include <net/snmp.h>

#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/ndisc.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include "mdetect.h"
#include "debug.h"
#include "mipv6.h"

#define MAX_ROUTERS 1000
static int num_routers = 0;
struct router *head=NULL;
/* searches for a specific router or any router that is reachable, 
 * if address is NULL. Also deletes obsolete routers.
 */
struct router *iterate_routers(
	struct in6_addr *search_addr, 
	struct router **curr_router_p)
{
	struct router *curr = NULL, *tmp = NULL, *prev = NULL, *ret = NULL;
	DEBUG_FUNC();
        
	if (search_addr != NULL) {               
		for (curr = head; curr != NULL; curr = curr->next) {
			/* separate iterate loops, a little clearer implemetation? */
			/* consider for example curr_router->state==NOT_REACHABLE */
			DEBUG((DBG_DATADUMP,
			       "Iterate: curr->last_ra_rcvd %d curr->state %d,"
			       " from: %x:%x:%x:%x:%x:%x:%x:%x", 
			       curr->last_ra_rcvd, curr->state,
			       NIPV6ADDR(&curr->ll_addr)));

			if (!ipv6_addr_cmp(search_addr, &curr->ll_addr)) {
				DEBUG((DBG_INFO,
				       "Iterate routers found a match"));
				ret = curr;
				break;
			}
		}
	} else {  /*if called with NULL search_addr, only check router list */
		DEBUG((DBG_INFO,"iterate_routers: search_addr NULL"));
		ret = NULL;
	}

	for (prev = curr = head; curr != NULL; ) {
		if (curr->interval ?
		    (curr->last_ra_rcvd < jiffies - curr->interval - R_TIME_OUT) :
		    (curr->last_ra_rcvd < jiffies - MAX_RADV_INTERVAL - R_TIME_OUT)){
			DEBUG((DBG_INFO, "Iterate: changing state to NOT_REACHABLE"));       
			curr->state = NOT_REACHABLE;
		}

		if ((curr->state == NOT_REACHABLE
		     || curr->last_ra_rcvd < jiffies - curr->lifetime)
		    && curr != ret && curr != *curr_router_p) {
			/* Clean the list of routers not reachable or 
			   whose lifetime has expired */
			tmp = curr;
				/*coa_del(tmp->dev,&tmp->raddr);*/
			curr = curr->next;

			if (tmp == head) /* node to be deleted is head */
				prev = head = curr;
			else
				prev->next = curr;

			if (tmp->next == NULL && prev != NULL)
				/* node to be deleted is the last one in the list */
				prev->next = NULL;
                                                              
			DEBUG((DBG_INFO, "Iterate: deleting router(num_routers:%i): %x:%x:%x:%x:%x:%x:%x:%x", num_routers, NIPV6ADDR(&tmp->ll_addr)));

			if (tmp == *curr_router_p) { /* for DEBUG purposes at the moment */
				DEBUG((DBG_INFO, "Iterate: deleting current router"));
				/* set curr_router NULL */
				*curr_router_p = NULL;
			} else if (curr_router_p != NULL && tmp == (*curr_router_p)->prev_router) {
				DEBUG((DBG_INFO, "Iterate: deleting previous router"));
				/* set curr_router->prev_router NULL */
				(*curr_router_p)->prev_router = NULL;
			}

			kfree((void *)tmp);
			num_routers--;
		} else { /* else move forward in the list */
			if (prev != curr)
				prev = prev->next;
			if (prev != curr){
				DEBUG((DBG_ERROR, "BUG Iterate: linked list failure"));
				break;
			}
			curr = curr->next;
		}
	}

	return ret;
}

/* 
 * Adds router as the first item on the list
 */
void head_add(struct router *new) {
	DEBUG_FUNC();
	new->next = head;
	head = new;
}

/*
 * creates a new router 
 */
struct router *new_router(struct router* nrt)
{
	struct router *rptr;
	DEBUG_FUNC();

	if (nrt == NULL || ipv6_addr_any(&nrt->raddr)) {
		/* better to check in this phase to avoid memory leaks */
		DEBUG((0,"Received NULL arguments"));
		return NULL;
	}
	/* check if someone is trying DoS attack, or we just have some
           memory leaks... */
	if (num_routers > MAX_ROUTERS){
		DEBUG((0, "mipv6 mdetect: failed to add new router, MAX_ROUTERS exceeded"));
		return NULL;
	}

	rptr = (struct router *) kmalloc(sizeof(struct router), GFP_ATOMIC);
	if (rptr == NULL) {
		DEBUG((0, "mipv6 mdetect: couldn't allocate memory for new router"));
		return NULL;
	}

	num_routers++;
	memset((void*)rptr, 0, sizeof(struct router));

	ipv6_addr_copy(&rptr->ll_addr, &nrt->ll_addr);
	ipv6_addr_copy(&rptr->raddr, &nrt->raddr);

	rptr->ifindex = nrt->ifindex;
	rptr->pfix_len = nrt->pfix_len;
	rptr->state = ROUTER_REACHABLE;
	rptr->last_ra_rcvd = jiffies;
	rptr->interval = nrt->interval;
	rptr->glob_addr = nrt->glob_addr;
	rptr->flags = nrt->flags;
	rptr->lifetime = nrt->lifetime;
	rptr->link_addr_len = nrt->link_addr_len;
	memcpy(&rptr->link_addr, &nrt->link_addr, nrt->link_addr_len);
	head_add(rptr);
	DEBUG((DBG_INFO, "mipv6 mdetect: added new router(num_routers: %i): "
	       "%x:%x:%x:%x:%x:%x:%x:%x", num_routers, 
	       NIPV6ADDR(&rptr->ll_addr)));
	return rptr;
}

/* Cleans up the list */
void list_free(struct router **curr_router_p){
	struct router *tmp, *curr;
	DEBUG_FUNC();

	DEBUG((DBG_INFO, "Freeing the router list"));
	/* set curr_router->prev_router and curr_router NULL */
	if (*curr_router_p)
		(*curr_router_p)->prev_router = NULL;
	*curr_router_p = NULL;
	for (curr = head; curr != NULL; ) {
		tmp = curr;
		/*	coa_del(tmp->dev,&tmp->raddr);*/
		curr = curr->next;
		DEBUG((DBG_INFO, "%x:%x:%x:%x:%x:%x:%x:%x",
		       NIPV6ADDR(&tmp->ll_addr)));
		kfree((void *)tmp);
		num_routers--;
	}
}








