summaryrefslogtreecommitdiffstats
path: root/isisd/isis_dr.c
diff options
context:
space:
mode:
Diffstat (limited to 'isisd/isis_dr.c')
-rw-r--r--isisd/isis_dr.c326
1 files changed, 326 insertions, 0 deletions
diff --git a/isisd/isis_dr.c b/isisd/isis_dr.c
new file mode 100644
index 0000000..400982f
--- /dev/null
+++ b/isisd/isis_dr.c
@@ -0,0 +1,326 @@
+/*
+ * 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
+ *
+ * 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 "hash.h"
+#include "thread.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 thread *thread)
+{
+ struct isis_circuit_arg *arg = THREAD_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;
+ THREAD_OFF(circuit->u.bc.t_run_dr[level - 1]);
+ THREAD_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);
+
+ thread_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);
+
+ thread_add_timer(master, send_l2_psnp, circuit,
+ isis_jitter(circuit->psnp_interval[level - 1],
+ PSNP_JITTER),
+ &circuit->t_send_psnp[1]);
+ }
+
+ THREAD_OFF(circuit->t_send_csnp[level - 1]);
+
+ thread_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]);
+
+
+ thread_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);
+
+ thread_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);
+
+ thread_add_timer(master, send_l2_csnp, circuit,
+ isis_jitter(circuit->csnp_interval[level - 1],
+ CSNP_JITTER),
+ &circuit->t_send_csnp[1]);
+ }
+
+ thread_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]);
+ thread_add_event(master, isis_event_dis_status_change, circuit, 0,
+ NULL);
+
+ return ISIS_OK;
+}