back to topotato report
topotato coverage report
Current view: top level - bgpd - bgp_keepalives.c (source / functions) Hit Total Coverage
Test: aggregated run ( view descriptions ) Lines: 119 122 97.5 %
Date: 2023-02-24 14:41:08 Functions: 15 16 93.8 %

          Line data    Source code
       1             : /* BGP Keepalives.
       2             :  * Implements a producer thread to generate BGP keepalives for peers.
       3             :  * Copyright (C) 2017 Cumulus Networks, Inc.
       4             :  * Quentin Young
       5             :  *
       6             :  * This file is part of FRRouting.
       7             :  *
       8             :  * FRRouting is free software; you can redistribute it and/or modify it under
       9             :  * the terms of the GNU General Public License as published by the Free
      10             :  * Software Foundation; either version 2, or (at your option) any later
      11             :  * version.
      12             :  *
      13             :  * FRRouting is distributed in the hope that it will be useful, but WITHOUT ANY
      14             :  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      15             :  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
      16             :  * details.
      17             :  *
      18             :  * You should have received a copy of the GNU General Public License along
      19             :  * with this program; see the file COPYING; if not, write to the Free Software
      20             :  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
      21             :  */
      22             : 
      23             : /* clang-format off */
      24             : #include <zebra.h>
      25             : #include <pthread.h>              // for pthread_mutex_lock, pthread_mutex_unlock
      26             : 
      27             : #include "frr_pthread.h"        // for frr_pthread
      28             : #include "hash.h"             // for hash, hash_clean, hash_create_size...
      29             : #include "log.h"              // for zlog_debug
      30             : #include "memory.h"           // for MTYPE_TMP, XFREE, XCALLOC, XMALLOC
      31             : #include "monotime.h"         // for monotime, monotime_since
      32             : 
      33             : #include "bgpd/bgpd.h"          // for peer, PEER_THREAD_KEEPALIVES_ON, peer...
      34             : #include "bgpd/bgp_debug.h"   // for bgp_debug_neighbor_events
      35             : #include "bgpd/bgp_packet.h"  // for bgp_keepalive_send
      36             : #include "bgpd/bgp_keepalives.h"
      37             : /* clang-format on */
      38             : 
      39         102 : DEFINE_MTYPE_STATIC(BGPD, BGP_PKAT, "Peer KeepAlive Timer");
      40         102 : DEFINE_MTYPE_STATIC(BGPD, BGP_COND, "BGP Peer pthread Conditional");
      41         102 : DEFINE_MTYPE_STATIC(BGPD, BGP_MUTEX, "BGP Peer pthread Mutex");
      42             : 
      43             : /*
      44             :  * Peer KeepAlive Timer.
      45             :  * Associates a peer with the time of its last keepalive.
      46             :  */
      47             : struct pkat {
      48             :         /* the peer to send keepalives to */
      49             :         struct peer *peer;
      50             :         /* absolute time of last keepalive sent */
      51             :         struct timeval last;
      52             : };
      53             : 
      54             : /* List of peers we are sending keepalives for, and associated mutex. */
      55             : static pthread_mutex_t *peerhash_mtx;
      56             : static pthread_cond_t *peerhash_cond;
      57             : static struct hash *peerhash;
      58             : 
      59          50 : static struct pkat *pkat_new(struct peer *peer)
      60             : {
      61          50 :         struct pkat *pkat = XMALLOC(MTYPE_BGP_PKAT, sizeof(struct pkat));
      62          50 :         pkat->peer = peer;
      63          50 :         monotime(&pkat->last);
      64          50 :         return pkat;
      65             : }
      66             : 
      67          50 : static void pkat_del(void *pkat)
      68             : {
      69           0 :         XFREE(MTYPE_BGP_PKAT, pkat);
      70           0 : }
      71             : 
      72             : 
      73             : /*
      74             :  * Callback for hash_iterate. Determines if a peer needs a keepalive and if so,
      75             :  * generates and sends it.
      76             :  *
      77             :  * For any given peer, if the elapsed time since its last keepalive exceeds its
      78             :  * configured keepalive timer, a keepalive is sent to the peer and its
      79             :  * last-sent time is reset. Additionally, If the elapsed time does not exceed
      80             :  * the configured keepalive timer, but the time until the next keepalive is due
      81             :  * is within a hardcoded tolerance, a keepalive is sent as if the configured
      82             :  * timer was exceeded. Doing this helps alleviate nanosecond sleeps between
      83             :  * ticks by grouping together peers who are due for keepalives at roughly the
      84             :  * same time. This tolerance value is arbitrarily chosen to be 100ms.
      85             :  *
      86             :  * In addition, this function calculates the maximum amount of time that the
      87             :  * keepalive thread can sleep before another tick needs to take place. This is
      88             :  * equivalent to shortest time until a keepalive is due for any one peer.
      89             :  *
      90             :  * @return maximum time to wait until next update (0 if infinity)
      91             :  */
      92          70 : static void peer_process(struct hash_bucket *hb, void *arg)
      93             : {
      94          70 :         struct pkat *pkat = hb->data;
      95             : 
      96          70 :         struct timeval *next_update = arg;
      97             : 
      98          70 :         static struct timeval elapsed;  // elapsed time since keepalive
      99          70 :         static struct timeval ka = {0}; // peer->v_keepalive as a timeval
     100          70 :         static struct timeval diff;     // ka - elapsed
     101             : 
     102          70 :         static const struct timeval tolerance = {0, 100000};
     103             : 
     104          70 :         uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive,
     105             :                                              memory_order_relaxed);
     106             : 
     107             :         /* 0 keepalive timer means no keepalives */
     108          70 :         if (v_ka == 0)
     109             :                 return;
     110             : 
     111             :         /* calculate elapsed time since last keepalive */
     112          70 :         monotime_since(&pkat->last, &elapsed);
     113             : 
     114             :         /* calculate difference between elapsed time and configured time */
     115          70 :         ka.tv_sec = v_ka;
     116          70 :         timersub(&ka, &elapsed, &diff);
     117             : 
     118          70 :         int send_keepalive =
     119          70 :                 elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <);
     120             : 
     121             :         if (send_keepalive) {
     122          14 :                 if (bgp_debug_keepalive(pkat->peer))
     123           0 :                         zlog_debug("%s [FSM] Timer (keepalive timer expire)",
     124             :                                    pkat->peer->host);
     125             : 
     126          14 :                 bgp_keepalive_send(pkat->peer);
     127          14 :                 monotime(&pkat->last);
     128          14 :                 memset(&elapsed, 0, sizeof(elapsed));
     129          14 :                 diff = ka;
     130             :         }
     131             : 
     132             :         /* if calculated next update for this peer < current delay, use it */
     133          70 :         if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <))
     134          69 :                 *next_update = diff;
     135             : }
     136             : 
     137          50 : static bool peer_hash_cmp(const void *f, const void *s)
     138             : {
     139          50 :         const struct pkat *p1 = f;
     140          50 :         const struct pkat *p2 = s;
     141             : 
     142          50 :         return p1->peer == p2->peer;
     143             : }
     144             : 
     145         104 : static unsigned int peer_hash_key(const void *arg)
     146             : {
     147         104 :         const struct pkat *pkat = arg;
     148         104 :         return (uintptr_t)pkat->peer;
     149             : }
     150             : 
     151             : /* Cleanup handler / deinitializer. */
     152          34 : static void bgp_keepalives_finish(void *arg)
     153             : {
     154          34 :         if (peerhash) {
     155          34 :                 hash_clean(peerhash, pkat_del);
     156          34 :                 hash_free(peerhash);
     157             :         }
     158             : 
     159          34 :         peerhash = NULL;
     160             : 
     161          34 :         pthread_mutex_unlock(peerhash_mtx);
     162          34 :         pthread_mutex_destroy(peerhash_mtx);
     163          34 :         pthread_cond_destroy(peerhash_cond);
     164             : 
     165          34 :         XFREE(MTYPE_BGP_MUTEX, peerhash_mtx);
     166          34 :         XFREE(MTYPE_BGP_COND, peerhash_cond);
     167          34 : }
     168             : 
     169             : /*
     170             :  * Entry function for peer keepalive generation pthread.
     171             :  */
     172          34 : void *bgp_keepalives_start(void *arg)
     173             : {
     174          34 :         struct frr_pthread *fpt = arg;
     175          34 :         fpt->master->owner = pthread_self();
     176             : 
     177          34 :         struct timeval currtime = {0, 0};
     178          34 :         struct timeval aftertime = {0, 0};
     179          34 :         struct timeval next_update = {0, 0};
     180          34 :         struct timespec next_update_ts = {0, 0};
     181             : 
     182             :         /*
     183             :          * The RCU mechanism for each pthread is initialized in a "locked"
     184             :          * state. That's ok for pthreads using the frr_pthread,
     185             :          * thread_fetch event loop, because that event loop unlocks regularly.
     186             :          * For foreign pthreads, the lock needs to be unlocked so that the
     187             :          * background rcu pthread can run.
     188             :          */
     189          34 :         rcu_read_unlock();
     190             : 
     191          34 :         peerhash_mtx = XCALLOC(MTYPE_BGP_MUTEX, sizeof(pthread_mutex_t));
     192          34 :         peerhash_cond = XCALLOC(MTYPE_BGP_COND, sizeof(pthread_cond_t));
     193             : 
     194             :         /* initialize mutex */
     195          34 :         pthread_mutex_init(peerhash_mtx, NULL);
     196             : 
     197             :         /* use monotonic clock with condition variable */
     198          34 :         pthread_condattr_t attrs;
     199          34 :         pthread_condattr_init(&attrs);
     200          34 :         pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
     201          34 :         pthread_cond_init(peerhash_cond, &attrs);
     202          34 :         pthread_condattr_destroy(&attrs);
     203             : 
     204             :         /*
     205             :          * We are not using normal FRR pthread mechanics and are
     206             :          * not using fpt_run
     207             :          */
     208          34 :         frr_pthread_set_name(fpt);
     209             : 
     210             :         /* initialize peer hashtable */
     211          34 :         peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL);
     212          34 :         pthread_mutex_lock(peerhash_mtx);
     213             : 
     214             :         /* register cleanup handler */
     215          34 :         pthread_cleanup_push(&bgp_keepalives_finish, NULL);
     216             : 
     217             :         /* notify anybody waiting on us that we are done starting up */
     218          34 :         frr_pthread_notify_running(fpt);
     219             : 
     220         132 :         while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) {
     221          98 :                 if (peerhash->count > 0)
     222          64 :                         pthread_cond_timedwait(peerhash_cond, peerhash_mtx,
     223             :                                                &next_update_ts);
     224             :                 else
     225          68 :                         while (peerhash->count == 0
     226          68 :                                && atomic_load_explicit(&fpt->running,
     227             :                                                        memory_order_relaxed))
     228          34 :                                 pthread_cond_wait(peerhash_cond, peerhash_mtx);
     229             : 
     230          98 :                 monotime(&currtime);
     231             : 
     232          98 :                 next_update.tv_sec = -1;
     233             : 
     234          98 :                 hash_iterate(peerhash, peer_process, &next_update);
     235          98 :                 if (next_update.tv_sec == -1)
     236          34 :                         memset(&next_update, 0, sizeof(next_update));
     237             : 
     238          98 :                 monotime_since(&currtime, &aftertime);
     239             : 
     240          98 :                 timeradd(&currtime, &next_update, &next_update);
     241         132 :                 TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts);
     242             :         }
     243             : 
     244             :         /* clean up */
     245          34 :         pthread_cleanup_pop(1);
     246             : 
     247          34 :         return NULL;
     248             : }
     249             : 
     250             : /* --- thread external functions ------------------------------------------- */
     251             : 
     252         185 : void bgp_keepalives_on(struct peer *peer)
     253             : {
     254         185 :         if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
     255             :                 return;
     256             : 
     257          50 :         struct frr_pthread *fpt = bgp_pth_ka;
     258          50 :         assert(fpt->running);
     259             : 
     260             :         /* placeholder bucket data to use for fast key lookups */
     261          50 :         static struct pkat holder = {0};
     262             : 
     263             :         /*
     264             :          * We need to ensure that bgp_keepalives_init was called first
     265             :          */
     266          50 :         assert(peerhash_mtx);
     267             : 
     268         100 :         frr_with_mutex (peerhash_mtx) {
     269          50 :                 holder.peer = peer;
     270          50 :                 if (!hash_lookup(peerhash, &holder)) {
     271          50 :                         struct pkat *pkat = pkat_new(peer);
     272          50 :                         (void)hash_get(peerhash, pkat, hash_alloc_intern);
     273          50 :                         peer_lock(peer);
     274             :                 }
     275          50 :                 SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
     276             :                 /* Force the keepalive thread to wake up */
     277          50 :                 pthread_cond_signal(peerhash_cond);
     278             :         }
     279             : }
     280             : 
     281         633 : void bgp_keepalives_off(struct peer *peer)
     282             : {
     283         633 :         if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
     284             :                 return;
     285             : 
     286          50 :         struct frr_pthread *fpt = bgp_pth_ka;
     287          50 :         assert(fpt->running);
     288             : 
     289             :         /* placeholder bucket data to use for fast key lookups */
     290          50 :         static struct pkat holder = {0};
     291             : 
     292             :         /*
     293             :          * We need to ensure that bgp_keepalives_init was called first
     294             :          */
     295          50 :         assert(peerhash_mtx);
     296             : 
     297          50 :         frr_with_mutex (peerhash_mtx) {
     298          50 :                 holder.peer = peer;
     299          50 :                 struct pkat *res = hash_release(peerhash, &holder);
     300          50 :                 if (res) {
     301          50 :                         pkat_del(res);
     302          50 :                         peer_unlock(peer);
     303             :                 }
     304          50 :                 UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
     305             :         }
     306             : }
     307             : 
     308          34 : int bgp_keepalives_stop(struct frr_pthread *fpt, void **result)
     309             : {
     310          34 :         assert(fpt->running);
     311             : 
     312          68 :         frr_with_mutex (peerhash_mtx) {
     313          34 :                 atomic_store_explicit(&fpt->running, false,
     314             :                                       memory_order_relaxed);
     315          34 :                 pthread_cond_signal(peerhash_cond);
     316             :         }
     317             : 
     318          34 :         pthread_join(fpt->thread, result);
     319          34 :         return 0;
     320             : }

Generated by: LCOV version v1.16-topotato