diff options
Diffstat (limited to 'bgpd/bgp_keepalives.c')
-rw-r--r-- | bgpd/bgp_keepalives.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/bgpd/bgp_keepalives.c b/bgpd/bgp_keepalives.c new file mode 100644 index 0000000..92123c2 --- /dev/null +++ b/bgpd/bgp_keepalives.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Keepalives. + * Implements a producer thread to generate BGP keepalives for peers. + * Copyright (C) 2017 Cumulus Networks, Inc. + * Quentin Young + */ + +/* clang-format off */ +#include <zebra.h> +#include <pthread.h> // for pthread_mutex_lock, pthread_mutex_unlock + +#include "frr_pthread.h" // for frr_pthread +#include "hash.h" // for hash, hash_clean, hash_create_size... +#include "log.h" // for zlog_debug +#include "memory.h" // for MTYPE_TMP, XFREE, XCALLOC, XMALLOC +#include "monotime.h" // for monotime, monotime_since + +#include "bgpd/bgpd.h" // for peer, PEER_EVENT_KEEPALIVES_ON, peer... +#include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events +#include "bgpd/bgp_packet.h" // for bgp_keepalive_send +#include "bgpd/bgp_keepalives.h" +/* clang-format on */ + +DEFINE_MTYPE_STATIC(BGPD, BGP_PKAT, "Peer KeepAlive Timer"); +DEFINE_MTYPE_STATIC(BGPD, BGP_COND, "BGP Peer pthread Conditional"); +DEFINE_MTYPE_STATIC(BGPD, BGP_MUTEX, "BGP Peer pthread Mutex"); + +/* + * Peer KeepAlive Timer. + * Associates a peer with the time of its last keepalive. + */ +struct pkat { + /* the peer to send keepalives to */ + struct peer *peer; + /* absolute time of last keepalive sent */ + struct timeval last; +}; + +/* List of peers we are sending keepalives for, and associated mutex. */ +static pthread_mutex_t *peerhash_mtx; +static pthread_cond_t *peerhash_cond; +static struct hash *peerhash; + +static struct pkat *pkat_new(struct peer *peer) +{ + struct pkat *pkat = XMALLOC(MTYPE_BGP_PKAT, sizeof(struct pkat)); + pkat->peer = peer; + monotime(&pkat->last); + return pkat; +} + +static void pkat_del(void *pkat) +{ + XFREE(MTYPE_BGP_PKAT, pkat); +} + + +/* + * Callback for hash_iterate. Determines if a peer needs a keepalive and if so, + * generates and sends it. + * + * For any given peer, if the elapsed time since its last keepalive exceeds its + * configured keepalive timer, a keepalive is sent to the peer and its + * last-sent time is reset. Additionally, If the elapsed time does not exceed + * the configured keepalive timer, but the time until the next keepalive is due + * is within a hardcoded tolerance, a keepalive is sent as if the configured + * timer was exceeded. Doing this helps alleviate nanosecond sleeps between + * ticks by grouping together peers who are due for keepalives at roughly the + * same time. This tolerance value is arbitrarily chosen to be 100ms. + * + * In addition, this function calculates the maximum amount of time that the + * keepalive thread can sleep before another tick needs to take place. This is + * equivalent to shortest time until a keepalive is due for any one peer. + * + * @return maximum time to wait until next update (0 if infinity) + */ +static void peer_process(struct hash_bucket *hb, void *arg) +{ + struct pkat *pkat = hb->data; + + struct timeval *next_update = arg; + + static struct timeval elapsed; // elapsed time since keepalive + static struct timeval ka = {0}; // peer->v_keepalive as a timeval + static struct timeval diff; // ka - elapsed + + static const struct timeval tolerance = {0, 100000}; + + uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive, + memory_order_relaxed); + + /* 0 keepalive timer means no keepalives */ + if (v_ka == 0) + return; + + /* calculate elapsed time since last keepalive */ + monotime_since(&pkat->last, &elapsed); + + /* calculate difference between elapsed time and configured time */ + ka.tv_sec = v_ka; + timersub(&ka, &elapsed, &diff); + + int send_keepalive = + elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <); + + if (send_keepalive) { + if (bgp_debug_keepalive(pkat->peer)) + zlog_debug("%s [FSM] Timer (keepalive timer expire)", + pkat->peer->host); + + bgp_keepalive_send(pkat->peer); + monotime(&pkat->last); + memset(&elapsed, 0, sizeof(elapsed)); + diff = ka; + } + + /* if calculated next update for this peer < current delay, use it */ + if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <)) + *next_update = diff; +} + +static bool peer_hash_cmp(const void *f, const void *s) +{ + const struct pkat *p1 = f; + const struct pkat *p2 = s; + + return p1->peer == p2->peer; +} + +static unsigned int peer_hash_key(const void *arg) +{ + const struct pkat *pkat = arg; + return (uintptr_t)pkat->peer; +} + +/* Cleanup handler / deinitializer. */ +static void bgp_keepalives_finish(void *arg) +{ + hash_clean_and_free(&peerhash, pkat_del); + + pthread_mutex_unlock(peerhash_mtx); + pthread_mutex_destroy(peerhash_mtx); + pthread_cond_destroy(peerhash_cond); + + XFREE(MTYPE_BGP_MUTEX, peerhash_mtx); + XFREE(MTYPE_BGP_COND, peerhash_cond); +} + +/* + * Entry function for peer keepalive generation pthread. + */ +void *bgp_keepalives_start(void *arg) +{ + struct frr_pthread *fpt = arg; + fpt->master->owner = pthread_self(); + + struct timeval currtime = {0, 0}; + struct timeval aftertime = {0, 0}; + struct timeval next_update = {0, 0}; + struct timespec next_update_ts = {0, 0}; + + /* + * The RCU mechanism for each pthread is initialized in a "locked" + * state. That's ok for pthreads using the frr_pthread, + * event_fetch event loop, because that event loop unlocks regularly. + * For foreign pthreads, the lock needs to be unlocked so that the + * background rcu pthread can run. + */ + rcu_read_unlock(); + + peerhash_mtx = XCALLOC(MTYPE_BGP_MUTEX, sizeof(pthread_mutex_t)); + peerhash_cond = XCALLOC(MTYPE_BGP_COND, sizeof(pthread_cond_t)); + + /* initialize mutex */ + pthread_mutex_init(peerhash_mtx, NULL); + + /* use monotonic clock with condition variable */ + pthread_condattr_t attrs; + pthread_condattr_init(&attrs); + pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC); + pthread_cond_init(peerhash_cond, &attrs); + pthread_condattr_destroy(&attrs); + + /* + * We are not using normal FRR pthread mechanics and are + * not using fpt_run + */ + frr_pthread_set_name(fpt); + + /* initialize peer hashtable */ + peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL); + pthread_mutex_lock(peerhash_mtx); + + /* register cleanup handler */ + pthread_cleanup_push(&bgp_keepalives_finish, NULL); + + /* notify anybody waiting on us that we are done starting up */ + frr_pthread_notify_running(fpt); + + while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) { + if (peerhash->count > 0) + pthread_cond_timedwait(peerhash_cond, peerhash_mtx, + &next_update_ts); + else + while (peerhash->count == 0 + && atomic_load_explicit(&fpt->running, + memory_order_relaxed)) + pthread_cond_wait(peerhash_cond, peerhash_mtx); + + monotime(&currtime); + + next_update.tv_sec = -1; + + hash_iterate(peerhash, peer_process, &next_update); + if (next_update.tv_sec == -1) + memset(&next_update, 0, sizeof(next_update)); + + monotime_since(&currtime, &aftertime); + + timeradd(&currtime, &next_update, &next_update); + TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts); + } + + /* clean up */ + pthread_cleanup_pop(1); + + return NULL; +} + +/* --- thread external functions ------------------------------------------- */ + +void bgp_keepalives_on(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) + return; + + struct frr_pthread *fpt = bgp_pth_ka; + assert(fpt->running); + + /* placeholder bucket data to use for fast key lookups */ + static struct pkat holder = {0}; + + /* + * We need to ensure that bgp_keepalives_init was called first + */ + assert(peerhash_mtx); + + frr_with_mutex (peerhash_mtx) { + holder.peer = peer; + if (!hash_lookup(peerhash, &holder)) { + struct pkat *pkat = pkat_new(peer); + (void)hash_get(peerhash, pkat, hash_alloc_intern); + peer_lock(peer); + } + SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); + /* Force the keepalive thread to wake up */ + pthread_cond_signal(peerhash_cond); + } +} + +void bgp_keepalives_off(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) + return; + + struct frr_pthread *fpt = bgp_pth_ka; + assert(fpt->running); + + /* placeholder bucket data to use for fast key lookups */ + static struct pkat holder = {0}; + + /* + * We need to ensure that bgp_keepalives_init was called first + */ + assert(peerhash_mtx); + + frr_with_mutex (peerhash_mtx) { + holder.peer = peer; + struct pkat *res = hash_release(peerhash, &holder); + if (res) { + pkat_del(res); + peer_unlock(peer); + } + UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); + } +} + +int bgp_keepalives_stop(struct frr_pthread *fpt, void **result) +{ + assert(fpt->running); + + frr_with_mutex (peerhash_mtx) { + atomic_store_explicit(&fpt->running, false, + memory_order_relaxed); + pthread_cond_signal(peerhash_cond); + } + + pthread_join(fpt->thread, result); + return 0; +} |