/* nhrp_peer.c - NHRP peer cache implementation
 *
 * Copyright (c) 2007-2012 Timo Teräs <timo.teras@iki.fi>
 *
 * This software is licensed under the MIT License.
 * See MIT-LICENSE.txt for additional details.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <netinet/in.h>
#include "nhrp_common.h"
#include "nhrp_peer.h"
#include "nhrp_interface.h"

#define NHRP_PEER_FORMAT_LEN		128

#define NHRP_SCRIPT_TIMEOUT		(2*60)
#define NHRP_NEGATIVE_CACHE_TIME	(3*60)

#define NHRP_RETRY_REGISTER_TIME	(30 + random()/(RAND_MAX/60))
#define NHRP_RETRY_ERROR_TIME		(60 + random()/(RAND_MAX/120))

#define NHRP_PEER_FLAG_PRUNE_PENDING	0x00010000

const char * const nhrp_peer_type[] = {
	[NHRP_PEER_TYPE_INCOMPLETE]	= "incomplete",
	[NHRP_PEER_TYPE_NEGATIVE]	= "negative",
	[NHRP_PEER_TYPE_CACHED]		= "cached",
	[NHRP_PEER_TYPE_SHORTCUT_ROUTE]	= "shortcut-route",
	[NHRP_PEER_TYPE_DYNAMIC]	= "dynamic",
	[NHRP_PEER_TYPE_DYNAMIC_NHS]	= "dynamic-nhs",
	[NHRP_PEER_TYPE_STATIC]		= "static",
	[NHRP_PEER_TYPE_STATIC_DNS]	= "dynamic-map",
	[NHRP_PEER_TYPE_LOCAL_ROUTE]	= "local-route",
	[NHRP_PEER_TYPE_LOCAL_ADDR]	= "local",
};

static int nhrp_peer_num_total = 0;
static struct list_head local_peer_list = LIST_INITIALIZER(local_peer_list);

static inline
int holding_time_to_reregister_time(int holding_time)
{
	/* RFC-2332 5.2.3 recommends one third of holding time as
	 * reregistration interval. */
	return holding_time / 3 + 1;
}

static inline
int holding_time_to_expiry_time(int holding_time, int offset_time)
{
	/* Reresolution interval */
	int expire;
	expire = 2 * holding_time / 3 - offset_time;
	if (expire < 0)
		expire = 0;
	return expire + 1;
}

/* Peer entrys life, pending callbacks and their call order are listed
 * here.
 *
 * Generally everything starts from nhrp_peer_insert() call which schedules
 * (during startup) or directly invokes nhrp_peer_insert_cb().
 *
 * INCOMPLETE:
 * 1. nhrp_peer_insert_cb: send resolution request
 * 2. nhrp_peer_handle_resolution_reply: entry deleted or reinserted NEGATIVE
 *
 * NEGATIVE:
 * 1. nhrp_peer_insert_cb: schedule task remove
 *
 * CACHED, STATIC, DYNAMIC, DYNAMIC_NHS:
 * 1. nhrp_peer_insert_cb: calls nhrp_peer_restart_cb
 * 2. nhrp_peer_restart_cb: resolves dns name, or calls nhrp_run_up_script()
 * 3. nhrp_peer_address_query_cb: calls nhrp_peer_run_up_script()
 * 4. nhrp_peer_run_up_script: spawns script, or goes to nhrp_peer_lower_is_up()
 * 5. nhrp_peer_script_peer_up_done: calls nhrp_peer_lower_is_up()
 * 6. nhrp_peer_lower_is_up: sends registration, or goes to nhrp_peer_is_up()
 * 7. nhrp_peer_handle_registration_reply:
 *	a. on success: calls nhrp_peer_is_up()
 *	b. on error reply: calls nhrp_peer_send_purge_protocol()
 *	   nhrp_peer_handle_purge_protocol_reply: sends new registration
 * 8. nhrp_peer_is_up: schedules re-register, expire or deletion
 *
 * ON EXPIRE:
 *	schedule remove
 *	nhrp_peer_renew is called if peer has USED flag set or becomes set,
 *	while the peer is expired
 * ON RENEW: calls sends resolution request, schedule EXPIRE
 *
 * ON ERROR for CACHED: reinsert as NEGATIVE
 * ON ERROR for STATIC: fork peer-down script (if was lower up)
 *			schedule task request link
 * ON ERROR for DYNAMIC: fork peer-down script (if was lower up)
 *			 delete peer
 *
 * SHORTCUT_ROUTE:
 * 1. nhrp_peer_insert_cb: spawns route-up script, or schedules EXPIRE
 *
 * STATIC_DNS:
 * 1. nhrp_peer_insert_cb: calls nhrp_peer_dnsmap_restart_cb
 * 2. nhrp_peer_dnsmap_restart_cb: resolves dns name
 * 3. nhrp_peer_dnsmap_query_cb: create new peer entries,
 *      renew existing and delete expired, schedule restart
 *
 * LOCAL:
 * nothing, only netlink code modifies these
 */

static void nhrp_peer_reinsert(struct nhrp_peer *peer, int type);
static void nhrp_peer_restart_cb(struct ev_timer *w, int revents);
static void nhrp_peer_dnsmap_restart_cb(struct ev_timer *w, int revents);
static void nhrp_peer_remove_cb(struct ev_timer *w, int revents);
static void nhrp_peer_send_resolve(struct nhrp_peer *peer);
static void nhrp_peer_send_register_cb(struct ev_timer *w, int revents);
static void nhrp_peer_expire_cb(struct ev_timer *w, int revents);

static const char *nhrp_error_indication_text(int ei)
{
	switch (ei) {
	case -1:
		return "timeout";
	case NHRP_ERROR_UNRECOGNIZED_EXTENSION:
		return "unrecognized extension";
	case NHRP_ERROR_LOOP_DETECTED:
		return "loop detected";
	case NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE:
		return "protocol address unreachable";
	case NHRP_ERROR_PROTOCOL_ERROR:
		return "protocol error";
	case NHRP_ERROR_SDU_SIZE_EXCEEDED:
		return "SDU size exceeded";
	case NHRP_ERROR_INVALID_EXTENSION:
		return "invalid extension";
	case NHRP_ERROR_INVALID_RESOLUTION_REPLY:
		return "unexpected resolution reply";
	case NHRP_ERROR_AUTHENTICATION_FAILURE:
		return "authentication failure";
	case NHRP_ERROR_HOP_COUNT_EXCEEDED:
		return "hop count exceeded";
	}
	return "unknown";
}

static const char *nhrp_cie_code_text(int ct)
{
	switch (ct) {
	case NHRP_CODE_SUCCESS:
		return "success";
	case NHRP_CODE_ADMINISTRATIVELY_PROHIBITED:
		return "administratively prohibited";
	case NHRP_CODE_INSUFFICIENT_RESOURCES:
		return "insufficient resources";
	case NHRP_CODE_NO_BINDING_EXISTS:
		return "no binding exists";
	case NHRP_CODE_BINDING_NON_UNIQUE:
		return "binding non-unique";
	case NHRP_CODE_UNIQUE_ADDRESS_REGISTERED:
		return "unique address already registered";
	}
	return "unknown";
}

static char *nhrp_peer_format_full(struct nhrp_peer *peer, size_t len,
				   char *buf, int full)
{
	char tmp[NHRP_PEER_FORMAT_LEN], *str;
	int i = 0;

	if (peer == NULL) {
		snprintf(buf, len, "(null)");
		return buf;
	}

	i += snprintf(&buf[i], len - i, "%s/%d",
		nhrp_address_format(&peer->protocol_address, sizeof(tmp), tmp),
		peer->prefix_length);

	if (peer->next_hop_address.type != PF_UNSPEC) {
		switch (peer->type) {
		case NHRP_PEER_TYPE_SHORTCUT_ROUTE:
		case NHRP_PEER_TYPE_LOCAL_ROUTE:
			str = "nexthop";
			break;
		case NHRP_PEER_TYPE_LOCAL_ADDR:
			str = "alias";
			break;
		default:
			str = "nbma";
			break;
		}
		i += snprintf(&buf[i], len - i, " %s %s",
			str,
			nhrp_address_format(&peer->next_hop_address,
			sizeof(tmp), tmp));
	}
	if (peer->nbma_hostname != NULL) {
		i += snprintf(&buf[i], len - i, " hostname %s",
			peer->nbma_hostname);
	}
	if (peer->next_hop_nat_oa.type != PF_UNSPEC) {
		i += snprintf(&buf[i], len - i, " nbma-nat-oa %s",
			      nhrp_address_format(&peer->next_hop_nat_oa,
						  sizeof(tmp), tmp));
	}
	if (peer->interface != NULL)
		i += snprintf(&buf[i], len - i, " dev %s",
			      peer->interface->name);
	if (peer->mtu)
		i += snprintf(&buf[i], len - i, " mtu %d", peer->mtu);

	if (!full)
		return buf;

	if (peer->flags & NHRP_PEER_FLAG_USED)
		i += snprintf(&buf[i], len - i, " used");
	if (peer->flags & NHRP_PEER_FLAG_UNIQUE)
		i += snprintf(&buf[i], len - i, " unique");
	if (peer->flags & NHRP_PEER_FLAG_UP)
		i += snprintf(&buf[i], len - i, " up");
	else if (peer->flags & NHRP_PEER_FLAG_LOWER_UP)
		i += snprintf(&buf[i], len - i, " lower-up");
	if (peer->expire_time != 0.0) {
		int rel;

		rel = peer->expire_time - ev_now();
		if (rel >= 0) {
			i += snprintf(&buf[i], len - i, " expires_in %d:%02d",
				      rel / 60, rel % 60);
		} else {
			i += snprintf(&buf[i], len - i, " expired");
		}
	}
	if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING)
		i += snprintf(&buf[i], len - i, " dying");

	return buf;
}

