/* 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); }