Timo Teräs | dafa05e | 2017-01-19 17:27:01 +0200 | [diff] [blame] | 1 | /* NHRP NHC nexthop server functions (registration) |
| 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 "zebra.h" |
| 11 | #include "zbuf.h" |
| 12 | #include "memory.h" |
| 13 | #include "thread.h" |
| 14 | #include "nhrpd.h" |
| 15 | #include "nhrp_protocol.h" |
| 16 | |
| 17 | static int nhrp_nhs_resolve(struct thread *t); |
| 18 | |
| 19 | struct nhrp_registration { |
| 20 | struct list_head reglist_entry; |
| 21 | struct thread *t_register; |
| 22 | struct nhrp_nhs *nhs; |
| 23 | struct nhrp_reqid reqid; |
| 24 | unsigned int timeout; |
| 25 | unsigned mark : 1; |
| 26 | union sockunion proto_addr; |
| 27 | struct nhrp_peer *peer; |
| 28 | struct notifier_block peer_notifier; |
| 29 | }; |
| 30 | |
| 31 | static int nhrp_reg_send_req(struct thread *t); |
| 32 | |
| 33 | static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg) |
| 34 | { |
| 35 | struct nhrp_packet_parser *p = arg; |
| 36 | struct nhrp_registration *r = container_of(reqid, struct nhrp_registration, reqid); |
| 37 | struct nhrp_nhs *nhs = r->nhs; |
| 38 | struct interface *ifp = nhs->ifp; |
| 39 | struct nhrp_interface *nifp = ifp->info; |
| 40 | struct nhrp_extension_header *ext; |
| 41 | struct nhrp_cie_header *cie; |
| 42 | struct nhrp_cache *c; |
| 43 | struct zbuf extpl; |
| 44 | union sockunion cie_nbma, cie_proto, *proto; |
| 45 | char buf[64]; |
| 46 | int ok = 0, holdtime; |
| 47 | |
| 48 | nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); |
| 49 | |
| 50 | if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) { |
| 51 | debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed"); |
| 52 | return; |
| 53 | } |
| 54 | |
| 55 | debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received"); |
| 56 | |
| 57 | ok = 1; |
| 58 | while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto)) != NULL) { |
| 59 | proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto : &p->src_proto; |
| 60 | debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %s: %d", |
| 61 | sockunion2str(proto, buf, sizeof(buf)), |
| 62 | cie->code); |
| 63 | if (!((cie->code == NHRP_CODE_SUCCESS) || |
| 64 | (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED && nhs->hub))) |
| 65 | ok = 0; |
| 66 | } |
| 67 | |
| 68 | if (!ok) |
| 69 | return; |
| 70 | |
| 71 | /* Parse extensions */ |
| 72 | sockunion_family(&nifp->nat_nbma) = AF_UNSPEC; |
| 73 | while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) { |
| 74 | switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { |
| 75 | case NHRP_EXTENSION_NAT_ADDRESS: |
| 76 | /* NHS adds second CIE if NAT is detected */ |
| 77 | if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto) && |
| 78 | nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto)) { |
| 79 | nifp->nat_nbma = cie_nbma; |
| 80 | debugf(NHRP_DEBUG_IF, "%s: NAT detected, real NBMA address: %s", |
| 81 | ifp->name, sockunion2str(&nifp->nbma, buf, sizeof(buf))); |
| 82 | } |
| 83 | break; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /* Success - schedule next registration, and route NHS */ |
| 88 | r->timeout = 2; |
| 89 | holdtime = nifp->afi[nhs->afi].holdtime; |
| 90 | THREAD_OFF(r->t_register); |
| 91 | |
| 92 | /* RFC 2332 5.2.3 - Registration is recommend to be renewed |
| 93 | * every one third of holdtime */ |
| 94 | THREAD_TIMER_ON(master, r->t_register, nhrp_reg_send_req, r, holdtime / 3); |
| 95 | |
| 96 | r->proto_addr = p->dst_proto; |
| 97 | c = nhrp_cache_get(ifp, &p->dst_proto, 1); |
| 98 | if (c) nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime, nhrp_peer_ref(r->peer), 0, NULL); |
| 99 | } |
| 100 | |
| 101 | static int nhrp_reg_timeout(struct thread *t) |
| 102 | { |
| 103 | struct nhrp_registration *r = THREAD_ARG(t); |
| 104 | struct nhrp_cache *c; |
| 105 | |
| 106 | r->t_register = NULL; |
| 107 | |
| 108 | if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) { |
| 109 | nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); |
| 110 | c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0); |
| 111 | if (c) nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL, 0, NULL); |
| 112 | sockunion_family(&r->proto_addr) = AF_UNSPEC; |
| 113 | } |
| 114 | |
| 115 | r->timeout <<= 1; |
| 116 | if (r->timeout > 64) r->timeout = 2; |
| 117 | THREAD_TIMER_MSEC_ON(master, r->t_register, nhrp_reg_send_req, r, 10); |
| 118 | |
| 119 | return 0; |
| 120 | } |
| 121 | |
| 122 | static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd) |
| 123 | { |
| 124 | struct nhrp_registration *r = container_of(n, struct nhrp_registration, peer_notifier); |
| 125 | char buf[SU_ADDRSTRLEN]; |
| 126 | |
| 127 | switch (cmd) { |
| 128 | case NOTIFY_PEER_UP: |
| 129 | case NOTIFY_PEER_DOWN: |
| 130 | case NOTIFY_PEER_IFCONFIG_CHANGED: |
| 131 | case NOTIFY_PEER_MTU_CHANGED: |
| 132 | debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %s", |
| 133 | sockunion2str(&r->peer->vc->remote.nbma, buf, sizeof buf)); |
| 134 | THREAD_TIMER_OFF(r->t_register); |
| 135 | THREAD_TIMER_MSEC_ON(master, r->t_register, nhrp_reg_send_req, r, 10); |
| 136 | break; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | static int nhrp_reg_send_req(struct thread *t) |
| 141 | { |
| 142 | struct nhrp_registration *r = THREAD_ARG(t); |
| 143 | struct nhrp_nhs *nhs = r->nhs; |
| 144 | char buf1[SU_ADDRSTRLEN], buf2[SU_ADDRSTRLEN]; |
| 145 | struct interface *ifp = nhs->ifp; |
| 146 | struct nhrp_interface *nifp = ifp->info; |
| 147 | struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi]; |
| 148 | union sockunion *dst_proto; |
| 149 | struct zbuf *zb; |
| 150 | struct nhrp_packet_header *hdr; |
| 151 | struct nhrp_extension_header *ext; |
| 152 | struct nhrp_cie_header *cie; |
| 153 | |
| 154 | r->t_register = NULL; |
| 155 | if (!nhrp_peer_check(r->peer, 2)) { |
| 156 | debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %s", |
| 157 | sockunion2str(&r->peer->vc->remote.nbma, buf1, sizeof buf1)); |
| 158 | THREAD_TIMER_ON(master, r->t_register, nhrp_reg_send_req, r, 120); |
| 159 | return 0; |
| 160 | } |
| 161 | |
| 162 | THREAD_TIMER_ON(master, r->t_register, nhrp_reg_timeout, r, r->timeout); |
| 163 | |
| 164 | /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */ |
| 165 | dst_proto = &nhs->proto_addr; |
| 166 | if (sockunion_family(dst_proto) == AF_UNSPEC) |
| 167 | dst_proto = &if_ad->addr; |
| 168 | |
| 169 | sockunion2str(&if_ad->addr, buf1, sizeof(buf1)); |
| 170 | sockunion2str(dst_proto, buf2, sizeof(buf2)); |
| 171 | debugf(NHRP_DEBUG_COMMON, "NHS: Register %s -> %s (timeout %d)", buf1, buf2, r->timeout); |
| 172 | |
| 173 | /* No protocol address configured for tunnel interface */ |
| 174 | if (sockunion_family(&if_ad->addr) == AF_UNSPEC) |
| 175 | return 0; |
| 176 | |
| 177 | zb = zbuf_alloc(1400); |
| 178 | hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST, &nifp->nbma, &if_ad->addr, dst_proto); |
| 179 | hdr->hop_count = 0; |
| 180 | if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)) |
| 181 | hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE); |
| 182 | |
| 183 | hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &r->reqid, nhrp_reg_reply)); |
| 184 | |
| 185 | /* FIXME: push CIE for each local protocol address */ |
| 186 | cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); |
| 187 | cie->prefix_length = 0xff; |
| 188 | cie->holding_time = htons(if_ad->holdtime); |
| 189 | cie->mtu = htons(if_ad->mtu); |
| 190 | |
| 191 | nhrp_ext_request(zb, hdr, ifp); |
| 192 | |
| 193 | /* Cisco NAT detection extension */ |
| 194 | hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT); |
| 195 | ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); |
| 196 | cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, &if_ad->addr); |
| 197 | cie->prefix_length = 8 * sockunion_get_addrlen(&nifp->nbma); |
| 198 | nhrp_ext_complete(zb, ext); |
| 199 | |
| 200 | nhrp_packet_complete(zb, hdr); |
| 201 | nhrp_peer_send(r->peer, zb); |
| 202 | zbuf_free(zb); |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | static void nhrp_reg_delete(struct nhrp_registration *r) |
| 208 | { |
| 209 | nhrp_peer_notify_del(r->peer, &r->peer_notifier); |
| 210 | nhrp_peer_unref(r->peer); |
| 211 | list_del(&r->reglist_entry); |
| 212 | THREAD_OFF(r->t_register); |
| 213 | XFREE(MTYPE_NHRP_REGISTRATION, r); |
| 214 | } |
| 215 | |
| 216 | static struct nhrp_registration *nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr) |
| 217 | { |
| 218 | struct nhrp_registration *r; |
| 219 | |
| 220 | list_for_each_entry(r, &nhs->reglist_head, reglist_entry) |
| 221 | if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr)) |
| 222 | return r; |
| 223 | return NULL; |
| 224 | } |
| 225 | |
| 226 | static void nhrp_nhs_resolve_cb(struct resolver_query *q, int n, union sockunion *addrs) |
| 227 | { |
| 228 | struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve); |
| 229 | struct nhrp_interface *nifp = nhs->ifp->info; |
| 230 | struct nhrp_registration *reg, *regn; |
| 231 | int i; |
| 232 | |
| 233 | nhs->t_resolve = NULL; |
| 234 | if (n < 0) { |
| 235 | /* Failed, retry in a moment */ |
| 236 | THREAD_TIMER_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 5); |
| 237 | return; |
| 238 | } |
| 239 | |
| 240 | THREAD_TIMER_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 2*60*60); |
| 241 | |
| 242 | list_for_each_entry(reg, &nhs->reglist_head, reglist_entry) |
| 243 | reg->mark = 1; |
| 244 | |
| 245 | nhs->hub = 0; |
| 246 | for (i = 0; i < n; i++) { |
| 247 | if (sockunion_same(&addrs[i], &nifp->nbma)) { |
| 248 | nhs->hub = 1; |
| 249 | continue; |
| 250 | } |
| 251 | |
| 252 | reg = nhrp_reg_by_nbma(nhs, &addrs[i]); |
| 253 | if (reg) { |
| 254 | reg->mark = 0; |
| 255 | continue; |
| 256 | } |
| 257 | |
| 258 | reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg)); |
| 259 | reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]); |
| 260 | reg->nhs = nhs; |
| 261 | reg->timeout = 1; |
| 262 | list_init(®->reglist_entry); |
| 263 | list_add_tail(®->reglist_entry, &nhs->reglist_head); |
| 264 | nhrp_peer_notify_add(reg->peer, ®->peer_notifier, nhrp_reg_peer_notify); |
| 265 | THREAD_TIMER_MSEC_ON(master, reg->t_register, nhrp_reg_send_req, reg, 50); |
| 266 | } |
| 267 | |
| 268 | list_for_each_entry_safe(reg, regn, &nhs->reglist_head, reglist_entry) { |
| 269 | if (reg->mark) |
| 270 | nhrp_reg_delete(reg); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | static int nhrp_nhs_resolve(struct thread *t) |
| 275 | { |
| 276 | struct nhrp_nhs *nhs = THREAD_ARG(t); |
| 277 | |
| 278 | resolver_resolve(&nhs->dns_resolve, AF_INET, nhs->nbma_fqdn, nhrp_nhs_resolve_cb); |
| 279 | |
| 280 | return 0; |
| 281 | } |
| 282 | |
| 283 | int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn) |
| 284 | { |
| 285 | struct nhrp_interface *nifp = ifp->info; |
| 286 | struct nhrp_nhs *nhs; |
| 287 | |
| 288 | if (sockunion_family(proto_addr) != AF_UNSPEC && |
| 289 | sockunion_family(proto_addr) != afi2family(afi)) |
| 290 | return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; |
| 291 | |
| 292 | list_for_each_entry(nhs, &nifp->afi[afi].nhslist_head, nhslist_entry) { |
| 293 | if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC && |
| 294 | sockunion_family(proto_addr) != AF_UNSPEC && |
| 295 | sockunion_same(&nhs->proto_addr, proto_addr)) |
| 296 | return NHRP_ERR_ENTRY_EXISTS; |
| 297 | |
| 298 | if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0) |
| 299 | return NHRP_ERR_ENTRY_EXISTS; |
| 300 | } |
| 301 | |
| 302 | nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs)); |
| 303 | if (!nhs) return NHRP_ERR_NO_MEMORY; |
| 304 | |
| 305 | *nhs = (struct nhrp_nhs) { |
| 306 | .afi = afi, |
| 307 | .ifp = ifp, |
| 308 | .proto_addr = *proto_addr, |
| 309 | .nbma_fqdn = strdup(nbma_fqdn), |
| 310 | .reglist_head = LIST_INITIALIZER(nhs->reglist_head), |
| 311 | }; |
| 312 | list_add_tail(&nhs->nhslist_entry, &nifp->afi[afi].nhslist_head); |
| 313 | THREAD_TIMER_MSEC_ON(master, nhs->t_resolve, nhrp_nhs_resolve, nhs, 1000); |
| 314 | |
| 315 | return NHRP_OK; |
| 316 | } |
| 317 | |
| 318 | int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, const char *nbma_fqdn) |
| 319 | { |
| 320 | struct nhrp_interface *nifp = ifp->info; |
| 321 | struct nhrp_nhs *nhs, *nnhs; |
| 322 | int ret = NHRP_ERR_ENTRY_NOT_FOUND; |
| 323 | |
| 324 | if (sockunion_family(proto_addr) != AF_UNSPEC && |
| 325 | sockunion_family(proto_addr) != afi2family(afi)) |
| 326 | return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; |
| 327 | |
| 328 | list_for_each_entry_safe(nhs, nnhs, &nifp->afi[afi].nhslist_head, nhslist_entry) { |
| 329 | if (!sockunion_same(&nhs->proto_addr, proto_addr)) |
| 330 | continue; |
| 331 | if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0) |
| 332 | continue; |
| 333 | |
| 334 | nhrp_nhs_free(nhs); |
| 335 | ret = NHRP_OK; |
| 336 | } |
| 337 | |
| 338 | return ret; |
| 339 | } |
| 340 | |
| 341 | int nhrp_nhs_free(struct nhrp_nhs *nhs) |
| 342 | { |
| 343 | struct nhrp_registration *r, *rn; |
| 344 | |
| 345 | list_for_each_entry_safe(r, rn, &nhs->reglist_head, reglist_entry) |
| 346 | nhrp_reg_delete(r); |
| 347 | THREAD_OFF(nhs->t_resolve); |
| 348 | list_del(&nhs->nhslist_entry); |
| 349 | free((void*) nhs->nbma_fqdn); |
| 350 | XFREE(MTYPE_NHRP_NHS, nhs); |
| 351 | return 0; |
| 352 | } |
| 353 | |
| 354 | void nhrp_nhs_terminate(void) |
| 355 | { |
| 356 | struct interface *ifp; |
| 357 | struct nhrp_interface *nifp; |
| 358 | struct nhrp_nhs *nhs, *tmp; |
| 359 | struct listnode *node; |
| 360 | afi_t afi; |
| 361 | |
| 362 | for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) { |
| 363 | nifp = ifp->info; |
| 364 | for (afi = 0; afi < AFI_MAX; afi++) { |
| 365 | list_for_each_entry_safe(nhs, tmp, &nifp->afi[afi].nhslist_head, nhslist_entry) |
| 366 | nhrp_nhs_free(nhs); |
| 367 | } |
| 368 | } |
| 369 | } |