static inline char *nhrp_peer_format(struct nhrp_peer *peer,
				     size_t len, char *buf)
{
	return nhrp_peer_format_full(peer, len, buf, TRUE);
}

static inline void nhrp_peer_debug_refcount(const char *func,
					    struct nhrp_peer *peer)
{
#if 0
	char tmp[NHRP_PEER_FORMAT_LEN];
	nhrp_debug("%s(%s %s) ref=%d",
		   func, nhrp_peer_type[peer->type],
		   nhrp_peer_format(peer, sizeof(tmp), tmp),
		   peer->ref);
#endif
}

static void nhrp_peer_resolve_nbma(struct nhrp_peer *peer)
{
	char tmp[64];
	int r;

	if (peer->interface->nbma_address.type == PF_UNSPEC) {
		r = kernel_route(NULL, &peer->next_hop_address,
				 &peer->my_nbma_address, NULL,
				 &peer->my_nbma_mtu);
		if (!r) {
			nhrp_error("No route to next hop address %s",
				   nhrp_address_format(&peer->next_hop_address,
						       sizeof(tmp), tmp));
		}
	} else {
		peer->my_nbma_address = peer->interface->nbma_address;
		peer->my_nbma_mtu = peer->interface->nbma_mtu;
	}
}

static char *env(const char *key, const char *value)
{
	char *buf;
	buf = malloc(strlen(key)+strlen(value)+2);
	if (buf == NULL)
		return NULL;
	sprintf(buf, "%s=%s", key, value);
	return buf;
}

static char *envu32(const char *key, uint32_t value)
{
	char *buf;
	buf = malloc(strlen(key)+16);
	if (buf == NULL)
		return NULL;
	sprintf(buf, "%s=%u", key, value);
	return buf;
}

int nhrp_peer_event_ok(union nhrp_peer_event e, int revents)
{
	int status;

	if (revents == 0)
		return TRUE;
	if (!(revents & EV_CHILD))
		return FALSE;
	status = e.child->rstatus;
	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
		return TRUE;
	return FALSE;
}

char *nhrp_peer_event_reason(union nhrp_peer_event e, int revents,
			     size_t buflen, char *buf)
{
	int status;

	if (revents & EV_CHILD) {
		status = e.child->rstatus;
		if (WIFEXITED(status))
			snprintf(buf, buflen, "exitstatus %d",
				 WEXITSTATUS(status));
		else if (WIFSIGNALED(status))
			snprintf(buf, buflen, "signal %d",
				 WTERMSIG(status));
		else
			snprintf(buf, buflen, "rstatus %d", status);
	} else if (revents & EV_TIMEOUT) {
		snprintf(buf, buflen, "timeout");
	} else if (revents == 0) {
		snprintf(buf, buflen, "success");
	} else {
		snprintf(buf, buflen, "unknown, revents=%x", revents);
	}
	return buf;
}

struct nhrp_peer *nhrp_peer_from_event(union nhrp_peer_event e, int revents)
{
	struct nhrp_peer *peer;

	if (revents & EV_CHILD) {
		peer = container_of(e.child, struct nhrp_peer, child);
	} else if (revents & EV_TIMEOUT) {
		peer = container_of(e.timer, struct nhrp_peer, timer);
	} else {
		NHRP_BUG_ON(revents != 0);
		peer = container_of(e.child, struct nhrp_peer, child);
	}

	ev_child_stop(&peer->child);
	ev_timer_stop(&peer->timer);

	return peer;
}

void nhrp_peer_run_script(struct nhrp_peer *peer, char *action,
			  void (*cb)(union nhrp_peer_event, int))
{
	struct nhrp_interface *iface = peer->interface;
	const char *argv[] = { nhrp_script_file, action, NULL };
	char *envp[32];
	char tmp[64];
	pid_t pid;
	int i = 0;

	/* Resolve own NBMA address before forking if required
	 * since it requires traversing peer cache and can trigger
	 * logging and other stuff. */
	if (peer->my_nbma_address.type == PF_UNSPEC)
		nhrp_peer_resolve_nbma(peer);

	/* Fork and execute script */
	pid = fork();
	if (pid == -1) {
		if (cb != NULL)
			cb(&peer->child, EV_CHILD | EV_ERROR);
		return;
	} else if (pid > 0) {
		if (cb != NULL) {
			ev_child_stop(&peer->child);
			ev_child_init(&peer->child, cb, pid, 0);
			ev_child_start(&peer->child);

			ev_set_cb(&peer->timer, cb);
			peer->timer.repeat = NHRP_SCRIPT_TIMEOUT;
			ev_timer_again(&peer->timer);
		}
		return;
	}

	envp[i++] = env("NHRP_TYPE", nhrp_peer_type[peer->type]);
	if (iface->protocol_address.type != PF_UNSPEC)
		envp[i++] = env("NHRP_SRCADDR",
				nhrp_address_format(&iface->protocol_address,
						    sizeof(tmp), tmp));
	if (peer->my_nbma_address.type != PF_UNSPEC)
		envp[i++] = env("NHRP_SRCNBMA",
				nhrp_address_format(&peer->my_nbma_address,
						    sizeof(tmp), tmp));
	envp[i++] = env("NHRP_DESTADDR",
			nhrp_address_format(&peer->protocol_address,
					    sizeof(tmp), tmp));
	envp[i++] = envu32("NHRP_DESTPREFIX", peer->prefix_length);

	if (peer->purge_reason)
		envp[i++] = env("NHRP_PEER_DOWN_REASON", peer->purge_reason);

	switch (peer->type) {
	case NHRP_PEER_TYPE_CACHED:
	case NHRP_PEER_TYPE_LOCAL_ADDR:
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_DYNAMIC:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		envp[i++] = env("NHRP_DESTNBMA",
			nhrp_address_format(&peer->next_hop_address,
					    sizeof(tmp), tmp));
		if (peer->mtu)
			envp[i++] = envu32("NHRP_DESTMTU", peer->mtu);
		if (peer->next_hop_nat_oa.type != PF_UNSPEC)
			envp[i++] = env("NHRP_DESTNBMA_NAT_OA",
				nhrp_address_format(&peer->next_hop_nat_oa,
						    sizeof(tmp), tmp));
		break;
	case NHRP_PEER_TYPE_SHORTCUT_ROUTE:
	case NHRP_PEER_TYPE_LOCAL_ROUTE:
		envp[i++] = env("NHRP_NEXTHOP",
			nhrp_address_format(&peer->next_hop_address,
					    sizeof(tmp), tmp));
		break;
	default:
		NHRP_BUG_ON("invalid peer type");
	}
	envp[i++] = env("NHRP_INTERFACE", peer->interface->name);
	envp[i++] = envu32("NHRP_GRE_KEY", peer->interface->gre_key);
	envp[i++] = NULL;

	execve(nhrp_script_file, (char **) argv, envp);
	exit(1);
}

void nhrp_peer_cancel_async(struct nhrp_peer *peer)
{
	if (peer->queued_packet) {
		nhrp_packet_put(peer->queued_packet);
		peer->queued_packet = NULL;
	}
	if (peer->request) {
		nhrp_server_finish_request(peer->request);
		peer->request = NULL;
	}

	nhrp_address_resolve_cancel(&peer->address_query);
	ev_timer_stop(&peer->timer);
	if (ev_is_active(&peer->child)) {
		kill(SIGINT, peer->child.pid);
		ev_child_stop(&peer->child);
	}
}

void nhrp_peer_send_packet_queue(struct nhrp_peer *peer)
{
	if (peer->queued_packet == NULL)
		return;

	nhrp_packet_marshall_and_send(peer->queued_packet);
	nhrp_packet_put(peer->queued_packet);
	peer->queued_packet = NULL;
}

static void nhrp_peer_schedule(struct nhrp_peer *peer, ev_tstamp timeout,
			       void (*cb)(struct ev_timer *w, int revents))
{
	ev_timer_stop(&peer->timer);
	ev_timer_init(&peer->timer, cb, timeout, 0.);
	ev_timer_start(&peer->timer);
}

static void nhrp_peer_restart_error(struct nhrp_peer *peer)
{
	switch (peer->type) {
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_STATIC_DNS:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		nhrp_peer_schedule(peer, NHRP_RETRY_ERROR_TIME,
				   nhrp_peer_restart_cb);
		break;
	default:
		nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE);
		break;
	}
}

static void nhrp_peer_script_route_up_done(union nhrp_peer_event e, int revents)
{
	struct nhrp_peer *peer = nhrp_peer_from_event(e, revents);
	char tmp[64], reason[32];

	if (nhrp_peer_event_ok(e, revents)) {
		if (revents)
			nhrp_debug("[%s] Route up script: success",
				   nhrp_address_format(&peer->protocol_address,
						       sizeof(tmp), tmp));

		peer->flags |= NHRP_PEER_FLAG_UP;
		nhrp_peer_schedule(
			peer,
			holding_time_to_expiry_time(peer->expire_time - ev_now(), 10),
			nhrp_peer_expire_cb);
	} else {
		nhrp_debug("[%s] Route up script: %s; "
			  "adding negative cached entry",
			  nhrp_address_format(&peer->protocol_address,
					      sizeof(tmp), tmp),
			  nhrp_peer_event_reason(e, revents,
						 sizeof(reason), reason));

		nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE);
	}
}

