summaryrefslogtreecommitdiffstats
path: root/nhrpd/nhrp_shortcut.c
diff options
context:
space:
mode:
Diffstat (limited to 'nhrpd/nhrp_shortcut.c')
-rw-r--r--nhrpd/nhrp_shortcut.c543
1 files changed, 543 insertions, 0 deletions
diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c
new file mode 100644
index 0000000..4975aca
--- /dev/null
+++ b/nhrpd/nhrp_shortcut.c
@@ -0,0 +1,543 @@
+/* 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "nhrpd.h"
+#include "table.h"
+#include "memory.h"
+#include "thread.h"
+#include "log.h"
+#include "nhrp_protocol.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut");
+
+static struct route_table *shortcut_rib[AFI_MAX];
+
+static void 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)
+{
+ if (s->expiring && s->cache && s->cache->used) {
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX used and expiring",
+ s->p);
+ nhrp_shortcut_send_resolution_req(s);
+ }
+}
+
+static void nhrp_shortcut_do_expire(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, s->holding_time / 3,
+ &s->t_timer);
+ s->expiring = 1;
+ nhrp_shortcut_check_use(s);
+}
+
+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);
+ struct nhrp_cache *c = s->cache;
+
+ switch (cmd) {
+ case NOTIFY_CACHE_UP:
+ if (!s->route_installed) {
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: route install %pFX nh %pSU dev %s",
+ s->p, &c->remote_addr,
+ c && c->ifp ? c->ifp->name : "<unk>");
+
+ nhrp_route_announce(1, s->type, s->p, c ? c->ifp : NULL,
+ c ? &c->remote_addr : NULL, 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;
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: forcing renewal of zebra announce on prefix change peer %pSU ht %u cur nbma %pSU dev %s",
+ &s->cache->remote_addr, holding_time,
+ &s->cache->cur.remote_nbma_natoa,
+ s->cache->ifp->name);
+ nhrp_shortcut_cache_notify(&s->cache_notifier,
+ NOTIFY_CACHE_UP);
+ }
+ }
+ if (!s->cache || !s->cache->route_installed) {
+ debugf(NHRP_DEBUG_ROUTE,
+ "Shortcut: notify cache down because cache?%s or ri?%s",
+ s->cache ? "yes" : "no",
+ s->cache ? (s->cache->route_installed ? "yes"
+ : "no")
+ : "n/a");
+ 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_add_timer(master, nhrp_shortcut_do_expire, s,
+ 2 * holding_time / 3, &s->t_timer);
+ }
+}
+
+static void nhrp_shortcut_delete(struct nhrp_shortcut *s)
+{
+ struct route_node *rn;
+ afi_t afi = family2afi(PREFIX_FAMILY(s->p));
+
+ THREAD_OFF(s->t_timer);
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+
+ debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX purged", s->p);
+
+ 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 void nhrp_shortcut_do_purge(struct thread *t)
+{
+ struct nhrp_shortcut *s = THREAD_ARG(t);
+ s->t_timer = NULL;
+ nhrp_shortcut_delete(s);
+}
+
+static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p)
+{
+ struct nhrp_shortcut *s;
+ struct route_node *rn;
+ 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 %pFX created", s->p);
+ } 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 interface *ifp = pp->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ 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;
+ struct nhrp_cache *c_dst = NULL;
+ union sockunion *proto, cie_proto, *nbma, cie_nbma, nat_nbma;
+ struct prefix prefix, route_prefix;
+ struct zbuf extpl;
+ int holding_time = pp->if_ad->holdtime;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid);
+ THREAD_OFF(s->t_timer);
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, 1, &s->t_timer);
+
+ 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;
+ }
+
+ /* 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 %pSU to %pSU",
+ &cie_proto, &pp->dst_proto);
+ ;
+ }
+
+ /* 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 == 0) {
+ 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;
+ }
+
+ /* 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: {
+ struct nhrp_cie_header *cie_nat;
+
+ do {
+ union sockunion cie_nat_proto, cie_nat_nbma;
+
+ sockunion_family(&cie_nat_proto) = AF_UNSPEC;
+ sockunion_family(&cie_nat_nbma) = AF_UNSPEC;
+ cie_nat = nhrp_cie_pull(&extpl, pp->hdr,
+ &cie_nat_nbma,
+ &cie_nat_proto);
+ /* We are interested only in peer CIE */
+ if (cie_nat
+ && sockunion_same(&cie_nat_proto, proto)) {
+ nat_nbma = cie_nat_nbma;
+ }
+ } while (cie_nat);
+ } break;
+ default:
+ break;
+ }
+ }
+
+ /* Update cache entry for the protocol to nbma binding */
+ if (sockunion_family(&nat_nbma) != AF_UNSPEC) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: NAT detected (NAT extension) proto %pSU NBMA %pSU claimed-NBMA %pSU",
+ proto, &nat_nbma, &cie_nbma);
+ nbma = &nat_nbma;
+ }
+ /* For NHRP resolution reply the cie_nbma in mandatory part is the
+ * address of the actual address of the sender
+ */
+ else if (!sockunion_same(&cie_nbma, &pp->peer->vc->remote.nbma)
+ && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, nifp)) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: NAT detected (no NAT Extension) proto %pSU NBMA %pSU claimed-NBMA %pSU",
+ proto, &pp->peer->vc->remote.nbma, &cie_nbma);
+ nbma = &pp->peer->vc->remote.nbma;
+ nat_nbma = *nbma;
+ } else {
+ nbma = &cie_nbma;
+ }
+
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: %pFX is at proto %pSU dst_proto %pSU NBMA %pSU cie-holdtime %d",
+ &prefix, proto, &pp->dst_proto, nbma,
+ htons(cie->holding_time));
+
+ if (sockunion_family(nbma)) {
+ c = nhrp_cache_get(pp->ifp, proto, 1);
+ if (c) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: cache found, update binding");
+ nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC,
+ holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu),
+ nbma,
+ &cie_nbma);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: no cache for proto %pSU", proto);
+ }
+
+ /* Update cache binding for dst_proto as well */
+ if (sockunion_cmp(proto, &pp->dst_proto)) {
+ c_dst = nhrp_cache_get(pp->ifp, &pp->dst_proto, 1);
+ if (c_dst) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: cache found, update binding");
+ nhrp_cache_update_binding(c_dst,
+ NHRP_CACHE_DYNAMIC,
+ holding_time,
+ nhrp_peer_get(pp->ifp, nbma),
+ htons(cie->mtu),
+ nbma,
+ &cie_nbma);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: no cache for proto %pSU",
+ &pp->dst_proto);
+ }
+ }
+ }
+
+ /* Update shortcut entry for subnet to protocol gw binding */
+ if (c) {
+ ps = nhrp_shortcut_get(&prefix);
+ if (ps) {
+ ps->addr = s->addr;
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: calling update_binding");
+ nhrp_shortcut_update_binding(ps, NHRP_CACHE_DYNAMIC, c,
+ holding_time);
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut: proto diff but no ps");
+ }
+ } else {
+ debugf(NHRP_DEBUG_COMMON,
+ "NO Shortcut because c NULL?%s or same proto?%s",
+ c ? "no" : "yes",
+ proto && pp && sockunion_same(proto, &pp->dst_proto)
+ ? "yes"
+ : "no");
+ }
+
+ 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_afi_data *if_ad;
+ struct nhrp_peer *peer;
+ struct nhrp_cie_header *cie;
+ struct nhrp_extension_header *ext;
+
+ 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: push CIE for each local protocol address */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL);
+ if_ad = &nifp->afi[family2afi(sockunion_family(&s->addr))];
+ cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)
+ ? 8 * sockunion_get_addrlen(&s->addr)
+ : 0xff;
+ cie->holding_time = htons(if_ad->holdtime);
+ cie->mtu = htons(if_ad->mtu);
+ debugf(NHRP_DEBUG_COMMON,
+ "Shortcut res_req: set cie ht to %u and mtu to %u. shortcut ht is %u",
+ ntohs(cie->holding_time), ntohs(cie->mtu), s->holding_time);
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT);
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) {
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma,
+ &if_ad->addr);
+ cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr);
+ cie->mtu = htons(if_ad->mtu);
+ nhrp_ext_complete(zb, ext);
+ }
+
+ 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;
+
+ if (!sockunion2hostprefix(addr, &p))
+ return;
+
+ s = nhrp_shortcut_get(&p);
+ if (s && s->type != NHRP_CACHE_INCOMPLETE) {
+ s->addr = *addr;
+ THREAD_OFF(s->t_timer);
+ thread_add_timer(master, nhrp_shortcut_do_purge, s, 30,
+ &s->t_timer);
+ 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_add_timer_msec(master, nhrp_shortcut_do_purge, s, 5,
+ &s->t_timer);
+ } 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_add_timer_msec(master, nhrp_shortcut_do_purge, s, 3000,
+ &s->t_timer);
+ 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);
+}