]> git.proxmox.com Git - mirror_frr.git/blobdiff - lib/bfd.c
Merge pull request #13108 from opensourcerouting/clippy-string-warn
[mirror_frr.git] / lib / bfd.c
index d22a9150d19fc80967b8b66f897c6797ab8547cf..cc6d09a60f5a199ea77b9c42b7d5a08d9b490b19 100644 (file)
--- a/lib/bfd.c
+++ b/lib/bfd.c
@@ -1,23 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
 /**
  * bfd.c: BFD handling routines
  *
  * @copyright Copyright (C) 2015 Cumulus Networks, Inc.
- *
- * 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 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 "command.h"
 #include "memory.h"
 #include "prefix.h"
-#include "thread.h"
+#include "frrevent.h"
 #include "stream.h"
 #include "vrf.h"
 #include "zclient.h"
+#include "libfrr.h"
 #include "table.h"
 #include "vty.h"
 #include "bfd.h"
 
 DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info");
+DEFINE_MTYPE_STATIC(LIB, BFD_SOURCE, "BFD source cache");
 
-static int bfd_debug = 0;
-static struct bfd_gbl bfd_gbl;
-
-/*
- * bfd_gbl_init - Initialize the BFD global structure
- */
-void bfd_gbl_init(void)
-{
-       memset(&bfd_gbl, 0, sizeof(struct bfd_gbl));
-}
-
-/*
- * bfd_gbl_exit - Called when daemon exits
- */
-void bfd_gbl_exit(void)
-{
-       SET_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN);
-}
-
-/*
- * bfd_info_create - Allocate the BFD information
+/**
+ * BFD protocol integration configuration.
  */
-struct bfd_info *bfd_info_create(void)
-{
-       struct bfd_info *bfd_info;
-
-       bfd_info = XCALLOC(MTYPE_BFD_INFO, sizeof(struct bfd_info));
-       assert(bfd_info);
-
-       bfd_info->status = BFD_STATUS_UNKNOWN;
-       bfd_info->type = BFD_TYPE_NOT_CONFIGURED;
-       bfd_info->last_update = 0;
-       return bfd_info;
-}
 
-/*
- * bfd_info_free - Free the BFD information.
- */
-void bfd_info_free(struct bfd_info **bfd_info)
-{
-       XFREE(MTYPE_BFD_INFO, *bfd_info);
-}
+/** Events definitions. */
+enum bfd_session_event {
      /** Remove the BFD session configuration. */
+       BSE_UNINSTALL,
+       /** Install the BFD session configuration. */
+       BSE_INSTALL,
+};
 
-/*
- * bfd_validate_param - Validate the BFD paramter information.
+/**
+ * BFD source selection result cache.
+ *
+ * This structure will keep track of the result based on the destination
+ * prefix. When the result changes all related BFD sessions with automatic
+ * source will be updated.
  */
-int bfd_validate_param(struct vty *vty, const char *dm_str, const char *rx_str,
-                      const char *tx_str, uint8_t *dm_val, uint32_t *rx_val,
-                      uint32_t *tx_val)
-{
-       *dm_val = strtoul(dm_str, NULL, 10);
-       *rx_val = strtoul(rx_str, NULL, 10);
-       *tx_val = strtoul(tx_str, NULL, 10);
-       return CMD_SUCCESS;
-}
+struct bfd_source_cache {
+       /** Address VRF belongs. */
+       vrf_id_t vrf_id;
+       /** Destination network address. */
+       struct prefix address;
+       /** Source selected. */
+       struct prefix source;
+       /** Is the source address valid? */
+       bool valid;
+       /** BFD sessions using this. */
+       size_t refcount;
+
+       SLIST_ENTRY(bfd_source_cache) entry;
+};
+SLIST_HEAD(bfd_source_list, bfd_source_cache);
 
-/*
- * bfd_set_param - Set the configured BFD paramter values
+/**
+ * Data structure to do the necessary tricks to hide the BFD protocol
+ * integration internals.
  */
