1 /*********************************************************************
2 * Copyright 2013 Cumulus Networks, LLC. All rights reserved.
3 * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 * bfd.c: implements the BFD protocol.
23 * Shrijeet Mukherjee [shm@cumulusnetworks.com]
24 * Kanna Rajagopal [kanna@cumulusnetworks.com]
25 * Radhika Mahankali [Radhika@cumulusnetworks.com]
30 #include "lib/jhash.h"
34 DEFINE_QOBJ_TYPE(bfd_session
);
39 static struct bfd_session
*bs_peer_waiting_find(struct bfd_peer_cfg
*bpc
);
41 static uint32_t ptm_bfd_gen_ID(void);
42 static void ptm_bfd_echo_xmt_TO(struct bfd_session
*bfd
);
43 static void bfd_session_free(struct bfd_session
*bs
);
44 static struct bfd_session
*bfd_session_new(void);
45 static struct bfd_session
*bfd_find_disc(struct sockaddr_any
*sa
,
47 static int bfd_session_update(struct bfd_session
*bs
, struct bfd_peer_cfg
*bpc
);
48 static const char *get_diag_str(int diag
);
50 static void bs_admin_down_handler(struct bfd_session
*bs
, int nstate
);
51 static void bs_down_handler(struct bfd_session
*bs
, int nstate
);
52 static void bs_init_handler(struct bfd_session
*bs
, int nstate
);
53 static void bs_up_handler(struct bfd_session
*bs
, int nstate
);
59 static struct bfd_session
*bs_peer_waiting_find(struct bfd_peer_cfg
*bpc
)
61 struct bfd_session_observer
*bso
;
62 struct bfd_session
*bs
= NULL
;
63 bool is_shop
, is_ipv4
;
65 TAILQ_FOREACH(bso
, &bglobal
.bg_obslist
, bso_entry
) {
68 is_shop
= !BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
);
69 is_ipv4
= !BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_IPV6
);
70 /* Quick checks first. */
71 if (is_shop
!= (!bpc
->bpc_mhop
))
73 if (is_ipv4
!= bpc
->bpc_ipv4
)
77 * Slow lookup without hash because we don't have all
81 if (strcmp(bs
->ifname
, bpc
->bpc_localif
))
83 if (memcmp(&bs
->shop
.peer
, &bpc
->bpc_peer
,
84 sizeof(bs
->shop
.peer
)))
90 if (strcmp(bs
->vrfname
, bpc
->bpc_vrfname
))
92 if (memcmp(&bs
->mhop
.peer
, &bpc
->bpc_peer
,
93 sizeof(bs
->mhop
.peer
)))
95 if (memcmp(&bs
->mhop
.local
, &bpc
->bpc_local
,
96 sizeof(bs
->mhop
.local
)))
107 struct bfd_session
*bs_peer_find(struct bfd_peer_cfg
*bpc
)
109 struct bfd_session
*bs
;
110 struct peer_label
*pl
;
111 struct interface
*ifp
;
113 struct bfd_mhop_key mhop
;
114 struct bfd_shop_key shop
;
116 /* Try to find label first. */
117 if (bpc
->bpc_has_label
) {
118 pl
= pl_find(bpc
->bpc_label
);
125 /* Otherwise fallback to peer/local hash lookup. */
127 memset(&mhop
, 0, sizeof(mhop
));
128 mhop
.peer
= bpc
->bpc_peer
;
129 mhop
.local
= bpc
->bpc_local
;
130 if (bpc
->bpc_has_vrfname
) {
131 vrf
= vrf_lookup_by_name(bpc
->bpc_vrfname
);
135 mhop
.vrfid
= vrf
->vrf_id
;
138 bs
= bfd_mhop_lookup(mhop
);
140 memset(&shop
, 0, sizeof(shop
));
141 shop
.peer
= bpc
->bpc_peer
;
142 if (bpc
->bpc_has_localif
) {
143 ifp
= if_lookup_by_name_all_vrf(bpc
->bpc_localif
);
147 shop
.ifindex
= ifp
->ifindex
;
150 bs
= bfd_shop_lookup(shop
);
156 /* Search for entries that are incomplete. */
157 return bs_peer_waiting_find(bpc
);
161 * Starts a disabled BFD session.
163 * A session is disabled when the specified interface/VRF doesn't exist
164 * yet. It might happen on FRR boot or with virtual interfaces.
166 int bfd_session_enable(struct bfd_session
*bs
)
168 struct sockaddr_in6
*sin6
;
169 struct interface
*ifp
= NULL
;
170 struct vrf
*vrf
= NULL
;
174 * If the interface or VRF doesn't exist, then we must register
175 * the session but delay its start.
177 if (bs
->ifname
[0] != 0) {
178 ifp
= if_lookup_by_name_all_vrf(bs
->ifname
);
181 "session-enable: specified interface doesn't exists.");
185 vrf
= vrf_lookup_by_id(ifp
->vrf_id
);
187 log_error("session-enable: specified VRF doesn't exists.");
192 if (bs
->vrfname
[0] != 0) {
193 vrf
= vrf_lookup_by_name(bs
->vrfname
);
195 log_error("session-enable: specified VRF doesn't exists.");
200 /* Assign interface/VRF pointers. */
203 bs
->vrf
= vrf_lookup_by_id(VRF_DEFAULT
);
205 if (bs
->ifname
[0] != 0 &&
206 BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
) == 0)
209 /* Set the IPv6 scope id for link-local addresses. */
210 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_IPV6
)) {
211 sin6
= &bs
->mhop
.peer
.sa_sin6
;
212 if (IN6_IS_ADDR_LINKLOCAL(&sin6
->sin6_addr
))
213 sin6
->sin6_scope_id
= bs
->ifp
!= NULL
217 sin6
= &bs
->mhop
.local
.sa_sin6
;
218 if (IN6_IS_ADDR_LINKLOCAL(&sin6
->sin6_addr
))
219 sin6
->sin6_scope_id
= bs
->ifp
!= NULL
223 bs
->local_ip
.sa_sin6
= *sin6
;
224 bs
->local_address
.sa_sin6
= *sin6
;
228 * Get socket for transmitting control packets. Note that if we
229 * could use the destination port (3784) for the source
230 * port we wouldn't need a socket per session.
232 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_IPV6
) == 0) {
233 psock
= bp_peer_socket(bs
);
237 psock
= bp_peer_socketv6(bs
);
243 * We've got a valid socket, lets start the timers and the
247 bfd_recvtimer_update(bs
);
248 ptm_bfd_start_xmt_timer(bs
, false);
250 /* Registrate session into data structures. */
251 bs
->discrs
.my_discr
= ptm_bfd_gen_ID();
253 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
)) {
255 bs
->mhop
.vrfid
= vrf
->vrf_id
;
257 bs
->mhop
.vrfid
= VRF_DEFAULT
;
262 bs
->shop
.ifindex
= ifp
->ifindex
;
264 bs
->shop
.ifindex
= IFINDEX_INTERNAL
;
273 * Disabled a running BFD session.
275 * A session is disabled when the specified interface/VRF gets removed
276 * (e.g. virtual interfaces).
278 void bfd_session_disable(struct bfd_session
*bs
)
280 /* Free up socket resources. */
281 if (bs
->sock
!= -1) {
286 /* Disable all timers. */
287 bfd_recvtimer_delete(bs
);
288 bfd_echo_recvtimer_delete(bs
);
289 bfd_xmttimer_delete(bs
);
290 bfd_echo_xmttimer_delete(bs
);
292 /* Unregister session from hashes to avoid unwanted activation. */
293 bfd_id_delete(bs
->discrs
.my_discr
);
294 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
))
295 bfd_mhop_delete(bs
->mhop
);
297 bfd_shop_delete(bs
->shop
);
300 static uint32_t ptm_bfd_gen_ID(void)
305 * RFC 5880, Section 6.8.1. recommends that we should generate
306 * random session identification numbers.
309 session_id
= ((random() << 16) & 0xFFFF0000)
310 | (random() & 0x0000FFFF);
311 } while (session_id
== 0 || bfd_id_lookup(session_id
) != NULL
);
316 void ptm_bfd_start_xmt_timer(struct bfd_session
*bfd
, bool is_echo
)
318 uint64_t jitter
, xmt_TO
;
321 xmt_TO
= is_echo
? bfd
->echo_xmt_TO
: bfd
->xmt_TO
;
324 * From section 6.5.2: trasmit interval should be randomly jittered
326 * 75% and 100% of nominal value, unless detect_mult is 1, then should
328 * between 75% and 90%.
330 maxpercent
= (bfd
->detect_mult
== 1) ? 16 : 26;
331 jitter
= (xmt_TO
* (75 + (random() % maxpercent
))) / 100;
332 /* XXX remove that division above */
335 bfd_echo_xmttimer_update(bfd
, jitter
);
337 bfd_xmttimer_update(bfd
, jitter
);
340 static void ptm_bfd_echo_xmt_TO(struct bfd_session
*bfd
)
342 /* Send the scheduled echo packet */
343 ptm_bfd_echo_snd(bfd
);
345 /* Restart the timer for next time */
346 ptm_bfd_start_xmt_timer(bfd
, true);
349 void ptm_bfd_xmt_TO(struct bfd_session
*bfd
, int fbit
)
351 /* Send the scheduled control packet */
352 ptm_bfd_snd(bfd
, fbit
);
354 /* Restart the timer for next time */
355 ptm_bfd_start_xmt_timer(bfd
, false);
358 void ptm_bfd_echo_stop(struct bfd_session
*bfd
)
360 bfd
->echo_xmt_TO
= 0;
361 bfd
->echo_detect_TO
= 0;
362 BFD_UNSET_FLAG(bfd
->flags
, BFD_SESS_FLAG_ECHO_ACTIVE
);
364 bfd_echo_xmttimer_delete(bfd
);
365 bfd_echo_recvtimer_delete(bfd
);
368 void ptm_bfd_echo_start(struct bfd_session
*bfd
)
370 bfd
->echo_detect_TO
= (bfd
->remote_detect_mult
* bfd
->echo_xmt_TO
);
371 if (bfd
->echo_detect_TO
> 0)
372 ptm_bfd_echo_xmt_TO(bfd
);
375 void ptm_bfd_ses_up(struct bfd_session
*bfd
)
377 int old_state
= bfd
->ses_state
;
380 bfd
->ses_state
= PTM_BFD_UP
;
381 monotime(&bfd
->uptime
);
383 /* Connection is up, lets negotiate timers. */
384 bfd_set_polling(bfd
);
386 /* Start sending control packets with poll bit immediately. */
391 if (old_state
!= bfd
->ses_state
) {
392 bfd
->stats
.session_up
++;
393 log_info("state-change: [%s] %s -> %s", bs_to_string(bfd
),
394 state_list
[old_state
].str
,
395 state_list
[bfd
->ses_state
].str
);
399 void ptm_bfd_ses_dn(struct bfd_session
*bfd
, uint8_t diag
)
401 int old_state
= bfd
->ses_state
;
403 bfd
->local_diag
= diag
;
404 bfd
->discrs
.remote_discr
= 0;
405 bfd
->ses_state
= PTM_BFD_DOWN
;
407 bfd
->demand_mode
= 0;
408 monotime(&bfd
->downtime
);
412 /* Slow down the control packets, the connection is down. */
413 bs_set_slow_timers(bfd
);
415 /* only signal clients when going from up->down state */
416 if (old_state
== PTM_BFD_UP
)
419 /* Stop echo packet transmission if they are active */
420 if (BFD_CHECK_FLAG(bfd
->flags
, BFD_SESS_FLAG_ECHO_ACTIVE
))
421 ptm_bfd_echo_stop(bfd
);
423 if (old_state
!= bfd
->ses_state
) {
424 bfd
->stats
.session_down
++;
425 log_info("state-change: [%s] %s -> %s reason:%s",
426 bs_to_string(bfd
), state_list
[old_state
].str
,
427 state_list
[bfd
->ses_state
].str
,
428 get_diag_str(bfd
->local_diag
));
432 static struct bfd_session
*bfd_find_disc(struct sockaddr_any
*sa
,
435 struct bfd_session
*bs
;
437 bs
= bfd_id_lookup(ldisc
);
441 /* Remove unused fields. */
442 switch (sa
->sa_sin
.sin_family
) {
444 sa
->sa_sin
.sin_port
= 0;
445 if (memcmp(sa
, &bs
->shop
.peer
, sizeof(sa
->sa_sin
)) == 0)
449 sa
->sa_sin6
.sin6_port
= 0;
450 if (memcmp(sa
, &bs
->shop
.peer
, sizeof(sa
->sa_sin6
)) == 0)
458 struct bfd_session
*ptm_bfd_sess_find(struct bfd_pkt
*cp
,
459 struct sockaddr_any
*peer
,
460 struct sockaddr_any
*local
,
461 ifindex_t ifindex
, vrf_id_t vrfid
,
464 struct bfd_session
*l_bfd
= NULL
;
465 struct bfd_mhop_key mhop
;
466 struct bfd_shop_key shop
;
468 /* Find our session using the ID signaled by the remote end. */
469 if (cp
->discrs
.remote_discr
)
470 return bfd_find_disc(peer
, ntohl(cp
->discrs
.remote_discr
));
472 /* Search for session without using discriminator. */
474 memset(&mhop
, 0, sizeof(mhop
));
479 l_bfd
= bfd_mhop_lookup(mhop
);
481 memset(&shop
, 0, sizeof(shop
));
483 shop
.ifindex
= ifindex
;
485 l_bfd
= bfd_shop_lookup(shop
);
488 /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */
492 int bfd_xmt_cb(struct thread
*t
)
494 struct bfd_session
*bs
= THREAD_ARG(t
);
496 ptm_bfd_xmt_TO(bs
, 0);
501 int bfd_echo_xmt_cb(struct thread
*t
)
503 struct bfd_session
*bs
= THREAD_ARG(t
);
505 if (bs
->echo_xmt_TO
> 0)
506 ptm_bfd_echo_xmt_TO(bs
);
511 /* Was ptm_bfd_detect_TO() */
512 int bfd_recvtimer_cb(struct thread
*t
)
514 struct bfd_session
*bs
= THREAD_ARG(t
);
516 switch (bs
->ses_state
) {
519 ptm_bfd_ses_dn(bs
, BD_CONTROL_EXPIRED
);
520 bfd_recvtimer_update(bs
);
524 /* Second detect time expiration, zero remote discr (section
527 bs
->discrs
.remote_discr
= 0;
534 /* Was ptm_bfd_echo_detect_TO() */
535 int bfd_echo_recvtimer_cb(struct thread
*t
)
537 struct bfd_session
*bs
= THREAD_ARG(t
);
539 switch (bs
->ses_state
) {
542 ptm_bfd_ses_dn(bs
, BD_ECHO_FAILED
);
549 static struct bfd_session
*bfd_session_new(void)
551 struct bfd_session
*bs
;
553 bs
= XCALLOC(MTYPE_BFDD_CONFIG
, sizeof(*bs
));
555 QOBJ_REG(bs
, bfd_session
);
557 bs
->timers
.desired_min_tx
= BFD_DEFDESIREDMINTX
;
558 bs
->timers
.required_min_rx
= BFD_DEFREQUIREDMINRX
;
559 bs
->timers
.required_min_echo
= BFD_DEF_REQ_MIN_ECHO
;
560 bs
->detect_mult
= BFD_DEFDETECTMULT
;
561 bs
->mh_ttl
= BFD_DEF_MHOP_TTL
;
562 bs
->ses_state
= PTM_BFD_DOWN
;
564 /* Initiate connection with slow timers. */
565 bs_set_slow_timers(bs
);
567 /* Initiate remote settings as well. */
568 bs
->remote_timers
= bs
->cur_timers
;
569 bs
->remote_detect_mult
= BFD_DEFDETECTMULT
;
572 monotime(&bs
->uptime
);
573 bs
->downtime
= bs
->uptime
;
578 int bfd_session_update_label(struct bfd_session
*bs
, const char *nlabel
)
580 /* New label treatment:
581 * - Check if the label is taken;
582 * - Try to allocate the memory for it and register;
584 if (bs
->pl
== NULL
) {
585 if (pl_find(nlabel
) != NULL
) {
586 /* Someone is already using it. */
590 if (pl_new(nlabel
, bs
) == NULL
)
597 * Test label change consistency:
598 * - Do nothing if it's the same label;
599 * - Check if the future label is already taken;
602 if (strcmp(nlabel
, bs
->pl
->pl_label
) == 0)
604 if (pl_find(nlabel
) != NULL
)
607 strlcpy(bs
->pl
->pl_label
, nlabel
, sizeof(bs
->pl
->pl_label
));
611 static void _bfd_session_update(struct bfd_session
*bs
,
612 struct bfd_peer_cfg
*bpc
)
615 /* Check if echo mode is already active. */
616 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO
))
619 BFD_SET_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO
);
621 /* Activate/update echo receive timeout timer. */
622 bs_echo_timer_handler(bs
);
624 /* Check if echo mode is already disabled. */
625 if (!BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO
))
628 BFD_UNSET_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO
);
629 ptm_bfd_echo_stop(bs
);
633 if (bpc
->bpc_has_txinterval
)
634 bs
->timers
.desired_min_tx
= bpc
->bpc_txinterval
* 1000;
636 if (bpc
->bpc_has_recvinterval
)
637 bs
->timers
.required_min_rx
= bpc
->bpc_recvinterval
* 1000;
639 if (bpc
->bpc_has_detectmultiplier
)
640 bs
->detect_mult
= bpc
->bpc_detectmultiplier
;
642 if (bpc
->bpc_has_echointerval
)
643 bs
->timers
.required_min_echo
= bpc
->bpc_echointerval
* 1000;
645 if (bpc
->bpc_has_label
)
646 bfd_session_update_label(bs
, bpc
->bpc_label
);
648 if (bpc
->bpc_shutdown
) {
649 /* Check if already shutdown. */
650 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_SHUTDOWN
))
653 BFD_SET_FLAG(bs
->flags
, BFD_SESS_FLAG_SHUTDOWN
);
655 /* Disable all events. */
656 bfd_recvtimer_delete(bs
);
657 bfd_echo_recvtimer_delete(bs
);
658 bfd_xmttimer_delete(bs
);
659 bfd_echo_xmttimer_delete(bs
);
661 /* Change and notify state change. */
662 bs
->ses_state
= PTM_BFD_ADM_DOWN
;
665 /* Don't try to send packets with a disabled session. */
669 /* Check if already working. */
670 if (!BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_SHUTDOWN
))
673 BFD_UNSET_FLAG(bs
->flags
, BFD_SESS_FLAG_SHUTDOWN
);
675 /* Change and notify state change. */
676 bs
->ses_state
= PTM_BFD_DOWN
;
679 /* Enable all timers. */
680 bfd_recvtimer_update(bs
);
681 bfd_xmttimer_update(bs
, bs
->xmt_TO
);
685 static int bfd_session_update(struct bfd_session
*bs
, struct bfd_peer_cfg
*bpc
)
687 /* User didn't want to update, return failure. */
688 if (bpc
->bpc_createonly
)
691 _bfd_session_update(bs
, bpc
);
693 control_notify_config(BCM_NOTIFY_CONFIG_UPDATE
, bs
);
698 static void bfd_session_free(struct bfd_session
*bs
)
700 struct bfd_session_observer
*bso
;
702 bfd_session_disable(bs
);
704 /* Remove observer if any. */
705 TAILQ_FOREACH(bso
, &bglobal
.bg_obslist
, bso_entry
) {
706 if (bso
->bso_bs
!= bs
)
712 bs_observer_del(bso
);
717 XFREE(MTYPE_BFDD_CONFIG
, bs
);
720 struct bfd_session
*ptm_bfd_sess_new(struct bfd_peer_cfg
*bpc
)
722 struct bfd_session
*bfd
, *l_bfd
;
724 /* check to see if this needs a new session */
725 l_bfd
= bs_peer_find(bpc
);
727 /* Requesting a duplicated peer means update configuration. */
728 if (bfd_session_update(l_bfd
, bpc
) == 0)
734 /* Get BFD session storage with its defaults. */
735 bfd
= bfd_session_new();
737 log_error("session-new: allocation failed");
742 * Store interface/VRF name in case we need to delay session
743 * start. See `bfd_session_enable` for more information.
745 if (bpc
->bpc_has_localif
)
746 strlcpy(bfd
->ifname
, bpc
->bpc_localif
, sizeof(bfd
->ifname
));
748 if (bpc
->bpc_has_vrfname
)
749 strlcpy(bfd
->vrfname
, bpc
->bpc_vrfname
, sizeof(bfd
->vrfname
));
751 /* Add observer if we have moving parts. */
752 if (bfd
->ifname
[0] || bfd
->vrfname
[0])
753 bs_observer_add(bfd
);
755 /* Copy remaining data. */
756 if (bpc
->bpc_ipv4
== false)
757 BFD_SET_FLAG(bfd
->flags
, BFD_SESS_FLAG_IPV6
);
760 BFD_SET_FLAG(bfd
->flags
, BFD_SESS_FLAG_MH
);
761 bfd
->mhop
.peer
= bpc
->bpc_peer
;
762 bfd
->mhop
.local
= bpc
->bpc_local
;
764 bfd
->shop
.peer
= bpc
->bpc_peer
;
767 bfd
->local_ip
= bpc
->bpc_local
;
768 bfd
->local_address
= bpc
->bpc_local
;
770 /* Try to enable session and schedule for packet receive/send. */
771 if (bfd_session_enable(bfd
) == -1) {
772 /* Unrecoverable failure, remove the session/peer. */
773 bfd_session_free(bfd
);
777 /* Apply other configurations. */
778 _bfd_session_update(bfd
, bpc
);
780 log_info("session-new: %s", bs_to_string(bfd
));
782 control_notify_config(BCM_NOTIFY_CONFIG_ADD
, bfd
);
787 int ptm_bfd_ses_del(struct bfd_peer_cfg
*bpc
)
789 struct bfd_session
*bs
;
791 /* Find session and call free(). */
792 bs
= bs_peer_find(bpc
);
796 /* This pointer is being referenced, don't let it be deleted. */
797 if (bs
->refcount
> 0) {
798 log_error("session-delete: refcount failure: %" PRIu64
804 log_info("session-delete: %s", bs_to_string(bs
));
806 control_notify_config(BCM_NOTIFY_CONFIG_DELETE
, bs
);
808 bfd_session_free(bs
);
813 void bfd_set_polling(struct bfd_session
*bs
)
816 * Start polling procedure: the only timers that require polling
817 * to change value without losing connection are:
819 * - Desired minimum transmission interval;
820 * - Required minimum receive interval;
822 * RFC 5880, Section 6.8.3.
828 * bs_<state>_handler() functions implement the BFD state machine
829 * transition mechanism. `<state>` is the current session state and
830 * the parameter `nstate` is the peer new state.
832 static void bs_admin_down_handler(struct bfd_session
*bs
833 __attribute__((__unused__
)),
834 int nstate
__attribute__((__unused__
)))
837 * We are administratively down, there is no state machine
842 static void bs_down_handler(struct bfd_session
*bs
, int nstate
)
845 case PTM_BFD_ADM_DOWN
:
847 * Remote peer doesn't want to talk, so lets keep the
851 /* Peer can't be up yet, wait it go to 'init' or 'down'. */
856 * Remote peer agreed that the path is down, lets try to
859 bs
->ses_state
= PTM_BFD_INIT
;
864 * Remote peer told us his path is up, lets turn
865 * activate the session.
871 log_debug("state-change: unhandled neighbor state: %d", nstate
);
876 static void bs_init_handler(struct bfd_session
*bs
, int nstate
)
879 case PTM_BFD_ADM_DOWN
:
881 * Remote peer doesn't want to talk, so lets make the
884 bs
->ses_state
= PTM_BFD_DOWN
;
888 /* Remote peer hasn't moved to first stage yet. */
893 /* We agreed on the settings and the path is up. */
898 log_debug("state-change: unhandled neighbor state: %d", nstate
);
903 static void bs_up_handler(struct bfd_session
*bs
, int nstate
)
906 case PTM_BFD_ADM_DOWN
:
908 /* Peer lost or asked to shutdown connection. */
909 ptm_bfd_ses_dn(bs
, BD_NEIGHBOR_DOWN
);
914 /* Path is up and working. */
918 log_debug("state-change: unhandled neighbor state: %d", nstate
);
923 void bs_state_handler(struct bfd_session
*bs
, int nstate
)
925 switch (bs
->ses_state
) {
926 case PTM_BFD_ADM_DOWN
:
927 bs_admin_down_handler(bs
, nstate
);
930 bs_down_handler(bs
, nstate
);
933 bs_init_handler(bs
, nstate
);
936 bs_up_handler(bs
, nstate
);
940 log_debug("state-change: [%s] is in invalid state: %d",
941 bs_to_string(bs
), nstate
);
947 * Handles echo timer manipulation after updating timer.
949 void bs_echo_timer_handler(struct bfd_session
*bs
)
954 * Before doing any echo handling, check if it is possible to
957 * - Check for `echo-mode` configuration.
958 * - Check that we are not using multi hop (RFC 5883,
960 * - Check that we are already at the up state.
962 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO
) == 0
963 || BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
)
964 || bs
->ses_state
!= PTM_BFD_UP
)
967 /* Remote peer asked to stop echo. */
968 if (bs
->remote_timers
.required_min_echo
== 0) {
969 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO_ACTIVE
))
970 ptm_bfd_echo_stop(bs
);
976 * Calculate the echo transmission timer: we must not send
977 * echo packets faster than the minimum required time
978 * announced by the remote system.
980 * RFC 5880, Section 6.8.9.
982 old_timer
= bs
->echo_xmt_TO
;
983 if (bs
->remote_timers
.required_min_echo
> bs
->timers
.required_min_echo
)
984 bs
->echo_xmt_TO
= bs
->remote_timers
.required_min_echo
;
986 bs
->echo_xmt_TO
= bs
->timers
.required_min_echo
;
988 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_ECHO_ACTIVE
) == 0
989 || old_timer
!= bs
->echo_xmt_TO
)
990 ptm_bfd_echo_start(bs
);
994 * RFC 5880 Section 6.5.
996 * When a BFD control packet with the final bit is received, we must
997 * update the session parameters.
999 void bs_final_handler(struct bfd_session
*bs
)
1001 /* Start using our new timers. */
1002 bs
->cur_timers
.desired_min_tx
= bs
->timers
.desired_min_tx
;
1003 bs
->cur_timers
.required_min_rx
= bs
->timers
.required_min_rx
;
1006 * TODO: demand mode. See RFC 5880 Section 6.1.
1008 * When using demand mode we must disable the detection timer
1009 * for lost control packets.
1011 if (bs
->demand_mode
) {
1012 /* Notify watchers about changed timers. */
1013 control_notify_config(BCM_NOTIFY_CONFIG_UPDATE
, bs
);
1018 * Calculate detection time based on new timers.
1020 * Transmission calculation:
1021 * We must respect the RequiredMinRxInterval from the remote
1022 * system: if our desired transmission timer is more than the
1023 * minimum receive rate, then we must lower it to at least the
1024 * minimum receive interval.
1026 * RFC 5880, Section 6.8.3.
1028 if (bs
->timers
.desired_min_tx
> bs
->remote_timers
.required_min_rx
)
1029 bs
->xmt_TO
= bs
->remote_timers
.required_min_rx
;
1031 bs
->xmt_TO
= bs
->timers
.desired_min_tx
;
1033 /* Apply new transmission timer immediately. */
1034 ptm_bfd_start_xmt_timer(bs
, false);
1037 * Detection timeout calculation:
1038 * The minimum detection timeout is the remote detection
1039 * multipler (number of packets to be missed) times the agreed
1040 * transmission interval.
1042 * RFC 5880, Section 6.8.4.
1044 * TODO: support sending/counting more packets inside detection
1047 if (bs
->remote_timers
.required_min_rx
> bs
->timers
.desired_min_tx
)
1048 bs
->detect_TO
= bs
->remote_detect_mult
1049 * bs
->remote_timers
.required_min_rx
;
1051 bs
->detect_TO
= bs
->remote_detect_mult
1052 * bs
->timers
.desired_min_tx
;
1054 /* Apply new receive timer immediately. */
1055 bfd_recvtimer_update(bs
);
1057 /* Notify watchers about changed timers. */
1058 control_notify_config(BCM_NOTIFY_CONFIG_UPDATE
, bs
);
1061 void bs_set_slow_timers(struct bfd_session
*bs
)
1064 * BFD connection must use slow timers before going up or after
1065 * losing connectivity to avoid wasting bandwidth.
1067 * RFC 5880, Section 6.8.3.
1069 bs
->cur_timers
.desired_min_tx
= BFD_DEF_SLOWTX
;
1070 bs
->cur_timers
.required_min_rx
= BFD_DEF_SLOWTX
;
1071 bs
->cur_timers
.required_min_echo
= 0;
1073 /* Set the appropriated timeouts for slow connection. */
1074 bs
->detect_TO
= (BFD_DEFDETECTMULT
* BFD_DEF_SLOWTX
);
1075 bs
->xmt_TO
= BFD_DEF_SLOWTX
;
1081 static const char *get_diag_str(int diag
)
1083 for (int i
= 0; diag_list
[i
].str
; i
++) {
1084 if (diag_list
[i
].type
== diag
)
1085 return diag_list
[i
].str
;
1090 const char *satostr(struct sockaddr_any
*sa
)
1092 #define INETSTR_BUFCOUNT 8
1093 static char buf
[INETSTR_BUFCOUNT
][INET6_ADDRSTRLEN
];
1095 struct sockaddr_in
*sin
= &sa
->sa_sin
;
1096 struct sockaddr_in6
*sin6
= &sa
->sa_sin6
;
1098 bufidx
+= (bufidx
+ 1) % INETSTR_BUFCOUNT
;
1101 switch (sin
->sin_family
) {
1103 inet_ntop(AF_INET
, &sin
->sin_addr
, buf
[bufidx
],
1104 sizeof(buf
[bufidx
]));
1107 inet_ntop(AF_INET6
, &sin6
->sin6_addr
, buf
[bufidx
],
1108 sizeof(buf
[bufidx
]));
1112 strlcpy(buf
[bufidx
], "unknown", sizeof(buf
[bufidx
]));
1119 const char *diag2str(uint8_t diag
)
1125 return "control detection time expired";
1127 return "echo function failed";
1129 return "neighbor signaled session down";
1131 return "forwarding plane reset";
1135 return "concatenated path down";
1137 return "administratively down";
1139 return "reverse concatenated path down";
1145 int strtosa(const char *addr
, struct sockaddr_any
*sa
)
1147 memset(sa
, 0, sizeof(*sa
));
1149 if (inet_pton(AF_INET
, addr
, &sa
->sa_sin
.sin_addr
) == 1) {
1150 sa
->sa_sin
.sin_family
= AF_INET
;
1151 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
1152 sa
->sa_sin
.sin_len
= sizeof(sa
->sa_sin
);
1153 #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
1157 if (inet_pton(AF_INET6
, addr
, &sa
->sa_sin6
.sin6_addr
) == 1) {
1158 sa
->sa_sin6
.sin6_family
= AF_INET6
;
1159 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
1160 sa
->sa_sin6
.sin6_len
= sizeof(sa
->sa_sin6
);
1161 #endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
1168 void integer2timestr(uint64_t time
, char *buf
, size_t buflen
)
1170 unsigned int year
, month
, day
, hour
, minute
, second
;
1173 #define MINUTES (60)
1174 #define HOURS (60 * MINUTES)
1175 #define DAYS (24 * HOURS)
1176 #define MONTHS (30 * DAYS)
1177 #define YEARS (12 * MONTHS)
1178 if (time
>= YEARS
) {
1179 year
= time
/ YEARS
;
1180 time
-= year
* YEARS
;
1182 rv
= snprintf(buf
, buflen
, "%u year(s), ", year
);
1186 if (time
>= MONTHS
) {
1187 month
= time
/ MONTHS
;
1188 time
-= month
* MONTHS
;
1190 rv
= snprintf(buf
, buflen
, "%u month(s), ", month
);
1198 rv
= snprintf(buf
, buflen
, "%u day(s), ", day
);
1202 if (time
>= HOURS
) {
1203 hour
= time
/ HOURS
;
1204 time
-= hour
* HOURS
;
1206 rv
= snprintf(buf
, buflen
, "%u hour(s), ", hour
);
1210 if (time
>= MINUTES
) {
1211 minute
= time
/ MINUTES
;
1212 time
-= minute
* MINUTES
;
1214 rv
= snprintf(buf
, buflen
, "%u minute(s), ", minute
);
1218 second
= time
% MINUTES
;
1219 snprintf(buf
, buflen
, "%u second(s)", second
);
1222 const char *bs_to_string(struct bfd_session
*bs
)
1224 static char buf
[256];
1226 bool is_mhop
= BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
);
1228 pos
= snprintf(buf
, sizeof(buf
), "mhop:%s", is_mhop
? "yes" : "no");
1229 if (BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
)) {
1230 pos
+= snprintf(buf
+ pos
, sizeof(buf
) - pos
,
1231 " peer:%s local:%s", satostr(&bs
->mhop
.peer
),
1232 satostr(&bs
->mhop
.local
));
1234 if (bs
->mhop
.vrfid
!= VRF_DEFAULT
)
1235 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " vrf:%u",
1238 pos
+= snprintf(buf
+ pos
, sizeof(buf
) - pos
, " peer:%s",
1239 satostr(&bs
->shop
.peer
));
1241 if (bs
->local_address
.sa_sin
.sin_family
)
1242 pos
+= snprintf(buf
+ pos
, sizeof(buf
) - pos
,
1244 satostr(&bs
->local_address
));
1246 if (bs
->shop
.ifindex
)
1247 snprintf(buf
+ pos
, sizeof(buf
) - pos
, " ifindex:%u",
1254 int bs_observer_add(struct bfd_session
*bs
)
1256 struct bfd_session_observer
*bso
;
1258 bso
= XMALLOC(MTYPE_BFDD_SESSION_OBSERVER
, sizeof(*bso
));
1260 bso
->bso_isinterface
= !BFD_CHECK_FLAG(bs
->flags
, BFD_SESS_FLAG_MH
);
1261 if (bso
->bso_isinterface
)
1262 strlcpy(bso
->bso_entryname
, bs
->ifname
,
1263 sizeof(bso
->bso_entryname
));
1265 strlcpy(bso
->bso_entryname
, bs
->vrfname
,
1266 sizeof(bso
->bso_entryname
));
1268 TAILQ_INSERT_TAIL(&bglobal
.bg_obslist
, bso
, bso_entry
);
1273 void bs_observer_del(struct bfd_session_observer
*bso
)
1275 TAILQ_REMOVE(&bglobal
.bg_obslist
, bso
, bso_entry
);
1276 XFREE(MTYPE_BFDD_SESSION_OBSERVER
, bso
);
1281 * BFD hash data structures to find sessions.
1283 static struct hash
*bfd_id_hash
;
1284 static struct hash
*bfd_shop_hash
;
1285 static struct hash
*bfd_mhop_hash
;
1287 static unsigned int bfd_id_hash_do(void *p
);
1288 static unsigned int bfd_shop_hash_do(void *p
);
1289 static unsigned int bfd_mhop_hash_do(void *p
);
1291 static void _shop_key(struct bfd_session
*bs
, const struct bfd_shop_key
*shop
);
1292 static void _shop_key2(struct bfd_session
*bs
, const struct bfd_shop_key
*shop
);
1293 static void _mhop_key(struct bfd_session
*bs
, const struct bfd_mhop_key
*mhop
);
1295 static void _bfd_free(struct hash_bucket
*hb
,
1296 void *arg
__attribute__((__unused__
)));
1298 /* BFD hash for our discriminator. */
1299 static unsigned int bfd_id_hash_do(void *p
)
1301 struct bfd_session
*bs
= p
;
1303 return jhash_1word(bs
->discrs
.my_discr
, 0);
1306 static bool bfd_id_hash_cmp(const void *n1
, const void *n2
)
1308 const struct bfd_session
*bs1
= n1
, *bs2
= n2
;
1310 return bs1
->discrs
.my_discr
== bs2
->discrs
.my_discr
;
1313 /* BFD hash for single hop. */
1314 static unsigned int bfd_shop_hash_do(void *p
)
1316 struct bfd_session
*bs
= p
;
1318 return jhash(&bs
->shop
, sizeof(bs
->shop
), 0);
1321 static bool bfd_shop_hash_cmp(const void *n1
, const void *n2
)
1323 const struct bfd_session
*bs1
= n1
, *bs2
= n2
;
1325 return memcmp(&bs1
->shop
, &bs2
->shop
, sizeof(bs1
->shop
)) == 0;
1328 /* BFD hash for multi hop. */
1329 static unsigned int bfd_mhop_hash_do(void *p
)
1331 struct bfd_session
*bs
= p
;
1333 return jhash(&bs
->mhop
, sizeof(bs
->mhop
), 0);
1336 static bool bfd_mhop_hash_cmp(const void *n1
, const void *n2
)
1338 const struct bfd_session
*bs1
= n1
, *bs2
= n2
;
1340 return memcmp(&bs1
->mhop
, &bs2
->mhop
, sizeof(bs1
->mhop
)) == 0;
1343 /* Helper functions */
1344 static void _shop_key(struct bfd_session
*bs
, const struct bfd_shop_key
*shop
)
1348 /* Remove unused fields. */
1349 switch (bs
->shop
.peer
.sa_sin
.sin_family
) {
1351 bs
->shop
.peer
.sa_sin
.sin_port
= 0;
1354 bs
->shop
.peer
.sa_sin6
.sin6_port
= 0;
1359 static void _shop_key2(struct bfd_session
*bs
, const struct bfd_shop_key
*shop
)
1361 _shop_key(bs
, shop
);
1362 bs
->shop
.ifindex
= IFINDEX_INTERNAL
;
1365 static void _mhop_key(struct bfd_session
*bs
, const struct bfd_mhop_key
*mhop
)
1369 /* Remove unused fields. */
1370 switch (bs
->mhop
.peer
.sa_sin
.sin_family
) {
1372 bs
->mhop
.peer
.sa_sin
.sin_port
= 0;
1373 bs
->mhop
.local
.sa_sin
.sin_port
= 0;
1376 bs
->mhop
.peer
.sa_sin6
.sin6_port
= 0;
1377 bs
->mhop
.local
.sa_sin6
.sin6_port
= 0;
1383 * Hash public interface / exported functions.
1386 /* Lookup functions. */
1387 struct bfd_session
*bfd_id_lookup(uint32_t id
)
1389 struct bfd_session bs
;
1391 bs
.discrs
.my_discr
= id
;
1393 return hash_lookup(bfd_id_hash
, &bs
);
1396 struct bfd_session
*bfd_shop_lookup(struct bfd_shop_key shop
)
1398 struct bfd_session bs
, *bsp
;
1400 _shop_key(&bs
, &shop
);
1402 bsp
= hash_lookup(bfd_shop_hash
, &bs
);
1403 if (bsp
== NULL
&& bs
.shop
.ifindex
!= 0) {
1405 * Since the local interface spec is optional, try
1406 * searching the key without it as well.
1408 _shop_key2(&bs
, &shop
);
1409 bsp
= hash_lookup(bfd_shop_hash
, &bs
);
1415 struct bfd_session
*bfd_mhop_lookup(struct bfd_mhop_key mhop
)
1417 struct bfd_session bs
;
1419 _mhop_key(&bs
, &mhop
);
1421 return hash_lookup(bfd_mhop_hash
, &bs
);
1427 * Delete functions searches and remove the item from the hash and
1428 * returns a pointer to the removed item data. If the item was not found
1429 * then it returns NULL.
1431 * The data stored inside the hash is not free()ed, so you must do it
1432 * manually after getting the pointer back.
1434 struct bfd_session
*bfd_id_delete(uint32_t id
)
1436 struct bfd_session bs
;
1438 bs
.discrs
.my_discr
= id
;
1440 return hash_release(bfd_id_hash
, &bs
);
1443 struct bfd_session
*bfd_shop_delete(struct bfd_shop_key shop
)
1445 struct bfd_session bs
, *bsp
;
1447 _shop_key(&bs
, &shop
);
1448 bsp
= hash_release(bfd_shop_hash
, &bs
);
1449 if (bsp
== NULL
&& shop
.ifindex
!= 0) {
1451 * Since the local interface spec is optional, try
1452 * searching the key without it as well.
1454 _shop_key2(&bs
, &shop
);
1455 bsp
= hash_release(bfd_shop_hash
, &bs
);
1461 struct bfd_session
*bfd_mhop_delete(struct bfd_mhop_key mhop
)
1463 struct bfd_session bs
;
1465 _mhop_key(&bs
, &mhop
);
1467 return hash_release(bfd_mhop_hash
, &bs
);
1470 /* Iteration functions. */
1471 void bfd_id_iterate(hash_iter_func hif
, void *arg
)
1473 hash_iterate(bfd_id_hash
, hif
, arg
);
1476 void bfd_shop_iterate(hash_iter_func hif
, void *arg
)
1478 hash_iterate(bfd_shop_hash
, hif
, arg
);
1481 void bfd_mhop_iterate(hash_iter_func hif
, void *arg
)
1483 hash_iterate(bfd_mhop_hash
, hif
, arg
);
1489 * Inserts session into hash and returns `true` on success, otherwise
1492 bool bfd_id_insert(struct bfd_session
*bs
)
1494 return (hash_get(bfd_id_hash
, bs
, hash_alloc_intern
) == bs
);
1497 bool bfd_shop_insert(struct bfd_session
*bs
)
1499 return (hash_get(bfd_shop_hash
, bs
, hash_alloc_intern
) == bs
);
1502 bool bfd_mhop_insert(struct bfd_session
*bs
)
1504 return (hash_get(bfd_mhop_hash
, bs
, hash_alloc_intern
) == bs
);
1507 void bfd_initialize(void)
1509 bfd_id_hash
= hash_create(bfd_id_hash_do
, bfd_id_hash_cmp
,
1510 "BFD discriminator hash");
1511 bfd_shop_hash
= hash_create(bfd_shop_hash_do
, bfd_shop_hash_cmp
,
1512 "BFD single hop hash");
1513 bfd_mhop_hash
= hash_create(bfd_mhop_hash_do
, bfd_mhop_hash_cmp
,
1514 "BFD multihop hop hash");
1517 static void _bfd_free(struct hash_bucket
*hb
,
1518 void *arg
__attribute__((__unused__
)))
1520 struct bfd_session
*bs
= hb
->data
;
1522 bfd_session_free(bs
);
1525 void bfd_shutdown(void)
1528 * Close and free all BFD sessions.
1530 * _bfd_free() will call bfd_session_free() which will take care
1531 * of removing the session from all hashes, so we just run an
1532 * assert() here to make sure it really happened.
1534 bfd_id_iterate(_bfd_free
, NULL
);
1535 assert(bfd_shop_hash
->count
== 0);
1536 assert(bfd_mhop_hash
->count
== 0);
1538 /* Now free the hashes themselves. */
1539 hash_free(bfd_id_hash
);
1540 hash_free(bfd_shop_hash
);
1541 hash_free(bfd_mhop_hash
);