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_interface.c b/nhrpd/nhrp_interface.c
new file mode 100644
index 0000000..8118927
--- /dev/null
+++ b/nhrpd/nhrp_interface.c
@@ -0,0 +1,404 @@
+/* NHRP interface
+ * 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 <net/if_arp.h>
+#include "zebra.h"
+#include "linklist.h"
+#include "memory.h"
+#include "thread.h"
+
+#include "nhrpd.h"
+#include "os.h"
+#include "netlink.h"
+
+static int nhrp_if_new_hook(struct interface *ifp)
+{
+	struct nhrp_interface *nifp;
+	afi_t afi;
+
+	nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface));
+	if (!nifp) return 0;
+
+	ifp->info = nifp;
+	nifp->ifp = ifp;
+
+	notifier_init(&nifp->notifier_list);
+	for (afi = 0; afi < AFI_MAX; afi++) {
+		struct nhrp_afi_data *ad = &nifp->afi[afi];
+		ad->holdtime = NHRPD_DEFAULT_HOLDTIME;
+		list_init(&ad->nhslist_head);
+	}
+
+	return 0;
+}
+
+static int nhrp_if_delete_hook(struct interface *ifp)
+{
+	XFREE(MTYPE_NHRP_IF, ifp->info);
+	return 0;
+}
+
+void nhrp_interface_init(void)
+{
+	if_add_hook(IF_NEW_HOOK,    nhrp_if_new_hook);
+	if_add_hook(IF_DELETE_HOOK, nhrp_if_delete_hook);
+}
+
+void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi)
+{
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_afi_data *if_ad = &nifp->afi[afi];
+	unsigned short new_mtu;
+
+	if (if_ad->configured_mtu < 0)
+		new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0;
+	else
+		new_mtu = if_ad->configured_mtu;
+	if (new_mtu >= 1500)
+		new_mtu = 0;
+
+	if (new_mtu != if_ad->mtu) {
+		debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, new_mtu);
+		if_ad->mtu = new_mtu;
+		notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_MTU_CHANGED);
+	}
+}
+
+static void nhrp_interface_update_source(struct interface *ifp)
+{
+	struct nhrp_interface *nifp = ifp->info;
+
+	if (!nifp->source || !nifp->nbmaifp ||
+	    nifp->linkidx == nifp->nbmaifp->ifindex)
+		return;
+
+	nifp->linkidx = nifp->nbmaifp->ifindex;
+	debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d", ifp->name, nifp->linkidx);
+	netlink_gre_set_link(ifp->ifindex, nifp->linkidx);
+}
+
+static void nhrp_interface_interface_notifier(struct notifier_block *n, unsigned long cmd)
+{
+	struct nhrp_interface *nifp = container_of(n, struct nhrp_interface, nbmanifp_notifier);
+	struct interface *nbmaifp = nifp->nbmaifp;
+	struct nhrp_interface *nbmanifp = nbmaifp->info;
+	char buf[SU_ADDRSTRLEN];
+
+	switch (cmd) {
+	case NOTIFY_INTERFACE_CHANGED:
+		nhrp_interface_update_mtu(nifp->ifp, AFI_IP);
+		nhrp_interface_update_source(nifp->ifp);
+		break;
+	case NOTIFY_INTERFACE_ADDRESS_CHANGED:
+		nifp->nbma = nbmanifp->afi[AFI_IP].addr;
+		nhrp_interface_update(nifp->ifp);
+		notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED);
+		debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %s",
+			nifp->ifp->name,
+			sockunion2str(&nifp->nbma, buf, sizeof buf));
+		break;
+	}
+}
+
+static void nhrp_interface_update_nbma(struct interface *ifp)
+{
+	struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL;
+	struct interface *nbmaifp = NULL;
+	union sockunion nbma;
+
+	sockunion_family(&nbma) = AF_UNSPEC;
+
+	if (nifp->source)
+		nbmaifp = if_lookup_by_name(nifp->source);
+
+	switch (ifp->ll_type) {
+	case ZEBRA_LLT_IPGRE: {
+			struct in_addr saddr = {0};
+			netlink_gre_get_info(ifp->ifindex, &nifp->grekey, &nifp->linkidx, &saddr);
+			debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, nifp->grekey, nifp->linkidx, saddr.s_addr);
+			if (saddr.s_addr)
+				sockunion_set(&nbma, AF_INET, (u_char *) &saddr.s_addr, sizeof(saddr.s_addr));
+			else if (!nbmaifp && nifp->linkidx != IFINDEX_INTERNAL)
+				nbmaifp = if_lookup_by_index(nifp->linkidx);
+		}
+		break;
+	default:
+		break;
+	}
+
+	if (nbmaifp)
+		nbmanifp = nbmaifp->info;
+
+	if (nbmaifp != nifp->nbmaifp) {
+		if (nifp->nbmaifp)
+			notifier_del(&nifp->nbmanifp_notifier);
+		nifp->nbmaifp = nbmaifp;
+		if (nbmaifp) {
+			notifier_add(&nifp->nbmanifp_notifier, &nbmanifp->notifier_list, nhrp_interface_interface_notifier);
+			debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, nbmaifp->name);
+		}
+	}
+
+	if (nbmaifp) {
+		if (sockunion_family(&nbma) == AF_UNSPEC)
+			nbma = nbmanifp->afi[AFI_IP].addr;
+		nhrp_interface_update_mtu(ifp, AFI_IP);
+		nhrp_interface_update_source(ifp);
+	}
+
+	if (!sockunion_same(&nbma, &nifp->nbma)) {
+		nifp->nbma = nbma;
+		nhrp_interface_update(nifp->ifp);
+		debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name);
+		notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_NBMA_CHANGED);
+	}
+
+	nhrp_interface_update(ifp);
+}
+
+static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, int force)
+{
+	const int family = afi2family(afi);
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_afi_data *if_ad = &nifp->afi[afi];
+	struct nhrp_cache *nc;
+	struct connected *c, *best;
+	struct listnode *cnode;
+	union sockunion addr;
+	char buf[PREFIX_STRLEN];
+
+	/* Select new best match preferring primary address */
+	best = NULL;
+	for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) {
+		if (PREFIX_FAMILY(c->address) != family)
+			continue;
+		if (best == NULL) {
+			best = c;
+			continue;
+		}
+		if ((best->flags & ZEBRA_IFA_SECONDARY) && !(c->flags & ZEBRA_IFA_SECONDARY)) {
+			best = c;
+			continue;
+		}
+		if (!(best->flags & ZEBRA_IFA_SECONDARY) && (c->flags & ZEBRA_IFA_SECONDARY))
+			continue;
+		if (best->address->prefixlen > c->address->prefixlen) {
+			best = c;
+			continue;
+		}
+		if (best->address->prefixlen < c->address->prefixlen)
+			continue;
+	}
+
+	/* On NHRP interfaces a host prefix is required */
+	if (best && if_ad->configured && best->address->prefixlen != 8 * prefix_blen(best->address)) {
+		zlog_notice("%s: %s is not a host prefix", ifp->name,
+			prefix2str(best->address, buf, sizeof buf));
+		best = NULL;
+	}
+
+	/* Update address if it changed */
+	if (best)
+		prefix2sockunion(best->address, &addr);
+	else
+		memset(&addr, 0, sizeof(addr));
+
+	if (!force && sockunion_same(&if_ad->addr, &addr))
+		return;
+
+	if (sockunion_family(&if_ad->addr) != AF_UNSPEC) {
+		nc = nhrp_cache_get(ifp, &if_ad->addr, 0);
+		if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, NULL, 0, NULL);
+	}
+
+	debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s",
+		ifp->name, afi == AFI_IP ? 4 : 6,
+		best ? prefix2str(best->address, buf, sizeof buf) : "(none)");
+	if_ad->addr = addr;
+
+	if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) {
+		nc = nhrp_cache_get(ifp, &addr, 1);
+		if (nc) nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, 0, NULL);
+	}
+
+	notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED);
+}
+
+void nhrp_interface_update(struct interface *ifp)
+{
+	struct nhrp_interface *nifp = ifp->info;
+	struct nhrp_afi_data *if_ad;
+	afi_t afi;
+	int enabled = 0;
+
+	notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED);
+
+	for (afi = 0; afi < AFI_MAX; afi++) {
+		if_ad = &nifp->afi[afi];
+
+		if (sockunion_family(&nifp->nbma) == AF_UNSPEC ||
+		    ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) ||
+		    !if_ad->network_id) {
+			if (if_ad->configured) {
+				if_ad->configured = 0;
+				nhrp_interface_update_address(ifp, afi, 1);
+			}
+			continue;
+		}
+
+		if (!if_ad->configured) {
+			os_configure_dmvpn(ifp->ifindex, ifp->name, afi2family(afi));
+			if_ad->configured = 1;
+			nhrp_interface_update_address(ifp, afi, 1);
+		}
+
+		enabled = 1;
+	}
+
+	if (enabled != nifp->enabled) {
+		nifp->enabled = enabled;
+		notifier_call(&nifp->notifier_list, enabled ? NOTIFY_INTERFACE_UP : NOTIFY_INTERFACE_DOWN);
+	}
+}
+
+int nhrp_interface_add(int cmd, struct zclient *client, zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	/* read and add the interface in the iflist. */
+	ifp = zebra_interface_add_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s",
+		ifp->name, ifp->ifindex,
+		ifp->ll_type, if_link_type_str(ifp->ll_type));
+
+	nhrp_interface_update_nbma(ifp);
+
+	return 0;
+}
+
+int nhrp_interface_delete(int cmd, struct zclient *client,
+			  zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+	struct stream *s;
+
+	s = client->ibuf;
+	ifp = zebra_interface_state_read(s, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name);
+	ifp->ifindex = IFINDEX_INTERNAL;
+	nhrp_interface_update(ifp);
+	/* if_delete(ifp); */
+	return 0;
+}
+
+int nhrp_interface_up(int cmd, struct zclient *client,
+		      zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	ifp = zebra_interface_state_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name);
+	nhrp_interface_update_nbma(ifp);
+
+	return 0;
+}
+
+int nhrp_interface_down(int cmd, struct zclient *client,
+			zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct interface *ifp;
+
+	ifp = zebra_interface_state_read(client->ibuf, vrf_id);
+	if (ifp == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name);
+	nhrp_interface_update(ifp);
+	return 0;
+}
+
+int nhrp_interface_address_add(int cmd, struct zclient *client,
+			       zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct connected *ifc;
+	char buf[PREFIX_STRLEN];
+
+	ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id);
+	if (ifc == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %s",
+		ifc->ifp->name,
+		prefix2str(ifc->address, buf, sizeof buf));
+
+	nhrp_interface_update_address(ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0);
+
+	return 0;
+}
+
+int nhrp_interface_address_delete(int cmd, struct zclient *client,
+				  zebra_size_t length, vrf_id_t vrf_id)
+{
+	struct connected *ifc;
+	char buf[PREFIX_STRLEN];
+
+	ifc = zebra_interface_address_read(cmd, client->ibuf, vrf_id);
+	if (ifc == NULL)
+		return 0;
+
+	debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %s",
+		ifc->ifp->name,
+		prefix2str(ifc->address, buf, sizeof buf));
+
+	nhrp_interface_update_address(ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0);
+	connected_free(ifc);
+
+	return 0;
+}
+
+void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, notifier_fn_t fn)
+{
+	struct nhrp_interface *nifp = ifp->info;
+	notifier_add(n, &nifp->notifier_list, fn);
+}
+
+void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n)
+{
+	notifier_del(n);
+}
+
+void nhrp_interface_set_protection(struct interface *ifp, const char *profile, const char *fallback_profile)
+{
+	struct nhrp_interface *nifp = ifp->info;
+
+	if (nifp->ipsec_profile) free(nifp->ipsec_profile);
+	nifp->ipsec_profile = profile ? strdup(profile) : NULL;
+
+	if (nifp->ipsec_fallback_profile) free(nifp->ipsec_fallback_profile);
+	nifp->ipsec_fallback_profile = fallback_profile ? strdup(fallback_profile) : NULL;
+}
+
+void nhrp_interface_set_source(struct interface *ifp, const char *ifname)
+{
+	struct nhrp_interface *nifp = ifp->info;
+
+	if (nifp->source) free(nifp->source);
+	nifp->source = ifname ? strdup(ifname) : NULL;
+
+	nhrp_interface_update_nbma(ifp);
+}