-void bfd_set_param(struct bfd_info **bfd_info, uint32_t min_rx, uint32_t min_tx,
-                  uint8_t detect_mult, const char *profile, int defaults,
-                  int *command)
-{
-       if (!*bfd_info) {
-               *bfd_info = bfd_info_create();
-               *command = ZEBRA_BFD_DEST_REGISTER;
-       } else {
-               if (((*bfd_info)->required_min_rx != min_rx)
-                   || ((*bfd_info)->desired_min_tx != min_tx)
-                   || ((*bfd_info)->detect_mult != detect_mult)
-                   || ((*bfd_info)->profile[0] == 0 && profile)
-                   || ((*bfd_info)->profile[0] && profile == NULL)
-                   || (profile && (*bfd_info)->profile[0]
-                       && strcmp((*bfd_info)->profile, profile)))
-                       *command = ZEBRA_BFD_DEST_UPDATE;
-       }
+struct bfd_session_params {
+       /** Contains the session parameters and more. */
+       struct bfd_session_arg args;
+       /** Contains the session state. */
+       struct bfd_session_status bss;
+       /** Protocol implementation status update callback. */
+       bsp_status_update updatecb;
+       /** Protocol implementation custom data pointer. */
+       void *arg;
 
-       if (*command) {
-               (*bfd_info)->required_min_rx = min_rx;
-               (*bfd_info)->desired_min_tx = min_tx;
-               (*bfd_info)->detect_mult = detect_mult;
-               if (profile)
-                       strlcpy((*bfd_info)->profile, profile,
-                               BFD_PROFILE_NAME_LEN);
-               else
-                       (*bfd_info)->profile[0] = '\0';
-       }
+       /**
+        * Next event.
+        *
+        * This variable controls what action to execute when the command batch
+        * finishes. Normally we'd use `event_add_event` value, however since
+        * that function is going to be called multiple times and the value
+        * might be different we'll use this variable to keep track of it.
+        */
+       enum bfd_session_event lastev;
+       /**
+        * BFD session configuration event.
+        *
+        * Multiple actions might be asked during a command batch (either via
+        * configuration load or northbound batch), so we'll use this to
+        * install/uninstall the BFD session parameters only once.
+        */
+       struct event *installev;
 
-       if (!defaults)
-               SET_FLAG((*bfd_info)->flags, BFD_FLAG_PARAM_CFG);
-       else
-               UNSET_FLAG((*bfd_info)->flags, BFD_FLAG_PARAM_CFG);
-}
+       /** BFD session installation state. */
+       bool installed;
 
-/*
- * bfd_peer_sendmsg - Format and send a peer register/Unregister
- *                    command to Zebra to be forwarded to BFD
- *
- * DEPRECATED: use zclient_bfd_command instead
- */
-void bfd_peer_sendmsg(struct zclient *zclient, struct bfd_info *bfd_info,
-                     int family, void *dst_ip, void *src_ip, char *if_name,
-                     int ttl, int multihop, int cbit, int command,
-                     int set_flag, vrf_id_t vrf_id)
-{
-       struct bfd_session_arg args = {};
-       size_t addrlen;
+       /** Automatic source selection. */
+       bool auto_source;
+       /** Currently selected source. */
+       struct bfd_source_cache *source_cache;
 
-       /* Individual reg/dereg messages are suppressed during shutdown. */
-       if (CHECK_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN)) {
-               if (bfd_debug)
-                       zlog_debug(
-                               "%s: Suppressing BFD peer reg/dereg messages",
-                               __func__);
-               return;
-       }
+       /** Global BFD paramaters list. */
+       TAILQ_ENTRY(bfd_session_params) entry;
+};
 
-       /* Check socket. */
-       if (!zclient || zclient->sock < 0) {
-               if (bfd_debug)
-                       zlog_debug(
-                               "%s: Can't send BFD peer register, Zebra client not established",
-                               __func__);
-               return;
-       }
+struct bfd_sessions_global {
+       /**
+        * Global BFD session parameters list for (re)installation and update
+        * without code duplication among daemons.
+        */
+       TAILQ_HEAD(bsplist, bfd_session_params) bsplist;
+       /** BFD automatic source selection cache. */
+       struct bfd_source_list source_list;
 
-       /* Fill in all arguments. */
-       args.ttl = ttl;
-       args.cbit = cbit;
-       args.family = family;
-       args.mhop = multihop;
-       args.vrf_id = vrf_id;
-       args.command = command;
-       args.set_flag = set_flag;
-       args.bfd_info = bfd_info;
-       if (args.bfd_info) {
-               args.min_rx = bfd_info->required_min_rx;
-               args.min_tx = bfd_info->desired_min_tx;
-               args.detection_multiplier = bfd_info->detect_mult;
-               if (bfd_info->profile[0]) {
-                       args.profilelen = strlen(bfd_info->profile);
-                       strlcpy(args.profile, bfd_info->profile,
-                               sizeof(args.profile));
-               }
-       }
+       /** Pointer to FRR's event manager. */
+       struct event_loop *tm;
+       /** Pointer to zebra client data structure. */
+       struct zclient *zc;
 
-       addrlen = family == AF_INET ? sizeof(struct in_addr)
-                                   : sizeof(struct in6_addr);
-       memcpy(&args.dst, dst_ip, addrlen);
-       if (src_ip)
-               memcpy(&args.src, src_ip, addrlen);
+       /** Debugging state. */
+       bool debugging;
+       /** Is shutting down? */
+       bool shutting_down;
+};
 
-       if (if_name)
-               args.ifnamelen =
-                       strlcpy(args.ifname, if_name, sizeof(args.ifname));
+/** Global configuration variable. */
+static struct bfd_sessions_global bsglobal;
 
-       zclient_bfd_command(zclient, &args);
-}
+/** Global empty address for IPv4/IPv6. */
+static const struct in6_addr i6a_zero;
 
 /*
- * bfd_get_command_dbg_str - Convert command to a debug string.
+ * Prototypes
  */
-const char *bfd_get_command_dbg_str(int command)
-{
-       switch (command) {
-       case ZEBRA_BFD_DEST_REGISTER:
-               return "Register";
-       case ZEBRA_BFD_DEST_DEREGISTER:
-               return "Deregister";
-       case ZEBRA_BFD_DEST_UPDATE:
-               return "Update";
-       default:
-               return "Unknown";
-       }
-}
+
+static void bfd_source_cache_get(struct bfd_session_params *session);
+static void bfd_source_cache_put(struct bfd_session_params *session);
 
 /*
  * bfd_get_peer_info - Extract the Peer information for which the BFD session
  *                     went down from the message sent from Zebra to clients.
  */
-struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
-                                   struct prefix *sp, int *status,
-                                   int *remote_cbit,
-                                   vrf_id_t vrf_id)
+static struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
+                                          struct prefix *sp, int *status,
+                                          int *remote_cbit, vrf_id_t vrf_id)
 {
        unsigned int ifindex;
        struct interface *ifp = NULL;
@@ -243,10 +165,10 @@ struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
        if (ifindex != 0) {
                ifp = if_lookup_by_index(ifindex, vrf_id);
                if (ifp == NULL) {
-                       if (bfd_debug)
+                       if (bsglobal.debugging)
                                zlog_debug(
-                                       "zebra_interface_bfd_read: Can't find interface by ifindex: %d ",
-                                       ifindex);
+                                       "%s: Can't find interface by ifindex: %d ",
+                                       __func__, ifindex);
                        return NULL;
                }
        }
@@ -273,6 +195,12 @@ struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
        return ifp;
 
 stream_failure:
+       /*
+        * Clean dp and sp because caller
+        * will immediately check them valid or not
+        */
+       memset(dp, 0, sizeof(*dp));
+       memset(sp, 0, sizeof(*sp));
        return NULL;
 }
 
@@ -305,7 +233,7 @@ static void bfd_last_update(time_t last_update, char *buf, size_t len)
        struct tm tm;
        struct timeval tv;
 
-       /* If no BFD satatus update has ever been received, print `never'. */
+       /* If no BFD status update has ever been received, print `never'. */
        if (last_update == 0) {
                snprintf(buf, len, "never");
                return;
@@ -321,96 +249,6 @@ static void bfd_last_update(time_t last_update, char *buf, size_t len)
                 tm.tm_min, tm.tm_sec);
 }
 
