mirror_frr/nhrpd/nhrp_interface.c
Philippe Guibert fef2ed139d nhrpd: cache config may disappear if iface not present at startup
When interface not present at config time, store separately the list of
config parameters. Then, when interface is ready and an address has been configured, the nbma setting is done. Reversely, when interface disappears,
there is no need to keep the maps present, then keep only the configuration.

Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>
2020-11-23 17:16:35 +00:00

470 lines
12 KiB
C

/* 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 "netlink.h"
DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface")
static void nhrp_interface_update_cache_config(struct interface *ifp,
bool available,
uint8_t family);
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;
list_init(&ad->nhslist_head);
}
return 0;
}
static int nhrp_if_delete_hook(struct interface *ifp)
{
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);
}
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->linkidx == nifp->nbmaifp->ifindex)
return;
nifp->linkidx = nifp->nbmaifp->ifindex;
debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d", ifp->name,
nifp->linkidx);
netlink_gre_set_link(ifp->ifindex, nifp->linkidx);
}
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;
char buf[SU_ADDRSTRLEN];
switch (cmd) {
case NOTIFY_INTERFACE_CHANGED:
nhrp_interface_update_mtu(nifp->ifp, AFI_IP);
nhrp_interface_update_source(nifp->ifp);
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 %s",
nifp->ifp->name,
sockunion2str(&nifp->nbma, buf, sizeof(buf)));
break;
}
}
static void nhrp_interface_update_nbma(struct interface *ifp)
{
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, VRF_DEFAULT);
switch (ifp->ll_type) {
case ZEBRA_LLT_IPGRE: {
struct in_addr saddr = {0};
netlink_gre_get_info(ifp->ifindex, &nifp->grekey,
&nifp->linkidx, &saddr);
debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name,
nifp->grekey, nifp->linkidx, saddr.s_addr);
if (saddr.s_addr != INADDR_ANY)
sockunion_set(&nbma, AF_INET, (uint8_t *)&saddr.s_addr,
sizeof(saddr.s_addr));
else if (!nbmaifp && nifp->linkidx != IFINDEX_INTERNAL)
nbmaifp =
if_lookup_by_index(nifp->linkidx, VRF_DEFAULT);
} break;
default:
break;
}
if (nbmaifp)
nbmanifp = nbmaifp->info;
if (nbmaifp != nifp->nbmaifp) {
if (nifp->nbmaifp)
notifier_del(&nifp->nbmanifp_notifier);
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);
}
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);
}
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));
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);
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_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);
}
return;
}
/* create */
c->map = 1;
if (cc->type == NHRP_CACHE_LOCAL)
nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0,
NULL);
else {
nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0,
nhrp_peer_get(ifp, &cc->nbma), 0,
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);
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)
{
notifier_del(n);
}
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)
free(nifp->ipsec_profile);
nifp->ipsec_profile = profile ? strdup(profile) : NULL;
if (nifp->ipsec_fallback_profile)
free(nifp->ipsec_fallback_profile);
nifp->ipsec_fallback_profile =
fallback_profile ? strdup(fallback_profile) : NULL;
notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_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);
}