From b7ca809d1c1386aed0d4571cfbcd448b9868a298 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Tue, 15 Nov 2022 11:22:09 -0300 Subject: [PATCH] lib: BFD automatic source selection Implement new BFD library issue to allow protocols to configure BFD sessions with automatic source selection. The source selection will be based on the Next Hop Tracking feature: `zebra` will do RIB lookups to determine the output interface and the primary source address of that interface will be used as source. Signed-off-by: Rafael Zalamena --- lib/bfd.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/bfd.h | 17 +++ 2 files changed, 431 insertions(+) diff --git a/lib/bfd.c b/lib/bfd.c index 29b8d85d8d..1bd31920f0 100644 --- a/lib/bfd.c +++ b/lib/bfd.c @@ -34,6 +34,7 @@ #include "bfd.h" DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info"); +DEFINE_MTYPE_STATIC(LIB, BFD_SOURCE, "BFD source cache"); /** * BFD protocol integration configuration. @@ -47,6 +48,29 @@ enum bfd_session_event { BSE_INSTALL, }; +/** + * 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. + */ +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); + /** * Data structure to do the necessary tricks to hide the BFD protocol * integration internals. @@ -82,6 +106,11 @@ struct bfd_session_params { /** BFD session installation state. */ bool installed; + /** Automatic source selection. */ + bool auto_source; + /** Currently selected source. */ + struct bfd_source_cache *source_cache; + /** Global BFD paramaters list. */ TAILQ_ENTRY(bfd_session_params) entry; }; @@ -92,11 +121,15 @@ struct bfd_sessions_global { * without code duplication among daemons. */ TAILQ_HEAD(bsplist, bfd_session_params) bsplist; + /** BFD automatic source selection cache. */ + struct bfd_source_list source_list; /** Pointer to FRR's event manager. */ struct thread_master *tm; /** Pointer to zebra client data structure. */ struct zclient *zc; + /** Zebra next hop tracking (NHT) client. */ + struct zclient *nht_zclient; /** Debugging state. */ bool debugging; @@ -110,6 +143,34 @@ static struct bfd_sessions_global bsglobal; /** Global empty address for IPv4/IPv6. */ static const struct in6_addr i6a_zero; +/* + * Prototypes + */ +static void bfd_nht_zclient_connect(struct thread *thread); + +static void bfd_nht_zclient_connected(struct zclient *zclient); +static int bfd_nht_update(ZAPI_CALLBACK_ARGS); + +static void bfd_source_cache_get(struct bfd_session_params *session); +static void bfd_source_cache_put(struct bfd_session_params *session); + +static inline void +bfd_source_cache_register(const struct bfd_source_cache *source) +{ + zclient_send_rnh(bsglobal.nht_zclient, ZEBRA_NEXTHOP_REGISTER, + &source->address, SAFI_UNICAST, false, false, + source->vrf_id); +} + +static inline void +bfd_source_cache_unregister(const struct bfd_source_cache *source) +{ + zclient_send_rnh(bsglobal.nht_zclient, ZEBRA_NEXTHOP_UNREGISTER, + &source->address, SAFI_UNICAST, false, false, + source->vrf_id); +} + + /* * bfd_get_peer_info - Extract the Peer information for which the BFD session * went down from the message sent from Zebra to clients. @@ -531,6 +592,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)); } @@ -565,6 +628,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; @@ -578,6 +643,9 @@ 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, @@ -589,6 +657,8 @@ void bfd_sess_set_ipv6_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_INET6; @@ -600,6 +670,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) @@ -646,8 +719,13 @@ 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; + + if (bsp->auto_source) + bfd_source_cache_get(bsp); } void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops) @@ -677,6 +755,18 @@ 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; @@ -746,6 +836,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) { @@ -950,10 +1045,17 @@ int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS) return 0; } +static zclient_handler *const bfd_nht_handlers[] = { + [ZEBRA_NEXTHOP_UPDATE] = bfd_nht_update, +}; + void bfd_protocol_integration_init(struct zclient *zc, struct thread_master *tm) { + struct zclient_options bfd_nht_options = zclient_options_default; + /* Initialize data structure. */ TAILQ_INIT(&bsglobal.bsplist); + SLIST_INIT(&bsglobal.source_list); /* Copy pointers. */ bsglobal.zc = zc; @@ -964,6 +1066,16 @@ void bfd_protocol_integration_init(struct zclient *zc, struct thread_master *tm) /* Send the client registration */ bfd_client_sendmsg(zc, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); + + /* Start NHT client (for automatic source decisions). */ + bsglobal.nht_zclient = + zclient_new(tm, &bfd_nht_options, bfd_nht_handlers, + array_size(bfd_nht_handlers)); + bsglobal.nht_zclient->sock = -1; + bsglobal.nht_zclient->privs = zc->privs; + bsglobal.nht_zclient->zebra_connected = bfd_nht_zclient_connected; + thread_add_timer(tm, bfd_nht_zclient_connect, bsglobal.nht_zclient, 1, + &bsglobal.nht_zclient->t_connect); } void bfd_protocol_integration_set_debug(bool enable) @@ -985,3 +1097,305 @@ 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; + + bfd_source_cache_register(source); + + 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; + } + + bfd_source_cache_unregister(session->source_cache); + 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; +} + +static void bfd_nht_zclient_connect(struct thread *thread) +{ + struct zclient *zclient = THREAD_ARG(thread); + + if (bsglobal.debugging) + zlog_debug("BFD NHT zclient connection attempt"); + + if (zclient_start(zclient) == -1) { + if (bsglobal.debugging) + zlog_debug("BFD NHT zclient connection failed"); + + thread_add_timer(bsglobal.tm, bfd_nht_zclient_connect, zclient, + 3, &zclient->t_connect); + return; + } + + if (bsglobal.debugging) + zlog_debug("BFD NHT zclient connection succeeded"); +} + +static void bfd_nht_zclient_connected(struct zclient *zclient) +{ + struct bfd_source_cache *source; + + if (bsglobal.debugging) + zlog_debug("BFD NHT zclient connected"); + + SLIST_FOREACH (source, &bsglobal.source_list, entry) + bfd_source_cache_register(source); +} + +static int bfd_nht_update(ZAPI_CALLBACK_ARGS) +{ + struct bfd_source_cache *source; + struct zapi_route route; + struct prefix match; + + if (!zapi_nexthop_update_decode(zclient->ibuf, &match, &route)) { + zlog_warn("BFD NHT update decode failure"); + return 0; + } + if (cmd != ZEBRA_NEXTHOP_UPDATE) + return 0; + + 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; +} diff --git a/lib/bfd.h b/lib/bfd.h index 344ecc2c4d..b7e4eea5f3 100644 --- a/lib/bfd.h +++ b/lib/bfd.h @@ -221,6 +221,18 @@ void bfd_sess_set_timers(struct bfd_session_params *bsp, uint8_t detection_multiplier, uint32_t min_rx, uint32_t min_tx); +/** + * Configures the automatic source selection for the BFD session. + * + * NOTE: + * Setting this configuration will override the IP source value set by + * `bfd_sess_set_ipv4_addrs` or `bfd_sess_set_ipv6_addrs`. + * + * \param bsp BFD session parameters + * \param enable BFD automatic source selection state. + */ +void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable); + /** * Installs or updates the BFD session based on the saved session arguments. * @@ -330,6 +342,11 @@ void bfd_sess_timers(const struct bfd_session_params *bsp, uint8_t *detection_multiplier, uint32_t *min_rx, uint32_t *min_tx); +/** + * Gets the automatic source selection state. + */ +bool bfd_sess_auto_source(const struct bfd_session_params *bsp); + /** * Show BFD session configuration and status. If `json` is provided (e.g. not * `NULL`) then information will be inserted in object, otherwise printed to -- 2.39.5