Timo Teräs | dafa05e | 2017-01-19 17:27:01 +0200 | [diff] [blame] | 1 | /* NHRP shortcut related functions |
| 2 | * Copyright (c) 2014-2015 Timo Teräs |
| 3 | * |
| 4 | * This file is free software: you may copy, redistribute and/or modify |
| 5 | * it under the terms of the GNU General Public License as published by |
| 6 | * the Free Software Foundation, either version 2 of the License, or |
| 7 | * (at your option) any later version. |
| 8 | */ |
| 9 | |
| 10 | #include "nhrpd.h" |
| 11 | #include "table.h" |
| 12 | #include "memory.h" |
| 13 | #include "thread.h" |
| 14 | #include "log.h" |
| 15 | #include "nhrp_protocol.h" |
| 16 | |
| 17 | static struct route_table *shortcut_rib[AFI_MAX]; |
| 18 | |
| 19 | static int nhrp_shortcut_do_purge(struct thread *t); |
| 20 | static void nhrp_shortcut_delete(struct nhrp_shortcut *s); |
| 21 | static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s); |
| 22 | |
| 23 | static void nhrp_shortcut_check_use(struct nhrp_shortcut *s) |
| 24 | { |
| 25 | char buf[PREFIX_STRLEN]; |
| 26 | |
| 27 | if (s->expiring && s->cache && s->cache->used) { |
| 28 | debugf(NHRP_DEBUG_ROUTE, "Shortcut %s used and expiring", |
| 29 | prefix2str(s->p, buf, sizeof buf)); |
| 30 | nhrp_shortcut_send_resolution_req(s); |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | static int nhrp_shortcut_do_expire(struct thread *t) |
| 35 | { |
| 36 | struct nhrp_shortcut *s = THREAD_ARG(t); |
| 37 | |
| 38 | s->t_timer = NULL; |
| 39 | THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, s->holding_time/3); |
| 40 | s->expiring = 1; |
| 41 | nhrp_shortcut_check_use(s); |
| 42 | |
| 43 | return 0; |
| 44 | } |
| 45 | |
| 46 | static void nhrp_shortcut_cache_notify(struct notifier_block *n, unsigned long cmd) |
| 47 | { |
| 48 | struct nhrp_shortcut *s = container_of(n, struct nhrp_shortcut, cache_notifier); |
| 49 | |
| 50 | switch (cmd) { |
| 51 | case NOTIFY_CACHE_UP: |
| 52 | if (!s->route_installed) { |
| 53 | nhrp_route_announce(1, s->type, s->p, NULL, &s->cache->remote_addr, 0); |
| 54 | s->route_installed = 1; |
| 55 | } |
| 56 | break; |
| 57 | case NOTIFY_CACHE_USED: |
| 58 | nhrp_shortcut_check_use(s); |
| 59 | break; |
| 60 | case NOTIFY_CACHE_DOWN: |
| 61 | case NOTIFY_CACHE_DELETE: |
| 62 | if (s->route_installed) { |
| 63 | nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); |
| 64 | s->route_installed = 0; |
| 65 | } |
| 66 | if (cmd == NOTIFY_CACHE_DELETE) |
| 67 | nhrp_shortcut_delete(s); |
| 68 | break; |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, enum nhrp_cache_type type, struct nhrp_cache *c, int holding_time) |
| 73 | { |
| 74 | s->type = type; |
| 75 | if (c != s->cache) { |
| 76 | if (s->cache) { |
| 77 | nhrp_cache_notify_del(s->cache, &s->cache_notifier); |
| 78 | s->cache = NULL; |
| 79 | } |
| 80 | s->cache = c; |
| 81 | if (s->cache) { |
| 82 | nhrp_cache_notify_add(s->cache, &s->cache_notifier, nhrp_shortcut_cache_notify); |
| 83 | if (s->cache->route_installed) { |
| 84 | /* Force renewal of Zebra announce on prefix change */ |
| 85 | s->route_installed = 0; |
| 86 | nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_UP); |
| 87 | } |
| 88 | } |
| 89 | if (!s->cache || !s->cache->route_installed) |
| 90 | nhrp_shortcut_cache_notify(&s->cache_notifier, NOTIFY_CACHE_DOWN); |
| 91 | } |
| 92 | if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) { |
| 93 | nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0); |
| 94 | s->route_installed = 1; |
| 95 | } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) { |
| 96 | nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); |
| 97 | s->route_installed = 0; |
| 98 | } |
| 99 | |
| 100 | THREAD_OFF(s->t_timer); |
| 101 | if (holding_time) { |
| 102 | s->expiring = 0; |
| 103 | s->holding_time = holding_time; |
| 104 | THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_expire, s, 2*holding_time/3); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | static void nhrp_shortcut_delete(struct nhrp_shortcut *s) |
| 109 | { |
| 110 | struct route_node *rn; |
| 111 | afi_t afi = family2afi(PREFIX_FAMILY(s->p)); |
| 112 | char buf[PREFIX_STRLEN]; |
| 113 | |
| 114 | THREAD_OFF(s->t_timer); |
| 115 | nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); |
| 116 | |
| 117 | debugf(NHRP_DEBUG_ROUTE, "Shortcut %s purged", |
| 118 | prefix2str(s->p, buf, sizeof buf)); |
| 119 | |
| 120 | nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0); |
| 121 | |
| 122 | /* Delete node */ |
| 123 | rn = route_node_lookup(shortcut_rib[afi], s->p); |
| 124 | if (rn) { |
| 125 | XFREE(MTYPE_NHRP_SHORTCUT, rn->info); |
| 126 | rn->info = NULL; |
| 127 | route_unlock_node(rn); |
| 128 | route_unlock_node(rn); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | static int nhrp_shortcut_do_purge(struct thread *t) |
| 133 | { |
| 134 | struct nhrp_shortcut *s = THREAD_ARG(t); |
| 135 | s->t_timer = NULL; |
| 136 | nhrp_shortcut_delete(s); |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p) |
| 141 | { |
| 142 | struct nhrp_shortcut *s; |
| 143 | struct route_node *rn; |
| 144 | char buf[PREFIX_STRLEN]; |
| 145 | afi_t afi = family2afi(PREFIX_FAMILY(p)); |
| 146 | |
| 147 | if (!shortcut_rib[afi]) |
| 148 | return 0; |
| 149 | |
| 150 | rn = route_node_get(shortcut_rib[afi], p); |
| 151 | if (!rn->info) { |
| 152 | s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, sizeof(struct nhrp_shortcut)); |
| 153 | s->type = NHRP_CACHE_INVALID; |
| 154 | s->p = &rn->p; |
| 155 | |
| 156 | debugf(NHRP_DEBUG_ROUTE, "Shortcut %s created", |
| 157 | prefix2str(s->p, buf, sizeof buf)); |
| 158 | } else { |
| 159 | s = rn->info; |
| 160 | route_unlock_node(rn); |
| 161 | } |
| 162 | return s; |
| 163 | } |
| 164 | |
| 165 | static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, void *arg) |
| 166 | { |
| 167 | struct nhrp_packet_parser *pp = arg; |
| 168 | struct nhrp_shortcut *s = container_of(reqid, struct nhrp_shortcut, reqid); |
| 169 | struct nhrp_shortcut *ps; |
| 170 | struct nhrp_extension_header *ext; |
| 171 | struct nhrp_cie_header *cie; |
| 172 | struct nhrp_cache *c = NULL; |
| 173 | union sockunion *proto, cie_proto, *nbma, *nbma_natoa, cie_nbma, nat_nbma; |
| 174 | struct prefix prefix, route_prefix; |
| 175 | struct zbuf extpl; |
| 176 | char bufp[PREFIX_STRLEN], buf[3][SU_ADDRSTRLEN]; |
| 177 | int holding_time = pp->if_ad->holdtime; |
| 178 | |
| 179 | nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); |
| 180 | THREAD_OFF(s->t_timer); |
| 181 | THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 1); |
| 182 | |
| 183 | if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) { |
| 184 | if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION && |
| 185 | pp->hdr->u.error.code == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) { |
| 186 | debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution: Protocol address unreachable"); |
| 187 | nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, NULL, holding_time); |
| 188 | } else { |
| 189 | debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution failed"); |
| 190 | } |
| 191 | return; |
| 192 | } |
| 193 | |
| 194 | /* Parse extensions */ |
| 195 | memset(&nat_nbma, 0, sizeof nat_nbma); |
| 196 | while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { |
| 197 | switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { |
| 198 | case NHRP_EXTENSION_NAT_ADDRESS: |
| 199 | nhrp_cie_pull(&extpl, pp->hdr, &nat_nbma, &cie_proto); |
| 200 | break; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | /* Minor sanity check */ |
| 205 | prefix2sockunion(s->p, &cie_proto); |
| 206 | if (!sockunion_same(&cie_proto, &pp->dst_proto)) { |
| 207 | debugf(NHRP_DEBUG_COMMON, "Shortcut: Warning dst_proto altered from %s to %s", |
| 208 | sockunion2str(&cie_proto, buf[0], sizeof buf[0]), |
| 209 | sockunion2str(&pp->dst_proto, buf[1], sizeof buf[1])); |
| 210 | } |
| 211 | |
| 212 | /* One or more CIEs should be given as reply, we support only one */ |
| 213 | cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto); |
| 214 | if (!cie || cie->code != NHRP_CODE_SUCCESS) { |
| 215 | debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", cie ? cie->code : -1); |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &pp->dst_proto; |
| 220 | if (cie->holding_time) |
| 221 | holding_time = htons(cie->holding_time); |
| 222 | |
| 223 | prefix = *s->p; |
| 224 | prefix.prefixlen = cie->prefix_length; |
| 225 | |
| 226 | /* Sanity check prefix length */ |
| 227 | if (prefix.prefixlen >= 8*prefix_blen(&prefix)) { |
| 228 | prefix.prefixlen = 8*prefix_blen(&prefix); |
| 229 | } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) == NHRP_ROUTE_NBMA_NEXTHOP) { |
| 230 | if (prefix.prefixlen < route_prefix.prefixlen) |
| 231 | prefix.prefixlen = route_prefix.prefixlen; |
| 232 | } |
| 233 | |
| 234 | debugf(NHRP_DEBUG_COMMON, "Shortcut: %s is at proto %s cie-nbma %s nat-nbma %s cie-holdtime %d", |
| 235 | prefix2str(&prefix, bufp, sizeof bufp), |
| 236 | sockunion2str(proto, buf[0], sizeof buf[0]), |
| 237 | sockunion2str(&cie_nbma, buf[1], sizeof buf[1]), |
| 238 | sockunion2str(&nat_nbma, buf[2], sizeof buf[2]), |
| 239 | htons(cie->holding_time)); |
| 240 | |
| 241 | /* Update cache entry for the protocol to nbma binding */ |
| 242 | if (sockunion_family(&nat_nbma) != AF_UNSPEC) { |
| 243 | nbma = &nat_nbma; |
| 244 | nbma_natoa = &cie_nbma; |
| 245 | } else { |
| 246 | nbma = &cie_nbma; |
| 247 | nbma_natoa = NULL; |
| 248 | } |
| 249 | if (sockunion_family(nbma)) { |
| 250 | c = nhrp_cache_get(pp->ifp, proto, 1); |
| 251 | if (c) { |
| 252 | nhrp_cache_update_binding( |
| 253 | c, NHRP_CACHE_CACHED, holding_time, |
| 254 | nhrp_peer_get(pp->ifp, nbma), |
| 255 | htons(cie->mtu), nbma_natoa); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | /* Update shortcut entry for subnet to protocol gw binding */ |
| 260 | if (c && !sockunion_same(proto, &pp->dst_proto)) { |
| 261 | ps = nhrp_shortcut_get(&prefix); |
| 262 | if (ps) { |
| 263 | ps->addr = s->addr; |
| 264 | nhrp_shortcut_update_binding(ps, NHRP_CACHE_CACHED, c, holding_time); |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled"); |
| 269 | } |
| 270 | |
| 271 | static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s) |
| 272 | { |
| 273 | struct zbuf *zb; |
| 274 | struct nhrp_packet_header *hdr; |
| 275 | struct interface *ifp; |
| 276 | struct nhrp_interface *nifp; |
| 277 | struct nhrp_peer *peer; |
| 278 | |
| 279 | if (nhrp_route_address(NULL, &s->addr, NULL, &peer) != NHRP_ROUTE_NBMA_NEXTHOP) |
| 280 | return; |
| 281 | |
| 282 | if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE) |
| 283 | s->type = NHRP_CACHE_INCOMPLETE; |
| 284 | |
| 285 | ifp = peer->ifp; |
| 286 | nifp = ifp->info; |
| 287 | |
| 288 | /* Create request */ |
| 289 | zb = zbuf_alloc(1500); |
| 290 | hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REQUEST, |
| 291 | &nifp->nbma, &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, &s->addr); |
| 292 | hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, nhrp_shortcut_recv_resolution_rep)); |
| 293 | hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER | |
| 294 | NHRP_FLAG_RESOLUTION_AUTHORATIVE | |
| 295 | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); |
| 296 | |
| 297 | /* RFC2332 - One or zero CIEs, if CIE is present contains: |
| 298 | * - Prefix length: widest acceptable prefix we accept (if U set, 0xff) |
| 299 | * - MTU: MTU of the source station |
| 300 | * - Holding Time: Max time to cache the source information |
| 301 | * */ |
| 302 | /* FIXME: Send holding time, and MTU */ |
| 303 | |
| 304 | nhrp_ext_request(zb, hdr, ifp); |
| 305 | |
| 306 | /* Cisco NAT detection extension */ |
| 307 | hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT); |
| 308 | nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); |
| 309 | |
| 310 | nhrp_packet_complete(zb, hdr); |
| 311 | |
| 312 | nhrp_peer_send(peer, zb); |
| 313 | nhrp_peer_unref(peer); |
| 314 | zbuf_free(zb); |
| 315 | } |
| 316 | |
| 317 | void nhrp_shortcut_initiate(union sockunion *addr) |
| 318 | { |
| 319 | struct prefix p; |
| 320 | struct nhrp_shortcut *s; |
| 321 | |
| 322 | sockunion2hostprefix(addr, &p); |
| 323 | s = nhrp_shortcut_get(&p); |
| 324 | if (s && s->type != NHRP_CACHE_INCOMPLETE) { |
| 325 | s->addr = *addr; |
| 326 | THREAD_OFF(s->t_timer); |
| 327 | THREAD_TIMER_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 30); |
| 328 | nhrp_shortcut_send_resolution_req(s); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | void nhrp_shortcut_init(void) |
| 333 | { |
| 334 | shortcut_rib[AFI_IP] = route_table_init(); |
| 335 | shortcut_rib[AFI_IP6] = route_table_init(); |
| 336 | } |
| 337 | |
| 338 | void nhrp_shortcut_terminate(void) |
| 339 | { |
| 340 | route_table_finish(shortcut_rib[AFI_IP]); |
| 341 | route_table_finish(shortcut_rib[AFI_IP6]); |
| 342 | } |
| 343 | |
| 344 | void nhrp_shortcut_foreach(afi_t afi, void (*cb)(struct nhrp_shortcut *, void *), void *ctx) |
| 345 | { |
| 346 | struct route_table *rt = shortcut_rib[afi]; |
| 347 | struct route_node *rn; |
| 348 | route_table_iter_t iter; |
| 349 | |
| 350 | if (!rt) return; |
| 351 | |
| 352 | route_table_iter_init(&iter, rt); |
| 353 | while ((rn = route_table_iter_next(&iter)) != NULL) { |
| 354 | if (rn->info) cb(rn->info, ctx); |
| 355 | } |
| 356 | route_table_iter_cleanup(&iter); |
| 357 | } |
| 358 | |
| 359 | struct purge_ctx { |
| 360 | const struct prefix *p; |
| 361 | int deleted; |
| 362 | }; |
| 363 | |
| 364 | void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force) |
| 365 | { |
| 366 | THREAD_OFF(s->t_timer); |
| 367 | nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); |
| 368 | |
| 369 | if (force) { |
| 370 | /* Immediate purge on route with draw or pending shortcut */ |
| 371 | THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 5); |
| 372 | } else { |
| 373 | /* Soft expire - force immediate renewal, but purge |
| 374 | * in few seconds to make sure stale route is not |
| 375 | * used too long. In practice most purges are caused |
| 376 | * by hub bgp change, but target usually stays same. |
| 377 | * This allows to keep nhrp route up, and to not |
| 378 | * cause temporary rerouting via hubs causing latency |
| 379 | * jitter. */ |
| 380 | THREAD_TIMER_MSEC_ON(master, s->t_timer, nhrp_shortcut_do_purge, s, 3000); |
| 381 | s->expiring = 1; |
| 382 | nhrp_shortcut_check_use(s); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx) |
| 387 | { |
| 388 | struct purge_ctx *pctx = ctx; |
| 389 | |
| 390 | if (prefix_match(pctx->p, s->p)) |
| 391 | nhrp_shortcut_purge(s, pctx->deleted || !s->cache); |
| 392 | } |
| 393 | |
| 394 | void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted) |
| 395 | { |
| 396 | struct purge_ctx pctx = { |
| 397 | .p = p, |
| 398 | .deleted = deleted, |
| 399 | }; |
| 400 | nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), nhrp_shortcut_purge_prefix, &pctx); |
| 401 | } |
| 402 | |