X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=lib%2Fbfd.c;h=cc6d09a60f5a199ea77b9c42b7d5a08d9b490b19;hb=aa2602bf60426086b003471ab8c52d5c340d797f;hp=0974c268096d4bd1fe9896d169d901312125b1e1;hpb=c0267e5a1b078ea4644f60fd48c00f48042f157a;p=mirror_frr.git diff --git a/lib/bfd.c b/lib/bfd.c index 0974c2680..cc6d09a60 100644 --- 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 @@ -25,200 +10,137 @@ #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,97 +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; - - /** 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; @@ -660,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; @@ -719,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 @@ -736,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. */ @@ -762,22 +528,20 @@ static int _bfd_sess_send(struct thread *t) 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) @@ -791,6 +555,8 @@ 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)); } @@ -816,7 +582,8 @@ static bool bfd_sess_address_changed(const struct bfd_session_params *bsp, } 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)) @@ -824,6 +591,8 @@ void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp, /* 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; @@ -837,17 +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_INET, (struct in6_addr *)src, - (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; @@ -859,6 +633,9 @@ 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) @@ -905,36 +682,25 @@ void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id) /* 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 (bsp->args.ttl == ((BFD_SINGLE_HOP_TTL + 1) - min_ttl)) - return; - - /* 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.ttl == min_ttl) + 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); } @@ -952,16 +718,28 @@ void bfd_sess_set_timers(struct bfd_session_params *bsp, bsp->args.min_tx = min_tx; } +void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable) +{ + 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) @@ -969,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) @@ -1026,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) { @@ -1085,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; @@ -1109,17 +890,17 @@ static int zclient_bfd_session_reply(ZAPI_CALLBACK_ARGS) 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, *bspn; size_t sessions_updated = 0; @@ -1132,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; @@ -1224,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) @@ -1260,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. + * + * 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; +}