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