mirror_frr/isisd/isis_adjacency.c
Emanuele Di Pascale 3cbe31c798 isisd: guard against adj timer display overflow
An adjacency should be removed when the holdtimer expires, but if the
system is overloaded we may end up doing it late. In the meanwhile vtysh
will display an incorrect value in the show isis neighbor output, due to
an overflow of the unsigned variable used to display the Holdtime, e.g.:

pe1# show isis neighbor
Area test:
 System Id     Interface   L   state   Holdtime  SNPA
 Spirent-1     2.201       1   Down    26        2020.2020.2020
 Spirent-1     2.203       1   Up      21        2020.2020.2020
 Spirent-1     2.204       1   Up      18446744073709551615  2020.2020.2020
 Spirent-1     2.207       1   Up      18446744073709551615  2020.2020.2020
 Spirent-1     2.208       1   Up      18446744073709551615  2020.2020.2020
 Spirent-1     2.209       1   Up      0         2020.2020.2020
 Spirent-1     2.210       1   Up      18446744073709551615  2020.2020.2020
 pe2           12.200      1   Up      30        2020.2020.2020

Guard against that by printing an "Expiring" message instead.

Signed-off-by: Emanuele Di Pascale <emanuele@voltanet.io>
2020-09-25 13:06:57 +02:00

673 lines
17 KiB
C

