/*
 * $Id: movement.c 1.172 06/05/19 01:51:45+03:00 vnuorval@tcs.hut.fi $
 *
 * This file is part of the MIPL Mobile IPv6 for Linux.
 * 
 * Author: Ville Nuorvala <vnuorval@tcs.hut.fi>
 *
 * Copyright 2003-2005 Go-Core Project
 * Copyright 2003-2006 Helsinki University of Technology
 *
 * MIPL Mobile IPv6 for Linux 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; version 2 of
 * the License.
 *
 * MIPL Mobile IPv6 for Linux is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIPL Mobile IPv6 for Linux; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pthread.h>
#include <syslog.h>
#include <errno.h>
#include <netinet/icmp6.h>
#include <net/if.h>
#include <linux/types.h>
#include <linux/ipv6_route.h>

#include "debug.h"
#include "icmp6.h"
#include "util.h"
#include "tqueue.h"
#include "list.h"
#include "movement.h"
#include "mn.h"
#include "rtnl.h"
#include "ndisc.h"
#include "proc_sys.h"
#include "prefix.h"
#include "conf.h"

#define MD_DEBUG_LEVEL 1

#if MD_DEBUG_LEVEL >= 1
#define MDBG dbg
#else 
#define MDBG(...) 
#endif /* MDBG */

#if MD_DEBUG_LEVEL >= 2
#define MDBG2 dbg
#else
#define MDBG2(...) 
#endif /* MDBG2 */

#if MD_DEBUG_LEVEL >= 3
#define MDBG3 dbg
#else
#define MDBG3(...)
#endif /* MDBG3 */

static LIST_HEAD(ifaces);
static pthread_mutex_t iface_lock;

static pthread_t md_listener;

static int conf_default_autoconf = 1;
static int conf_default_ra = 1;
static int conf_default_ra_defrtr = 1;
static int conf_default_rs = 3;
static int conf_default_rs_ival = 4;

static int conf_forwarding = 0;
static int conf_autoconf = 0;
static int conf_ra = 0;
static int conf_ra_defrtr = 0;
static int conf_rs = 0;

static unsigned int adv_ivals_md_trigger = 1;
static unsigned int adv_ivals_dad_limit = 3;

static int nud_expire_rtr = 1;

static int md_movements_disabled = 1; /* see md_start() */

static void __md_trigger_movement_event(int event_type, int data,
					struct md_inet6_iface *iface,
					struct md_coa *coa)
{
	struct movement_event e;

	if (md_movements_disabled)
		return;

	memset(&e, 0, sizeof(struct movement_event));
	e.md_strategy = (conf.MnRouterProbes > 0 ? 
			 MD_STRATEGY_LAZY : MD_STRATEGY_EAGER);
	e.event_type = event_type;
	e.data = data;
	e.iface_list = &ifaces;
	e.iface = iface;
	e.coa = coa;

	MDBG2("strategy %d type %d iface %s (%d) "
	      "CoA %x:%x:%x:%x:%x:%x:%x:%x\n",
	      e.md_strategy, e.event_type,
	      e.iface ? e.iface->name : "none",
	      e.iface ? e.iface->ifindex : 0,
	      NIP6ADDR(e.coa ? &e.coa->addr : &in6addr_any));

	mn_movement_event(&e);
}

void md_trigger_movement_event(int event_type, int data, int ifindex)
{
	pthread_mutex_lock(&iface_lock);
	if (!list_empty(&ifaces)) {
		struct md_inet6_iface *iface;
		iface = md_get_inet6_iface(&ifaces, ifindex);
		__md_trigger_movement_event(event_type, data, iface, NULL);
	}
	pthread_mutex_unlock(&iface_lock);
}

static inline void md_free_coa(struct md_coa *coa)
{
	MDBG3("freeing CoA %x:%x:%x:%x:%x:%x:%x:%x on iface %d\n", 
	      NIP6ADDR(&coa->addr), coa->ifindex);
	list_del(&coa->list);
	free(coa);
}

static void md_flush_coa(struct md_coa *coa)
{
	/* For tunnels, addresses are not managed by us so
	   we don't remove them from the interface */
	if (!(coa->flags&IFA_F_HOMEADDRESS_NODAD) &&
	    !(coa->if_is_tunnel)) {
		MDBG2("deleting CoA "
		      "%x:%x:%x:%x:%x:%x:%x:%x on iface %d\n", 
		      NIP6ADDR(&coa->addr), coa->ifindex);
		addr_del(&coa->addr, coa->plen, coa->ifindex);
	}
	md_free_coa(coa);
}

static void md_flush_coa_list(struct list_head *coas)
{
	struct list_head *list, *n;
	list_for_each_safe(list, n, coas) {
		struct md_coa *coa;
		coa = list_entry(list, struct md_coa, list);
		md_flush_coa(coa);
	}
}

static inline void md_free_router_prefix(struct prefix_list_entry *p)
{
	MDBG3("freeing prefix %x:%x:%x:%x:%x:%x:%x:%x/%d\n", 
	      NIP6ADDR(&p->ple_prefix), p->ple_plen);
	list_del(&p->list);
	free(p);
}

static void md_prefix_rule_del(struct prefix_list_entry *p)
{
	struct in6_addr prefix;
	ipv6_addr_prefix(&prefix, &p->ple_prefix, p->ple_plen);
	rule_del(NULL, RT6_TABLE_MAIN, IP6_RULE_PRIO_MIP6_COA_OUT,
		 RTN_UNICAST, &prefix, p->ple_plen,
		 &in6addr_any, 0, 0);
}

static void __md_free_router(struct md_router *rtr)
{
	struct list_head *l, *n;
	if (rtr->used) {
		MDBG2("deleting default route via %x:%x:%x:%x:%x:%x:%x:%x\n", 
		      NIP6ADDR(&rtr->lladdr));

		route_del(rtr->ifindex, RT_TABLE_MAIN, 0,
			  &in6addr_any, 0, &in6addr_any, 0, &rtr->lladdr);
	}
	list_for_each_safe(l, n, &rtr->prefixes) {
		struct prefix_list_entry *p;
		p = list_entry(l, struct prefix_list_entry, list);
		if (rtr->used) {
			md_prefix_rule_del(p);
			MDBG2("deleting prefix route "
			      "%x:%x:%x:%x:%x:%x:%x:%x/%d\n", 
			      NIP6ADDR(&p->ple_prefix), p->ple_plen);
			route_del(rtr->ifindex, RT_TABLE_MAIN, 0, &in6addr_any,
				  0, &p->ple_prefix, p->ple_plen, NULL);
		}
		md_free_router_prefix(p);
	}
	list_del(&rtr->list);
	MDBG3("freeing router %x:%x:%x:%x:%x:%x:%x:%x\n", 
	      NIP6ADDR(&rtr->lladdr));
	free(rtr);
}

static inline void md_free_router(struct md_router *rtr)
{
	assert(!tsisset(rtr->lifetime));
	__md_free_router(rtr);
}

static void md_flush_router_list(struct list_head *routers)
{
	struct list_head *list, *n;
	list_for_each_safe(list, n, routers) {
		struct md_router *rtr;
		rtr = list_entry(list, struct md_router, list);
		md_free_router(rtr);
	}
}

static void md_free_inet6_iface(struct md_inet6_iface *iface)
{
	MDBG3("freeing iface %s (%d)\n", iface->name, iface->ifindex);
	if (!iface->is_tunnel) {
		/* tunnel interfaces have no router information */
		assert(list_empty(&iface->default_rtr));
		md_flush_router_list(&iface->expired_rtrs);
	}
	assert(list_empty(&iface->coas));
	md_flush_coa_list(&iface->expired_coas);
	free(iface);
}

static void md_expire_coa(struct md_inet6_iface *iface, struct md_coa *coa)
{
	list_del(&coa->list);
	MDBG2("expiring CoA %x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
	      NIP6ADDR(&coa->addr), iface->name, iface->ifindex);
	list_add_tail(&coa->list, &iface->expired_coas);
}

static void md_reset_egress_forward(void)
{
	struct list_head *l;
	int forward = 0;;

	if (list_empty(&ifaces))
		return;

	list_for_each(l, &ifaces) {
		struct md_inet6_iface *i;
		i = list_entry(l, struct md_inet6_iface, list);
		forward |= i->home_link;
	}
	list_for_each(l, &ifaces) {
		struct md_inet6_iface *i;
		i = list_entry(l, struct md_inet6_iface, list);
		set_iface_proc_entry(PROC_SYS_IP6_FORWARDING,
				     i->name, forward);
	}
}

static void md_reset_home_link(struct md_inet6_iface *i)
{
	if (i->home_link)
		md_reset_egress_forward();
	i->home_link = 0;
	i->ll_dad_unsafe = 0;
}

static void md_expire_router(struct md_inet6_iface *iface,
			     struct md_router *old, struct md_router *new)
{
	struct list_head *plist, *pn;

	assert(old != NULL);

	del_task(&old->tqe);

	tsclear(old->lifetime);
	list_del(&old->list);

	MDBG("expiring router %x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
	     NIP6ADDR(&old->lladdr), iface->name, iface->ifindex);

	if (old->used) {
		list_for_each_safe(plist, pn, &old->prefixes) {
			struct prefix_list_entry *p;
			struct list_head *clist, *cn;
			p = list_entry(plist, struct prefix_list_entry, list);
			
			/* don't expire prefixes also advertised by the
			   new router */
			if (new && prefix_list_find(&new->prefixes, 
						    &p->ple_prefix,
						    p->ple_plen)) {
				md_free_router_prefix(p);
				continue;
			}
			list_for_each_safe(clist, cn, &iface->coas) {
				struct md_coa *coa;
				coa = list_entry(clist, struct md_coa, list);
				if (!ipv6_pfx_cmp(&p->ple_prefix,
						  &coa->addr, coa->plen))
					md_expire_coa(iface, coa);
			}
		}
		if (new == NULL)
			md_reset_home_link(iface);
	}
	list_add_tail(&old->list, &iface->expired_rtrs);
}

