diff options
Diffstat (limited to 'ospfd/ospf_gr.c')
-rw-r--r-- | ospfd/ospf_gr.c | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/ospfd/ospf_gr.c b/ospfd/ospf_gr.c new file mode 100644 index 0000000..c23c420 --- /dev/null +++ b/ospfd/ospf_gr.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC 3623 Graceful OSPF Restart. + * + * Copyright 2021 NetDEF (c), All rights reserved. + * Copyright 2020 6WIND (c), All rights reserved. + */ + +#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" +#include "ospfd/ospf_gr_clippy.c" + +static void ospf_gr_grace_period_expired(struct event *thread); + +/* 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, + enum ospf_gr_restart_reason reason, + 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); + tlv_reason.reason = reason; + 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, + enum ospf_gr_restart_reason reason) +{ + 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, reason, 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, + enum ospf_gr_restart_reason reason, + bool maxage) +{ + struct ospf_lsa *lsa, *old; + + /* Skip originating a Grace-LSA when not necessary. */ + if (!if_is_operative(oi->ifp) || if_is_loopback(oi->ifp) || + (reason != OSPF_GR_UNKNOWN_RESTART && + ospf_interface_neighbor_count(oi) == 0)) + return; + + /* Create new Grace-LSA. */ + lsa = ospf_gr_lsa_new(oi, reason); + if (!lsa) { + zlog_warn("%s: ospf_gr_lsa_new() failed", __func__); + return; + } + + if (maxage) + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + + /* 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); + + if (!maxage && reason == OSPF_GR_UNKNOWN_RESTART) { + struct list *update; + struct in_addr addr; + + /* + * When performing an unplanned restart, send a handcrafted + * Grace-LSA since the interface isn't fully initialized yet. + */ + ospf_lsa_checksum(lsa->data); + ospf_lsa_lock(lsa); + update = list_new(); + listnode_add(update, lsa); + addr.s_addr = htonl(OSPF_ALLSPFROUTERS); + ospf_ls_upd_queue_send(oi, update, addr, true); + list_delete(&update); + ospf_lsa_discard(lsa); + } else { + /* 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; + } + + /* Flood the LSA through out the interface */ + ospf_flood_through_interface(oi, NULL, lsa); + } + + /* Update new LSA origination count. */ + oi->ospf->lsa_originate_count++; +} + +/* 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_interface *oi; + struct listnode *inode; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: flushing self-originated Grace-LSA [area %pI4] [interface %s]", + &area->area_id, oi->ifp->name); + + ospf_gr_lsa_originate(oi, ospf->gr_info.reason, true); + } + } +} + +/* 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; + EVENT_OFF(ospf->gr_info.t_grace_period); + + 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); + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, anode, oi)) { + /* Disable hello delay. */ + if (oi->gr.hello_delay.t_grace_send) { + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, + ospf_hello_timer, 1); + } + + /* + * 2) The router should reoriginate network-LSAs on all + * segments where it is the Designated Router. + */ + 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; + XFREE(MTYPE_TMP, ospf->gr_info.exit_reason); + ospf->gr_info.exit_reason = XSTRDUP(MTYPE_TMP, reason); + 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); +} + +/* Enter the Graceful Restart mode. */ +void ospf_gr_restart_enter(struct ospf *ospf, + enum ospf_gr_restart_reason reason, time_t timestamp) +{ + unsigned long remaining_time; + + ospf->gr_info.restart_in_progress = true; + ospf->gr_info.reason = reason; + + /* Schedule grace period timeout. */ + remaining_time = timestamp - time(NULL); + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: remaining time until grace period expires: %lu(s)", + remaining_time); + + event_add_timer(master, ospf_gr_grace_period_expired, ospf, + remaining_time, &ospf->gr_info.t_grace_period); +} + +/* 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 void ospf_gr_grace_period_expired(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + + ospf->gr_info.t_grace_period = NULL; + ospf_gr_restart_exit(ospf, "grace period has expired"); +} + +/* + * 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; +} + +/* Send extra Grace-LSA out the interface (unplanned outages only). */ +void ospf_gr_iface_send_grace_lsa(struct event *thread) +{ + struct ospf_interface *oi = EVENT_ARG(thread); + struct ospf_if_params *params = IF_DEF_PARAMS(oi->ifp); + + ospf_gr_lsa_originate(oi, oi->ospf->gr_info.reason, false); + + if (++oi->gr.hello_delay.elapsed_seconds < params->v_gr_hello_delay) + event_add_timer(master, ospf_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); + else + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); +} + +/* + * 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, bool prepare) +{ + 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_get_name(ospf); + + 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_int_add(json_instance, "gracePeriod", + ospf->gr_info.grace_period); + + /* + * 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. + */ + if (prepare) + 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. + */ +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_get_name(ospf); + + 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; + json_object *json_grace_period; + time_t timestamp = 0; + + filepath = ospf_gr_nvm_filepath(ospf); + inst_name = ospf_get_name(ospf); + + 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, "gracePeriod", + &json_grace_period); + json_object_object_get_ex(json_instance, "timestamp", &json_timestamp); + + if (json_timestamp) { + time_t now; + + /* Planned GR: 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 + ospf_gr_restart_enter(ospf, OSPF_GR_SW_RESTART, + timestamp); + } else if (json_grace_period) { + uint32_t grace_period; + + /* + * Unplanned GR: the Grace-LSAs will be sent later as soon as + * the interfaces are operational. + */ + grace_period = json_object_get_int(json_grace_period); + ospf->gr_info.grace_period = grace_period; + ospf_gr_restart_enter(ospf, OSPF_GR_UNKNOWN_RESTART, + time(NULL) + ospf->gr_info.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); +} + +void ospf_gr_unplanned_start_interface(struct ospf_interface *oi) +{ + /* Send Grace-LSA. */ + ospf_gr_lsa_originate(oi, oi->ospf->gr_info.reason, false); + + /* Start GR hello-delay interval. */ + oi->gr.hello_delay.elapsed_seconds = 0; + event_add_timer(master, ospf_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); +} + +/* 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; + } + + /* Send a Grace-LSA to all neighbors. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, inode, oi)) { + if (OSPF_IF_PARAM(oi, opaque_capable)) + ospf_gr_lsa_originate(oi, OSPF_GR_SW_RESTART, + false); + else + zlog_debug( + "GR: skipping grace LSA on interface %s (%s) with opaque capability disabled", + IF_NAME(oi), ospf_get_name(oi->ospf)); + } + + /* Record end of the grace period in non-volatile memory. */ + ospf_gr_nvm_update(ospf, true); + + /* + * 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\n") +{ + struct ospf *ospf; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + vty_out(vty, + "%% Can't start graceful restart: opaque capability not enabled (VRF %s)\n\n", + ospf_get_name(ospf)); + return CMD_WARNING; + } + } + + 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; + + /* Freeze OSPF routes in the RIB. */ + (void)ospf_zebra_gr_enable(ospf, ospf->gr_info.grace_period); + + /* Record that GR is enabled in non-volatile memory. */ + ospf_gr_nvm_update(ospf, false); + + return CMD_SUCCESS; +} + +DEFPY(no_graceful_restart, no_graceful_restart_cmd, + "no graceful-restart [grace-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; + ospf_gr_nvm_delete(ospf); + ospf_zebra_gr_disable(ospf); + + 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); +} |