-/*
- * bfd_show_param - Show the BFD parameter information.
- */
-void bfd_show_param(struct vty *vty, struct bfd_info *bfd_info, int bfd_tag,
-                   int extra_space, bool use_json, json_object *json_obj)
-{
-       json_object *json_bfd = NULL;
-
-       if (!bfd_info)
-               return;
-
-       if (use_json) {
-               if (bfd_tag)
-                       json_bfd = json_object_new_object();
-               else
-                       json_bfd = json_obj;
-
-               json_object_int_add(json_bfd, "detectMultiplier",
-                                   bfd_info->detect_mult);
-               json_object_int_add(json_bfd, "rxMinInterval",
-                                   bfd_info->required_min_rx);
-               json_object_int_add(json_bfd, "txMinInterval",
-                                   bfd_info->desired_min_tx);
-               if (bfd_tag)
-                       json_object_object_add(json_obj, "peerBfdInfo",
-                                              json_bfd);
-       } else {
-               vty_out(vty,
-                       "  %s%sDetect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n",
-                       (extra_space) ? "  " : "", (bfd_tag) ? "BFD: " : "  ",
-                       bfd_info->detect_mult, bfd_info->required_min_rx,
-                       bfd_info->desired_min_tx);
-       }
-}
-
-/*
- * bfd_show_status - Show the BFD status information.
- */
-static void bfd_show_status(struct vty *vty, struct bfd_info *bfd_info,
-                           int bfd_tag, int extra_space, bool use_json,
-                           json_object *json_bfd)
-{
-       char time_buf[32];
-
-       if (!bfd_info)
-               return;
-
-       bfd_last_update(bfd_info->last_update, time_buf, 32);
-       if (use_json) {
-               json_object_string_add(json_bfd, "status",
-                                      bfd_get_status_str(bfd_info->status));
-               json_object_string_add(json_bfd, "lastUpdate", time_buf);
-       } else {
-               vty_out(vty, "  %s%sStatus: %s, Last update: %s\n",
-                       (extra_space) ? "  " : "", (bfd_tag) ? "BFD: " : "  ",
-                       bfd_get_status_str(bfd_info->status), time_buf);
-       }
-}
-
-/*
- * bfd_show_info - Show the BFD information.
- */
-void bfd_show_info(struct vty *vty, struct bfd_info *bfd_info, int multihop,
-                  int extra_space, bool use_json, json_object *json_obj)
-{
-       json_object *json_bfd = NULL;
-
-       if (!bfd_info)
-               return;
-
-       if (use_json) {
-               json_bfd = json_object_new_object();
-               if (multihop)
-                       json_object_string_add(json_bfd, "type", "multi hop");
-               else
-                       json_object_string_add(json_bfd, "type", "single hop");
-       } else {
-               vty_out(vty, "  %sBFD: Type: %s\n", (extra_space) ? "  " : "",
-                       (multihop) ? "multi hop" : "single hop");
-       }
-
-       bfd_show_param(vty, bfd_info, 0, extra_space, use_json, json_bfd);
-       bfd_show_status(vty, bfd_info, 0, extra_space, use_json, json_bfd);
-
-       if (use_json)
-               json_object_object_add(json_obj, "peerBfdInfo", json_bfd);
-       else
-               vty_out(vty, "\n");
-}
-
 /*
  * bfd_client_sendmsg - Format and send a client register
  *                    command to Zebra to be forwarded to BFD
@@ -423,7 +261,7 @@ void bfd_client_sendmsg(struct zclient *zclient, int command,
 
        /* Check socket. */
        if (!zclient || zclient->sock < 0) {
-               if (bfd_debug)
+               if (bsglobal.debugging)
                        zlog_debug(
                                "%s: Can't send BFD client register, Zebra client not established",
                                __func__);
@@ -441,10 +279,10 @@ void bfd_client_sendmsg(struct zclient *zclient, int command,
        ret = zclient_send_message(zclient);
 
        if (ret == ZCLIENT_SEND_FAILURE) {
-               if (bfd_debug)
+               if (bsglobal.debugging)
                        zlog_debug(
-                               "bfd_client_sendmsg %ld: zclient_send_message() failed",
-                               (long)getpid());
+                               "%s:  %ld: zclient_send_message() failed",
+                               __func__, (long)getpid());
                return;
        }
 
@@ -457,8 +295,8 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
        size_t addrlen;
 
        /* Individual reg/dereg messages are suppressed during shutdown. */
-       if (CHECK_FLAG(bfd_gbl.flags, BFD_GBL_FLAG_IN_SHUTDOWN)) {
-               if (bfd_debug)
+       if (bsglobal.shutting_down) {
+               if (bsglobal.debugging)
                        zlog_debug(
                                "%s: Suppressing BFD peer reg/dereg messages",
                                __func__);
@@ -467,7 +305,7 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
 
        /* Check socket. */
        if (!zc || zc->sock < 0) {
-               if (bfd_debug)
+               if (bsglobal.debugging)
                        zlog_debug("%s: zclient unavailable", __func__);
                return -1;
        }
@@ -502,13 +340,21 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
        stream_putw(s, args->family);
        stream_put(s, &args->src, addrlen);
 
-       /* Send the expected TTL. */
-       stream_putc(s, args->ttl);
+       /* Send the expected hops. */
+       stream_putc(s, args->hops);
 
        /* Send interface name if any. */
-       stream_putc(s, args->ifnamelen);
-       if (args->ifnamelen)
-               stream_put(s, args->ifname, args->ifnamelen);
+       if (args->mhop) {
+               /* Don't send interface. */
+               stream_putc(s, 0);
+               if (bsglobal.debugging && args->ifnamelen)
+                       zlog_debug("%s: multi hop is configured, not sending interface",
+                                  __func__);
+       } else {
+               stream_putc(s, args->ifnamelen);
+               if (args->ifnamelen)
+                       stream_put(s, args->ifname, args->ifnamelen);
+       }
 
        /* Send the C bit indicator. */
        stream_putc(s, args->cbit);
@@ -533,8 +379,8 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
                stream_putw(s, args->family);
                stream_put(s, &args->src, addrlen);
 
-               /* Send the expected TTL. */
-               stream_putc(s, args->ttl);
+               /* Send the expected hops. */
+               stream_putc(s, args->hops);
        } else {
                /* Multi hop indicator. */
                stream_putc(s, 0);
@@ -550,6 +396,9 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
                if (args->ifnamelen)
                        stream_put(s, args->ifname, args->ifnamelen);
        }
+
+       /* Send the C bit indicator. */
+       stream_putc(s, args->cbit);
 #endif /* HAVE_BFDD */
 
        /* Finish the message by writing the size. */
@@ -557,99 +406,14 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
 
        /* Send message to zebra. */
        if (zclient_send_message(zc) == ZCLIENT_SEND_FAILURE) {
-               if (bfd_debug)
+               if (bsglobal.debugging)
                        zlog_debug("%s: zclient_send_message failed", __func__);
                return -1;
        }
 
-       /* Write registration indicator into data structure. */
-       if (args->bfd_info && args->set_flag) {
-               if (args->command == ZEBRA_BFD_DEST_REGISTER)
-                       SET_FLAG(args->bfd_info->flags, BFD_FLAG_BFD_REG);
-               else if (args->command == ZEBRA_BFD_DEST_DEREGISTER)
-                       UNSET_FLAG(args->bfd_info->flags, BFD_FLAG_BFD_REG);
-       }
-
        return 0;
 }
 
-/**
- * BFD protocol integration configuration.
- */
-
-/** Events definitions. */
-enum bfd_session_event {
-       /** Remove the BFD session configuration. */
-       BSE_UNINSTALL,
-       /** Install the BFD session configuration. */
-       BSE_INSTALL,
-};
-
-/**
- * Data structure to do the necessary tricks to hide the BFD protocol
- * integration internals.
- */
-struct bfd_session_params {
-       /** Contains the session parameters and more. */
-       struct bfd_session_arg args;
-       /** Contains the session state. */
-       struct bfd_session_status bss;
-       /** Protocol implementation status update callback. */
-       bsp_status_update updatecb;
-       /** Protocol implementation custom data pointer. */
-       void *arg;
-
-       /**
-        * Next event.
-        *
-        * This variable controls what action to execute when the command batch
-        * finishes. Normally we'd use `thread_add_event` value, however since
-        * that function is going to be called multiple times and the value
-        * might be different we'll use this variable to keep track of it.
-        */
-       enum bfd_session_event lastev;
-       /**
-        * BFD session configuration event.
-        *
-        * Multiple actions might be asked during a command batch (either via
-        * configuration load or northbound batch), so we'll use this to
-        * install/uninstall the BFD session parameters only once.
-        */
-       struct thread *installev;
-
-       /** BFD session installation state. */
-       bool installed;
-       /** BFD session enabled. */
-       bool enabled;
-
-       /** Global BFD paramaters list. */
-       TAILQ_ENTRY(bfd_session_params) entry;
-};
-
-struct bfd_sessions_global {
-       /**
-        * Global BFD session parameters list for (re)installation and update
-        * without code duplication among daemons.
-        */
-       TAILQ_HEAD(bsplist, bfd_session_params) bsplist;
-
-       /** Pointer to FRR's event manager. */
-       struct thread_master *tm;
-       /** Pointer to zebra client data structure. */
-       struct zclient *zc;
-
-       /** Debugging state. */
-       bool debugging;
-       /** Is shutting down? */
-       bool shutting_down;
-};
-
-/** Global configuration variable. */
-static struct bfd_sessions_global bsglobal;
-
-/** Global empty address for IPv4/IPv6. */
-static const struct in6_addr i6a_zero;
-
 struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *arg)
 {
        struct bfd_session_params *bsp;
@@ -662,7 +426,7 @@ struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *arg)
 
        /* Set defaults. */
        bsp->args.detection_multiplier = BFD_DEF_DETECT_MULT;