static void md_block_rule_del(struct md_inet6_iface *iface)
{
	rule_del(NULL, 0, IP6_RULE_PRIO_MIP6_BLOCK_HOA, RTN_BLACKHOLE,
		 &in6addr_any, 0, &in6addr_any, 0, 0);
	rule_del(NULL, RT6_TABLE_MAIN, IP6_RULE_PRIO_MIP6_COA_OUT, RTN_UNICAST,
		 &in6addr_any, 128, &in6addr_any, 0, 0);
	iface->iface_flags &= ~MD_BLOCK_TRAFFIC;
}

static void md_flush_inet6_iface(struct md_inet6_iface *iface)
{
	struct md_router *rtr;
	struct list_head *l, *n;
	del_task(&iface->tqe);

	/* We do not maintain router related
	   information for tunnel interfaces */
	if (!iface->is_tunnel) {
		iface->router_solicits = 0;
		list_for_each_safe(l, n, &iface->backup_rtrs) {
			rtr = list_entry(l, struct md_router, list);
			md_expire_router(iface, rtr, NULL);
		}
		if ((rtr = md_get_first_router(&iface->default_rtr)) != NULL) {
			md_expire_router(iface, rtr, NULL);
		}
		md_flush_router_list(&iface->expired_rtrs);
	}

	list_for_each_safe(l, n, &iface->coas) {
		struct md_coa *coa = list_entry(l, struct md_coa, list);
		md_expire_coa(iface, coa);
	}
	md_flush_coa_list(&iface->expired_coas);
}

static void md_expire_inet6_iface(struct md_inet6_iface *iface)
{
	MDBG2("expiring iface %s (%d)\n", iface->name, iface->ifindex);
	md_flush_inet6_iface(iface);
	if (iface->iface_flags & MD_BLOCK_TRAFFIC)
		md_block_rule_del(iface);
	list_del(&iface->list);
	__md_trigger_movement_event(ME_IFACE_DOWN, 0, iface, NULL);
}

static void md_link_down(struct md_inet6_iface *iface)
{
	MDBG2("link down on iface %s (%d)\n", iface->name, iface->ifindex);
	md_flush_inet6_iface(iface);
	__md_trigger_movement_event(ME_LINK_DOWN, 0, iface, NULL);
}

static void
md_init_coa(struct md_coa *coa, struct ifaddrmsg *ifa, struct rtattr **rta_tb,
	    int if_is_tunnel)
{
	memset(coa, 0, sizeof(struct md_coa));
	INIT_LIST_HEAD(&coa->list);
	coa->flags = ifa->ifa_flags;
	coa->plen = ifa->ifa_prefixlen;
	coa->scope = ifa->ifa_scope;
	coa->ifindex = ifa->ifa_index;
	/* For tunnel interfaces, mark the CoA
	   to avoid removing the address from
	   the interface when flushing it. */
	coa->if_is_tunnel = if_is_tunnel;
	coa->addr = *(struct in6_addr *) RTA_DATA(rta_tb[IFA_ADDRESS]);
}

static struct md_coa *md_create_coa(struct md_inet6_iface *iface,
				    struct ifaddrmsg *ifa,
				    struct rtattr **rta_tb)
{
	struct md_coa *coa = malloc(sizeof(struct md_coa));
	if (coa != NULL) {
		struct ifa_cacheinfo *ci;
		md_init_coa(coa, ifa, rta_tb, iface->is_tunnel);
		ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
		clock_gettime(CLOCK_REALTIME, &coa->timestamp);
		tssetsec(coa->valid_time, ci->ifa_valid);
		tssetsec(coa->preferred_time, ci->ifa_prefered);
		MDBG3("creating CoA %x:%x:%x:%x:%x:%x:%x:%x on "
		      "iface %s (%d)\n", 
		      NIP6ADDR(&coa->addr), iface->name, iface->ifindex);
	}
	return coa;
}

static int update_coa(struct md_inet6_iface *iface, 
		      struct ifaddrmsg *ifa, struct rtattr **rta_tb)
{
	struct md_coa *new, *old;
	struct in6_addr *addr;
	
	addr = RTA_DATA(rta_tb[IFA_ADDRESS]);
	if (!in6_is_addr_routable_unicast(addr))
		return 0;

	if ((new = md_create_coa(iface, ifa, rta_tb)) == NULL)
		return -ENOMEM;

	if ((old = md_get_coa(&iface->coas, addr)) == NULL) {
		list_add(&new->list, &iface->coas); 
		MDBG2("adding CoA %x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
		      NIP6ADDR(&new->addr), iface->name, iface->ifindex);
		if (!(iface->iface_flags & MD_LINK_LOCAL_DAD)) {
			__md_trigger_movement_event(ME_COA_NEW, 0, iface, new);
			if (iface->iface_flags & MD_BLOCK_TRAFFIC)
				md_block_rule_del(iface);
		}
	} else {
		struct timespec oexp, nexp;

		tsadd(old->valid_time, old->timestamp, oexp);
		tsadd(new->valid_time, new->timestamp, nexp);

		MDBG3("updating CoA "
		      "%x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
		      NIP6ADDR(&old->addr), iface->name, iface->ifindex);

		old->flags = new->flags;
		old->plen = new->plen;
		old->timestamp = new->timestamp;
		old->valid_time = new->valid_time;
		old->preferred_time = new->preferred_time;

		free(new);

		/* check if lifetime of CoA changed */
		if (tsbefore(oexp, nexp))
			__md_trigger_movement_event(ME_COA_LFT_DEC, 0,
						    iface, old);
		else if (tsafter(oexp, nexp))
			__md_trigger_movement_event(ME_COA_LFT_INC, 0,
						    iface, old);
	}
	return 0;
}

static int process_new_addr(struct ifaddrmsg *ifa, struct rtattr **rta_tb)
{
	struct md_inet6_iface *iface;
	int res = 0;
	
	MDBG3("new address %x:%x:%x:%x:%x:%x:%x:%x on iface %d\n", 
	      NIP6ADDR((struct in6_addr *)RTA_DATA(rta_tb[IFA_ADDRESS])), 
	      ifa->ifa_index);

	pthread_mutex_lock(&iface_lock);
	if ((iface = md_get_inet6_iface(&ifaces, ifa->ifa_index)) != NULL) {
		if (ifa->ifa_scope == RT_SCOPE_LINK) {
			iface->iface_flags &= ~MD_LINK_LOCAL_DAD;
			__md_trigger_movement_event(ME_LINK_UP, 0, 
						    iface, NULL);
			if (iface->iface_flags & MD_BLOCK_TRAFFIC)
				md_block_rule_del(iface);
		} else if (ifa->ifa_scope == RT_SCOPE_UNIVERSE) {
			res = update_coa(iface, ifa, rta_tb);
		}
	}
	pthread_mutex_unlock(&iface_lock);
	return res;
}

static void md_inet6_iface_init(struct md_inet6_iface *i, int ifindex)
{
	struct list_head *list;

	memset(i, 0, sizeof(struct md_inet6_iface));
	i->ifindex = ifindex;

	/* Mark interface as tunnel if set in the conf */
	list_for_each(list, &conf.net_ifaces) {
		struct net_iface *nif;
		nif = list_entry(list, struct net_iface, list);
		if (nif->ifindex == ifindex)
			i->is_tunnel = nif->is_tunnel;
	}

	INIT_LIST_HEAD(&i->list);
	INIT_LIST_HEAD(&i->default_rtr);
	INIT_LIST_HEAD(&i->backup_rtrs);
	INIT_LIST_HEAD(&i->expired_rtrs);
	INIT_LIST_HEAD(&i->coas);
	INIT_LIST_HEAD(&i->expired_coas);
	INIT_LIST_HEAD(&i->tqe.list);
}

static int process_del_addr(struct ifaddrmsg *ifa, struct rtattr **rta_tb)
{
	struct in6_addr *addr = RTA_DATA(rta_tb[IFA_ADDRESS]);
	struct md_inet6_iface *iface;
	struct md_coa *coa;
	struct md_inet6_iface iface_h;
	struct md_coa coa_h;
	
	int res = 0;

	MDBG3("deleted address %x:%x:%x:%x:%x:%x:%x:%x on iface %d\n", 
	      NIP6ADDR(addr), ifa->ifa_index);

	if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
	    !in6_is_addr_routable_unicast(addr))
		return 0;

	pthread_mutex_lock(&iface_lock);
	if ((iface = md_get_inet6_iface(&ifaces, ifa->ifa_index)) != NULL) {
		coa = md_get_coa(&iface->coas, addr);
		if (coa != NULL)
			md_expire_coa(iface, coa);
		else
			coa = md_get_coa(&iface->expired_coas, addr);
	} else {
		md_inet6_iface_init(&iface_h, ifa->ifa_index);
		iface = &iface_h;
		coa = NULL;
	}
	if (coa == NULL) {
		md_init_coa(&coa_h, ifa, rta_tb, iface->is_tunnel);
		coa = &coa_h;
	}
	__md_trigger_movement_event(ME_COA_EXPIRED, 0, iface, coa);
	if (coa != &coa_h)
		md_free_coa(coa);
	pthread_mutex_unlock(&iface_lock);
	return res;
}

static int process_addr(struct nlmsghdr *n)
{
	struct ifaddrmsg *ifa;
	struct rtattr *rta_tb[IFA_MAX+1];

	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifa)))
		return -1;

	ifa = NLMSG_DATA(n);

	memset(rta_tb, 0, sizeof(rta_tb));
	parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa),
		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));

	if (!rta_tb[IFA_ADDRESS] || !rta_tb[IFA_CACHEINFO]) 
		return -1;
	
	if (n->nlmsg_type == RTM_NEWADDR)
		process_new_addr(ifa, rta_tb);
	else if (n->nlmsg_type == RTM_DELADDR)
		process_del_addr(ifa, rta_tb);

	return 0;
}

