2023-02-08 13:17:09 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2017-06-09 21:22:34 +02:00
|
|
|
/* BGP Keepalives.
|
|
|
|
* Implements a producer thread to generate BGP keepalives for peers.
|
|
|
|
* Copyright (C) 2017 Cumulus Networks, Inc.
|
|
|
|
* Quentin Young
|
2017-01-06 00:13:16 +01:00
|
|
|
*/
|
2017-06-09 21:22:34 +02:00
|
|
|
|
2017-06-09 21:34:29 +02:00
|
|
|
/* clang-format off */
|
2017-01-06 00:13:16 +01:00
|
|
|
#include <zebra.h>
|
2017-06-09 21:34:29 +02:00
|
|
|
#include <pthread.h> // for pthread_mutex_lock, pthread_mutex_unlock
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2017-06-09 21:34:29 +02:00
|
|
|
#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
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2022-12-25 16:26:52 +01:00
|
|
|
#include "bgpd/bgpd.h" // for peer, PEER_EVENT_KEEPALIVES_ON, peer...
|
2017-06-09 21:34:29 +02:00
|
|
|
#include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events
|
|
|
|
#include "bgpd/bgp_packet.h" // for bgp_keepalive_send
|
2017-01-06 00:13:16 +01:00
|
|
|
#include "bgpd/bgp_keepalives.h"
|
2017-06-09 21:34:29 +02:00
|
|
|
/* clang-format on */
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2022-12-05 18:17:00 +01:00
|
|
|
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");
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/*
|
2017-01-06 00:13:16 +01:00
|
|
|
* Peer KeepAlive Timer.
|
|
|
|
* Associates a peer with the time of its last keepalive.
|
|
|
|
*/
|
|
|
|
struct pkat {
|
2018-01-24 17:07:27 +01:00
|
|
|
/* the peer to send keepalives to */
|
2025-03-07 02:49:44 +01:00
|
|
|
struct peer *peer;
|
2018-01-24 17:07:27 +01:00
|
|
|
/* absolute time of last keepalive sent */
|
2017-01-06 00:13:16 +01:00
|
|
|
struct timeval last;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* List of peers we are sending keepalives for, and associated mutex. */
|
2017-04-12 19:17:30 +02:00
|
|
|
static pthread_mutex_t *peerhash_mtx;
|
|
|
|
static pthread_cond_t *peerhash_cond;
|
|
|
|
static struct hash *peerhash;
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2025-03-07 02:49:44 +01:00
|
|
|
static struct pkat *pkat_new(struct peer *peer)
|
2017-01-06 00:13:16 +01:00
|
|
|
{
|
2022-12-05 18:17:00 +01:00
|
|
|
struct pkat *pkat = XMALLOC(MTYPE_BGP_PKAT, sizeof(struct pkat));
|
2025-03-07 02:49:44 +01:00
|
|
|
pkat->peer = peer;
|
2017-01-06 00:13:16 +01:00
|
|
|
monotime(&pkat->last);
|
|
|
|
return pkat;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pkat_del(void *pkat)
|
|
|
|
{
|
2022-12-05 18:17:00 +01:00
|
|
|
XFREE(MTYPE_BGP_PKAT, pkat);
|
2017-01-06 00:13:16 +01:00
|
|
|
}
|
|
|
|
|
2025-03-07 02:49:44 +01:00
|
|
|
|
2017-01-06 00:13:16 +01:00
|
|
|
/*
|
2017-05-02 02:37:45 +02:00
|
|
|
* Callback for hash_iterate. Determines if a peer needs a keepalive and if so,
|
|
|
|
* generates and sends it.
|
2017-01-06 00:13:16 +01:00
|
|
|
*
|
|
|
|
* 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)
|
|
|
|
*/
|
2019-02-19 16:46:52 +01:00
|
|
|
static void peer_process(struct hash_bucket *hb, void *arg)
|
2017-01-06 00:13:16 +01:00
|
|
|
{
|
2017-04-12 19:17:30 +02:00
|
|
|
struct pkat *pkat = hb->data;
|
2025-03-07 02:49:44 +01:00
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
struct timeval *next_update = arg;
|
2017-01-06 00:13:16 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-11-20 17:26:59 +01:00
|
|
|
static const struct timeval tolerance = {0, 100000};
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2025-03-07 02:49:44 +01:00
|
|
|
uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive,
|
|
|
|
memory_order_relaxed);
|
2019-09-16 17:33:49 +02:00
|
|
|
|
|
|
|
/* 0 keepalive timer means no keepalives */
|
|
|
|
if (v_ka == 0)
|
|
|
|
return;
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* calculate elapsed time since last keepalive */
|
2017-04-12 19:17:30 +02:00
|
|
|
monotime_since(&pkat->last, &elapsed);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* calculate difference between elapsed time and configured time */
|
2019-09-16 17:33:49 +02:00
|
|
|
ka.tv_sec = v_ka;
|
2017-04-12 19:17:30 +02:00
|
|
|
timersub(&ka, &elapsed, &diff);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
int send_keepalive =
|
|
|
|
elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
if (send_keepalive) {
|
2025-03-07 02:49:44 +01:00
|
|
|
if (bgp_debug_keepalive(pkat->peer))
|
|
|
|
zlog_debug("%s [FSM] Timer (keepalive timer expire)",
|
|
|
|
pkat->peer->host);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2025-03-07 02:49:44 +01:00
|
|
|
bgp_keepalive_send(pkat->peer->connection);
|
2017-04-12 19:17:30 +02:00
|
|
|
monotime(&pkat->last);
|
2022-05-11 12:16:44 +02:00
|
|
|
memset(&elapsed, 0, sizeof(elapsed));
|
2018-01-24 17:07:27 +01:00
|
|
|
diff = ka;
|
2017-01-06 00:13:16 +01:00
|
|
|
}
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* if calculated next update for this peer < current delay, use it */
|
2018-02-21 18:01:34 +01:00
|
|
|
if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <))
|
2017-04-12 19:17:30 +02:00
|
|
|
*next_update = diff;
|
|
|
|
}
|
|
|
|
|
2018-10-17 21:27:12 +02:00
|
|
|
static bool peer_hash_cmp(const void *f, const void *s)
|
2017-04-12 19:17:30 +02:00
|
|
|
{
|
|
|
|
const struct pkat *p1 = f;
|
|
|
|
const struct pkat *p2 = s;
|
2018-10-17 21:27:12 +02:00
|
|
|
|
2025-03-07 02:49:44 +01:00
|
|
|
return p1->peer == p2->peer;
|
2017-04-12 19:17:30 +02:00
|
|
|
}
|
|
|
|
|
2019-05-14 22:19:07 +02:00
|
|
|
static unsigned int peer_hash_key(const void *arg)
|
2017-04-12 19:17:30 +02:00
|
|
|
{
|
2019-05-14 22:19:07 +02:00
|
|
|
const struct pkat *pkat = arg;
|
2025-03-07 02:49:44 +01:00
|
|
|
return (uintptr_t)pkat->peer;
|
2017-01-06 00:13:16 +01:00
|
|
|
}
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* Cleanup handler / deinitializer. */
|
2017-05-12 05:54:18 +02:00
|
|
|
static void bgp_keepalives_finish(void *arg)
|
2017-03-29 21:16:28 +02:00
|
|
|
{
|
2023-03-21 13:54:21 +01:00
|
|
|
hash_clean_and_free(&peerhash, pkat_del);
|
2017-03-29 21:16:28 +02:00
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
pthread_mutex_unlock(peerhash_mtx);
|
|
|
|
pthread_mutex_destroy(peerhash_mtx);
|
|
|
|
pthread_cond_destroy(peerhash_cond);
|
2017-03-29 21:16:28 +02:00
|
|
|
|
2022-12-05 18:17:00 +01:00
|
|
|
XFREE(MTYPE_BGP_MUTEX, peerhash_mtx);
|
|
|
|
XFREE(MTYPE_BGP_COND, peerhash_cond);
|
2017-03-29 21:16:28 +02:00
|
|
|
}
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/*
|
2017-03-29 21:16:28 +02:00
|
|
|
* Entry function for peer keepalive generation pthread.
|
|
|
|
*/
|
2017-05-12 05:54:18 +02:00
|
|
|
void *bgp_keepalives_start(void *arg)
|
2017-03-29 21:16:28 +02:00
|
|
|
{
|
2018-01-24 17:07:27 +01:00
|
|
|
struct frr_pthread *fpt = arg;
|
|
|
|
fpt->master->owner = pthread_self();
|
|
|
|
|
2017-03-29 21:16:28 +02:00
|
|
|
struct timeval currtime = {0, 0};
|
2017-04-12 19:17:30 +02:00
|
|
|
struct timeval aftertime = {0, 0};
|
2017-03-29 21:16:28 +02:00
|
|
|
struct timeval next_update = {0, 0};
|
|
|
|
struct timespec next_update_ts = {0, 0};
|
|
|
|
|
2022-09-06 14:34:10 +02:00
|
|
|
/*
|
|
|
|
* The RCU mechanism for each pthread is initialized in a "locked"
|
|
|
|
* state. That's ok for pthreads using the frr_pthread,
|
2022-12-11 13:51:16 +01:00
|
|
|
* event_fetch event loop, because that event loop unlocks regularly.
|
2022-09-06 14:34:10 +02:00
|
|
|
* For foreign pthreads, the lock needs to be unlocked so that the
|
|
|
|
* background rcu pthread can run.
|
|
|
|
*/
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
2022-12-05 18:17:00 +01:00
|
|
|
peerhash_mtx = XCALLOC(MTYPE_BGP_MUTEX, sizeof(pthread_mutex_t));
|
|
|
|
peerhash_cond = XCALLOC(MTYPE_BGP_COND, sizeof(pthread_cond_t));
|
2018-01-24 17:07:27 +01:00
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
2019-01-09 20:59:22 +01:00
|
|
|
/*
|
|
|
|
* We are not using normal FRR pthread mechanics and are
|
|
|
|
* not using fpt_run
|
|
|
|
*/
|
|
|
|
frr_pthread_set_name(fpt);
|
2018-07-26 23:20:54 +02:00
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* initialize peer hashtable */
|
|
|
|
peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL);
|
2017-04-12 19:17:30 +02:00
|
|
|
pthread_mutex_lock(peerhash_mtx);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* register cleanup handler */
|
2017-05-12 05:54:18 +02:00
|
|
|
pthread_cleanup_push(&bgp_keepalives_finish, NULL);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* notify anybody waiting on us that we are done starting up */
|
|
|
|
frr_pthread_notify_running(fpt);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) {
|
2017-04-12 19:17:30 +02:00
|
|
|
if (peerhash->count > 0)
|
|
|
|
pthread_cond_timedwait(peerhash_cond, peerhash_mtx,
|
2017-01-06 00:13:16 +01:00
|
|
|
&next_update_ts);
|
|
|
|
else
|
2017-04-12 19:17:30 +02:00
|
|
|
while (peerhash->count == 0
|
2018-01-24 17:07:27 +01:00
|
|
|
&& atomic_load_explicit(&fpt->running,
|
|
|
|
memory_order_relaxed))
|
2017-04-12 19:17:30 +02:00
|
|
|
pthread_cond_wait(peerhash_cond, peerhash_mtx);
|
2017-01-06 00:13:16 +01:00
|
|
|
|
|
|
|
monotime(&currtime);
|
2017-04-12 19:17:30 +02:00
|
|
|
|
|
|
|
next_update.tv_sec = -1;
|
|
|
|
|
|
|
|
hash_iterate(peerhash, peer_process, &next_update);
|
|
|
|
if (next_update.tv_sec == -1)
|
2022-05-11 12:16:44 +02:00
|
|
|
memset(&next_update, 0, sizeof(next_update));
|
2017-04-12 19:17:30 +02:00
|
|
|
|
|
|
|
monotime_since(&currtime, &aftertime);
|
|
|
|
|
2017-01-06 00:13:16 +01:00
|
|
|
timeradd(&currtime, &next_update, &next_update);
|
|
|
|
TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts);
|
|
|
|
}
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
/* clean up */
|
2017-01-06 00:13:16 +01:00
|
|
|
pthread_cleanup_pop(1);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --- thread external functions ------------------------------------------- */
|
|
|
|
|
2023-08-27 03:34:59 +02:00
|
|
|
void bgp_keepalives_on(struct peer_connection *connection)
|
2017-01-06 00:13:16 +01:00
|
|
|
{
|
2023-08-27 03:34:59 +02:00
|
|
|
struct peer *peer = connection->peer;
|
|
|
|
|
2018-01-24 23:47:17 +01:00
|
|
|
if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
|
|
|
|
return;
|
|
|
|
|
2018-09-12 21:23:52 +02:00
|
|
|
struct frr_pthread *fpt = bgp_pth_ka;
|
2018-01-24 17:07:27 +01:00
|
|
|
assert(fpt->running);
|
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
/* placeholder bucket data to use for fast key lookups */
|
|
|
|
static struct pkat holder = {0};
|
|
|
|
|
2018-08-16 02:44:31 +02:00
|
|
|
/*
|
|
|
|
* We need to ensure that bgp_keepalives_init was called first
|
|
|
|
*/
|
|
|
|
assert(peerhash_mtx);
|
2017-11-13 23:59:04 +01:00
|
|
|
|
2022-07-20 21:47:42 +02:00
|
|
|
frr_with_mutex (peerhash_mtx) {
|
2025-03-07 02:49:44 +01:00
|
|
|
holder.peer = peer;
|
2017-04-12 19:17:30 +02:00
|
|
|
if (!hash_lookup(peerhash, &holder)) {
|
2025-03-07 02:49:44 +01:00
|
|
|
struct pkat *pkat = pkat_new(peer);
|
*: remove the checking returned value for hash_get()
Firstly, *keep no change* for `hash_get()` with NULL
`alloc_func`.
Only focus on cases with non-NULL `alloc_func` of
`hash_get()`.
Since `hash_get()` with non-NULL `alloc_func` parameter
shall not fail, just ignore the returned value of it.
The returned value must not be NULL.
So in this case, remove the unnecessary checking NULL
or not for the returned value and add `void` in front
of it.
Importantly, also *keep no change* for the two cases with
non-NULL `alloc_func` -
1) Use `assert(<returned_data> == <searching_data>)` to
ensure it is a created node, not a found node.
Refer to `isis_vertex_queue_insert()` of isisd, there
are many examples of this case in isid.
2) Use `<returned_data> != <searching_data>` to judge it
is a found node, then free <searching_data>.
Refer to `aspath_intern()` of bgpd, there are many
examples of this case in bgpd.
Here, <returned_data> is the returned value from `hash_get()`,
and <searching_data> is the data, which is to be put into
hash table.
Signed-off-by: anlan_cs <vic.lan@pica8.com>
2022-04-21 08:37:12 +02:00
|
|
|
(void)hash_get(peerhash, pkat, hash_alloc_intern);
|
2017-04-12 19:17:30 +02:00
|
|
|
peer_lock(peer);
|
|
|
|
}
|
2017-03-27 21:47:23 +02:00
|
|
|
SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
|
2023-01-16 05:40:54 +01:00
|
|
|
/* Force the keepalive thread to wake up */
|
|
|
|
pthread_cond_signal(peerhash_cond);
|
2017-01-06 00:13:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-27 03:34:59 +02:00
|
|
|
void bgp_keepalives_off(struct peer_connection *connection)
|
2017-01-06 00:13:16 +01:00
|
|
|
{
|
2023-08-27 03:34:59 +02:00
|
|
|
struct peer *peer = connection->peer;
|
|
|
|
|
2018-01-24 23:47:17 +01:00
|
|
|
if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
|
|
|
|
return;
|
|
|
|
|
2018-09-12 21:23:52 +02:00
|
|
|
struct frr_pthread *fpt = bgp_pth_ka;
|
2018-01-24 17:07:27 +01:00
|
|
|
assert(fpt->running);
|
|
|
|
|
2017-04-12 19:17:30 +02:00
|
|
|
/* placeholder bucket data to use for fast key lookups */
|
|
|
|
static struct pkat holder = {0};
|
2017-03-27 21:47:23 +02:00
|
|
|
|
2018-08-16 02:44:31 +02:00
|
|
|
/*
|
|
|
|
* We need to ensure that bgp_keepalives_init was called first
|
|
|
|
*/
|
|
|
|
assert(peerhash_mtx);
|
2017-11-13 23:59:04 +01:00
|
|
|
|
2022-07-20 21:47:42 +02:00
|
|
|
frr_with_mutex (peerhash_mtx) {
|
2025-03-07 02:49:44 +01:00
|
|
|
holder.peer = peer;
|
2017-04-12 19:17:30 +02:00
|
|
|
struct pkat *res = hash_release(peerhash, &holder);
|
|
|
|
if (res) {
|
|
|
|
pkat_del(res);
|
|
|
|
peer_unlock(peer);
|
|
|
|
}
|
2017-03-27 21:47:23 +02:00
|
|
|
UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
|
2017-01-06 00:13:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-24 17:07:27 +01:00
|
|
|
int bgp_keepalives_stop(struct frr_pthread *fpt, void **result)
|
2017-04-16 07:18:07 +02:00
|
|
|
{
|
2018-01-24 17:07:27 +01:00
|
|
|
assert(fpt->running);
|
|
|
|
|
2023-01-16 05:40:54 +01:00
|
|
|
frr_with_mutex (peerhash_mtx) {
|
|
|
|
atomic_store_explicit(&fpt->running, false,
|
|
|
|
memory_order_relaxed);
|
|
|
|
pthread_cond_signal(peerhash_cond);
|
|
|
|
}
|
2018-01-24 17:07:27 +01:00
|
|
|
|
2017-04-16 07:18:07 +02:00
|
|
|
pthread_join(fpt->thread, result);
|
|
|
|
return 0;
|
|
|
|
}
|