summaryrefslogtreecommitdiffstats
path: root/nhrpd/nhrp_cache.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--nhrpd/nhrp_cache.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c
new file mode 100644
index 0000000..81d9bb2
--- /dev/null
+++ b/nhrpd/nhrp_cache.c
@@ -0,0 +1,575 @@
+/* NHRP cache
+ * 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 "zebra.h"
+#include "memory.h"
+#include "thread.h"
+#include "hash.h"
+#include "nhrpd.h"
+
+#include "netlink.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE, "NHRP cache entry");
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE_CONFIG, "NHRP cache config entry");
+
+unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES];
+
+const char *const nhrp_cache_type_str[] = {
+ [NHRP_CACHE_INVALID] = "invalid",
+ [NHRP_CACHE_INCOMPLETE] = "incomplete",
+ [NHRP_CACHE_NEGATIVE] = "negative",
+ [NHRP_CACHE_CACHED] = "cached",
+ [NHRP_CACHE_DYNAMIC] = "dynamic",
+ [NHRP_CACHE_NHS] = "nhs",
+ [NHRP_CACHE_STATIC] = "static",
+ [NHRP_CACHE_LOCAL] = "local",
+};
+
+static unsigned int nhrp_cache_protocol_key(const void *peer_data)
+{
+ const struct nhrp_cache *p = peer_data;
+ return sockunion_hash(&p->remote_addr);
+}
+
+static bool nhrp_cache_protocol_cmp(const void *cache_data,
+ const void *key_data)
+{
+ const struct nhrp_cache *a = cache_data;
+ const struct nhrp_cache *b = key_data;
+
+ return sockunion_same(&a->remote_addr, &b->remote_addr);
+}
+
+static void *nhrp_cache_alloc(void *data)
+{
+ struct nhrp_cache *p, *key = data;
+
+ p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache));
+
+ *p = (struct nhrp_cache){
+ .cur.type = NHRP_CACHE_INVALID,
+ .new.type = NHRP_CACHE_INVALID,
+ .remote_addr = key->remote_addr,
+ .ifp = key->ifp,
+ .notifier_list =
+ NOTIFIER_LIST_INITIALIZER(&p->notifier_list),
+ };
+ nhrp_cache_counts[p->cur.type]++;
+
+ return p;
+}
+
+static void nhrp_cache_free(struct nhrp_cache *c)
+{
+ struct nhrp_interface *nifp = c->ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Deleting cache entry");
+ nhrp_cache_counts[c->cur.type]--;
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE);
+ assert(!notifier_active(&c->notifier_list));
+ hash_release(nifp->cache_hash, c);
+ THREAD_OFF(c->t_timeout);
+ THREAD_OFF(c->t_auth);
+ XFREE(MTYPE_NHRP_CACHE, c);
+}
+
+static unsigned int nhrp_cache_config_protocol_key(const void *peer_data)
+{
+ const struct nhrp_cache_config *p = peer_data;
+ return sockunion_hash(&p->remote_addr);
+}
+
+static bool nhrp_cache_config_protocol_cmp(const void *cache_data,
+ const void *key_data)
+{
+ const struct nhrp_cache_config *a = cache_data;
+ const struct nhrp_cache_config *b = key_data;
+
+ if (!sockunion_same(&a->remote_addr, &b->remote_addr))
+ return false;
+ if (a->ifp != b->ifp)
+ return false;
+ return true;
+}
+
+static void *nhrp_cache_config_alloc(void *data)
+{
+ struct nhrp_cache_config *p, *key = data;
+
+ p = XCALLOC(MTYPE_NHRP_CACHE_CONFIG, sizeof(struct nhrp_cache_config));
+
+ *p = (struct nhrp_cache_config){
+ .remote_addr = key->remote_addr,
+ .ifp = key->ifp,
+ };
+ return p;
+}
+
+void nhrp_cache_config_free(struct nhrp_cache_config *c)
+{
+ struct nhrp_interface *nifp = c->ifp->info;
+
+ hash_release(nifp->cache_config_hash, c);
+ XFREE(MTYPE_NHRP_CACHE_CONFIG, c);
+}
+
+struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp,
+ union sockunion *remote_addr,
+ int create)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_config key;
+
+ if (!nifp->cache_config_hash) {
+ nifp->cache_config_hash =
+ hash_create(nhrp_cache_config_protocol_key,
+ nhrp_cache_config_protocol_cmp,
+ "NHRP Config Cache");
+ if (!nifp->cache_config_hash)
+ return NULL;
+ }
+ key.remote_addr = *remote_addr;
+ key.ifp = ifp;
+
+ return hash_get(nifp->cache_config_hash, &key,
+ create ? nhrp_cache_config_alloc : NULL);
+}
+
+static void do_nhrp_cache_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct nhrp_cache *c = hb->data;
+
+ nhrp_cache_free(c);
+}
+
+static void do_nhrp_cache_config_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct nhrp_cache_config *cc = hb->data;
+
+ nhrp_cache_config_free(cc);
+}
+
+void nhrp_cache_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted cache entries (%lu)",
+ nifp->cache_hash ? nifp->cache_hash->count : 0);
+
+ if (nifp->cache_hash) {
+ hash_iterate(nifp->cache_hash, do_nhrp_cache_free, NULL);
+ hash_free(nifp->cache_hash);
+ }
+
+ if (nifp->cache_config_hash) {
+ hash_iterate(nifp->cache_config_hash, do_nhrp_cache_config_free,
+ NULL);
+ hash_free(nifp->cache_config_hash);
+ }
+}
+
+struct nhrp_cache *nhrp_cache_get(struct interface *ifp,
+ union sockunion *remote_addr, int create)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache key;
+
+ if (!nifp->cache_hash) {
+ nifp->cache_hash =
+ hash_create(nhrp_cache_protocol_key,
+ nhrp_cache_protocol_cmp, "NHRP Cache");
+ if (!nifp->cache_hash)
+ return NULL;
+ }
+
+ key.remote_addr = *remote_addr;
+ key.ifp = ifp;
+
+ return hash_get(nifp->cache_hash, &key,
+ create ? nhrp_cache_alloc : NULL);
+}
+
+static void nhrp_cache_do_free(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+
+ c->t_timeout = NULL;
+ nhrp_cache_free(c);
+}
+
+static void nhrp_cache_do_timeout(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+
+ c->t_timeout = NULL;
+ if (c->cur.type != NHRP_CACHE_INVALID)
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL,
+ NULL);
+}
+
+static void nhrp_cache_update_route(struct nhrp_cache *c)
+{
+ struct prefix pfx;
+ struct nhrp_peer *p = c->cur.peer;
+ struct nhrp_interface *nifp;
+
+ if (!sockunion2hostprefix(&c->remote_addr, &pfx))
+ return;
+
+ if (p && nhrp_peer_check(p, 1)) {
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ /* remote_nbma_natoa is already set. Therefore, binding
+ * should be updated to this value and not vc's remote
+ * nbma.
+ */
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (remote_nbma_natoa set): Update binding for %pSU dev %s from (deleted) peer.vc.nbma %pSU to %pSU",
+ &c->remote_addr, p->ifp->name,
+ &p->vc->remote.nbma, &c->cur.remote_nbma_natoa);
+
+ netlink_update_binding(p->ifp, &c->remote_addr,
+ &c->cur.remote_nbma_natoa);
+ } else {
+ /* update binding to peer->vc->remote->nbma */
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (remote_nbma_natoa unspec): Update binding for %pSU dev %s from (deleted) to peer.vc.nbma %pSU",
+ &c->remote_addr, p->ifp->name,
+ &p->vc->remote.nbma);
+
+ netlink_update_binding(p->ifp, &c->remote_addr,
+ &p->vc->remote.nbma);
+ }
+
+ nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL,
+ c->cur.mtu);
+ if (c->cur.type >= NHRP_CACHE_DYNAMIC) {
+ nhrp_route_update_nhrp(&pfx, c->ifp);
+ c->nhrp_route_installed = 1;
+ } else if (c->nhrp_route_installed) {
+ nhrp_route_update_nhrp(&pfx, NULL);
+ c->nhrp_route_installed = 0;
+ }
+ if (!c->route_installed) {
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_UP);
+ c->route_installed = 1;
+ }
+ } else {
+ /* debug the reason for peer check fail */
+ if (p) {
+ nifp = p->ifp->info;
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (peer check failed: online?%d requested?%d ipsec?%d)",
+ p->online, p->requested,
+ nifp->ipsec_profile ? 1 : 0);
+ } else
+ debugf(NHRP_DEBUG_COMMON,
+ "cache (peer check failed: no p)");
+
+ if (c->nhrp_route_installed) {
+ nhrp_route_update_nhrp(&pfx, NULL);
+ c->nhrp_route_installed = 0;
+ }
+ if (c->route_installed) {
+ assert(sockunion2hostprefix(&c->remote_addr, &pfx));
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
+ nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL,
+ 0);
+ c->route_installed = 0;
+ }
+ }
+}
+
+static void nhrp_cache_peer_notifier(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_cache *c =
+ container_of(n, struct nhrp_cache, peer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ nhrp_cache_update_route(c);
+ break;
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN);
+ nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL,
+ NULL);
+ break;
+ case NOTIFY_PEER_NBMA_CHANGING:
+ if (c->cur.type == NHRP_CACHE_DYNAMIC)
+ c->cur.peer->vc->abort_migration = 1;
+ break;
+ }
+}
+
+static void nhrp_cache_reset_new(struct nhrp_cache *c)
+{
+ THREAD_OFF(c->t_auth);
+ if (notifier_list_anywhere(&c->newpeer_notifier))
+ nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier);
+ nhrp_peer_unref(c->new.peer);
+ memset(&c->new, 0, sizeof(c->new));
+ c->new.type = NHRP_CACHE_INVALID;
+}
+
+static void nhrp_cache_update_timers(struct nhrp_cache *c)
+{
+ THREAD_OFF(c->t_timeout);
+
+ switch (c->cur.type) {
+ case NHRP_CACHE_INVALID:
+ if (!c->t_auth)
+ thread_add_timer_msec(master, nhrp_cache_do_free, c, 10,
+ &c->t_timeout);
+ break;
+ default:
+ if (c->cur.expires)
+ thread_add_timer(master, nhrp_cache_do_timeout, c,
+ c->cur.expires - monotime(NULL),
+ &c->t_timeout);
+ break;
+ }
+}
+
+static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg)
+{
+ struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid);
+ char buf[3][SU_ADDRSTRLEN];
+
+ debugf(NHRP_DEBUG_COMMON, "cache: %s %pSU: %s", c->ifp->name,
+ &c->remote_addr, (const char *)arg);
+
+ nhrp_reqid_free(&nhrp_event_reqid, r);
+
+ if (arg && strcmp(arg, "accept") == 0) {
+ if (c->cur.peer) {
+ netlink_update_binding(c->cur.peer->ifp,
+ &c->remote_addr, NULL);
+ nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier);
+ nhrp_peer_unref(c->cur.peer);
+ }
+ nhrp_cache_counts[c->cur.type]--;
+ nhrp_cache_counts[c->new.type]++;
+ c->cur = c->new;
+ c->cur.peer = nhrp_peer_ref(c->cur.peer);
+ nhrp_cache_reset_new(c);
+ if (c->cur.peer)
+ nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier,
+ nhrp_cache_peer_notifier);
+
+ if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: update binding for %pSU dev %s from (deleted) peer.vc.nbma %s to %pSU",
+ &c->remote_addr, c->ifp->name,
+ (c->cur.peer ? sockunion2str(
+ &c->cur.peer->vc->remote.nbma, buf[1],
+ sizeof(buf[1]))
+ : "(no peer)"),
+ &c->cur.remote_nbma_natoa);
+
+ if (c->cur.peer)
+ netlink_update_binding(
+ c->cur.peer->ifp, &c->remote_addr,
+ &c->cur.remote_nbma_natoa);
+ }
+
+ nhrp_cache_update_route(c);
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE);
+ } else {
+ nhrp_cache_reset_new(c);
+ }
+
+ nhrp_cache_update_timers(c);
+}
+
+static void nhrp_cache_do_auth_timeout(struct thread *t)
+{
+ struct nhrp_cache *c = THREAD_ARG(t);
+ c->t_auth = NULL;
+ nhrp_cache_authorize_binding(&c->eventid, (void *)"timeout");
+}
+
+static void nhrp_cache_newpeer_notifier(struct notifier_block *n,
+ unsigned long cmd)
+{
+ struct nhrp_cache *c =
+ container_of(n, struct nhrp_cache, newpeer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ if (nhrp_peer_check(c->new.peer, 1)) {
+ evmgr_notify("authorize-binding", c,
+ nhrp_cache_authorize_binding);
+ thread_add_timer(master, nhrp_cache_do_auth_timeout, c,
+ 10, &c->t_auth);
+ }
+ break;
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ nhrp_cache_reset_new(c);
+ break;
+ }
+}
+
+int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type,
+ int holding_time, struct nhrp_peer *p,
+ uint32_t mtu, union sockunion *nbma_oa,
+ union sockunion *nbma_claimed)
+{
+ char buf[2][SU_ADDRSTRLEN];
+
+ if (c->cur.type > type || c->new.type > type) {
+ nhrp_peer_unref(p);
+ return 0;
+ }
+
+ /* Sanitize MTU */
+ switch (sockunion_family(&c->remote_addr)) {
+ case AF_INET:
+ if (mtu < 576 || mtu >= 1500)
+ mtu = 0;
+ /* Opennhrp announces nbma mtu, but we use protocol mtu.
+ * This heuristic tries to fix up it. */
+ if (mtu > 1420)
+ mtu = (mtu & -16) - 80;
+ break;
+ default:
+ mtu = 0;
+ break;
+ }
+
+ sockunion2str(&c->cur.remote_nbma_natoa, buf[0], sizeof(buf[0]));
+ if (nbma_oa)
+ sockunion2str(nbma_oa, buf[1], sizeof(buf[1]));
+
+ nhrp_cache_reset_new(c);
+ if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: same type %u, updating expiry and changing nbma addr from %s to %s",
+ type, buf[0], nbma_oa ? buf[1] : "(NULL)");
+ if (holding_time > 0)
+ c->cur.expires = monotime(NULL) + holding_time;
+
+ if (nbma_oa)
+ c->cur.remote_nbma_natoa = *nbma_oa;
+ else
+ memset(&c->cur.remote_nbma_natoa, 0,
+ sizeof(c->cur.remote_nbma_natoa));
+
+ if (nbma_claimed)
+ c->cur.remote_nbma_claimed = *nbma_claimed;
+ else
+ memset(&c->cur.remote_nbma_claimed, 0,
+ sizeof(c->cur.remote_nbma_claimed));
+
+ nhrp_peer_unref(p);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "cache: new type %u/%u, or peer %s, or mtu %u/%u, nbma %s --> %s (map %d)",
+ c->cur.type, type, (c->cur.peer == p) ? "same" : "diff",
+ c->cur.mtu, mtu, buf[0], nbma_oa ? buf[1] : "(NULL)",
+ c->map);
+ c->new.type = type;
+ c->new.peer = p;
+ c->new.mtu = mtu;
+ c->new.holding_time = holding_time;
+ if (nbma_oa)
+ c->new.remote_nbma_natoa = *nbma_oa;
+
+ if (nbma_claimed)
+ c->new.remote_nbma_claimed = *nbma_claimed;
+
+ if (holding_time > 0)
+ c->new.expires = monotime(NULL) + holding_time;
+ else if (holding_time < 0)
+ nhrp_cache_reset_new(c);
+
+ if (c->new.type == NHRP_CACHE_INVALID
+ || c->new.type >= NHRP_CACHE_STATIC || c->map) {
+ nhrp_cache_authorize_binding(&c->eventid,
+ (void *)"accept");
+ } else {
+ nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier,
+ nhrp_cache_newpeer_notifier);
+ nhrp_cache_newpeer_notifier(&c->newpeer_notifier,
+ NOTIFY_PEER_UP);
+ thread_add_timer(master, nhrp_cache_do_auth_timeout, c,
+ 60, &c->t_auth);
+ }
+ }
+ nhrp_cache_update_timers(c);
+
+ return 1;
+}
+
+void nhrp_cache_set_used(struct nhrp_cache *c, int used)
+{
+ c->used = used;
+ if (c->used)
+ notifier_call(&c->notifier_list, NOTIFY_CACHE_USED);
+}
+
+struct nhrp_cache_iterator_ctx {
+ void (*cb)(struct nhrp_cache *, void *);
+ void *ctx;
+};
+
+struct nhrp_cache_config_iterator_ctx {
+ void (*cb)(struct nhrp_cache_config *, void *);
+ void *ctx;
+};
+
+static void nhrp_cache_iterator(struct hash_bucket *b, void *ctx)
+{
+ struct nhrp_cache_iterator_ctx *ic = ctx;
+ ic->cb(b->data, ic->ctx);
+}
+
+static void nhrp_cache_config_iterator(struct hash_bucket *b, void *ctx)
+{
+ struct nhrp_cache_config_iterator_ctx *ic = ctx;
+ ic->cb(b->data, ic->ctx);
+}
+
+void nhrp_cache_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache *, void *), void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_iterator_ctx ic = {
+ .cb = cb, .ctx = ctx,
+ };
+
+ if (nifp->cache_hash)
+ hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic);
+}
+
+void nhrp_cache_config_foreach(struct interface *ifp,
+ void (*cb)(struct nhrp_cache_config *, void *), void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_cache_config_iterator_ctx ic = {
+ .cb = cb, .ctx = ctx,
+ };
+
+ if (nifp->cache_config_hash)
+ hash_iterate(nifp->cache_config_hash, nhrp_cache_config_iterator, &ic);
+}
+
+void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n,
+ notifier_fn_t fn)
+{
+ notifier_add(n, &c->notifier_list, fn);
+}
+
+void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n)
+{
+ notifier_del(n, &c->notifier_list);
+}