-       bsp->args.ttl = BFD_SINGLE_HOP_TTL;
+       bsp->args.hops = 1;
        bsp->args.min_rx = BFD_DEF_MIN_RX;
        bsp->args.min_tx = BFD_DEF_MIN_TX;
        bsp->args.vrf_id = VRF_DEFAULT;
@@ -721,14 +485,14 @@ static bool _bfd_sess_valid(const struct bfd_session_params *bsp)
        return true;
 }
 
-static int _bfd_sess_send(struct thread *t)
+static void _bfd_sess_send(struct event *t)
 {
-       struct bfd_session_params *bsp = THREAD_ARG(t);
+       struct bfd_session_params *bsp = EVENT_ARG(t);
        int rv;
 
        /* Validate configuration before trying to send bogus data. */
        if (!_bfd_sess_valid(bsp))
-               return 0;
+               return;
 
        if (bsp->lastev == BSE_INSTALL) {
                bsp->args.command = bsp->installed ? ZEBRA_BFD_DEST_UPDATE
@@ -738,7 +502,7 @@ static int _bfd_sess_send(struct thread *t)
 
        /* If not installed and asked for uninstall, do nothing. */
        if (!bsp->installed && bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER)
-               return 0;
+               return;
 
        rv = zclient_bfd_command(bsglobal.zc, &bsp->args);
        /* Command was sent successfully. */
@@ -748,23 +512,36 @@ static int _bfd_sess_send(struct thread *t)
                        bsp->installed = false;
                else if (bsp->args.command == ZEBRA_BFD_DEST_REGISTER)
                        bsp->installed = true;
+       } else {
+               struct ipaddr src, dst;
+
+               src.ipa_type = bsp->args.family;
+               src.ipaddr_v6 = bsp->args.src;
+               dst.ipa_type = bsp->args.family;
+               dst.ipaddr_v6 = bsp->args.dst;
+
+               zlog_err(
+                       "%s: BFD session %pIA -> %pIA interface %s VRF %s(%u) was not %s",
+                       __func__, &src, &dst,
+                       bsp->args.ifnamelen ? bsp->args.ifname : "*",
+                       vrf_id_to_name(bsp->args.vrf_id), bsp->args.vrf_id,
+                       bsp->lastev == BSE_INSTALL ? "installed"
+                                                  : "uninstalled");
        }
-
-       return 0;
 }
 
 static void _bfd_sess_remove(struct bfd_session_params *bsp)
 {
+       /* Cancel any pending installation request. */
+       EVENT_OFF(bsp->installev);
+
        /* Not installed, nothing to do. */
        if (!bsp->installed)
                return;
 
-       /* Cancel any pending installation request. */
-       THREAD_OFF(bsp->installev);
-
        /* Send request to remove any session. */
        bsp->lastev = BSE_UNINSTALL;
-       thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
+       event_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
 }
 
 void bfd_sess_free(struct bfd_session_params **bsp)
@@ -778,24 +555,44 @@ void bfd_sess_free(struct bfd_session_params **bsp)
        /* Remove from global list. */
        TAILQ_REMOVE(&bsglobal.bsplist, (*bsp), entry);
 
+       bfd_source_cache_put(*bsp);
+
        /* Free the memory and point to NULL. */
        XFREE(MTYPE_BFD_INFO, (*bsp));
 }
 
-void bfd_sess_enable(struct bfd_session_params *bsp, bool enable)
+static bool bfd_sess_address_changed(const struct bfd_session_params *bsp,
+                                    uint32_t family,
+                                    const struct in6_addr *src,
+                                    const struct in6_addr *dst)
 {
-       /* Remove the session when disabling. */
-       if (!enable)
-               _bfd_sess_remove(bsp);
+       size_t addrlen;
+
+       if (bsp->args.family != family)
+               return true;
+
+       addrlen = (family == AF_INET) ? sizeof(struct in_addr)
+                                     : sizeof(struct in6_addr);
+       if ((src == NULL && memcmp(&bsp->args.src, &i6a_zero, addrlen))
+           || (src && memcmp(src, &bsp->args.src, addrlen))
+           || memcmp(dst, &bsp->args.dst, addrlen))
+               return true;
 
-       bsp->enabled = enable;
+       return false;
 }
 
 void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp,
-                            struct in_addr *src, struct in_addr *dst)
+                            const struct in_addr *src,
+                            const struct in_addr *dst)
 {
+       if (!bfd_sess_address_changed(bsp, AF_INET, (struct in6_addr *)src,
+                                     (struct in6_addr *)dst))
+               return;
+
        /* If already installed, remove the old setting. */
        _bfd_sess_remove(bsp);
+       /* Address changed so we must reapply auto source. */
+       bfd_source_cache_put(bsp);
 
        bsp->args.family = AF_INET;
 
@@ -809,13 +606,22 @@ void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp,
 
        assert(dst);
        memcpy(&bsp->args.dst, dst, sizeof(struct in_addr));
+
+       if (bsp->auto_source)
+               bfd_source_cache_get(bsp);
 }
 
 void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp,
