]>
Commit | Line | Data |
---|---|---|
6a8d3f1c OZ |
1 | /* |
2 | * BIRD -- Bidirectional Forwarding Detection (BFD) | |
3 | * | |
4 | * Can be freely distributed and used under the terms of the GNU GPL. | |
5 | */ | |
bf139664 | 6 | |
1ec52253 OZ |
7 | /** |
8 | * DOC: Bidirectional Forwarding Detection | |
9 | * | |
10 | * The BFD protocol is implemented in three files: |bfd.c| containing the | |
11 | * protocol logic and the protocol glue with BIRD core, |packets.c| handling BFD | |
12 | * packet processing, RX, TX and protocol sockets. |io.c| then contains generic | |
13 | * code for the event loop, threads and event sources (sockets, microsecond | |
14 | * timers). This generic code will be merged to the main BIRD I/O code in the | |
15 | * future. | |
16 | * | |
17 | * The BFD implementation uses a separate thread with an internal event loop for | |
18 | * handling the protocol logic, which requires high-res and low-latency timing, | |
19 | * so it is not affected by the rest of BIRD, which has several low-granularity | |
20 | * hooks in the main loop, uses second-based timers and cannot offer good | |
21 | * latency. The core of BFD protocol (the code related to BFD sessions, | |
22 | * interfaces and packets) runs in the BFD thread, while the rest (the code | |
23 | * related to BFD requests, BFD neighbors and the protocol glue) runs in the | |
24 | * main thread. | |
25 | * | |
26 | * BFD sessions are represented by structure &bfd_session that contains a state | |
27 | * related to the session and two timers (TX timer for periodic packets and hold | |
28 | * timer for session timeout). These sessions are allocated from @session_slab | |
29 | * and are accessible by two hash tables, @session_hash_id (by session ID) and | |
910adaa0 OZ |
30 | * @session_hash_ip (by IP addresses of neighbors and associated interfaces). |
31 | * Slab and both hashes are in the main protocol structure &bfd_proto. The | |
32 | * protocol logic related to BFD sessions is implemented in internal functions | |
33 | * bfd_session_*(), which are expected to be called from the context of BFD | |
34 | * thread, and external functions bfd_add_session(), bfd_remove_session() and | |
35 | * bfd_reconfigure_session(), which form an interface to the BFD core for the | |
36 | * rest and are expected to be called from the context of main thread. | |
1ec52253 OZ |
37 | * |
38 | * Each BFD session has an associated BFD interface, represented by structure | |
39 | * &bfd_iface. A BFD interface contains a socket used for TX (the one for RX is | |
40 | * shared in &bfd_proto), an interface configuration and reference counter. | |
41 | * Compared to interface structures of other protocols, these structures are not | |
42 | * created and removed based on interface notification events, but according to | |
43 | * the needs of BFD sessions. When a new session is created, it requests a | |
44 | * proper BFD interface by function bfd_get_iface(), which either finds an | |
45 | * existing one in &iface_list (from &bfd_proto) or allocates a new one. When a | |
ffa398b8 | 46 | * session is removed, an associated iface is discharged by bfd_free_iface(). |
1ec52253 OZ |
47 | * |
48 | * BFD requests are the external API for the other protocols. When a protocol | |
49 | * wants a BFD session, it calls bfd_request_session(), which creates a | |
50 | * structure &bfd_request containing approprite information and an notify hook. | |
51 | * This structure is a resource associated with the caller's resource pool. When | |
52 | * a BFD protocol is available, a BFD request is submitted to the protocol, an | |
53 | * appropriate BFD session is found or created and the request is attached to | |
54 | * the session. When a session changes state, all attached requests (and related | |
55 | * protocols) are notified. Note that BFD requests do not depend on BFD protocol | |
56 | * running. When the BFD protocol is stopped or removed (or not available from | |
57 | * beginning), related BFD requests are stored in @bfd_wait_list, where waits | |
58 | * for a new protocol. | |
59 | * | |
60 | * BFD neighbors are just a way to statically configure BFD sessions without | |
61 | * requests from other protocol. Structures &bfd_neighbor are part of BFD | |
62 | * configuration (like static routes in the static protocol). BFD neighbors are | |
63 | * handled by BFD protocol like it is a BFD client -- when a BFD neighbor is | |
64 | * ready, the protocol just creates a BFD request like any other protocol. | |
ffa398b8 | 65 | * |
1ec52253 | 66 | * The protocol uses a new generic event loop (structure &birdloop) from |io.c|, |
a6f79ca5 OZ |
67 | * which supports sockets, timers and events like the main loop. A birdloop is |
68 | * associated with a thread (field @thread) in which event hooks are executed. | |
69 | * Most functions for setting event sources (like sk_start() or tm_start()) must | |
70 | * be called from the context of that thread. Birdloop allows to temporarily | |
71 | * acquire the context of that thread for the main thread by calling | |
72 | * birdloop_enter() and then birdloop_leave(), which also ensures mutual | |
73 | * exclusion with all event hooks. Note that resources associated with a | |
74 | * birdloop (like timers) should be attached to the independent resource pool, | |
75 | * detached from the main resource tree. | |
1ec52253 OZ |
76 | * |
77 | * There are two kinds of interaction between the BFD core (running in the BFD | |
78 | * thread) and the rest of BFD (running in the main thread). The first kind are | |
79 | * configuration calls from main thread to the BFD thread (like bfd_add_session()). | |
80 | * These calls are synchronous and use birdloop_enter() mechanism for mutual | |
81 | * exclusion. The second kind is a notification about session changes from the | |
82 | * BFD thread to the main thread. This is done in an asynchronous way, sesions | |
83 | * with pending notifications are linked (in the BFD thread) to @notify_list in | |
84 | * &bfd_proto, and then bfd_notify_hook() in the main thread is activated using | |
85 | * bfd_notify_kick() and a pipe. The hook then processes scheduled sessions and | |
86 | * calls hooks from associated BFD requests. This @notify_list (and state fields | |
87 | * in structure &bfd_session) is protected by a spinlock in &bfd_proto and | |
88 | * functions bfd_lock_sessions() / bfd_unlock_sessions(). | |
89 | * | |
90 | * There are few data races (accessing @p->p.debug from TRACE() from the BFD | |
91 | * thread and accessing some some private fields of %bfd_session from | |
92 | * bfd_show_sessions() from the main thread, but these are harmless (i hope). | |
93 | * | |
94 | * TODO: document functions and access restrictions for fields in BFD structures. | |
95 | * | |
96 | * Supported standards: | |
97 | * - RFC 5880 - main BFD standard | |
98 | * - RFC 5881 - BFD for IP links | |
99 | * - RFC 5882 - generic application of BFD | |
100 | * - RFC 5883 - BFD for multihop paths | |
101 | */ | |
102 | ||
bf139664 OZ |
103 | #include "bfd.h" |
104 | ||
105 | ||
6a8d3f1c OZ |
106 | #define HASH_ID_KEY(n) n->loc_id |
107 | #define HASH_ID_NEXT(n) n->next_id | |
e7d2ac44 OZ |
108 | #define HASH_ID_EQ(a,b) a == b |
109 | #define HASH_ID_FN(k) k | |
bf139664 | 110 | |
910adaa0 | 111 | #define HASH_IP_KEY(n) n->addr, n->ifindex |
6a8d3f1c | 112 | #define HASH_IP_NEXT(n) n->next_ip |
910adaa0 OZ |
113 | #define HASH_IP_EQ(a1,n1,a2,n2) ipa_equal(a1, a2) && n1 == n2 |
114 | #define HASH_IP_FN(a,n) ipa_hash(a) ^ u32_hash(n) | |
bf139664 | 115 | |
4a23ede2 MM |
116 | static list STATIC_LIST_INIT(bfd_proto_list); |
117 | static list STATIC_LIST_INIT(bfd_wait_list); | |
0e175f9f OZ |
118 | |
119 | const char *bfd_state_names[] = { "AdminDown", "Down", "Init", "Up" }; | |
37bf2078 | 120 | const char *bfd_diag_names[] = { "Nothing", "Timeout", "Echo failed", "Neighbor down", "Fwd reset", "Path down", "C path down", "Admin down", "RC path down" }; |
0e175f9f | 121 | |
1ec52253 OZ |
122 | static void bfd_session_set_min_tx(struct bfd_session *s, u32 val); |
123 | static struct bfd_iface *bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface); | |
124 | static void bfd_free_iface(struct bfd_iface *ifa); | |
6a8d3f1c | 125 | static inline void bfd_notify_kick(struct bfd_proto *p); |
bf139664 | 126 | |
1ec52253 OZ |
127 | |
128 | /* | |
129 | * BFD sessions | |
130 | */ | |
131 | ||
9d3fc306 OZ |
132 | static inline struct bfd_session_config |
133 | bfd_merge_options(const struct bfd_iface_config *cf, const struct bfd_options *opts) | |
134 | { | |
135 | return (struct bfd_session_config) { | |
136 | .min_rx_int = opts->min_rx_int ?: cf->min_rx_int, | |
137 | .min_tx_int = opts->min_tx_int ?: cf->min_tx_int, | |
138 | .idle_tx_int = opts->idle_tx_int ?: cf->idle_tx_int, | |
139 | .multiplier = opts->multiplier ?: cf->multiplier, | |
13c6cf8a | 140 | .passive = opts->passive_set ? opts->passive : cf->passive, |
9d3fc306 OZ |
141 | }; |
142 | } | |
143 | ||
ffa398b8 | 144 | static void |
6a8d3f1c | 145 | bfd_session_update_state(struct bfd_session *s, uint state, uint diag) |
bf139664 | 146 | { |
1ec52253 | 147 | struct bfd_proto *p = s->ifa->bfd; |
ffa398b8 | 148 | uint old_state = s->loc_state; |
6a8d3f1c | 149 | int notify; |
bf139664 | 150 | |
1ec52253 | 151 | if (state == old_state) |
6a8d3f1c | 152 | return; |
bf139664 | 153 | |
1ec52253 OZ |
154 | TRACE(D_EVENTS, "Session to %I changed state from %s to %s", |
155 | s->addr, bfd_state_names[old_state], bfd_state_names[state]); | |
156 | ||
6a8d3f1c OZ |
157 | bfd_lock_sessions(p); |
158 | s->loc_state = state; | |
159 | s->loc_diag = diag; | |
f047271c | 160 | s->last_state_change = current_time(); |
bf139664 | 161 | |
6a8d3f1c OZ |
162 | notify = !NODE_VALID(&s->n); |
163 | if (notify) | |
164 | add_tail(&p->notify_list, &s->n); | |
165 | bfd_unlock_sessions(p); | |
bf139664 | 166 | |
1ec52253 | 167 | if (state == BFD_STATE_UP) |
9d3fc306 | 168 | bfd_session_set_min_tx(s, s->cf.min_tx_int); |
bf139664 | 169 | |
1ec52253 | 170 | if (old_state == BFD_STATE_UP) |
9d3fc306 | 171 | bfd_session_set_min_tx(s, s->cf.idle_tx_int); |
bf139664 | 172 | |
1ec52253 OZ |
173 | if (notify) |
174 | bfd_notify_kick(p); | |
bf139664 OZ |
175 | } |
176 | ||
177 | static void | |
6a8d3f1c | 178 | bfd_session_update_tx_interval(struct bfd_session *s) |
bf139664 | 179 | { |
6a8d3f1c OZ |
180 | u32 tx_int = MAX(s->des_min_tx_int, s->rem_min_rx_int); |
181 | u32 tx_int_l = tx_int - (tx_int / 4); // 75 % | |
182 | u32 tx_int_h = tx_int - (tx_int / 10); // 90 % | |
bf139664 | 183 | |
6a8d3f1c OZ |
184 | s->tx_timer->recurrent = tx_int_l; |
185 | s->tx_timer->randomize = tx_int_h - tx_int_l; | |
bf139664 | 186 | |
6a8d3f1c OZ |
187 | /* Do not set timer if no previous event */ |
188 | if (!s->last_tx) | |
189 | return; | |
bf139664 | 190 | |
6a8d3f1c | 191 | /* Set timer relative to last tx_timer event */ |
a6f79ca5 | 192 | tm_set(s->tx_timer, s->last_tx + tx_int_l); |
bf139664 OZ |
193 | } |
194 | ||
195 | static void | |
6a8d3f1c | 196 | bfd_session_update_detection_time(struct bfd_session *s, int kick) |
bf139664 | 197 | { |
6a8d3f1c | 198 | btime timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult; |
bf139664 | 199 | |
6a8d3f1c OZ |
200 | if (kick) |
201 | s->last_rx = current_time(); | |
bf139664 | 202 | |
6a8d3f1c OZ |
203 | if (!s->last_rx) |
204 | return; | |
bf139664 | 205 | |
a6f79ca5 | 206 | tm_set(s->hold_timer, s->last_rx + timeout); |
bf139664 OZ |
207 | } |
208 | ||
209 | static void | |
1ec52253 | 210 | bfd_session_control_tx_timer(struct bfd_session *s, int reset) |
bf139664 | 211 | { |
1ec52253 | 212 | // if (!s->opened) goto stop; |
bf139664 OZ |
213 | |
214 | if (s->passive && (s->rem_id == 0)) | |
215 | goto stop; | |
216 | ||
ffa398b8 OZ |
217 | if (s->rem_demand_mode && |
218 | !s->poll_active && | |
bf139664 OZ |
219 | (s->loc_state == BFD_STATE_UP) && |
220 | (s->rem_state == BFD_STATE_UP)) | |
221 | goto stop; | |
222 | ||
223 | if (s->rem_min_rx_int == 0) | |
224 | goto stop; | |
225 | ||
226 | /* So TX timer should run */ | |
a6f79ca5 | 227 | if (reset || !tm_active(s->tx_timer)) |
1ec52253 OZ |
228 | { |
229 | s->last_tx = 0; | |
a6f79ca5 | 230 | tm_start(s->tx_timer, 0); |
1ec52253 | 231 | } |
bf139664 | 232 | |
bf139664 OZ |
233 | return; |
234 | ||
13c6cf8a | 235 | stop: |
a6f79ca5 | 236 | tm_stop(s->tx_timer); |
bf139664 OZ |
237 | s->last_tx = 0; |
238 | } | |
239 | ||
240 | static void | |
bf139664 OZ |
241 | bfd_session_request_poll(struct bfd_session *s, u8 request) |
242 | { | |
1ec52253 OZ |
243 | /* Not sure about this, but doing poll in this case does not make sense */ |
244 | if (s->rem_id == 0) | |
245 | return; | |
246 | ||
bf139664 OZ |
247 | s->poll_scheduled |= request; |
248 | ||
249 | if (s->poll_active) | |
250 | return; | |
251 | ||
252 | s->poll_active = s->poll_scheduled; | |
253 | s->poll_scheduled = 0; | |
1ec52253 OZ |
254 | |
255 | bfd_session_control_tx_timer(s, 1); | |
bf139664 OZ |
256 | } |
257 | ||
6a8d3f1c | 258 | static void |
bf139664 OZ |
259 | bfd_session_terminate_poll(struct bfd_session *s) |
260 | { | |
261 | u8 poll_done = s->poll_active & ~s->poll_scheduled; | |
262 | ||
263 | if (poll_done & BFD_POLL_TX) | |
264 | s->des_min_tx_int = s->des_min_tx_new; | |
265 | ||
266 | if (poll_done & BFD_POLL_RX) | |
267 | s->req_min_rx_int = s->req_min_rx_new; | |
268 | ||
1ec52253 OZ |
269 | s->poll_active = s->poll_scheduled; |
270 | s->poll_scheduled = 0; | |
bf139664 OZ |
271 | |
272 | /* Timers are updated by caller - bfd_session_process_ctl() */ | |
bf139664 OZ |
273 | } |
274 | ||
275 | void | |
6a8d3f1c | 276 | bfd_session_process_ctl(struct bfd_session *s, u8 flags, u32 old_tx_int, u32 old_rx_int) |
bf139664 OZ |
277 | { |
278 | if (s->poll_active && (flags & BFD_FLAG_FINAL)) | |
279 | bfd_session_terminate_poll(s); | |
280 | ||
6a8d3f1c | 281 | if ((s->des_min_tx_int != old_tx_int) || (s->rem_min_rx_int != old_rx_int)) |
bf139664 OZ |
282 | bfd_session_update_tx_interval(s); |
283 | ||
284 | bfd_session_update_detection_time(s, 1); | |
285 | ||
286 | /* Update session state */ | |
287 | int next_state = 0; | |
288 | int diag = BFD_DIAG_NOTHING; | |
289 | ||
290 | switch (s->loc_state) | |
291 | { | |
292 | case BFD_STATE_ADMIN_DOWN: | |
293 | return; | |
294 | ||
295 | case BFD_STATE_DOWN: | |
296 | if (s->rem_state == BFD_STATE_DOWN) next_state = BFD_STATE_INIT; | |
297 | else if (s->rem_state == BFD_STATE_INIT) next_state = BFD_STATE_UP; | |
298 | break; | |
299 | ||
300 | case BFD_STATE_INIT: | |
301 | if (s->rem_state == BFD_STATE_ADMIN_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN; | |
302 | else if (s->rem_state >= BFD_STATE_INIT) next_state = BFD_STATE_UP; | |
303 | break; | |
304 | ||
305 | case BFD_STATE_UP: | |
306 | if (s->rem_state <= BFD_STATE_DOWN) next_state = BFD_STATE_DOWN, diag = BFD_DIAG_NEIGHBOR_DOWN; | |
307 | break; | |
308 | } | |
309 | ||
310 | if (next_state) | |
311 | bfd_session_update_state(s, next_state, diag); | |
312 | ||
1ec52253 | 313 | bfd_session_control_tx_timer(s, 0); |
bf139664 OZ |
314 | |
315 | if (flags & BFD_FLAG_POLL) | |
1ec52253 OZ |
316 | bfd_send_ctl(s->ifa->bfd, s, 1); |
317 | } | |
318 | ||
ffa398b8 | 319 | static void |
1ec52253 OZ |
320 | bfd_session_timeout(struct bfd_session *s) |
321 | { | |
322 | struct bfd_proto *p = s->ifa->bfd; | |
323 | ||
324 | TRACE(D_EVENTS, "Session to %I expired", s->addr); | |
325 | ||
326 | s->rem_state = BFD_STATE_DOWN; | |
327 | s->rem_id = 0; | |
328 | s->rem_min_tx_int = 0; | |
329 | s->rem_min_rx_int = 1; | |
330 | s->rem_demand_mode = 0; | |
331 | s->rem_detect_mult = 0; | |
e03dc6a9 | 332 | s->rx_csn_known = 0; |
1ec52253 OZ |
333 | |
334 | s->poll_active = 0; | |
335 | s->poll_scheduled = 0; | |
336 | ||
337 | bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_TIMEOUT); | |
338 | ||
339 | bfd_session_control_tx_timer(s, 1); | |
bf139664 OZ |
340 | } |
341 | ||
bf139664 OZ |
342 | static void |
343 | bfd_session_set_min_tx(struct bfd_session *s, u32 val) | |
344 | { | |
345 | /* Note that des_min_tx_int <= des_min_tx_new */ | |
346 | ||
347 | if (val == s->des_min_tx_new) | |
348 | return; | |
349 | ||
350 | s->des_min_tx_new = val; | |
351 | ||
352 | /* Postpone timer update if des_min_tx_int increases and the session is up */ | |
353 | if ((s->loc_state != BFD_STATE_UP) || (val < s->des_min_tx_int)) | |
354 | { | |
355 | s->des_min_tx_int = val; | |
356 | bfd_session_update_tx_interval(s); | |
357 | } | |
358 | ||
359 | bfd_session_request_poll(s, BFD_POLL_TX); | |
360 | } | |
361 | ||
362 | static void | |
363 | bfd_session_set_min_rx(struct bfd_session *s, u32 val) | |
364 | { | |
365 | /* Note that req_min_rx_int >= req_min_rx_new */ | |
366 | ||
367 | if (val == s->req_min_rx_new) | |
368 | return; | |
369 | ||
ffa398b8 | 370 | s->req_min_rx_new = val; |
bf139664 OZ |
371 | |
372 | /* Postpone timer update if req_min_rx_int decreases and the session is up */ | |
373 | if ((s->loc_state != BFD_STATE_UP) || (val > s->req_min_rx_int)) | |
374 | { | |
375 | s->req_min_rx_int = val; | |
376 | bfd_session_update_detection_time(s, 0); | |
377 | } | |
378 | ||
379 | bfd_session_request_poll(s, BFD_POLL_RX); | |
380 | } | |
381 | ||
6a8d3f1c OZ |
382 | struct bfd_session * |
383 | bfd_find_session_by_id(struct bfd_proto *p, u32 id) | |
384 | { | |
385 | return HASH_FIND(p->session_hash_id, HASH_ID, id); | |
386 | } | |
387 | ||
388 | struct bfd_session * | |
910adaa0 | 389 | bfd_find_session_by_addr(struct bfd_proto *p, ip_addr addr, uint ifindex) |
6a8d3f1c | 390 | { |
910adaa0 | 391 | return HASH_FIND(p->session_hash_ip, HASH_IP, addr, ifindex); |
6a8d3f1c OZ |
392 | } |
393 | ||
394 | static void | |
a6f79ca5 | 395 | bfd_tx_timer_hook(timer *t) |
6a8d3f1c OZ |
396 | { |
397 | struct bfd_session *s = t->data; | |
398 | ||
399 | s->last_tx = current_time(); | |
1ec52253 | 400 | bfd_send_ctl(s->ifa->bfd, s, 0); |
6a8d3f1c OZ |
401 | } |
402 | ||
403 | static void | |
a6f79ca5 | 404 | bfd_hold_timer_hook(timer *t) |
6a8d3f1c OZ |
405 | { |
406 | bfd_session_timeout(t->data); | |
407 | } | |
408 | ||
409 | static u32 | |
410 | bfd_get_free_id(struct bfd_proto *p) | |
411 | { | |
412 | u32 id; | |
413 | for (id = random_u32(); 1; id++) | |
414 | if (id && !bfd_find_session_by_id(p, id)) | |
415 | break; | |
416 | ||
417 | return id; | |
418 | } | |
419 | ||
420 | static struct bfd_session * | |
9d3fc306 | 421 | bfd_add_session(struct bfd_proto *p, ip_addr addr, ip_addr local, struct iface *iface, struct bfd_options *opts) |
6a8d3f1c OZ |
422 | { |
423 | birdloop_enter(p->loop); | |
424 | ||
1ec52253 OZ |
425 | struct bfd_iface *ifa = bfd_get_iface(p, local, iface); |
426 | ||
c9ae8165 | 427 | struct bfd_session *s = sl_allocz(p->session_slab); |
6a8d3f1c | 428 | s->addr = addr; |
1ec52253 | 429 | s->ifa = ifa; |
910adaa0 | 430 | s->ifindex = iface ? iface->index : 0; |
6a8d3f1c | 431 | s->loc_id = bfd_get_free_id(p); |
1ec52253 | 432 | |
6a8d3f1c | 433 | HASH_INSERT(p->session_hash_id, HASH_ID, s); |
6a8d3f1c | 434 | HASH_INSERT(p->session_hash_ip, HASH_IP, s); |
1ec52253 | 435 | |
9d3fc306 | 436 | s->cf = bfd_merge_options(ifa->cf, opts); |
6a8d3f1c OZ |
437 | |
438 | /* Initialization of state variables - see RFC 5880 6.8.1 */ | |
439 | s->loc_state = BFD_STATE_DOWN; | |
440 | s->rem_state = BFD_STATE_DOWN; | |
9d3fc306 OZ |
441 | s->des_min_tx_int = s->des_min_tx_new = s->cf.idle_tx_int; |
442 | s->req_min_rx_int = s->req_min_rx_new = s->cf.min_rx_int; | |
6a8d3f1c | 443 | s->rem_min_rx_int = 1; |
9d3fc306 OZ |
444 | s->detect_mult = s->cf.multiplier; |
445 | s->passive = s->cf.passive; | |
e03dc6a9 | 446 | s->tx_csn = random_u32(); |
6a8d3f1c | 447 | |
a6f79ca5 OZ |
448 | s->tx_timer = tm_new_init(p->tpool, bfd_tx_timer_hook, s, 0, 0); |
449 | s->hold_timer = tm_new_init(p->tpool, bfd_hold_timer_hook, s, 0, 0); | |
6a8d3f1c | 450 | bfd_session_update_tx_interval(s); |
1ec52253 OZ |
451 | bfd_session_control_tx_timer(s, 1); |
452 | ||
453 | init_list(&s->request_list); | |
f047271c | 454 | s->last_state_change = current_time(); |
1ec52253 OZ |
455 | |
456 | TRACE(D_EVENTS, "Session to %I added", s->addr); | |
6a8d3f1c OZ |
457 | |
458 | birdloop_leave(p->loop); | |
459 | ||
460 | return s; | |
461 | } | |
462 | ||
1ec52253 | 463 | /* |
6a8d3f1c OZ |
464 | static void |
465 | bfd_open_session(struct bfd_proto *p, struct bfd_session *s, ip_addr local, struct iface *ifa) | |
466 | { | |
467 | birdloop_enter(p->loop); | |
468 | ||
6a8d3f1c OZ |
469 | s->opened = 1; |
470 | ||
471 | bfd_session_control_tx_timer(s); | |
472 | ||
473 | birdloop_leave(p->loop); | |
474 | } | |
475 | ||
476 | static void | |
477 | bfd_close_session(struct bfd_proto *p, struct bfd_session *s) | |
478 | { | |
479 | birdloop_enter(p->loop); | |
480 | ||
6a8d3f1c OZ |
481 | s->opened = 0; |
482 | ||
483 | bfd_session_update_state(s, BFD_STATE_DOWN, BFD_DIAG_PATH_DOWN); | |
484 | bfd_session_control_tx_timer(s); | |
485 | ||
486 | birdloop_leave(p->loop); | |
487 | } | |
1ec52253 | 488 | */ |
6a8d3f1c OZ |
489 | |
490 | static void | |
491 | bfd_remove_session(struct bfd_proto *p, struct bfd_session *s) | |
492 | { | |
1ec52253 OZ |
493 | ip_addr ip = s->addr; |
494 | ||
864f52a5 OZ |
495 | /* Caller should ensure that request list is empty */ |
496 | ||
6a8d3f1c OZ |
497 | birdloop_enter(p->loop); |
498 | ||
864f52a5 OZ |
499 | /* Remove session from notify list if scheduled for notification */ |
500 | /* No need for bfd_lock_sessions(), we are already protected by birdloop_enter() */ | |
501 | if (NODE_VALID(&s->n)) | |
502 | rem_node(&s->n); | |
503 | ||
1ec52253 | 504 | bfd_free_iface(s->ifa); |
6a8d3f1c OZ |
505 | |
506 | rfree(s->tx_timer); | |
507 | rfree(s->hold_timer); | |
508 | ||
6a8d3f1c | 509 | HASH_REMOVE(p->session_hash_id, HASH_ID, s); |
6a8d3f1c | 510 | HASH_REMOVE(p->session_hash_ip, HASH_IP, s); |
6a8d3f1c | 511 | |
ebd807c0 | 512 | sl_free(s); |
6a8d3f1c | 513 | |
1ec52253 OZ |
514 | TRACE(D_EVENTS, "Session to %I removed", ip); |
515 | ||
6a8d3f1c OZ |
516 | birdloop_leave(p->loop); |
517 | } | |
518 | ||
519 | static void | |
1ec52253 | 520 | bfd_reconfigure_session(struct bfd_proto *p, struct bfd_session *s) |
6a8d3f1c | 521 | { |
9d3fc306 OZ |
522 | if (EMPTY_LIST(s->request_list)) |
523 | return; | |
524 | ||
6a8d3f1c OZ |
525 | birdloop_enter(p->loop); |
526 | ||
9d3fc306 OZ |
527 | struct bfd_request *req = SKIP_BACK(struct bfd_request, n, HEAD(s->request_list)); |
528 | s->cf = bfd_merge_options(s->ifa->cf, &req->opts); | |
6a8d3f1c | 529 | |
9d3fc306 | 530 | u32 tx = (s->loc_state == BFD_STATE_UP) ? s->cf.min_tx_int : s->cf.idle_tx_int; |
1ec52253 | 531 | bfd_session_set_min_tx(s, tx); |
9d3fc306 OZ |
532 | bfd_session_set_min_rx(s, s->cf.min_rx_int); |
533 | s->detect_mult = s->cf.multiplier; | |
534 | s->passive = s->cf.passive; | |
6a8d3f1c | 535 | |
1ec52253 | 536 | bfd_session_control_tx_timer(s, 0); |
6a8d3f1c OZ |
537 | |
538 | birdloop_leave(p->loop); | |
1ec52253 OZ |
539 | |
540 | TRACE(D_EVENTS, "Session to %I reconfigured", s->addr); | |
541 | } | |
542 | ||
543 | ||
544 | /* | |
545 | * BFD interfaces | |
546 | */ | |
547 | ||
548 | static struct bfd_iface_config bfd_default_iface = { | |
549 | .min_rx_int = BFD_DEFAULT_MIN_RX_INT, | |
550 | .min_tx_int = BFD_DEFAULT_MIN_TX_INT, | |
551 | .idle_tx_int = BFD_DEFAULT_IDLE_TX_INT, | |
13c6cf8a | 552 | .multiplier = BFD_DEFAULT_MULTIPLIER, |
1ec52253 OZ |
553 | }; |
554 | ||
555 | static inline struct bfd_iface_config * | |
556 | bfd_find_iface_config(struct bfd_config *cf, struct iface *iface) | |
557 | { | |
558 | struct bfd_iface_config *ic; | |
559 | ||
560 | ic = iface ? (void *) iface_patt_find(&cf->patt_list, iface, NULL) : cf->multihop; | |
561 | ||
562 | return ic ? ic : &bfd_default_iface; | |
563 | } | |
564 | ||
565 | static struct bfd_iface * | |
566 | bfd_get_iface(struct bfd_proto *p, ip_addr local, struct iface *iface) | |
567 | { | |
568 | struct bfd_iface *ifa; | |
569 | ||
570 | WALK_LIST(ifa, p->iface_list) | |
571 | if (ipa_equal(ifa->local, local) && (ifa->iface == iface)) | |
572 | return ifa->uc++, ifa; | |
573 | ||
574 | struct bfd_config *cf = (struct bfd_config *) (p->p.cf); | |
575 | struct bfd_iface_config *ic = bfd_find_iface_config(cf, iface); | |
576 | ||
577 | ifa = mb_allocz(p->tpool, sizeof(struct bfd_iface)); | |
578 | ifa->local = local; | |
579 | ifa->iface = iface; | |
580 | ifa->cf = ic; | |
581 | ifa->bfd = p; | |
582 | ||
583 | ifa->sk = bfd_open_tx_sk(p, local, iface); | |
584 | ifa->uc = 1; | |
585 | ||
692055e3 OZ |
586 | if (cf->strict_bind) |
587 | ifa->rx = bfd_open_rx_sk_bound(p, local, iface); | |
588 | ||
1ec52253 OZ |
589 | add_tail(&p->iface_list, &ifa->n); |
590 | ||
591 | return ifa; | |
592 | } | |
593 | ||
594 | static void | |
595 | bfd_free_iface(struct bfd_iface *ifa) | |
596 | { | |
597 | if (!ifa || --ifa->uc) | |
598 | return; | |
599 | ||
ffa398b8 OZ |
600 | if (ifa->sk) |
601 | { | |
602 | sk_stop(ifa->sk); | |
603 | rfree(ifa->sk); | |
604 | } | |
605 | ||
692055e3 OZ |
606 | if (ifa->rx) |
607 | { | |
608 | sk_stop(ifa->rx); | |
609 | rfree(ifa->rx); | |
610 | } | |
611 | ||
1ec52253 | 612 | rem_node(&ifa->n); |
1ec52253 OZ |
613 | mb_free(ifa); |
614 | } | |
615 | ||
616 | static void | |
617 | bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_config *nc) | |
618 | { | |
99ad208d OZ |
619 | struct bfd_iface_config *new = bfd_find_iface_config(nc, ifa->iface); |
620 | struct bfd_iface_config *old = ifa->cf; | |
621 | ||
622 | /* Check options that are handled in bfd_reconfigure_session() */ | |
623 | ifa->changed = | |
624 | (new->min_rx_int != old->min_rx_int) || | |
625 | (new->min_tx_int != old->min_tx_int) || | |
626 | (new->idle_tx_int != old->idle_tx_int) || | |
627 | (new->multiplier != old->multiplier) || | |
628 | (new->passive != old->passive); | |
1ec52253 OZ |
629 | |
630 | /* This should be probably changed to not access ifa->cf from the BFD thread */ | |
631 | birdloop_enter(p->loop); | |
99ad208d | 632 | ifa->cf = new; |
1ec52253 OZ |
633 | birdloop_leave(p->loop); |
634 | } | |
635 | ||
636 | ||
637 | /* | |
638 | * BFD requests | |
639 | */ | |
640 | ||
641 | static void | |
aa70e14c | 642 | bfd_request_notify(struct bfd_request *req, u8 state, u8 remote, u8 diag) |
1ec52253 OZ |
643 | { |
644 | u8 old_state = req->state; | |
645 | ||
646 | if (state == old_state) | |
647 | return; | |
648 | ||
649 | req->state = state; | |
650 | req->diag = diag; | |
651 | req->old_state = old_state; | |
aa70e14c | 652 | req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN) && (remote != BFD_STATE_ADMIN_DOWN); |
1ec52253 OZ |
653 | |
654 | if (req->hook) | |
655 | req->hook(req); | |
656 | } | |
657 | ||
658 | static int | |
659 | bfd_add_request(struct bfd_proto *p, struct bfd_request *req) | |
660 | { | |
7f9adafc OZ |
661 | struct bfd_config *cf = (struct bfd_config *) (p->p.cf); |
662 | ||
18f70a62 | 663 | if (p->p.vrf_set && (p->p.vrf != req->vrf)) |
cf7ff995 OZ |
664 | return 0; |
665 | ||
7f9adafc OZ |
666 | if (ipa_is_ip4(req->addr) ? !cf->accept_ipv4 : !cf->accept_ipv6) |
667 | return 0; | |
668 | ||
669 | if (req->iface ? !cf->accept_direct : !cf->accept_multihop) | |
670 | return 0; | |
671 | ||
910adaa0 OZ |
672 | uint ifindex = req->iface ? req->iface->index : 0; |
673 | struct bfd_session *s = bfd_find_session_by_addr(p, req->addr, ifindex); | |
aa70e14c | 674 | u8 loc_state, rem_state, diag; |
1ec52253 OZ |
675 | |
676 | if (!s) | |
9d3fc306 | 677 | s = bfd_add_session(p, req->addr, req->local, req->iface, &req->opts); |
1ec52253 OZ |
678 | |
679 | rem_node(&req->n); | |
680 | add_tail(&s->request_list, &req->n); | |
681 | req->session = s; | |
682 | ||
683 | bfd_lock_sessions(p); | |
aa70e14c OZ |
684 | loc_state = s->loc_state; |
685 | rem_state = s->rem_state; | |
1ec52253 OZ |
686 | diag = s->loc_diag; |
687 | bfd_unlock_sessions(p); | |
688 | ||
aa70e14c | 689 | bfd_request_notify(req, loc_state, rem_state, diag); |
1ec52253 OZ |
690 | |
691 | return 1; | |
692 | } | |
693 | ||
694 | static void | |
695 | bfd_submit_request(struct bfd_request *req) | |
696 | { | |
697 | node *n; | |
698 | ||
699 | WALK_LIST(n, bfd_proto_list) | |
700 | if (bfd_add_request(SKIP_BACK(struct bfd_proto, bfd_node, n), req)) | |
701 | return; | |
702 | ||
703 | rem_node(&req->n); | |
704 | add_tail(&bfd_wait_list, &req->n); | |
705 | req->session = NULL; | |
aa70e14c | 706 | bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, BFD_STATE_ADMIN_DOWN, 0); |
1ec52253 OZ |
707 | } |
708 | ||
709 | static void | |
710 | bfd_take_requests(struct bfd_proto *p) | |
711 | { | |
712 | node *n, *nn; | |
713 | ||
714 | WALK_LIST_DELSAFE(n, nn, bfd_wait_list) | |
715 | bfd_add_request(p, SKIP_BACK(struct bfd_request, n, n)); | |
716 | } | |
717 | ||
718 | static void | |
719 | bfd_drop_requests(struct bfd_proto *p) | |
720 | { | |
721 | node *n; | |
722 | ||
723 | HASH_WALK(p->session_hash_id, next_id, s) | |
724 | { | |
725 | /* We assume that p is not in bfd_proto_list */ | |
726 | WALK_LIST_FIRST(n, s->request_list) | |
727 | bfd_submit_request(SKIP_BACK(struct bfd_request, n, n)); | |
728 | } | |
729 | HASH_WALK_END; | |
730 | } | |
731 | ||
732 | static struct resclass bfd_request_class; | |
733 | ||
734 | struct bfd_request * | |
cf7ff995 OZ |
735 | bfd_request_session(pool *p, ip_addr addr, ip_addr local, |
736 | struct iface *iface, struct iface *vrf, | |
9d3fc306 OZ |
737 | void (*hook)(struct bfd_request *), void *data, |
738 | const struct bfd_options *opts) | |
1ec52253 OZ |
739 | { |
740 | struct bfd_request *req = ralloc(p, &bfd_request_class); | |
741 | ||
742 | /* Hack: self-link req->n, we will call rem_node() on it */ | |
743 | req->n.prev = req->n.next = &req->n; | |
744 | ||
745 | req->addr = addr; | |
746 | req->local = local; | |
747 | req->iface = iface; | |
cf7ff995 | 748 | req->vrf = vrf; |
1ec52253 | 749 | |
9d3fc306 OZ |
750 | if (opts) |
751 | req->opts = *opts; | |
752 | ||
1ec52253 OZ |
753 | bfd_submit_request(req); |
754 | ||
755 | req->hook = hook; | |
756 | req->data = data; | |
757 | ||
758 | return req; | |
759 | } | |
760 | ||
9d3fc306 OZ |
761 | void |
762 | bfd_update_request(struct bfd_request *req, const struct bfd_options *opts) | |
763 | { | |
764 | struct bfd_session *s = req->session; | |
765 | ||
766 | if (!memcmp(opts, &req->opts, sizeof(const struct bfd_options))) | |
767 | return; | |
768 | ||
769 | req->opts = *opts; | |
770 | ||
771 | if (s) | |
772 | bfd_reconfigure_session(s->ifa->bfd, s); | |
773 | } | |
774 | ||
1ec52253 OZ |
775 | static void |
776 | bfd_request_free(resource *r) | |
777 | { | |
778 | struct bfd_request *req = (struct bfd_request *) r; | |
779 | struct bfd_session *s = req->session; | |
780 | ||
781 | rem_node(&req->n); | |
782 | ||
783 | /* Remove the session if there is no request for it. Skip that if | |
784 | inside notify hooks, will be handled by bfd_notify_hook() itself */ | |
785 | ||
786 | if (s && EMPTY_LIST(s->request_list) && !s->notify_running) | |
787 | bfd_remove_session(s->ifa->bfd, s); | |
788 | } | |
789 | ||
790 | static void | |
791 | bfd_request_dump(resource *r) | |
792 | { | |
793 | struct bfd_request *req = (struct bfd_request *) r; | |
794 | ||
795 | debug("(code %p, data %p)\n", req->hook, req->data); | |
796 | } | |
797 | ||
798 | static struct resclass bfd_request_class = { | |
799 | "BFD request", | |
800 | sizeof(struct bfd_request), | |
801 | bfd_request_free, | |
802 | bfd_request_dump, | |
803 | NULL, | |
13c6cf8a | 804 | NULL, |
1ec52253 OZ |
805 | }; |
806 | ||
807 | ||
808 | /* | |
809 | * BFD neighbors | |
810 | */ | |
811 | ||
812 | static void | |
813 | bfd_neigh_notify(struct neighbor *nb) | |
814 | { | |
815 | struct bfd_proto *p = (struct bfd_proto *) nb->proto; | |
816 | struct bfd_neighbor *n = nb->data; | |
817 | ||
818 | if (!n) | |
819 | return; | |
820 | ||
821 | if ((nb->scope > 0) && !n->req) | |
822 | { | |
2d0b7e24 | 823 | ip_addr local = ipa_nonzero(n->local) ? n->local : nb->ifa->ip; |
9d3fc306 | 824 | n->req = bfd_request_session(p->p.pool, n->addr, local, nb->iface, p->p.vrf, NULL, NULL, NULL); |
1ec52253 OZ |
825 | } |
826 | ||
827 | if ((nb->scope <= 0) && n->req) | |
828 | { | |
829 | rfree(n->req); | |
830 | n->req = NULL; | |
831 | } | |
6a8d3f1c OZ |
832 | } |
833 | ||
bf139664 OZ |
834 | static void |
835 | bfd_start_neighbor(struct bfd_proto *p, struct bfd_neighbor *n) | |
836 | { | |
1ec52253 | 837 | n->active = 1; |
bf139664 | 838 | |
1ec52253 | 839 | if (n->multihop) |
bf139664 | 840 | { |
9d3fc306 | 841 | n->req = bfd_request_session(p->p.pool, n->addr, n->local, NULL, p->p.vrf, NULL, NULL, NULL); |
bf139664 OZ |
842 | return; |
843 | } | |
844 | ||
586c1800 | 845 | struct neighbor *nb = neigh_find(&p->p, n->addr, n->iface, NEF_STICKY); |
bf139664 OZ |
846 | if (!nb) |
847 | { | |
848 | log(L_ERR "%s: Invalid remote address %I%J", p->p.name, n->addr, n->iface); | |
849 | return; | |
850 | } | |
851 | ||
852 | if (nb->data) | |
853 | { | |
1ec52253 | 854 | log(L_ERR "%s: Duplicate neighbor %I", p->p.name, n->addr); |
bf139664 OZ |
855 | return; |
856 | } | |
857 | ||
1ec52253 OZ |
858 | n->neigh = nb; |
859 | nb->data = n; | |
bf139664 OZ |
860 | |
861 | if (nb->scope > 0) | |
1ec52253 | 862 | bfd_neigh_notify(nb); |
bf139664 | 863 | else |
6a8d3f1c | 864 | TRACE(D_EVENTS, "Waiting for %I%J to become my neighbor", n->addr, n->iface); |
bf139664 OZ |
865 | } |
866 | ||
867 | static void | |
3e236955 | 868 | bfd_stop_neighbor(struct bfd_proto *p UNUSED, struct bfd_neighbor *n) |
bf139664 | 869 | { |
1ec52253 OZ |
870 | if (n->neigh) |
871 | n->neigh->data = NULL; | |
872 | n->neigh = NULL; | |
bf139664 | 873 | |
1ec52253 OZ |
874 | rfree(n->req); |
875 | n->req = NULL; | |
bf139664 OZ |
876 | } |
877 | ||
1ec52253 OZ |
878 | static inline int |
879 | bfd_same_neighbor(struct bfd_neighbor *x, struct bfd_neighbor *y) | |
bf139664 | 880 | { |
1ec52253 OZ |
881 | return ipa_equal(x->addr, y->addr) && ipa_equal(x->local, y->local) && |
882 | (x->iface == y->iface) && (x->multihop == y->multihop); | |
883 | } | |
bf139664 | 884 | |
1ec52253 OZ |
885 | static void |
886 | bfd_reconfigure_neighbors(struct bfd_proto *p, struct bfd_config *new) | |
887 | { | |
888 | struct bfd_config *old = (struct bfd_config *) (p->p.cf); | |
889 | struct bfd_neighbor *on, *nn; | |
bf139664 | 890 | |
1ec52253 OZ |
891 | WALK_LIST(on, old->neigh_list) |
892 | { | |
893 | WALK_LIST(nn, new->neigh_list) | |
894 | if (bfd_same_neighbor(nn, on)) | |
895 | { | |
896 | nn->neigh = on->neigh; | |
897 | if (nn->neigh) | |
898 | nn->neigh->data = nn; | |
899 | ||
900 | nn->req = on->req; | |
901 | nn->active = 1; | |
4821251e | 902 | goto next; |
1ec52253 OZ |
903 | } |
904 | ||
905 | bfd_stop_neighbor(p, on); | |
4821251e | 906 | next:; |
1ec52253 | 907 | } |
bf139664 | 908 | |
1ec52253 OZ |
909 | WALK_LIST(nn, new->neigh_list) |
910 | if (!nn->active) | |
911 | bfd_start_neighbor(p, nn); | |
bf139664 OZ |
912 | } |
913 | ||
914 | ||
1ec52253 OZ |
915 | /* |
916 | * BFD notify socket | |
917 | */ | |
918 | ||
6a8d3f1c OZ |
919 | /* This core notify code should be replaced after main loop transition to birdloop */ |
920 | ||
921 | int pipe(int pipefd[2]); | |
922 | void pipe_drain(int fd); | |
923 | void pipe_kick(int fd); | |
924 | ||
925 | static int | |
3e236955 | 926 | bfd_notify_hook(sock *sk, uint len UNUSED) |
6a8d3f1c OZ |
927 | { |
928 | struct bfd_proto *p = sk->data; | |
929 | struct bfd_session *s; | |
930 | list tmp_list; | |
aa70e14c | 931 | u8 loc_state, rem_state, diag; |
1ec52253 | 932 | node *n, *nn; |
6a8d3f1c OZ |
933 | |
934 | pipe_drain(sk->fd); | |
935 | ||
936 | bfd_lock_sessions(p); | |
937 | init_list(&tmp_list); | |
938 | add_tail_list(&tmp_list, &p->notify_list); | |
939 | init_list(&p->notify_list); | |
940 | bfd_unlock_sessions(p); | |
941 | ||
942 | WALK_LIST_FIRST(s, tmp_list) | |
943 | { | |
944 | bfd_lock_sessions(p); | |
665b8e52 | 945 | rem_node(&s->n); |
aa70e14c OZ |
946 | loc_state = s->loc_state; |
947 | rem_state = s->rem_state; | |
1ec52253 | 948 | diag = s->loc_diag; |
6a8d3f1c OZ |
949 | bfd_unlock_sessions(p); |
950 | ||
1ec52253 OZ |
951 | s->notify_running = 1; |
952 | WALK_LIST_DELSAFE(n, nn, s->request_list) | |
aa70e14c | 953 | bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), loc_state, rem_state, diag); |
1ec52253 OZ |
954 | s->notify_running = 0; |
955 | ||
956 | /* Remove the session if all requests were removed in notify hooks */ | |
957 | if (EMPTY_LIST(s->request_list)) | |
958 | bfd_remove_session(p, s); | |
6a8d3f1c OZ |
959 | } |
960 | ||
961 | return 0; | |
962 | } | |
963 | ||
964 | static inline void | |
965 | bfd_notify_kick(struct bfd_proto *p) | |
966 | { | |
967 | pipe_kick(p->notify_ws->fd); | |
968 | } | |
969 | ||
970 | static void | |
971 | bfd_noterr_hook(sock *sk, int err) | |
972 | { | |
973 | struct bfd_proto *p = sk->data; | |
974 | log(L_ERR "%s: Notify socket error: %m", p->p.name, err); | |
975 | } | |
976 | ||
977 | static void | |
978 | bfd_notify_init(struct bfd_proto *p) | |
979 | { | |
980 | int pfds[2]; | |
981 | sock *sk; | |
982 | ||
983 | int rv = pipe(pfds); | |
984 | if (rv < 0) | |
985 | die("pipe: %m"); | |
986 | ||
987 | sk = sk_new(p->p.pool); | |
988 | sk->type = SK_MAGIC; | |
989 | sk->rx_hook = bfd_notify_hook; | |
990 | sk->err_hook = bfd_noterr_hook; | |
991 | sk->fd = pfds[0]; | |
992 | sk->data = p; | |
993 | if (sk_open(sk) < 0) | |
994 | die("bfd: sk_open failed"); | |
995 | p->notify_rs = sk; | |
996 | ||
997 | /* The write sock is not added to any event loop */ | |
998 | sk = sk_new(p->p.pool); | |
999 | sk->type = SK_MAGIC; | |
1000 | sk->fd = pfds[1]; | |
1001 | sk->data = p; | |
1002 | sk->flags = SKF_THREAD; | |
1003 | if (sk_open(sk) < 0) | |
1004 | die("bfd: sk_open failed"); | |
1005 | p->notify_ws = sk; | |
1006 | } | |
1007 | ||
bf139664 | 1008 | |
1ec52253 OZ |
1009 | /* |
1010 | * BFD protocol glue | |
1011 | */ | |
1012 | ||
bf139664 OZ |
1013 | static struct proto * |
1014 | bfd_init(struct proto_config *c) | |
1015 | { | |
f4a60a9b | 1016 | struct proto *p = proto_new(c); |
bf139664 | 1017 | |
6a8d3f1c | 1018 | p->neigh_notify = bfd_neigh_notify; |
bf139664 OZ |
1019 | |
1020 | return p; | |
1021 | } | |
1022 | ||
1023 | static int | |
1024 | bfd_start(struct proto *P) | |
1025 | { | |
1026 | struct bfd_proto *p = (struct bfd_proto *) P; | |
1027 | struct bfd_config *cf = (struct bfd_config *) (P->cf); | |
1028 | ||
1ec52253 | 1029 | p->loop = birdloop_new(); |
6a8d3f1c OZ |
1030 | p->tpool = rp_new(NULL, "BFD thread root"); |
1031 | pthread_spin_init(&p->lock, PTHREAD_PROCESS_PRIVATE); | |
1032 | ||
bf139664 | 1033 | p->session_slab = sl_new(P->pool, sizeof(struct bfd_session)); |
1ec52253 OZ |
1034 | HASH_INIT(p->session_hash_id, P->pool, 8); |
1035 | HASH_INIT(p->session_hash_ip, P->pool, 8); | |
6a8d3f1c | 1036 | |
1ec52253 | 1037 | init_list(&p->iface_list); |
6a8d3f1c | 1038 | |
6a8d3f1c OZ |
1039 | init_list(&p->notify_list); |
1040 | bfd_notify_init(p); | |
1041 | ||
1ec52253 OZ |
1042 | add_tail(&bfd_proto_list, &p->bfd_node); |
1043 | ||
6a8d3f1c | 1044 | birdloop_enter(p->loop); |
7f9adafc | 1045 | |
692055e3 OZ |
1046 | if (!cf->strict_bind) |
1047 | { | |
1048 | if (cf->accept_ipv4 && cf->accept_direct) | |
1049 | p->rx4_1 = bfd_open_rx_sk(p, 0, SK_IPV4); | |
7f9adafc | 1050 | |
692055e3 OZ |
1051 | if (cf->accept_ipv4 && cf->accept_multihop) |
1052 | p->rx4_m = bfd_open_rx_sk(p, 1, SK_IPV4); | |
7f9adafc | 1053 | |
692055e3 OZ |
1054 | if (cf->accept_ipv6 && cf->accept_direct) |
1055 | p->rx6_1 = bfd_open_rx_sk(p, 0, SK_IPV6); | |
7f9adafc | 1056 | |
692055e3 OZ |
1057 | if (cf->accept_ipv6 && cf->accept_multihop) |
1058 | p->rx6_m = bfd_open_rx_sk(p, 1, SK_IPV6); | |
1059 | } | |
7f9adafc | 1060 | |
6a8d3f1c | 1061 | birdloop_leave(p->loop); |
bf139664 | 1062 | |
1ec52253 OZ |
1063 | bfd_take_requests(p); |
1064 | ||
bf139664 | 1065 | struct bfd_neighbor *n; |
6a8d3f1c | 1066 | WALK_LIST(n, cf->neigh_list) |
bf139664 OZ |
1067 | bfd_start_neighbor(p, n); |
1068 | ||
0e175f9f | 1069 | birdloop_start(p->loop); |
6a8d3f1c | 1070 | |
bf139664 OZ |
1071 | return PS_UP; |
1072 | } | |
1073 | ||
1074 | ||
1075 | static int | |
1076 | bfd_shutdown(struct proto *P) | |
1077 | { | |
1078 | struct bfd_proto *p = (struct bfd_proto *) P; | |
1ec52253 OZ |
1079 | struct bfd_config *cf = (struct bfd_config *) (P->cf); |
1080 | ||
1081 | rem_node(&p->bfd_node); | |
bf139664 | 1082 | |
0e175f9f OZ |
1083 | birdloop_stop(p->loop); |
1084 | ||
1ec52253 OZ |
1085 | struct bfd_neighbor *n; |
1086 | WALK_LIST(n, cf->neigh_list) | |
1087 | bfd_stop_neighbor(p, n); | |
1088 | ||
1089 | bfd_drop_requests(p); | |
1090 | ||
0e175f9f OZ |
1091 | /* FIXME: This is hack */ |
1092 | birdloop_enter(p->loop); | |
1093 | rfree(p->tpool); | |
1094 | birdloop_leave(p->loop); | |
1095 | ||
1ec52253 | 1096 | birdloop_free(p->loop); |
bf139664 | 1097 | |
1ec52253 | 1098 | return PS_DOWN; |
bf139664 OZ |
1099 | } |
1100 | ||
1101 | static int | |
1102 | bfd_reconfigure(struct proto *P, struct proto_config *c) | |
1103 | { | |
1104 | struct bfd_proto *p = (struct bfd_proto *) P; | |
7f9adafc | 1105 | struct bfd_config *old = (struct bfd_config *) (P->cf); |
bf139664 | 1106 | struct bfd_config *new = (struct bfd_config *) c; |
1ec52253 | 1107 | struct bfd_iface *ifa; |
bf139664 | 1108 | |
7f9adafc OZ |
1109 | /* TODO: Improve accept reconfiguration */ |
1110 | if ((new->accept_ipv4 != old->accept_ipv4) || | |
1111 | (new->accept_ipv6 != old->accept_ipv6) || | |
1112 | (new->accept_direct != old->accept_direct) || | |
692055e3 OZ |
1113 | (new->accept_multihop != old->accept_multihop) || |
1114 | (new->strict_bind != old->strict_bind)) | |
7f9adafc OZ |
1115 | return 0; |
1116 | ||
6a8d3f1c OZ |
1117 | birdloop_mask_wakeups(p->loop); |
1118 | ||
1ec52253 OZ |
1119 | WALK_LIST(ifa, p->iface_list) |
1120 | bfd_reconfigure_iface(p, ifa, new); | |
1121 | ||
1122 | HASH_WALK(p->session_hash_id, next_id, s) | |
1123 | { | |
1124 | if (s->ifa->changed) | |
1125 | bfd_reconfigure_session(p, s); | |
1126 | } | |
1127 | HASH_WALK_END; | |
bf139664 | 1128 | |
1ec52253 | 1129 | bfd_reconfigure_neighbors(p, new); |
bf139664 | 1130 | |
6a8d3f1c OZ |
1131 | birdloop_unmask_wakeups(p->loop); |
1132 | ||
bf139664 OZ |
1133 | return 1; |
1134 | } | |
1135 | ||
1136 | static void | |
3e236955 | 1137 | bfd_copy_config(struct proto_config *dest, struct proto_config *src UNUSED) |
bf139664 OZ |
1138 | { |
1139 | struct bfd_config *d = (struct bfd_config *) dest; | |
6a8d3f1c | 1140 | // struct bfd_config *s = (struct bfd_config *) src; |
bf139664 | 1141 | |
1ec52253 | 1142 | /* We clean up patt_list and neigh_list, neighbors and ifaces are non-sharable */ |
0479b443 | 1143 | init_list(&d->patt_list); |
6a8d3f1c | 1144 | init_list(&d->neigh_list); |
bf139664 OZ |
1145 | } |
1146 | ||
37bf2078 KK |
1147 | void bfd_show_details(struct bfd_session *s) |
1148 | { | |
1149 | cli_msg(-1020, " IP address: %I", s->addr); | |
1150 | cli_msg(-1020, " Interface: %s", (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---"); | |
1151 | cli_msg(-1020, " Role: %s", (s->passive) ? "Passive" : "Active"); | |
1152 | cli_msg(-1020, " Local session:"); | |
1153 | cli_msg(-1020, " State: %s", bfd_state_names[s->loc_state]); | |
1154 | cli_msg(-1020, " Session ID: %u", s->loc_id); | |
1155 | if (s->loc_diag || s->rem_diag) | |
1156 | cli_msg(-1020, " Issue: %s", bfd_diag_names[s->loc_diag]); | |
1157 | ||
1158 | cli_msg(-1020, " Remote session:"); | |
1159 | cli_msg(-1020, " State: %s", bfd_state_names[s->rem_state]); | |
1160 | cli_msg(-1020, " Session ID: %u", s->loc_id, s->rem_id); | |
1161 | if (s->loc_diag || s->rem_diag) | |
1162 | cli_msg(-1020, " Issue: %s", bfd_diag_names[s->rem_diag]); | |
1163 | ||
1164 | cli_msg(-1020, " Session mode: %s", (s->rem_demand_mode) ? "Demand" : "Asynchronous"); | |
1165 | if (!s->rem_demand_mode) | |
1166 | { | |
1167 | cli_msg(-1020, " Local intervals:"); | |
1168 | cli_msg(-1020, " Desired min tx: %t", s->des_min_tx_int); | |
1169 | cli_msg(-1020, " Required min rx: %t", s->req_min_rx_int); | |
1170 | cli_msg(-1020, " Remote intervals:"); | |
1171 | cli_msg(-1020, " Desired min tx: %t", s->rem_min_tx_int); | |
1172 | cli_msg(-1020, " Required min rx: %t", s->rem_min_rx_int); | |
1173 | cli_msg(-1020, " Timers:"); | |
1174 | cli_msg(-1020, " Hold timer remains %t/%t", tm_remains(s->hold_timer), MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult); // The total time is just copied from timers setings. I hope it is not (and will not) be problem. | |
1175 | cli_msg(-1020, " TX timer remains %t", tm_remains(s->tx_timer)); | |
1176 | } | |
1177 | else if (tm_remains(s->hold_timer) > 0) | |
1178 | { | |
1179 | cli_msg(-1020, " Hold timer remains %t", tm_remains(s->hold_timer)); | |
1180 | } | |
1181 | cli_msg(-1020, " Latest actions:"); | |
1182 | cli_msg(-1020, " Last received valid control packet before %t", current_time() - s->last_rx); | |
1183 | cli_msg(-1020, " Last sent periodic control packet before %t", current_time() - s->last_tx); | |
1184 | btime tim = (btime)(((u64) s->tx_csn_time) << 20); | |
1185 | if (tim > 0) | |
1186 | cli_msg(-1020, " Last csn change before %t", current_time() - tim); | |
1187 | if (s->poll_active || s->poll_scheduled) | |
1188 | cli_msg(-1020, " Poll %s%s", (s->poll_active) ? ", poll active" : "", (s->poll_scheduled) ? ", poll scheduled" : ""); | |
1189 | else | |
1190 | cli_msg(-1020, " Poll inactive"); | |
1191 | cli_msg(-1020, ""); | |
1192 | } | |
1193 | ||
6a8d3f1c | 1194 | void |
a48dc5ef | 1195 | bfd_show_sessions(struct proto *P, int details, net_addr addr) |
6a8d3f1c | 1196 | { |
1ec52253 | 1197 | byte tbuf[TM_DATETIME_BUFFER_SIZE]; |
6a8d3f1c | 1198 | struct bfd_proto *p = (struct bfd_proto *) P; |
0479b443 | 1199 | uint state, diag UNUSED; |
d3fa9e84 | 1200 | btime tx_int, timeout; |
6a8d3f1c OZ |
1201 | const char *ifname; |
1202 | ||
1203 | if (p->p.proto_state != PS_UP) | |
1204 | { | |
0c95f85e | 1205 | cli_msg(-1020, "%s: is not up", p->p.name); |
6a8d3f1c OZ |
1206 | return; |
1207 | } | |
1208 | ||
0c95f85e | 1209 | cli_msg(-1020, "%s:", p->p.name); |
37bf2078 KK |
1210 | if (!details) |
1211 | cli_msg(-1020, "%-25s %-10s %-10s %-12s %8s %8s", | |
1ec52253 | 1212 | "IP address", "Interface", "State", "Since", "Interval", "Timeout"); |
6a8d3f1c | 1213 | |
6a8d3f1c OZ |
1214 | |
1215 | HASH_WALK(p->session_hash_id, next_id, s) | |
1216 | { | |
1ec52253 | 1217 | /* FIXME: this is thread-unsafe, but perhaps harmless */ |
a48dc5ef KK |
1218 | |
1219 | if (addr.type != 0 && !ipa_in_netX(s->addr, &addr)) | |
1220 | continue; | |
37bf2078 KK |
1221 | if (!details) |
1222 | { | |
1223 | state = s->loc_state; | |
1224 | diag = s->loc_diag; | |
1225 | ifname = (s->ifa && s->ifa->iface) ? s->ifa->iface->name : "---"; | |
1226 | tx_int = s->last_tx ? MAX(s->des_min_tx_int, s->rem_min_rx_int) : 0; | |
1227 | timeout = (btime) MAX(s->req_min_rx_int, s->rem_min_tx_int) * s->rem_detect_mult; | |
1228 | ||
1229 | state = (state < 4) ? state : 0; | |
1230 | tm_format_time(tbuf, &config->tf_proto, s->last_state_change); | |
1231 | ||
1232 | cli_msg(-1020, "%-25I %-10s %-10s %-12s %7t %7t", | |
d3fa9e84 | 1233 | s->addr, ifname, bfd_state_names[state], tbuf, tx_int, timeout); |
37bf2078 KK |
1234 | } |
1235 | else | |
1236 | { | |
1237 | bfd_show_details(s); | |
1238 | } | |
6a8d3f1c OZ |
1239 | } |
1240 | HASH_WALK_END; | |
6a8d3f1c OZ |
1241 | } |
1242 | ||
1243 | ||
bf139664 OZ |
1244 | struct protocol proto_bfd = { |
1245 | .name = "BFD", | |
1246 | .template = "bfd%d", | |
ee7e2ffd | 1247 | .class = PROTOCOL_BFD, |
f4a60a9b | 1248 | .proto_size = sizeof(struct bfd_proto), |
2bbc3083 | 1249 | .config_size = sizeof(struct bfd_config), |
bf139664 OZ |
1250 | .init = bfd_init, |
1251 | .start = bfd_start, | |
1252 | .shutdown = bfd_shutdown, | |
1253 | .reconfigure = bfd_reconfigure, | |
1254 | .copy_config = bfd_copy_config, | |
1255 | }; | |
4a23ede2 MM |
1256 | |
1257 | void | |
1258 | bfd_build(void) | |
1259 | { | |
1260 | proto_build(&proto_bfd); | |
1261 | } |