/* * 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 #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]++; hook_call(isis_adj_state_change_hook, adj); /* 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); hook_call(isis_adj_state_change_hook, adj); 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]++; hook_call(isis_adj_state_change_hook, adj); /* 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); hook_call(isis_adj_state_change_hook, adj); if (new_state == ISIS_ADJ_DOWN) del = true; } } } 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) 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) 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; }