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