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 6 : DEFINE_MTYPE_STATIC(BGPD, BGP_PKAT, "Peer KeepAlive Timer");
40 6 : DEFINE_MTYPE_STATIC(BGPD, BGP_COND, "BGP Peer pthread Conditional");
41 6 : 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 2 : static struct pkat *pkat_new(struct peer *peer)
60 : {
61 2 : struct pkat *pkat = XMALLOC(MTYPE_BGP_PKAT, sizeof(struct pkat));
62 2 : pkat->peer = peer;
63 2 : monotime(&pkat->last);
64 2 : return pkat;
65 : }
66 :
67 2 : 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 2 : static void peer_process(struct hash_bucket *hb, void *arg)
93 : {
94 2 : struct pkat *pkat = hb->data;
95 :
96 2 : struct timeval *next_update = arg;
97 :
98 2 : static struct timeval elapsed; // elapsed time since keepalive
99 2 : static struct timeval ka = {0}; // peer->v_keepalive as a timeval
100 2 : static struct timeval diff; // ka - elapsed
101 :
102 2 : static const struct timeval tolerance = {0, 100000};
103 :
104 2 : uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive,
105 : memory_order_relaxed);
106 :
107 : /* 0 keepalive timer means no keepalives */
108 2 : if (v_ka == 0)
109 : return;
110 :
111 : /* calculate elapsed time since last keepalive */
112 2 : monotime_since(&pkat->last, &elapsed);
113 :
114 : /* calculate difference between elapsed time and configured time */
115 2 : ka.tv_sec = v_ka;
116 2 : timersub(&ka, &elapsed, &diff);
117 :
118 2 : int send_keepalive =
119 2 : elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <);
120 :
121 : if (send_keepalive) {
122 0 : if (bgp_debug_keepalive(pkat->peer))
123 0 : zlog_debug("%s [FSM] Timer (keepalive timer expire)",
124 : pkat->peer->host);
125 :
126 0 : bgp_keepalive_send(pkat->peer);
127 0 : monotime(&pkat->last);
128 0 : memset(&elapsed, 0, sizeof(elapsed));
129 0 : diff = ka;
130 : }
131 :
132 : /* if calculated next update for this peer < current delay, use it */
133 2 : if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <))
134 2 : *next_update = diff;
135 : }
136 :
137 2 : static bool peer_hash_cmp(const void *f, const void *s)
138 : {
139 2 : const struct pkat *p1 = f;
140 2 : const struct pkat *p2 = s;
141 :
142 2 : return p1->peer == p2->peer;
143 : }
144 :
145 4 : static unsigned int peer_hash_key(const void *arg)
146 : {
147 4 : const struct pkat *pkat = arg;
148 4 : return (uintptr_t)pkat->peer;
149 : }
150 :
151 : /* Cleanup handler / deinitializer. */
152 2 : static void bgp_keepalives_finish(void *arg)
153 : {
154 2 : if (peerhash) {
155 2 : hash_clean(peerhash, pkat_del);
156 2 : hash_free(peerhash);
157 : }
158 :
159 2 : peerhash = NULL;
160 :
161 2 : pthread_mutex_unlock(peerhash_mtx);
162 2 : pthread_mutex_destroy(peerhash_mtx);
163 2 : pthread_cond_destroy(peerhash_cond);
164 :
165 2 : XFREE(MTYPE_BGP_MUTEX, peerhash_mtx);
166 2 : XFREE(MTYPE_BGP_COND, peerhash_cond);
167 2 : }
168 :
169 : /*
170 : * Entry function for peer keepalive generation pthread.
171 : */
172 2 : void *bgp_keepalives_start(void *arg)
173 : {
174 2 : struct frr_pthread *fpt = arg;
175 2 : fpt->master->owner = pthread_self();
176 :
177 2 : struct timeval currtime = {0, 0};
178 2 : struct timeval aftertime = {0, 0};
179 2 : struct timeval next_update = {0, 0};
180 2 : 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 2 : rcu_read_unlock();
190 :
191 2 : peerhash_mtx = XCALLOC(MTYPE_BGP_MUTEX, sizeof(pthread_mutex_t));
192 2 : peerhash_cond = XCALLOC(MTYPE_BGP_COND, sizeof(pthread_cond_t));
193 :
194 : /* initialize mutex */
195 2 : pthread_mutex_init(peerhash_mtx, NULL);
196 :
197 : /* use monotonic clock with condition variable */
198 2 : pthread_condattr_t attrs;
199 2 : pthread_condattr_init(&attrs);
200 2 : pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
201 2 : pthread_cond_init(peerhash_cond, &attrs);
202 2 : pthread_condattr_destroy(&attrs);
203 :
204 : /*
205 : * We are not using normal FRR pthread mechanics and are
206 : * not using fpt_run
207 : */
208 2 : frr_pthread_set_name(fpt);
209 :
210 : /* initialize peer hashtable */
211 2 : peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL);
212 2 : pthread_mutex_lock(peerhash_mtx);
213 :
214 : /* register cleanup handler */
215 2 : pthread_cleanup_push(&bgp_keepalives_finish, NULL);
216 :
217 : /* notify anybody waiting on us that we are done starting up */
218 2 : frr_pthread_notify_running(fpt);
219 :
220 6 : while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) {
221 4 : if (peerhash->count > 0)
222 2 : pthread_cond_timedwait(peerhash_cond, peerhash_mtx,
223 : &next_update_ts);
224 : else
225 4 : while (peerhash->count == 0
226 4 : && atomic_load_explicit(&fpt->running,
227 : memory_order_relaxed))
228 2 : pthread_cond_wait(peerhash_cond, peerhash_mtx);
229 :
230 4 : monotime(&currtime);
231 :
232 4 : next_update.tv_sec = -1;
233 :
234 4 : hash_iterate(peerhash, peer_process, &next_update);
235 4 : if (next_update.tv_sec == -1)
236 2 : memset(&next_update, 0, sizeof(next_update));
237 :
238 4 : monotime_since(&currtime, &aftertime);
239 :
240 4 : timeradd(&currtime, &next_update, &next_update);
241 6 : TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts);
242 : }
243 :
244 : /* clean up */
245 2 : pthread_cleanup_pop(1);
246 :
247 2 : return NULL;
248 : }
249 :
250 : /* --- thread external functions ------------------------------------------- */
251 :
252 5 : void bgp_keepalives_on(struct peer *peer)
253 : {
254 5 : if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
255 : return;
256 :
257 2 : struct frr_pthread *fpt = bgp_pth_ka;
258 2 : assert(fpt->running);
259 :
260 : /* placeholder bucket data to use for fast key lookups */
261 2 : static struct pkat holder = {0};
262 :
263 : /*
264 : * We need to ensure that bgp_keepalives_init was called first
265 : */
266 2 : assert(peerhash_mtx);
267 :
268 4 : frr_with_mutex (peerhash_mtx) {
269 2 : holder.peer = peer;
270 2 : if (!hash_lookup(peerhash, &holder)) {
271 2 : struct pkat *pkat = pkat_new(peer);
272 2 : (void)hash_get(peerhash, pkat, hash_alloc_intern);
273 2 : peer_lock(peer);
274 : }
275 2 : SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
276 : /* Force the keepalive thread to wake up */
277 2 : pthread_cond_signal(peerhash_cond);
278 : }
279 : }
280 :
281 21 : void bgp_keepalives_off(struct peer *peer)
282 : {
283 21 : if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON))
284 : return;
285 :
286 2 : struct frr_pthread *fpt = bgp_pth_ka;
287 2 : assert(fpt->running);
288 :
289 : /* placeholder bucket data to use for fast key lookups */
290 2 : static struct pkat holder = {0};
291 :
292 : /*
293 : * We need to ensure that bgp_keepalives_init was called first
294 : */
295 2 : assert(peerhash_mtx);
296 :
297 2 : frr_with_mutex (peerhash_mtx) {
298 2 : holder.peer = peer;
299 2 : struct pkat *res = hash_release(peerhash, &holder);
300 2 : if (res) {
301 2 : pkat_del(res);
302 2 : peer_unlock(peer);
303 : }
304 2 : UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON);
305 : }
306 : }
307 :
308 2 : int bgp_keepalives_stop(struct frr_pthread *fpt, void **result)
309 : {
310 2 : assert(fpt->running);
311 :
312 4 : frr_with_mutex (peerhash_mtx) {
313 2 : atomic_store_explicit(&fpt->running, false,
314 : memory_order_relaxed);
315 2 : pthread_cond_signal(peerhash_cond);
316 : }
317 :
318 2 : pthread_join(fpt->thread, result);
319 2 : return 0;
320 : }
|