/*
* IS-IS Rout(e)ing protocol - isis_adjacency.c
* handling of IS-IS adjacencies
*
* Copyright (C) 2001,2002 Sampo Saaristo
* Tampere University of Technology
* Institute of Communications Engineering
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public Licenseas published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; see the file COPYING; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <zebra.h>
#include "log.h"
#include "memory.h"
#include "hash.h"
#include "vty.h"
#include "linklist.h"
#include "thread.h"
#include "if.h"
#include "stream.h"
#include "isisd/isis_constants.h"
#include "isisd/isis_common.h"
#include "isisd/isis_flags.h"
#include "isisd/isisd.h"
#include "isisd/isis_circuit.h"
#include "isisd/isis_adjacency.h"
#include "isisd/isis_misc.h"
#include "isisd/isis_dr.h"
#include "isisd/isis_dynhn.h"
#include "isisd/isis_pdu.h"
#include "isisd/isis_lsp.h"
#include "isisd/isis_events.h"
#include "isisd/isis_mt.h"
#include "isisd/isis_tlvs.h"
#include "isisd/fabricd.h"
#include "isisd/isis_nb.h"
static struct isis_adjacency *adj_alloc(const uint8_t *id)
{
struct isis_adjacency *adj;
adj = XCALLOC(MTYPE_ISIS_ADJACENCY, sizeof(struct isis_adjacency));
memcpy(adj->sysid, id, ISIS_SYS_ID_LEN);
return adj;
}
struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa,
int level, struct isis_circuit *circuit)
{
struct isis_adjacency *adj;
int i;
adj = adj_alloc(id); /* P2P kludge */
if (snpa) {
memcpy(adj->snpa, snpa, ETH_ALEN);
} else {
memset(adj->snpa, ' ', ETH_ALEN);
}
adj->circuit = circuit;
adj->level = level;
adj->flaps = 0;
adj->last_flap = time(NULL);
adj->threeway_state = ISIS_THREEWAY_DOWN;
if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
listnode_add(circuit->u.bc.adjdb[level - 1], adj);
adj->dischanges[level - 1] = 0;
for (i = 0; i < DIS_RECORDS;
i++) /* clear N DIS state change records */
{
adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis =
ISIS_UNKNOWN_DIS;
adj->dis_record[(i * ISIS_LEVELS) + level - 1]
.last_dis_change = time(NULL);
}
}
adj->adj_sids = list_new();
listnode_add(circuit->area->adjacency_list, adj);
return adj;
}
struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb)
{
struct isis_adjacency *adj;
struct listnode *node;
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
if (memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN) == 0)
return adj;
return NULL;
}
struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa,
struct list *adjdb)
{
struct listnode *node;
struct isis_adjacency *adj;
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
if (memcmp(adj->snpa, ssnpa, ETH_ALEN) == 0)
return adj;
return NULL;
}
struct isis_adjacency *isis_adj_find(const struct isis_area *area, int level,
const uint8_t *sysid)
{
struct isis_adjacency *adj;
struct listnode *node;
for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) {
if (!(adj->level & level))
continue;
if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN))
return adj;
}
return NULL;
}
DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj))
void isis_delete_adj(void *arg)
{
struct isis_adjacency *adj = arg;
if (!adj)
return;
THREAD_TIMER_OFF(adj->t_expire);
if (adj->adj_state != ISIS_ADJ_DOWN)
adj->adj_state = ISIS_ADJ_DOWN;
hook_call(isis_adj_state_change_hook, adj);
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses);
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses);
XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv6_addresses);
adj_mt_finish(adj);
list_delete(&adj->adj_sids);
listnode_delete(adj->circuit->area->adjacency_list, adj);
XFREE(MTYPE_ISIS_ADJACENCY, adj);
return;
}
static const char *adj_state2string(int state)
{
switch (state) {
case ISIS_ADJ_INITIALIZING:
return "Initializing";
case ISIS_ADJ_UP:
return "Up";
case ISIS_ADJ_DOWN:
return "Down";
default:
return "Unknown";
}
return NULL; /* not reached */
}
static const char *adj_level2string(int level)
{
switch (level) {
case IS_LEVEL_1:
return "level-1";
case IS_LEVEL_2:
return "level-2";
case IS_LEVEL_1_AND_2:
return "level-1-2";
default:
return "unknown";
}
return NULL; /* not reached */
}
void isis_adj_process_threeway(struct isis_adjacency *adj,
struct isis_threeway_adj *tw_adj,
enum isis_adj_usage adj_usage)
{
enum isis_threeway_state next_tw_state = ISIS_THREEWAY_DOWN;
if (tw_adj && !adj->circuit->disable_threeway_adj) {
if (tw_adj->state == ISIS_THREEWAY_DOWN) {
next_tw_state = ISIS_THREEWAY_INITIALIZING;
} else if (tw_adj->state == ISIS_THREEWAY_INITIALIZING) {
next_tw_state = ISIS_THREEWAY_UP;
} else if (tw_adj->state == ISIS_THREEWAY_UP) {
if (adj->threeway_state == ISIS_THREEWAY_DOWN)
next_tw_state = ISIS_THREEWAY_DOWN;
else
next_tw_state = ISIS_THREEWAY_UP;
}
} else {
next_tw_state = ISIS_THREEWAY_UP;
}
if (next_tw_state != adj->threeway_state) {
if (IS_DEBUG_ADJ_PACKETS) {
zlog_info("ISIS-Adj (%s): Threeway state change %s to %s",
adj->circuit->area->area_tag,
isis_threeway_state_name(adj->threeway_state),
isis_threeway_state_name(next_tw_state));
}
}
if (next_tw_state != ISIS_THREEWAY_DOWN)
fabricd_initial_sync_hello(adj->circuit);
if (next_tw_state == ISIS_THREEWAY_DOWN) {
isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
"Neighbor restarted");
return;
}
if (next_tw_state == ISIS_THREEWAY_UP) {
if (adj->adj_state != ISIS_ADJ_UP) {
isis_adj_state_change(&adj, ISIS_ADJ_UP, NULL);
adj->adj_usage = adj_usage;
}
}
if (adj->threeway_state != next_tw_state) {
send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY);
}
adj->threeway_state = next_tw_state;
}
void isis_log_adj_change(struct isis_adjacency *adj,
enum isis_adj_state old_state,
enum isis_adj_state new_state, const char *reason)
{
const char *adj_name;
struct isis_dynhn *dyn;
dyn = dynhn_find_by_id(adj->sysid);
if (dyn)
adj_name = dyn->hostname;
else
adj_name = sysid_print(adj->sysid);
zlog_info(
"%%ADJCHANGE: Adjacency to %s (%s) for %s changed from %s to %s, %s",
adj_name, adj->circuit->interface->name,
adj_level2string(adj->level), adj_state2string(old_state),
adj_state2string(new_state), reason ? reason : "unspecified");
}
void isis_adj_state_change(struct isis_adjacency **padj,
enum isis_adj_state new_state, const char *reason)
{
struct isis_adjacency *adj = *padj;
enum isis_adj_state old_state = adj->adj_state;
struct isis_circuit *circuit = adj->circuit;
bool del = false;
if (new_state == old_state)
return;
adj->adj_state = new_state;
send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY);
if (IS_DEBUG_ADJ_PACKETS) {
zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s",
circuit->area->area_tag, old_state, new_state,
reason ? reason : "unspecified");
}
if (circuit->area->log_adj_changes)
isis_log_adj_change(adj, old_state, new_state, reason);
circuit->adj_state_changes++;
#ifndef FABRICD
/* send northbound notification */
isis_notif_adj_state_change(adj, new_state, reason);
#endif /* ifndef FABRICD */
if (circuit->circ_type == CIRCUIT_T_BROADCAST) {
for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
if ((adj->level & level) == 0)
continue;
if (new_state == ISIS_ADJ_UP) {
circuit->upadjcount[level - 1]++;
/* update counter & timers for debugging
* purposes */
adj->last_flap = time(NULL);
adj->flaps++;
} else if (old_state == ISIS_ADJ_UP) {
listnode_delete(circuit->u.bc.adjdb[level - 1],
adj);
circuit->upadjcount[level - 1]--;
if (circuit->upadjcount[level - 1] == 0)
isis_tx_queue_clean(circuit->tx_queue);
if (new_state == ISIS_ADJ_DOWN)
del = true;
}
if (circuit->u.bc.lan_neighs[level - 1]) {
list_delete_all_node(
circuit->u.bc.lan_neighs[level - 1]);
isis_adj_build_neigh_list(
circuit->u.bc.adjdb[level - 1],
circuit->u.bc.lan_neighs[level - 1]);
}
/* On adjacency state change send new pseudo LSP if we
* are the DR */
if (circuit->u.bc.is_dr[level - 1])
lsp_regenerate_schedule_pseudo(circuit, level);
}
} else if (circuit->circ_type == CIRCUIT_T_P2P) {
for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) {
if ((adj->level & level) == 0)
continue;
if (new_state == ISIS_ADJ_UP) {
circuit->upadjcount[level - 1]++;
/* update counter & timers for debugging
* purposes */
adj->last_flap = time(NULL);
adj->flaps++;
if (level == IS_LEVEL_1) {
thread_add_timer(master, send_l1_csnp,
circuit, 0,
&circuit->t_send_csnp[0]);
} else {
thread_add_timer(master, send_l2_csnp,
circuit, 0,
&circuit->t_send_csnp[1]);
}
} else if (old_state == ISIS_ADJ_UP) {
if (adj->circuit->u.p2p.neighbor == adj)
adj->circuit->u.p2p.neighbor = NULL;
circuit->upadjcount[level - 1]--;
if (circuit->upadjcount[level - 1] == 0)
isis_tx_queue_clean(circuit->tx_queue);
if (new_state == ISIS_ADJ_DOWN)
del = true;
}
}
}
hook_call(isis_adj_state_change_hook, adj);
if (del) {
isis_delete_adj(adj);
*padj = NULL;
}
}
void isis_adj_print(struct isis_adjacency *adj)
{
struct isis_dynhn *dyn;
if (!adj)
return;
dyn = dynhn_find_by_id(adj->sysid);
if (dyn)
zlog_debug("%s", dyn->hostname);
zlog_debug("SystemId %20s SNPA %s, level %d; Holding Time %d",
sysid_print(adj->sysid), snpa_print(adj->snpa), adj->level,
adj->hold_time);
if (adj->ipv4_address_count) {
zlog_debug("IPv4 Address(es):");
for (unsigned int i = 0; i < adj->ipv4_address_count; i++)
zlog_debug("%s", inet_ntoa(adj->ipv4_addresses[i]));
}
if (adj->ipv6_address_count) {
zlog_debug("IPv6 Address(es):");
for (unsigned int i = 0; i < adj->ipv6_address_count; i++) {
char buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &adj->ipv6_addresses[i], buf,
sizeof(buf));
zlog_debug("%s", buf);
}
}
zlog_debug("Speaks: %s", nlpid2string(&adj->nlpids));
return;
}
const char *isis_adj_yang_state(enum isis_adj_state state)
{
switch (state) {
case ISIS_ADJ_DOWN:
return "down";
case ISIS_ADJ_UP:
return "up";
case ISIS_ADJ_INITIALIZING:
return "init";
default:
return "failed";
}
}
int isis_adj_expire(struct thread *thread)
{
struct isis_adjacency *adj;
/*
* Get the adjacency
*/
adj = THREAD_ARG(thread);
assert(adj);
adj->t_expire = NULL;
/* trigger the adj expire event */
isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "holding time expired");
return 0;
}
/*
* show isis neighbor [detail]
*/
void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty,
char detail)
{
time_t now;
struct isis_dynhn *dyn;
int level;
dyn = dynhn_find_by_id(adj->sysid);
if (dyn)
vty_out(vty, " %-20s", dyn->hostname);
else
vty_out(vty, " %-20s", sysid_print(adj->sysid));
if (detail == ISIS_UI_LEVEL_BRIEF) {
if (adj->circuit)
vty_out(vty, "%-12s", adj->circuit->interface->name);
else
vty_out(vty, "NULL circuit!");
vty_out(vty, "%-3u", adj->level); /* level */
vty_out(vty, "%-13s", adj_state2string(adj->adj_state));
now = time(NULL);
if (adj->last_upd) {
if (adj->last_upd + adj->hold_time
< (unsigned long long)now)
vty_out(vty, " Expiring");
else
vty_out(vty, " %-9llu",
(unsigned long long)adj->last_upd
+ adj->hold_time - now);
} else
vty_out(vty, "- ");
vty_out(vty, "%-10s", snpa_print(adj->snpa));
vty_out(vty, "\n");
}
if (detail == ISIS_UI_LEVEL_DETAIL) {
struct sr_adjacency *sra;
struct listnode *anode;
level = adj->level;
vty_out(vty, "\n");
if (adj->circuit)
vty_out(vty, " Interface: %s",
adj->circuit->interface->name);
else
vty_out(vty, " Interface: NULL circuit");
vty_out(vty, ", Level: %u", adj->level); /* level */
vty_out(vty, ", State: %s", adj_state2string(adj->adj_state));
now = time(NULL);
if (adj->last_upd) {
if (adj->last_upd + adj->hold_time
< (unsigned long long)now)
vty_out(vty, " Expiring");
else
vty_out(vty, ", Expires in %s",
time2string(adj->last_upd
+ adj->hold_time - now));
} else
vty_out(vty, ", Expires in %s",
time2string(adj->hold_time));
vty_out(vty, "\n");
vty_out(vty, " Adjacency flaps: %u", adj->flaps);
vty_out(vty, ", Last: %s ago",
time2string(now - adj->last_flap));
vty_out(vty, "\n");
vty_out(vty, " Circuit type: %s",
circuit_t2string(adj->circuit_t));
vty_out(vty, ", Speaks: %s", nlpid2string(&adj->nlpids));
vty_out(vty, "\n");
if (adj->mt_count != 1
|| adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) {
vty_out(vty, " Topologies:\n");
for (unsigned int i = 0; i < adj->mt_count; i++)
vty_out(vty, " %s\n",
isis_mtid2str(adj->mt_set[i]));
}
vty_out(vty, " SNPA: %s", snpa_print(adj->snpa));
if (adj->circuit
&& (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) {
dyn = dynhn_find_by_id(adj->lanid);
if (dyn)
vty_out(vty, ", LAN id: %s.%02x", dyn->hostname,
adj->lanid[ISIS_SYS_ID_LEN]);
else
vty_out(vty, ", LAN id: %s.%02x",
sysid_print(adj->lanid),
adj->lanid[ISIS_SYS_ID_LEN]);
vty_out(vty, "\n");
vty_out(vty, " LAN Priority: %u",
adj->prio[adj->level - 1]);
vty_out(vty, ", %s, DIS flaps: %u, Last: %s ago",
isis_disflag2string(
adj->dis_record[ISIS_LEVELS + level - 1]
.dis),
adj->dischanges[level - 1],
time2string(now - (adj->dis_record[ISIS_LEVELS
+ level - 1]
.last_dis_change)));
}
vty_out(vty, "\n");
if (adj->area_address_count) {
vty_out(vty, " Area Address(es):\n");
for (unsigned int i = 0; i < adj->area_address_count;
i++) {
vty_out(vty, " %s\n",
isonet_print(adj->area_addresses[i]
.area_addr,
adj->area_addresses[i]
.addr_len));
}
}
if (adj->ipv4_address_count) {
vty_out(vty, " IPv4 Address(es):\n");
for (unsigned int i = 0; i < adj->ipv4_address_count;
i++)
vty_out(vty, " %s\n",
inet_ntoa(adj->ipv4_addresses[i]));
}
if (adj->ipv6_address_count) {
vty_out(vty, " IPv6 Address(es):\n");
for (unsigned int i = 0; i < adj->ipv6_address_count;
i++) {
char buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &adj->ipv6_addresses[i],
buf, sizeof(buf));
vty_out(vty, " %s\n", buf);
}
}
for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) {
const char *adj_type;
const char *backup;
uint32_t sid;
switch (sra->adj->circuit->circ_type) {
case CIRCUIT_T_BROADCAST:
adj_type = "LAN Adjacency-SID";
sid = sra->u.ladj_sid->sid;
break;
case CIRCUIT_T_P2P:
adj_type = "Adjacency-SID";
sid = sra->u.adj_sid->sid;
break;
default:
continue;
}
backup = (sra->type == ISIS_SR_LAN_BACKUP) ? " (backup)"
: "";
vty_out(vty, " %s %s%s: %u\n",
(sra->nexthop.family == AF_INET) ? "IPv4"
: "IPv6",
adj_type, backup, sid);
}
vty_out(vty, "\n");
}
return;
}
void isis_adj_build_neigh_list(struct list *adjdb, struct list *list)
{
struct isis_adjacency *adj;
struct listnode *node;
if (!list) {
zlog_warn("isis_adj_build_neigh_list(): NULL list");
return;
}
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
if (!adj) {
zlog_warn("isis_adj_build_neigh_list(): NULL adj");
return;
}
if ((adj->adj_state == ISIS_ADJ_UP
|| adj->adj_state == ISIS_ADJ_INITIALIZING))
listnode_add(list, adj->snpa);
}
return;
}
void isis_adj_build_up_list(struct list *adjdb, struct list *list)
{
struct isis_adjacency *adj;
struct listnode *node;
if (adjdb == NULL) {
zlog_warn("isis_adj_build_up_list(): adjacency DB is empty");
return;
}
if (!list) {
zlog_warn("isis_adj_build_up_list(): NULL list");
return;
}
for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) {
if (!adj) {
zlog_warn("isis_adj_build_up_list(): NULL adj");
return;
}
if (adj->adj_state == ISIS_ADJ_UP)
listnode_add(list, adj);
}
return;
}
int isis_adj_usage2levels(enum isis_adj_usage usage)
{
switch (usage) {
case ISIS_ADJ_LEVEL1:
return IS_LEVEL_1;
case ISIS_ADJ_LEVEL2:
return IS_LEVEL_2;
case ISIS_ADJ_LEVEL1AND2:
return IS_LEVEL_1 | IS_LEVEL_2;
default:
break;
}
return 0;
}