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_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 0000000..421f288
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,402 @@
+/* NHRP shortcut related 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 "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static int nhrp_shortcut_do_purge(struct thread *t);
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s);
+static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s);
+
+static void nhrp_shortcut_check_use(struct nhrp_shortcut *s)
+{
+	char buf[PREFIX_STRLEN];
+
+	if (s->expiring && s->cache && s->cache->used) {
+		debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring",
+			prefix2str(s->p, buf, sizeof buf));
+		nhrp_shortcut_send_resolution_req(s);
+	}
+}
+
+static int nhrp_shortcut_do_expire(struct thread *t)
+{
+	struct nhrp_shortcut *s = THREAD_ARG(t);
+
+	s->t_timer = NULL;
+	THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3);
+	s->expiring = 1;
+	nhrp_shortcut_check_use(s);
+
+	return 0;
+}
+
+static void nhrp_shortcut_cache_notify(struct notifier_block *n, unsigned long cmd)
+{
+	struct nhrp_shortcut *s = container_of(n, struct nhrp_shortcut, cache_notifier);
+
+	switch (cmd) {
+	case NOTIFY_CACHE_UP:
+		if (!s->route_installed) {
+			nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 0);
+			s->route_installed = 1;
+		}
+		break;
+	case NOTIFY_CACHE_USED:
+		nhrp_shortcut_check_use(s);
+		break;
+	case NOTIFY_CACHE_DOWN:
+	case NOTIFY_CACHE_DELETE:
+		if (s->route_installed) {
+			nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0);
+			s->route_installed = 0;
+		}
+		if (cmd == NOTIFY_CACHE_DELETE)
+			nhrp_shortcut_delete(s);
+		break;
+	}
+}
+
+static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, enum nhrp_cache_type type, struct nhrp_cache *c, int holding_time)
+{
+	s->type = type;
+	if (c != s->cache) {
+		if (s->cache) {
+			nhrp_cache_notify_del(s->cache, &s->cache_notifier);
+			s->cache = NULL;
+		}
+		s->cache = c;
+		if (s->cache) {
+			nhrp_cache_notify_add(s->cache, &s->cache_notifier, nhrp_shortcut_cache_notify);
+			if (s->cache->route_installed) {
+				/* Force renewal of Zebra announce on prefix change */
+				s->route_installed = 0;
+				nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP);
+			}
+		}
+		if (!s->cache || !s->cache->route_installed)
+			nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_DOWN);
+	}
+	if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) {
+		nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0);
+		s->route_installed = 1;
+	} else if (s->type == NHRP_CACHE_INVALID && s->route_installed) {
+		nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0);
+		s->route_installed = 0;
+	}
+
+	THREAD_OFF(s->t_timer);
+	if (holding_time) {
+		s->expiring = 0;
+		s->holding_time = holding_time;
+		THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3);
+	}
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+	struct route_node *rn;
+	afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+	char buf[PREFIX_STRLEN];
+
+	THREAD_OFF(s->t_timer);
+	nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+	debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged",
+		prefix2str(s->p, buf, sizeof buf));
+
+	nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0);
+
+	/* Delete node */
+	rn = route_node_lookup(shortcut_rib[afi], s->p);
+	if (rn) {
+		XFREE(MTYPE_NHRP_SHORTCUT, rn->info);
+		rn->info = NULL;
+		route_unlock_node(rn);
+		route_unlock_node(rn);
+	}
+}
+
+static int nhrp_shortcut_do_purge(struct thread *t)
+{
+	struct nhrp_shortcut *s = THREAD_ARG(t);
+	s->t_timer = NULL;
+	nhrp_shortcut_delete(s);
+	return 0;
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+	struct nhrp_shortcut *s;
+	struct route_node *rn;
+	char buf[PREFIX_STRLEN];
+	afi_t afi = family2afi(PREFIX_FAMILY(p));
+
+	if (!shortcut_rib[afi])
+		return 0;
+
+	rn = route_node_get(shortcut_rib[afi], p);
+	if (!rn->info) {
+		s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, sizeof(struct nhrp_shortcut));
+		s->type = NHRP_CACHE_INVALID;
+		s->p = &rn->p;
+
+		debugf(NHRP_DEBUG_ROUTE, "Shortcut %s created",
+			prefix2str(s->p, buf, sizeof buf));
+	} else {
+		s = rn->info;
+		route_unlock_node(rn);
+	}
+	return s;
+}
+
+static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, void *arg)
+{
+	struct nhrp_packet_parser *pp = arg;
+	struct nhrp_shortcut *s = container_of(reqid, struct nhrp_shortcut, reqid);
+	struct nhrp_shortcut *ps;
+	struct nhrp_extension_header *ext;
+	struct nhrp_cie_header *cie;
+	struct nhrp_cache *c = NULL;
+	union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma;
+	struct prefix prefix, route_prefix;
+	struct zbuf extpl;
+	char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN];
+	int holding_time = pp->if_ad->holdtime;
+
+	nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+	THREAD_OFF(s->t_timer);
+	THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1);
+
+	if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) {
+		if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION &&
+		    pp->hdr->u.error.code == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) {
+			debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution: Protocol address unreachable");
+			nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, NULL, holding_time);
+		} else {
+			debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution failed");
+		}
+		return;
+	}
+
+	/* Parse extensions */
+	memset(&nat_nbma, 0, sizeof nat_nbma);
+	while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) {
+		switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+		case NHRP_EXTENSION_NAT_ADDRESS:
+			nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto);
+			break;
+		}
+	}
+
+	/* Minor sanity check */
+	prefix2sockunion(s->p, &cie_proto);
+	if (!sockunion_same(&cie_proto, &pp->dst_proto)) {
+		debugf(NHRP_DEBUG_COMMON, "Shortcut: Warning dst_proto altered from %s to %s",
+			sockunion2str(&cie_proto, buf[0], sizeof buf[0]),
+			sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1]));
+	}
+
+	/* One or more CIEs should be given as reply, we support only one */
+	cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto);
+	if (!cie || cie->code != NHRP_CODE_SUCCESS) {
+		debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", cie ? cie->code : -1);
+		return;
+	}
+
+	proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &pp->dst_proto;
+	if (cie->holding_time)
+		holding_time = htons(cie->holding_time);
+
+	prefix = *s->p;
+	prefix.prefixlen = cie->prefix_length;
+
+	/* Sanity check prefix length */
+	if (prefix.prefixlen >= 8*prefix_blen(&prefix)) {
+		prefix.prefixlen = 8*prefix_blen(&prefix);
+	} else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) == NHRP_ROUTE_NBMA_NEXTHOP) {
+		if (prefix.prefixlen < route_prefix.prefixlen)
+			prefix.prefixlen = route_prefix.prefixlen;
+	}
+
+	debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d",
+		prefix2str(&prefix, bufp, sizeof bufp),
+		sockunion2str(proto, buf[0], sizeof buf[0]),
+		sockunion2str(&cie_nbma, buf[1], sizeof buf[1]),
+		sockunion2str(&nat_nbma, buf[2], sizeof buf[2]),
+		htons(cie->holding_time));
+
+	/* Update cache entry for the protocol to nbma binding */
+	if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+		nbma = &nat_nbma;
+		nbma_natoa = &cie_nbma;
+	} else {
+		nbma = &cie_nbma;
+		nbma_natoa = NULL;
+	}
+	if (sockunion_family(nbma)) {
+		c = nhrp_cache_get(pp->ifp, proto, 1);
+		if (c) {
+			nhrp_cache_update_binding(
+					c, NHRP_CACHE_CACHED, holding_time,
+					nhrp_peer_get(pp->ifp, nbma),
+					htons(cie->mtu), nbma_natoa);
+		}
+	}
+
+	/* Update shortcut entry for subnet to protocol gw binding */
+	if (c && !sockunion_same(proto, &pp->dst_proto)) {
+		ps = nhrp_shortcut_get(&prefix);
+		if (ps) {
+			ps->addr = s->addr;
+			nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time);
+		}
+	}
+
+	debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled");
+}
+
+static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s)
+{
+	struct zbuf *zb;
+	struct nhrp_packet_header *hdr;
+	struct interface *ifp;
+	struct nhrp_interface *nifp;
+	struct nhrp_peer *peer;
+
+	if (nhrp_route_address(NULL, &s->addr, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP)
+		return;
+
+	if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE)
+		s->type = NHRP_CACHE_INCOMPLETE;
+
+	ifp = peer->ifp;
+	nifp = ifp->info;
+
+	/* Create request */
+	zb = zbuf_alloc(1500);
+	hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REQUEST,
+		&nifp->nbma, &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, &s->addr);
+	hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, nhrp_shortcut_recv_resolution_rep));
+	hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER |
+			   NHRP_FLAG_RESOLUTION_AUTHORATIVE |
+			   NHRP_FLAG_RESOLUTION_SOURCE_STABLE);
+
+	/* RFC2332 - One or zero CIEs, if CIE is present contains:
+	 *  - Prefix length: widest acceptable prefix we accept (if U set, 0xff)
+	 *  - MTU: MTU of the source station
+	 *  - Holding Time: Max time to cache the source information
+	 * */
+	/* FIXME: Send holding time, and MTU */
+
+	nhrp_ext_request(zb, hdr, ifp);
+
+	/* Cisco NAT detection extension */
+	hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+	nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+
+	nhrp_packet_complete(zb, hdr);
+
+	nhrp_peer_send(peer, zb);
+	nhrp_peer_unref(peer);
+	zbuf_free(zb);
+}
+
+void nhrp_shortcut_initiate(union sockunion *addr)
+{
+	struct prefix p;
+	struct nhrp_shortcut *s;
+
+	sockunion2hostprefix(addr, &p);
+	s = nhrp_shortcut_get(&p);
+	if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+		s->addr = *addr;
+		THREAD_OFF(s->t_timer);
+		THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30);
+		nhrp_shortcut_send_resolution_req(s);
+	}
+}
+
+void nhrp_shortcut_init(void)
+{
+	shortcut_rib[AFI_IP] = route_table_init();
+	shortcut_rib[AFI_IP6] = route_table_init();
+}
+
+void nhrp_shortcut_terminate(void)
+{
+	route_table_finish(shortcut_rib[AFI_IP]);
+	route_table_finish(shortcut_rib[AFI_IP6]);
+}
+
+void nhrp_shortcut_foreach(afi_t afi, void (*cb)(struct nhrp_shortcut *, void *), void *ctx)
+{
+	struct route_table *rt = shortcut_rib[afi];
+	struct route_node *rn;
+	route_table_iter_t iter;
+
+	if (!rt) return;
+
+	route_table_iter_init(&iter, rt);
+	while ((rn = route_table_iter_next(&iter)) != NULL) {
+		if (rn->info) cb(rn->info, ctx);
+	}
+	route_table_iter_cleanup(&iter);
+}
+
+struct purge_ctx {
+	const struct prefix *p;
+	int deleted;
+};
+
+void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force)
+{
+	THREAD_OFF(s->t_timer);
+	nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+	if (force) {
+		/* Immediate purge on route with draw or pending shortcut */
+		THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5);
+	} else {
+		/* Soft expire - force immediate renewal, but purge
+		 * in few seconds to make sure stale route is not
+		 * used too long. In practice most purges are caused
+		 * by hub bgp change, but target usually stays same.
+		 * This allows to keep nhrp route up, and to not
+		 * cause temporary rerouting via hubs causing latency
+		 * jitter. */
+		THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000);
+		s->expiring = 1;
+		nhrp_shortcut_check_use(s);
+	}
+}
+
+static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx)
+{
+	struct purge_ctx *pctx = ctx;
+
+	if (prefix_match(pctx->p, s->p))
+		nhrp_shortcut_purge(s, pctx->deleted || !s->cache);
+}
+
+void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted)
+{
+	struct purge_ctx pctx = {
+		.p = p,
+		.deleted = deleted,
+	};
+	nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), nhrp_shortcut_purge_prefix, &pctx);
+}
+