-                            struct in6_addr *src, struct in6_addr *dst)
+                            const struct in6_addr *src,
+                            const struct in6_addr *dst)
 {
+       if (!bfd_sess_address_changed(bsp, AF_INET6, src, dst))
+               return;
+
        /* If already installed, remove the old setting. */
        _bfd_sess_remove(bsp);
+       /* Address changed so we must reapply auto source. */
+       bfd_source_cache_put(bsp);
 
        bsp->args.family = AF_INET6;
 
@@ -827,10 +633,17 @@ void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp,
 
        assert(dst);
        bsp->args.dst = *dst;
+
+       if (bsp->auto_source)
+               bfd_source_cache_get(bsp);
 }
 
 void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname)
 {
+       if ((ifname == NULL && bsp->args.ifnamelen == 0)
+           || (ifname && strcmp(bsp->args.ifname, ifname) == 0))
+               return;
+
        /* If already installed, remove the old setting. */
        _bfd_sess_remove(bsp);
 
@@ -864,32 +677,30 @@ void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile)
 
 void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id)
 {
+       if (bsp->args.vrf_id == vrf_id)
+               return;
+
        /* If already installed, remove the old setting. */
        _bfd_sess_remove(bsp);
+       /* Address changed so we must reapply auto source. */
+       bfd_source_cache_put(bsp);
 
        bsp->args.vrf_id = vrf_id;
-}
 
-void bfd_sess_set_mininum_ttl(struct bfd_session_params *bsp, uint8_t min_ttl)
-{
-       assert(min_ttl != 0);
-
-       /* If already installed, remove the old setting. */
-       _bfd_sess_remove(bsp);
-
-       /* Invert TTL value: protocol expects number of hops. */
-       min_ttl = (BFD_SINGLE_HOP_TTL + 1) - min_ttl;
-       bsp->args.ttl = min_ttl;
-       bsp->args.mhop = (min_ttl > 1);
+       if (bsp->auto_source)
+               bfd_source_cache_get(bsp);
 }
 
-void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t min_ttl)
+void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops)
 {
+       if (bsp->args.hops == hops)
+               return;
+
        /* If already installed, remove the old setting. */
        _bfd_sess_remove(bsp);
 
-       bsp->args.ttl = min_ttl;
-       bsp->args.mhop = (min_ttl > 1);
+       bsp->args.hops = hops;
+       bsp->args.mhop = (hops > 1);
 }
 
 
@@ -907,20 +718,28 @@ void bfd_sess_set_timers(struct bfd_session_params *bsp,
        bsp->args.min_tx = min_tx;
 }
 
-void bfd_sess_install(struct bfd_session_params *bsp)
+void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable)
 {
-       /* Don't attempt to install/update when disabled. */
-       if (!bsp->enabled)
+       if (bsp->auto_source == enable)
                return;
 
+       bsp->auto_source = enable;
+       if (enable)
+               bfd_source_cache_get(bsp);
+       else
+               bfd_source_cache_put(bsp);
+}
+
+void bfd_sess_install(struct bfd_session_params *bsp)
+{
        bsp->lastev = BSE_INSTALL;
-       thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
+       event_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
 }
 
 void bfd_sess_uninstall(struct bfd_session_params *bsp)
 {
        bsp->lastev = BSE_UNINSTALL;
-       thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
+       event_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
 }
 
 enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp)
@@ -928,14 +747,9 @@ enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp)
        return bsp->bss.state;
 }
 
-uint8_t bfd_sess_minimum_ttl(const struct bfd_session_params *bsp)
-{
-       return ((BFD_SINGLE_HOP_TTL + 1) - bsp->args.ttl);
-}
-
 uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp)
 {
-       return bsp->args.ttl;
+       return bsp->args.hops;
 }
 
 const char *bfd_sess_profile(const struct bfd_session_params *bsp)
@@ -985,6 +799,11 @@ void bfd_sess_timers(const struct bfd_session_params *bsp,
        *min_tx = bsp->args.min_tx;
 }
 
