diff options
author | Renato Westphal <renato@opensourcerouting.org> | 2021-05-31 15:27:51 +0200 |
---|---|---|
committer | Renato Westphal <renato@opensourcerouting.org> | 2021-07-05 16:43:02 +0200 |
commit | 1051417011b98dcebba527e0c9053db6fc7c6e30 (patch) | |
tree | 4e07b4e25d3de792d20c6a78eb095b45a77ae731 | |
parent | ospfd: rename the graceful restart header (diff) | |
download | frr-1051417011b98dcebba527e0c9053db6fc7c6e30.tar.xz frr-1051417011b98dcebba527e0c9053db6fc7c6e30.zip |
ospfd: introduce support for Graceful Restart (restarting mode)
RFC 3623 specifies the Graceful Restart enhancement to the OSPF
routing protocol. This PR implements support for the restarting mode,
whereas the helper mode was implemented by #6811.
This work is based on #6782, which implemented the pre-restart part
and settled the foundations for the post-restart part (behavioral
changes, GR exit conditions, and on-exit actions).
Here's a quick summary of how the GR restarting mode works:
* GR can be enabled on a per-instance basis using the `graceful-restart
[grace-period (1-1800)]` command;
* To perform a graceful shutdown, the `graceful-restart prepare ospf`
EXEC-level command needs to be issued before restarting the ospfd
daemon (there's no specific requirement on how the daemon should
be restarted);
* `graceful-restart prepare ospf` will initiate the graceful restart
for all GR-enabled instances by taking the following actions:
o Flooding Grace-LSAs over all interfaces
o Freezing the OSPF routes in the RIB
o Saving the end of the grace period in non-volatile memory (a JSON
file stored in `$frr_statedir`)
* Once ospfd is started again, it will follow the procedures
described in RFC 3623 until it detects it's time to exit the graceful
restart (either successfully or unsuccessfully).
Testing done:
* New topotest featuring a multi-area OSPF topology (including stub
and NSSA areas);
* Successful interop tests against IOS-XR routers acting as helpers.
Co-authored-by: GalaxyGorilla <sascha@netdef.org>
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | doc/user/ospfd.rst | 24 | ||||
-rw-r--r-- | lib/command.h | 2 | ||||
-rw-r--r-- | ospfd/ospf_abr.c | 6 | ||||
-rw-r--r-- | ospfd/ospf_ase.c | 10 | ||||
-rw-r--r-- | ospfd/ospf_flood.c | 34 | ||||
-rw-r--r-- | ospfd/ospf_flood.h | 3 | ||||
-rw-r--r-- | ospfd/ospf_gr.c | 824 | ||||
-rw-r--r-- | ospfd/ospf_gr.h | 11 | ||||
-rw-r--r-- | ospfd/ospf_gr_helper.c | 11 | ||||
-rw-r--r-- | ospfd/ospf_lsa.c | 46 | ||||
-rw-r--r-- | ospfd/ospf_main.c | 2 | ||||
-rw-r--r-- | ospfd/ospf_nsm.c | 4 | ||||
-rw-r--r-- | ospfd/ospf_packet.c | 11 | ||||
-rw-r--r-- | ospfd/ospf_spf.c | 2 | ||||
-rw-r--r-- | ospfd/ospf_spf.h | 1 | ||||
-rw-r--r-- | ospfd/ospf_vty.c | 15 | ||||
-rw-r--r-- | ospfd/ospf_zebra.c | 62 | ||||
-rw-r--r-- | ospfd/ospf_zebra.h | 2 | ||||
-rw-r--r-- | ospfd/ospfd.c | 19 | ||||
-rw-r--r-- | ospfd/ospfd.h | 13 | ||||
-rw-r--r-- | ospfd/subdir.am | 3 |
22 files changed, 1089 insertions, 17 deletions
diff --git a/configure.ac b/configure.ac index 6108a3752..2a8411d12 100644 --- a/configure.ac +++ b/configure.ac @@ -2529,6 +2529,7 @@ AC_SUBST([frr_statedir]) AC_DEFINE_UNQUOTED([LDPD_SOCKET], ["$frr_statedir%s%s/ldpd.sock"], [ldpd control socket]) AC_DEFINE_UNQUOTED([ZEBRA_SERV_PATH], ["$frr_statedir%s%s/zserv.api"], [zebra api socket]) AC_DEFINE_UNQUOTED([BFDD_CONTROL_SOCKET], ["$frr_statedir%s%s/bfdd.sock"], [bfdd control socket]) +AC_DEFINE_UNQUOTED([OSPFD_GR_STATE], ["$frr_statedir%s/ospfd-gr.json"], [ospfd GR state information]) AC_DEFINE_UNQUOTED([DAEMON_VTY_DIR], ["$frr_statedir%s%s"], [daemon vty directory]) AC_DEFINE_UNQUOTED([DAEMON_DB_DIR], ["$frr_statedir"], [daemon database directory]) diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index 8d67ec865..e8ca39472 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -710,8 +710,17 @@ Redistribution -Graceful Restart Helper -======================= +Graceful Restart +================ + +.. clicmd:: graceful-restart [grace-period (1-1800)] + + + Configure Graceful Restart (RFC 3623) restarting support. + When enabled, the default grace period is 120 seconds. + + To perform a graceful shutdown, the "graceful-restart prepare ip ospf" + EXEC-level command needs to be issued before restarting the ospfd daemon. .. clicmd:: graceful-restart helper-only [A.B.C.D] @@ -743,6 +752,17 @@ Graceful Restart Helper restarts. By default, it supports both planned and unplanned outages. + +.. clicmd:: graceful-restart prepare ip ospf + + + Initiate a graceful restart for all OSPF instances configured with the + "graceful-restart" command. The ospfd daemon should be restarted during + the instance-specific grace period, otherwise the graceful restart will fail. + + This is an EXEC-level command. + + .. _showing-ospf-information: Showing Information diff --git a/lib/command.h b/lib/command.h index 13bd61e9f..2b50bc237 100644 --- a/lib/command.h +++ b/lib/command.h @@ -438,6 +438,8 @@ struct cmd_node { #define BFD_PROFILE_STR "BFD profile.\n" #define BFD_PROFILE_NAME_STR "BFD profile name.\n" #define SHARP_STR "Sharp Routing Protocol\n" +#define OSPF_GR_STR \ + "OSPF non-stop forwarding (NSF) also known as OSPF Graceful Restart\n" #define CMD_VNI_RANGE "(1-16777215)" #define CONF_BACKUP_EXT ".sav" diff --git a/ospfd/ospf_abr.c b/ospfd/ospf_abr.c index a6027ee9d..6a009d214 100644 --- a/ospfd/ospf_abr.c +++ b/ospfd/ospf_abr.c @@ -1694,6 +1694,9 @@ static void ospf_abr_manage_discard_routes(struct ospf *ospf) static void ospf_abr_nssa_task(struct ospf *ospf) /* called only if any_nssa */ { + if (ospf->gr_info.restart_in_progress) + return; + if (IS_DEBUG_OSPF_NSSA) zlog_debug("Check for NSSA-ABR Tasks():"); @@ -1758,6 +1761,9 @@ static void ospf_abr_nssa_task(struct ospf *ospf) /* called only if any_nssa */ summary-LSA origination and flooding. */ void ospf_abr_task(struct ospf *ospf) { + if (ospf->gr_info.restart_in_progress) + return; + if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_abr_task(): Start"); diff --git a/ospfd/ospf_ase.c b/ospfd/ospf_ase.c index 51da4a55a..aaacebca1 100644 --- a/ospfd/ospf_ase.c +++ b/ospfd/ospf_ase.c @@ -609,6 +609,16 @@ static int ospf_ase_calculate_timer(struct thread *t) + (stop_time.tv_usec - start_time.tv_usec)); } + + /* + * Uninstall remnant routes that were installed before the restart, but + * that are no longer valid. + */ + if (ospf->gr_info.finishing_restart) { + ospf_zebra_gr_disable(ospf); + ospf->gr_info.finishing_restart = false; + } + return 0; } diff --git a/ospfd/ospf_flood.c b/ospfd/ospf_flood.c index a52812b86..d11c441e3 100644 --- a/ospfd/ospf_flood.c +++ b/ospfd/ospf_flood.c @@ -379,8 +379,8 @@ int ospf_flood(struct ospf *ospf, struct ospf_neighbor *nbr, SET_FLAG(new->flags, OSPF_LSA_RECEIVED); (void)ospf_lsa_is_self_originated(ospf, new); /* Let it set the flag */ - /* Received Grace LSA */ - if (IS_GRACE_LSA(new)) { + /* Received non-self-originated Grace LSA */ + if (IS_GRACE_LSA(new) && !IS_LSA_SELF(new)) { if (IS_LSA_MAXAGE(new)) { @@ -445,9 +445,9 @@ int ospf_flood(struct ospf *ospf, struct ospf_neighbor *nbr, } /* OSPF LSA flooding -- RFC2328 Section 13.3. */ -static int ospf_flood_through_interface(struct ospf_interface *oi, - struct ospf_neighbor *inbr, - struct ospf_lsa *lsa) +int ospf_flood_through_interface(struct ospf_interface *oi, + struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) { struct ospf_neighbor *onbr; struct route_node *rn; @@ -1031,6 +1031,18 @@ void ospf_ls_retransmit_delete_nbr_as(struct ospf *ospf, struct ospf_lsa *lsa) flushing an LSA from the whole domain. */ void ospf_lsa_flush_area(struct ospf_lsa *lsa, struct ospf_area *area) { + struct ospf *ospf = area->ospf; + + if (ospf_lsa_is_self_originated(ospf, lsa) + && ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s:LSA[Type%d:%pI4]: Graceful Restart in progress -- not flushing self-originated LSA", + ospf_get_name(ospf), lsa->data->type, + &lsa->data->id); + return; + } + /* Reset the lsa origination time such that it gives more time for the ACK to be received and avoid retransmissions */ @@ -1041,11 +1053,21 @@ void ospf_lsa_flush_area(struct ospf_lsa *lsa, struct ospf_area *area) monotime(&lsa->tv_recv); lsa->tv_orig = lsa->tv_recv; ospf_flood_through_area(area, NULL, lsa); - ospf_lsa_maxage(area->ospf, lsa); + ospf_lsa_maxage(ospf, lsa); } void ospf_lsa_flush_as(struct ospf *ospf, struct ospf_lsa *lsa) { + if (ospf_lsa_is_self_originated(ospf, lsa) + && ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s:LSA[Type%d:%pI4]: Graceful Restart in progress -- not flushing self-originated LSA", + ospf_get_name(ospf), lsa->data->type, + &lsa->data->id); + return; + } + /* Reset the lsa origination time such that it gives more time for the ACK to be received and avoid retransmissions */ diff --git a/ospfd/ospf_flood.h b/ospfd/ospf_flood.h index 6f7ecfccf..95a5b358c 100644 --- a/ospfd/ospf_flood.h +++ b/ospfd/ospf_flood.h @@ -30,6 +30,9 @@ extern int ospf_flood_through_area(struct ospf_area *, struct ospf_neighbor *, struct ospf_lsa *); extern int ospf_flood_through_as(struct ospf *, struct ospf_neighbor *, struct ospf_lsa *); +extern int ospf_flood_through_interface(struct ospf_interface *oi, + struct ospf_neighbor *inbr, + struct ospf_lsa *lsa); extern unsigned long ospf_ls_request_count(struct ospf_neighbor *); extern int ospf_ls_request_isempty(struct ospf_neighbor *); diff --git a/ospfd/ospf_gr.c b/ospfd/ospf_gr.c new file mode 100644 index 000000000..c10804030 --- /dev/null +++ b/ospfd/ospf_gr.c @@ -0,0 +1,824 @@ +/* + * This is an implementation of RFC 3623 Graceful OSPF Restart. + * + * Copyright 2021 NetDEF (c), All rights reserved. + * Copyright 2020 6WIND (c), All rights reserved. + * + * This program 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 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 "memory.h" +#include "command.h" +#include "table.h" +#include "vty.h" +#include "log.h" +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_opaque.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_dump.h" +#ifndef VTYSH_EXTRACT_PL +#include "ospfd/ospf_gr_clippy.c" +#endif + +static void ospf_gr_nvm_delete(struct ospf *ospf); + +/* Lookup self-originated Grace-LSA in the LSDB. */ +static struct ospf_lsa *ospf_gr_lsa_lookup(struct ospf *ospf, + struct ospf_area *area) +{ + struct ospf_lsa *lsa; + struct in_addr lsa_id; + uint32_t lsa_id_host_byte_order; + + lsa_id_host_byte_order = SET_OPAQUE_LSID(OPAQUE_TYPE_GRACE_LSA, 0); + lsa_id.s_addr = htonl(lsa_id_host_byte_order); + lsa = ospf_lsa_lookup(ospf, area, OSPF_OPAQUE_LINK_LSA, lsa_id, + ospf->router_id); + + return lsa; +} + +/* Fill in fields of the Grace-LSA that is being originated. */ +static void ospf_gr_lsa_body_set(struct ospf_gr_info *gr_info, + struct ospf_interface *oi, struct stream *s) +{ + struct grace_tlv_graceperiod tlv_period = {}; + struct grace_tlv_restart_reason tlv_reason = {}; + struct grace_tlv_restart_addr tlv_address = {}; + + /* Put grace period. */ + tlv_period.header.type = htons(GRACE_PERIOD_TYPE); + tlv_period.header.length = htons(GRACE_PERIOD_LENGTH); + tlv_period.interval = htonl(gr_info->grace_period); + stream_put(s, &tlv_period, sizeof(tlv_period)); + + /* Put restart reason. */ + tlv_reason.header.type = htons(RESTART_REASON_TYPE); + tlv_reason.header.length = htons(RESTART_REASON_LENGTH); + if (gr_info->restart_support) + tlv_reason.reason = OSPF_GR_SW_RESTART; + else + tlv_reason.reason = OSPF_GR_UNKNOWN_RESTART; + stream_put(s, &tlv_reason, sizeof(tlv_reason)); + + /* Put IP address. */ + if (oi->type == OSPF_IFTYPE_BROADCAST || oi->type == OSPF_IFTYPE_NBMA + || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + tlv_address.header.type = htons(RESTARTER_IP_ADDR_TYPE); + tlv_address.header.length = htons(RESTARTER_IP_ADDR_LEN); + tlv_address.addr = oi->address->u.prefix4; + stream_put(s, &tlv_address, sizeof(tlv_address)); + } +} + +/* Generate Grace-LSA for a given interface. */ +static struct ospf_lsa *ospf_gr_lsa_new(struct ospf_interface *oi) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + uint8_t options, lsa_type; + struct in_addr lsa_id; + uint32_t lsa_id_host_byte_order; + uint16_t length; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = LSA_OPTIONS_GET(oi->area); + options |= LSA_OPTIONS_NSSA_GET(oi->area); + options |= OSPF_OPTION_O; + + lsa_type = OSPF_OPAQUE_LINK_LSA; + lsa_id_host_byte_order = SET_OPAQUE_LSID(OPAQUE_TYPE_GRACE_LSA, 0); + lsa_id.s_addr = htonl(lsa_id_host_byte_order); + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, oi->ospf->router_id); + + /* Set opaque-LSA body fields. */ + ospf_gr_lsa_body_set(&oi->ospf->gr_info, oi, s); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + if (IS_DEBUG_OSPF_GR) + zlog_debug("LSA[Type%d:%pI4]: Create an Opaque-LSA/GR instance", + lsa_type, &lsa_id); + + new->area = oi->area; + new->oi = oi; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Originate and install Grace-LSA for a given interface. */ +static void ospf_gr_lsa_originate(struct ospf_interface *oi) +{ + struct ospf_lsa *lsa, *old; + + if (ospf_interface_neighbor_count(oi) == 0) + return; + + /* Create new Grace-LSA. */ + lsa = ospf_gr_lsa_new(oi); + if (!lsa) { + zlog_warn("%s: ospf_gr_lsa_new() failed", __func__); + return; + } + + /* Find the old LSA and increase the seqno. */ + old = ospf_gr_lsa_lookup(oi->ospf, oi->area); + if (old) + lsa->data->ls_seqnum = lsa_seqnum_increment(old); + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(oi->ospf, oi, lsa) == NULL) { + zlog_warn("%s: ospf_lsa_install() failed", __func__); + ospf_lsa_unlock(&lsa); + return; + } + + /* Update new LSA origination count. */ + oi->ospf->lsa_originate_count++; + + /* Flood the LSA through out the interface */ + ospf_flood_through_interface(oi, NULL, lsa); +} + +/* Flush a given self-originated Grace-LSA. */ +static struct ospf_lsa *ospf_gr_flush_grace_lsa(struct ospf_interface *oi, + struct ospf_lsa *old) +{ + struct ospf_lsa *lsa; + + if (ospf_interface_neighbor_count(oi) == 0) + return NULL; + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: flushing self-originated Grace-LSAs [interface %s]", + oi->ifp->name); + + lsa = ospf_lsa_dup(old); + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + lsa->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* Install updated LSA into LSDB. */ + if (ospf_lsa_install(oi->ospf, oi, lsa) == NULL) { + zlog_warn("%s: ospf_lsa_install() failed", __func__); + ospf_lsa_unlock(&lsa); + return NULL; + } + + /* Flood the LSA through out the interface */ + ospf_flood_through_interface(oi, NULL, lsa); + + return lsa; +} + +/* Flush all self-originated Grace-LSAs. */ +static void ospf_gr_flush_grace_lsas(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *anode; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, anode, area)) { + struct ospf_lsa *lsa; + struct ospf_interface *oi; + struct listnode *inode; + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: flushing self-originated Grace-LSAs [area %pI4]", + &area->area_id); + + lsa = ospf_gr_lsa_lookup(ospf, area); + if (!lsa) { + zlog_warn("%s: Grace-LSA not found [area %pI4]", + __func__, &area->area_id); + continue; + } + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi)) + ospf_gr_flush_grace_lsa(oi, lsa); + } +} + +/* Exit from the Graceful Restart mode. */ +static void ospf_gr_restart_exit(struct ospf *ospf, const char *reason) +{ + struct ospf_area *area; + struct listnode *onode, *anode; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("GR: exiting graceful restart: %s", reason); + + ospf->gr_info.restart_in_progress = false; + OSPF_TIMER_OFF(ospf->gr_info.t_grace_period); + + /* Record in non-volatile memory that the restart is complete. */ + ospf_gr_nvm_delete(ospf); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, onode, area)) { + struct ospf_interface *oi; + + /* + * 1) The router should reoriginate its router-LSAs for all + * attached areas in order to make sure they have the correct + * contents. + */ + ospf_router_lsa_update_area(area); + + /* + * 2) The router should reoriginate network-LSAs on all segments + * where it is the Designated Router. + */ + for (ALL_LIST_ELEMENTS_RO(area->oiflist, anode, oi)) + if (oi->state == ISM_DR) + ospf_network_lsa_update(oi); + } + + /* + * 5) Any received self-originated LSAs that are no longer valid should + * be flushed. + */ + ospf_schedule_abr_task(ospf); + + /* + * 3) The router reruns its OSPF routing calculations, this time + * installing the results into the system forwarding table, and + * originating summary-LSAs, Type-7 LSAs and AS-external-LSAs as + * necessary. + * + * 4) Any remnant entries in the system forwarding table that were + * installed before the restart, but that are no longer valid, + * should be removed. + */ + ospf->gr_info.finishing_restart = true; + ospf_spf_calculate_schedule(ospf, SPF_FLAG_GR_FINISH); + + /* 6) Any grace-LSAs that the router originated should be flushed. */ + ospf_gr_flush_grace_lsas(ospf); +} + +/* Check if a Router-LSA contains a given link. */ +static bool ospf_router_lsa_contains_adj(struct ospf_lsa *lsa, + struct in_addr *id) +{ + struct router_lsa *rl; + + rl = (struct router_lsa *)lsa->data; + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + + if (rl->link[i].type != LSA_LINK_TYPE_POINTOPOINT) + continue; + + if (IPV4_ADDR_SAME(id, link_id)) + return true; + } + + return false; +} + +static bool ospf_gr_check_router_lsa_consistency(struct ospf *ospf, + struct ospf_area *area, + struct ospf_lsa *lsa) +{ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_SELF)) { + struct ospf_lsa *lsa_self = lsa; + struct router_lsa *rl = (struct router_lsa *)lsa->data; + + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + struct ospf_lsa *lsa_adj; + + if (rl->link[i].type != LSA_LINK_TYPE_POINTOPOINT) + continue; + + lsa_adj = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + *link_id); + if (!lsa_adj) + continue; + + if (!ospf_router_lsa_contains_adj(lsa_adj, + &lsa_self->data->id)) + return false; + } + } else { + struct ospf_lsa *lsa_self; + + lsa_self = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + ospf->router_id); + if (!lsa_self + || !CHECK_FLAG(lsa_self->flags, OSPF_LSA_RECEIVED)) + return true; + + if (ospf_router_lsa_contains_adj(lsa, &ospf->router_id) + != ospf_router_lsa_contains_adj(lsa_self, &lsa->data->id)) + return false; + } + + return true; +} + +/* + * Check for LSAs that are inconsistent with the pre-restart LSAs, and abort the + * ongoing graceful restart when that's the case. + */ +void ospf_gr_check_lsdb_consistency(struct ospf *ospf, struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + + for (rn = route_top(ROUTER_LSDB(area)); rn; rn = route_next(rn)) { + lsa = rn->info; + if (!lsa) + continue; + + if (!ospf_gr_check_router_lsa_consistency(ospf, area, lsa)) { + char reason[256]; + + snprintfrr(reason, sizeof(reason), + "detected inconsistent LSA[%s] [area %pI4]", + dump_lsa_key(lsa), &area->area_id); + ospf_gr_restart_exit(ospf, reason); + route_unlock_node(rn); + return; + } + } +} + +/* Lookup neighbor by address in a given OSPF area. */ +static struct ospf_neighbor * +ospf_area_nbr_lookup_by_addr(struct ospf_area *area, struct in_addr *addr) +{ + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_addr(oi->nbrs, addr); + if (nbr) + return nbr; + } + + return NULL; +} + +/* Lookup neighbor by Router ID in a given OSPF area. */ +static struct ospf_neighbor * +ospf_area_nbr_lookup_by_routerid(struct ospf_area *area, struct in_addr *id) +{ + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, id); + if (nbr) + return nbr; + } + + return NULL; +} + +/* Check if there's a fully formed adjacency with the given neighbor ID. */ +static bool ospf_gr_check_adj_id(struct ospf_area *area, + struct in_addr *nbr_id) +{ + struct ospf_neighbor *nbr; + + nbr = ospf_area_nbr_lookup_by_routerid(area, nbr_id); + if (!nbr || nbr->state < NSM_Full) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("GR: missing adjacency to router %pI4", + nbr_id); + return false; + } + + return true; +} + +static bool ospf_gr_check_adjs_lsa_transit(struct ospf_area *area, + struct in_addr *link_id) +{ + struct ospf *ospf = area->ospf; + struct ospf_interface *oi; + + /* + * Check if the transit network refers to a local interface (in which + * case it must be a DR for that network). + */ + oi = ospf_if_lookup_by_local_addr(ospf, NULL, *link_id); + if (oi) { + struct ospf_lsa *lsa; + struct network_lsa *nlsa; + size_t cnt; + + /* Lookup Network LSA corresponding to this interface. */ + lsa = ospf_lsa_lookup_by_id(area, OSPF_NETWORK_LSA, *link_id); + if (!lsa) + return false; + + /* Iterate over all routers present in the network. */ + nlsa = (struct network_lsa *)lsa->data; + cnt = (lsa->size - (OSPF_LSA_HEADER_SIZE + 4)) / 4; + for (size_t i = 0; i < cnt; i++) { + struct in_addr *nbr_id = &nlsa->routers[i]; + + /* Skip self in the pseudonode. */ + if (IPV4_ADDR_SAME(nbr_id, &ospf->router_id)) + continue; + + /* + * Check if there's a fully formed adjacency with this + * router. + */ + if (!ospf_gr_check_adj_id(area, nbr_id)) + return false; + } + } else { + struct ospf_neighbor *nbr; + + /* Check if there's a fully formed adjacency with the DR. */ + nbr = ospf_area_nbr_lookup_by_addr(area, link_id); + if (!nbr || nbr->state < NSM_Full) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: missing adjacency to DR router %pI4", + link_id); + return false; + } + } + + return true; +} + +static bool ospf_gr_check_adjs_lsa(struct ospf_area *area, struct ospf_lsa *lsa) +{ + struct router_lsa *rl = (struct router_lsa *)lsa->data; + + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + + switch (rl->link[i].type) { + case LSA_LINK_TYPE_POINTOPOINT: + if (!ospf_gr_check_adj_id(area, link_id)) + return false; + break; + case LSA_LINK_TYPE_TRANSIT: + if (!ospf_gr_check_adjs_lsa_transit(area, link_id)) + return false; + break; + default: + break; + } + } + + return true; +} + +/* + * Check if all adjacencies prior to the restart were reestablished. + * + * This is done using pre-restart Router LSAs and pre-restart Network LSAs + * received from the helping neighbors. + */ +void ospf_gr_check_adjs(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + struct ospf_lsa *lsa_self; + + lsa_self = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + ospf->router_id); + if (!lsa_self || !ospf_gr_check_adjs_lsa(area, lsa_self)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: not all adjacencies were reestablished yet [area %pI4]", + &area->area_id); + return; + } + } + + ospf_gr_restart_exit(ospf, "all adjacencies were reestablished"); +} + +/* Handling of grace period expiry. */ +static int ospf_gr_grace_period_expired(struct thread *thread) +{ + struct ospf *ospf = THREAD_ARG(thread); + + ospf->gr_info.t_grace_period = NULL; + ospf_gr_restart_exit(ospf, "grace period has expired"); + + return 0; +} + +/* + * Returns the path of the file (non-volatile memory) that contains GR status + * information. + */ +static char *ospf_gr_nvm_filepath(struct ospf *ospf) +{ + static char filepath[MAXPATHLEN]; + char instance[16] = ""; + + if (ospf->instance) + snprintf(instance, sizeof(instance), "-%d", ospf->instance); + snprintf(filepath, sizeof(filepath), OSPFD_GR_STATE, instance); + return filepath; +} + +/* + * Record in non-volatile memory that the given OSPF instance is attempting to + * perform a graceful restart. + */ +static void ospf_gr_nvm_update(struct ospf *ospf) +{ + char *filepath; + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + + filepath = ospf_gr_nvm_filepath(ospf); + inst_name = ospf->name ? ospf->name : VRF_DEFAULT_NAME; + + json = json_object_from_file(filepath); + if (json == NULL) + json = json_object_new_object(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + /* + * Record not only the grace period, but also a UNIX timestamp + * corresponding to the end of that period. That way, once ospfd is + * restarted, it will be possible to take into account the time that + * passed while ospfd wasn't running. + */ + json_object_int_add(json_instance, "gracePeriod", + ospf->gr_info.grace_period); + json_object_int_add(json_instance, "timestamp", + time(NULL) + ospf->gr_info.grace_period); + + json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY); + json_object_free(json); +} + +/* + * Delete GR status information about the given OSPF instance from non-volatile + * memory. + */ +static void ospf_gr_nvm_delete(struct ospf *ospf) +{ + char *filepath; + const char *inst_name; + json_object *json; + json_object *json_instances; + + filepath = ospf_gr_nvm_filepath(ospf); + inst_name = ospf->name ? ospf->name : VRF_DEFAULT_NAME; + + json = json_object_from_file(filepath); + if (json == NULL) + json = json_object_new_object(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_del(json_instances, inst_name); + + json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY); + json_object_free(json); +} + +/* + * Fetch from non-volatile memory whether the given OSPF instance is performing + * a graceful shutdown or not. + */ +void ospf_gr_nvm_read(struct ospf *ospf) +{ + char *filepath; + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + json_object *json_timestamp; + time_t timestamp = 0; + + filepath = ospf_gr_nvm_filepath(ospf); + inst_name = ospf->name ? ospf->name : VRF_DEFAULT_NAME; + + json = json_object_from_file(filepath); + if (json == NULL) + json = json_object_new_object(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_object_get_ex(json_instance, "timestamp", &json_timestamp); + if (json_timestamp) { + time_t now; + unsigned long remaining_time; + + /* Check if the grace period has already expired. */ + now = time(NULL); + timestamp = json_object_get_int(json_timestamp); + if (now > timestamp) { + ospf_gr_restart_exit( + ospf, "grace period has expired already"); + } else { + /* Schedule grace period timeout. */ + ospf->gr_info.restart_in_progress = true; + remaining_time = timestamp - time(NULL); + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: remaining time until grace period expires: %lu(s)", + remaining_time); + thread_add_timer(master, ospf_gr_grace_period_expired, + ospf, remaining_time, + &ospf->gr_info.t_grace_period); + } + } + + json_object_object_del(json_instances, inst_name); + + json_object_to_file_ext(filepath, json, JSON_C_TO_STRING_PRETTY); + json_object_free(json); +} + +/* Prepare to start a Graceful Restart. */ +static void ospf_gr_prepare(void) +{ + struct ospf *ospf; + struct ospf_interface *oi; + struct listnode *onode; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, onode, ospf)) { + struct listnode *inode; + + if (!ospf->gr_info.restart_support + || ospf->gr_info.prepare_in_progress) + continue; + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: preparing to perform a graceful restart [period %u second(s)] [vrf %s]", + ospf->gr_info.grace_period, + ospf_vrf_id_to_name(ospf->vrf_id)); + + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + zlog_warn( + "%s: failed to activate graceful restart: opaque capability not enabled", + __func__); + continue; + } + + /* Freeze OSPF routes in the RIB. */ + if (ospf_zebra_gr_enable(ospf, ospf->gr_info.grace_period)) { + zlog_warn( + "%s: failed to activate graceful restart: not connected to zebra", + __func__); + continue; + } + + /* Send a Grace-LSA to all neighbors. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, inode, oi)) + ospf_gr_lsa_originate(oi); + + /* Record end of the grace period in non-volatile memory. */ + ospf_gr_nvm_update(ospf); + + /* + * Mark that a Graceful Restart preparation is in progress, to + * prevent ospfd from flushing its self-originated LSAs on exit. + */ + ospf->gr_info.prepare_in_progress = true; + } +} + +DEFPY(graceful_restart_prepare, graceful_restart_prepare_cmd, + "graceful-restart prepare ip ospf", + "Graceful Restart commands\n" + "Prepare upcoming graceful restart\n" + IP_STR + "Prepare to restart the OSPF process") +{ + ospf_gr_prepare(); + + return CMD_SUCCESS; +} + +DEFPY(graceful_restart, graceful_restart_cmd, + "graceful-restart [grace-period (1-1800)$grace_period]", + OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Check and get restart period if present. */ + if (!grace_period_str) + grace_period = OSPF_DFLT_GRACE_INTERVAL; + + ospf->gr_info.restart_support = true; + ospf->gr_info.grace_period = grace_period; + + return CMD_SUCCESS; +} + +DEFPY(no_graceful_restart, no_graceful_restart_cmd, + "no graceful-restart [period (1-1800)]", + NO_STR OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (!ospf->gr_info.restart_support) + return CMD_SUCCESS; + + if (ospf->gr_info.prepare_in_progress) { + vty_out(vty, + "%% Error: Graceful Restart preparation in progress\n"); + return CMD_WARNING; + } + + ospf->gr_info.restart_support = false; + ospf->gr_info.grace_period = OSPF_DFLT_GRACE_INTERVAL; + + return CMD_SUCCESS; +} + +void ospf_gr_init(void) +{ + install_element(ENABLE_NODE, &graceful_restart_prepare_cmd); + install_element(OSPF_NODE, &graceful_restart_cmd); + install_element(OSPF_NODE, &no_graceful_restart_cmd); +} diff --git a/ospfd/ospf_gr.h b/ospfd/ospf_gr.h index 2106e0f5c..496ba3c59 100644 --- a/ospfd/ospf_gr.h +++ b/ospfd/ospf_gr.h @@ -32,6 +32,7 @@ #define OSPF_MAX_GRACE_INTERVAL 1800 #define OSPF_MIN_GRACE_INTERVAL 1 +#define OSPF_DFLT_GRACE_INTERVAL 120 enum ospf_helper_exit_reason { OSPF_GR_HELPER_EXIT_NONE = 0, @@ -55,7 +56,8 @@ enum ospf_gr_helper_rejected_reason { OSPF_HELPER_NOT_A_VALID_NEIGHBOUR, OSPF_HELPER_PLANNED_ONLY_RESTART, OSPF_HELPER_TOPO_CHANGE_RTXMT_LIST, - OSPF_HELPER_LSA_AGE_MORE + OSPF_HELPER_LSA_AGE_MORE, + OSPF_HELPER_RESTARTING, }; /* Ref RFC3623 appendex-A */ @@ -179,4 +181,11 @@ extern void ospf_gr_helper_supported_gracetime_set(struct ospf *ospf, uint32_t interval); extern void ospf_gr_helper_set_supported_planned_only_restart(struct ospf *ospf, bool planned_only); + +extern void ospf_gr_check_lsdb_consistency(struct ospf *ospf, + struct ospf_area *area); +extern void ospf_gr_check_adjs(struct ospf *ospf); +extern void ospf_gr_nvm_read(struct ospf *ospf); +extern void ospf_gr_init(void); + #endif /* _ZEBRA_OSPF_GR_H */ diff --git a/ospfd/ospf_gr_helper.c b/ospfd/ospf_gr_helper.c index 7d0c229af..11ad45d30 100644 --- a/ospfd/ospf_gr_helper.c +++ b/ospfd/ospf_gr_helper.c @@ -72,6 +72,7 @@ static const char * const ospf_rejected_reason_desc[] = { "Supports only planned restart but received unplanned", "Topo change due to change in lsa rxmt list", "LSA age is more than Grace interval", + "Router is in the process of graceful restart", }; static void show_ospf_grace_lsa_info(struct vty *vty, struct ospf_lsa *lsa); @@ -489,6 +490,16 @@ int ospf_process_grace_lsa(struct ospf *ospf, struct ospf_lsa *lsa, return OSPF_GR_NOT_HELPER; } + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s: router is in the process of graceful restart", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_RESTARTING; + return OSPF_GR_NOT_HELPER; + } + /* check supported grace period configured * if configured, use this to start the grace * timer otherwise use the interval received diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c index b098581ce..723a54a77 100644 --- a/ospfd/ospf_lsa.c +++ b/ospfd/ospf_lsa.c @@ -819,6 +819,14 @@ static struct ospf_lsa *ospf_router_lsa_originate(struct ospf_area *area) { struct ospf_lsa *new; + if (area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_ROUTER_LSA); + return NULL; + } + /* Create new router-LSA instance. */ if ((new = ospf_router_lsa_new(area)) == NULL) { zlog_err("%s: ospf_router_lsa_new returned NULL", __func__); @@ -1045,6 +1053,14 @@ void ospf_network_lsa_update(struct ospf_interface *oi) { struct ospf_lsa *new; + if (oi->area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_NETWORK_LSA); + return; + } + if (oi->network_lsa_self != NULL) { ospf_lsa_refresh(oi->ospf, oi->network_lsa_self); return; @@ -1212,6 +1228,14 @@ struct ospf_lsa *ospf_summary_lsa_originate(struct prefix_ipv4 *p, struct ospf_lsa *new; struct in_addr id; + if (area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_SUMMARY_LSA); + return NULL; + } + id = ospf_lsa_unique_id(area->ospf, area->lsdb, OSPF_SUMMARY_LSA, p); if (id.s_addr == 0xffffffff) { @@ -1353,6 +1377,14 @@ struct ospf_lsa *ospf_summary_asbr_lsa_originate(struct prefix_ipv4 *p, struct ospf_lsa *new; struct in_addr id; + if (area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_ASBR_SUMMARY_LSA); + return NULL; + } + id = ospf_lsa_unique_id(area->ospf, area->lsdb, OSPF_ASBR_SUMMARY_LSA, p); @@ -1799,6 +1831,13 @@ struct ospf_lsa *ospf_translated_nssa_originate(struct ospf *ospf, struct ospf_lsa *new; struct as_external_lsa *extnew; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Translated Type5]: Graceful Restart in progress, don't originate"); + return NULL; + } + /* we cant use ospf_external_lsa_originate() as we need to set * the OSPF_LSA_LOCAL_XLT flag, must originate by hand */ @@ -1964,6 +2003,13 @@ struct ospf_lsa *ospf_external_lsa_originate(struct ospf *ospf, { struct ospf_lsa *new; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: Graceful Restart in progress, don't originate"); + return NULL; + } + /* Added for NSSA project.... External LSAs are originated in ASBRs as usual, but for NSSA diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c index d94de1299..73d596b03 100644 --- a/ospfd/ospf_main.c +++ b/ospfd/ospf_main.c @@ -54,6 +54,7 @@ #include "ospfd/ospf_zebra.h" #include "ospfd/ospf_vty.h" #include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" #include "ospfd/ospf_errors.h" #include "ospfd/ospf_ldp_sync.h" #include "ospfd/ospf_routemap_nb.h" @@ -225,6 +226,7 @@ int main(int argc, char **argv) ospf_route_map_init(); ospf_opaque_init(); + ospf_gr_init(); ospf_gr_helper_init(); /* OSPF errors init */ diff --git a/ospfd/ospf_nsm.c b/ospfd/ospf_nsm.c index 0dc150852..892d264a2 100644 --- a/ospfd/ospf_nsm.c +++ b/ospfd/ospf_nsm.c @@ -49,6 +49,7 @@ #include "ospfd/ospf_flood.h" #include "ospfd/ospf_abr.h" #include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" #include "ospfd/ospf_errors.h" DEFINE_HOOK(ospf_nsm_change, @@ -727,6 +728,9 @@ static void nsm_change_state(struct ospf_neighbor *nbr, int state) ospf_network_lsa_update(oi); } } + + if (state == NSM_Full && oi->ospf->gr_info.restart_in_progress) + ospf_gr_check_adjs(oi->ospf); } ospf_opaque_nsm_change(nbr, old_state); diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c index 743db7002..9930b0bd4 100644 --- a/ospfd/ospf_packet.c +++ b/ospfd/ospf_packet.c @@ -2025,9 +2025,11 @@ static void ospf_ls_upd(struct ospf *ospf, struct ip *iph, ospf_ls_ack_send(nbr, lsa); - ospf_opaque_self_originated_lsa_received(nbr, - lsa); - continue; + if (!ospf->gr_info.restart_in_progress) { + ospf_opaque_self_originated_lsa_received( + nbr, lsa); + continue; + } } } @@ -2213,6 +2215,9 @@ static void ospf_ls_upd(struct ospf *ospf, struct ip *iph, assert(listcount(lsas) == 0); list_delete(&lsas); + + if (ospf->gr_info.restart_in_progress) + ospf_gr_check_lsdb_consistency(oi->ospf, oi->area); } /* OSPF Link State Acknowledgment message read -- RFC2328 Section 13.7. */ diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c index 43d6ff44b..6a5144026 100644 --- a/ospfd/ospf_spf.c +++ b/ospfd/ospf_spf.c @@ -1903,6 +1903,8 @@ static int ospf_spf_calculate_schedule_worker(struct thread *thread) strlcat(rbuf, "ASBR, ", sizeof(rbuf)); if (spf_reason_flags & (1 << SPF_FLAG_MAXAGE)) strlcat(rbuf, "M, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_GR_FINISH)) + strlcat(rbuf, "GR, ", sizeof(rbuf)); size_t rbuflen = strlen(rbuf); if (rbuflen >= 2) diff --git a/ospfd/ospf_spf.h b/ospfd/ospf_spf.h index 4ff4a6d12..20f38440a 100644 --- a/ospfd/ospf_spf.h +++ b/ospfd/ospf_spf.h @@ -69,6 +69,7 @@ typedef enum { SPF_FLAG_ABR_STATUS_CHANGE, SPF_FLAG_ASBR_STATUS_CHANGE, SPF_FLAG_CONFIG_CHANGE, + SPF_FLAG_GR_FINISH, } ospf_spf_reason_t; extern void ospf_spf_calculate_schedule(struct ospf *, ospf_spf_reason_t); diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index db8e961b8..b230e066c 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -12250,6 +12250,18 @@ static int ospf_cfg_write_helper_dis_rtr_walkcb(struct hash_bucket *bucket, return HASHWALK_CONTINUE; } +static void config_write_ospf_gr(struct vty *vty, struct ospf *ospf) +{ + if (!ospf->gr_info.restart_support) + return; + + if (ospf->gr_info.grace_period == OSPF_DFLT_GRACE_INTERVAL) + vty_out(vty, " graceful-restart\n"); + else + vty_out(vty, " graceful-restart grace-period %u\n", + ospf->gr_info.grace_period); +} + static int config_write_ospf_gr_helper(struct vty *vty, struct ospf *ospf) { if (ospf->is_helper_supported) @@ -12464,7 +12476,8 @@ static int ospf_config_write_one(struct vty *vty, struct ospf *ospf) /* Redistribute information print. */ config_write_ospf_redistribute(vty, ospf); - /* Print gr helper configs */ + /* Graceful Restart print */ + config_write_ospf_gr(vty, ospf); config_write_ospf_gr_helper(vty, ospf); /* Print external route aggregation. */ diff --git a/ospfd/ospf_zebra.c b/ospfd/ospf_zebra.c index 017915e0e..de33c9732 100644 --- a/ospfd/ospf_zebra.c +++ b/ospfd/ospf_zebra.c @@ -264,6 +264,14 @@ void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_path *path; struct listnode *node; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + p); + return; + } + memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; @@ -323,6 +331,14 @@ void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *p, { struct zapi_route api; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + p); + return; + } + memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; @@ -340,6 +356,14 @@ void ospf_zebra_add_discard(struct ospf *ospf, struct prefix_ipv4 *p) { struct zapi_route api; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + p); + return; + } + memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; @@ -358,6 +382,14 @@ void ospf_zebra_delete_discard(struct ospf *ospf, struct prefix_ipv4 *p) { struct zapi_route api; + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + p); + return; + } + memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; api.type = ZEBRA_ROUTE_OSPF; @@ -1180,6 +1212,36 @@ void ospf_routemap_unset(struct ospf_redist *red) ROUTEMAP(red) = NULL; } +static int ospf_zebra_gr_update(struct ospf *ospf, int command, + uint32_t stale_time) +{ + struct zapi_cap api; + + if (!zclient || zclient->sock < 0 || !ospf) + return 1; + + memset(&api, 0, sizeof(struct zapi_cap)); + api.cap = command; + api.stale_removal_time = stale_time; + api.vrf_id = ospf->vrf_id; + + (void)zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, + &api); + + return 0; +} + +int ospf_zebra_gr_enable(struct ospf *ospf, uint32_t stale_time) +{ + return ospf_zebra_gr_update(ospf, ZEBRA_CLIENT_GR_CAPABILITIES, + stale_time); +} + +int ospf_zebra_gr_disable(struct ospf *ospf) +{ + return ospf_zebra_gr_update(ospf, ZEBRA_CLIENT_GR_DISABLE, 0); +} + /* Zebra route add and delete treatment. */ static int ospf_zebra_read_route(ZAPI_CALLBACK_ARGS) { diff --git a/ospfd/ospf_zebra.h b/ospfd/ospf_zebra.h index 3f4edfa29..3bd8a7680 100644 --- a/ospfd/ospf_zebra.h +++ b/ospfd/ospf_zebra.h @@ -88,6 +88,8 @@ extern int ospf_distribute_list_out_set(struct ospf *, int, const char *); extern int ospf_distribute_list_out_unset(struct ospf *, int, const char *); extern void ospf_routemap_set(struct ospf_redist *, const char *); extern void ospf_routemap_unset(struct ospf_redist *); +extern int ospf_zebra_gr_enable(struct ospf *ospf, uint32_t stale_time); +extern int ospf_zebra_gr_disable(struct ospf *ospf); extern int ospf_distance_set(struct vty *, struct ospf *, const char *, const char *, const char *); extern int ospf_distance_unset(struct vty *, struct ospf *, const char *, diff --git a/ospfd/ospfd.c b/ospfd/ospfd.c index a4b50e0c3..3226d6644 100644 --- a/ospfd/ospfd.c +++ b/ospfd/ospfd.c @@ -420,6 +420,12 @@ static struct ospf *ospf_new(unsigned short instance, const char *name) thread_add_read(master, ospf_read, new, new->fd, &new->t_read); + /* + * Read from non-volatile memory whether this instance is performing a + * graceful restart or not. + */ + ospf_gr_nvm_read(new); + return new; } @@ -708,7 +714,8 @@ static void ospf_finish_final(struct ospf *ospf) ospf_opaque_finish(); - ospf_flush_self_originated_lsas_now(ospf); + if (!ospf->gr_info.prepare_in_progress) + ospf_flush_self_originated_lsas_now(ospf); /* Unregister redistribution */ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { @@ -805,6 +812,7 @@ static void ospf_finish_final(struct ospf *ospf) OSPF_TIMER_OFF(ospf->t_sr_update); OSPF_TIMER_OFF(ospf->t_default_routemap_timer); OSPF_TIMER_OFF(ospf->t_external_aggr); + OSPF_TIMER_OFF(ospf->gr_info.t_grace_period); LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) ospf_discard_from_db(ospf, ospf->lsdb, lsa); @@ -826,7 +834,8 @@ static void ospf_finish_final(struct ospf *ospf) if (ospf->old_table) ospf_route_table_free(ospf->old_table); if (ospf->new_table) { - ospf_route_delete(ospf, ospf->new_table); + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->new_table); ospf_route_table_free(ospf->new_table); } if (ospf->old_rtrs) @@ -834,11 +843,13 @@ static void ospf_finish_final(struct ospf *ospf) if (ospf->new_rtrs) ospf_rtrs_free(ospf->new_rtrs); if (ospf->new_external_route) { - ospf_route_delete(ospf, ospf->new_external_route); + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->new_external_route); ospf_route_table_free(ospf->new_external_route); } if (ospf->old_external_route) { - ospf_route_delete(ospf, ospf->old_external_route); + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->old_external_route); ospf_route_table_free(ospf->old_external_route); } if (ospf->external_lsas) { diff --git a/ospfd/ospfd.h b/ospfd/ospfd.h index 318400e96..d64a044e1 100644 --- a/ospfd/ospfd.h +++ b/ospfd/ospfd.h @@ -134,6 +134,16 @@ enum protection_type { OSPF_TI_LFA_NODE_PROTECTION, }; +/* OSPF nonstop forwarding aka Graceful Restart */ +struct ospf_gr_info { + bool restart_support; + bool restart_in_progress; + bool prepare_in_progress; + bool finishing_restart; + uint32_t grace_period; + struct thread *t_grace_period; +}; + /* OSPF instance structure. */ struct ospf { /* OSPF's running state based on the '[no] router ospf [<instance>]' @@ -384,6 +394,9 @@ struct ospf { /* MPLS LDP-IGP Sync */ struct ldp_sync_info_cmd ldp_sync_cmd; + /* OSPF Graceful Restart info */ + struct ospf_gr_info gr_info; + /* TI-LFA support for all interfaces. */ bool ti_lfa_enabled; enum protection_type ti_lfa_protection_type; diff --git a/ospfd/subdir.am b/ospfd/subdir.am index 51254391f..2c4cc262c 100644 --- a/ospfd/subdir.am +++ b/ospfd/subdir.am @@ -8,6 +8,7 @@ sbin_PROGRAMS += ospfd/ospfd vtysh_scan += \ ospfd/ospf_bfd.c \ ospfd/ospf_dump.c \ + ospfd/ospf_gr.c \ ospfd/ospf_ldp_sync.c \ ospfd/ospf_opaque.c \ ospfd/ospf_ri.c \ @@ -35,6 +36,7 @@ ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_errors.c \ ospfd/ospf_ext.c \ ospfd/ospf_flood.c \ + ospfd/ospf_gr.c \ ospfd/ospf_ia.c \ ospfd/ospf_interface.c \ ospfd/ospf_ism.c \ @@ -82,6 +84,7 @@ clippy_scan += \ ospfd/ospf_vty.c \ ospfd/ospf_ldp_sync.c \ ospfd/ospf_dump.c \ + ospfd/ospf_gr.c \ # end noinst_HEADERS += \ |