// SPDX-License-Identifier: GPL-2.0-or-later /* * IS-IS Rout(e)ing protocol - isis_dr.c * IS-IS designated router related routines * * Copyright (C) 2001,2002 Sampo Saaristo * Tampere University of Technology * Institute of Communications Engineering */ #include #include "log.h" #include "hash.h" #include "frrevent.h" #include "linklist.h" #include "vty.h" #include "stream.h" #include "if.h" #include "isisd/isis_constants.h" #include "isisd/isis_common.h" #include "isisd/isis_misc.h" #include "isisd/isis_flags.h" #include "isisd/isis_circuit.h" #include "isisd/isisd.h" #include "isisd/isis_adjacency.h" #include "isisd/isis_constants.h" #include "isisd/isis_pdu.h" #include "isisd/isis_lsp.h" #include "isisd/isis_dr.h" #include "isisd/isis_events.h" const char *isis_disflag2string(int disflag) { switch (disflag) { case ISIS_IS_NOT_DIS: return "is not DIS"; case ISIS_IS_DIS: return "is DIS"; case ISIS_WAS_DIS: return "was DIS"; default: return "unknown DIS state"; } return NULL; /* not reached */ } void isis_run_dr(struct event *thread) { struct isis_circuit_arg *arg = EVENT_ARG(thread); assert(arg); struct isis_circuit *circuit = arg->circuit; int level = arg->level; assert(circuit); if (circuit->circ_type != CIRCUIT_T_BROADCAST) { zlog_warn("%s: scheduled for non broadcast circuit from %s:%d", __func__, thread->xref->xref.file, thread->xref->xref.line); return; } if (circuit->u.bc.run_dr_elect[level - 1]) zlog_warn("%s: run_dr_elect already set for l%d", __func__, level); circuit->u.bc.t_run_dr[level - 1] = NULL; circuit->u.bc.run_dr_elect[level - 1] = 1; } static int isis_check_dr_change(struct isis_adjacency *adj, int level) { int i; if (adj->dis_record[level - 1].dis != adj->dis_record[(1 * ISIS_LEVELS) + level - 1].dis) /* was there a DIS state transition ? */ { adj->dischanges[level - 1]++; adj->circuit->desig_changes[level - 1]++; /* ok rotate the history list through */ for (i = DIS_RECORDS - 1; i > 0; i--) { adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = adj->dis_record[((i - 1) * ISIS_LEVELS) + level - 1] .dis; adj->dis_record[(i * ISIS_LEVELS) + level - 1] .last_dis_change = adj->dis_record[((i - 1) * ISIS_LEVELS) + level - 1] .last_dis_change; } } return ISIS_OK; } int isis_dr_elect(struct isis_circuit *circuit, int level) { struct list *adjdb; struct listnode *node; struct isis_adjacency *adj, *adj_dr = NULL; struct list *list = list_new(); uint8_t own_prio; int biggest_prio = -1; int cmp_res, retval = ISIS_OK; own_prio = circuit->priority[level - 1]; adjdb = circuit->u.bc.adjdb[level - 1]; if (!adjdb) { zlog_warn("%s adjdb == NULL", __func__); list_delete(&list); return ISIS_WARNING; } isis_adj_build_up_list(adjdb, list); /* * Loop the adjacencies and find the one with the biggest priority */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) { /* clear flag for show output */ adj->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; adj->dis_record[level - 1].last_dis_change = time(NULL); if (adj->prio[level - 1] > biggest_prio) { biggest_prio = adj->prio[level - 1]; adj_dr = adj; } else if (adj->prio[level - 1] == biggest_prio) { /* * Comparison of MACs breaks a tie */ if (adj_dr) { cmp_res = memcmp(adj_dr->snpa, adj->snpa, ETH_ALEN); if (cmp_res < 0) { adj_dr = adj; } if (cmp_res == 0) zlog_warn( "%s: multiple adjacencies with same SNPA", __func__); } else { adj_dr = adj; } } } if (!adj_dr) { /* * Could not find the DR - means we are alone. Resign if we were * DR. */ if (circuit->u.bc.is_dr[level - 1]) retval = isis_dr_resign(circuit, level); list_delete(&list); return retval; } /* * Now we have the DR adjacency, compare it to self */ if (adj_dr->prio[level - 1] < own_prio || (adj_dr->prio[level - 1] == own_prio && memcmp(adj_dr->snpa, circuit->u.bc.snpa, ETH_ALEN) < 0)) { adj_dr->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; adj_dr->dis_record[level - 1].last_dis_change = time(NULL); /* rotate the history log */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) isis_check_dr_change(adj, level); /* We are the DR, commence DR */ if (circuit->u.bc.is_dr[level - 1] == 0 && listcount(list) > 0) retval = isis_dr_commence(circuit, level); } else { /* ok we have found the DIS - lets mark the adjacency */ /* set flag for show output */ adj_dr->dis_record[level - 1].dis = ISIS_IS_DIS; adj_dr->dis_record[level - 1].last_dis_change = time(NULL); /* now loop through a second time to check if there has been a * DIS change * if yes rotate the history log */ for (ALL_LIST_ELEMENTS_RO(list, node, adj)) isis_check_dr_change(adj, level); /* * We are not DR - if we were -> resign */ if (circuit->u.bc.is_dr[level - 1]) retval = isis_dr_resign(circuit, level); } list_delete(&list); return retval; } int isis_dr_resign(struct isis_circuit *circuit, int level) { uint8_t id[ISIS_SYS_ID_LEN + 2]; if (IS_DEBUG_EVENTS) zlog_debug("%s l%d", __func__, level); circuit->u.bc.is_dr[level - 1] = 0; circuit->u.bc.run_dr_elect[level - 1] = 0; EVENT_OFF(circuit->u.bc.t_run_dr[level - 1]); EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); circuit->lsp_regenerate_pending[level - 1] = 0; memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN); LSP_PSEUDO_ID(id) = circuit->circuit_id; LSP_FRAGMENT(id) = 0; lsp_purge_pseudo(id, circuit, level); if (level == 1) { memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); event_add_timer(master, send_l1_psnp, circuit, isis_jitter(circuit->psnp_interval[level - 1], PSNP_JITTER), &circuit->t_send_psnp[0]); } else { memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); event_add_timer(master, send_l2_psnp, circuit, isis_jitter(circuit->psnp_interval[level - 1], PSNP_JITTER), &circuit->t_send_psnp[1]); } EVENT_OFF(circuit->t_send_csnp[level - 1]); event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); return ISIS_OK; } int isis_dr_commence(struct isis_circuit *circuit, int level) { uint8_t old_dr[ISIS_SYS_ID_LEN + 2]; if (IS_DEBUG_EVENTS) zlog_debug("%s l%d", __func__, level); /* Lets keep a pause in DR election */ circuit->u.bc.run_dr_elect[level - 1] = 0; circuit->u.bc.is_dr[level - 1] = 1; if (level == 1) { memcpy(old_dr, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(old_dr) = 0; if (LSP_PSEUDO_ID(old_dr)) { /* there was a dr elected, purge its LSPs from the db */ lsp_purge_pseudo(old_dr, circuit, level); } memcpy(circuit->u.bc.l1_desig_is, circuit->isis->sysid, ISIS_SYS_ID_LEN); *(circuit->u.bc.l1_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id; assert(circuit->circuit_id); /* must be non-zero */ lsp_generate_pseudo(circuit, 1); event_add_timer(master, send_l1_csnp, circuit, isis_jitter(circuit->csnp_interval[level - 1], CSNP_JITTER), &circuit->t_send_csnp[0]); } else { memcpy(old_dr, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1); LSP_FRAGMENT(old_dr) = 0; if (LSP_PSEUDO_ID(old_dr)) { /* there was a dr elected, purge its LSPs from the db */ lsp_purge_pseudo(old_dr, circuit, level); } memcpy(circuit->u.bc.l2_desig_is, circuit->isis->sysid, ISIS_SYS_ID_LEN); *(circuit->u.bc.l2_desig_is + ISIS_SYS_ID_LEN) = circuit->circuit_id; assert(circuit->circuit_id); /* must be non-zero */ lsp_generate_pseudo(circuit, 2); event_add_timer(master, send_l2_csnp, circuit, isis_jitter(circuit->csnp_interval[level - 1], CSNP_JITTER), &circuit->t_send_csnp[1]); } event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], 2 * circuit->hello_interval[level - 1], &circuit->u.bc.t_run_dr[level - 1]); event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); return ISIS_OK; }