static int nhrp_peer_routes_up(void *ctx, struct nhrp_peer *peer)
{
	if (!(peer->flags & NHRP_PEER_FLAG_UP))
		nhrp_peer_run_script(peer, "route-up",
				     nhrp_peer_script_route_up_done);

	return 0;
}

static int nhrp_peer_routes_renew(void *ctx, struct nhrp_peer *peer)
{
	int *num_routes = (int *) ctx;

	if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING) {
		peer->flags &= ~NHRP_PEER_FLAG_PRUNE_PENDING;
		nhrp_peer_cancel_async(peer);
		nhrp_peer_send_resolve(peer);
		(*num_routes)++;
	}

	return 0;
}

static void nhrp_peer_renew(struct nhrp_peer *peer)
{
	struct nhrp_interface *iface = peer->interface;
	struct nhrp_peer_selector sel;
	int num_routes = 0;

	/* Renew the cached information: all related routes
	 * or the peer itself */
	if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) {
		memset(&sel, 0, sizeof(sel));
		sel.flags = NHRP_PEER_FIND_UP;
		sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE);
		sel.interface = iface;
		sel.next_hop_address = peer->protocol_address;
		nhrp_peer_foreach(nhrp_peer_routes_renew, &num_routes, &sel);
	}

	if (peer->flags & NHRP_PEER_FLAG_PRUNE_PENDING) {
		peer->flags &= ~NHRP_PEER_FLAG_PRUNE_PENDING;
		nhrp_peer_cancel_async(peer);
		nhrp_peer_send_resolve(peer);
	}
}

static int is_used(void *ctx, struct nhrp_peer *peer)
{
	if (peer->flags & NHRP_PEER_FLAG_USED)
		return 1;

	return 0;
}

static void nhrp_peer_expire_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);
	struct nhrp_peer_selector sel;
	int used;

	peer->flags |= NHRP_PEER_FLAG_PRUNE_PENDING;
	nhrp_peer_schedule(peer, peer->expire_time - ev_now(),
			   nhrp_peer_remove_cb);

	if (peer->type == NHRP_PEER_TYPE_SHORTCUT_ROUTE) {
		memset(&sel, 0, sizeof(sel));
		sel.interface = peer->interface;
		sel.protocol_address = peer->next_hop_address;
		used = nhrp_peer_foreach(is_used, NULL, &sel);
	} else
		used = peer->flags & NHRP_PEER_FLAG_USED;

	if (used)
		nhrp_peer_renew(peer);
}

static void nhrp_peer_is_down(struct nhrp_peer *peer)
{
	struct nhrp_peer_selector sel;

	/* Remove UP flags if not being removed permanently, so futher
	 * lookups are valid */
	if (!(peer->flags & NHRP_PEER_FLAG_REMOVED))
		peer->flags &= ~(NHRP_PEER_FLAG_LOWER_UP | NHRP_PEER_FLAG_UP);

	/* Check if there are routes using this peer as next-hop */
	if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) {
		memset(&sel, 0, sizeof(sel));
		sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE);
		sel.interface = peer->interface;
		sel.next_hop_address = peer->protocol_address;
		nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);
	}

	/* Remove from lists */
	if (list_hashed(&peer->mcast_list_entry))
		list_del(&peer->mcast_list_entry);
	if (hlist_hashed(&peer->nbma_hash_entry))
		hlist_del(&peer->nbma_hash_entry);
}

static void nhrp_peer_is_up(struct nhrp_peer *peer)
{
	struct nhrp_interface *iface = peer->interface;
	struct nhrp_peer_selector sel;
	int mcast = 0, i;
	char tmp[64];

	if ((peer->flags & (NHRP_PEER_FLAG_UP | NHRP_PEER_FLAG_REGISTER))
	    == NHRP_PEER_FLAG_REGISTER) {
		/* First time registration reply received */
		nhrp_peer_run_script(peer, "nhs-up", NULL);
	}

	/* Remove from mcast list if previously there */
	if (list_hashed(&peer->mcast_list_entry))
		list_del(&peer->mcast_list_entry);

	/* Check if this one needs multicast traffic */
	if (BIT(peer->type) & iface->mcast_mask) {
		mcast = 1;
	} else {
		for (i = 0; i < iface->mcast_numaddr; i++) {
			if (!nhrp_address_cmp(&peer->protocol_address,
					      &iface->mcast_addr[i])) {
				mcast = 1;
				break;
			}
		}
	}

	if (mcast) {
		list_add(&peer->mcast_list_entry, &iface->mcast_list);
		nhrp_debug("[%s] Peer inserted to multicast list",
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp), tmp));
	}

	/* Searchable by NBMA */
	if (hlist_hashed(&peer->nbma_hash_entry))
		hlist_del(&peer->nbma_hash_entry);
	if (BIT(peer->type) & (BIT(NHRP_PEER_TYPE_CACHED) |
			       BIT(NHRP_PEER_TYPE_DYNAMIC) |
			       BIT(NHRP_PEER_TYPE_DYNAMIC_NHS) |
			       BIT(NHRP_PEER_TYPE_STATIC))) {
		i = nhrp_address_hash(&peer->next_hop_address) % NHRP_INTERFACE_NBMA_HASH_SIZE;
		hlist_add_head(&peer->nbma_hash_entry, &iface->nbma_hash[i]);
	}

	peer->flags |= NHRP_PEER_FLAG_UP | NHRP_PEER_FLAG_LOWER_UP;

	/* Check if there are routes using this peer as next-hop*/
	if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) {
		memset(&sel, 0, sizeof(sel));
		sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE);
		sel.interface = iface;
		sel.next_hop_address = peer->protocol_address;
		nhrp_peer_foreach(nhrp_peer_routes_up, NULL, &sel);
	}

	nhrp_peer_send_packet_queue(peer);

	/* Schedule expiry or renewal */
	switch (peer->type) {
	case NHRP_PEER_TYPE_DYNAMIC:
		nhrp_peer_schedule(peer, peer->expire_time - ev_now(),
				   nhrp_peer_remove_cb);
		break;
	case NHRP_PEER_TYPE_CACHED:
		nhrp_peer_schedule(
			peer,
			holding_time_to_expiry_time(peer->expire_time - ev_now(), 0),
			nhrp_peer_expire_cb);
		break;
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		if (peer->flags & NHRP_PEER_FLAG_REGISTER) {
			nhrp_peer_schedule(
				peer,
				holding_time_to_reregister_time(iface->holding_time),
				nhrp_peer_send_register_cb);
		}
		break;
	default:
		NHRP_BUG_ON("invalid peer type");
		break;
	}
}

static void nhrp_peer_lower_is_up(struct nhrp_peer *peer)
{
	peer->flags |= NHRP_PEER_FLAG_LOWER_UP;

	if (peer->flags & NHRP_PEER_FLAG_REGISTER)
		nhrp_peer_send_register_cb(&peer->timer, 0);
	else
		nhrp_peer_is_up(peer);
}

static void nhrp_peer_script_peer_up_done(union nhrp_peer_event e, int revents)
{
	struct nhrp_peer *peer = nhrp_peer_from_event(e, revents);
	char tmp[64], reason[32];

	if (nhrp_peer_event_ok(e, revents)) {
		nhrp_debug("[%s] Peer up script: success",
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp), tmp));

		kernel_inject_neighbor(&peer->protocol_address,
				       &peer->next_hop_address,
				       peer->interface);
		nhrp_peer_lower_is_up(peer);
	} else {
		nhrp_error("[%s] Peer up script failed: %s",
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp), tmp),
			   nhrp_peer_event_reason(e, revents,
						  sizeof(reason), reason));
		nhrp_peer_restart_error(peer);
	}
}

static void nhrp_peer_run_up_script(struct nhrp_peer *peer)
{
	nhrp_peer_run_script(peer, "peer-up",
			     nhrp_peer_script_peer_up_done);
}

static void nhrp_peer_address_query_cb(struct nhrp_address_query *query,
				       int num_addr, struct nhrp_address *addrs)
{
	struct nhrp_peer *peer = container_of(query, struct nhrp_peer,
					      address_query);
	char host[64];

	if (num_addr > 0) {
		nhrp_debug("Resolved '%s' as %s",
			  peer->nbma_hostname,
			  nhrp_address_format(&addrs[0], sizeof(host), host));
		peer->next_hop_address = addrs[0];
		peer->afnum = nhrp_afnum_from_pf(peer->next_hop_address.type);
		nhrp_peer_run_up_script(peer);
	} else {
		nhrp_error("Failed to resolve '%s'", peer->nbma_hostname);
		nhrp_peer_restart_error(peer);
	}
}

static void nhrp_peer_restart_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);

	if (peer->nbma_hostname != NULL) {
		nhrp_address_resolve(&peer->address_query,
				     peer->nbma_hostname,
				     nhrp_peer_address_query_cb);
	} else {
		nhrp_peer_resolve_nbma(peer);

		if (!(peer->flags & NHRP_PEER_FLAG_LOWER_UP))
			nhrp_peer_run_up_script(peer);
		else
			nhrp_peer_script_peer_up_done(&peer->child, 0);
	}
}