+bool bfd_sess_auto_source(const struct bfd_session_params *bsp)
+{
+       return bsp->auto_source;
+}
+
 void bfd_sess_show(struct vty *vty, struct json_object *json,
                   struct bfd_session_params *bsp)
 {
@@ -1044,10 +863,13 @@ void bfd_sess_show(struct vty *vty, struct json_object *json,
  *
  * Use this as `zclient` `bfd_dest_replay` callback.
  */
-static int zclient_bfd_session_reply(ZAPI_CALLBACK_ARGS)
+int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS)
 {
        struct bfd_session_params *bsp;
 
+       if (!zclient->bfd_integration)
+               return 0;
+
        /* Do nothing when shutting down. */
        if (bsglobal.shutting_down)
                return 0;
@@ -1060,27 +882,27 @@ static int zclient_bfd_session_reply(ZAPI_CALLBACK_ARGS)
 
        /* Replay all activated peers. */
        TAILQ_FOREACH (bsp, &bsglobal.bsplist, entry) {
-               /* Skip disabled sessions. */
-               if (!bsp->enabled)
+               /* Skip not installed sessions. */
+               if (!bsp->installed)
                        continue;
 
                /* We are reconnecting, so we must send installation. */
                bsp->installed = false;
 
                /* Cancel any pending installation request. */
-               THREAD_OFF(bsp->installev);
+               EVENT_OFF(bsp->installev);
 
                /* Ask for installation. */
                bsp->lastev = BSE_INSTALL;
-               thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
+               event_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
        }
 
        return 0;
 }
 
-static int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
+int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
 {
-       struct bfd_session_params *bsp;
+       struct bfd_session_params *bsp, *bspn;
        size_t sessions_updated = 0;
        struct interface *ifp;
        int remote_cbit = false;
@@ -1091,6 +913,9 @@ static int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
        struct prefix sp;
        char ifstr[128], cbitstr[32];
 
+       if (!zclient->bfd_integration)
+               return 0;
+
        /* Do nothing when shutting down. */
        if (bsglobal.shutting_down)
                return 0;
@@ -1137,9 +962,9 @@ static int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
        now = monotime(NULL);
 
        /* Notify all matching sessions about update. */
-       TAILQ_FOREACH (bsp, &bsglobal.bsplist, entry) {
-               /* Skip disabled or not installed entries. */
-               if (!bsp->enabled || !bsp->installed)
+       TAILQ_FOREACH_SAFE (bsp, &bsglobal.bsplist, entry, bspn) {
+               /* Skip not installed entries. */
+               if (!bsp->installed)
                        continue;
                /* Skip different VRFs. */
                if (bsp->args.vrf_id != vrf_id)
@@ -1183,21 +1008,54 @@ static int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
        return 0;
 }
 
-void bfd_protocol_integration_init(struct zclient *zc, struct thread_master *tm)
+/**
+ * Frees all allocated resources and stops any activity.
+ *
+ * Must be called after every BFD session has been successfully
+ * unconfigured otherwise this function will `free()` any available
+ * session causing existing pointers to dangle.
+ *
+ * This is just a comment, in practice it will be called by the FRR
+ * library late finish hook. \see `bfd_protocol_integration_init`.
+ */
+static int bfd_protocol_integration_finish(void)
+{
+       if (bsglobal.zc == NULL)
+               return 0;
+
+       while (!TAILQ_EMPTY(&bsglobal.bsplist)) {
+               struct bfd_session_params *session =
+                       TAILQ_FIRST(&bsglobal.bsplist);
+               bfd_sess_free(&session);
+       }
+
+       /*
+        * BFD source cache is linked to sessions, if all sessions are gone
+        * then the source cache must be empty.
+        */
+       if (!SLIST_EMPTY(&bsglobal.source_list))
+               zlog_warn("BFD integration source cache not empty");
+
+       return 0;
+}
+
+void bfd_protocol_integration_init(struct zclient *zc, struct event_loop *tm)
 {
        /* Initialize data structure. */
        TAILQ_INIT(&bsglobal.bsplist);
+       SLIST_INIT(&bsglobal.source_list);
 
        /* Copy pointers. */
        bsglobal.zc = zc;
        bsglobal.tm = tm;
 
-       /* Install our callbacks. */
-       zc->interface_bfd_dest_update = zclient_bfd_session_update;
-       zc->bfd_dest_replay = zclient_bfd_session_reply;
+       /* Enable BFD callbacks. */
+       zc->bfd_integration = true;
 
        /* Send the client registration */
        bfd_client_sendmsg(zc, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
+
+       hook_register(frr_fini, bfd_protocol_integration_finish);
 }
 
 void bfd_protocol_integration_set_debug(bool enable)
@@ -1219,3 +1077,262 @@ bool bfd_protocol_integration_shutting_down(void)
 {
        return bsglobal.shutting_down;
 }
+
+/*
+ * BFD automatic source selection
+ *
+ * This feature will use the next hop tracking (NHT) provided by zebra
+ * to find out the source address by looking at the output interface.
+ *
+ * When the interface address / routing table change we'll be notified
+ * and be able to update the source address accordingly.
+ *
+ *     <daemon>                 zebra
+ *         |
+ * +-----------------+
+ * | BFD session set |
+ * | to auto source  |
+ * +-----------------+
+ *         |
+ *         \                 +-----------------+
+ *          -------------->  | Resolves        |
+ *                           | destination     |
+ *                           | address         |
+ *                           +-----------------+
+ *                                |
+ * +-----------------+            /
+ * | Sets resolved   | <----------
+ * | source address  |
+ * +-----------------+
+ */
+static bool
+bfd_source_cache_session_match(const struct bfd_source_cache *source,
+                              const struct bfd_session_params *session)
+{
+       const struct in_addr *address;
+       const struct in6_addr *address_v6;
+
+       if (session->args.vrf_id != source->vrf_id)
+               return false;
+       if (session->args.family != source->address.family)
+               return false;
+
+       switch (session->args.family) {
+       case AF_INET:
+               address = (const struct in_addr *)&session->args.dst;
+               if (address->s_addr != source->address.u.prefix4.s_addr)
+                       return false;
+               break;
+       case AF_INET6:
+               address_v6 = &session->args.dst;
+               if (memcmp(address_v6, &source->address.u.prefix6,
+                          sizeof(struct in6_addr)))
+                       return false;
+               break;
+       default:
+               return false;
+       }
+
+       return true;
+}
+
+static struct bfd_source_cache *
+bfd_source_cache_find(vrf_id_t vrf_id, const struct prefix *prefix)
+{
+       struct bfd_source_cache *source;
+
+       SLIST_FOREACH (source, &bsglobal.source_list, entry) {
+               if (source->vrf_id != vrf_id)
+                       continue;
+               if (!prefix_same(&source->address, prefix))
+                       continue;
+
+               return source;
+       }
+
+       return NULL;
+}
+
+static void bfd_source_cache_get(struct bfd_session_params *session)
+{
+       struct bfd_source_cache *source;
+       struct prefix target = {};
+
+       switch (session->args.family) {
+       case AF_INET:
+               target.family = AF_INET;
+               target.prefixlen = IPV4_MAX_BITLEN;
+               memcpy(&target.u.prefix4, &session->args.dst,
+                      sizeof(struct in_addr));
+               break;
+       case AF_INET6:
+               target.family = AF_INET6;
+               target.prefixlen = IPV6_MAX_BITLEN;
+               memcpy(&target.u.prefix6, &session->args.dst,
+                      sizeof(struct in6_addr));
+               break;
+       default:
+               return;
+       }
+
+       source = bfd_source_cache_find(session->args.vrf_id, &target);
+       if (source) {
+               if (session->source_cache == source)
+                       return;
+
+               bfd_source_cache_put(session);
+               session->source_cache = source;
+               source->refcount++;
+               return;
+       }
+
+       source = XCALLOC(MTYPE_BFD_SOURCE, sizeof(*source));
+       prefix_copy(&source->address, &target);
+       source->vrf_id = session->args.vrf_id;
+       SLIST_INSERT_HEAD(&bsglobal.source_list, source, entry);
+
+       bfd_source_cache_put(session);
+       session->source_cache = source;
+       source->refcount = 1;
+
+       return;
+}
+
+static void bfd_source_cache_put(struct bfd_session_params *session)
+{
+       if (session->source_cache == NULL)
+               return;
+
+       session->source_cache->refcount--;
+       if (session->source_cache->refcount > 0) {
+               session->source_cache = NULL;
+               return;
+       }
+
+       SLIST_REMOVE(&bsglobal.source_list, session->source_cache,
+                    bfd_source_cache, entry);
+       XFREE(MTYPE_BFD_SOURCE, session->source_cache);
+}
+
+/** Updates BFD running session if source address has changed. */
+static void
+bfd_source_cache_update_session(const struct bfd_source_cache *source,
+                               struct bfd_session_params *session)
+{
+       const struct in_addr *address;
+       const struct in6_addr *address_v6;
+
+       switch (session->args.family) {
+       case AF_INET:
+               address = (const struct in_addr *)&session->args.src;
+               if (memcmp(address, &source->source.u.prefix4,
+                          sizeof(struct in_addr)) == 0)
+                       return;
+
+               _bfd_sess_remove(session);
+               memcpy(&session->args.src, &source->source.u.prefix4,
+                      sizeof(struct in_addr));
+               break;
+       case AF_INET6:
+               address_v6 = &session->args.src;
+               if (memcmp(address_v6, &source->source.u.prefix6,
+                          sizeof(struct in6_addr)) == 0)
+                       return;
+
+               _bfd_sess_remove(session);
+               memcpy(&session->args.src, &source->source.u.prefix6,
+                      sizeof(struct in6_addr));
+               break;
+       default:
+               return;
+       }
+
+       bfd_sess_install(session);
+}
+
+static void
+bfd_source_cache_update_sessions(const struct bfd_source_cache *source)
+{
+       struct bfd_session_params *session;
+
+       if (!source->valid)
+               return;
+
+       TAILQ_FOREACH (session, &bsglobal.bsplist, entry) {
+               if (!session->auto_source)
+                       continue;
+               if (!bfd_source_cache_session_match(source, session))
+                       continue;
+
+               bfd_source_cache_update_session(source, session);
+       }
+}
+
+/**
+ * Try to translate next hop information into source address.
+ *
+ * \returns `true` if source changed otherwise `false`.
+ */
+static bool bfd_source_cache_update(struct bfd_source_cache *source,
+                                   const struct zapi_route *route)
+{
+       size_t nh_index;
+
+       for (nh_index = 0; nh_index < route->nexthop_num; nh_index++) {
+               const struct zapi_nexthop *nh = &route->nexthops[nh_index];
+               const struct interface *interface;
+               const struct connected *connected;
+               const struct listnode *node;
+
+               interface = if_lookup_by_index(nh->ifindex, nh->vrf_id);
+               if (interface == NULL) {
+                       zlog_err("next hop interface not found (index %d)",
+                                nh->ifindex);
+                       continue;
+               }
+
+               for (ALL_LIST_ELEMENTS_RO(interface->connected, node,
+                                         connected)) {
+                       if (source->address.family !=
+                           connected->address->family)
+                               continue;
+                       if (prefix_same(connected->address, &source->source))
+                               return false;
+                       /*
+                        * Skip link-local as it is only useful for single hop
+                        * and in that case no source is specified usually.
+                        */
+                       if (source->address.family == AF_INET6 &&
+                           IN6_IS_ADDR_LINKLOCAL(
+                                   &connected->address->u.prefix6))
+                               continue;
+
+                       prefix_copy(&source->source, connected->address);
+                       source->valid = true;
+                       return true;
+               }
+       }
+
+       memset(&source->source, 0, sizeof(source->source));
+       source->valid = false;
+       return false;
+}
+
+int bfd_nht_update(const struct prefix *match, const struct zapi_route *route)
+{
+       struct bfd_source_cache *source;
+
+       if (bsglobal.debugging)
+               zlog_debug("BFD NHT update for %pFX", &route->prefix);
+
+       SLIST_FOREACH (source, &bsglobal.source_list, entry) {
+               if (source->vrf_id != route->vrf_id)
+                       continue;
+               if (!prefix_same(match, &source->address))
+                       continue;
+               if (bfd_source_cache_update(source, route))
+                       bfd_source_cache_update_sessions(source);
+       }
+
+       return 0;
+}