static void md_discover_router(struct tq_elem *tqe);

static void __md_discover_router(struct md_inet6_iface *iface)
{
	MDBG("discover link on iface %s (%d)\n", iface->name, iface->ifindex);

	if (md_is_link_up(iface) && 
	    iface->router_solicits++ <= iface->devconf[DEVCONF_RTR_SOLICITS]) {
		struct timespec exp_in;
		ndisc_send_rs(iface->ifindex, &in6addr_all_routers_mc, NULL, 0);
		tssetsec(exp_in, iface->devconf[DEVCONF_RTR_SOLICIT_INTERVAL]);
		add_task_rel(&exp_in, &iface->tqe, md_discover_router);
	}
}

static void md_discover_router(struct tq_elem *tqe)
{
	pthread_mutex_lock(&iface_lock);
	if (!task_interrupted()) {
		struct md_inet6_iface *iface;
		iface = tq_data(tqe, struct md_inet6_iface, tqe);
		__md_discover_router(iface);
	}
	pthread_mutex_unlock(&iface_lock);
}

static void md_check_expired_coas(struct md_inet6_iface *iface, 
				  struct md_router *rtr);

/* Called on netlink retrieved information for an address
   in order to update list of coa on that iface (function
   follows rtnl_filter_t prototype). */
static int apply_update_tunnel_coa(__attribute__ ((unused)) const struct sockaddr_nl *who,
				   struct nlmsghdr *n, void *arg)
{
	struct md_inet6_iface *iface = (struct md_inet6_iface *)arg;
	int ifindex = iface->ifindex;
	struct ifaddrmsg *ifa = NLMSG_DATA(n);
	struct rtattr *rta_tb[RTA_MAX+1];

	if (n->nlmsg_type == NLMSG_DONE ||
	    n->nlmsg_type == NLMSG_ERROR)
		return 0;

	if (n->nlmsg_type != RTM_NEWADDR ||
	    n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifa)) ||
	    ifa->ifa_family != AF_INET6)
			return -EINVAL;

	if (ifa->ifa_flags & IFA_F_TENTATIVE)
		return 0;

	memset(rta_tb, 0, sizeof(rta_tb));
	parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa),
		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));

	if (!rta_tb[IFA_ADDRESS])
		return 0;

	if (ifindex < 0 || ifa->ifa_index != (uint32_t)ifindex)
		return 0;

	update_coa(iface, ifa, rta_tb);
	return 0;
}

/* Grab the list of addresses configured on a provided
   tunnel interface and add usable ones to CoA list */
static int __md_update_tunnel_iface_coa_list(struct md_inet6_iface *iface)
{
	struct ifaddrmsg ifa;
	int len, res = 0;
	struct rtnl_handle rth;

	/* Open socket */
	if (rtnl_open_byproto(&rth, 0, NETLINK_ROUTE) < 0)
		return -1;

	/* Fill ifaddrmsg (will serve as request) */
	memset(&ifa, 0, sizeof(ifa));
	ifa.ifa_family = AF_INET6;
	ifa.ifa_prefixlen = 0;
	ifa.ifa_flags = IFA_F_PERMANENT;
	ifa.ifa_scope = RT_SCOPE_UNIVERSE;
	ifa.ifa_index = iface->ifindex;
	len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));

	/* Dump address information for previous request and work on
	 * every part of the result */
	if (rtnl_dump_request(&rth, RTM_GETADDR, &ifa, len) < 0 ||
	    rtnl_dump_filter(&rth, apply_update_tunnel_coa,
			     (void *)iface, NULL, NULL) < 0)
		res = -1;

	rtnl_close(&rth);
	return res;
}

static uint32_t md_router_compute_def_route_metric(uint16_t iface_pref,
						   uint8_t  rtr_pref);

/* called on all routes find by __md_update_tunnel_iface_routes */
static int apply_update_tunnel_route(__attribute__ ((unused)) const struct sockaddr_nl *who,
				     struct nlmsghdr *n, void *arg)
{
	struct md_inet6_iface *iface = (struct md_inet6_iface *)arg;
	uint32_t old_metric=0, new_metric;
	int ifindex = iface->ifindex;
	struct rtmsg *r = NLMSG_DATA(n);
	struct rtattr *rta_tb[RTA_MAX+1];
	struct rtattr *rta;
	int len = n->nlmsg_len;
	struct in6_addr *gateway = NULL;

	if (n->nlmsg_type == NLMSG_DONE ||
	    n->nlmsg_type == NLMSG_ERROR)
		return 0;

	if (n->nlmsg_type != RTM_NEWROUTE||
	    len < 0 || (size_t)len < NLMSG_LENGTH(sizeof(*r)) ||
	    r->rtm_family != AF_INET6) {
		return -EINVAL;
	}

	len -= NLMSG_LENGTH(sizeof(*r));
	if (len < 0)
		return -1;

	parse_rtattr(rta_tb, RTA_MAX, RTM_RTA(r), len);

	rta = rta_tb[RTA_OIF]; /* Main filter is ifindex */
	if (rta == NULL || *(int*)RTA_DATA(rta) != ifindex) {
		return 0;
	}

	/* Man page is explicit enough, we only deal with the default
	   route in main table by removing it and installing a new one
	   through the device with a modified metric. Gateway is kept
	   if present (for 6to4 mainly). We just skip other routes. */
	if (r->rtm_dst_len != 0 ||
	    r->rtm_src_len != 0 ||
	    r->rtm_table != RT_TABLE_MAIN ||
	    r->rtm_scope != RT_SCOPE_UNIVERSE ||
	    r->rtm_type != RTN_UNICAST ||
	    rta_tb[RTA_DST] != NULL ||
	    rta_tb[RTA_SRC] != NULL)
		return 0;

	if (rta_tb[RTA_GATEWAY]) {
		gateway = RTA_DATA(rta_tb[RTA_GATEWAY]);
	}

	if (rta_tb[RTA_PRIORITY])
		old_metric = *(uint32_t*)RTA_DATA(rta_tb[RTA_PRIORITY]);

	/* Compute new metric */
	new_metric = md_router_compute_def_route_metric(iface->preference, 0);

	/* No change needed */
	if (old_metric == new_metric)
		return 0;

	/* Different metrics, start by adding the new route and then remove
	 * the old one. Having done some tests, I cannot manage to do that
	 * reliably in a single operation (i.e. change the metric). --arno */
	route_add(ifindex, RT_TABLE_MAIN, RTPROT_STATIC, r->rtm_flags,
		  new_metric, &in6addr_any, 0, &in6addr_any, 0, gateway);
	route_del(ifindex, RT_TABLE_MAIN, old_metric,
		  &in6addr_any, 0, &in6addr_any, 0, gateway);

	return 0;
}

/* Grab routes that use provided interface, keep link local ones,
 * modify metric of default one(s ?) and remove others. */
static int __md_update_tunnel_iface_routes(struct md_inet6_iface *iface)
{
	struct rtmsg r;
	struct rtnl_handle rth;
	int len, res = 0;

	if (rtnl_open_byproto(&rth, 0, NETLINK_ROUTE) < 0)
		return -1;

	/* Fill rtmsg (will serve as request) */
	memset(&r, 0, sizeof(r));
	r.rtm_family = AF_INET6;
	r.rtm_table = RT_TABLE_MAIN;
	r.rtm_scope = RT_SCOPE_UNIVERSE;
	r.rtm_protocol = RTPROT_UNSPEC;
	r.rtm_type = RTN_UNICAST;
	len = NLMSG_LENGTH(sizeof(r));

	/* Dump route information for previous request and work on
	 * every part of the result */
	if (rtnl_dump_request(&rth, RTM_GETROUTE, &r, len) < 0 ||
	    rtnl_dump_filter(&rth, apply_update_tunnel_route,
			     (void *)iface, NULL, NULL) < 0)
		res = -1;

	rtnl_close(&rth);
	return res;
}

static void md_link_up(struct md_inet6_iface *iface)
{
	MDBG2("link up on iface %s (%d)\n", iface->name, iface->ifindex);

	if (iface->is_tunnel) {
		/* When interface is a tunnel, we do not deal with router
		 * discovery and interface configuration. We only:
		 * - grab and update the list of CoAs for the interface.
		 * - updates routes (i.e. change metric) */
		__md_update_tunnel_iface_coa_list(iface);
		__md_update_tunnel_iface_routes(iface);
	} else
		__md_discover_router(iface);
}

static void __md_new_link(struct md_inet6_iface *iface, int link_changed)
{
	MDBG2("new link on iface %s (%d)\n", iface->name, iface->ifindex);

	del_task(&iface->tqe);

	/* Skip router related updates for autoconfigured tunnel iface*/
	if (!iface->is_tunnel) {
		assert(!list_empty(&iface->default_rtr));
		iface->router_solicits = 0;

		if (link_changed) {
			struct list_head *l, *n;
			if (!iface->ll_dad_unsafe) {
				iface->iface_flags |= MD_LINK_LOCAL_DAD;
				addr_do(&iface->lladdr, 64, iface->ifindex,
					NULL, mn_lladdr_dad);
			}
			list_for_each_safe(l, n, &iface->backup_rtrs) {
				struct md_router *rtr;
				rtr = list_entry(l, struct md_router, list);
				md_expire_router(iface, rtr, NULL);
			}
		}
		md_flush_router_list(&iface->expired_rtrs);
	}

	md_flush_coa_list(&iface->expired_coas);
}


