summaryrefslogtreecommitdiffstats
path: root/isisd/isis_bfd.c
diff options
context:
space:
mode:
Diffstat (limited to 'isisd/isis_bfd.c')
-rw-r--r--isisd/isis_bfd.c219
1 files changed, 219 insertions, 0 deletions
diff --git a/isisd/isis_bfd.c b/isisd/isis_bfd.c
new file mode 100644
index 0000000..5e24e35
--- /dev/null
+++ b/isisd/isis_bfd.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IS-IS Rout(e)ing protocol - BFD support
+ * Copyright (C) 2018 Christian Franke
+ */
+#include <zebra.h>
+
+#include "zclient.h"
+#include "nexthop.h"
+#include "bfd.h"
+#include "lib_errors.h"
+
+#include "isisd/isis_bfd.h"
+#include "isisd/isis_zebra.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_adjacency.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isisd.h"
+#include "isisd/fabricd.h"
+
+DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session");
+
+static void adj_bfd_cb(struct bfd_session_params *bsp,
+ const struct bfd_session_status *bss, void *arg)
+{
+ struct isis_adjacency *adj = arg;
+
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: BFD changed status for adjacency %s old %s new %s",
+ isis_adj_name(adj),
+ bfd_get_status_str(bss->previous_state),
+ bfd_get_status_str(bss->state));
+
+ if (bss->state == BFD_STATUS_DOWN
+ && bss->previous_state == BFD_STATUS_UP) {
+ adj->circuit->area->bfd_signalled_down = true;
+ isis_adj_state_change(&adj, ISIS_ADJ_DOWN,
+ "bfd session went down");
+ }
+}
+
+static void bfd_handle_adj_down(struct isis_adjacency *adj)
+{
+ bfd_sess_free(&adj->bfd_session);
+}
+
+static void bfd_handle_adj_up(struct isis_adjacency *adj)
+{
+ struct isis_circuit *circuit = adj->circuit;
+ int family;
+ union g_addr dst_ip;
+ union g_addr src_ip;
+ struct list *local_ips;
+ struct prefix *local_ip;
+
+ if (!circuit->bfd_config.enabled) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization on adjacency with %s because BFD is not enabled for the circuit",
+ isis_adj_name(adj));
+ goto out;
+ }
+
+ /* If IS-IS IPv6 is configured wait for IPv6 address to be programmed
+ * before starting up BFD
+ */
+ if (circuit->ipv6_router
+ && (listcount(circuit->ipv6_link) == 0
+ || adj->ll_ipv6_count == 0)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization on adjacency with %s because IPv6 is enabled but not ready",
+ isis_adj_name(adj));
+ return;
+ }
+
+ /*
+ * If IS-IS is enabled for both IPv4 and IPv6 on the circuit, prefer
+ * creating a BFD session over IPv6.
+ */
+ if (circuit->ipv6_router && adj->ll_ipv6_count) {
+ family = AF_INET6;
+ dst_ip.ipv6 = adj->ll_ipv6_addrs[0];
+ local_ips = circuit->ipv6_link;
+ if (list_isempty(local_ips)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization: IPv6 enabled and no local IPv6 addresses");
+ goto out;
+ }
+ local_ip = listgetdata(listhead(local_ips));
+ src_ip.ipv6 = local_ip->u.prefix6;
+ } else if (circuit->ip_router && adj->ipv4_address_count) {
+ family = AF_INET;
+ dst_ip.ipv4 = adj->ipv4_addresses[0];
+ local_ips = fabricd_ip_addrs(adj->circuit);
+ if (!local_ips || list_isempty(local_ips)) {
+ if (IS_DEBUG_BFD)
+ zlog_debug(
+ "ISIS-BFD: skipping BFD initialization: IPv4 enabled and no local IPv4 addresses");
+ goto out;
+ }
+ local_ip = listgetdata(listhead(local_ips));
+ src_ip.ipv4 = local_ip->u.prefix4;
+ } else
+ goto out;
+
+ if (adj->bfd_session == NULL)
+ adj->bfd_session = bfd_sess_new(adj_bfd_cb, adj);
+
+ bfd_sess_set_timers(adj->bfd_session, BFD_DEF_DETECT_MULT,
+ BFD_DEF_MIN_RX, BFD_DEF_MIN_TX);
+ if (family == AF_INET)
+ bfd_sess_set_ipv4_addrs(adj->bfd_session, &src_ip.ipv4,
+ &dst_ip.ipv4);
+ else
+ bfd_sess_set_ipv6_addrs(adj->bfd_session, &src_ip.ipv6,
+ &dst_ip.ipv6);
+ bfd_sess_set_interface(adj->bfd_session, adj->circuit->interface->name);
+ bfd_sess_set_vrf(adj->bfd_session,
+ adj->circuit->interface->vrf->vrf_id);
+ bfd_sess_set_profile(adj->bfd_session, circuit->bfd_config.profile);
+ bfd_sess_install(adj->bfd_session);
+ return;
+out:
+ bfd_handle_adj_down(adj);
+}
+
+static int bfd_handle_adj_state_change(struct isis_adjacency *adj)
+{
+ if (adj->adj_state == ISIS_ADJ_UP)
+ bfd_handle_adj_up(adj);
+ else
+ bfd_handle_adj_down(adj);
+ return 0;
+}
+
+static void bfd_adj_cmd(struct isis_adjacency *adj)
+{
+ if (adj->adj_state == ISIS_ADJ_UP && adj->circuit->bfd_config.enabled)
+ bfd_handle_adj_up(adj);
+ else
+ bfd_handle_adj_down(adj);
+}
+
+void isis_bfd_circuit_cmd(struct isis_circuit *circuit)
+{
+ switch (circuit->circ_type) {
+ case CIRCUIT_T_BROADCAST:
+ for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
+ struct list *adjdb = circuit->u.bc.adjdb[level - 1];
+
+ struct listnode *node;
+ struct isis_adjacency *adj;
+
+ if (!adjdb)
+ continue;
+ for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj))
+ bfd_adj_cmd(adj);
+ }
+ break;
+ case CIRCUIT_T_P2P:
+ if (circuit->u.p2p.neighbor)
+ bfd_adj_cmd(circuit->u.p2p.neighbor);
+ break;
+ default:
+ break;
+ }
+}
+
+static int bfd_handle_adj_ip_enabled(struct isis_adjacency *adj, int family,
+ bool global)
+{
+
+ if (family != AF_INET6 || global)
+ return 0;
+
+ if (adj->bfd_session)
+ return 0;
+
+ if (adj->adj_state != ISIS_ADJ_UP)
+ return 0;
+
+ bfd_handle_adj_up(adj);
+
+ return 0;
+}
+
+static int bfd_handle_circuit_add_addr(struct isis_circuit *circuit)
+{
+ struct isis_adjacency *adj;
+ struct listnode *node;
+
+ if (circuit->area == NULL)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(circuit->area->adjacency_list, node, adj)) {
+ if (adj->bfd_session)
+ continue;
+
+ if (adj->adj_state != ISIS_ADJ_UP)
+ continue;
+
+ bfd_handle_adj_up(adj);
+ }
+
+ return 0;
+}
+
+void isis_bfd_init(struct event_loop *tm)
+{
+ bfd_protocol_integration_init(zclient, tm);
+
+ hook_register(isis_adj_state_change_hook, bfd_handle_adj_state_change);
+ hook_register(isis_adj_ip_enabled_hook, bfd_handle_adj_ip_enabled);
+ hook_register(isis_circuit_add_addr_hook, bfd_handle_circuit_add_addr);
+}