nhrpd: implement next hop resolution protocol

This provides DMVPN support and integrates to strongSwan. Please read
README.nhrpd and README.kernel for more details.
diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c
new file mode 100644
index 0000000..45bfd7d
--- /dev/null
+++ b/nhrpd/nhrp_peer.c
@@ -0,0 +1,860 @@
+/* NHRP peer functions
+ * Copyright (c) 2014-2015 Timo Teräs
+ *
+ * This file is free software: you may copy, redistribute and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <netinet/if_ether.h>
+
+#include "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+#include "os.h"
+
+struct ipv6hdr {
+	uint8_t priority_version;
+	uint8_t flow_lbl[3];
+	uint16_t payload_len;
+	uint8_t nexthdr;
+	uint8_t hop_limit;
+	struct in6_addr saddr;
+	struct in6_addr daddr;
+};
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir);
+
+static void nhrp_peer_check_delete(struct nhrp_peer *p)
+{
+	struct nhrp_interface *nifp = p->ifp->info;
+
+	if (p->ref || notifier_active(&p->notifier_list))
+		return;
+
+	THREAD_OFF(p->t_fallback);
+	hash_release(nifp->peer_hash, p);
+	nhrp_interface_notify_del(p->ifp, &p->ifp_notifier);
+	nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+	XFREE(MTYPE_NHRP_PEER, p);
+}
+
+static int nhrp_peer_notify_up(struct thread *t)
+{
+	struct nhrp_peer *p = THREAD_ARG(t);
+	struct nhrp_vc *vc = p->vc;
+	struct interface *ifp = p->ifp;
+	struct nhrp_interface *nifp = ifp->info;
+
+	p->t_fallback = NULL;
+	if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) {
+		p->online = 1;
+		nhrp_peer_ref(p);
+		notifier_call(&p->notifier_list, NOTIFY_PEER_UP);
+		nhrp_peer_unref(p);
+	}
+
+	return 0;
+}
+
+static void __nhrp_peer_check(struct nhrp_peer *p)
+{
+	struct nhrp_vc *vc = p->vc;
+	struct interface *ifp = p->ifp;
+	struct nhrp_interface *nifp = ifp->info;
+	unsigned online;
+
+	online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec);
+	if (p->online != online) {
+		THREAD_OFF(p->t_fallback);
+		if (online && notifier_active(&p->notifier_list)) {
+			/* If we requested the IPsec connection, delay
+			 * the up notification a bit to allow things
+			 * settle down. This allows IKE to install
+			 * SPDs and SAs. */
+			THREAD_TIMER_MSEC_ON(
+				master, p->t_fallback,
+				nhrp_peer_notify_up, p, 50);
+		} else {
+			nhrp_peer_ref(p);
+			p->online = online;
+			if (online) {
+				notifier_call(&p->notifier_list, NOTIFY_PEER_UP);
+			} else {
+				p->requested = p->fallback_requested = 0;
+				notifier_call(&p->notifier_list, NOTIFY_PEER_DOWN);
+			}
+			nhrp_peer_unref(p);
+		}
+	}
+}
+
+static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd)
+{
+	struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier);
+
+	switch (cmd) {
+	case NOTIFY_VC_IPSEC_CHANGED:
+		__nhrp_peer_check(p);
+		break;
+	case NOTIFY_VC_IPSEC_UPDATE_NBMA:
+		nhrp_peer_ref(p);
+		notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING);
+		nhrp_peer_unref(p);
+		break;
+	}
+}
+
+static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd)
+{
+	struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier);
+	struct nhrp_interface *nifp;
+	struct nhrp_vc *vc;
+
+	nhrp_peer_ref(p);
+	switch (cmd) {
+	case NOTIFY_INTERFACE_UP:
+	case NOTIFY_INTERFACE_DOWN:
+		__nhrp_peer_check(p);
+		break;
+	case NOTIFY_INTERFACE_NBMA_CHANGED:
+		/* Source NBMA changed, rebind to new VC */
+		nifp = p->ifp->info;
+		vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1);
+		if (vc && p->vc != vc) {
+			nhrp_vc_notify_del(p->vc, &p->vc_notifier);
+			p->vc = vc;
+			nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify);
+			__nhrp_peer_check(p);
+		}
+		/* Fall-through to post config update */
+	case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+		notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED);
+		break;
+	case NOTIFY_INTERFACE_MTU_CHANGED:
+		notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED);
+		break;
+	}
+	nhrp_peer_unref(p);
+}
+
+static unsigned int nhrp_peer_key(void *peer_data)
+{
+	struct nhrp_peer *p = peer_data;
+	return sockunion_hash(&p->vc->remote.nbma);
+}
+
+static int nhrp_peer_cmp(const void *cache_data, const void *key_data)
+{
+	const struct nhrp_peer *a = cache_data;
+	const struct nhrp_peer *b = key_data;
+	return a->ifp == b->ifp && a->vc == b->vc;
+}
+
+static void *nhrp_peer_create(void *data)
+{
+	struct nhrp_peer *p, *key = data;
+
+	p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p));
+	if (p) {
+		*p = (struct nhrp_peer) {
+			.ref = 0,
+			.ifp = key->ifp,
+			.vc = key->vc,
+			.notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
+		};
+		nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify);
+		nhrp_interface_notify_add(p->ifp, &p->ifp_notifier, nhrp_peer_ifp_notify);
+	}
+	return p;
+}
+
+struct nhrp_peer *nhrp_peer_get(struct interface *ifp, const union sockunion *remote_nbma)
+{
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_peer key, *p;
+	struct nhrp_vc *vc;
+
+	if (!nifp->peer_hash) {
+		nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp);
+		if (!nifp->peer_hash) return NULL;
+	}
+
+	vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1);
+	if (!vc) return NULL;
+
+	key.ifp = ifp;
+	key.vc = vc;
+
+	p = hash_get(nifp->peer_hash, &key, nhrp_peer_create);
+	nhrp_peer_ref(p);
+	if (p->ref == 1) __nhrp_peer_check(p);
+
+	return p;
+}
+
+struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p)
+{
+	if (p) p->ref++;
+	return p;
+}
+
+void nhrp_peer_unref(struct nhrp_peer *p)
+{
+	if (p) {
+		p->ref--;
+		nhrp_peer_check_delete(p);
+	}
+}
+
+static int nhrp_peer_request_timeout(struct thread *t)
+{
+	struct nhrp_peer *p = THREAD_ARG(t);
+	struct nhrp_vc *vc = p->vc;
+	struct interface *ifp = p->ifp;
+	struct nhrp_interface *nifp = ifp->info;
+
+	p->t_fallback = NULL;
+
+	if (p->online)
+		return 0;
+
+	if (nifp->ipsec_fallback_profile && !p->prio && !p->fallback_requested) {
+		p->fallback_requested = 1;
+		vici_request_vc(nifp->ipsec_fallback_profile,
+				&vc->local.nbma, &vc->remote.nbma, p->prio);
+		THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p, 30);
+	} else {
+		p->requested = p->fallback_requested = 0;
+	}
+
+	return 0;
+}
+
+int nhrp_peer_check(struct nhrp_peer *p, int establish)
+{
+	struct nhrp_vc *vc = p->vc;
+	struct interface *ifp = p->ifp;
+	struct nhrp_interface *nifp = ifp->info;
+
+	if (p->online)
+		return 1;
+	if (!establish)
+		return 0;
+	if (p->requested)
+		return 0;
+	if (sockunion_family(&vc->local.nbma) == AF_UNSPEC)
+		return 0;
+
+	p->prio = establish > 1;
+	p->requested = 1;
+	vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, &vc->remote.nbma, p->prio);
+	THREAD_TIMER_ON(master, p->t_fallback, nhrp_peer_request_timeout, p,
+			(nifp->ipsec_fallback_profile && !p->prio) ? 15 : 30);
+
+	return 0;
+}
+
+void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n, notifier_fn_t fn)
+{
+	notifier_add(n, &p->notifier_list, fn);
+}
+
+void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n)
+{
+	notifier_del(n);
+	nhrp_peer_check_delete(p);
+}
+
+void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb)
+{
+	char buf[2][256];
+
+	nhrp_packet_debug(zb, "Send");
+
+	if (!p->online)
+		return;
+
+	debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %s -> %s",
+		sockunion2str(&p->vc->local.nbma, buf[0], sizeof buf[0]),
+		sockunion2str(&p->vc->remote.nbma, buf[1], sizeof buf[1]));
+
+	os_sendmsg(zb->head, zbuf_used(zb),
+		p->ifp->ifindex,
+		sockunion_get_addr(&p->vc->remote.nbma),
+		sockunion_get_addrlen(&p->vc->remote.nbma));
+	zbuf_reset(zb);
+}
+
+static void nhrp_handle_resolution_req(struct nhrp_packet_parser *p)
+{
+	struct zbuf *zb, payload;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_cie_header *cie;
+	struct nhrp_extension_header *ext;
+	struct nhrp_interface *nifp;
+	struct nhrp_peer *peer;
+
+	if (!(p->if_ad->flags & NHRP_IFF_SHORTCUT)) {
+		debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled");
+		/* FIXME: Send error indication? */
+		return;
+	}
+
+	if (p->if_ad->network_id &&
+	    p->route_type == NHRP_ROUTE_OFF_NBMA &&
+	    p->route_prefix.prefixlen < 8) {
+		debugf(NHRP_DEBUG_COMMON, "Shortcut to more generic than /8 dropped");
+		return;
+	}
+
+	debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req");
+
+	if (nhrp_route_address(p->ifp, &p->src_proto, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+		return;
+
+#if 0
+	/* FIXME: Update requestors binding if CIE specifies holding time */
+	nhrp_cache_update_binding(
+			NHRP_CACHE_CACHED, &p->src_proto,
+			nhrp_peer_get(p->ifp, &p->src_nbma),
+			htons(cie->holding_time));
+#endif
+
+	nifp = peer->ifp->info;
+
+	/* Create reply */
+	zb = zbuf_alloc(1500);
+	hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &p->src_nbma, &p->src_proto, &p->dst_proto);
+
+	/* Copied information from request */
+	hdr->flags = p->hdr->flags & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER|NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
+	hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE | NHRP_FLAG_RESOLUTION_AUTHORATIVE);
+	hdr->u.request_id = p->hdr->u.request_id;
+
+	/* CIE payload */
+	cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &p->if_ad->addr);
+	cie->holding_time = htons(p->if_ad->holdtime);
+	cie->mtu = htons(p->if_ad->mtu);
+	if (p->if_ad->network_id && p->route_type == NHRP_ROUTE_OFF_NBMA)
+		cie->prefix_length = p->route_prefix.prefixlen;
+	else
+		cie->prefix_length = 8 * sockunion_get_addrlen(&p->if_ad->addr);
+
+	/* Handle extensions */
+	while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+		switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+		case NHRP_EXTENSION_NAT_ADDRESS:
+			if (sockunion_family(&nifp->nat_nbma) == AF_UNSPEC)
+				break;
+			ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+			if (!ext) goto err;
+			cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, &p->if_ad->addr);
+			if (!cie) goto err;
+			nhrp_ext_complete(zb, ext);
+			break;
+		default:
+			if (nhrp_ext_reply(zb, hdr, p->ifp, ext, &payload) < 0)
+				goto err;
+			break;
+		}
+	}
+
+	nhrp_packet_complete(zb, hdr);
+	nhrp_peer_send(peer, zb);
+err:
+	nhrp_peer_unref(peer);
+	zbuf_free(zb);
+}
+
+static void nhrp_handle_registration_request(struct nhrp_packet_parser *p)
+{
+	struct interface *ifp = p->ifp;
+	struct zbuf *zb, payload;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_cie_header *cie;
+	struct nhrp_extension_header *ext;
+	struct nhrp_cache *c;
+	union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr, *nbma_natoa;
+	int holdtime, natted = 0;
+	size_t paylen;
+	void *pay;
+
+	debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req");
+
+	if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma))
+		natted = 1;
+
+	/* Create reply */
+	zb = zbuf_alloc(1500);
+	hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY,
+		&p->src_nbma, &p->src_proto, &p->if_ad->addr);
+
+	/* Copied information from request */
+	hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE | NHRP_FLAG_REGISTRATION_NAT);
+	hdr->u.request_id = p->hdr->u.request_id;
+
+	/* Copy payload CIEs */
+	paylen = zbuf_used(&p->payload);
+	pay = zbuf_pushn(zb, paylen);
+	if (!pay) goto err;
+	memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen);
+	zbuf_init(&payload, pay, paylen, paylen);
+
+	while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto)) != NULL) {
+		if (cie->prefix_length != 0xff && !(p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) {
+			cie->code = NHRP_CODE_BINDING_NON_UNIQUE;
+			continue;
+		}
+
+		/* We currently support only unique prefix registrations */
+		if (cie->prefix_length != 0xff) {
+			cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+			continue;
+		}
+
+		proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) ? &p->src_proto : &cie_proto;
+		nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC) ? &p->src_nbma : &cie_nbma;
+		nbma_natoa = NULL;
+		if (natted) {
+			nbma_natoa = nbma_addr;
+			nbma_addr = &p->peer->vc->remote.nbma;
+		}
+
+		holdtime = htons(cie->holding_time);
+		if (!holdtime) holdtime = p->if_ad->holdtime;
+
+		c = nhrp_cache_get(ifp, proto_addr, 1);
+		if (!c) {
+			cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES;
+			continue;
+		}
+
+		if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime, nhrp_peer_ref(p->peer), htons(cie->mtu), nbma_natoa)) {
+			cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED;
+			continue;
+		}
+
+		cie->code = NHRP_CODE_SUCCESS;
+	}
+
+	/* Handle extensions */
+	while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) {
+		switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+		case NHRP_EXTENSION_NAT_ADDRESS:
+			ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+			if (!ext) goto err;
+			zbuf_copy(zb, &payload, zbuf_used(&payload));
+			if (natted) {
+				nhrp_cie_push(zb, NHRP_CODE_SUCCESS,
+					&p->peer->vc->remote.nbma,
+					&p->src_proto);
+			}
+			nhrp_ext_complete(zb, ext);
+			break;
+		default:
+			if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0)
+				goto err;
+			break;
+		}
+	}
+
+	nhrp_packet_complete(zb, hdr);
+	nhrp_peer_send(p->peer, zb);
+err:
+	zbuf_free(zb);
+}
+
+static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type, union sockunion *src, union sockunion *dst)
+{
+	switch (protocol_type) {
+	case ETH_P_IP: {
+			struct iphdr *iph = zbuf_pull(zb, struct iphdr);
+			if (iph) {
+				if (src) sockunion_set(src, AF_INET, (uint8_t*) &iph->saddr, sizeof(iph->saddr));
+				if (dst) sockunion_set(dst, AF_INET, (uint8_t*) &iph->daddr, sizeof(iph->daddr));
+			}
+		}
+		break;
+	case ETH_P_IPV6: {
+			struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr);
+			if (iph) {
+				if (src) sockunion_set(src, AF_INET6, (uint8_t*) &iph->saddr, sizeof(iph->saddr));
+				if (dst) sockunion_set(dst, AF_INET6, (uint8_t*) &iph->daddr, sizeof(iph->daddr));
+			}
+		}
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type, struct zbuf *pkt)
+{
+	union sockunion dst;
+	struct zbuf *zb, payload;
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_afi_data *if_ad;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_peer *p;
+	char buf[2][SU_ADDRSTRLEN];
+
+	if (!nifp->enabled) return;
+
+	payload = *pkt;
+	if (!parse_ether_packet(&payload, protocol_type, &dst, NULL))
+		return;
+
+	if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP)
+		return;
+
+	if_ad = &nifp->afi[family2afi(sockunion_family(&dst))];
+	if (!(if_ad->flags & NHRP_IFF_REDIRECT)) {
+		debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s about packet to %s ignored",
+			sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+			sockunion2str(&dst, buf[1], sizeof buf[1]));
+		return;
+	}
+
+	debugf(NHRP_DEBUG_COMMON, "Send Traffic Indication to %s (online=%d) about packet to %s",
+		sockunion2str(&p->vc->remote.nbma, buf[0], sizeof buf[0]),
+		p->online,
+		sockunion2str(&dst, buf[1], sizeof buf[1]));
+
+	/* Create reply */
+	zb = zbuf_alloc(1500);
+	hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, &if_ad->addr, &dst);
+	hdr->hop_count = 0;
+
+	/* Payload is the packet causing indication */
+	zbuf_copy(zb, pkt, zbuf_used(pkt));
+	nhrp_packet_complete(zb, hdr);
+	nhrp_peer_send(p, zb);
+	nhrp_peer_unref(p);
+	zbuf_free(zb);
+}
+
+static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp)
+{
+	struct zbuf origmsg = pp->payload;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_reqid *reqid;
+	union sockunion src_nbma, src_proto, dst_proto;
+	char buf[2][SU_ADDRSTRLEN];
+
+	hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto);
+	if (!hdr) return;
+
+	debugf(NHRP_DEBUG_COMMON, "Error Indication from %s about packet to %s ignored",
+		sockunion2str(&pp->src_proto, buf[0], sizeof buf[0]),
+		sockunion2str(&dst_proto, buf[1], sizeof buf[1]));
+
+	reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id));
+	if (reqid)
+		reqid->cb(reqid, pp);
+}
+
+static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p)
+{
+	union sockunion dst;
+	char buf[2][SU_ADDRSTRLEN];
+
+	if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, &dst))
+		return;
+
+	debugf(NHRP_DEBUG_COMMON, "Traffic Indication from %s about packet to %s: %s",
+		sockunion2str(&p->src_proto, buf[0], sizeof buf[0]),
+		sockunion2str(&dst, buf[1], sizeof buf[1]),
+		(p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut" : "ignored");
+
+	if (p->if_ad->flags & NHRP_IFF_SHORTCUT)
+		nhrp_shortcut_initiate(&dst);
+}
+
+enum packet_type_t {
+	PACKET_UNKNOWN = 0,
+	PACKET_REQUEST,
+	PACKET_REPLY,
+	PACKET_INDICATION,
+};
+
+static struct {
+	enum packet_type_t type;
+	const char *name;
+	void (*handler)(struct nhrp_packet_parser *);
+} packet_types[] = {
+	[NHRP_PACKET_RESOLUTION_REQUEST] = {
+		.type = PACKET_REQUEST,
+		.name = "Resolution-Request",
+		.handler = nhrp_handle_resolution_req,
+	},
+	[NHRP_PACKET_RESOLUTION_REPLY] = {
+		.type = PACKET_REPLY,
+		.name = "Resolution-Reply",
+	},
+	[NHRP_PACKET_REGISTRATION_REQUEST] = {
+		.type = PACKET_REQUEST,
+		.name = "Registration-Request",
+		.handler = nhrp_handle_registration_request,
+	},
+	[NHRP_PACKET_REGISTRATION_REPLY] = {
+		.type = PACKET_REPLY,
+		.name = "Registration-Reply",
+	},
+	[NHRP_PACKET_PURGE_REQUEST] = {
+		.type = PACKET_REQUEST,
+		.name = "Purge-Request",
+	},
+	[NHRP_PACKET_PURGE_REPLY] = {
+		.type = PACKET_REPLY,
+		.name = "Purge-Reply",
+	},
+	[NHRP_PACKET_ERROR_INDICATION] = {
+		.type = PACKET_INDICATION,
+		.name = "Error-Indication",
+		.handler = nhrp_handle_error_ind,
+	},
+	[NHRP_PACKET_TRAFFIC_INDICATION] = {
+		.type = PACKET_INDICATION,
+		.name = "Traffic-Indication",
+		.handler = nhrp_handle_traffic_ind,
+	}
+};
+
+static void nhrp_peer_forward(struct nhrp_peer *p, struct nhrp_packet_parser *pp)
+{
+	struct zbuf *zb, extpl;
+	struct nhrp_packet_header *hdr;
+	struct nhrp_extension_header *ext, *dst;
+	struct nhrp_cie_header *cie;
+	struct nhrp_interface *nifp = pp->ifp->info;
+	struct nhrp_afi_data *if_ad = pp->if_ad;
+	union sockunion cie_nbma, cie_protocol;
+	uint16_t type, len;
+
+	if (pp->hdr->hop_count == 0)
+		return;
+
+	/* Create forward packet - copy header */
+	zb = zbuf_alloc(1500);
+	hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto, &pp->dst_proto);
+	hdr->flags = pp->hdr->flags;
+	hdr->hop_count = pp->hdr->hop_count - 1;
+	hdr->u.request_id = pp->hdr->u.request_id;
+
+	/* Copy payload */
+	zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload));
+
+	/* Copy extensions */
+	while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
+		type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY;
+		len = htons(ext->length);
+
+		if (type == NHRP_EXTENSION_END)
+			break;
+
+		dst = nhrp_ext_push(zb, hdr, htons(ext->type));
+		if (!dst) goto err;
+
+		switch (type) {
+		case NHRP_EXTENSION_FORWARD_TRANSIT_NHS:
+		case NHRP_EXTENSION_REVERSE_TRANSIT_NHS:
+			zbuf_put(zb, extpl.head, len);
+			if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS) ==
+			    (packet_types[hdr->type].type == PACKET_REPLY)) {
+				/* Check NHS list for forwarding loop */
+				while ((cie = nhrp_cie_pull(&extpl, pp->hdr, &cie_nbma, &cie_protocol)) != NULL) {
+					if (sockunion_same(&p->vc->remote.nbma, &cie_nbma))
+						goto err;
+				}
+				/* Append our selves to the list */
+				cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &if_ad->addr);
+				if (!cie) goto err;
+				cie->holding_time = htons(if_ad->holdtime);
+			}
+			break;
+		default:
+			if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY)
+				/* FIXME: RFC says to just copy, but not
+				 * append our selves to the transit NHS list */
+				goto err;
+		case NHRP_EXTENSION_RESPONDER_ADDRESS:
+			/* Supported compulsory extensions, and any
+			 * non-compulsory that is not explicitly handled,
+			 * should be just copied. */
+			zbuf_copy(zb, &extpl, len);
+			break;
+		}
+		nhrp_ext_complete(zb, dst);
+	}
+
+	nhrp_packet_complete(zb, hdr);
+	nhrp_peer_send(p, zb);
+	zbuf_free(zb);
+	return;
+err:
+	nhrp_packet_debug(pp->pkt, "FWD-FAIL");
+	zbuf_free(zb);
+}
+
+static void nhrp_packet_debug(struct zbuf *zb, const char *dir)
+{
+	char buf[2][SU_ADDRSTRLEN];
+	union sockunion src_nbma, src_proto, dst_proto;
+	struct nhrp_packet_header *hdr;
+	struct zbuf zhdr;
+	int reply;
+
+	if (likely(!(debug_flags & NHRP_DEBUG_COMMON)))
+		return;
+
+	zbuf_init(&zhdr, zb->buf, zb->tail-zb->buf, zb->tail-zb->buf);
+	hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto);
+
+	sockunion2str(&src_proto, buf[0], sizeof buf[0]);
+	sockunion2str(&dst_proto, buf[1], sizeof buf[1]);
+
+	reply = packet_types[hdr->type].type == PACKET_REPLY;
+	debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %s -> %s",
+		dir,
+		packet_types[hdr->type].name ? : "Unknown",
+		hdr->type,
+		reply ? buf[1] : buf[0],
+		reply ? buf[0] : buf[1]);
+}
+
+struct nhrp_route_info {
+	int local;
+	struct interface *ifp;
+	struct nhrp_vc *vc;
+};
+
+void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb)
+{
+	char buf[2][SU_ADDRSTRLEN];
+	struct nhrp_packet_header *hdr;
+	struct nhrp_vc *vc = p->vc;
+	struct interface *ifp = p->ifp;
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_packet_parser pp;
+	struct nhrp_peer *peer = NULL;
+	struct nhrp_reqid *reqid;
+	const char *info = NULL;
+	union sockunion *target_addr;
+	unsigned paylen, extoff, extlen, realsize;
+	afi_t afi;
+
+	debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %s -> %s",
+		sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+		sockunion2str(&vc->local.nbma, buf[1], sizeof buf[1]));
+
+	if (!p->online) {
+		info = "peer not online";
+		goto drop;
+	}
+
+	if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) {
+		info = "bad checksum";
+		goto drop;
+	}
+
+	realsize = zbuf_used(zb);
+	hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto);
+	if (!hdr) {
+		info = "corrupt header";
+		goto drop;
+	}
+
+	pp.ifp = ifp;
+	pp.pkt = zb;
+	pp.hdr = hdr;
+	pp.peer = p;
+
+	afi = htons(hdr->afnum);
+	if (hdr->type > ZEBRA_NUM_OF(packet_types) ||
+	    hdr->version != NHRP_VERSION_RFC2332 ||
+	    afi >= AFI_MAX ||
+	    packet_types[hdr->type].type == PACKET_UNKNOWN ||
+	    htons(hdr->packet_size) > realsize) {
+		zlog_info("From %s: error: packet type %d, version %d, AFI %d, size %d (real size %d)",
+			   sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+			   (int) hdr->type, (int) hdr->version, (int) afi,
+			   (int) htons(hdr->packet_size),
+			   (int) realsize);
+		goto drop;
+	}
+	pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[afi];
+
+	extoff = htons(hdr->extension_offset);
+	if (extoff) {
+		if (extoff >= realsize) {
+			info = "extoff larger than packet";
+			goto drop;
+		}
+		paylen = extoff - (zb->head - zb->buf);
+	} else {
+		paylen = zbuf_used(zb);
+	}
+	zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen);
+	extlen = zbuf_used(zb);
+	zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen);
+
+	if (!nifp->afi[afi].network_id) {
+		info = "nhrp not enabled";
+		goto drop;
+	}
+
+	nhrp_packet_debug(zb, "Recv");
+
+	/* FIXME: Check authentication here. This extension needs to be
+	 * pre-handled. */
+
+	/* Figure out if this is local */
+	target_addr = (packet_types[hdr->type].type == PACKET_REPLY) ? &pp.src_proto : &pp.dst_proto;
+
+	if (sockunion_same(&pp.src_proto, &pp.dst_proto))
+		pp.route_type = NHRP_ROUTE_LOCAL;
+	else
+		pp.route_type = nhrp_route_address(pp.ifp, target_addr, &pp.route_prefix, &peer);
+
+	switch (pp.route_type) {
+	case NHRP_ROUTE_LOCAL:
+		nhrp_packet_debug(zb, "!LOCAL");
+		if (packet_types[hdr->type].type == PACKET_REPLY) {
+			reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id));
+			if (reqid) {
+				reqid->cb(reqid, &pp);
+				break;
+			} else {
+				nhrp_packet_debug(zb, "!UNKNOWN-REQID");
+				/* FIXME: send error-indication */
+			}
+		}
+	case NHRP_ROUTE_OFF_NBMA:
+		if (packet_types[hdr->type].handler) {
+			packet_types[hdr->type].handler(&pp);
+			break;
+		}
+		break;
+	case NHRP_ROUTE_NBMA_NEXTHOP:
+		nhrp_peer_forward(peer, &pp);
+		break;
+	case NHRP_ROUTE_BLACKHOLE:
+		break;
+	}
+
+drop:
+	if (info) {
+		zlog_info("From %s: error: %s",
+			  sockunion2str(&vc->remote.nbma, buf[0], sizeof buf[0]),
+			  info);
+	}
+	if (peer) nhrp_peer_unref(peer);
+	zbuf_free(zb);
+}