]> git.proxmox.com Git - mirror_frr.git/blobdiff - ospfd/ospf_gr.c
ospfd: add support for unplanned graceful restart
[mirror_frr.git] / ospfd / ospf_gr.c
index f60f0e863e2670fd5bbe483a9ce1d16687c33769..ab188809edfda8dbef33e9b67455fe8c0f628dad 100644 (file)
@@ -33,7 +33,7 @@
 #include "ospfd/ospf_dump.h"
 #include "ospfd/ospf_gr_clippy.c"
 
-static void ospf_gr_nvm_delete(struct ospf *ospf);
+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,
@@ -53,7 +53,9 @@ static struct ospf_lsa *ospf_gr_lsa_lookup(struct ospf *ospf,
 
 /* 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 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 = {};
@@ -68,10 +70,7 @@ static void ospf_gr_lsa_body_set(struct ospf_gr_info *gr_info,
        /* 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;
+       tlv_reason.reason = reason;
        stream_put(s, &tlv_reason, sizeof(tlv_reason));
 
        /* Put IP address. */
@@ -85,7 +84,8 @@ static void ospf_gr_lsa_body_set(struct ospf_gr_info *gr_info,
 }
 
 /* Generate Grace-LSA for a given interface. */
-static struct ospf_lsa *ospf_gr_lsa_new(struct ospf_interface *oi)
+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;
@@ -112,7 +112,7 @@ static struct ospf_lsa *ospf_gr_lsa_new(struct ospf_interface *oi)
        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);
+       ospf_gr_lsa_body_set(&oi->ospf->gr_info, oi, reason, s);
 
        /* Set length. */
        length = stream_get_endp(s);
@@ -135,15 +135,20 @@ static struct ospf_lsa *ospf_gr_lsa_new(struct ospf_interface *oi)
 }
 
 /* Originate and install Grace-LSA for a given interface. */
-static void ospf_gr_lsa_originate(struct ospf_interface *oi, bool maxage)
+static void ospf_gr_lsa_originate(struct ospf_interface *oi,
+                                 enum ospf_gr_restart_reason reason,
+                                 bool maxage)
 {
        struct ospf_lsa *lsa, *old;
 
-       if (ospf_interface_neighbor_count(oi) == 0)
+       /* 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);
+       lsa = ospf_gr_lsa_new(oi, reason);
        if (!lsa) {
                zlog_warn("%s: ospf_gr_lsa_new() failed", __func__);
                return;
@@ -157,18 +162,36 @@ static void ospf_gr_lsa_originate(struct ospf_interface *oi, bool maxage)
        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;
+       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++;
-
-       /* Flood the LSA through out the interface */
-       ospf_flood_through_interface(oi, NULL, lsa);
 }
 
 /* Flush all self-originated Grace-LSAs. */
@@ -181,13 +204,14 @@ static void ospf_gr_flush_grace_lsas(struct ospf *ospf)
                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);
+               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);
 
-               for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi))
-                       ospf_gr_lsa_originate(oi, true);
+                       ospf_gr_lsa_originate(oi, ospf->gr_info.reason, true);
+               }
        }
 }
 
@@ -203,9 +227,6 @@ static void ospf_gr_restart_exit(struct ospf *ospf, const char *reason)
        ospf->gr_info.restart_in_progress = false;
        EVENT_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;
 
@@ -242,12 +263,34 @@ static void ospf_gr_restart_exit(struct ospf *ospf, const char *reason)
         *    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, int 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)
@@ -522,7 +565,7 @@ static char *ospf_gr_nvm_filepath(struct ospf *ospf)
  * 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)
+static void ospf_gr_nvm_update(struct ospf *ospf, bool prepare)
 {
        char *filepath;
        const char *inst_name;
@@ -550,16 +593,18 @@ static void ospf_gr_nvm_update(struct ospf *ospf)
                                       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.
         */
-       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);
+       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);
@@ -569,7 +614,7 @@ static void ospf_gr_nvm_update(struct ospf *ospf)
  * Delete GR status information about the given OSPF instance from non-volatile
  * memory.
  */
-static void ospf_gr_nvm_delete(struct ospf *ospf)
+void ospf_gr_nvm_delete(struct ospf *ospf)
 {
        char *filepath;
        const char *inst_name;
@@ -607,6 +652,7 @@ void ospf_gr_nvm_read(struct ospf *ospf)
        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);
@@ -629,29 +675,33 @@ void ospf_gr_nvm_read(struct ospf *ospf)
                                       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;
-               unsigned long remaining_time;
 
-               /* Check if the grace period has already expired. */
+               /* 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 {
-                       /* 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);
-                       event_add_timer(master, ospf_gr_grace_period_expired,
-                                       ospf, remaining_time,
-                                       &ospf->gr_info.t_grace_period);
-               }
+               } 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);
@@ -660,6 +710,12 @@ void ospf_gr_nvm_read(struct ospf *ospf)
        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);
+}
+
 /* Prepare to start a Graceful Restart. */
 static void ospf_gr_prepare(void)
 {
@@ -687,20 +743,12 @@ static void ospf_gr_prepare(void)
                        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, false);
+                       ospf_gr_lsa_originate(oi, OSPF_GR_SW_RESTART, false);
 
                /* Record end of the grace period in non-volatile memory. */
-               ospf_gr_nvm_update(ospf);
+               ospf_gr_nvm_update(ospf, true);
 
                /*
                 * Mark that a Graceful Restart preparation is in progress, to
@@ -749,6 +797,12 @@ DEFPY(graceful_restart, graceful_restart_cmd,
        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;
 }
 
@@ -771,6 +825,8 @@ DEFPY(no_graceful_restart, no_graceful_restart_cmd,
 
        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;
 }