static struct md_inet6_iface *
md_create_inet6_iface(struct ifinfomsg *ifi, struct rtattr **rta_tb)
{
	struct md_inet6_iface *iface;
	int hwalen;
	
	/* Note: steps performed below are ok even for tunnel interfaces */
	if ((iface = malloc(sizeof(struct md_inet6_iface))) != NULL) {
		md_inet6_iface_init(iface, ifi->ifi_index);
		if (rta_tb[IFLA_IFNAME])
			strncpy(iface->name, RTA_DATA(rta_tb[IFLA_IFNAME]), 
				IF_NAMESIZE - 1);
		iface->link_flags = ifi->ifi_flags;
		iface->type = ifi->ifi_type;

		/* For interfaces w/ meaningful L2 addresses, if the address
		 * is available and ok, we derive a link-local address */
		if (rta_tb[IFLA_ADDRESS] != NULL  &&
		    (hwalen = ndisc_get_l2addr_len(ifi->ifi_type)) > 0 &&
		    rta_tb[IFLA_ADDRESS]->rta_len == RTA_LENGTH(hwalen)) {
			ndisc_set_linklocal(&iface->lladdr,
					    RTA_DATA(rta_tb[IFLA_ADDRESS]),
					    iface->type);
		}

		if (rta_tb[IFLA_PROTINFO]) {
			struct rtattr *inet6_tb[IFLA_INET6_MAX+1];

			memset(inet6_tb, 0, sizeof(inet6_tb));
		
			parse_rtattr(inet6_tb, IFLA_INET6_MAX, 
				     RTA_DATA(rta_tb[IFLA_PROTINFO]),
				     rta_tb[IFLA_PROTINFO]->rta_len);
		
			if (inet6_tb[IFLA_INET6_CONF]) {
				memcpy(iface->devconf, 
				       RTA_DATA(inet6_tb[IFLA_INET6_CONF]),
				       sizeof(iface->devconf));
			}
		}
		if (iface->devconf[DEVCONF_RTR_SOLICITS] <= 0)
			iface->devconf[DEVCONF_RTR_SOLICITS] = conf_default_rs;
		if (iface->devconf[DEVCONF_RTR_SOLICIT_INTERVAL] <= 0)
			iface->devconf[DEVCONF_RTR_SOLICIT_INTERVAL] = conf_default_rs_ival;
		MDBG3("creating iface %s (%d)\n", iface->name, iface->ifindex);
	}
	return iface;
}

static void iface_proc_entries_init(struct md_inet6_iface *iface)
{
	set_iface_proc_entry(PROC_SYS_IP6_FORWARDING, iface->name,
			     conf_forwarding);
	set_iface_proc_entry(PROC_SYS_IP6_AUTOCONF, iface->name,
			     conf_autoconf);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA, iface->name,
			     conf_ra);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA_DEFRTR, iface->name,
			     conf_ra_defrtr);
	set_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICITS, iface->name,
			     conf_rs);
	tssetmsec(iface->reachable, DEFAULT_REACHABLE_TIME);
	tssetmsec(iface->retransmit, DEFAULT_RETRANSMIT_TIMER);
}

static inline int link_flags_changed(unsigned int nf, unsigned int of)
{
	return (nf & (IFF_UP|IFF_RUNNING)) != (of & (IFF_UP|IFF_RUNNING));
}

/* Look for interface by name (if any) in the list of interfaces
 * referenced in the configuration and update its ifindex as the
 * interface is now available (dongle/adapter was plugged) */
static void iface_ifindex_update(int ifindex)
{
	char ifname[IF_NAMESIZE];
	struct list_head *list;

	/* We need the name of the interface to find it and
	 * update its ifindex */
	if (if_indextoname(ifindex, ifname) == NULL)
		return;

	list_for_each(list, &conf.net_ifaces) {
		struct net_iface *nif;
		nif = list_entry(list, struct net_iface, list);
		if (strncmp(ifname, nif->name, IF_NAMESIZE-1) == 0) {
			nif->ifindex = ifindex;
			break;
		}
	}
}

/* Look for interface by ifindex in the list of interfaces referenced
 * by configuration and invalidate its ifindex as the interface was
 * removed */
static void iface_ifindex_invalidate(int ifindex)
{
	struct list_head *list;

	/* Invalidate ifindex for that interface */
	list_for_each(list, &conf.net_ifaces) {
		struct net_iface *nif;
		nif = list_entry(list, struct net_iface, list);
		if (nif->ifindex == ifindex) {
			nif->ifindex = 0;
			break;
		}
	}
}

static int process_new_inet6_iface(struct ifinfomsg *ifi,
				   struct rtattr **rta_tb)
{
	struct md_inet6_iface *iface;

	if ((iface = md_get_inet6_iface(&ifaces, ifi->ifi_index)) == NULL) {
		unsigned int pref;

		iface_ifindex_update(ifi->ifi_index);

		if ((pref = conf.pmgr.accept_inet6_iface(ifi->ifi_index)) &&
		    (iface = md_create_inet6_iface(ifi, rta_tb)) != NULL) {
			MDBG2("adding iface %s (%d)\n",
			      iface->name, iface->ifindex);
			iface->preference = pref;
			list_add_tail(&iface->list, &ifaces);
			if (!iface->is_tunnel)
				iface_proc_entries_init(iface);
			if (md_is_link_up(iface))
				md_link_up(iface);
		}
	} else if (link_flags_changed(ifi->ifi_flags, iface->link_flags)) {
		iface->link_flags = ifi->ifi_flags;
		if (md_is_link_up(iface))
			md_link_up(iface);
		else 
			md_link_down(iface);
	}
	return 0;
}

static int 
process_del_inet6_iface(struct ifinfomsg *ifi)
{
       	struct md_inet6_iface *iface;
	if ((iface = md_get_inet6_iface(&ifaces, ifi->ifi_index)) != NULL) {
		MDBG2("deleting iface %s (%d)\n", iface->name, iface->ifindex);
		md_expire_inet6_iface(iface);
		md_free_inet6_iface(iface);
		iface_ifindex_invalidate(ifi->ifi_index);
	}
	return 0;
}

static int process_link(struct nlmsghdr *n)
{
	struct ifinfomsg *ifi;
	struct rtattr *rta_tb[IFLA_MAX+1];

	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi)))
		return -1;

	ifi = NLMSG_DATA(n);

	if (ifi->ifi_family != AF_UNSPEC && ifi->ifi_family != AF_INET6)
		return 0;

	memset(rta_tb, 0, sizeof(rta_tb));
	parse_rtattr(rta_tb, IFLA_MAX, IFLA_RTA(ifi),
		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi)));

	/* Check if interface type is supported, i.e. if we have
	 * internal logic to perform minimal L2 operations. We do
	 * that by checking we do know HW address length. */
	if (ndisc_get_l2addr_len(ifi->ifi_type) < 0) {
		syslog(LOG_WARNING,
		       "Interface %d (%s):type %d unsupported",
		       ifi->ifi_index,
		       (char *) RTA_DATA(rta_tb[IFLA_IFNAME]),
		       ifi->ifi_type);
		return 0;
	}

	pthread_mutex_lock(&iface_lock);
	if (n->nlmsg_type == RTM_NEWLINK)
		process_new_inet6_iface(ifi, rta_tb);
	else if (n->nlmsg_type == RTM_DELLINK)
		process_del_inet6_iface(ifi);
	pthread_mutex_unlock(&iface_lock);

	return 0;
}

static void md_router_timeout(struct md_router *rtr);

static int process_fail_neigh(struct ndmsg *ndm, struct rtattr **rta_tb)
{
	struct md_inet6_iface *iface;
	struct md_router *rtr;
	struct in6_addr *addr;

	if (!nud_expire_rtr)
		return 0;

	iface = md_get_inet6_iface(&ifaces, ndm->ndm_ifindex);
	if (iface == NULL || iface->is_tunnel)
		return 0;

	rtr = md_get_first_router(&iface->default_rtr);
	if (rtr == NULL)
		return 0;

	addr = RTA_DATA(rta_tb[NDA_DST]);
	if (rtr_addr_chk(rtr,addr) || IN6_ARE_ADDR_EQUAL(&rtr->lladdr,addr))
		md_router_timeout(rtr);
	return 0;
}

static int process_neigh(struct nlmsghdr *n)
{
	struct ndmsg *ndm;
	struct rtattr *rta_tb[NDA_MAX+1];

	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ndm)))
		return -1;

	ndm = NLMSG_DATA(n);

	if (ndm->ndm_family != AF_INET6 || !(ndm->ndm_state & NUD_FAILED))
		return 0;

	memset(rta_tb, 0, sizeof(rta_tb));
	parse_rtattr(rta_tb, NDA_MAX, NDA_RTA(ndm),
		     n->nlmsg_len - NLMSG_LENGTH(sizeof(*ndm)));

	pthread_mutex_lock(&iface_lock);
	process_fail_neigh(ndm, rta_tb);
	pthread_mutex_unlock(&iface_lock);

	return 0;
}

static int process_nlmsg(__attribute__ ((unused)) const struct sockaddr_nl *who,
			 struct nlmsghdr *n,
			 __attribute__ ((unused)) void *arg)
{
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	switch (n->nlmsg_type) {
	case RTM_NEWLINK:
	case RTM_DELLINK:
		/* interface or link, up or down */ 
		process_link(n);
		break;
	case RTM_NEWNEIGH:
		/* changes in reachability state of default router */
		process_neigh(n);
		break;
	case RTM_NEWADDR:
	case RTM_DELADDR:
		/* new or deleted CoAs */
		process_addr(n);
		break;
	default:
		/* To do: listen to changes in default and prefix routes(?) */
		break;
	}
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
	return 0;
}

static struct prefix_list_entry *
md_create_router_prefix(struct md_router *rtr, 
			struct nd_opt_prefix_info *pinfo)
{
	struct prefix_list_entry *p;

	if (!(p = malloc(sizeof(struct prefix_list_entry))))
		return NULL;

	p->timestamp = rtr->timestamp;
	memcpy(&p->pinfo, pinfo, sizeof(struct nd_opt_prefix_info));

	if (p->ple_flags & ND_OPT_PI_FLAG_RADDR)
		rtr->raddr_cnt++;
	rtr->prefix_cnt++;