static void nhrp_peer_send_protocol_purge(struct nhrp_peer *peer)
{
	char tmp[64];
	struct nhrp_packet *packet;
	struct nhrp_cie *cie;
	struct nhrp_payload *payload;
	int sent = FALSE;

	packet = nhrp_packet_alloc();
	if (packet == NULL)
		goto error;

	packet->hdr = (struct nhrp_packet_header) {
		.afnum = peer->afnum,
		.protocol_type = peer->protocol_type,
		.version = NHRP_VERSION_RFC2332,
		.type = NHRP_PACKET_PURGE_REQUEST,
		.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT,
		.flags = NHRP_FLAG_PURGE_NO_REPLY,
	};
	if (peer->flags & NHRP_PEER_FLAG_CISCO) {
		/* Cisco IOS seems to require reqistration and purge
		 * request id to match, so we need to used a fixed
		 * value. This is in violation of RFC, though. */
		packet->hdr.u.request_id =
			nhrp_address_hash(&peer->interface->protocol_address);
	}
	packet->dst_protocol_address = peer->protocol_address;

	/* Payload CIE */
	cie = nhrp_cie_alloc();
	if (cie == NULL)
		goto error_free_packet;

	*cie = (struct nhrp_cie) {
		.hdr.code = NHRP_CODE_SUCCESS,
		.hdr.mtu = 0,
		.hdr.preference = 0,
		.hdr.prefix_length = 0xff,
	};
	cie->protocol_address = peer->interface->protocol_address;

	payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_payload_add_cie(payload, cie);

	nhrp_debug("Sending Purge Request (of protocol address) to %s",
		  nhrp_address_format(&peer->protocol_address,
				      sizeof(tmp), tmp));

	packet->dst_peer = nhrp_peer_get(peer);
	packet->dst_iface = peer->interface;
	sent = nhrp_packet_send(packet);
error_free_packet:
	nhrp_packet_put(packet);
error:
	if (sent)
		nhrp_peer_schedule(peer, 2, nhrp_peer_send_register_cb);
	else
		nhrp_peer_restart_error(peer);
}

static int nhrp_add_local_route_cie(void *ctx, struct nhrp_peer *route)
{
	struct nhrp_packet *packet = (struct nhrp_packet *) ctx;
	struct nhrp_payload *payload;
	struct nhrp_cie *cie;

	if (route->interface != NULL &&
	    !(route->interface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST))
		return 0;

	cie = nhrp_cie_alloc();
	if (cie == NULL)
		return 0;

	*cie = (struct nhrp_cie) {
		.hdr.code = 0,
		.hdr.prefix_length = route->prefix_length,
		.protocol_address = route->protocol_address,
	};

	payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_payload_add_cie(payload, cie);

	return 0;
}

int nhrp_peer_discover_nhs(struct nhrp_peer *peer,
			   struct nhrp_address *newaddr)
{
	struct nhrp_peer_selector sel;
	char tmp[32], tmp2[32];

	if (nhrp_address_cmp(&peer->protocol_address, newaddr) == 0)
		return TRUE;

	if (peer->type != NHRP_PEER_TYPE_DYNAMIC_NHS ||
	    !nhrp_address_is_network(&peer->protocol_address,
				     peer->prefix_length)) {
		nhrp_error("Unexpected NHS protocol address change %s -> %s",
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp2), tmp2),
			   nhrp_address_format(newaddr, sizeof(tmp), tmp));
		return FALSE;
	}

	if (nhrp_address_prefix_cmp(&peer->protocol_address, newaddr,
				    peer->prefix_length) != 0) {
		nhrp_error("Protocol address change to %s is not within %s/%d",
			   nhrp_address_format(newaddr, sizeof(tmp), tmp),
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp2), tmp2),
			   peer->prefix_length);
		return FALSE;
	}

	/* Remove incomplete/cached entries */
	memset(&sel, 0, sizeof(sel));
	sel.flags = NHRP_PEER_FIND_EXACT;
	sel.type_mask = NHRP_PEER_TYPEMASK_REMOVABLE;
	sel.interface = peer->interface;
	sel.protocol_address = *newaddr;
	nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);

	/* Update protocol address */
	peer->protocol_address = *newaddr;

	return TRUE;
}

static void nhrp_peer_handle_registration_reply(void *ctx,
						struct nhrp_packet *reply)
{
	struct nhrp_peer *peer = (struct nhrp_peer *) ctx;
	struct nhrp_payload *payload;
	struct nhrp_cie *cie;
	struct nhrp_packet *packet;
	char tmp[NHRP_PEER_FORMAT_LEN];
	int ec = -1;

	if (peer->flags & NHRP_PEER_FLAG_REMOVED)
		goto ret;

	if (reply == NULL ||
	    reply->hdr.type != NHRP_PACKET_REGISTRATION_REPLY) {
		ec = reply ? reply->hdr.u.error.code : -1;
		nhrp_debug("Failed to register to %s: %s (%d)",
			  nhrp_address_format(&peer->protocol_address,
					      sizeof(tmp), tmp),
			  nhrp_error_indication_text(ec), ntohs(ec));

		if (ec == NHRP_ERROR_HOP_COUNT_EXCEEDED)
			nhrp_peer_discover_nhs(peer,
					       &reply->src_protocol_address);

		if (reply != NULL) {
			nhrp_peer_schedule(peer, NHRP_RETRY_REGISTER_TIME,
					   nhrp_peer_send_register_cb);
		} else {
			nhrp_peer_restart_error(peer);
		}
		goto ret;
	}

	/* Check servers protocol address */
	if (!nhrp_peer_discover_nhs(peer, &reply->dst_protocol_address)) {
		nhrp_peer_restart_error(peer);
		goto ret;
	}

	/* Check result */
	payload = nhrp_packet_payload(reply, NHRP_PAYLOAD_TYPE_CIE_LIST);
	if (payload != NULL) {
		cie = nhrp_payload_get_cie(payload, 1);
		if (cie != NULL)
			ec = cie->hdr.code;
	}

	nhrp_debug("Received Registration Reply from %s: %s",
		  nhrp_address_format(&peer->protocol_address,
				      sizeof(tmp), tmp),
		  nhrp_cie_code_text(ec));

	switch (ec) {
	case NHRP_CODE_SUCCESS:
		break;
	case NHRP_CODE_UNIQUE_ADDRESS_REGISTERED:
		nhrp_peer_send_protocol_purge(peer);
		goto ret;
	default:
		nhrp_peer_schedule(peer, NHRP_RETRY_REGISTER_TIME,
				   nhrp_peer_send_register_cb);
		goto ret;
	}

	/* Check for NAT */
	payload = nhrp_packet_extension(reply,
					NHRP_EXTENSION_NAT_ADDRESS |
					NHRP_EXTENSION_FLAG_NOCREATE,
					NHRP_PAYLOAD_TYPE_CIE_LIST);
	if (payload != NULL) {
		cie = nhrp_payload_get_cie(payload, 2);
		if (cie != NULL) {
			nhrp_debug("NAT detected: our real NBMA address is %s",
				  nhrp_address_format(&cie->nbma_address,
						      sizeof(tmp), tmp));
			peer->interface->nat_cie = *cie;
		}
	}

	/* If not re-registration, send a purge request for each subnet
	 * we accept shortcuts to, to clear server redirection cache. */
	if (!(peer->flags & NHRP_PEER_FLAG_UP) &&
	    (packet = nhrp_packet_alloc()) != NULL) {
		struct nhrp_peer_selector sel;

		packet->hdr = (struct nhrp_packet_header) {
			.afnum = peer->afnum,
			.protocol_type = peer->protocol_type,
			.version = NHRP_VERSION_RFC2332,
			.type = NHRP_PACKET_PURGE_REQUEST,
			.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT,
		};
		packet->dst_protocol_address = peer->protocol_address;

		memset(&sel, 0, sizeof(sel));
		sel.type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ADDR);
		nhrp_peer_foreach(nhrp_add_local_route_cie, packet, &sel);

		nhrp_packet_extension(packet,
				      NHRP_EXTENSION_FORWARD_TRANSIT_NHS |
				      NHRP_EXTENSION_FLAG_COMPULSORY,
				      NHRP_PAYLOAD_TYPE_CIE_LIST);
		nhrp_packet_extension(packet,
				      NHRP_EXTENSION_REVERSE_TRANSIT_NHS |
				      NHRP_EXTENSION_FLAG_COMPULSORY,
				      NHRP_PAYLOAD_TYPE_CIE_LIST);
		nhrp_packet_extension(packet,
				      NHRP_EXTENSION_RESPONDER_ADDRESS |
				      NHRP_EXTENSION_FLAG_COMPULSORY,
				      NHRP_PAYLOAD_TYPE_CIE_LIST);

		nhrp_debug("Sending Purge Request (of local routes) to %s",
			  nhrp_address_format(&peer->protocol_address,
					      sizeof(tmp), tmp));

		packet->dst_peer = nhrp_peer_get(peer);
		packet->dst_iface = peer->interface;
		nhrp_packet_send_request(packet, NULL, NULL);
		nhrp_packet_put(packet);
	}

	/* Re-register after holding time expires */
	nhrp_peer_is_up(peer);
ret:
	nhrp_peer_put(peer);
}

