| /* |
| * 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; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| */ |
| |
| |
| #include <zebra.h> |
| #include <net/ethernet.h> |
| |
| #include "log.h" |
| #include "hash.h" |
| #include "thread.h" |
| #include "linklist.h" |
| #include "vty.h" |
| #include "stream.h" |
| #include "if.h" |
| |
| #include "isisd/dict.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_tlv.h" |
| #include "isisd/isis_lsp.h" |
| #include "isisd/isis_dr.h" |
| #include "isisd/isis_events.h" |
| |
| extern struct isis *isis; |
| extern struct thread_master *master; |
| |
| 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 */ |
| } |
| |
| |
| |
| int |
| isis_run_dr_l1 (struct thread *thread) |
| { |
| struct isis_circuit *circuit; |
| |
| circuit = THREAD_ARG (thread); |
| assert (circuit); |
| |
| if (circuit->u.bc.run_dr_elect[0]) |
| zlog_warn ("isis_run_dr(): run_dr_elect already set for l1"); |
| |
| circuit->u.bc.t_run_dr[0] = NULL; |
| circuit->u.bc.run_dr_elect[0] = 1; |
| |
| return ISIS_OK; |
| } |
| |
| int |
| isis_run_dr_l2 (struct thread *thread) |
| { |
| struct isis_circuit *circuit; |
| |
| circuit = THREAD_ARG (thread); |
| assert (circuit); |
| |
| if (circuit->u.bc.run_dr_elect[1]) |
| zlog_warn ("isis_run_dr(): run_dr_elect already set for l2"); |
| |
| |
| circuit->u.bc.t_run_dr[1] = NULL; |
| circuit->u.bc.run_dr_elect[1] = 1; |
| |
| return ISIS_OK; |
| } |
| |
| 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]++; |
| /* 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 (); |
| u_char own_prio; |
| int biggest_prio = -1; |
| int cmp_res, retval = ISIS_OK; |
| |
| own_prio = circuit->u.bc.priority[level - 1]; |
| adjdb = circuit->u.bc.adjdb[level - 1]; |
| |
| if (!adjdb) { |
| zlog_warn ("isis_dr_elect() adjdb == NULL"); |
| retval = ISIS_WARNING; |
| list_delete (list); |
| goto out; |
| } |
| isis_adj_build_up_list (adjdb, list); |
| |
| /* |
| * Loop the adjacencies and find the one with the biggest priority |
| */ |
| for (node = listhead (list); node; nextnode (node)) { |
| adj = getdata (node); |
| /* 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 ("isis_dr_elect(): multiple adjacencies with same SNPA"); |
| } else { |
| adj_dr = adj; |
| } |
| } |
| } |
| |
| if (!adj_dr) { |
| /* |
| * Could not find the DR - means we are alone and thus the DR |
| */ |
| if ( !circuit->u.bc.is_dr[level - 1]) { |
| list_delete (list); |
| list = NULL; |
| return isis_dr_commence (circuit, level); |
| } |
| goto out; |
| } |
| |
| /* |
| * 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)) { |
| if (!circuit->u.bc.is_dr[level - 1]) { |
| /* |
| * We are the DR -> commence |
| */ |
| list_delete (list); |
| return 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 (node = listhead (list); node; nextnode (node)) { |
| adj = getdata (node); |
| isis_check_dr_change(adj, level); |
| } |
| |
| /* |
| * We are not DR - if we were -> resign |
| */ |
| |
| if (circuit->u.bc.is_dr[level - 1]) { |
| list_delete (list); |
| return isis_dr_resign (circuit, level); |
| } |
| } |
| out: |
| if (list) |
| list_delete (list); |
| return retval; |
| } |
| |
| int |
| isis_dr_resign (struct isis_circuit *circuit, int level) |
| { |
| u_char id[ISIS_SYS_ID_LEN + 2]; |
| |
| zlog_info ("isis_dr_resign l%d", level); |
| |
| circuit->u.bc.is_dr[level - 1] = 0; |
| circuit->u.bc.run_dr_elect[level - 1] = 0; |
| if (circuit->u.bc.t_run_dr[level - 1]) { |
| thread_cancel (circuit->u.bc.t_run_dr[level - 1]); |
| circuit->u.bc.t_run_dr[level - 1] = NULL; |
| } |
| if (circuit->u.bc.t_refresh_pseudo_lsp[level - 1]) { |
| thread_cancel (circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); |
| circuit->u.bc.t_refresh_pseudo_lsp[level - 1] = NULL; |
| } |
| |
| memcpy (id, isis->sysid, ISIS_SYS_ID_LEN); |
| LSP_PSEUDO_ID(id) = circuit->circuit_id; |
| LSP_FRAGMENT(id) = 0; |
| lsp_purge_dr (id, circuit, level); |
| |
| if (level == 1) { |
| memset (circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); |
| |
| if (circuit->t_send_csnp[0]) |
| thread_cancel (circuit->t_send_csnp[0]); |
| |
| circuit->u.bc.t_run_dr[0] = |
| thread_add_timer (master, isis_run_dr_l1, circuit, |
| 2 * circuit->hello_interval[1]); |
| |
| circuit->t_send_psnp[0] = |
| thread_add_timer (master, |
| send_l1_psnp, |
| circuit, |
| isis_jitter (circuit->psnp_interval[level - 1], |
| PSNP_JITTER)); |
| } else { |
| memset (circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); |
| |
| if (circuit->t_send_csnp[1]) |
| thread_cancel (circuit->t_send_csnp[1]); |
| |
| circuit->u.bc.t_run_dr[1] = |
| thread_add_timer (master, isis_run_dr_l2, circuit, |
| 2 * circuit->hello_interval[1]); |
| circuit->t_send_psnp[1] = |
| thread_add_timer (master, |
| send_l2_psnp, |
| circuit, |
| isis_jitter (circuit->psnp_interval[level - 1], |
| PSNP_JITTER)); |
| } |
| |
| thread_add_event (master, isis_event_dis_status_change, circuit, 0); |
| |
| return ISIS_OK; |
| } |
| |
| int |
| isis_dr_commence (struct isis_circuit *circuit, int level) |
| { |
| u_char old_dr[ISIS_SYS_ID_LEN + 2]; |
| |
| zlog_info ("isis_dr_commence l%d", level); |
| |
| /* Lets keep a pause in DR election */ |
| circuit->u.bc.run_dr_elect[level - 1] = 0; |
| if (level == 1) |
| circuit->u.bc.t_run_dr[0] = |
| thread_add_timer (master, isis_run_dr_l1, circuit, |
| 2 * circuit->hello_multiplier[0] * |
| circuit->hello_interval[0]); |
| else |
| circuit->u.bc.t_run_dr[1] = |
| thread_add_timer (master, isis_run_dr_l2, circuit, |
| 2 * circuit->hello_multiplier[1] * |
| circuit->hello_interval[1]); |
| 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_dr (old_dr, circuit, level); |
| } |
| memcpy (circuit->u.bc.l1_desig_is, 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 */ |
| /* if (circuit->t_send_l1_psnp) |
| thread_cancel (circuit->t_send_l1_psnp); */ |
| lsp_l1_pseudo_generate (circuit); |
| |
| circuit->u.bc.t_run_dr[0] = |
| thread_add_timer (master, isis_run_dr_l1, circuit, |
| 2 * circuit->hello_interval[0]); |
| |
| circuit->t_send_csnp[0] = thread_add_timer (master, |
| send_l1_csnp, |
| circuit, |
| isis_jitter |
| (circuit->csnp_interval[level-1], |
| CSNP_JITTER)); |
| } 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_dr (old_dr, circuit, level); |
| } |
| memcpy (circuit->u.bc.l2_desig_is, 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 */ |
| /* if (circuit->t_send_l1_psnp) |
| thread_cancel (circuit->t_send_l1_psnp); */ |
| lsp_l2_pseudo_generate (circuit); |
| |
| circuit->u.bc.t_run_dr[1] = |
| thread_add_timer (master, isis_run_dr_l2, circuit, |
| 2 * circuit->hello_interval[1]); |
| |
| circuit->t_send_csnp[1] = |
| thread_add_timer (master, |
| send_l2_csnp, |
| circuit, |
| isis_jitter (circuit->csnp_interval[level-1], |
| CSNP_JITTER)); |
| |
| } |
| |
| thread_add_event (master, isis_event_dis_status_change, circuit, 0); |
| |
| return ISIS_OK; |
| } |
| |