diff options
Diffstat (limited to '')
-rw-r--r-- | nhrpd/nhrp_interface.c | 558 |
1 files changed, 558 insertions, 0 deletions
diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c new file mode 100644 index 0000000..4ac30a7 --- /dev/null +++ b/nhrpd/nhrp_interface.c @@ -0,0 +1,558 @@ +/* NHRP interface + * 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 <net/if_arp.h> +#include "zebra.h" +#include "linklist.h" +#include "memory.h" +#include "thread.h" + +#include "nhrpd.h" +#include "os.h" +#include "hash.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF_GRE, "NHRP GRE interface"); + +struct hash *nhrp_gre_list; + +static void nhrp_interface_update_cache_config(struct interface *ifp, + bool available, + uint8_t family); + +static unsigned int nhrp_gre_info_key(const void *data) +{ + const struct nhrp_gre_info *r = data; + + return r->ifindex; +} + +static bool nhrp_gre_info_cmp(const void *data, const void *key) +{ + const struct nhrp_gre_info *a = data, *b = key; + + if (a->ifindex == b->ifindex) + return true; + return false; +} + +static void *nhrp_interface_gre_alloc(void *data) +{ + struct nhrp_gre_info *a; + struct nhrp_gre_info *b = data; + + a = XMALLOC(MTYPE_NHRP_IF_GRE, sizeof(struct nhrp_gre_info)); + memcpy(a, b, sizeof(struct nhrp_gre_info)); + return a; +} + +struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p) +{ + struct nhrp_gre_info *a; + + a = (struct nhrp_gre_info *)hash_get(nhrp_gre_list, p, + nhrp_interface_gre_alloc); + return a; +} + +static int nhrp_if_new_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp; + afi_t afi; + + nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface)); + + ifp->info = nifp; + nifp->ifp = ifp; + + notifier_init(&nifp->notifier_list); + for (afi = 0; afi < AFI_MAX; afi++) { + struct nhrp_afi_data *ad = &nifp->afi[afi]; + ad->holdtime = NHRPD_DEFAULT_HOLDTIME; + nhrp_nhslist_init(&ad->nhslist_head); + nhrp_mcastlist_init(&ad->mcastlist_head); + } + + return 0; +} + +static int nhrp_if_delete_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_IF, "Deleted interface (%s)", ifp->name); + + nhrp_cache_interface_del(ifp); + nhrp_nhs_interface_del(ifp); + nhrp_multicast_interface_del(ifp); + nhrp_peer_interface_del(ifp); + + if (nifp->ipsec_profile) + free(nifp->ipsec_profile); + if (nifp->ipsec_fallback_profile) + free(nifp->ipsec_fallback_profile); + if (nifp->source) + free(nifp->source); + + XFREE(MTYPE_NHRP_IF, ifp->info); + return 0; +} + +void nhrp_interface_init(void) +{ + hook_register_prio(if_add, 0, nhrp_if_new_hook); + hook_register_prio(if_del, 0, nhrp_if_delete_hook); + + nhrp_gre_list = hash_create(nhrp_gre_info_key, nhrp_gre_info_cmp, + "NHRP GRE list Hash"); +} + +void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + unsigned short new_mtu; + + if (if_ad->configured_mtu < 0) + new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0; + else + new_mtu = if_ad->configured_mtu; + if (new_mtu >= 1500) + new_mtu = 0; + + if (new_mtu != if_ad->mtu) { + debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, + new_mtu); + if_ad->mtu = new_mtu; + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_MTU_CHANGED); + } +} + +static void nhrp_interface_update_source(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + if (!nifp->source || !nifp->nbmaifp + || ((ifindex_t)nifp->link_idx == nifp->nbmaifp->ifindex + && (nifp->link_vrf_id == nifp->nbmaifp->vrf->vrf_id))) + return; + + nifp->link_idx = nifp->nbmaifp->ifindex; + nifp->link_vrf_id = nifp->nbmaifp->vrf->vrf_id; + debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d, vr %u", + ifp->name, nifp->link_idx, nifp->link_vrf_id); + nhrp_send_zebra_gre_source_set(ifp, nifp->link_idx, nifp->link_vrf_id); +} + +static void nhrp_interface_interface_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_interface *nifp = + container_of(n, struct nhrp_interface, nbmanifp_notifier); + struct interface *nbmaifp = nifp->nbmaifp; + struct nhrp_interface *nbmanifp = nbmaifp->info; + + switch (cmd) { + case NOTIFY_INTERFACE_CHANGED: + nhrp_interface_update_nbma(nifp->ifp, NULL); + break; + case NOTIFY_INTERFACE_ADDRESS_CHANGED: + nifp->nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update(nifp->ifp); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %pSU", + nifp->ifp->name, &nifp->nbma); + break; + } +} + +void nhrp_interface_update_nbma(struct interface *ifp, + struct nhrp_gre_info *gre_info) +{ + struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL; + struct interface *nbmaifp = NULL; + union sockunion nbma; + + sockunion_family(&nbma) = AF_UNSPEC; + + if (nifp->source) + nbmaifp = if_lookup_by_name(nifp->source, nifp->link_vrf_id); + + switch (ifp->ll_type) { + case ZEBRA_LLT_IPGRE: { + struct in_addr saddr = {0}; + + if (!gre_info) { + nhrp_send_zebra_gre_request(ifp); + return; + } + nifp->i_grekey = gre_info->ikey; + nifp->o_grekey = gre_info->okey; + nifp->link_idx = gre_info->ifindex_link; + nifp->link_vrf_id = gre_info->vrfid_link; + saddr.s_addr = gre_info->vtep_ip.s_addr; + + debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, + nifp->i_grekey, nifp->link_idx, saddr.s_addr); + if (saddr.s_addr) + sockunion_set(&nbma, AF_INET, + (uint8_t *)&saddr.s_addr, + sizeof(saddr.s_addr)); + else if (!nbmaifp && nifp->link_idx != IFINDEX_INTERNAL) + nbmaifp = + if_lookup_by_index(nifp->link_idx, + nifp->link_vrf_id); + } break; + default: + break; + } + + if (nbmaifp) + nbmanifp = nbmaifp->info; + + if (nbmaifp != nifp->nbmaifp) { + if (nifp->nbmaifp) { + struct nhrp_interface *prev_nifp = nifp->nbmaifp->info; + + notifier_del(&nifp->nbmanifp_notifier, + &prev_nifp->notifier_list); + } + nifp->nbmaifp = nbmaifp; + if (nbmaifp) { + notifier_add(&nifp->nbmanifp_notifier, + &nbmanifp->notifier_list, + nhrp_interface_interface_notifier); + debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, + nbmaifp->name); + } + } + + if (nbmaifp) { + if (sockunion_family(&nbma) == AF_UNSPEC) + nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update_mtu(ifp, AFI_IP); + nhrp_interface_update_source(ifp); + } + + if (!sockunion_same(&nbma, &nifp->nbma)) { + nifp->nbma = nbma; + nhrp_interface_update(nifp->ifp); + debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + } + + nhrp_interface_update(ifp); +} + +static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, + int force) +{ + const int family = afi2family(afi); + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + struct nhrp_cache *nc; + struct connected *c, *best; + struct listnode *cnode; + union sockunion addr; + char buf[PREFIX_STRLEN]; + + /* Select new best match preferring primary address */ + best = NULL; + for (ALL_LIST_ELEMENTS_RO(ifp->connected, cnode, c)) { + if (PREFIX_FAMILY(c->address) != family) + continue; + if (best == NULL) { + best = c; + continue; + } + if ((best->flags & ZEBRA_IFA_SECONDARY) + && !(c->flags & ZEBRA_IFA_SECONDARY)) { + best = c; + continue; + } + if (!(best->flags & ZEBRA_IFA_SECONDARY) + && (c->flags & ZEBRA_IFA_SECONDARY)) + continue; + if (best->address->prefixlen > c->address->prefixlen) { + best = c; + continue; + } + if (best->address->prefixlen < c->address->prefixlen) + continue; + } + + /* On NHRP interfaces a host prefix is required */ + if (best && if_ad->configured + && best->address->prefixlen != 8 * prefix_blen(best->address)) { + zlog_notice("%s: %pFX is not a host prefix", ifp->name, + best->address); + best = NULL; + } + + /* Update address if it changed */ + if (best) + prefix2sockunion(best->address, &addr); + else + memset(&addr, 0, sizeof(addr)); + + if (!force && sockunion_same(&if_ad->addr, &addr)) + return; + + if (sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &if_ad->addr, 0); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, + NULL, 0, NULL, NULL); + } + + debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", ifp->name, + afi == AFI_IP ? 4 : 6, + best ? prefix2str(best->address, buf, sizeof(buf)) : "(none)"); + if_ad->addr = addr; + + if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &addr, 1); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, + 0, NULL, NULL); + } + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); +} + +void nhrp_interface_update(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad; + afi_t afi; + int enabled = 0; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED); + + for (afi = 0; afi < AFI_MAX; afi++) { + if_ad = &nifp->afi[afi]; + + if (sockunion_family(&nifp->nbma) == AF_UNSPEC + || ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) + || !if_ad->network_id) { + if (if_ad->configured) { + if_ad->configured = 0; + nhrp_interface_update_address(ifp, afi, 1); + } + continue; + } + + if (!if_ad->configured) { + os_configure_dmvpn(ifp->ifindex, ifp->name, + afi2family(afi)); + nhrp_send_zebra_configure_arp(ifp, afi2family(afi)); + if_ad->configured = 1; + nhrp_interface_update_address(ifp, afi, 1); + } + + enabled = 1; + } + + if (enabled != nifp->enabled) { + nifp->enabled = enabled; + notifier_call(&nifp->notifier_list, + enabled ? NOTIFY_INTERFACE_UP + : NOTIFY_INTERFACE_DOWN); + } +} + +int nhrp_ifp_create(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s", + ifp->name, ifp->ifindex, ifp->ll_type, + if_link_type_str(ifp->ll_type)); + + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_destroy(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name); + + nhrp_interface_update_cache_config(ifp, false, AF_INET); + nhrp_interface_update_cache_config(ifp, false, AF_INET6); + nhrp_interface_update(ifp); + + return 0; +} + +struct map_ctx { + int family; + bool enabled; +}; + +static void interface_config_update_nhrp_map(struct nhrp_cache_config *cc, + void *data) +{ + struct map_ctx *ctx = data; + struct interface *ifp = cc->ifp; + struct nhrp_cache *c; + union sockunion nbma_addr; + + if (sockunion_family(&cc->remote_addr) != ctx->family) + return; + + /* gre layer not ready */ + if (ifp->vrf->vrf_id == VRF_UNKNOWN) + return; + + c = nhrp_cache_get(ifp, &cc->remote_addr, ctx->enabled ? 1 : 0); + if (!c && !ctx->enabled) + return; + + /* suppress */ + if (!ctx->enabled) { + if (c && c->map) { + nhrp_cache_update_binding( + c, c->cur.type, -1, + nhrp_peer_get(ifp, &nbma_addr), 0, NULL, NULL); + } + return; + } + + /* Newly created */ + assert(c != NULL); + + c->map = 1; + if (cc->type == NHRP_CACHE_LOCAL) + nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, + NULL, NULL); + else { + nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, + nhrp_peer_get(ifp, &cc->nbma), 0, + NULL, NULL); + } +} + +static void nhrp_interface_update_cache_config(struct interface *ifp, bool available, uint8_t family) +{ + struct map_ctx mapctx; + + mapctx = (struct map_ctx){ + .family = family, + .enabled = available + }; + nhrp_cache_config_foreach(ifp, interface_config_update_nhrp_map, + &mapctx); + +} + +int nhrp_ifp_up(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name); + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_down(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name); + nhrp_interface_update(ifp); + + return 0; +} + +int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + nhrp_interface_update_cache_config(ifc->ifp, true, PREFIX_FAMILY(ifc->address)); + return 0; +} + +int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + connected_free(&ifc); + + return 0; +} + +void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, + notifier_fn_t fn) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_add(n, &nifp->notifier_list, fn); +} + +void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_del(n, &nifp->notifier_list); +} + +void nhrp_interface_set_protection(struct interface *ifp, const char *profile, + const char *fallback_profile) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->ipsec_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_profile); + nhrp_vc_reset(); + free(nifp->ipsec_profile); + } + nifp->ipsec_profile = profile ? strdup(profile) : NULL; + + if (nifp->ipsec_fallback_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_fallback_profile); + nhrp_vc_reset(); + free(nifp->ipsec_fallback_profile); + } + nifp->ipsec_fallback_profile = + fallback_profile ? strdup(fallback_profile) : NULL; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_IPSEC_CHANGED); +} + +void nhrp_interface_set_source(struct interface *ifp, const char *ifname) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->source) + free(nifp->source); + nifp->source = ifname ? strdup(ifname) : NULL; + + nhrp_interface_update_nbma(ifp, NULL); +} |