static void nhrp_peer_send_register_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);
	char dst[64];
	struct nhrp_packet *packet;
	struct nhrp_cie *cie;
	struct nhrp_payload *payload;
	int sent = FALSE;

	packet = nhrp_packet_alloc();
	if (packet == NULL)
		goto error;

	packet->hdr = (struct nhrp_packet_header) {
		.afnum = peer->afnum,
		.protocol_type = peer->protocol_type,
		.version = NHRP_VERSION_RFC2332,
		.type = NHRP_PACKET_REGISTRATION_REQUEST,
		.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT,
		.flags = NHRP_FLAG_REGISTRATION_UNIQUE |
			 NHRP_FLAG_REGISTRATION_NAT
	};
	if (peer->flags & NHRP_PEER_FLAG_CISCO) {
		/* Cisco IOS seems to require reqistration and purge
		 * request id to match, so we need to used a fixed
		 * value. This is in violation of RFC, though. */
		packet->hdr.u.request_id =
			nhrp_address_hash(&peer->interface->protocol_address);
	}
	packet->dst_protocol_address = peer->protocol_address;

	if (peer->type == NHRP_PEER_TYPE_DYNAMIC_NHS &&
	    nhrp_address_is_network(&peer->protocol_address,
				    peer->prefix_length)) {
		/* We are not yet sure of the protocol address of the NHS -
		 * send registration to the broadcast address with one hop
		 * limit. Except the NHS to reply with it's real protocol
		 * address. */
		nhrp_address_set_broadcast(&packet->dst_protocol_address,
					   peer->prefix_length);
		packet->hdr.hop_count = 0;
	}


	/* Payload CIE */
	cie = nhrp_cie_alloc();
	if (cie == NULL)
		goto error;

        *cie = (struct nhrp_cie) {
		.hdr.code = NHRP_CODE_SUCCESS,
		.hdr.prefix_length = 0xff,
		.hdr.mtu = htons(peer->my_nbma_mtu),
		.hdr.holding_time = htons(peer->interface->holding_time),
		.hdr.preference = 0,
	};

	payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_payload_add_cie(payload, cie);

	/* Standard extensions */
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_FORWARD_TRANSIT_NHS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_REVERSE_TRANSIT_NHS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_RESPONDER_ADDRESS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);

	/* Cisco NAT extension CIE */
	cie = nhrp_cie_alloc();
	if (cie == NULL)
		goto error_free_packet;

        *cie = (struct nhrp_cie) {
		.hdr.code = NHRP_CODE_SUCCESS,
		.hdr.prefix_length = peer->protocol_address.addr_len * 8,
		.hdr.preference = 0,
		.nbma_address = peer->next_hop_address,
		.protocol_address = peer->protocol_address,
	};

	payload = nhrp_packet_extension(packet, NHRP_EXTENSION_NAT_ADDRESS,
					NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_payload_add_cie(payload, cie);

	nhrp_debug("Sending Registration Request to %s (my mtu=%d)",
		  nhrp_address_format(&peer->protocol_address,
				      sizeof(dst), dst),
		  peer->my_nbma_mtu);

	packet->dst_peer = nhrp_peer_get(peer);
	packet->dst_iface = peer->interface;
	sent = nhrp_packet_send_request(packet,
					nhrp_peer_handle_registration_reply,
					nhrp_peer_get(peer));

error_free_packet:
	nhrp_packet_put(packet);
error:
	if (!sent)
		nhrp_peer_restart_error(peer);
}

static int error_on_matching(void *ctx, struct nhrp_peer *peer)
{
	return 1;
}

static void nhrp_peer_handle_resolution_reply(void *ctx,
					      struct nhrp_packet *reply)
{
	struct nhrp_peer *peer = (struct nhrp_peer *) ctx, *np;
	struct nhrp_payload *payload;
	struct nhrp_cie *cie, *natcie = NULL, *natoacie = NULL;
	struct nhrp_interface *iface;
	struct nhrp_peer_selector sel;
	char dst[64], tmp[64], nbma[64];
	int ec;

	if (peer->flags & NHRP_PEER_FLAG_REMOVED)
		goto ret;

	if (reply == NULL ||
	    reply->hdr.type != NHRP_PACKET_RESOLUTION_REPLY) {
		ec = reply ? reply->hdr.u.error.code : -1;

		nhrp_debug("Failed to resolve %s: %s (%d)",
			  nhrp_address_format(&peer->protocol_address,
					      sizeof(tmp), tmp),
			  nhrp_error_indication_text(ec), ntohs(ec));

		if (reply != NULL) {
			/* We got reply that this address is not available -
			 * negative cache it. */
			peer->flags |= NHRP_PEER_FLAG_UP;
			nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE);
		} else {
			/* Time out - NHS reachable, or packet lost multiple
			 * times. Keep trying if still needed. */
			nhrp_peer_remove(peer);
		}
		goto ret;
	}

	payload = nhrp_packet_payload(reply, NHRP_PAYLOAD_TYPE_CIE_LIST);
	cie = list_next(&payload->u.cie_list, struct nhrp_cie, cie_list_entry);
	if (cie == NULL)
		goto ret;

	nhrp_debug("Received Resolution Reply %s/%d is at proto %s nbma %s",
		  nhrp_address_format(&peer->protocol_address,
				      sizeof(dst), dst),
		  cie->hdr.prefix_length,
		  nhrp_address_format(&cie->protocol_address,
				      sizeof(tmp), tmp),
		  nhrp_address_format(&cie->nbma_address,
				      sizeof(nbma), nbma));

	payload = nhrp_packet_extension(reply,
					NHRP_EXTENSION_NAT_ADDRESS |
					NHRP_EXTENSION_FLAG_NOCREATE,
					NHRP_PAYLOAD_TYPE_CIE_LIST);
	if ((reply->hdr.flags & NHRP_FLAG_RESOLUTION_NAT) &&
	    (payload != NULL)) {
		natcie = list_next(&payload->u.cie_list, struct nhrp_cie, cie_list_entry);
		if (natcie != NULL) {
			natoacie = cie;
			nhrp_debug("NAT detected: really at proto %s nbma %s",
				nhrp_address_format(&natcie->protocol_address,
					sizeof(tmp), tmp),
				nhrp_address_format(&natcie->nbma_address,
					sizeof(nbma), nbma));
		}
	}
	if (natcie == NULL)
		natcie = cie;

	if (nhrp_address_cmp(&peer->protocol_address, &cie->protocol_address)
	    == 0) {
		/* Destination is within NBMA network; update cache */
		peer->mtu = ntohs(cie->hdr.mtu);
		peer->prefix_length = cie->hdr.prefix_length;
		peer->next_hop_address = natcie->nbma_address;
		if (natoacie != NULL)
			peer->next_hop_nat_oa = natoacie->nbma_address;
		peer->expire_time = ev_now() + ntohs(cie->hdr.holding_time);
		nhrp_address_set_network(&peer->protocol_address,
					 peer->prefix_length);
		nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_CACHED);
		goto ret;
	}

	/* Check that we won't replace a local address */
	sel = (struct nhrp_peer_selector) {
		.flags = NHRP_PEER_FIND_EXACT,
		.type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ADDR),
		.protocol_address = peer->protocol_address,
		.prefix_length = cie->hdr.prefix_length,
	};
	if (nhrp_peer_foreach(error_on_matching, NULL, &sel)) {
		nhrp_error("Local route %s/%d exists: not replacing "
			   "with shortcut",
			   nhrp_address_format(&peer->protocol_address,
					       sizeof(tmp), tmp),
			   cie->hdr.prefix_length);
		peer->flags |= NHRP_PEER_FLAG_UP;
		nhrp_peer_reinsert(peer, NHRP_PEER_TYPE_NEGATIVE);
		goto ret;
	}

	/* Update the received NBMA address to nexthop */
	iface = peer->interface;
	np = nhrp_peer_route(iface, &cie->protocol_address,
			     NHRP_PEER_FIND_EXACT, 0);
	if (np == NULL) {
		np = nhrp_peer_alloc(iface);
		np->type = NHRP_PEER_TYPE_CACHED;
		np->afnum = reply->hdr.afnum;
		np->protocol_type = reply->hdr.protocol_type;
		np->protocol_address = cie->protocol_address;
		np->next_hop_address = natcie->nbma_address;
		if (natoacie != NULL)
			np->next_hop_nat_oa = natoacie->nbma_address;
		np->mtu = ntohs(cie->hdr.mtu);
		np->prefix_length = cie->protocol_address.addr_len * 8;
		np->expire_time = ev_now() + ntohs(cie->hdr.holding_time);
		nhrp_peer_insert(np);
		nhrp_peer_put(np);
	}

	/* Off NBMA destination; a shortcut route */
	np = nhrp_peer_alloc(iface);
	np->type = NHRP_PEER_TYPE_SHORTCUT_ROUTE;
	np->afnum = reply->hdr.afnum;
	np->protocol_type = reply->hdr.protocol_type;
	np->protocol_address = peer->protocol_address;
	np->prefix_length = cie->hdr.prefix_length;
	np->next_hop_address = cie->protocol_address;
	np->expire_time = ev_now() + ntohs(cie->hdr.holding_time);
	nhrp_address_set_network(&np->protocol_address, np->prefix_length);
	nhrp_peer_insert(np);
	nhrp_peer_put(np);

	/* Delete the incomplete entry */
	nhrp_peer_remove(peer);
ret:
	nhrp_peer_put(peer);
}

