| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #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); |
| |
| 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++) |
| { |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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 |
| |
| isis_notif_adj_state_change(adj, new_state, reason); |
| #endif |
| |
| 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]++; |
| |
| |
| 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]); |
| } |
| |
| |
| |
| 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]++; |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| adj = THREAD_ARG(thread); |
| assert(adj); |
| adj->t_expire = NULL; |
| |
| |
| isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "holding time expired"); |
| |
| return 0; |
| } |
| |
| |
| |
| |
| 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); |
| 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); |
| 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; |
| } |