	MDBG3("creating new prefix %x:%x:%x:%x:%x:%x:%x:%x/%d\n", 
	     NIP6ADDR(&p->ple_prefix), p->ple_plen);

	return p;
}

static void md_check_home_link(struct md_inet6_iface *i, struct md_router *rtr)
{
	struct list_head *l;
	int home_link = 0;
	int ll_dad_unsafe = 0;

	/* Don't bother checking: MN can't be at home on a tunnel iface */
	if (!i->is_tunnel) {
		list_for_each(l, &conf.home_addrs) {
			struct home_addr_info *hai;
			hai = list_entry(l, struct home_addr_info, list);
			if (mn_is_at_home(&rtr->prefixes,
					  &hai->home_prefix,
					  hai->home_plen)) {
				home_link = 1;
				ll_dad_unsafe |= hai->lladdr_comp;
			}
		}
	}
	if (i->home_link != home_link)
		md_reset_egress_forward();
	i->home_link = home_link;
	i->ll_dad_unsafe = ll_dad_unsafe;
}

static struct md_router *md_create_router(struct md_inet6_iface *iface, 
					  const struct in6_addr *saddr, 
					  struct nd_router_advert *ra, int len)
{
	struct md_router *new;
	int optlen = len - sizeof(struct nd_router_advert);
	uint8_t *opt = (uint8_t *)(ra + 1);

	if (!(new = malloc(sizeof(struct md_router))))
		return NULL;

	memset(new, 0, sizeof(struct md_router));
	clock_gettime(CLOCK_REALTIME, &new->timestamp);
	INIT_LIST_HEAD(&new->prefixes);
	INIT_LIST_HEAD(&new->list);
	INIT_LIST_HEAD(&new->tqe.list);

	while (optlen > 1) {
		uint16_t olen = opt[1] << 3;

		if (olen > (unsigned int)optlen || olen == 0)
			goto free_rtr;
		switch (opt[0]) {
			struct nd_opt_prefix_info *pinfo;
			struct prefix_list_entry *p;
			struct nd_opt_mtu *mtu;
			struct nd_opt_adv_interval *r;

		case ND_OPT_SOURCE_LINKADDR:
			new->hwalen = ndisc_l2addr_from_opt(iface->type,
							    new->hwa,
							    &opt[2], olen-2);
			if (new->hwalen < 0)
				goto free_rtr;
			break;

		case ND_OPT_PREFIX_INFORMATION:
			if (olen < sizeof(struct nd_opt_prefix_info))
				goto free_rtr;

			pinfo = (struct nd_opt_prefix_info *)opt;
			/* internal representation host byte order */
			pinfo->nd_opt_pi_valid_time = 
				ntohl(pinfo->nd_opt_pi_valid_time);
			pinfo->nd_opt_pi_preferred_time =
				ntohl(pinfo->nd_opt_pi_preferred_time);

			if (pinfo->nd_opt_pi_prefix_len > 128 ||
			    !(p = md_create_router_prefix(new, pinfo)))
				goto free_rtr;

			list_add_tail(&p->list, &new->prefixes);
			break;

		case ND_OPT_MTU:
			if (olen < sizeof(struct nd_opt_mtu))
				goto free_rtr;

			mtu = (struct nd_opt_mtu *)opt;
			new->mtu = ntohl(mtu->nd_opt_mtu_mtu);
			break;

		case ND_OPT_RTR_ADV_INTERVAL:
			if (olen < sizeof(struct nd_opt_adv_interval))
				goto free_rtr;

			r = (struct nd_opt_adv_interval *) opt;
			tssetmsec(new->adv_ival,
				  ntohl(r->nd_opt_adv_interval_ival));
			break;
		}
		optlen -= olen;
		opt += olen;
	}
	if (new->prefix_cnt == 0)
		goto free_rtr;
	
	new->iface = iface;
	new->hoplimit = ra->nd_ra_curhoplimit;
	new->ra_flags = ra->nd_ra_flags_reserved;
	tssetsec(new->rtr_lifetime, ntohs(ra->nd_ra_router_lifetime));
	tssetmsec(new->reachable, ntohl(ra->nd_ra_reachable));
	tssetmsec(new->retransmit, ntohl(ra->nd_ra_retransmit));

	if (tsisset(new->adv_ival)) {
		struct timespec tmp;
		tssetmsec(tmp, tstomsec(new->adv_ival) * adv_ivals_md_trigger);
		new->lifetime = tsmin(tmp, new->rtr_lifetime);
	} else
		new->lifetime = new->rtr_lifetime;

	new->lladdr = *saddr;
	new->ifindex = iface->ifindex;

	MDBG3("creating new router "
	      "%x:%x:%x:%x:%x:%x:%x:%x on interface %s (%d)\n", 
	     NIP6ADDR(saddr), iface->name, iface->ifindex);

	return new;
free_rtr:
	__md_free_router(new);
	return NULL;
}

int rtr_addr_chk(struct md_router *rtr, struct in6_addr *rtr_addr)
{
	if (rtr->raddr_cnt > 0) {
		struct list_head *lp;
		list_for_each(lp, &rtr->prefixes) {
			struct prefix_list_entry *p;
			p = list_entry(lp, struct prefix_list_entry, list);
			if (p->ple_flags & ND_OPT_PI_FLAG_RADDR &&
			    IN6_ARE_ADDR_EQUAL(&p->ple_prefix, rtr_addr))
				return 1;
		}
	}
	return 0;
}

static int rtr_addr_match(struct md_router *r1, struct md_router *r2)
{
	struct list_head *l1;

	list_for_each(l1, &r1->prefixes) {
		struct prefix_list_entry *p1;
		struct list_head *l2;

		p1 = list_entry(l1, struct prefix_list_entry, list);

		if (!(p1->ple_flags & ND_OPT_PI_FLAG_RADDR))
			continue;

		list_for_each(l2, &r2->prefixes) {
			struct prefix_list_entry *p2;

			p2 = list_entry(l2, struct prefix_list_entry, list);
			if (p2->ple_flags & ND_OPT_PI_FLAG_RADDR &&
			    p1->ple_plen == p2->ple_plen &&
			    !IN6_ARE_ADDR_EQUAL(&p1->ple_prefix, 
						&p2->ple_prefix))
				return 0;
			
		}
	}
	return 1;
}

static int md_router_cmp(struct md_router *new, struct md_router *old)
{
	/* Neither router nor link-local addresses match; different nodes */
	if (IN6_ARE_ADDR_EQUAL(&new->lladdr, &old->lladdr)) {
		if (new->raddr_cnt > 0 || old->raddr_cnt > 0)  
			return !rtr_addr_match(new, old);
		return 0;
	}
        return -1;
}

static struct in6_addr *md_get_rtr_addr(struct md_router *rtr)
{
	if (rtr->raddr_cnt > 0) {
		struct list_head *list;
		list_for_each(list, &rtr->prefixes) {
			struct prefix_list_entry *p;
			p = list_entry(list, struct prefix_list_entry, list);
			if (p->ple_flags & ND_OPT_PI_FLAG_RADDR)
				return &p->ple_prefix;
		}

	}
	return &rtr->lladdr;
}

static void md_router_timeout_probe(struct tq_elem *tqe);

static void md_probe_router(struct md_router *rtr)
{
	if (md_is_link_up(rtr->iface)) {
		struct in6_addr *rtr_addr = md_get_rtr_addr(rtr);
		struct timespec expires;

		MDBG2("perform NUD for router "
		      "%x:%x:%x:%x:%x:%x:%x:%x on interface %s (%d)\n", 
		      NIP6ADDR(&rtr->lladdr), rtr->iface->name,
		      rtr->iface->ifindex);

		rtr->probed = 1;
		rtr->solicited_addr = *rtr_addr;

		neigh_add(rtr->ifindex, NUD_PROBE, NTF_ROUTER,
			  rtr_addr, rtr->hwalen > 0 ? rtr->hwa : NULL,
			  rtr->hwalen, 1);

		clock_gettime(CLOCK_REALTIME, &rtr->timestamp);
		tssetmsec(rtr->lifetime,
			  tstomsec(rtr->iface->retransmit) *
			  conf.MnRouterProbes);
		if (tsisset(conf.MnRouterProbeTimeout_ts) &&
		    tsbefore(rtr->lifetime, conf.MnRouterProbeTimeout_ts))
			rtr->lifetime = conf.MnRouterProbeTimeout_ts;
		tsadd(rtr->lifetime, rtr->timestamp, expires);
		add_task_abs(&expires, &rtr->tqe, md_router_timeout_probe);
	}
}

static void md_update_backup_router_stats(struct md_router *rtr,
					  struct timespec *now,
					  struct timespec *tstamp)
{
	struct list_head *l;
	list_for_each(l, &rtr->prefixes) {
		struct prefix_list_entry *p;
		p = list_entry(l, struct prefix_list_entry, list);
		p->ple_valid_time = mpd_curr_lft(now, tstamp, 
						 p->ple_valid_time);
		p->ple_prefd_time = mpd_curr_lft(now, tstamp, 
						 p->ple_prefd_time);;
	}
}

static void md_change_default_router(struct md_inet6_iface *iface,
				     struct md_router *new,
 				     struct md_router *old);

static int md_change_to_backup_router(struct md_inet6_iface *iface,
				      struct md_router *old)
{
	struct list_head *l, *n;

	list_for_each_safe(l, n, &iface->backup_rtrs) {
		struct md_router *new = list_entry(l, struct md_router, list);
		struct timespec expires;

		tsadd(new->timestamp, new->lifetime, expires);

		if (tsafter(old->timestamp, expires)) {
			del_task(&new->tqe);
			list_del(&new->list);
			md_update_backup_router_stats(new,
						      &old->timestamp, 
						      &new->timestamp);
			md_change_default_router(iface, new, old);
			return 0;
		}
	}
	return -1;
}