static void nhrp_peer_send_resolve(struct nhrp_peer *peer)
{
	char dst[64];
	struct nhrp_packet *packet;
	struct nhrp_cie *cie;
	struct nhrp_payload *payload;

	packet = nhrp_packet_alloc();
	if (packet == NULL)
		goto error;

	packet->hdr = (struct nhrp_packet_header) {
		.afnum = peer->afnum,
		.protocol_type = peer->protocol_type,
		.version = NHRP_VERSION_RFC2332,
		.type = NHRP_PACKET_RESOLUTION_REQUEST,
		.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT,
		.flags = NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER |
			 NHRP_FLAG_RESOLUTION_AUTHORATIVE |
			 NHRP_FLAG_RESOLUTION_NAT
	};
	packet->dst_protocol_address = peer->protocol_address;

	/* Payload CIE */
	cie = nhrp_cie_alloc();
	if (cie == NULL)
		goto error;

        *cie = (struct nhrp_cie) {
		.hdr.code = NHRP_CODE_SUCCESS,
		.hdr.prefix_length = 0,
		.hdr.mtu = 0,
		.hdr.holding_time = htons(peer->interface->holding_time),
	};

	payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_payload_add_cie(payload, cie);

	nhrp_debug("Sending Resolution Request to %s",
		  nhrp_address_format(&peer->protocol_address,
				      sizeof(dst), dst));

	/* Standard extensions */
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_FORWARD_TRANSIT_NHS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_REVERSE_TRANSIT_NHS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_RESPONDER_ADDRESS |
			      NHRP_EXTENSION_FLAG_COMPULSORY,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);
	nhrp_packet_extension(packet,
			      NHRP_EXTENSION_NAT_ADDRESS,
			      NHRP_PAYLOAD_TYPE_CIE_LIST);

	packet->dst_iface = peer->interface;
	nhrp_packet_send_request(packet,
				 nhrp_peer_handle_resolution_reply,
				 nhrp_peer_get(peer));

error:
	nhrp_packet_put(packet);
}

struct nhrp_peer *nhrp_peer_alloc(struct nhrp_interface *iface)
{
	struct nhrp_peer *p;

	nhrp_peer_num_total++;
	p = calloc(1, sizeof(struct nhrp_peer));
	p->ref = 1;
	p->interface = iface;
	list_init(&p->peer_list_entry);
	list_init(&p->mcast_list_entry);
	ev_timer_init(&p->timer, NULL, 0., 0.);
	ev_child_init(&p->child, NULL, 0, 0);

	return p;
}

struct nhrp_peer *nhrp_peer_get(struct nhrp_peer *peer)
{
	if (peer == NULL)
		return NULL;

	peer->ref++;
	nhrp_peer_debug_refcount(__FUNCTION__, peer);

	return peer;
}

static void nhrp_peer_run_nhs_down(struct nhrp_peer *peer)
{
	if ((peer->flags & (NHRP_PEER_FLAG_REGISTER |
			    NHRP_PEER_FLAG_UP |
			    NHRP_PEER_FLAG_REPLACED))
	    == (NHRP_PEER_FLAG_REGISTER | NHRP_PEER_FLAG_UP))
		nhrp_peer_run_script(peer, "nhs-down", NULL);
}

static void nhrp_peer_release(struct nhrp_peer *peer)
{
	struct nhrp_interface *iface = peer->interface;
	struct nhrp_peer_selector sel;

	nhrp_peer_cancel_async(peer);

	/* Remove from lists */
	if (list_hashed(&peer->mcast_list_entry))
		list_del(&peer->mcast_list_entry);
	if (hlist_hashed(&peer->nbma_hash_entry))
		hlist_del(&peer->nbma_hash_entry);

	if (peer->parent != NULL) {
		nhrp_peer_put(peer->parent);
		peer->parent = NULL;
	}

	switch (peer->type) {
	case NHRP_PEER_TYPE_SHORTCUT_ROUTE:
		if ((peer->flags & NHRP_PEER_FLAG_UP) &&
		    !(peer->flags & NHRP_PEER_FLAG_REPLACED))
			nhrp_peer_run_script(peer, "route-down", NULL);
		break;
	case NHRP_PEER_TYPE_CACHED:
	case NHRP_PEER_TYPE_DYNAMIC:
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		if (peer->flags & NHRP_PEER_FLAG_REPLACED)
			break;

		/* Remove cached routes using this entry as next-hop */
		memset(&sel, 0, sizeof(sel));
		sel.type_mask = BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE);
		sel.interface = iface;
		sel.next_hop_address = peer->protocol_address;
		nhrp_peer_foreach(nhrp_peer_remove_matching, NULL,
				  &sel);

		/* Execute peer-down */
		nhrp_peer_run_nhs_down(peer);
		if (peer->flags & NHRP_PEER_FLAG_UP) {
			peer->purge_reason = "timeout";
			nhrp_peer_run_script(peer, "peer-down", NULL);
		}

		/* Remove from arp cache */
		if (peer->protocol_address.type != PF_UNSPEC)
			kernel_inject_neighbor(&peer->protocol_address,
					       NULL, peer->interface);
		break;
	case NHRP_PEER_TYPE_INCOMPLETE:
	case NHRP_PEER_TYPE_NEGATIVE:
	case NHRP_PEER_TYPE_LOCAL_ADDR:
	case NHRP_PEER_TYPE_LOCAL_ROUTE:
	case NHRP_PEER_TYPE_STATIC_DNS:
		break;
	default:
		NHRP_BUG_ON("invalid peer type");
		break;
	}

	if (peer->nbma_hostname) {
		free(peer->nbma_hostname);
		peer->nbma_hostname = NULL;
	}

	free(peer);
	nhrp_peer_num_total--;
}

int nhrp_peer_put(struct nhrp_peer *peer)
{
	NHRP_BUG_ON(peer->ref == 0);

	peer->ref--;
	nhrp_peer_debug_refcount(__FUNCTION__, peer);

	if (peer->ref > 0)
		return FALSE;

	nhrp_peer_release(peer);

	return TRUE;
}

static int nhrp_peer_mark_matching(void *ctx, struct nhrp_peer *peer)
{
	peer->flags |= NHRP_PEER_FLAG_MARK;
	return 0;
}

static int nhrp_peer_renew_nhs_matching(void *ctx, struct nhrp_peer *peer)
{
	peer->flags &= ~NHRP_PEER_FLAG_MARK;
	return 1;
}

static void nhrp_peer_dnsmap_query_cb(struct nhrp_address_query *query,
				      int num_addr, struct nhrp_address *addrs)
{
	struct nhrp_peer *np, *peer =
		container_of(query, struct nhrp_peer, address_query);
	struct nhrp_peer_selector sel;
	int i;

	if (num_addr < 0) {
		nhrp_error("Failed to resolve '%s'", peer->nbma_hostname);
		nhrp_peer_schedule(peer, 10, nhrp_peer_dnsmap_restart_cb);
		return;
	}

	if (num_addr > 0) {
		/* Refresh protocol */
		peer->afnum = nhrp_afnum_from_pf(addrs[0].type);
	}

	/* Mark existing dynamic nhs entries as expired */
	memset(&sel, 0, sizeof(sel));
	sel.type_mask = BIT(NHRP_PEER_TYPE_DYNAMIC_NHS);
	sel.interface = peer->interface;
	sel.parent = peer;
	nhrp_peer_foreach(nhrp_peer_mark_matching, NULL, &sel);

	for (i = 0; i < num_addr; i++) {
		/* If this NBMA exists as dynamic NHS, mark it ok. */
		sel.next_hop_address = addrs[i];
		if (nhrp_peer_foreach(nhrp_peer_renew_nhs_matching,
				      NULL, &sel) != 0)
			continue;

		/* New NHS, create a peer entry */
		np = nhrp_peer_alloc(peer->interface);
		np->type = NHRP_PEER_TYPE_DYNAMIC_NHS;
		np->flags |= NHRP_PEER_FLAG_REGISTER;
		np->afnum = peer->afnum;
		np->protocol_type = peer->protocol_type;
		np->protocol_address = peer->protocol_address;
		np->prefix_length = peer->prefix_length;
		np->next_hop_address = addrs[i];
		np->parent = nhrp_peer_get(peer);
		nhrp_address_set_network(&np->protocol_address,
					 np->prefix_length);
		nhrp_peer_insert(np);
		nhrp_peer_put(np);
	}

	/* Delete all dynamic nhs:s that were not in the DNS reply */
	nhrp_address_set_type(&sel.next_hop_address, AF_UNSPEC);
	sel.flags = NHRP_PEER_FIND_MARK;
	nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);

	/* Refresh DNS info */
	nhrp_peer_schedule(peer, peer->interface->holding_time,
			   nhrp_peer_dnsmap_restart_cb);
}

static void nhrp_peer_dnsmap_restart_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);

	NHRP_BUG_ON(peer->nbma_hostname == NULL);
	nhrp_address_resolve(&peer->address_query, peer->nbma_hostname,
			     nhrp_peer_dnsmap_query_cb);
}

