/* * OSPF version 2 Interface State Machine * From RFC2328 [OSPF Version 2] * Copyright (C) 1999, 2000 Toshiaki Takada * * This file is part of GNU Zebra. * * GNU Zebra is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * GNU Zebra 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 GNU Zebra; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "thread.h" #include "linklist.h" #include "prefix.h" #include "if.h" #include "table.h" #include "log.h" #include "ospfd/ospfd.h" #include "ospfd/ospf_interface.h" #include "ospfd/ospf_ism.h" #include "ospfd/ospf_asbr.h" #include "ospfd/ospf_lsa.h" #include "ospfd/ospf_lsdb.h" #include "ospfd/ospf_neighbor.h" #include "ospfd/ospf_nsm.h" #include "ospfd/ospf_network.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_packet.h" #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_snmp.h" /* elect DR and BDR. Refer to RFC2319 section 9.4 */ static struct ospf_neighbor * ospf_dr_election_sub (struct list *routers) { struct listnode *node; struct ospf_neighbor *nbr, *max = NULL; /* Choose highest router priority. In case of tie, choose highest Router ID. */ for (ALL_LIST_ELEMENTS_RO (routers, node, nbr)) { if (max == NULL) max = nbr; else { if (max->priority < nbr->priority) max = nbr; else if (max->priority == nbr->priority) if (IPV4_ADDR_CMP (&max->router_id, &nbr->router_id) < 0) max = nbr; } } return max; } static struct ospf_neighbor * ospf_elect_dr (struct ospf_interface *oi, struct list *el_list) { struct list *dr_list; struct listnode *node; struct ospf_neighbor *nbr, *dr = NULL, *bdr = NULL; dr_list = list_new (); /* Add neighbors to the list. */ for (ALL_LIST_ELEMENTS_RO (el_list, node, nbr)) { /* neighbor declared to be DR. */ if (NBR_IS_DR (nbr)) listnode_add (dr_list, nbr); /* Preserve neighbor BDR. */ if (IPV4_ADDR_SAME (&BDR (oi), &nbr->address.u.prefix4)) bdr = nbr; } /* Elect Designated Router. */ if (listcount (dr_list) > 0) dr = ospf_dr_election_sub (dr_list); else dr = bdr; /* Set DR to interface. */ if (dr) DR (oi) = dr->address.u.prefix4; else DR (oi).s_addr = 0; list_delete (dr_list); return dr; } static struct ospf_neighbor * ospf_elect_bdr (struct ospf_interface *oi, struct list *el_list) { struct list *bdr_list, *no_dr_list; struct listnode *node; struct ospf_neighbor *nbr, *bdr = NULL; bdr_list = list_new (); no_dr_list = list_new (); /* Add neighbors to the list. */ for (ALL_LIST_ELEMENTS_RO (el_list, node, nbr)) { /* neighbor declared to be DR. */ if (NBR_IS_DR (nbr)) continue; /* neighbor declared to be BDR. */ if (NBR_IS_BDR (nbr)) listnode_add (bdr_list, nbr); listnode_add (no_dr_list, nbr); } /* Elect Backup Designated Router. */ if (listcount (bdr_list) > 0) bdr = ospf_dr_election_sub (bdr_list); else bdr = ospf_dr_election_sub (no_dr_list); /* Set BDR to interface. */ if (bdr) BDR (oi) = bdr->address.u.prefix4; else BDR (oi).s_addr = 0; list_delete (bdr_list); list_delete (no_dr_list); return bdr; } static int ospf_ism_state (struct ospf_interface *oi) { if (IPV4_ADDR_SAME (&DR (oi), &oi->address->u.prefix4)) return ISM_DR; else if (IPV4_ADDR_SAME (&BDR (oi), &oi->address->u.prefix4)) return ISM_Backup; else return ISM_DROther; } static void ospf_dr_eligible_routers (struct route_table *nbrs, struct list *el_list) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top (nbrs); rn; rn = route_next (rn)) if ((nbr = rn->info) != NULL) /* Ignore 0.0.0.0 node*/ if (nbr->router_id.s_addr != 0) /* Is neighbor eligible? */ if (nbr->priority > 0) /* Is neighbor upper 2-Way? */ if (nbr->state >= NSM_TwoWay) listnode_add (el_list, nbr); } /* Generate AdjOK? NSM event. */ static void ospf_dr_change (struct ospf *ospf, struct route_table *nbrs) { struct route_node *rn; struct ospf_neighbor *nbr; for (rn = route_top (nbrs); rn; rn = route_next (rn)) if ((nbr = rn->info) != NULL) /* Ignore 0.0.0.0 node*/ if (nbr->router_id.s_addr != 0) /* Is neighbor upper 2-Way? */ if (nbr->state >= NSM_TwoWay) /* Ignore myself. */ if (!IPV4_ADDR_SAME (&nbr->router_id, &ospf->router_id)) OSPF_NSM_EVENT_SCHEDULE (nbr, NSM_AdjOK); } static int ospf_dr_election (struct ospf_interface *oi) { struct in_addr old_dr, old_bdr; int old_state, new_state; struct list *el_list; /* backup current values. */ old_dr = DR (oi); old_bdr = BDR (oi); old_state = oi->state; el_list = list_new (); /* List eligible routers. */ ospf_dr_eligible_routers (oi->nbrs, el_list); /* First election of DR and BDR. */ ospf_elect_bdr (oi, el_list); ospf_elect_dr (oi, el_list); new_state = ospf_ism_state (oi); zlog_debug ("DR-Election[1st]: Backup %s", inet_ntoa (BDR (oi))); zlog_debug ("DR-Election[1st]: DR %s", inet_ntoa (DR (oi))); if (new_state != old_state && !(new_state == ISM_DROther && old_state < ISM_DROther)) { ospf_elect_bdr (oi, el_list); ospf_elect_dr (oi, el_list); new_state = ospf_ism_state (oi); zlog_debug ("DR-Election[2nd]: Backup %s", inet_ntoa (BDR (oi))); zlog_debug ("DR-Election[2nd]: DR %s", inet_ntoa (DR (oi))); } list_delete (el_list); /* if DR or BDR changes, cause AdjOK? neighbor event. */ if (!IPV4_ADDR_SAME (&old_dr, &DR (oi)) || !IPV4_ADDR_SAME (&old_bdr, &BDR (oi))) ospf_dr_change (oi->ospf, oi->nbrs); return new_state; } int ospf_hello_timer (struct thread *thread) { struct ospf_interface *oi; oi = THREAD_ARG (thread); oi->t_hello = NULL; if (IS_DEBUG_OSPF (ism, ISM_TIMERS)) zlog (NULL, LOG_DEBUG, "ISM[%s]: Timer (Hello timer expire)", IF_NAME (oi)); /* Sending hello packet. */ ospf_hello_send (oi); /* Hello timer set. */ OSPF_HELLO_TIMER_ON (oi); return 0; } static int ospf_wait_timer (struct thread *thread) { struct ospf_interface *oi; oi = THREAD_ARG (thread); oi->t_wait = NULL; if (IS_DEBUG_OSPF (ism, ISM_TIMERS)) zlog (NULL, LOG_DEBUG, "ISM[%s]: Timer (Wait timer expire)", IF_NAME (oi)); OSPF_ISM_EVENT_SCHEDULE (oi, ISM_WaitTimer); return 0; } /* Hook function called after ospf ISM event is occured. And vty's network command invoke this function after making interface structure. */ static void ism_timer_set (struct ospf_interface *oi) { switch (oi->state) { case ISM_Down: /* First entry point of ospf interface state machine. In this state interface parameters must be set to initial values, and timers are reset also. */ OSPF_ISM_TIMER_OFF (oi->t_hello); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_OFF (oi->t_ls_ack); break; case ISM_Loopback: /* In this state, the interface may be looped back and will be unavailable for regular data traffic. */ OSPF_ISM_TIMER_OFF (oi->t_hello); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_OFF (oi->t_ls_ack); break; case ISM_Waiting: /* The router is trying to determine the identity of DRouter and BDRouter. The router begin to receive and send Hello Packets. */ /* send first hello immediately */ OSPF_ISM_TIMER_MSEC_ON (oi->t_hello, ospf_hello_timer, 1); OSPF_ISM_TIMER_ON (oi->t_wait, ospf_wait_timer, OSPF_IF_PARAM (oi, v_wait)); OSPF_ISM_TIMER_OFF (oi->t_ls_ack); break; case ISM_PointToPoint: /* The interface connects to a physical Point-to-point network or virtual link. The router attempts to form an adjacency with neighboring router. Hello packets are also sent. */ /* send first hello immediately */ OSPF_ISM_TIMER_MSEC_ON (oi->t_hello, ospf_hello_timer, 1); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_ON (oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_DROther: /* The network type of the interface is broadcast or NBMA network, and the router itself is neither Designated Router nor Backup Designated Router. */ OSPF_HELLO_TIMER_ON (oi); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_ON (oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_Backup: /* The network type of the interface is broadcast os NBMA network, and the router is Backup Designated Router. */ OSPF_HELLO_TIMER_ON (oi); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_ON (oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; case ISM_DR: /* The network type of the interface is broadcast or NBMA network, and the router is Designated Router. */ OSPF_HELLO_TIMER_ON (oi); OSPF_ISM_TIMER_OFF (oi->t_wait); OSPF_ISM_TIMER_ON (oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); break; } } static int ism_interface_up (struct ospf_interface *oi) { int next_state = 0; /* if network type is point-to-point, Point-to-MultiPoint or virtual link, the state transitions to Point-to-Point. */ if (oi->type == OSPF_IFTYPE_POINTOPOINT || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT || oi->type == OSPF_IFTYPE_VIRTUALLINK) next_state = ISM_PointToPoint; /* Else if the router is not eligible to DR, the state transitions to DROther. */ else if (PRIORITY (oi) == 0) /* router is eligible? */ next_state = ISM_DROther; else /* Otherwise, the state transitions to Waiting. */ next_state = ISM_Waiting; if (oi->type == OSPF_IFTYPE_NBMA) ospf_nbr_nbma_if_update (oi->ospf, oi); /* ospf_ism_event (t); */ return next_state; } static int ism_loop_ind (struct ospf_interface *oi) { int ret = 0; /* call ism_interface_down. */ /* ret = ism_interface_down (oi); */ return ret; } /* Interface down event handler. */ static int ism_interface_down (struct ospf_interface *oi) { ospf_if_cleanup (oi); return 0; } static int ism_backup_seen (struct ospf_interface *oi) { return ospf_dr_election (oi); } static int ism_wait_timer (struct ospf_interface *oi) { return ospf_dr_election (oi); } static int ism_neighbor_change (struct ospf_interface *oi) { return ospf_dr_election (oi); } static int ism_ignore (struct ospf_interface *oi) { if (IS_DEBUG_OSPF (ism, ISM_EVENTS)) zlog (NULL, LOG_DEBUG, "ISM[%s]: ism_ignore called", IF_NAME (oi)); return 0; } /* Interface State Machine */ struct { int (*func) (struct ospf_interface *); int next_state; } ISM [OSPF_ISM_STATE_MAX][OSPF_ISM_EVENT_MAX] = { { /* DependUpon: dummy state. */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_DependUpon }, /* InterfaceUp */ { ism_ignore, ISM_DependUpon }, /* WaitTimer */ { ism_ignore, ISM_DependUpon }, /* BackupSeen */ { ism_ignore, ISM_DependUpon }, /* NeighborChange */ { ism_ignore, ISM_DependUpon }, /* LoopInd */ { ism_ignore, ISM_DependUpon }, /* UnloopInd */ { ism_ignore, ISM_DependUpon }, /* InterfaceDown */ }, { /* Down:*/ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_interface_up, ISM_DependUpon }, /* InterfaceUp */ { ism_ignore, ISM_Down }, /* WaitTimer */ { ism_ignore, ISM_Down }, /* BackupSeen */ { ism_ignore, ISM_Down }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_Down }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* Loopback: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_Loopback }, /* InterfaceUp */ { ism_ignore, ISM_Loopback }, /* WaitTimer */ { ism_ignore, ISM_Loopback }, /* BackupSeen */ { ism_ignore, ISM_Loopback }, /* NeighborChange */ { ism_ignore, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_Down }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* Waiting: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_Waiting }, /* InterfaceUp */ { ism_wait_timer, ISM_DependUpon }, /* WaitTimer */ { ism_backup_seen, ISM_DependUpon }, /* BackupSeen */ { ism_ignore, ISM_Waiting }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_Waiting }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* Point-to-Point: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_PointToPoint }, /* InterfaceUp */ { ism_ignore, ISM_PointToPoint }, /* WaitTimer */ { ism_ignore, ISM_PointToPoint }, /* BackupSeen */ { ism_ignore, ISM_PointToPoint }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_PointToPoint }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* DROther: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_DROther }, /* InterfaceUp */ { ism_ignore, ISM_DROther }, /* WaitTimer */ { ism_ignore, ISM_DROther }, /* BackupSeen */ { ism_neighbor_change, ISM_DependUpon }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_DROther }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* Backup: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_Backup }, /* InterfaceUp */ { ism_ignore, ISM_Backup }, /* WaitTimer */ { ism_ignore, ISM_Backup }, /* BackupSeen */ { ism_neighbor_change, ISM_DependUpon }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_Backup }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, { /* DR: */ { ism_ignore, ISM_DependUpon }, /* NoEvent */ { ism_ignore, ISM_DR }, /* InterfaceUp */ { ism_ignore, ISM_DR }, /* WaitTimer */ { ism_ignore, ISM_DR }, /* BackupSeen */ { ism_neighbor_change, ISM_DependUpon }, /* NeighborChange */ { ism_loop_ind, ISM_Loopback }, /* LoopInd */ { ism_ignore, ISM_DR }, /* UnloopInd */ { ism_interface_down, ISM_Down }, /* InterfaceDown */ }, }; static const char *ospf_ism_event_str[] = { "NoEvent", "InterfaceUp", "WaitTimer", "BackupSeen", "NeighborChange", "LoopInd", "UnLoopInd", "InterfaceDown", }; static void ism_change_state (struct ospf_interface *oi, int state) { int old_state; struct ospf_lsa *lsa; /* Logging change of state. */ if (IS_DEBUG_OSPF (ism, ISM_STATUS)) zlog (NULL, LOG_DEBUG, "ISM[%s]: State change %s -> %s", IF_NAME (oi), LOOKUP (ospf_ism_state_msg, oi->state), LOOKUP (ospf_ism_state_msg, state)); old_state = oi->state; oi->state = state; oi->state_change++; #ifdef HAVE_SNMP /* Terminal state or regression */ if ((state == ISM_DR) || (state == ISM_Backup) || (state == ISM_DROther) || (state == ISM_PointToPoint) || (state < old_state)) { /* ospfVirtIfStateChange */ if (oi->type == OSPF_IFTYPE_VIRTUALLINK) ospfTrapVirtIfStateChange (oi); /* ospfIfStateChange */ else ospfTrapIfStateChange (oi); } #endif /* Set multicast memberships appropriately for new state. */ ospf_if_set_multicast(oi); if (old_state == ISM_Down || state == ISM_Down) ospf_check_abr_status (oi->ospf); /* Originate router-LSA. */ if (state == ISM_Down) { if (oi->area->act_ints > 0) oi->area->act_ints--; } else if (old_state == ISM_Down) oi->area->act_ints++; /* schedule router-LSA originate. */ ospf_router_lsa_update_area (oi->area); /* Originate network-LSA. */ if (old_state != ISM_DR && state == ISM_DR) ospf_network_lsa_update (oi); else if (old_state == ISM_DR && state != ISM_DR) { /* Free self originated network LSA. */ lsa = oi->network_lsa_self; if (lsa) ospf_lsa_flush_area (lsa, oi->area); ospf_lsa_unlock (&oi->network_lsa_self); oi->network_lsa_self = NULL; } #ifdef HAVE_OPAQUE_LSA ospf_opaque_ism_change (oi, old_state); #endif /* HAVE_OPAQUE_LSA */ /* Check area border status. */ ospf_check_abr_status (oi->ospf); } /* Execute ISM event process. */ int ospf_ism_event (struct thread *thread) { int event; int next_state; struct ospf_interface *oi; oi = THREAD_ARG (thread); event = THREAD_VAL (thread); /* Call function. */ next_state = (*(ISM [oi->state][event].func))(oi); if (! next_state) next_state = ISM [oi->state][event].next_state; if (IS_DEBUG_OSPF (ism, ISM_EVENTS)) zlog (NULL, LOG_DEBUG, "ISM[%s]: %s (%s)", IF_NAME (oi), LOOKUP (ospf_ism_state_msg, oi->state), ospf_ism_event_str[event]); /* If state is changed. */ if (next_state != oi->state) ism_change_state (oi, next_state); /* Make sure timer is set. */ ism_timer_set (oi); return 0; }