frr/ldpd/adjacency.c

384 lines
8.3 KiB
C

// SPDX-License-Identifier: ISC
/* $OpenBSD$ */
/*
* Copyright (c) 2013, 2015 Renato Westphal <renato@openbsd.org>
* Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
* Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
* Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
*/
#include <zebra.h>
#include "ldpd.h"
#include "ldpe.h"
#include "log.h"
static __inline int adj_compare(const struct adj *, const struct adj *);
static void adj_itimer(struct event *);
static __inline int tnbr_compare(const struct tnbr *, const struct tnbr *);
static void tnbr_del(struct ldpd_conf *, struct tnbr *);
static void tnbr_start(struct tnbr *);
static void tnbr_stop(struct tnbr *);
static void tnbr_hello_timer(struct event *);
static void tnbr_start_hello_timer(struct tnbr *);
static void tnbr_stop_hello_timer(struct tnbr *);
RB_GENERATE(global_adj_head, adj, global_entry, adj_compare)
RB_GENERATE(nbr_adj_head, adj, nbr_entry, adj_compare)
RB_GENERATE(ia_adj_head, adj, ia_entry, adj_compare)
RB_GENERATE(tnbr_head, tnbr, entry, tnbr_compare)
static __inline int
adj_compare(const struct adj *a, const struct adj *b)
{
if (adj_get_af(a) < adj_get_af(b))
return (-1);
if (adj_get_af(a) > adj_get_af(b))
return (1);
if (ntohl(a->lsr_id.s_addr) < ntohl(b->lsr_id.s_addr))
return (-1);
if (ntohl(a->lsr_id.s_addr) > ntohl(b->lsr_id.s_addr))
return (1);
if (a->source.type < b->source.type)
return (-1);
if (a->source.type > b->source.type)
return (1);
switch (a->source.type) {
case HELLO_LINK:
if (if_cmp_name_func(a->source.link.ia->iface->name,
b->source.link.ia->iface->name) < 0)
return (-1);
if (if_cmp_name_func(a->source.link.ia->iface->name,
b->source.link.ia->iface->name) > 0)
return (1);
return (ldp_addrcmp(a->source.link.ia->af,
&a->source.link.src_addr, &b->source.link.src_addr));
case HELLO_TARGETED:
return (ldp_addrcmp(a->source.target->af,
&a->source.target->addr, &b->source.target->addr));
default:
fatalx("adj_compare: unknown hello type");
}
return (0);
}
struct adj *
adj_new(struct in_addr lsr_id, struct hello_source *source,
union ldpd_addr *addr)
{
struct adj *adj;
log_debug("%s: lsr-id %pI4, %s", __func__, &lsr_id,
log_hello_src(source));
if ((adj = calloc(1, sizeof(*adj))) == NULL)
fatal(__func__);
adj->lsr_id = lsr_id;
adj->nbr = NULL;
adj->source = *source;
adj->trans_addr = *addr;
RB_INSERT(global_adj_head, &global.adj_tree, adj);
switch (source->type) {
case HELLO_LINK:
RB_INSERT(ia_adj_head, &source->link.ia->adj_tree, adj);
break;
case HELLO_TARGETED:
source->target->adj = adj;
break;
}
return (adj);
}
void
adj_del(struct adj *adj, uint32_t notif_status)
{
struct nbr *nbr = adj->nbr;
log_debug("%s: lsr-id %pI4, %s (%s)", __func__, &adj->lsr_id,
log_hello_src(&adj->source), af_name(adj_get_af(adj)));
adj_stop_itimer(adj);
RB_REMOVE(global_adj_head, &global.adj_tree, adj);
if (nbr)
RB_REMOVE(nbr_adj_head, &nbr->adj_tree, adj);
switch (adj->source.type) {
case HELLO_LINK:
RB_REMOVE(ia_adj_head, &adj->source.link.ia->adj_tree, adj);
if (nbr)
ldp_sync_fsm_adj_event(adj, LDP_SYNC_EVT_ADJ_DEL);
break;
case HELLO_TARGETED:
adj->source.target->adj = NULL;
break;
}
free(adj);
/*
* If the neighbor still exists but none of its remaining
* adjacencies (if any) are from the preferred address-family,
* then delete it.
*/
if (nbr && nbr_adj_count(nbr, nbr->af) == 0) {
session_shutdown(nbr, notif_status, 0, 0);
nbr_del(nbr);
}
}
struct adj *
adj_find(struct in_addr lsr_id, struct hello_source *source)
{
struct adj adj;
adj.lsr_id = lsr_id;
adj.source = *source;
return (RB_FIND(global_adj_head, &global.adj_tree, &adj));
}
int
adj_get_af(const struct adj *adj)
{
switch (adj->source.type) {
case HELLO_LINK:
return (adj->source.link.ia->af);
case HELLO_TARGETED:
return (adj->source.target->af);
default:
fatalx("adj_get_af: unknown hello type");
}
}
/* adjacency timers */
/* ARGSUSED */
static void adj_itimer(struct event *thread)
{
struct adj *adj = EVENT_ARG(thread);
adj->inactivity_timer = NULL;
log_debug("%s: lsr-id %pI4", __func__, &adj->lsr_id);
if (adj->source.type == HELLO_TARGETED) {
if (!CHECK_FLAG(adj->source.target->flags, F_TNBR_CONFIGURED) &&
adj->source.target->pw_count == 0 &&
adj->source.target->rlfa_count == 0) {
/* remove dynamic targeted neighbor */
tnbr_del(leconf, adj->source.target);
return;
}
}
adj_del(adj, S_HOLDTIME_EXP);
}
void
adj_start_itimer(struct adj *adj)
{
EVENT_OFF(adj->inactivity_timer);
adj->inactivity_timer = NULL;
event_add_timer(master, adj_itimer, adj, adj->holdtime,
&adj->inactivity_timer);
}
void
adj_stop_itimer(struct adj *adj)
{
EVENT_OFF(adj->inactivity_timer);
}
/* targeted neighbors */
static __inline int
tnbr_compare(const struct tnbr *a, const struct tnbr *b)
{
if (a->af < b->af)
return (-1);
if (a->af > b->af)
return (1);
return (ldp_addrcmp(a->af, &a->addr, &b->addr));
}
struct tnbr *
tnbr_new(int af, union ldpd_addr *addr)
{
struct tnbr *tnbr;
if ((tnbr = calloc(1, sizeof(*tnbr))) == NULL)
fatal(__func__);
tnbr->af = af;
tnbr->addr = *addr;
tnbr->state = TNBR_STA_DOWN;
return (tnbr);
}
static void
tnbr_del(struct ldpd_conf *xconf, struct tnbr *tnbr)
{
tnbr_stop(tnbr);
RB_REMOVE(tnbr_head, &xconf->tnbr_tree, tnbr);
free(tnbr);
}
struct tnbr *
tnbr_find(struct ldpd_conf *xconf, int af, union ldpd_addr *addr)
{
struct tnbr tnbr;
tnbr.af = af;
tnbr.addr = *addr;
return (RB_FIND(tnbr_head, &xconf->tnbr_tree, &tnbr));
}
struct tnbr *
tnbr_check(struct ldpd_conf *xconf, struct tnbr *tnbr)
{
if (!CHECK_FLAG(tnbr->flags, (F_TNBR_CONFIGURED|F_TNBR_DYNAMIC)) &&
tnbr->pw_count == 0 && tnbr->rlfa_count == 0) {
tnbr_del(xconf, tnbr);
return (NULL);
}
return (tnbr);
}
static void
tnbr_start(struct tnbr *tnbr)
{
send_hello(HELLO_TARGETED, NULL, tnbr);
tnbr_start_hello_timer(tnbr);
tnbr->state = TNBR_STA_ACTIVE;
}
static void
tnbr_stop(struct tnbr *tnbr)
{
tnbr_stop_hello_timer(tnbr);
if (tnbr->adj)
adj_del(tnbr->adj, S_SHUTDOWN);
tnbr->state = TNBR_STA_DOWN;
}
void
tnbr_update(struct tnbr *tnbr)
{
int socket_ok, rtr_id_ok;
if ((ldp_af_global_get(&global, tnbr->af))->ldp_edisc_socket != -1)
socket_ok = 1;
else
socket_ok = 0;
if (ldp_rtr_id_get(leconf) != INADDR_ANY)
rtr_id_ok = 1;
else
rtr_id_ok = 0;
if (tnbr->state == TNBR_STA_DOWN) {
if (!socket_ok || !rtr_id_ok)
return;
tnbr_start(tnbr);
} else if (tnbr->state == TNBR_STA_ACTIVE) {
if (socket_ok && rtr_id_ok)
return;
tnbr_stop(tnbr);
}
}
void
tnbr_update_all(int af)
{
struct tnbr *tnbr;
/* update targeted neighbors */
RB_FOREACH(tnbr, tnbr_head, &leconf->tnbr_tree)
if (tnbr->af == af || af == AF_UNSPEC)
tnbr_update(tnbr);
}
uint16_t
tnbr_get_hello_holdtime(struct tnbr *tnbr)
{
if ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime != 0)
return ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime);
return (leconf->thello_holdtime);
}
uint16_t
tnbr_get_hello_interval(struct tnbr *tnbr)
{
if ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval != 0)
return ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval);
return (leconf->thello_interval);
}
/* target neighbors timers */
/* ARGSUSED */
static void tnbr_hello_timer(struct event *thread)
{
struct tnbr *tnbr = EVENT_ARG(thread);
tnbr->hello_timer = NULL;
send_hello(HELLO_TARGETED, NULL, tnbr);
tnbr_start_hello_timer(tnbr);
}
static void
tnbr_start_hello_timer(struct tnbr *tnbr)
{
EVENT_OFF(tnbr->hello_timer);
tnbr->hello_timer = NULL;
event_add_timer(master, tnbr_hello_timer, tnbr,
tnbr_get_hello_interval(tnbr), &tnbr->hello_timer);
}
static void
tnbr_stop_hello_timer(struct tnbr *tnbr)
{
EVENT_OFF(tnbr->hello_timer);
}
struct ctl_adj *
adj_to_ctl(struct adj *adj)
{
static struct ctl_adj actl;
actl.af = adj_get_af(adj);
actl.id = adj->lsr_id;
actl.type = adj->source.type;
switch (adj->source.type) {
case HELLO_LINK:
memcpy(actl.ifname, adj->source.link.ia->iface->name,
sizeof(actl.ifname));
actl.src_addr = adj->source.link.src_addr;
break;
case HELLO_TARGETED:
actl.src_addr = adj->source.target->addr;
break;
}
actl.holdtime = adj->holdtime;
actl.holdtime_remaining =
event_timer_remain_second(adj->inactivity_timer);
actl.trans_addr = adj->trans_addr;
actl.ds_tlv = adj->ds_tlv;
return (&actl);
}