static void nhrp_peer_insert_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);

	nhrp_peer_cancel_async(peer);
	switch (peer->type) {
	case NHRP_PEER_TYPE_LOCAL_ADDR:
		peer->flags |= NHRP_PEER_FLAG_UP;
		forward_local_addresses_changed();
		break;
	case NHRP_PEER_TYPE_LOCAL_ROUTE:
		peer->flags |= NHRP_PEER_FLAG_UP;
		break;
	case NHRP_PEER_TYPE_INCOMPLETE:
		nhrp_peer_send_resolve(peer);
		break;
	case NHRP_PEER_TYPE_CACHED:
	case NHRP_PEER_TYPE_DYNAMIC:
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		nhrp_peer_restart_cb(w, 0);
		break;
	case NHRP_PEER_TYPE_STATIC_DNS:
		nhrp_peer_dnsmap_restart_cb(w, 0);
		break;
	case NHRP_PEER_TYPE_SHORTCUT_ROUTE:
		if (peer->flags & NHRP_PEER_FLAG_UP)
			nhrp_peer_script_route_up_done(&peer->child, 0);
		else if (nhrp_peer_route(peer->interface,
					 &peer->next_hop_address,
					 NHRP_PEER_FIND_UP | NHRP_PEER_FIND_EXACT,
					 NHRP_PEER_TYPEMASK_ADJACENT) != NULL)
			nhrp_peer_run_script(peer, "route-up",
					     nhrp_peer_script_route_up_done);
		else
			nhrp_peer_schedule(
				peer,
				holding_time_to_expiry_time(peer->expire_time - ev_now(), 10),
				nhrp_peer_expire_cb);
		break;
	case NHRP_PEER_TYPE_NEGATIVE:
		peer->expire_time = ev_now() + NHRP_NEGATIVE_CACHE_TIME;

		if (peer->flags & NHRP_PEER_FLAG_UP)
			kernel_inject_neighbor(&peer->protocol_address,
					       NULL, peer->interface);
		nhrp_peer_schedule(peer, NHRP_NEGATIVE_CACHE_TIME,
				   nhrp_peer_remove_cb);
		break;
	default:
		NHRP_BUG_ON("invalid peer type");
		break;
	}
}

static void nhrp_peer_reinsert(struct nhrp_peer *peer, int type)
{
	NHRP_BUG_ON((peer->type == NHRP_PEER_TYPE_LOCAL_ADDR) !=
		    (type == NHRP_PEER_TYPE_LOCAL_ADDR));
	NHRP_BUG_ON((peer->type == NHRP_PEER_TYPE_LOCAL_ROUTE) !=
		    (type == NHRP_PEER_TYPE_LOCAL_ROUTE));

	peer->flags &= ~NHRP_PEER_FLAG_REMOVED;
	peer->type = type;
	nhrp_peer_insert_cb(&peer->timer, 0);
}

static int nhrp_peer_replace_shortcut(void *ctx, struct nhrp_peer *peer)
{
	struct nhrp_peer *shortcut = (struct nhrp_peer *) ctx;

	/* Shortcut of identical prefix is replacement, either
	 * due to renewal, or new shortcut next-hop. */
	if (nhrp_address_cmp(&peer->protocol_address,
			     &shortcut->protocol_address) == 0 &&
	    peer->prefix_length == shortcut->prefix_length) {
		peer->flags |= NHRP_PEER_FLAG_REPLACED;

		/* If identical shortcut is being refreshed,
		 * mark the refresher peer entry up. */
		if ((peer->flags & NHRP_PEER_FLAG_UP) &&
		    nhrp_address_cmp(&peer->next_hop_address,
				     &shortcut->next_hop_address) == 0)
			shortcut->flags |= NHRP_PEER_FLAG_UP;
	}

	/* Delete the old peer unconditionally */
	nhrp_peer_remove(peer);

	return 0;
}

void nhrp_peer_insert(struct nhrp_peer *peer)
{
	struct nhrp_peer_selector sel;
	char tmp[NHRP_PEER_FORMAT_LEN];

	/* First, prune all duplicates */
	memset(&sel, 0, sizeof(sel));
	sel.interface = peer->interface;
	sel.protocol_address = peer->protocol_address;
	sel.prefix_length = peer->prefix_length;
	switch (peer->type) {
	case NHRP_PEER_TYPE_SHORTCUT_ROUTE:
		/* remove all existing shortcuts with same nexthop */
		sel.flags = NHRP_PEER_FIND_SUBNET;
		sel.type_mask |= BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE);
		nhrp_peer_foreach(nhrp_peer_replace_shortcut, peer, &sel);
		break;
	case NHRP_PEER_TYPE_LOCAL_ROUTE:
		sel.type_mask |= BIT(NHRP_PEER_TYPE_LOCAL_ROUTE);
	default:
		/* remove exact protocol address matches */
		sel.flags = NHRP_PEER_FIND_EXACT;
		sel.type_mask |= NHRP_PEER_TYPEMASK_REMOVABLE;
		nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel);
		break;
	}

	/* Keep a reference as long as we are on the list */
	peer = nhrp_peer_get(peer);
	nhrp_debug("Adding %s %s",
		   nhrp_peer_type[peer->type],
		   nhrp_peer_format(peer, sizeof(tmp), tmp));

	if (peer->type == NHRP_PEER_TYPE_LOCAL_ADDR)
		list_add(&peer->peer_list_entry, &local_peer_list);
	else
		list_add(&peer->peer_list_entry, &peer->interface->peer_list);

	/* Start peers life */
	if (nhrp_running || peer->type == NHRP_PEER_TYPE_LOCAL_ADDR)
		nhrp_peer_insert_cb(&peer->timer, 0);
	else
		nhrp_peer_schedule(peer, 0, &nhrp_peer_insert_cb);
}

static void nhrp_peer_script_peer_down_done(union nhrp_peer_event e,
					    int revents)
{
	struct nhrp_peer *peer = nhrp_peer_from_event(e, revents);

	nhrp_peer_schedule(peer, 5, nhrp_peer_restart_cb);
}

void nhrp_peer_purge(struct nhrp_peer *peer, const char *purge_reason)
{
	switch (peer->type) {
	case NHRP_PEER_TYPE_STATIC:
	case NHRP_PEER_TYPE_DYNAMIC_NHS:
		peer->purge_reason = purge_reason;
		nhrp_peer_run_nhs_down(peer);
		nhrp_peer_is_down(peer);
		nhrp_peer_cancel_async(peer);
		if (peer->flags & NHRP_PEER_FLAG_LOWER_UP) {
			nhrp_peer_run_script(peer, "peer-down",
					     nhrp_peer_script_peer_down_done);
		} else {
			nhrp_peer_script_peer_down_done(&peer->child, 0);
		}
		nhrp_address_set_type(&peer->my_nbma_address, PF_UNSPEC);
		break;
	case NHRP_PEER_TYPE_STATIC_DNS:
		nhrp_peer_schedule(peer, 0, nhrp_peer_dnsmap_restart_cb);
		break;
	default:
		peer->purge_reason = purge_reason;
		nhrp_peer_remove(peer);
		break;
	}
}

int nhrp_peer_purge_matching(void *ctx, struct nhrp_peer *peer)
{
	int *count = (int *) ctx;
	nhrp_peer_purge(peer, "user-request");
	if (count != NULL)
		(*count)++;
	return 0;
}

int nhrp_peer_lowerdown_matching(void *ctx, struct nhrp_peer *peer)
{
	int *count = (int *) ctx;
	nhrp_peer_purge(peer, "lower-down");
	if (count != NULL)
		(*count)++;
	return 0;
}

static void nhrp_peer_remove_cb(struct ev_timer *w, int revents)
{
	struct nhrp_peer *peer = container_of(w, struct nhrp_peer, timer);
	int type;

	peer->flags |= NHRP_PEER_FLAG_REMOVED;
	peer->purge_reason = "expired";
	nhrp_peer_is_down(peer);
	list_del(&peer->peer_list_entry);

	type = peer->type;
	nhrp_peer_put(peer);

	if (type == NHRP_PEER_TYPE_LOCAL_ADDR)
		forward_local_addresses_changed();
}

void nhrp_peer_remove(struct nhrp_peer *peer)
{
	char tmp[NHRP_PEER_FORMAT_LEN];

	if (peer->flags & NHRP_PEER_FLAG_REMOVED)
		return;

	nhrp_debug("Removing %s %s",
		   nhrp_peer_type[peer->type],
		   nhrp_peer_format(peer, sizeof(tmp), tmp));

	peer->flags |= NHRP_PEER_FLAG_REMOVED;
	nhrp_peer_is_down(peer);
	nhrp_peer_cancel_async(peer);
	nhrp_peer_schedule(peer, 0, nhrp_peer_remove_cb);
}

int nhrp_peer_remove_matching(void *ctx, struct nhrp_peer *peer)
{
	int *count = (int *) ctx;

	nhrp_peer_remove(peer);
	if (count != NULL)
		(*count)++;

	return 0;
}

int nhrp_peer_set_used_matching(void *ctx, struct nhrp_peer *peer)
{
	int used = (int) (intptr_t) ctx;

	if (used) {
		peer->flags |= NHRP_PEER_FLAG_USED;
		nhrp_peer_renew(peer);
	} else {
		peer->flags &= ~NHRP_PEER_FLAG_USED;
	}
	return 0;
}

