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