static void md_router_timeout(struct md_router *rtr)
{
	struct md_inet6_iface *iface = rtr->iface;

	MDBG2("expire router %x:%x:%x:%x:%x:%x:%x:%x "
	      "without NUD on interface %s (%d)\n", 
	      NIP6ADDR(&rtr->lladdr), iface->name,
	      iface->ifindex);

	clock_gettime(CLOCK_REALTIME, &rtr->timestamp);

	if (rtr->probed)
		neigh_del(rtr->ifindex, &rtr->solicited_addr);

	if (rtr->used) {
		if (list_empty(&iface->backup_rtrs) ||
		    md_change_to_backup_router(iface, rtr)) {
			md_expire_router(iface, rtr, NULL);
			__md_discover_router(rtr->iface);
			__md_trigger_movement_event(ME_RTR_EXPIRED, 0, 
						    rtr->iface, NULL);
		}
	} else
		md_expire_router(iface, rtr, NULL);
}

static void md_router_timeout_probe(struct tq_elem *tqe)
{
	pthread_mutex_lock(&iface_lock);
	if (!task_interrupted()) {
		struct md_router *rtr = tq_data(tqe, struct md_router, tqe);
		if (rtr->probed)
			md_router_timeout(rtr);
		else
			md_probe_router(rtr);
	}
	pthread_mutex_unlock(&iface_lock);
}

/* Some notes on route metric, interface preferences and router preferences:
 *
 * Below, we deal with the metric associated with default routes. As a
 * remainder, on Linux, the higher the metric value on the route, the
 * lower the priority of the route. Usual IPv6 routes installed by kernel
 * from RA are given a priority of 1024.
 *
 * In UMIP, the *basis* we use for the default metric we use is 1023
 * (DEFAULT_ROUTE_METRIC as defined below). This value is not used
 * directly for the route metric. Keep reading.
 *
 * In UMIP, interfaces are given preference values, used by the policy
 * manager for the selection of interfaces. Preference values are
 * in the range [1, POL_MN_IF_MIN_PREFERENCE] (i.e. 10). The higher the
 * value, the lower the preference. Note that 0 means the interface
 * will not be used. The preference value for a given interface is also
 * used in the computation of the metric m for the default route associated
 * with the interface:
 *
 *  m = DEFAULT_ROUTE_METRIC - 3*(POL_MN_IF_MIN_PREFERENCE - ifpref)
 *
 * And because multiple routers may be available on a subnet associated
 * with a given interface and may report specific router preference
 * values, i.e. 'low' (3), 'medium' (0) or 'high' (1) (see Section 2.1 of
 * RFC 4191), the router preference advertised by a router is also used
 * in the computation of the *final* metric (hence the 3 above) for the
 * default route via a given router:
 *
 *   rtrpref is 'low' (-1)  =>  m = m-1
 *   rtrpref is 'high' (1)  =>  m = m+1
 *   rtrpref is sth else    =>  m is untouched
 */

#define DEFAULT_ROUTE_METRIC 1023

/* Return 1 if new router has a strictly lower default router preference
 * value than old one. */
static int md_router_prefer_old(struct md_router *old, struct md_router *new)
{
	uint8_t old_prf_flag = (old->ra_flags >> 3) & 0x03;
	uint8_t new_prf_flag = (new->ra_flags >> 3) & 0x03;
	int old_prf_val, new_prf_val;

	/* Map flags (low, medium, high) to -1, 0, 1. Reserved val (0b00)
	 * is mapped to medium, i.e. 0. */
	old_prf_val = ((old_prf_flag >> 1) ? -1 : 1) * (old_prf_flag & 0x1);
	new_prf_val = ((new_prf_flag >> 1) ? -1 : 1) * (new_prf_flag & 0x1);

	return (new_prf_val < old_prf_val);
}

/* Given the interface preference between 1 and POL_MN_IF_MIN_PREFERENCE
 * and a router preference value (0 if none), the function returns a metric
 * to use for the default route using that interface via that router. */
static uint32_t md_router_compute_def_route_metric(uint16_t iface_pref,
						   uint8_t  rtr_pref)
{
	uint32_t metric = DEFAULT_ROUTE_METRIC;
	metric -= 3*(POL_MN_IF_MIN_PREFERENCE - iface_pref);

	if (iface_pref > POL_MN_IF_MIN_PREFERENCE ||
	    iface_pref == 0) /* 0 should not be met */
		iface_pref = POL_MN_IF_MIN_PREFERENCE;

	/* "sub-modulate" with possible def rtr pref from RA. We
	 * simply consider it as a two-bit signed integer here */
	metric -= (((rtr_pref & 0x3) >> 1) ? -1 : 1) * (rtr_pref & 0x1);

	return metric;
}

static void md_update_router_stats(struct md_router *rtr)
{
	struct list_head *list;
	struct in6_addr coa;
	uint8_t rtr_pref = (rtr->ra_flags >> 3) & 0x03;
	uint32_t metric;
	uint16_t iface_pref = POL_MN_IF_MIN_PREFERENCE;
	struct md_inet6_iface *iface;

	neigh_add(rtr->ifindex, NUD_STALE, NTF_ROUTER,
		  &rtr->lladdr, rtr->hwa, rtr->hwalen, 1);

	/* Deal with interface preference as set by user ... */
	if ((iface = md_get_inet6_iface(&ifaces, rtr->ifindex)) == NULL)
		MDBG2("Router we are inserting a route for is "
		      "reachable via an unknown interface (%d)\n",
		      rtr->ifindex);
	else
		iface_pref = iface->preference;

	metric = md_router_compute_def_route_metric(iface_pref, rtr_pref);

	MDBG2("adding default route via %x:%x:%x:%x:%x:%x:%x:%x with metric"
	      " %d\n", NIP6ADDR(&rtr->lladdr), metric);

	route_add(rtr->ifindex, RT_TABLE_MAIN, RTPROT_RA,
		  RTF_DEFAULT|RTF_ADDRCONF, metric,
		  &in6addr_any, 0, &in6addr_any, 0, &rtr->lladdr);
	
	list_for_each(list, &rtr->prefixes) {
		struct prefix_list_entry *p;
		p = list_entry(list, struct prefix_list_entry, list);
		/* pass prefix to kernel if it was included in the latest RA */
		if (!tsbefore(rtr->timestamp, p->timestamp) &&
		    p->ple_prefd_time <= p->ple_valid_time) {
			ipv6_addr_set(&coa,
				      (&p->ple_prefix)->s6_addr32[0],
				      (&p->ple_prefix)->s6_addr32[1],
				      (&(rtr->iface)->lladdr)->s6_addr32[2],
				      (&(rtr->iface)->lladdr)->s6_addr32[3]);

			MDBG("Adding CoA %x:%x:%x:%x:%x:%x:%x:%x on interface"
			     " (%d)\n", NIP6ADDR(&coa),rtr->ifindex);

			addr_add(&coa, p->ple_plen, 0, RT_SCOPE_UNIVERSE,
				 rtr->ifindex, p->ple_prefd_time,
				 p->ple_valid_time);

			if (p->ple_flags & ND_OPT_PI_FLAG_RADDR)
				neigh_add(rtr->ifindex, NUD_STALE,
					  NTF_ROUTER, &p->ple_prefix,
					  rtr->hwa, rtr->hwalen, 1);
		}
	}
	if (rtr->hoplimit != 0) {
		set_iface_proc_entry(PROC_SYS_IP6_CURHLIM,
				     rtr->iface->name, rtr->hoplimit);
	} else {
		set_iface_proc_entry(PROC_SYS_IP6_CURHLIM,
				     rtr->iface->name, DEFAULT_HOP_LIMIT);
	}
	if (rtr->mtu >= IP6_MIN_MTU)
		set_iface_proc_entry(PROC_SYS_IP6_LINKMTU,
				     rtr->iface->name, rtr->mtu);
	if (tsisset(rtr->reachable)) {
		set_iface_proc_entry(PROC_SYS_IP6_BASEREACHTIME_MS,
				     rtr->iface->name, 
				     tstomsec(rtr->reachable));
		rtr->iface->reachable = rtr->reachable;
	} else {
		set_iface_proc_entry(PROC_SYS_IP6_BASEREACHTIME_MS,
				     rtr->iface->name, 
				     DEFAULT_REACHABLE_TIME);
		tssetmsec(rtr->iface->reachable, DEFAULT_REACHABLE_TIME);
	}
	if (tsisset(rtr->retransmit)) {
		set_iface_proc_entry(PROC_SYS_IP6_RETRANSTIMER_MS,
				     rtr->iface->name, 
				     tstomsec(rtr->retransmit));
		rtr->iface->retransmit = rtr->retransmit;
	} else {
		set_iface_proc_entry(PROC_SYS_IP6_RETRANSTIMER_MS,
				     rtr->iface->name, 
				     DEFAULT_RETRANSMIT_TIMER);
		tssetmsec(rtr->iface->retransmit, DEFAULT_RETRANSMIT_TIMER);
	}
}

static void md_prefix_rule_add(struct prefix_list_entry *p)
{
	struct in6_addr prefix;
	ipv6_addr_prefix(&prefix, &p->ple_prefix, p->ple_plen);
	rule_add(NULL, RT6_TABLE_MAIN, IP6_RULE_PRIO_MIP6_COA_OUT,
		 RTN_UNICAST, &prefix, p->ple_plen,
		 &in6addr_any, 0, 0);
}