int nhrp_peer_match(struct nhrp_peer *p, struct nhrp_peer_selector *sel)
{
	if (sel->type_mask && !(sel->type_mask & BIT(p->type)))
		return FALSE;

	if ((sel->flags & NHRP_PEER_FIND_UP) &&
	    !(p->flags & NHRP_PEER_FLAG_UP))
		return FALSE;

	if ((sel->flags & NHRP_PEER_FIND_MARK) &&
	   !(p->flags & NHRP_PEER_FLAG_MARK))
		return FALSE;

	if (sel->interface != NULL &&
	    p->interface != sel->interface &&
	    !(p->interface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST))
		return FALSE;

	if (sel->hostname != NULL &&
	    (p->nbma_hostname == NULL ||
	     strcmp(sel->hostname, p->nbma_hostname) != 0))
		return FALSE;

	if (sel->parent != NULL &&
	    p->parent != sel->parent)
		return FALSE;

	if (sel->local_nbma_address.type != PF_UNSPEC) {
		if (nhrp_address_cmp(&p->my_nbma_address,
				     &sel->local_nbma_address) != 0)
			return FALSE;
	}

	if (sel->protocol_address.type != PF_UNSPEC) {
		if (sel->prefix_length == 0)
			sel->prefix_length = sel->protocol_address.addr_len * 8;

		if (sel->flags & NHRP_PEER_FIND_EXACT) {
			if (nhrp_address_cmp(&p->protocol_address,
					     &sel->protocol_address) != 0)
				return FALSE;

			if (p->prefix_length != sel->prefix_length &&
			    p->type != NHRP_PEER_TYPE_STATIC &&
			    p->type != NHRP_PEER_TYPE_DYNAMIC_NHS)
				return FALSE;
		} else if (sel->flags & NHRP_PEER_FIND_ROUTE) {
			if (nhrp_address_prefix_cmp(&p->protocol_address,
						    &sel->protocol_address,
						    p->prefix_length) != 0)
				return FALSE;
		} else {
			if (p->prefix_length < sel->prefix_length) {
				if (sel->prefix_length
				    == sel->protocol_address.addr_len * 8 &&
				    nhrp_address_cmp(&p->protocol_address,
						     &sel->protocol_address)
				    == 0)
					return TRUE;

				return FALSE;
			}

			if (nhrp_address_prefix_cmp(&p->protocol_address,
						    &sel->protocol_address,
						    sel->prefix_length) != 0)
				return FALSE;
		}
	}

	if (sel->next_hop_address.type != PF_UNSPEC) {
		if (nhrp_address_cmp(&p->next_hop_address,
				     &sel->next_hop_address) != 0)
			return FALSE;
	}

	return TRUE;
}

struct enum_interface_peers_ctx {
	nhrp_peer_enumerator enumerator;
	void *ctx;
	struct nhrp_peer_selector *sel;
};

static int enumerate_peer_cache(struct list_head *peer_cache,
				nhrp_peer_enumerator e, void *ctx,
				struct nhrp_peer_selector *sel)
{
	struct nhrp_peer *p;
	int rc = 0;

	list_for_each_entry(p, peer_cache, peer_list_entry) {
		if (p->flags & NHRP_PEER_FLAG_REMOVED)
			continue;

		if (sel == NULL || nhrp_peer_match(p, sel)) {
			rc = e(ctx, p);
			if (rc != 0)
				break;
		}
	}

	return rc;
}

static int enum_interface_peers(void *ctx, struct nhrp_interface *iface)
{
	struct enum_interface_peers_ctx *ectx =
		(struct enum_interface_peers_ctx *) ctx;

	return enumerate_peer_cache(&iface->peer_list,
				    ectx->enumerator, ectx->ctx,
				    ectx->sel);
}

int nhrp_peer_foreach(nhrp_peer_enumerator e, void *ctx,
		      struct nhrp_peer_selector *sel)
{
	struct nhrp_interface *iface = NULL;
	struct enum_interface_peers_ctx ectx = { e, ctx, sel };
	int rc;

	if (sel != NULL)
		iface = sel->interface;

	rc = enumerate_peer_cache(&local_peer_list, e, ctx, sel);
	if (rc != 0)
		return rc;

	/* Speed optimization: TYPE_LOCAL peers cannot be found from
	 * other places */
	if (sel != NULL &&
	    sel->type_mask == BIT(NHRP_PEER_TYPE_LOCAL_ADDR))
		return 0;

	if (iface == NULL)
		rc = nhrp_interface_foreach(enum_interface_peers, &ectx);
	else
		rc = enumerate_peer_cache(&iface->peer_list, e, ctx, sel);

	return rc;
}

struct route_decision {
	struct nhrp_peer_selector sel;
	struct list_head *exclude;
	struct nhrp_peer *best_found;
	struct nhrp_address *src;
	int found_exact, found_up;
};

static int decide_route(void *ctx, struct nhrp_peer *peer)
{
	struct route_decision *rd = (struct route_decision *) ctx;
	int exact;

	if (peer->type != NHRP_PEER_TYPE_SHORTCUT_ROUTE) {
		/* Exclude addresses from CIE from routing decision
		 * to avoid routing loops within NHS clusters. */
		if (rd->exclude != NULL &&
		    nhrp_address_match_cie_list(&peer->next_hop_address,
						&peer->protocol_address,
						rd->exclude))
			return 0;

		/* Exclude also source address, we don't want to
		 * forward questions back to who's asking. */
		if (rd->src != NULL &&
		    nhrp_address_cmp(rd->src, &peer->protocol_address) == 0)
			return 0;
	} else {
		/* Exclude routes that point back to the sender
		 * of the packet */
		if (rd->src != NULL &&
		    nhrp_address_cmp(rd->src, &peer->next_hop_address) == 0)
			return 0;
	}

	exact = (peer->type >= NHRP_PEER_TYPE_DYNAMIC_NHS) &&
		(nhrp_address_cmp(&peer->protocol_address,
				  &rd->sel.protocol_address) == 0);
	if (rd->found_exact > exact)
		return 0;

	if (rd->found_up && !(peer->flags & NHRP_PEER_FLAG_UP))
		return 0;

	if (rd->best_found != NULL &&
	    rd->found_exact == exact &&
	    rd->found_up == (peer->flags & NHRP_PEER_FLAG_UP)) {
		if (rd->best_found->prefix_length > peer->prefix_length)
			return 0;

		if (rd->best_found->prefix_length == peer->prefix_length &&
		    rd->best_found->last_used < peer->last_used)
			return 0;
	}

	rd->best_found = peer;
	rd->found_exact = exact;
	rd->found_up = peer->flags & NHRP_PEER_FLAG_UP;
	return 0;
}

struct nhrp_peer *nhrp_peer_route_full(struct nhrp_interface *interface,
				       struct nhrp_address *dst,
				       int flags, int type_mask,
				       struct nhrp_address *src,
				       struct list_head *exclude)
{
	struct route_decision rd;

	memset(&rd, 0, sizeof(rd));
	rd.sel.flags = flags & ~NHRP_PEER_FIND_UP;
	if ((flags & (NHRP_PEER_FIND_ROUTE | NHRP_PEER_FIND_EXACT |
		      NHRP_PEER_FIND_SUBNET)) == 0)
		rd.sel.flags |= NHRP_PEER_FIND_ROUTE;
	rd.sel.type_mask = type_mask;
	rd.sel.interface = interface;
	rd.sel.protocol_address = *dst;
	rd.exclude = exclude;
	rd.src = src;
	nhrp_peer_foreach(decide_route, &rd, &rd.sel);

	if (rd.best_found == NULL)
		return NULL;

	if ((flags & NHRP_PEER_FIND_UP) &&
	    !(rd.best_found->flags & NHRP_PEER_FLAG_UP))
		return NULL;

	rd.best_found->last_used = ev_now();
	return rd.best_found;
}

void nhrp_peer_traffic_indication(struct nhrp_interface *iface,
				  uint16_t afnum, struct nhrp_address *dst)
{
	struct nhrp_peer *peer;
	int type;

	/* For off-NBMA destinations, we consider all shortcut routes,
	 * but NBMA destinations should be exact because we want to drop
	 * NHS from the path. */
	if (nhrp_address_prefix_cmp(dst, &iface->protocol_address,
				    iface->protocol_address_prefix) != 0)
		type = NHRP_PEER_FIND_ROUTE;
	else
		type = NHRP_PEER_FIND_EXACT;

	/* Have we done something for this destination already? */
	peer = nhrp_peer_route(iface, dst, type,
			       ~BIT(NHRP_PEER_TYPE_LOCAL_ROUTE));
	if (peer != NULL)
		return;

	/* Initiate resolution */
	peer = nhrp_peer_alloc(iface);
	peer->type = NHRP_PEER_TYPE_INCOMPLETE;
	peer->afnum = afnum;
	peer->protocol_type = nhrp_protocol_from_pf(dst->type);
	peer->protocol_address = *dst;
	peer->prefix_length = dst->addr_len * 8;
	nhrp_peer_insert(peer);
	nhrp_peer_put(peer);
}

static int dump_peer(void *ctx, struct nhrp_peer *peer)
{
	int *num_total = (int *) ctx;
	char tmp[NHRP_PEER_FORMAT_LEN];

	nhrp_debug("%s %s",
		  nhrp_peer_type[peer->type],
		  nhrp_peer_format(peer, sizeof(tmp), tmp));
	(*num_total)++;
	return 0;
}

void nhrp_peer_dump_cache(void)
{
	int num_total = 0;

	nhrp_debug("Peer cache dump:");
	nhrp_peer_foreach(dump_peer, &num_total, NULL);
	nhrp_debug("Total %d peer cache entries, %d allocated entries",
		  num_total, nhrp_peer_num_total);
}

void nhrp_peer_cleanup(void)
{
	ev_tstamp prev = ev_now();

	nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, NULL);

	while (nhrp_peer_num_total > 0) {
		if (ev_now() > prev + 5.0) {
			nhrp_debug("Waiting for peers to die, %d left", nhrp_peer_num_total);
			prev = ev_now();
		}
		ev_loop(EVLOOP_ONESHOT);
	}
}