summaryrefslogtreecommitdiffstats
path: root/nhrpd/nhrp_nhs.c
diff options
context:
space:
mode:
Diffstat (limited to 'nhrpd/nhrp_nhs.c')
-rw-r--r--nhrpd/nhrp_nhs.c467
1 files changed, 467 insertions, 0 deletions
diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c
new file mode 100644
index 0000000..03b4b53
--- /dev/null
+++ b/nhrpd/nhrp_nhs.c
@@ -0,0 +1,467 @@
+/* NHRP NHC nexthop server functions (registration)
+ * 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 "zbuf.h"
+#include "memory.h"
+#include "thread.h"
+#include "nhrpd.h"
+#include "nhrp_protocol.h"
+
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_NHS, "NHRP next hop server");
+DEFINE_MTYPE_STATIC(NHRPD, NHRP_REGISTRATION, "NHRP registration entries");
+
+static void nhrp_nhs_resolve(struct thread *t);
+static void nhrp_reg_send_req(struct thread *t);
+
+static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg)
+{
+ struct nhrp_packet_parser *p = arg;
+ struct nhrp_registration *r =
+ container_of(reqid, struct nhrp_registration, reqid);
+ struct nhrp_nhs *nhs = r->nhs;
+ struct interface *ifp = nhs->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cie_header *cie;
+ struct nhrp_cache *c;
+ struct zbuf extpl;
+ union sockunion cie_nbma, cie_nbma_nhs, cie_proto, cie_proto_nhs,
+ *proto;
+ int ok = 0, holdtime;
+ unsigned short mtu = 0;
+
+ nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid);
+
+ if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) {
+ debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed");
+ return;
+ }
+
+ debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received");
+
+ ok = 1;
+ while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto))
+ != NULL) {
+ proto = sockunion_family(&cie_proto) != AF_UNSPEC
+ ? &cie_proto
+ : &p->src_proto;
+ debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %pSU: %d",
+ proto, cie->code);
+ if (!((cie->code == NHRP_CODE_SUCCESS)
+ || (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED
+ && nhs->hub)))
+ ok = 0;
+ mtu = ntohs(cie->mtu);
+ debugf(NHRP_DEBUG_COMMON, "NHS: CIE MTU: %d", mtu);
+ }
+
+ if (!ok)
+ return;
+
+ /* Parse extensions */
+ sockunion_family(&nifp->nat_nbma) = AF_UNSPEC;
+ sockunion_family(&cie_nbma_nhs) = AF_UNSPEC;
+ while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) {
+ switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) {
+ case NHRP_EXTENSION_NAT_ADDRESS:
+ /* NHS adds second CIE if NAT is detected */
+ if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto)
+ && nhrp_cie_pull(&extpl, p->hdr, &cie_nbma,
+ &cie_proto)) {
+ nifp->nat_nbma = cie_nbma;
+ debugf(NHRP_DEBUG_IF,
+ "%s: NAT detected, real NBMA address: %pSU",
+ ifp->name, &nifp->nbma);
+ }
+ break;
+ case NHRP_EXTENSION_RESPONDER_ADDRESS:
+ /* NHS adds its own record as responder address */
+ nhrp_cie_pull(&extpl, p->hdr, &cie_nbma_nhs,
+ &cie_proto_nhs);
+ break;
+ }
+ }
+
+ /* Success - schedule next registration, and route NHS */
+ r->timeout = 2;
+ holdtime = nifp->afi[nhs->afi].holdtime;
+ THREAD_OFF(r->t_register);
+
+ /* RFC 2332 5.2.3 - Registration is recommend to be renewed
+ * every one third of holdtime */
+ thread_add_timer(master, nhrp_reg_send_req, r, holdtime / 3,
+ &r->t_register);
+
+ r->proto_addr = p->dst_proto;
+ c = nhrp_cache_get(ifp, &p->dst_proto, 1);
+ if (c)
+ nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime,
+ nhrp_peer_ref(r->peer), mtu, NULL,
+ &cie_nbma_nhs);
+}
+
+static void nhrp_reg_timeout(struct thread *t)
+{
+ struct nhrp_registration *r = THREAD_ARG(t);
+ struct nhrp_cache *c;
+
+
+ if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) {
+ nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid);
+ c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0);
+ if (c)
+ nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL,
+ 0, NULL, NULL);
+ sockunion_family(&r->proto_addr) = AF_UNSPEC;
+ }
+
+ r->timeout <<= 1;
+ if (r->timeout > 64) {
+ /* If registration fails repeatedly, this may be because the
+ * IPSec connection is not working. Close the connection so it
+ * can be re-established correctly
+ */
+ if (r->peer && r->peer->vc && r->peer->vc->ike_uniqueid) {
+ debugf(NHRP_DEBUG_COMMON,
+ "Terminating IPSec Connection for %d",
+ r->peer->vc->ike_uniqueid);
+ vici_terminate_vc_by_ike_id(r->peer->vc->ike_uniqueid);
+ r->peer->vc->ike_uniqueid = 0;
+ }
+ r->timeout = 2;
+ }
+ thread_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register);
+}
+
+static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd)
+{
+ struct nhrp_registration *r =
+ container_of(n, struct nhrp_registration, peer_notifier);
+
+ switch (cmd) {
+ case NOTIFY_PEER_UP:
+ case NOTIFY_PEER_DOWN:
+ case NOTIFY_PEER_IFCONFIG_CHANGED:
+ case NOTIFY_PEER_MTU_CHANGED:
+ debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %pSU",
+ &r->peer->vc->remote.nbma);
+ THREAD_OFF(r->t_register);
+ thread_add_timer_msec(master, nhrp_reg_send_req, r, 10,
+ &r->t_register);
+ break;
+ }
+}
+
+static void nhrp_reg_send_req(struct thread *t)
+{
+ struct nhrp_registration *r = THREAD_ARG(t);
+ struct nhrp_nhs *nhs = r->nhs;
+ struct interface *ifp = nhs->ifp;
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi];
+ union sockunion *dst_proto, nhs_proto;
+ struct zbuf *zb;
+ struct nhrp_packet_header *hdr;
+ struct nhrp_extension_header *ext;
+ struct nhrp_cie_header *cie;
+
+ if (!nhrp_peer_check(r->peer, 2)) {
+ debugf(NHRP_DEBUG_COMMON, "NHS: Waiting link for %pSU",
+ &r->peer->vc->remote.nbma);
+ thread_add_timer(master, nhrp_reg_send_req, r, 120,
+ &r->t_register);
+ return;
+ }
+
+ thread_add_timer(master, nhrp_reg_timeout, r, r->timeout,
+ &r->t_register);
+
+ /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */
+ dst_proto = &nhs->proto_addr;
+ if (sockunion_family(dst_proto) == AF_UNSPEC)
+ dst_proto = &if_ad->addr;
+
+ debugf(NHRP_DEBUG_COMMON, "NHS: Register %pSU -> %pSU (timeout %d)",
+ &if_ad->addr, dst_proto, r->timeout);
+
+ /* No protocol address configured for tunnel interface */
+ if (sockunion_family(&if_ad->addr) == AF_UNSPEC)
+ return;
+
+ zb = zbuf_alloc(1400);
+ hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST,
+ &nifp->nbma, &if_ad->addr, dst_proto);
+ hdr->hop_count = 1;
+ if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE))
+ hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE);
+
+ hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid,
+ &r->reqid, nhrp_reg_reply));
+
+ /* FIXME: push CIE for each local protocol address */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL);
+ /* RFC2332 5.2.1 if unique is set then prefix length must be 0xff */
+ cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)
+ ? 8 * sockunion_get_addrlen(dst_proto)
+ : 0xff;
+ cie->holding_time = htons(if_ad->holdtime);
+ cie->mtu = htons(if_ad->mtu);
+
+ nhrp_ext_request(zb, hdr, ifp);
+
+ /* Cisco NAT detection extension */
+ if (sockunion_family(&r->proto_addr) != AF_UNSPEC) {
+ nhs_proto = r->proto_addr;
+ } else if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC) {
+ nhs_proto = nhs->proto_addr;
+ } else {
+ /* cisco magic: If NHS is not known then use all 0s as
+ * client protocol address in NAT Extension header
+ */
+ memset(&nhs_proto, 0, sizeof(nhs_proto));
+ sockunion_family(&nhs_proto) = afi2family(nhs->afi);
+ }
+
+ hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT);
+ ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS);
+ /* push NHS details */
+ cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &r->peer->vc->remote.nbma,
+ &nhs_proto);
+ 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(r->peer, zb);
+ zbuf_free(zb);
+}
+
+static void nhrp_reg_delete(struct nhrp_registration *r)
+{
+ nhrp_peer_notify_del(r->peer, &r->peer_notifier);
+ nhrp_peer_unref(r->peer);
+ nhrp_reglist_del(&r->nhs->reglist_head, r);
+ THREAD_OFF(r->t_register);
+ XFREE(MTYPE_NHRP_REGISTRATION, r);
+}
+
+static struct nhrp_registration *
+nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr)
+{
+ struct nhrp_registration *r;
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, r)
+ if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr))
+ return r;
+ return NULL;
+}
+
+static void nhrp_nhs_resolve_cb(struct resolver_query *q, const char *errstr,
+ int n, union sockunion *addrs)
+{
+ struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve);
+ struct nhrp_interface *nifp = nhs->ifp->info;
+ struct nhrp_registration *reg;
+ int i;
+
+ if (n < 0) {
+ /* Failed, retry in a moment */
+ thread_add_timer(master, nhrp_nhs_resolve, nhs, 5,
+ &nhs->t_resolve);
+ return;
+ }
+
+ thread_add_timer(master, nhrp_nhs_resolve, nhs, 2 * 60 * 60,
+ &nhs->t_resolve);
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg)
+ reg->mark = 1;
+
+ nhs->hub = 0;
+ for (i = 0; i < n; i++) {
+ if (sockunion_same(&addrs[i], &nifp->nbma)) {
+ nhs->hub = 1;
+ continue;
+ }
+
+ reg = nhrp_reg_by_nbma(nhs, &addrs[i]);
+ if (reg) {
+ reg->mark = 0;
+ continue;
+ }
+
+ reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg));
+ reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]);
+ reg->nhs = nhs;
+ reg->timeout = 1;
+ nhrp_reglist_add_tail(&nhs->reglist_head, reg);
+ nhrp_peer_notify_add(reg->peer, &reg->peer_notifier,
+ nhrp_reg_peer_notify);
+ thread_add_timer_msec(master, nhrp_reg_send_req, reg, 50,
+ &reg->t_register);
+ }
+
+ frr_each_safe (nhrp_reglist, &nhs->reglist_head, reg)
+ if (reg->mark)
+ nhrp_reg_delete(reg);
+}
+
+static void nhrp_nhs_resolve(struct thread *t)
+{
+ struct nhrp_nhs *nhs = THREAD_ARG(t);
+
+ resolver_resolve(&nhs->dns_resolve, AF_INET, VRF_DEFAULT,
+ nhs->nbma_fqdn, nhrp_nhs_resolve_cb);
+}
+
+int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_same(&nhs->proto_addr, proto_addr))
+ return NHRP_ERR_ENTRY_EXISTS;
+
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0)
+ return NHRP_ERR_ENTRY_EXISTS;
+ }
+
+ nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs));
+
+ *nhs = (struct nhrp_nhs){
+ .afi = afi,
+ .ifp = ifp,
+ .proto_addr = *proto_addr,
+ .nbma_fqdn = strdup(nbma_fqdn),
+ .reglist_head = INIT_DLIST(nhs->reglist_head),
+ };
+ nhrp_nhslist_add_tail(&nifp->afi[afi].nhslist_head, nhs);
+ thread_add_timer_msec(master, nhrp_nhs_resolve, nhs, 1000,
+ &nhs->t_resolve);
+
+ return NHRP_OK;
+}
+
+int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr,
+ const char *nbma_fqdn)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ int ret = NHRP_ERR_ENTRY_NOT_FOUND;
+
+ if (sockunion_family(proto_addr) != AF_UNSPEC
+ && sockunion_family(proto_addr) != afi2family(afi))
+ return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH;
+
+ frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (!sockunion_same(&nhs->proto_addr, proto_addr))
+ continue;
+ if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0)
+ continue;
+
+ nhrp_nhs_free(nifp, afi, nhs);
+ ret = NHRP_OK;
+ }
+
+ return ret;
+}
+
+int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs)
+{
+ struct nhrp_registration *r;
+
+ frr_each_safe (nhrp_reglist, &nhs->reglist_head, r)
+ nhrp_reg_delete(r);
+ THREAD_OFF(nhs->t_resolve);
+ nhrp_nhslist_del(&nifp->afi[afi].nhslist_head, nhs);
+ free((void *)nhs->nbma_fqdn);
+ XFREE(MTYPE_NHRP_NHS, nhs);
+ return 0;
+}
+
+void nhrp_nhs_interface_del(struct interface *ifp)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ afi_t afi;
+
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ debugf(NHRP_DEBUG_COMMON, "Cleaning up nhs entries (%zu)",
+ nhrp_nhslist_count(&nifp->afi[afi].nhslist_head));
+
+ frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs)
+ nhrp_nhs_free(nifp, afi, nhs);
+ }
+}
+
+void nhrp_nhs_terminate(void)
+{
+ struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ struct interface *ifp;
+ struct nhrp_interface *nifp;
+ struct nhrp_nhs *nhs;
+ afi_t afi;
+
+ FOR_ALL_INTERFACES (vrf, ifp) {
+ nifp = ifp->info;
+ for (afi = 0; afi < AFI_MAX; afi++) {
+ frr_each_safe (nhrp_nhslist,
+ &nifp->afi[afi].nhslist_head, nhs)
+ nhrp_nhs_free(nifp, afi, nhs);
+ }
+ }
+}
+
+void nhrp_nhs_foreach(struct interface *ifp, afi_t afi,
+ void (*cb)(struct nhrp_nhs *, struct nhrp_registration *,
+ void *),
+ void *ctx)
+{
+ struct nhrp_interface *nifp = ifp->info;
+ struct nhrp_nhs *nhs;
+ struct nhrp_registration *reg;
+
+ frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) {
+ if (nhrp_reglist_count(&nhs->reglist_head)) {
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg)
+ cb(nhs, reg, ctx);
+ } else
+ cb(nhs, 0, ctx);
+ }
+}
+
+int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp)
+{
+ int i;
+ struct nhrp_nhs *nhs;
+ struct nhrp_registration *reg;
+
+ for (i = 0; i < AFI_MAX; i++) {
+ frr_each (nhrp_nhslist, &nifp->afi[i].nhslist_head, nhs) {
+ if (!nhrp_reglist_count(&nhs->reglist_head))
+ continue;
+
+ frr_each (nhrp_reglist, &nhs->reglist_head, reg) {
+ if (!sockunion_cmp(in_ip,
+ &reg->peer->vc->remote.nbma))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}