static void md_update_router(struct md_router *new, struct md_router *old)
{
	struct list_head *lnew, *n;

	MDBG2("updating router %x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
	      NIP6ADDR(&old->lladdr), old->iface->name, old->iface->ifindex);

	if (tsisset(old->lifetime))
		del_task(&old->tqe);

	old->timestamp = new->timestamp;
	old->adv_ival = new->adv_ival;
	old->rtr_lifetime = new->rtr_lifetime;
	old->hoplimit = new->hoplimit;
	old->ra_flags = new->ra_flags;
	old->reachable = new->reachable;
	old->retransmit = new->retransmit;
	old->mtu = new->mtu;
	old->lifetime = new->lifetime;

	list_for_each_safe(lnew, n, &new->prefixes) {
		struct prefix_list_entry *pnew, *pold;

		pnew = list_entry(lnew, struct prefix_list_entry, list);
		if ((pold = prefix_list_get(&old->prefixes,
					    &pnew->ple_prefix,
					    pnew->ple_plen)) != NULL) {
			pold->timestamp = pnew->timestamp;
			pold->ple_flags = pnew->ple_flags;
			pold->ple_valid_time = pnew->ple_valid_time;
			pold->ple_prefd_time = pnew->ple_prefd_time;
		} else {
			if (old->used)
				md_prefix_rule_add(pnew);
			list_del(lnew);
			list_add_tail(lnew, &old->prefixes);
			old->prefix_cnt++;
			if (pnew->ple_flags & ND_OPT_PI_FLAG_RADDR)
				old->raddr_cnt++;
		}
	}
	__md_free_router(new);
	if (old->used)
		md_update_router_stats(old);
	if (tsisset(old->lifetime)) {
		struct timespec expires;
		tsadd(old->lifetime, old->timestamp, expires);
		add_task_abs(&expires, &old->tqe, md_router_timeout_probe);
	}
}

static int md_block_rule_add(struct md_inet6_iface *iface)
{
	iface->iface_flags |= MD_BLOCK_TRAFFIC;
	/* Allow DAD probes and RS messages */
	rule_add(NULL, RT6_TABLE_MAIN,
		 IP6_RULE_PRIO_MIP6_COA_OUT, RTN_UNICAST,
		 &in6addr_any, 128, &in6addr_any, 0, 0);
	/* drop outgoing global traffic until DAD has been performed
	   on CoA to make routing and tunnel end-point updates atomic
	   during handoff */
	return rule_add(NULL, 0,
			IP6_RULE_PRIO_MIP6_BLOCK_HOA, RTN_BLACKHOLE,
			&in6addr_any, 0, &in6addr_any, 0, 0);
}

static void md_add_default_router(struct md_inet6_iface *iface,
				  struct md_router *rtr)
{
	assert(list_empty(&iface->default_rtr));
	assert(rtr->used);
	list_add(&rtr->list, &iface->default_rtr);
	md_check_home_link(iface, rtr);
}

static void md_change_default_router(struct md_inet6_iface *iface,
				     struct md_router *new,
 				     struct md_router *old)
{
	struct timespec expires;
	struct list_head *l;
	int link_changed = 1;
	if (!tsisset(new->lifetime)) {
		__md_free_router(new);
		return;
	}
	MDBG("add new router %x:%x:%x:%x:%x:%x:%x:%x on interface %s (%d)\n", 
	     NIP6ADDR(&new->lladdr), new->iface->name, new->iface->ifindex);

	list_for_each(l, &new->prefixes) {
		struct prefix_list_entry *p;

		p = list_entry(l, struct prefix_list_entry, list);

		if (old && prefix_list_find(&old->prefixes, 
					    &p->ple_prefix,
					    p->ple_plen))
			link_changed = 0;
		else
			md_prefix_rule_add(p);
	}
	if (old)
		md_expire_router(iface, old, new);

	if (list_empty(&iface->coas) &&
	    !(iface->iface_flags & MD_BLOCK_TRAFFIC)) {
		md_block_rule_add(iface);
	}
	new->used = 1;
	md_add_default_router(iface, new);
	__md_new_link(iface, link_changed);

	md_update_router_stats(new);
	tsadd(new->lifetime, new->timestamp, expires);
	add_task_abs(&expires, &new->tqe, md_router_timeout_probe);

	if (!list_empty(&iface->coas) &&
	    !(iface->iface_flags & MD_LINK_LOCAL_DAD))
		__md_trigger_movement_event(ME_RTR_NEW, 0, iface, NULL);
}

static void
md_check_expired_coas(struct md_inet6_iface *iface, struct md_router *rtr)
{
	struct list_head *clist, *n;
	struct list_head *plist;
	
	list_for_each(plist, &rtr->prefixes) {
		struct prefix_list_entry *p;
		p = list_entry(plist, struct prefix_list_entry, list);
		list_for_each_safe(clist, n, &iface->expired_coas) { 
			struct md_coa *coa;
			coa = list_entry(clist, struct md_coa, list);

			if (ipv6_pfx_cmp(&p->ple_prefix,  
					 &coa->addr, coa->plen))
				continue;

			MDBG2("CoA %x:%x:%x:%x:%x:%x:%x:%x still usable\n", 
			      NIP6ADDR(&coa->addr));

			list_del(&coa->list);
			list_add_tail(&coa->list, &iface->coas);
		}
	}
}


static int 
md_check_expired_routers(struct md_inet6_iface *iface, struct md_router *new)
{
	struct list_head *list, *n;
	
	list_for_each_safe(list, n, &iface->expired_rtrs) {
		struct md_router *old;
		old = list_entry(list, struct md_router, list);
		if (!md_router_cmp(new, old)) {
			struct timespec diff;
			long passed_ms, valid_ms;

			/* don't used previously unused expired md_routers
			   since they might contain outdated information */
			if (!old->used)
				return 0;

			tssub(new->timestamp, old->timestamp, diff);
			passed_ms = tstomsec(diff);
			valid_ms = adv_ivals_dad_limit * 
				tstomsec(old->adv_ival);
			
			if (passed_ms >= valid_ms)
				break;

			MDBG2("router %x:%x:%x:%x:%x:%x:%x:%x still usable\n", 
			      NIP6ADDR(&old->lladdr));

			md_update_router(new, old);
			list_del(&old->list);
			md_add_default_router(iface, old);
			md_check_expired_coas(iface, old);
			__md_new_link(iface, 0);
			if (!list_empty(&iface->coas) &&
			    !(iface->iface_flags & MD_LINK_LOCAL_DAD))
				__md_trigger_movement_event(ME_RTR_BACK, 0,
							    iface, NULL);
			return 1;
		}
		
	}
	return 0;
}

static int
md_check_backup_routers(struct md_inet6_iface *iface, struct md_router *new)
{
	struct list_head *list, *n;
	
	list_for_each_safe(list, n, &iface->backup_rtrs) {
		struct md_router *old;
		old = list_entry(list, struct md_router, list);
		if (!md_router_cmp(new, old)) {
			md_update_router(new, old);
			if (!tsisset(old->lifetime)) {
				md_expire_router(iface, old, NULL);
			}
			return 1;
		}
	}
	return 0;
}

static void md_add_backup_router(struct md_inet6_iface *iface,
				 struct md_router *new)
{
	struct timespec expires;

	if (!tsisset(new->lifetime)) {
		__md_free_router(new);
		return;
	}

	MDBG("add new backup router %x:%x:%x:%x:%x:%x:%x:%x on interface %s (%d)\n", 
	     NIP6ADDR(&new->lladdr), new->iface->name, new->iface->ifindex);

	list_add(&new->list, &iface->backup_rtrs);

	tsadd(new->lifetime, new->timestamp, expires);
	add_task_abs(&expires, &new->tqe, md_router_timeout_probe);
}

static void 
md_check_default_router(struct md_inet6_iface *iface, struct md_router *new)
{
	struct md_router *old;

	MDBG2("looking for existing routers on iface %s (%d)\n", 
	      iface->name, iface->ifindex);

	if ((old = md_get_first_router(&iface->default_rtr)) == NULL)
		goto change_def_rtr;

	/* We had a default router referenced on that interface (old).
	 * Now check if new one and old one are in fact the same. */
	if (md_router_cmp(new, old))
		goto new_router_found;

	md_update_router(new, old); /* new: freed in update */
	if (!tsisset(old->lifetime)) {
		md_expire_router(iface, old, NULL);
		__md_discover_router(iface);
		__md_trigger_movement_event(ME_RTR_EXPIRED, 0, iface, NULL);
	} else {
		__md_new_link(iface, 0);
		__md_trigger_movement_event(ME_RTR_UPDATED, 0, iface, NULL);
	}
	return;

 new_router_found:
	/* There are some cases for which we want to perform NUD against
	 * old router instead of directly switching to new:
	 *  - if new has lower default router preference value
	 *  - if asked by configuration to perform NUD before switching
	 * In that case, the new router becomes a backup router */
	if (md_router_prefer_old(old, new) || conf.MnRouterProbes > 0) {
		md_probe_router(old);
		md_add_backup_router(iface, new);
		return;
	}

 change_def_rtr:
	md_change_default_router(iface, new, old);
}

