]> git.proxmox.com Git - mirror_frr.git/blob - lib/bfd.c
04f1c27b85d3ea5393aee4ced21c0665cec20701
[mirror_frr.git] / lib / bfd.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * bfd.c: BFD handling routines
4 *
5 * @copyright Copyright (C) 2015 Cumulus Networks, Inc.
6 */
7
8 #include <zebra.h>
9
10 #include "command.h"
11 #include "memory.h"
12 #include "prefix.h"
13 #include "event.h"
14 #include "stream.h"
15 #include "vrf.h"
16 #include "zclient.h"
17 #include "libfrr.h"
18 #include "table.h"
19 #include "vty.h"
20 #include "bfd.h"
21
22 DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info");
23 DEFINE_MTYPE_STATIC(LIB, BFD_SOURCE, "BFD source cache");
24
25 /**
26 * BFD protocol integration configuration.
27 */
28
29 /** Events definitions. */
30 enum bfd_session_event {
31 /** Remove the BFD session configuration. */
32 BSE_UNINSTALL,
33 /** Install the BFD session configuration. */
34 BSE_INSTALL,
35 };
36
37 /**
38 * BFD source selection result cache.
39 *
40 * This structure will keep track of the result based on the destination
41 * prefix. When the result changes all related BFD sessions with automatic
42 * source will be updated.
43 */
44 struct bfd_source_cache {
45 /** Address VRF belongs. */
46 vrf_id_t vrf_id;
47 /** Destination network address. */
48 struct prefix address;
49 /** Source selected. */
50 struct prefix source;
51 /** Is the source address valid? */
52 bool valid;
53 /** BFD sessions using this. */
54 size_t refcount;
55
56 SLIST_ENTRY(bfd_source_cache) entry;
57 };
58 SLIST_HEAD(bfd_source_list, bfd_source_cache);
59
60 /**
61 * Data structure to do the necessary tricks to hide the BFD protocol
62 * integration internals.
63 */
64 struct bfd_session_params {
65 /** Contains the session parameters and more. */
66 struct bfd_session_arg args;
67 /** Contains the session state. */
68 struct bfd_session_status bss;
69 /** Protocol implementation status update callback. */
70 bsp_status_update updatecb;
71 /** Protocol implementation custom data pointer. */
72 void *arg;
73
74 /**
75 * Next event.
76 *
77 * This variable controls what action to execute when the command batch
78 * finishes. Normally we'd use `thread_add_event` value, however since
79 * that function is going to be called multiple times and the value
80 * might be different we'll use this variable to keep track of it.
81 */
82 enum bfd_session_event lastev;
83 /**
84 * BFD session configuration event.
85 *
86 * Multiple actions might be asked during a command batch (either via
87 * configuration load or northbound batch), so we'll use this to
88 * install/uninstall the BFD session parameters only once.
89 */
90 struct event *installev;
91
92 /** BFD session installation state. */
93 bool installed;
94
95 /** Automatic source selection. */
96 bool auto_source;
97 /** Currently selected source. */
98 struct bfd_source_cache *source_cache;
99
100 /** Global BFD paramaters list. */
101 TAILQ_ENTRY(bfd_session_params) entry;
102 };
103
104 struct bfd_sessions_global {
105 /**
106 * Global BFD session parameters list for (re)installation and update
107 * without code duplication among daemons.
108 */
109 TAILQ_HEAD(bsplist, bfd_session_params) bsplist;
110 /** BFD automatic source selection cache. */
111 struct bfd_source_list source_list;
112
113 /** Pointer to FRR's event manager. */
114 struct thread_master *tm;
115 /** Pointer to zebra client data structure. */
116 struct zclient *zc;
117
118 /** Debugging state. */
119 bool debugging;
120 /** Is shutting down? */
121 bool shutting_down;
122 };
123
124 /** Global configuration variable. */
125 static struct bfd_sessions_global bsglobal;
126
127 /** Global empty address for IPv4/IPv6. */
128 static const struct in6_addr i6a_zero;
129
130 /*
131 * Prototypes
132 */
133
134 static void bfd_source_cache_get(struct bfd_session_params *session);
135 static void bfd_source_cache_put(struct bfd_session_params *session);
136
137 /*
138 * bfd_get_peer_info - Extract the Peer information for which the BFD session
139 * went down from the message sent from Zebra to clients.
140 */
141 static struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp,
142 struct prefix *sp, int *status,
143 int *remote_cbit, vrf_id_t vrf_id)
144 {
145 unsigned int ifindex;
146 struct interface *ifp = NULL;
147 int plen;
148 int local_remote_cbit;
149
150 /*
151 * If the ifindex lookup fails the
152 * rest of the data in the stream is
153 * not read. All examples of this function
154 * call immediately use the dp->family which
155 * is not good. Ensure we are not using
156 * random data
157 */
158 memset(dp, 0, sizeof(*dp));
159 memset(sp, 0, sizeof(*sp));
160
161 /* Get interface index. */
162 STREAM_GETL(s, ifindex);
163
164 /* Lookup index. */
165 if (ifindex != 0) {
166 ifp = if_lookup_by_index(ifindex, vrf_id);
167 if (ifp == NULL) {
168 if (bsglobal.debugging)
169 zlog_debug(
170 "%s: Can't find interface by ifindex: %d ",
171 __func__, ifindex);
172 return NULL;
173 }
174 }
175
176 /* Fetch destination address. */
177 STREAM_GETC(s, dp->family);
178
179 plen = prefix_blen(dp);
180 STREAM_GET(&dp->u.prefix, s, plen);
181 STREAM_GETC(s, dp->prefixlen);
182
183 /* Get BFD status. */
184 STREAM_GETL(s, (*status));
185
186 STREAM_GETC(s, sp->family);
187
188 plen = prefix_blen(sp);
189 STREAM_GET(&sp->u.prefix, s, plen);
190 STREAM_GETC(s, sp->prefixlen);
191
192 STREAM_GETC(s, local_remote_cbit);
193 if (remote_cbit)
194 *remote_cbit = local_remote_cbit;
195 return ifp;
196
197 stream_failure:
198 /*
199 * Clean dp and sp because caller
200 * will immediately check them valid or not
201 */
202 memset(dp, 0, sizeof(*dp));
203 memset(sp, 0, sizeof(*sp));
204 return NULL;
205 }
206
207 /*
208 * bfd_get_status_str - Convert BFD status to a display string.
209 */
210 const char *bfd_get_status_str(int status)
211 {
212 switch (status) {
213 case BFD_STATUS_DOWN:
214 return "Down";
215 case BFD_STATUS_UP:
216 return "Up";
217 case BFD_STATUS_ADMIN_DOWN:
218 return "Admin Down";
219 case BFD_STATUS_UNKNOWN:
220 default:
221 return "Unknown";
222 }
223 }
224
225 /*
226 * bfd_last_update - Calculate the last BFD update time and convert it
227 * into a dd:hh:mm:ss display format.
228 */
229 static void bfd_last_update(time_t last_update, char *buf, size_t len)
230 {
231 time_t curr;
232 time_t diff;
233 struct tm tm;
234 struct timeval tv;
235
236 /* If no BFD status update has ever been received, print `never'. */
237 if (last_update == 0) {
238 snprintf(buf, len, "never");
239 return;
240 }
241
242 /* Get current time. */
243 monotime(&tv);
244 curr = tv.tv_sec;
245 diff = curr - last_update;
246 gmtime_r(&diff, &tm);
247
248 snprintf(buf, len, "%d:%02d:%02d:%02d", tm.tm_yday, tm.tm_hour,
249 tm.tm_min, tm.tm_sec);
250 }
251
252 /*
253 * bfd_client_sendmsg - Format and send a client register
254 * command to Zebra to be forwarded to BFD
255 */
256 void bfd_client_sendmsg(struct zclient *zclient, int command,
257 vrf_id_t vrf_id)
258 {
259 struct stream *s;
260 enum zclient_send_status ret;
261
262 /* Check socket. */
263 if (!zclient || zclient->sock < 0) {
264 if (bsglobal.debugging)
265 zlog_debug(
266 "%s: Can't send BFD client register, Zebra client not established",
267 __func__);
268 return;
269 }
270
271 s = zclient->obuf;
272 stream_reset(s);
273 zclient_create_header(s, command, vrf_id);
274
275 stream_putl(s, getpid());
276
277 stream_putw_at(s, 0, stream_get_endp(s));
278
279 ret = zclient_send_message(zclient);
280
281 if (ret == ZCLIENT_SEND_FAILURE) {
282 if (bsglobal.debugging)
283 zlog_debug(
284 "%s: %ld: zclient_send_message() failed",
285 __func__, (long)getpid());
286 return;
287 }
288
289 return;
290 }
291
292 int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
293 {
294 struct stream *s;
295 size_t addrlen;
296
297 /* Individual reg/dereg messages are suppressed during shutdown. */
298 if (bsglobal.shutting_down) {
299 if (bsglobal.debugging)
300 zlog_debug(
301 "%s: Suppressing BFD peer reg/dereg messages",
302 __func__);
303 return -1;
304 }
305
306 /* Check socket. */
307 if (!zc || zc->sock < 0) {
308 if (bsglobal.debugging)
309 zlog_debug("%s: zclient unavailable", __func__);
310 return -1;
311 }
312
313 s = zc->obuf;
314 stream_reset(s);
315
316 /* Create new message. */
317 zclient_create_header(s, args->command, args->vrf_id);
318 stream_putl(s, getpid());
319
320 /* Encode destination address. */
321 stream_putw(s, args->family);
322 addrlen = (args->family == AF_INET) ? sizeof(struct in_addr)
323 : sizeof(struct in6_addr);
324 stream_put(s, &args->dst, addrlen);
325
326 /*
327 * For more BFD integration protocol details, see function
328 * `_ptm_msg_read` in `bfdd/ptm_adapter.c`.
329 */
330 #if HAVE_BFDD > 0
331 /* Session timers. */
332 stream_putl(s, args->min_rx);
333 stream_putl(s, args->min_tx);
334 stream_putc(s, args->detection_multiplier);
335
336 /* Is multi hop? */
337 stream_putc(s, args->mhop != 0);
338
339 /* Source address. */
340 stream_putw(s, args->family);
341 stream_put(s, &args->src, addrlen);
342
343 /* Send the expected hops. */
344 stream_putc(s, args->hops);
345
346 /* Send interface name if any. */
347 if (args->mhop) {
348 /* Don't send interface. */
349 stream_putc(s, 0);
350 if (bsglobal.debugging && args->ifnamelen)
351 zlog_debug("%s: multi hop is configured, not sending interface",
352 __func__);
353 } else {
354 stream_putc(s, args->ifnamelen);
355 if (args->ifnamelen)
356 stream_put(s, args->ifname, args->ifnamelen);
357 }
358
359 /* Send the C bit indicator. */
360 stream_putc(s, args->cbit);
361
362 /* Send profile name if any. */
363 stream_putc(s, args->profilelen);
364 if (args->profilelen)
365 stream_put(s, args->profile, args->profilelen);
366 #else /* PTM BFD */
367 /* Encode timers if this is a registration message. */
368 if (args->command != ZEBRA_BFD_DEST_DEREGISTER) {
369 stream_putl(s, args->min_rx);
370 stream_putl(s, args->min_tx);
371 stream_putc(s, args->detection_multiplier);
372 }
373
374 if (args->mhop) {
375 /* Multi hop indicator. */
376 stream_putc(s, 1);
377
378 /* Multi hop always sends the source address. */
379 stream_putw(s, args->family);
380 stream_put(s, &args->src, addrlen);
381
382 /* Send the expected hops. */
383 stream_putc(s, args->hops);
384 } else {
385 /* Multi hop indicator. */
386 stream_putc(s, 0);
387
388 /* Single hop only sends the source address when IPv6. */
389 if (args->family == AF_INET6) {
390 stream_putw(s, args->family);
391 stream_put(s, &args->src, addrlen);
392 }
393
394 /* Send interface name if any. */
395 stream_putc(s, args->ifnamelen);
396 if (args->ifnamelen)
397 stream_put(s, args->ifname, args->ifnamelen);
398 }
399
400 /* Send the C bit indicator. */
401 stream_putc(s, args->cbit);
402 #endif /* HAVE_BFDD */
403
404 /* Finish the message by writing the size. */
405 stream_putw_at(s, 0, stream_get_endp(s));
406
407 /* Send message to zebra. */
408 if (zclient_send_message(zc) == ZCLIENT_SEND_FAILURE) {
409 if (bsglobal.debugging)
410 zlog_debug("%s: zclient_send_message failed", __func__);
411 return -1;
412 }
413
414 return 0;
415 }
416
417 struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *arg)
418 {
419 struct bfd_session_params *bsp;
420
421 bsp = XCALLOC(MTYPE_BFD_INFO, sizeof(*bsp));
422
423 /* Save application data. */
424 bsp->updatecb = updatecb;
425 bsp->arg = arg;
426
427 /* Set defaults. */
428 bsp->args.detection_multiplier = BFD_DEF_DETECT_MULT;
429 bsp->args.hops = 1;
430 bsp->args.min_rx = BFD_DEF_MIN_RX;
431 bsp->args.min_tx = BFD_DEF_MIN_TX;
432 bsp->args.vrf_id = VRF_DEFAULT;
433
434 /* Register in global list. */
435 TAILQ_INSERT_TAIL(&bsglobal.bsplist, bsp, entry);
436
437 return bsp;
438 }
439
440 static bool _bfd_sess_valid(const struct bfd_session_params *bsp)
441 {
442 /* Peer/local address not configured. */
443 if (bsp->args.family == 0)
444 return false;
445
446 /* Address configured but invalid. */
447 if (bsp->args.family != AF_INET && bsp->args.family != AF_INET6) {
448 if (bsglobal.debugging)
449 zlog_debug("%s: invalid session family: %d", __func__,
450 bsp->args.family);
451 return false;
452 }
453
454 /* Invalid address. */
455 if (memcmp(&bsp->args.dst, &i6a_zero, sizeof(i6a_zero)) == 0) {
456 if (bsglobal.debugging) {
457 if (bsp->args.family == AF_INET)
458 zlog_debug("%s: invalid address: %pI4",
459 __func__,
460 (struct in_addr *)&bsp->args.dst);
461 else
462 zlog_debug("%s: invalid address: %pI6",
463 __func__, &bsp->args.dst);
464 }
465 return false;
466 }
467
468 /* Multi hop requires local address. */
469 if (bsp->args.mhop
470 && memcmp(&i6a_zero, &bsp->args.src, sizeof(i6a_zero)) == 0) {
471 if (bsglobal.debugging)
472 zlog_debug(
473 "%s: multi hop but no local address provided",
474 __func__);
475 return false;
476 }
477
478 /* Check VRF ID. */
479 if (bsp->args.vrf_id == VRF_UNKNOWN) {
480 if (bsglobal.debugging)
481 zlog_debug("%s: asked for unknown VRF", __func__);
482 return false;
483 }
484
485 return true;
486 }
487
488 static void _bfd_sess_send(struct event *t)
489 {
490 struct bfd_session_params *bsp = THREAD_ARG(t);
491 int rv;
492
493 /* Validate configuration before trying to send bogus data. */
494 if (!_bfd_sess_valid(bsp))
495 return;
496
497 if (bsp->lastev == BSE_INSTALL) {
498 bsp->args.command = bsp->installed ? ZEBRA_BFD_DEST_UPDATE
499 : ZEBRA_BFD_DEST_REGISTER;
500 } else
501 bsp->args.command = ZEBRA_BFD_DEST_DEREGISTER;
502
503 /* If not installed and asked for uninstall, do nothing. */
504 if (!bsp->installed && bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER)
505 return;
506
507 rv = zclient_bfd_command(bsglobal.zc, &bsp->args);
508 /* Command was sent successfully. */
509 if (rv == 0) {
510 /* Update installation status. */
511 if (bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER)
512 bsp->installed = false;
513 else if (bsp->args.command == ZEBRA_BFD_DEST_REGISTER)
514 bsp->installed = true;
515 } else {
516 struct ipaddr src, dst;
517
518 src.ipa_type = bsp->args.family;
519 src.ipaddr_v6 = bsp->args.src;
520 dst.ipa_type = bsp->args.family;
521 dst.ipaddr_v6 = bsp->args.dst;
522
523 zlog_err(
524 "%s: BFD session %pIA -> %pIA interface %s VRF %s(%u) was not %s",
525 __func__, &src, &dst,
526 bsp->args.ifnamelen ? bsp->args.ifname : "*",
527 vrf_id_to_name(bsp->args.vrf_id), bsp->args.vrf_id,
528 bsp->lastev == BSE_INSTALL ? "installed"
529 : "uninstalled");
530 }
531 }
532
533 static void _bfd_sess_remove(struct bfd_session_params *bsp)
534 {
535 /* Cancel any pending installation request. */
536 THREAD_OFF(bsp->installev);
537
538 /* Not installed, nothing to do. */
539 if (!bsp->installed)
540 return;
541
542 /* Send request to remove any session. */
543 bsp->lastev = BSE_UNINSTALL;
544 thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
545 }
546
547 void bfd_sess_free(struct bfd_session_params **bsp)
548 {
549 if (*bsp == NULL)
550 return;
551
552 /* Remove any installed session. */
553 _bfd_sess_remove(*bsp);
554
555 /* Remove from global list. */
556 TAILQ_REMOVE(&bsglobal.bsplist, (*bsp), entry);
557
558 bfd_source_cache_put(*bsp);
559
560 /* Free the memory and point to NULL. */
561 XFREE(MTYPE_BFD_INFO, (*bsp));
562 }
563
564 static bool bfd_sess_address_changed(const struct bfd_session_params *bsp,
565 uint32_t family,
566 const struct in6_addr *src,
567 const struct in6_addr *dst)
568 {
569 size_t addrlen;
570
571 if (bsp->args.family != family)
572 return true;
573
574 addrlen = (family == AF_INET) ? sizeof(struct in_addr)
575 : sizeof(struct in6_addr);
576 if ((src == NULL && memcmp(&bsp->args.src, &i6a_zero, addrlen))
577 || (src && memcmp(src, &bsp->args.src, addrlen))
578 || memcmp(dst, &bsp->args.dst, addrlen))
579 return true;
580
581 return false;
582 }
583
584 void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp,
585 const struct in_addr *src,
586 const struct in_addr *dst)
587 {
588 if (!bfd_sess_address_changed(bsp, AF_INET, (struct in6_addr *)src,
589 (struct in6_addr *)dst))
590 return;
591
592 /* If already installed, remove the old setting. */
593 _bfd_sess_remove(bsp);
594 /* Address changed so we must reapply auto source. */
595 bfd_source_cache_put(bsp);
596
597 bsp->args.family = AF_INET;
598
599 /* Clean memory, set zero value and avoid static analyser warnings. */
600 memset(&bsp->args.src, 0, sizeof(bsp->args.src));
601 memset(&bsp->args.dst, 0, sizeof(bsp->args.dst));
602
603 /* Copy the equivalent of IPv4 to arguments structure. */
604 if (src)
605 memcpy(&bsp->args.src, src, sizeof(struct in_addr));
606
607 assert(dst);
608 memcpy(&bsp->args.dst, dst, sizeof(struct in_addr));
609
610 if (bsp->auto_source)
611 bfd_source_cache_get(bsp);
612 }
613
614 void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp,
615 const struct in6_addr *src,
616 const struct in6_addr *dst)
617 {
618 if (!bfd_sess_address_changed(bsp, AF_INET6, src, dst))
619 return;
620
621 /* If already installed, remove the old setting. */
622 _bfd_sess_remove(bsp);
623 /* Address changed so we must reapply auto source. */
624 bfd_source_cache_put(bsp);
625
626 bsp->args.family = AF_INET6;
627
628 /* Clean memory, set zero value and avoid static analyser warnings. */
629 memset(&bsp->args.src, 0, sizeof(bsp->args.src));
630
631 if (src)
632 bsp->args.src = *src;
633
634 assert(dst);
635 bsp->args.dst = *dst;
636
637 if (bsp->auto_source)
638 bfd_source_cache_get(bsp);
639 }
640
641 void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname)
642 {
643 if ((ifname == NULL && bsp->args.ifnamelen == 0)
644 || (ifname && strcmp(bsp->args.ifname, ifname) == 0))
645 return;
646
647 /* If already installed, remove the old setting. */
648 _bfd_sess_remove(bsp);
649
650 if (ifname == NULL) {
651 bsp->args.ifname[0] = 0;
652 bsp->args.ifnamelen = 0;
653 return;
654 }
655
656 if (strlcpy(bsp->args.ifname, ifname, sizeof(bsp->args.ifname))
657 > sizeof(bsp->args.ifname))
658 zlog_warn("%s: interface name truncated: %s", __func__, ifname);
659
660 bsp->args.ifnamelen = strlen(bsp->args.ifname);
661 }
662
663 void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile)
664 {
665 if (profile == NULL) {
666 bsp->args.profile[0] = 0;
667 bsp->args.profilelen = 0;
668 return;
669 }
670
671 if (strlcpy(bsp->args.profile, profile, sizeof(bsp->args.profile))
672 > sizeof(bsp->args.profile))
673 zlog_warn("%s: profile name truncated: %s", __func__, profile);
674
675 bsp->args.profilelen = strlen(bsp->args.profile);
676 }
677
678 void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id)
679 {
680 if (bsp->args.vrf_id == vrf_id)
681 return;
682
683 /* If already installed, remove the old setting. */
684 _bfd_sess_remove(bsp);
685 /* Address changed so we must reapply auto source. */
686 bfd_source_cache_put(bsp);
687
688 bsp->args.vrf_id = vrf_id;
689
690 if (bsp->auto_source)
691 bfd_source_cache_get(bsp);
692 }
693
694 void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops)
695 {
696 if (bsp->args.hops == hops)
697 return;
698
699 /* If already installed, remove the old setting. */
700 _bfd_sess_remove(bsp);
701
702 bsp->args.hops = hops;
703 bsp->args.mhop = (hops > 1);
704 }
705
706
707 void bfd_sess_set_cbit(struct bfd_session_params *bsp, bool enable)
708 {
709 bsp->args.cbit = enable;
710 }
711
712 void bfd_sess_set_timers(struct bfd_session_params *bsp,
713 uint8_t detection_multiplier, uint32_t min_rx,
714 uint32_t min_tx)
715 {
716 bsp->args.detection_multiplier = detection_multiplier;
717 bsp->args.min_rx = min_rx;
718 bsp->args.min_tx = min_tx;
719 }
720
721 void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable)
722 {
723 if (bsp->auto_source == enable)
724 return;
725
726 bsp->auto_source = enable;
727 if (enable)
728 bfd_source_cache_get(bsp);
729 else
730 bfd_source_cache_put(bsp);
731 }
732
733 void bfd_sess_install(struct bfd_session_params *bsp)
734 {
735 bsp->lastev = BSE_INSTALL;
736 thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
737 }
738
739 void bfd_sess_uninstall(struct bfd_session_params *bsp)
740 {
741 bsp->lastev = BSE_UNINSTALL;
742 thread_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev);
743 }
744
745 enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp)
746 {
747 return bsp->bss.state;
748 }
749
750 uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp)
751 {
752 return bsp->args.hops;
753 }
754
755 const char *bfd_sess_profile(const struct bfd_session_params *bsp)
756 {
757 return bsp->args.profilelen ? bsp->args.profile : NULL;
758 }
759
760 void bfd_sess_addresses(const struct bfd_session_params *bsp, int *family,
761 struct in6_addr *src, struct in6_addr *dst)
762 {
763 *family = bsp->args.family;
764 if (src)
765 *src = bsp->args.src;
766 if (dst)
767 *dst = bsp->args.dst;
768 }
769
770 const char *bfd_sess_interface(const struct bfd_session_params *bsp)
771 {
772 if (bsp->args.ifnamelen)
773 return bsp->args.ifname;
774
775 return NULL;
776 }
777
778 const char *bfd_sess_vrf(const struct bfd_session_params *bsp)
779 {
780 return vrf_id_to_name(bsp->args.vrf_id);
781 }
782
783 vrf_id_t bfd_sess_vrf_id(const struct bfd_session_params *bsp)
784 {
785 return bsp->args.vrf_id;
786 }
787
788 bool bfd_sess_cbit(const struct bfd_session_params *bsp)
789 {
790 return bsp->args.cbit;
791 }
792
793 void bfd_sess_timers(const struct bfd_session_params *bsp,
794 uint8_t *detection_multiplier, uint32_t *min_rx,
795 uint32_t *min_tx)
796 {
797 *detection_multiplier = bsp->args.detection_multiplier;
798 *min_rx = bsp->args.min_rx;
799 *min_tx = bsp->args.min_tx;
800 }
801
802 bool bfd_sess_auto_source(const struct bfd_session_params *bsp)
803 {
804 return bsp->auto_source;
805 }
806
807 void bfd_sess_show(struct vty *vty, struct json_object *json,
808 struct bfd_session_params *bsp)
809 {
810 json_object *json_bfd = NULL;
811 char time_buf[64];
812
813 if (!bsp)
814 return;
815
816 /* Show type. */
817 if (json) {
818 json_bfd = json_object_new_object();
819 if (bsp->args.mhop)
820 json_object_string_add(json_bfd, "type", "multi hop");
821 else
822 json_object_string_add(json_bfd, "type", "single hop");
823 } else
824 vty_out(vty, " BFD: Type: %s\n",
825 bsp->args.mhop ? "multi hop" : "single hop");
826
827 /* Show configuration. */
828 if (json) {
829 json_object_int_add(json_bfd, "detectMultiplier",
830 bsp->args.detection_multiplier);
831 json_object_int_add(json_bfd, "rxMinInterval",
832 bsp->args.min_rx);
833 json_object_int_add(json_bfd, "txMinInterval",
834 bsp->args.min_tx);
835 } else {
836 vty_out(vty,
837 " Detect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n",
838 bsp->args.detection_multiplier, bsp->args.min_rx,
839 bsp->args.min_tx);
840 }
841
842 bfd_last_update(bsp->bss.last_event, time_buf, sizeof(time_buf));
843 if (json) {
844 json_object_string_add(json_bfd, "status",
845 bfd_get_status_str(bsp->bss.state));
846 json_object_string_add(json_bfd, "lastUpdate", time_buf);
847 } else
848 vty_out(vty, " Status: %s, Last update: %s\n",
849 bfd_get_status_str(bsp->bss.state), time_buf);
850
851 if (json)
852 json_object_object_add(json, "peerBfdInfo", json_bfd);
853 else
854 vty_out(vty, "\n");
855 }
856
857 /*
858 * Zebra communication related.
859 */
860
861 /**
862 * Callback for reinstallation of all registered BFD sessions.
863 *
864 * Use this as `zclient` `bfd_dest_replay` callback.
865 */
866 int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS)
867 {
868 struct bfd_session_params *bsp;
869
870 if (!zclient->bfd_integration)
871 return 0;
872
873 /* Do nothing when shutting down. */
874 if (bsglobal.shutting_down)
875 return 0;
876
877 if (bsglobal.debugging)
878 zlog_debug("%s: sending all sessions registered", __func__);
879
880 /* Send the client registration */
881 bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id);
882
883 /* Replay all activated peers. */
884 TAILQ_FOREACH (bsp, &bsglobal.bsplist, entry) {
885 /* Skip not installed sessions. */
886 if (!bsp->installed)
887 continue;
888
889 /* We are reconnecting, so we must send installation. */
890 bsp->installed = false;
891
892 /* Cancel any pending installation request. */
893 THREAD_OFF(bsp->installev);
894
895 /* Ask for installation. */
896 bsp->lastev = BSE_INSTALL;
897 thread_execute(bsglobal.tm, _bfd_sess_send, bsp, 0);
898 }
899
900 return 0;
901 }
902
903 int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS)
904 {
905 struct bfd_session_params *bsp, *bspn;
906 size_t sessions_updated = 0;
907 struct interface *ifp;
908 int remote_cbit = false;
909 int state = BFD_STATUS_UNKNOWN;
910 time_t now;
911 size_t addrlen;
912 struct prefix dp;
913 struct prefix sp;
914 char ifstr[128], cbitstr[32];
915
916 if (!zclient->bfd_integration)
917 return 0;
918
919 /* Do nothing when shutting down. */
920 if (bsglobal.shutting_down)
921 return 0;
922
923 ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &state, &remote_cbit,
924 vrf_id);
925 /*
926 * When interface lookup fails or an invalid stream is read, we must
927 * not proceed otherwise it will trigger an assertion while checking
928 * family type below.
929 */
930 if (dp.family == 0 || sp.family == 0)
931 return 0;
932
933 if (bsglobal.debugging) {
934 ifstr[0] = 0;
935 if (ifp)
936 snprintf(ifstr, sizeof(ifstr), " (interface %s)",
937 ifp->name);
938
939 snprintf(cbitstr, sizeof(cbitstr), " (CPI bit %s)",
940 remote_cbit ? "yes" : "no");
941
942 zlog_debug("%s: %pFX -> %pFX%s VRF %s(%u)%s: %s", __func__, &sp,
943 &dp, ifstr, vrf_id_to_name(vrf_id), vrf_id, cbitstr,
944 bfd_get_status_str(state));
945 }
946
947 switch (dp.family) {
948 case AF_INET:
949 addrlen = sizeof(struct in_addr);
950 break;
951 case AF_INET6:
952 addrlen = sizeof(struct in6_addr);
953 break;
954
955 default:
956 /* Unexpected value. */
957 assert(0);
958 break;
959 }
960
961 /* Cache current time to avoid multiple monotime clock calls. */
962 now = monotime(NULL);
963
964 /* Notify all matching sessions about update. */
965 TAILQ_FOREACH_SAFE (bsp, &bsglobal.bsplist, entry, bspn) {
966 /* Skip not installed entries. */
967 if (!bsp->installed)
968 continue;
969 /* Skip different VRFs. */
970 if (bsp->args.vrf_id != vrf_id)
971 continue;
972 /* Skip different families. */
973 if (bsp->args.family != dp.family)
974 continue;
975 /* Skip different interface. */
976 if (bsp->args.ifnamelen && ifp
977 && strcmp(bsp->args.ifname, ifp->name) != 0)
978 continue;
979 /* Skip non matching destination addresses. */
980 if (memcmp(&bsp->args.dst, &dp.u, addrlen) != 0)
981 continue;
982 /*
983 * Source comparison test:
984 * We will only compare source if BFD daemon provided the
985 * source address and the protocol set a source address in
986 * the configuration otherwise we'll just skip it.
987 */
988 if (sp.family && memcmp(&bsp->args.src, &i6a_zero, addrlen) != 0
989 && memcmp(&sp.u, &i6a_zero, addrlen) != 0
990 && memcmp(&bsp->args.src, &sp.u, addrlen) != 0)
991 continue;
992 /* No session state change. */
993 if ((int)bsp->bss.state == state)
994 continue;
995
996 bsp->bss.last_event = now;
997 bsp->bss.previous_state = bsp->bss.state;
998 bsp->bss.state = state;
999 bsp->bss.remote_cbit = remote_cbit;
1000 bsp->updatecb(bsp, &bsp->bss, bsp->arg);
1001 sessions_updated++;
1002 }
1003
1004 if (bsglobal.debugging)
1005 zlog_debug("%s: sessions updated: %zu", __func__,
1006 sessions_updated);
1007
1008 return 0;
1009 }
1010
1011 /**
1012 * Frees all allocated resources and stops any activity.
1013 *
1014 * Must be called after every BFD session has been successfully
1015 * unconfigured otherwise this function will `free()` any available
1016 * session causing existing pointers to dangle.
1017 *
1018 * This is just a comment, in practice it will be called by the FRR
1019 * library late finish hook. \see `bfd_protocol_integration_init`.
1020 */
1021 static int bfd_protocol_integration_finish(void)
1022 {
1023 if (bsglobal.zc == NULL)
1024 return 0;
1025
1026 while (!TAILQ_EMPTY(&bsglobal.bsplist)) {
1027 struct bfd_session_params *session =
1028 TAILQ_FIRST(&bsglobal.bsplist);
1029 bfd_sess_free(&session);
1030 }
1031
1032 /*
1033 * BFD source cache is linked to sessions, if all sessions are gone
1034 * then the source cache must be empty.
1035 */
1036 if (!SLIST_EMPTY(&bsglobal.source_list))
1037 zlog_warn("BFD integration source cache not empty");
1038
1039 return 0;
1040 }
1041
1042 void bfd_protocol_integration_init(struct zclient *zc, struct thread_master *tm)
1043 {
1044 /* Initialize data structure. */
1045 TAILQ_INIT(&bsglobal.bsplist);
1046 SLIST_INIT(&bsglobal.source_list);
1047
1048 /* Copy pointers. */
1049 bsglobal.zc = zc;
1050 bsglobal.tm = tm;
1051
1052 /* Enable BFD callbacks. */
1053 zc->bfd_integration = true;
1054
1055 /* Send the client registration */
1056 bfd_client_sendmsg(zc, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT);
1057
1058 hook_register(frr_fini, bfd_protocol_integration_finish);
1059 }
1060
1061 void bfd_protocol_integration_set_debug(bool enable)
1062 {
1063 bsglobal.debugging = enable;
1064 }
1065
1066 void bfd_protocol_integration_set_shutdown(bool enable)
1067 {
1068 bsglobal.shutting_down = enable;
1069 }
1070
1071 bool bfd_protocol_integration_debug(void)
1072 {
1073 return bsglobal.debugging;
1074 }
1075
1076 bool bfd_protocol_integration_shutting_down(void)
1077 {
1078 return bsglobal.shutting_down;
1079 }
1080
1081 /*
1082 * BFD automatic source selection
1083 *
1084 * This feature will use the next hop tracking (NHT) provided by zebra
1085 * to find out the source address by looking at the output interface.
1086 *
1087 * When the interface address / routing table change we'll be notified
1088 * and be able to update the source address accordingly.
1089 *
1090 * <daemon> zebra
1091 * |
1092 * +-----------------+
1093 * | BFD session set |
1094 * | to auto source |
1095 * +-----------------+
1096 * |
1097 * \ +-----------------+
1098 * --------------> | Resolves |
1099 * | destination |
1100 * | address |
1101 * +-----------------+
1102 * |
1103 * +-----------------+ /
1104 * | Sets resolved | <----------
1105 * | source address |
1106 * +-----------------+
1107 */
1108 static bool
1109 bfd_source_cache_session_match(const struct bfd_source_cache *source,
1110 const struct bfd_session_params *session)
1111 {
1112 const struct in_addr *address;
1113 const struct in6_addr *address_v6;
1114
1115 if (session->args.vrf_id != source->vrf_id)
1116 return false;
1117 if (session->args.family != source->address.family)
1118 return false;
1119
1120 switch (session->args.family) {
1121 case AF_INET:
1122 address = (const struct in_addr *)&session->args.dst;
1123 if (address->s_addr != source->address.u.prefix4.s_addr)
1124 return false;
1125 break;
1126 case AF_INET6:
1127 address_v6 = &session->args.dst;
1128 if (memcmp(address_v6, &source->address.u.prefix6,
1129 sizeof(struct in6_addr)))
1130 return false;
1131 break;
1132 default:
1133 return false;
1134 }
1135
1136 return true;
1137 }
1138
1139 static struct bfd_source_cache *
1140 bfd_source_cache_find(vrf_id_t vrf_id, const struct prefix *prefix)
1141 {
1142 struct bfd_source_cache *source;
1143
1144 SLIST_FOREACH (source, &bsglobal.source_list, entry) {
1145 if (source->vrf_id != vrf_id)
1146 continue;
1147 if (!prefix_same(&source->address, prefix))
1148 continue;
1149
1150 return source;
1151 }
1152
1153 return NULL;
1154 }
1155
1156 static void bfd_source_cache_get(struct bfd_session_params *session)
1157 {
1158 struct bfd_source_cache *source;
1159 struct prefix target = {};
1160
1161 switch (session->args.family) {
1162 case AF_INET:
1163 target.family = AF_INET;
1164 target.prefixlen = IPV4_MAX_BITLEN;
1165 memcpy(&target.u.prefix4, &session->args.dst,
1166 sizeof(struct in_addr));
1167 break;
1168 case AF_INET6:
1169 target.family = AF_INET6;
1170 target.prefixlen = IPV6_MAX_BITLEN;
1171 memcpy(&target.u.prefix6, &session->args.dst,
1172 sizeof(struct in6_addr));
1173 break;
1174 default:
1175 return;
1176 }
1177
1178 source = bfd_source_cache_find(session->args.vrf_id, &target);
1179 if (source) {
1180 if (session->source_cache == source)
1181 return;
1182
1183 bfd_source_cache_put(session);
1184 session->source_cache = source;
1185 source->refcount++;
1186 return;
1187 }
1188
1189 source = XCALLOC(MTYPE_BFD_SOURCE, sizeof(*source));
1190 prefix_copy(&source->address, &target);
1191 source->vrf_id = session->args.vrf_id;
1192 SLIST_INSERT_HEAD(&bsglobal.source_list, source, entry);
1193
1194 bfd_source_cache_put(session);
1195 session->source_cache = source;
1196 source->refcount = 1;
1197
1198 return;
1199 }
1200
1201 static void bfd_source_cache_put(struct bfd_session_params *session)
1202 {
1203 if (session->source_cache == NULL)
1204 return;
1205
1206 session->source_cache->refcount--;
1207 if (session->source_cache->refcount > 0) {
1208 session->source_cache = NULL;
1209 return;
1210 }
1211
1212 SLIST_REMOVE(&bsglobal.source_list, session->source_cache,
1213 bfd_source_cache, entry);
1214 XFREE(MTYPE_BFD_SOURCE, session->source_cache);
1215 }
1216
1217 /** Updates BFD running session if source address has changed. */
1218 static void
1219 bfd_source_cache_update_session(const struct bfd_source_cache *source,
1220 struct bfd_session_params *session)
1221 {
1222 const struct in_addr *address;
1223 const struct in6_addr *address_v6;
1224
1225 switch (session->args.family) {
1226 case AF_INET:
1227 address = (const struct in_addr *)&session->args.src;
1228 if (memcmp(address, &source->source.u.prefix4,
1229 sizeof(struct in_addr)) == 0)
1230 return;
1231
1232 _bfd_sess_remove(session);
1233 memcpy(&session->args.src, &source->source.u.prefix4,
1234 sizeof(struct in_addr));
1235 break;
1236 case AF_INET6:
1237 address_v6 = &session->args.src;
1238 if (memcmp(address_v6, &source->source.u.prefix6,
1239 sizeof(struct in6_addr)) == 0)
1240 return;
1241
1242 _bfd_sess_remove(session);
1243 memcpy(&session->args.src, &source->source.u.prefix6,
1244 sizeof(struct in6_addr));
1245 break;
1246 default:
1247 return;
1248 }
1249
1250 bfd_sess_install(session);
1251 }
1252
1253 static void
1254 bfd_source_cache_update_sessions(const struct bfd_source_cache *source)
1255 {
1256 struct bfd_session_params *session;
1257
1258 if (!source->valid)
1259 return;
1260
1261 TAILQ_FOREACH (session, &bsglobal.bsplist, entry) {
1262 if (!session->auto_source)
1263 continue;
1264 if (!bfd_source_cache_session_match(source, session))
1265 continue;
1266
1267 bfd_source_cache_update_session(source, session);
1268 }
1269 }
1270
1271 /**
1272 * Try to translate next hop information into source address.
1273 *
1274 * \returns `true` if source changed otherwise `false`.
1275 */
1276 static bool bfd_source_cache_update(struct bfd_source_cache *source,
1277 const struct zapi_route *route)
1278 {
1279 size_t nh_index;
1280
1281 for (nh_index = 0; nh_index < route->nexthop_num; nh_index++) {
1282 const struct zapi_nexthop *nh = &route->nexthops[nh_index];
1283 const struct interface *interface;
1284 const struct connected *connected;
1285 const struct listnode *node;
1286
1287 interface = if_lookup_by_index(nh->ifindex, nh->vrf_id);
1288 if (interface == NULL) {
1289 zlog_err("next hop interface not found (index %d)",
1290 nh->ifindex);
1291 continue;
1292 }
1293
1294 for (ALL_LIST_ELEMENTS_RO(interface->connected, node,
1295 connected)) {
1296 if (source->address.family !=
1297 connected->address->family)
1298 continue;
1299 if (prefix_same(connected->address, &source->source))
1300 return false;
1301 /*
1302 * Skip link-local as it is only useful for single hop
1303 * and in that case no source is specified usually.
1304 */
1305 if (source->address.family == AF_INET6 &&
1306 IN6_IS_ADDR_LINKLOCAL(
1307 &connected->address->u.prefix6))
1308 continue;
1309
1310 prefix_copy(&source->source, connected->address);
1311 source->valid = true;
1312 return true;
1313 }
1314 }
1315
1316 memset(&source->source, 0, sizeof(source->source));
1317 source->valid = false;
1318 return false;
1319 }
1320
1321 int bfd_nht_update(const struct prefix *match, const struct zapi_route *route)
1322 {
1323 struct bfd_source_cache *source;
1324
1325 if (bsglobal.debugging)
1326 zlog_debug("BFD NHT update for %pFX", &route->prefix);
1327
1328 SLIST_FOREACH (source, &bsglobal.source_list, entry) {
1329 if (source->vrf_id != route->vrf_id)
1330 continue;
1331 if (!prefix_same(match, &source->address))
1332 continue;
1333 if (bfd_source_cache_update(source, route))
1334 bfd_source_cache_update_sessions(source);
1335 }
1336
1337 return 0;
1338 }