static void md_recv_na(const struct icmp6_hdr *ih, ssize_t len,
		       __attribute__ ((unused)) const struct in6_addr *saddr,
		       const struct in6_addr *daddr, int iif, int hoplimit)
{
	struct nd_neighbor_advert *na = (struct nd_neighbor_advert *)ih;
	int optlen;
	struct md_inet6_iface *iface;
	struct md_router *rtr;
	uint8_t *opt;
	uint8_t hwa[L2ADDR_MAX_SIZE];
	int hwalen = 0;

	if (hoplimit < 255 || ih->icmp6_code != 0 ||
	    len < 0 || (size_t)len < sizeof(struct nd_neighbor_advert) ||
	    IN6_IS_ADDR_MULTICAST(&na->nd_na_target) ||
	    (na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED &&
	     IN6_IS_ADDR_MULTICAST(daddr)))
		return;

	pthread_mutex_lock(&iface_lock);

	if ((iface = md_get_inet6_iface(&ifaces, iif)) == NULL ||
	    (rtr = md_get_first_router(&iface->default_rtr)) == NULL ||
	    rtr->probed == 0 || rtr->hwalen < 0 ||
	    !IN6_ARE_ADDR_EQUAL(&rtr->solicited_addr, &na->nd_na_target)) {
		goto out;
	}
	optlen = len - sizeof(struct nd_neighbor_advert);
	opt = (uint8_t *)(na + 1);

	MDBG2("received NA from %x:%x:%x:%x:%x:%x:%x:%x on iface %s (%d)\n", 
	      NIP6ADDR(&rtr->lladdr), iface->name, iface->ifindex);
		
		
	while (optlen > 1) {
		int olen = opt[1] << 3;
		
		if (olen > optlen || olen == 0) 
			goto out;
		
		switch (opt[0]) {
		case ND_OPT_TARGET_LINKADDR:
			hwalen = ndisc_l2addr_from_opt(iface->type, hwa,
						       &opt[2], olen - 2);
			if (hwalen < 0 || hwalen != rtr->hwalen)
				goto out;
			break;
		}
		optlen -= olen;
		opt += olen;
	}
	if (na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER &&
	    (hwalen || !memcmp(hwa, &rtr->hwa, rtr->hwalen))) {
		struct timespec expires;
		clock_gettime(CLOCK_REALTIME, &rtr->timestamp);
		if (tsisset(rtr->lifetime))
			del_task(&rtr->tqe);
		rtr->lifetime = rtr->iface->reachable;
		rtr->solicited_addr = in6addr_any;
		rtr->probed = 0;
		tsadd(rtr->lifetime, rtr->timestamp, expires);
		add_task_abs(&expires, &rtr->tqe, md_router_timeout_probe);
		if (!list_empty(&iface->coas) &&
		    !(iface->iface_flags & MD_LINK_LOCAL_DAD))
			__md_trigger_movement_event(ME_RTR_BACK, 0,
						    iface, NULL);
	} else {
		if (tsisset(rtr->lifetime))
			del_task(&rtr->tqe);
		md_router_timeout(rtr);
	}
out:
	pthread_mutex_unlock(&iface_lock);
}

static struct icmp6_handler md_na_handler = {
	.recv = md_recv_na,
};

static void md_recv_ra(const struct icmp6_hdr *ih, ssize_t len,
		       const struct in6_addr *saddr,
		       const struct in6_addr *daddr, int iif, int hoplimit)
{
	struct nd_router_advert *ra = (struct nd_router_advert *)ih;
	struct md_inet6_iface *iface;
	struct md_router *new;

	/* validity checks */
	if (hoplimit < 255 || !IN6_IS_ADDR_LINKLOCAL(saddr) ||
	    ih->icmp6_code != 0 || len < 0 ||
	    (size_t)len < sizeof(struct nd_router_advert) ||
	    !conf.pmgr.accept_ra(iif, saddr, daddr, ra))
		return;

	MDBG2("received RA from %x:%x:%x:%x:%x:%x:%x:%x on iface %d\n", 
	      NIP6ADDR(saddr), iif);

	pthread_mutex_lock(&iface_lock);
	if ((iface = md_get_inet6_iface(&ifaces, iif)) != NULL &&
	    (!iface->is_tunnel) &&
	    (new = md_create_router(iface, saddr, ra, len)) != NULL) {
		if (!md_check_expired_routers(iface, new) &&
		    !md_check_backup_routers(iface, new))
			md_check_default_router(iface, new);
	}
	pthread_mutex_unlock(&iface_lock);
}

static struct icmp6_handler md_ra_handler = {
	.recv = md_recv_ra,
};

struct rtnl_handle md_rth;

static void *md_nl_listen(__attribute__ ((unused)) void *arg)
{
	pthread_dbg("thread started");
	rtnl_listen(&md_rth, process_nlmsg, NULL);
	pthread_exit(NULL);
}

static void iface_default_proc_entries_init(void)
{
	get_iface_proc_entry(PROC_SYS_IP6_AUTOCONF,
			     "default", &conf_default_autoconf);
	get_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA,
			     "default", &conf_default_ra);
	get_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA_DEFRTR,
			     "default", &conf_default_ra_defrtr);
	get_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICITS,
			     "default", &conf_default_rs);
	get_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICIT_INTERVAL,
			     "default", &conf_default_rs_ival);

	set_iface_proc_entry(PROC_SYS_IP6_AUTOCONF,
			     "default", conf_autoconf);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA,
			     "default", conf_ra);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA_DEFRTR,
			     "default", conf_ra_defrtr);
	set_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICITS,
			     "default", conf_rs);
}

int md_init(void)
{
	int err;
	pthread_mutexattr_t mattrs;
	int val;

	pthread_mutexattr_init(&mattrs);
	pthread_mutexattr_settype(&mattrs, PTHREAD_MUTEX_FAST_NP);
	if (pthread_mutex_init(&iface_lock, &mattrs))
		return -1;

	if ((err = rtnl_route_open(&md_rth, 0)) < 0)
		return err;

	val = RTNLGRP_LINK;
	if (setsockopt(md_rth.fd, SOL_NETLINK,
		       NETLINK_ADD_MEMBERSHIP, &val, sizeof(val)) < 0) {
		dbg("%d %s\n", __LINE__, strerror(errno));  
		return -1;
	}
	val = RTNLGRP_NEIGH;
	if (setsockopt(md_rth.fd, SOL_NETLINK,
		       NETLINK_ADD_MEMBERSHIP, &val, sizeof(val)) < 0) {
		dbg("%d %s\n", __LINE__, strerror(errno));  
		return -1;
	}
	val = RTNLGRP_IPV6_IFADDR;
	if (setsockopt(md_rth.fd, SOL_NETLINK,
		       NETLINK_ADD_MEMBERSHIP, &val, sizeof(val)) < 0) {
		dbg("%d %s\n", __LINE__, strerror(errno));  
		return -1;
	}
	val = RTNLGRP_IPV6_IFINFO;
	if (setsockopt(md_rth.fd, SOL_NETLINK,
		       NETLINK_ADD_MEMBERSHIP, &val, sizeof(val)) < 0) {
		dbg("%d %s\n", __LINE__, strerror(errno));  
		return -1;
	}
	iface_default_proc_entries_init();
	return 0;
}

int md_start(void)
{
	icmp6_handler_reg(ND_NEIGHBOR_ADVERT, &md_na_handler);
	icmp6_handler_reg(ND_ROUTER_ADVERT, &md_ra_handler);

	/* At startup, internal structures are initialized below by
	 * iterating on information returned by netlink. For tunnel
	 * interfaces, CoA are learned. For non-tunnel interfaces,
	 * RS are sent and RA received (asynchronously). Simply put,
	 * a lot of interfaces/CoA may appear in a short timeframe
	 * which may result in UMIP changing its mind and switching
	 * between those. Because we are usually not in a rush at
	 * startup, it's better to wait for things to calm down and
	 * for all options to be available before deciding which
	 * iface/CoA to use. This is particularly true when dynamic
	 * keying (IKE) is used because changing CoA in the middle
	 * of the negotiation is a bad idea.
	 * By having md_movements_disabled set to a non-zero value,
	 * __md_trigger_movement_event() prevents movements to occur.
	 * After a the expected delay, md_movements_disabled is set
	 * reset to 0 and a movement is forced so that the best
	 * interface gets selected at that point. --arno */
	if (!tsisset(conf.InterfaceInitialInitDelay_ts))
		md_movements_disabled = 0;

	if (pthread_create(&md_listener, NULL, md_nl_listen, NULL))
		return -1;

	inet6_ifaces_iterate(process_nlmsg, NULL);

	if (md_movements_disabled) { /* Delay loop */
		struct timespec req, rem;
		tscpy(req, conf.InterfaceInitialInitDelay_ts);
		while (nanosleep(&req, &rem)) {
			tscpy(req, rem);
		}
		md_movements_disabled = 0;
	}

	pthread_mutex_lock(&iface_lock);
	__md_trigger_movement_event(ME_INIT, 0, NULL, NULL);
	pthread_mutex_unlock(&iface_lock);

	return 0;
}

void md_stop(void)
{
	rtnl_close(&md_rth);
	icmp6_handler_dereg(ND_ROUTER_ADVERT, &md_ra_handler);
	icmp6_handler_dereg(ND_NEIGHBOR_ADVERT, &md_na_handler);
	pthread_cancel(md_listener);
	pthread_join(md_listener, NULL);
}

static void iface_default_proc_entries_cleanup(void)
{
	set_iface_proc_entry(PROC_SYS_IP6_AUTOCONF,
			     "default", conf_default_autoconf);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA,
			     "default", conf_default_ra);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA_DEFRTR,
			     "default", conf_default_ra_defrtr);
	set_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICITS,
			     "default", conf_default_rs);
}

static void iface_proc_entries_cleanup(struct md_inet6_iface *iface)
{
	set_iface_proc_entry(PROC_SYS_IP6_FORWARDING, iface->name,
			     iface->devconf[DEVCONF_FORWARDING]);
	set_iface_proc_entry(PROC_SYS_IP6_AUTOCONF, iface->name,
			     iface->devconf[DEVCONF_AUTOCONF]);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA, iface->name,
			     iface->devconf[DEVCONF_ACCEPT_RA]);
	set_iface_proc_entry(PROC_SYS_IP6_ACCEPT_RA_DEFRTR, iface->name,
			     iface->devconf[DEVCONF_ACCEPT_RA_DEFRTR]);
	set_iface_proc_entry(PROC_SYS_IP6_RTR_SOLICITS, iface->name, 
			     iface->devconf[DEVCONF_RTR_SOLICITS]);
}

void md_cleanup(void)
{
	struct list_head *l, *n;

	pthread_mutex_lock(&iface_lock);
	iface_default_proc_entries_cleanup();
	list_for_each_safe(l, n, &ifaces) {
		struct md_inet6_iface *iface;
		iface = list_entry(l, struct md_inet6_iface, list);
		md_expire_inet6_iface(iface);
		iface_proc_entries_cleanup(iface);
		if (!iface->is_tunnel)
			ndisc_send_rs(iface->ifindex, &in6addr_all_routers_mc,
				      NULL, 0);
		md_free_inet6_iface(iface);
	}
	pthread_mutex_unlock(&iface_lock);
	return;
}
