summaryrefslogtreecommitdiffstats
path: root/bfdd
diff options
context:
space:
mode:
authorDonald Sharp <sharpd@cumulusnetworks.com>2020-12-03 02:50:47 +0100
committerGitHub <noreply@github.com>2020-12-03 02:50:47 +0100
commit0fb4ab03888247f3fcb48c5e388aa5ef19eb1230 (patch)
tree5819d22bdeba9e1ec19be91d5d346fb4b72aad93 /bfdd
parentMerge pull request #7590 from opensourcerouting/isisd-lfa (diff)
parentbfdd: move interface/vrf reset code (diff)
downloadfrr-0fb4ab03888247f3fcb48c5e388aa5ef19eb1230.tar.xz
frr-0fb4ab03888247f3fcb48c5e388aa5ef19eb1230.zip
Merge pull request #6950 from opensourcerouting/bfd-distributed-v3
bfdd: distributed BFD
Diffstat (limited to 'bfdd')
-rw-r--r--bfdd/bfd.c92
-rw-r--r--bfdd/bfd.h65
-rw-r--r--bfdd/bfdd.c160
-rw-r--r--bfdd/bfdd_vty.c42
-rw-r--r--bfdd/bfddp_packet.h381
-rw-r--r--bfdd/dplane.c1199
-rw-r--r--bfdd/ptm_adapter.c3
-rw-r--r--bfdd/subdir.am8
8 files changed, 1924 insertions, 26 deletions
diff --git a/bfdd/bfd.c b/bfdd/bfd.c
index c77b5cd1a..f7ce0ece3 100644
--- a/bfdd/bfd.c
+++ b/bfdd/bfd.c
@@ -216,6 +216,9 @@ void bfd_session_apply(struct bfd_session *bs)
&& (bs->timers.desired_min_tx != min_tx
|| bs->timers.required_min_rx != min_rx))
bfd_set_polling(bs);
+
+ /* Send updated information to data plane. */
+ bfd_dplane_update_session(bs);
}
void bfd_profile_remove(struct bfd_session *bs)
@@ -293,6 +296,10 @@ int bfd_session_enable(struct bfd_session *bs)
struct vrf *vrf = NULL;
int psock;
+ /* We are using data plane, we don't need software. */
+ if (bs->bdc)
+ return 0;
+
/*
* If the interface or VRF doesn't exist, then we must register
* the session but delay its start.
@@ -332,9 +339,14 @@ int bfd_session_enable(struct bfd_session *bs)
bs->vrf = vrf_lookup_by_id(VRF_DEFAULT);
assert(bs->vrf);
- if (bs->key.ifname[0]
- && CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) == 0)
- bs->ifp = ifp;
+ /* Assign interface pointer (if any). */
+ bs->ifp = ifp;
+
+ /* Attempt to use data plane. */
+ if (bglobal.bg_use_dplane && bfd_dplane_add_session(bs) == 0) {
+ control_notify_config(BCM_NOTIFY_CONFIG_ADD, bs);
+ return 0;
+ }
/* Sanity check: don't leak open sockets. */
if (bs->sock != -1) {
@@ -383,6 +395,10 @@ int bfd_session_enable(struct bfd_session *bs)
*/
void bfd_session_disable(struct bfd_session *bs)
{
+ /* We are using data plane, we don't need software. */
+ if (bs->bdc)
+ return;
+
/* Free up socket resources. */
if (bs->sock != -1) {
close(bs->sock);
@@ -393,8 +409,6 @@ void bfd_session_disable(struct bfd_session *bs)
bfd_recvtimer_delete(bs);
bfd_xmttimer_delete(bs);
ptm_bfd_echo_stop(bs);
- bs->vrf = NULL;
- bs->ifp = NULL;
/* Set session down so it doesn't report UP and disabled. */
ptm_bfd_sess_dn(bs, BD_PATH_DOWN);
@@ -804,6 +818,9 @@ void bfd_session_free(struct bfd_session *bs)
bfd_session_disable(bs);
+ /* Remove session from data plane if any. */
+ bfd_dplane_delete_session(bs);
+
bfd_key_delete(bs->key);
bfd_id_delete(bs->discrs.my_discr);
@@ -1267,14 +1284,18 @@ void bfd_set_echo(struct bfd_session *bs, bool echo)
SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
/* Activate/update echo receive timeout timer. */
- bs_echo_timer_handler(bs);
+ if (bs->bdc == NULL)
+ bs_echo_timer_handler(bs);
} else {
/* Check if echo mode is already disabled. */
if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
return;
UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
- ptm_bfd_echo_stop(bs);
+
+ /* Deactivate timeout timer. */
+ if (bs->bdc == NULL)
+ ptm_bfd_echo_stop(bs);
}
}
@@ -1299,6 +1320,14 @@ void bfd_set_shutdown(struct bfd_session *bs, bool shutdown)
SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+ /* Handle data plane shutdown case. */
+ if (bs->bdc) {
+ bs->ses_state = PTM_BFD_ADM_DOWN;
+ bfd_dplane_update_session(bs);
+ control_notify(bs, bs->ses_state);
+ return;
+ }
+
/* Disable all events. */
bfd_recvtimer_delete(bs);
bfd_echo_recvtimer_delete(bs);
@@ -1319,6 +1348,14 @@ void bfd_set_shutdown(struct bfd_session *bs, bool shutdown)
UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+ /* Handle data plane shutdown case. */
+ if (bs->bdc) {
+ bs->ses_state = PTM_BFD_DOWN;
+ bfd_dplane_update_session(bs);
+ control_notify(bs, bs->ses_state);
+ return;
+ }
+
/* Change and notify state change. */
bs->ses_state = PTM_BFD_DOWN;
control_notify(bs, bs->ses_state);
@@ -2028,6 +2065,16 @@ static int bfd_vrf_enable(struct vrf *vrf)
bvrf = XCALLOC(MTYPE_BFDD_VRF, sizeof(struct bfd_vrf_global));
bvrf->vrf = vrf;
vrf->info = (void *)bvrf;
+
+ /* Disable sockets if using data plane. */
+ if (bglobal.bg_use_dplane) {
+ bvrf->bg_shop = -1;
+ bvrf->bg_mhop = -1;
+ bvrf->bg_shop6 = -1;
+ bvrf->bg_mhop6 = -1;
+ bvrf->bg_echo = -1;
+ bvrf->bg_echov6 = -1;
+ }
} else
bvrf = vrf->info;
@@ -2049,25 +2096,24 @@ static int bfd_vrf_enable(struct vrf *vrf)
if (!bvrf->bg_echov6)
bvrf->bg_echov6 = bp_echov6_socket(vrf);
- /* Add descriptors to the event loop. */
- if (!bvrf->bg_ev[0])
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop,
- &bvrf->bg_ev[0]);
- if (!bvrf->bg_ev[1])
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop,
- &bvrf->bg_ev[1]);
+ if (!bvrf->bg_ev[0] && bvrf->bg_shop != -1)
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_shop, &bvrf->bg_ev[0]);
+ if (!bvrf->bg_ev[1] && bvrf->bg_mhop != -1)
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_mhop, &bvrf->bg_ev[1]);
if (!bvrf->bg_ev[2] && bvrf->bg_shop6 != -1)
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6,
- &bvrf->bg_ev[2]);
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_shop6, &bvrf->bg_ev[2]);
if (!bvrf->bg_ev[3] && bvrf->bg_mhop6 != -1)
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6,
- &bvrf->bg_ev[3]);
- if (!bvrf->bg_ev[4])
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo,
- &bvrf->bg_ev[4]);
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_mhop6, &bvrf->bg_ev[3]);
+ if (!bvrf->bg_ev[4] && bvrf->bg_echo != -1)
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_echo, &bvrf->bg_ev[4]);
if (!bvrf->bg_ev[5] && bvrf->bg_echov6 != -1)
- thread_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6,
- &bvrf->bg_ev[5]);
+ thread_add_read(master, bfd_recv_cb, bvrf,
+ bvrf->bg_echov6, &bvrf->bg_ev[5]);
}
if (vrf->vrf_id != VRF_DEFAULT) {
bfdd_zclient_register(vrf->vrf_id);
diff --git a/bfdd/bfd.h b/bfdd/bfd.h
index af3f92d6a..7c537b40d 100644
--- a/bfdd/bfd.h
+++ b/bfdd/bfd.h
@@ -269,6 +269,7 @@ struct bfd_session {
struct bfd_key key;
struct peer_label *pl;
+ struct bfd_dplane_ctx *bdc;
struct sockaddr_any local_address;
struct interface *ifp;
struct vrf *vrf;
@@ -424,6 +425,10 @@ struct bfd_vrf_global {
struct thread *bg_ev[6];
};
+/* Forward declaration of data plane context struct. */
+struct bfd_dplane_ctx;
+TAILQ_HEAD(dplane_queue, bfd_dplane_ctx);
+
struct bfd_global {
int bg_csock;
struct thread *bg_csockev;
@@ -441,7 +446,15 @@ struct bfd_global {
*/
bool bg_shutdown;
+ /* Distributed BFD items. */
+ bool bg_use_dplane;
+ int bg_dplane_sock;
+ struct thread *bg_dplane_sockev;
+ struct dplane_queue bg_dplaneq;
+
/* Debug options. */
+ /* Show distributed BFD debug messages. */
+ bool debug_dplane;
/* Show all peer state changes events. */
bool debug_peer_event;
/*
@@ -742,4 +755,56 @@ void bfd_session_update_vrf_name(struct bfd_session *bs, struct vrf *vrf);
int ptm_bfd_notify(struct bfd_session *bs, uint8_t notify_state);
+/*
+ * dplane.c
+ */
+
+/**
+ * Initialize BFD data plane infrastructure for distributed BFD implementation.
+ *
+ * \param sa socket address.
+ * \param salen socket address structure length.
+ * \param client `true` means connecting socket, `false` listening socket.
+ */
+void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client);
+
+/**
+ * Attempts to delegate the BFD session liveness detection to hardware.
+ *
+ * \param bs the BFD session data structure.
+ *
+ * \returns
+ * `0` on success and BFD daemon should do nothing or `-1` on failure
+ * and we should fallback to software implementation.
+ */
+int bfd_dplane_add_session(struct bfd_session *bs);
+
+/**
+ * Send new session settings to data plane.
+ *
+ * \param bs the BFD session to update.
+ */
+int bfd_dplane_update_session(const struct bfd_session *bs);
+
+/**
+ * Deletes session from data plane.
+ *
+ * \param bs the BFD session to delete.
+ *
+ * \returns `0` on success otherwise `-1`.
+ */
+int bfd_dplane_delete_session(struct bfd_session *bs);
+
+/**
+ * Asks the data plane for updated counters and update the session data
+ * structure.
+ *
+ * \param bs the BFD session that needs updating.
+ *
+ * \returns `0` on success otherwise `-1` on failure.
+ */
+int bfd_dplane_update_session_counters(struct bfd_session *bs);
+
+void bfd_dplane_show_counters(struct vty *vty);
+
#endif /* _BFD_H_ */
diff --git a/bfdd/bfdd.c b/bfdd/bfdd.c
index 098e7a289..b8a059708 100644
--- a/bfdd/bfdd.c
+++ b/bfdd/bfdd.c
@@ -20,12 +20,20 @@
#include <zebra.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+
#include "filter.h"
#include "if.h"
#include "vrf.h"
#include "bfd.h"
#include "bfdd_nb.h"
+#include "bfddp_packet.h"
#include "lib/version.h"
#include "lib/command.h"
@@ -129,8 +137,10 @@ FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617,
.n_yang_modules = array_size(bfdd_yang_modules))
#define OPTION_CTLSOCK 1001
+#define OPTION_DPLANEADDR 2000
static const struct option longopts[] = {
{"bfdctl", required_argument, NULL, OPTION_CTLSOCK},
+ {"dplaneaddr", required_argument, NULL, OPTION_DPLANEADDR},
{0}
};
@@ -160,6 +170,143 @@ const struct bfd_state_str_list state_list[] = {
{.str = NULL},
};
+static uint16_t
+parse_port(const char *str)
+{
+ char *nulbyte;
+ long rv;
+
+ errno = 0;
+ rv = strtol(str, &nulbyte, 10);
+ /* No conversion performed. */
+ if (rv == 0 && errno == EINVAL) {
+ fprintf(stderr, "invalid BFD data plane address port: %s\n",
+ str);
+ exit(0);
+ }
+ /* Invalid number range. */
+ if ((rv <= 0 || rv >= 65535) || errno == ERANGE) {
+ fprintf(stderr, "invalid BFD data plane port range: %s\n",
+ str);
+ exit(0);
+ }
+ /* There was garbage at the end of the string. */
+ if (*nulbyte != 0) {
+ fprintf(stderr, "invalid BFD data plane port: %s\n",
+ str);
+ exit(0);
+ }
+
+ return (uint16_t)rv;
+}
+
+static void
+distributed_bfd_init(const char *arg)
+{
+ char *sptr, *saux;
+ bool is_client = false;
+ size_t slen;
+ socklen_t salen;
+ char addr[64];
+ char type[64];
+ union {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+ } sa;
+
+ /* Basic parsing: find ':' to figure out type part and address part. */
+ sptr = strchr(arg, ':');
+ if (sptr == NULL) {
+ fprintf(stderr, "invalid BFD data plane socket: %s\n", arg);
+ exit(1);
+ }
+
+ /* Calculate type string length. */
+ slen = (size_t)(sptr - arg);
+
+ /* Copy the address part. */
+ sptr++;
+ strlcpy(addr, sptr, sizeof(addr));
+
+ /* Copy type part. */
+ strlcpy(type, arg, slen + 1);
+
+ /* Reset address data. */
+ memset(&sa, 0, sizeof(sa));
+
+ /* Fill the address information. */
+ if (strcmp(type, "unix") == 0 || strcmp(type, "unixc") == 0) {
+ if (strcmp(type, "unixc") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sun);
+ sa.sun.sun_family = AF_UNIX;
+ strlcpy(sa.sun.sun_path, addr, sizeof(sa.sun.sun_path));
+ } else if (strcmp(type, "ipv4") == 0 || strcmp(type, "ipv4c") == 0) {
+ if (strcmp(type, "ipv4c") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sin);
+ sa.sin.sin_family = AF_INET;
+
+ /* Parse port if any. */
+ sptr = strchr(addr, ':');
+ if (sptr == NULL) {
+ sa.sin.sin_port = htons(BFD_DATA_PLANE_DEFAULT_PORT);
+ } else {
+ *sptr = 0;
+ sa.sin.sin_port = htons(parse_port(sptr + 1));
+ }
+
+ if (inet_pton(AF_INET, addr, &sa.sin.sin_addr) != 1)
+ errx(1, "%s: inet_pton: invalid address %s", __func__,
+ addr);
+ } else if (strcmp(type, "ipv6") == 0 || strcmp(type, "ipv6c") == 0) {
+ if (strcmp(type, "ipv6c") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sin6);
+ sa.sin6.sin6_family = AF_INET6;
+
+ /* Check for IPv6 enclosures '[]' */
+ sptr = &addr[0];
+ if (*sptr != '[')
+ errx(1, "%s: invalid IPv6 address format: %s", __func__,
+ addr);
+
+ saux = strrchr(addr, ']');
+ if (saux == NULL)
+ errx(1, "%s: invalid IPv6 address format: %s", __func__,
+ addr);
+
+ /* Consume the '[]:' part. */
+ slen = saux - sptr;
+ memmove(addr, addr + 1, slen);
+ addr[slen - 1] = 0;
+
+ /* Parse port if any. */
+ saux++;
+ sptr = strrchr(saux, ':');
+ if (sptr == NULL) {
+ sa.sin6.sin6_port = htons(BFD_DATA_PLANE_DEFAULT_PORT);
+ } else {
+ *sptr = 0;
+ sa.sin6.sin6_port = htons(parse_port(sptr + 1));
+ }
+
+ if (inet_pton(AF_INET6, addr, &sa.sin6.sin6_addr) != 1)
+ errx(1, "%s: inet_pton: invalid address %s", __func__,
+ addr);
+ } else {
+ fprintf(stderr, "invalid BFD data plane socket type: %s\n",
+ type);
+ exit(1);
+ }
+
+ /* Initialize BFD data plane listening socket. */
+ bfd_dplane_init((struct sockaddr *)&sa, salen, is_client);
+}
static void bg_init(void)
{
@@ -185,7 +332,7 @@ static void bg_init(void)
int main(int argc, char *argv[])
{
- char ctl_path[512];
+ char ctl_path[512], dplane_addr[512];
bool ctlsockused = false;
int opt;
@@ -194,7 +341,8 @@ int main(int argc, char *argv[])
frr_preinit(&bfdd_di, argc, argv);
frr_opt_add("", longopts,
- " --bfdctl Specify bfdd control socket\n");
+ " --bfdctl Specify bfdd control socket\n"
+ " --dplaneaddr Specify BFD data plane address\n");
snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET,
"", "");
@@ -208,6 +356,10 @@ int main(int argc, char *argv[])
strlcpy(ctl_path, optarg, sizeof(ctl_path));
ctlsockused = true;
break;
+ case OPTION_DPLANEADDR:
+ strlcpy(dplane_addr, optarg, sizeof(dplane_addr));
+ bglobal.bg_use_dplane = true;
+ break;
default:
frr_help_exit(1);
@@ -248,6 +400,10 @@ int main(int argc, char *argv[])
/* read configuration file and daemonize */
frr_config_fork();
+ /* Initialize BFD data plane listening socket. */
+ if (bglobal.bg_use_dplane)
+ distributed_bfd_init(dplane_addr);
+
frr_run(master);
/* NOTREACHED */
diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c
index 837a7b7d7..53e23cf6c 100644
--- a/bfdd/bfdd_vty.c
+++ b/bfdd/bfdd_vty.c
@@ -348,6 +348,11 @@ static void _display_peer_counter(struct vty *vty, struct bfd_session *bs)
{
_display_peer_header(vty, bs);
+ /* Ask data plane for updated counters. */
+ if (bfd_dplane_update_session_counters(bs) == -1)
+ zlog_debug("%s: failed to update BFD session counters (%s)",
+ __func__, bs_to_string(bs));
+
vty_out(vty, "\t\tControl packet input: %" PRIu64 " packets\n",
bs->stats.rx_ctrl_pkt);
vty_out(vty, "\t\tControl packet output: %" PRIu64 " packets\n",
@@ -369,6 +374,11 @@ static struct json_object *__display_peer_counters_json(struct bfd_session *bs)
{
struct json_object *jo = _peer_json_header(bs);
+ /* Ask data plane for updated counters. */
+ if (bfd_dplane_update_session_counters(bs) == -1)
+ zlog_debug("%s: failed to update BFD session counters (%s)",
+ __func__, bs_to_string(bs));
+
json_object_int_add(jo, "control-packet-input", bs->stats.rx_ctrl_pkt);
json_object_int_add(jo, "control-packet-output", bs->stats.tx_ctrl_pkt);
json_object_int_add(jo, "echo-packet-input", bs->stats.rx_echo_pkt);
@@ -748,6 +758,28 @@ DEFPY(bfd_show_peers_brief, bfd_show_peers_brief_cmd,
return CMD_SUCCESS;
}
+DEFPY(show_bfd_distributed, show_bfd_distributed_cmd,
+ "show bfd distributed",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ "Show BFD data plane (distributed BFD) statistics\n")
+{
+ bfd_dplane_show_counters(vty);
+ return CMD_SUCCESS;
+}
+
+DEFPY(
+ bfd_debug_distributed, bfd_debug_distributed_cmd,
+ "[no] debug bfd distributed",
+ NO_STR
+ DEBUG_STR
+ "Bidirection Forwarding Detection\n"
+ "BFD data plane (distributed BFD) debugging\n")
+{
+ bglobal.debug_dplane = !no;
+ return CMD_SUCCESS;
+}
+
DEFPY(
bfd_debug_peer, bfd_debug_peer_cmd,
"[no] debug bfd peer",
@@ -888,6 +920,8 @@ DEFUN_NOSH(show_debugging_bfd,
"BFD daemon\n")
{
vty_out(vty, "BFD debugging status:\n");
+ if (bglobal.debug_dplane)
+ vty_out(vty, " Distributed BFD debugging is on.\n");
if (bglobal.debug_peer_event)
vty_out(vty, " Peer events debugging is on.\n");
if (bglobal.debug_zebra)
@@ -919,6 +953,11 @@ static int bfdd_write_config(struct vty *vty)
struct lyd_node *dnode;
int written = 0;
+ if (bglobal.debug_dplane) {
+ vty_out(vty, "debug bfd distributed\n");
+ written = 1;
+ }
+
if (bglobal.debug_peer_event) {
vty_out(vty, "debug bfd peer\n");
written = 1;
@@ -951,12 +990,15 @@ void bfdd_vty_init(void)
install_element(ENABLE_NODE, &bfd_show_peers_cmd);
install_element(ENABLE_NODE, &bfd_show_peer_cmd);
install_element(ENABLE_NODE, &bfd_show_peers_brief_cmd);
+ install_element(ENABLE_NODE, &show_bfd_distributed_cmd);
install_element(ENABLE_NODE, &show_debugging_bfd_cmd);
+ install_element(ENABLE_NODE, &bfd_debug_distributed_cmd);
install_element(ENABLE_NODE, &bfd_debug_peer_cmd);
install_element(ENABLE_NODE, &bfd_debug_zebra_cmd);
install_element(ENABLE_NODE, &bfd_debug_network_cmd);
+ install_element(CONFIG_NODE, &bfd_debug_distributed_cmd);
install_element(CONFIG_NODE, &bfd_debug_peer_cmd);
install_element(CONFIG_NODE, &bfd_debug_zebra_cmd);
install_element(CONFIG_NODE, &bfd_debug_network_cmd);
diff --git a/bfdd/bfddp_packet.h b/bfdd/bfddp_packet.h
new file mode 100644
index 000000000..8865baef6
--- /dev/null
+++ b/bfdd/bfddp_packet.h
@@ -0,0 +1,381 @@
+/*
+ * BFD Data Plane protocol messages header.
+ *
+ * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael F. Zalamena
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the “Software”), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/**
+ * \file bfddp_packet.h
+ */
+#ifndef BFD_DP_PACKET_H
+#define BFD_DP_PACKET_H
+
+#include <netinet/in.h>
+
+#include <stdint.h>
+
+/*
+ * Protocol definitions.
+ */
+
+/**
+ * BFD protocol version as defined in RFC5880 Section 4.1 Generic BFD Control
+ * Packet Format.
+ */
+#define BFD_PROTOCOL_VERSION 1
+
+/** Default data plane port. */
+#define BFD_DATA_PLANE_DEFAULT_PORT 50700
+
+/** BFD single hop UDP port, as defined in RFC 5881 Section 4. Encapsulation. */
+#define BFD_SINGLE_HOP_PORT 3784
+
+/** BFD multi hop UDP port, as defined in RFC 5883 Section 5. Encapsulation. */
+#define BFD_MULTI_HOP_PORT 4784
+
+/** Default slow start multiplier. */
+#define SLOWSTART_DMULT 3
+/** Default slow start transmission speed. */
+#define SLOWSTART_TX 1000000u
+/** Default slow start receive speed. */
+#define SLOWSTART_RX 1000000u
+/** Default slow start echo receive speed. */
+#define SLOWSTART_ERX 0u
+
+/*
+ * BFD single hop source UDP ports. As defined in RFC 5881 Section 4.
+ * Encapsulation.
+ */
+#define BFD_SOURCE_PORT_BEGIN 49152
+#define BFD_SOURCE_PORT_END 65535
+
+/** BFD data plane protocol version. */
+#define BFD_DP_VERSION 1
+
+/** BFD data plane message types. */
+enum bfddp_message_type {
+ /** Ask for BFD daemon or data plane for echo packet. */
+ ECHO_REQUEST = 0,
+ /** Answer a ECHO_REQUEST packet. */
+ ECHO_REPLY = 1,
+ /** Add or update BFD peer session. */
+ DP_ADD_SESSION = 2,
+ /** Delete BFD peer session. */
+ DP_DELETE_SESSION = 3,
+ /** Tell BFD daemon state changed: timer expired or session down. */
+ BFD_STATE_CHANGE = 4,
+
+ /** Ask for BFD session counters. */
+ DP_REQUEST_SESSION_COUNTERS = 5,
+ /** Tell BFD daemon about counters values. */
+ BFD_SESSION_COUNTERS = 6,
+};
+
+/**
+ * `ECHO_REQUEST`/`ECHO_REPLY` data payload.
+ *
+ * Data plane might use whatever precision it wants for `dp_time`
+ * field, however if you want to be able to tell the delay between
+ * data plane packet send and BFD daemon packet processing you should
+ * use `gettimeofday()` and have the data plane clock synchronized with
+ * BFD daemon (not a problem if data plane runs in the same system).
+ *
+ * Normally data plane will only check the time stamp it sent to determine
+ * the whole packet trip time.
+ */
+struct bfddp_echo {
+ /** Filled by data plane. */
+ uint64_t dp_time;
+ /** Filled by BFD daemon. */
+ uint64_t bfdd_time;
+};
+
+
+/** BFD session flags. */
+enum bfddp_session_flag {
+ /** Set when using multi hop. */
+ SESSION_MULTIHOP = (1 << 0),
+ /** Set when using demand mode. */
+ SESSION_DEMAND = (1 << 1),
+ /** Set when using cbit (Control Plane Independent). */
+ SESSION_CBIT = (1 << 2),
+ /** Set when using echo mode. */
+ SESSION_ECHO = (1 << 3),
+ /** Set when using IPv6. */
+ SESSION_IPV6 = (1 << 4),
+ /** Set when using passive mode. */
+ SESSION_PASSIVE = (1 << 5),
+ /** Set when session is administrative down. */
+ SESSION_SHUTDOWN = (1 << 6),
+};
+
+/**
+ * `DP_ADD_SESSION`/`DP_DELETE_SESSION` data payload.
+ *
+ * `lid` is unique in BFD daemon so it might be used as key for data
+ * structures lookup.
+ */
+struct bfddp_session {
+ /** Important session flags. \see bfddp_session_flag. */
+ uint32_t flags;
+ /**
+ * Session source address.
+ *
+ * Check `flags` field for `SESSION_IPV6` before using as IPv6.
+ */
+ struct in6_addr src;
+ /**
+ * Session destination address.
+ *
+ * Check `flags` field for `SESSION_IPV6` before using as IPv6.
+ */
+ struct in6_addr dst;
+
+ /** Local discriminator. */
+ uint32_t lid;
+ /**
+ * Minimum desired transmission interval (in microseconds) without
+ * jitter.
+ */
+ uint32_t min_tx;
+ /**
+ * Required minimum receive interval rate (in microseconds) without
+ * jitter.
+ */
+ uint32_t min_rx;
+ /**
+ * Required minimum echo receive interval rate (in microseconds)
+ * without jitter.
+ */
+ uint32_t min_echo_rx;
+ /** Amount of milliseconds to wait before starting the session */
+ uint32_t hold_time;
+
+ /** Minimum TTL. */
+ uint8_t ttl;
+ /** Detection multiplier. */
+ uint8_t detect_mult;
+ /** Reserved / zeroed. */
+ uint16_t zero;
+
+ /** Interface index (set to `0` when unavailable). */
+ uint32_t ifindex;
+ /** Interface name (empty when unavailable). */
+ char ifname[64];
+
+ /* TODO: missing authentication. */
+};
+
+/** BFD packet state values as defined in RFC 5880, Section 4.1. */
+enum bfd_state_value {
+ /** Session is administratively down. */
+ STATE_ADMINDOWN = 0,
+ /** Session is down or went down. */
+ STATE_DOWN = 1,
+ /** Session is initializing. */
+ STATE_INIT = 2,
+ /** Session is up. */
+ STATE_UP = 3,
+};
+
+/** BFD diagnostic field values as defined in RFC 5880, Section 4.1. */
+enum bfd_diagnostic_value {
+ /** Nothing was diagnosed. */
+ DIAG_NOTHING = 0,
+ /** Control detection time expired. */
+ DIAG_CONTROL_EXPIRED = 1,
+ /** Echo function failed. */
+ DIAG_ECHO_FAILED = 2,
+ /** Neighbor signaled down. */
+ DIAG_DOWN = 3,
+ /** Forwarding plane reset. */
+ DIAG_FP_RESET = 4,
+ /** Path down. */
+ DIAG_PATH_DOWN = 5,
+ /** Concatenated path down. */
+ DIAG_CONCAT_PATH_DOWN = 6,
+ /** Administratively down. */
+ DIAG_ADMIN_DOWN = 7,
+ /** Reverse concatenated path down. */
+ DIAG_REV_CONCAT_PATH_DOWN = 8,
+};
+
+/** BFD remote state flags. */
+enum bfd_remote_flags {
+ /** Control Plane Independent bit. */
+ RBIT_CPI = (1 << 0),
+ /** Demand mode bit. */
+ RBIT_DEMAND = (1 << 1),
+ /** Multipoint bit. */
+ RBIT_MP = (1 << 2),
+};
+
+/**
+ * `BFD_STATE_CHANGE` data payload.
+ */
+struct bfddp_state_change {
+ /** Local discriminator. */
+ uint32_t lid;
+ /** Remote discriminator. */
+ uint32_t rid;
+ /** Remote configurations/bits set. \see bfd_remote_flags. */
+ uint32_t remote_flags;
+ /** Remote minimum desired transmission interval. */
+ uint32_t desired_tx;
+ /** Remote minimum receive interval. */
+ uint32_t required_rx;
+ /** Remote minimum echo receive interval. */
+ uint32_t required_echo_rx;
+ /** Remote state. \see bfd_state_values.*/
+ uint8_t state;
+ /** Remote diagnostics (if any) */
+ uint8_t diagnostics;
+ /** Remote detection multiplier. */
+ uint8_t detection_multiplier;
+};
+
+/**
+ * BFD control packet state bits definition.
+ */
+enum bfddp_control_state_bits {
+ /** Used to request connection establishment signal. */
+ STATE_POLL_BIT = (1 << 5),
+ /** Finalizes the connection establishment signal. */
+ STATE_FINAL_BIT = (1 << 4),
+ /** Signalizes that forward plane doesn't depend on control plane. */
+ STATE_CPI_BIT = (1 << 3),
+ /** Signalizes the use of authentication. */
+ STATE_AUTH_BIT = (1 << 2),
+ /** Signalizes that peer is using demand mode. */
+ STATE_DEMAND_BIT = (1 << 1),
+ /** Used in RFC 8562 implementation. */
+ STATE_MULTI_BIT = (1 << 0),
+};
+
+/**
+ * BFD control packet.
+ *
+ * As defined in 'RFC 5880 Section 4.1 Generic BFD Control Packet Format'.
+ */
+struct bfddp_control_packet {
+ /** (3 bits version << 5) | (5 bits diag). */
+ uint8_t version_diag;
+ /**
+ * (2 bits state << 6) | (6 bits flags)
+ *
+ * \see bfd_state_value, bfddp_control_state_bits.
+ */
+ uint8_t state_bits;
+ /** Detection multiplier. */
+ uint8_t detection_multiplier;
+ /** Packet length in bytes. */
+ uint8_t length;
+ /** Our discriminator. */
+ uint32_t local_id;
+ /** Remote system discriminator. */
+ uint32_t remote_id;
+ /** Desired minimum send interval in microseconds. */
+ uint32_t desired_tx;
+ /** Desired minimum receive interval in microseconds. */
+ uint32_t required_rx;
+ /** Desired minimum echo receive interval in microseconds. */
+ uint32_t required_echo_rx;
+};
+
+/**
+ * The protocol wire message header structure.
+ */
+struct bfddp_message_header {
+ /** Protocol version format. \see BFD_DP_VERSION. */
+ uint8_t version;
+ /** Reserved / zero field. */
+ uint8_t zero;
+ /** Message contents type. \see bfddp_message_type. */
+ uint16_t type;
+ /**
+ * Message identification (to pair request/response).
+ *
+ * The ID `0` is reserved for asynchronous messages (e.g. unrequested
+ * messages).
+ */
+ uint16_t id;
+ /** Message length. */
+ uint16_t length;
+};
+
+/**
+ * Data plane session counters request.
+ *
+ * Message type: `DP_REQUEST_SESSION_COUNTERS`.
+ */
+struct bfddp_request_counters {
+ /** Session local discriminator. */
+ uint32_t lid;
+};
+
+/**
+ * BFD session counters reply.
+ *
+ * Message type: `BFD_SESSION_COUNTERS`.
+ */
+struct bfddp_session_counters {
+ /** Session local discriminator. */
+ uint32_t lid;
+
+ /** Control packet bytes input. */
+ uint64_t control_input_bytes;
+ /** Control packets input. */
+ uint64_t control_input_packets;
+ /** Control packet bytes output. */
+ uint64_t control_output_bytes;
+ /** Control packets output. */
+ uint64_t control_output_packets;
+
+ /** Echo packet bytes input. */
+ uint64_t echo_input_bytes;
+ /** Echo packets input. */
+ uint64_t echo_input_packets;
+ /** Echo packet bytes output. */
+ uint64_t echo_output_bytes;
+ /** Echo packets output. */
+ uint64_t echo_output_packets;
+};
+
+/**
+ * The protocol wire messages structure.
+ */
+struct bfddp_message {
+ /** Message header. \see bfddp_message_header. */
+ struct bfddp_message_header header;
+
+ /** Message payload. \see bfddp_message_type. */
+ union {
+ struct bfddp_echo echo;
+ struct bfddp_session session;
+ struct bfddp_state_change state;
+ struct bfddp_control_packet control;
+ struct bfddp_request_counters counters_req;
+ struct bfddp_session_counters session_counters;
+ } data;
+};
+
+#endif /* BFD_DP_PACKET_H */
diff --git a/bfdd/dplane.c b/bfdd/dplane.c
new file mode 100644
index 000000000..b8f0aadd9
--- /dev/null
+++ b/bfdd/dplane.c
@@ -0,0 +1,1199 @@
+/*
+ * BFD data plane implementation (distributed BFD).
+ *
+ * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifdef __FreeBSD__
+#include <sys/endian.h>
+#else
+#include <endian.h>
+#endif /* __FreeBSD__ */
+
+#include <errno.h>
+#include <time.h>
+
+#include "lib/hook.h"
+#include "lib/network.h"
+#include "lib/printfrr.h"
+#include "lib/stream.h"
+#include "lib/thread.h"
+
+#include "bfd.h"
+#include "bfddp_packet.h"
+
+#include "lib/openbsd-queue.h"
+
+DEFINE_MTYPE_STATIC(BFDD, BFDD_DPLANE_CTX, "Data plane client allocated memory")
+
+/** Data plane client socket buffer size. */
+#define BFD_DPLANE_CLIENT_BUF_SIZE 8192
+
+struct bfd_dplane_ctx {
+ /** Client file descriptor. */
+ int sock;
+ /** Is this a connected or accepted? */
+ bool client;
+ /** Is the socket still connecting? */
+ bool connecting;
+ /** Client/server address. */
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+ } addr;
+ /** Address length. */
+ socklen_t addrlen;
+ /** Data plane current last used ID. */
+ uint16_t last_id;
+
+ /** Input buffer data. */
+ struct stream *inbuf;
+ /** Output buffer data. */
+ struct stream *outbuf;
+ /** Input event data. */
+ struct thread *inbufev;
+ /** Output event data. */
+ struct thread *outbufev;
+ /** Connection event. */
+ struct thread *connectev;
+
+ /** Amount of bytes read. */
+ uint64_t in_bytes;
+ /** Amount of bytes read peak. */
+ uint64_t in_bytes_peak;
+ /** Amount of bytes written. */
+ uint64_t out_bytes;
+ /** Amount of bytes written peak. */
+ uint64_t out_bytes_peak;
+ /** Amount of output buffer full events (`bfd_dplane_enqueue` failed).
+ */
+ uint64_t out_fullev;
+
+ /** Amount of messages read (full messages). */
+ uint64_t in_msgs;
+ /** Amount of messages enqueued (maybe written). */
+ uint64_t out_msgs;
+
+ TAILQ_ENTRY(bfd_dplane_ctx) entry;
+};
+
+/**
+ * Callback type for `bfd_dplane_expect`. \see bfd_dplane_expect.
+ */
+typedef void (*bfd_dplane_expect_cb)(struct bfddp_message *msg, void *arg);
+
+static int bfd_dplane_client_connect(struct thread *t);
+static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc);
+static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc);
+static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
+ struct bfd_session *bs);
+
+/*
+ * BFD data plane helper functions.
+ */
+static const char *bfd_dplane_messagetype2str(enum bfddp_message_type bmt)
+{
+ switch (bmt) {
+ case ECHO_REQUEST:
+ return "ECHO_REQUEST";
+ case ECHO_REPLY:
+ return "ECHO_REPLY";
+ case DP_ADD_SESSION:
+ return "DP_ADD_SESSION";
+ case DP_DELETE_SESSION:
+ return "DP_DELETE_SESSION";
+ case BFD_STATE_CHANGE:
+ return "BFD_STATE_CHANGE";
+ case DP_REQUEST_SESSION_COUNTERS:
+ return "DP_REQUEST_SESSION_COUNTERS";
+ case BFD_SESSION_COUNTERS:
+ return "BFD_SESSION_COUNTERS";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void bfd_dplane_debug_message(const struct bfddp_message *msg)
+{
+ enum bfddp_message_type bmt;
+ char buf[256], addrs[256];
+ uint32_t flags;
+ int rv;
+
+ if (!bglobal.debug_dplane)
+ return;
+
+ bmt = ntohs(msg->header.type);
+ zlog_debug("dplane-packet: [version=%d length=%d type=%s (%d)]",
+ msg->header.version, ntohs(msg->header.length),
+ bfd_dplane_messagetype2str(bmt), bmt);
+
+ switch (bmt) {
+ case ECHO_REPLY:
+ case ECHO_REQUEST:
+ zlog_debug(" [dp_time=%" PRIu64 " bfdd_time=%" PRIu64 "]",
+ (uint64_t)be64toh(msg->data.echo.dp_time),
+ (uint64_t)be64toh(msg->data.echo.bfdd_time));
+ break;
+
+ case DP_ADD_SESSION:
+ case DP_DELETE_SESSION:
+ flags = ntohl(msg->data.session.flags);
+ if (flags & SESSION_IPV6)
+ snprintfrr(addrs, sizeof(addrs), "src=%pI6 dst=%pI6",
+ &msg->data.session.src,
+ &msg->data.session.dst);
+ else
+ snprintfrr(addrs, sizeof(addrs), "src=%pI4 dst=%pI4",
+ &msg->data.session.src,
+ &msg->data.session.dst);
+
+ buf[0] = 0;
+ if (flags & SESSION_CBIT)
+ strlcat(buf, "cpi ", sizeof(buf));
+ if (flags & SESSION_ECHO)
+ strlcat(buf, "echo ", sizeof(buf));
+ if (flags & SESSION_IPV6)
+ strlcat(buf, "ipv6 ", sizeof(buf));
+ if (flags & SESSION_DEMAND)
+ strlcat(buf, "demand ", sizeof(buf));
+ if (flags & SESSION_PASSIVE)
+ strlcat(buf, "passive ", sizeof(buf));
+ if (flags & SESSION_MULTIHOP)
+ strlcat(buf, "multihop ", sizeof(buf));
+ if (flags & SESSION_SHUTDOWN)
+ strlcat(buf, "shutdown ", sizeof(buf));
+
+ /* Remove the last space to make things prettier. */
+ rv = (int)strlen(buf);
+ if (rv > 0)
+ buf[rv - 1] = 0;
+
+ zlog_debug(
+ " [flags=0x%08x{%s} %s ttl=%d detect_mult=%d "
+ "ifindex=%d ifname=%s]",
+ flags, buf, addrs, msg->data.session.ttl,
+ msg->data.session.detect_mult,
+ ntohl(msg->data.session.ifindex),
+ msg->data.session.ifname);
+ break;
+
+ case BFD_STATE_CHANGE:
+ buf[0] = 0;
+ flags = ntohl(msg->data.state.remote_flags);
+ if (flags & RBIT_CPI)
+ strlcat(buf, "cbit ", sizeof(buf));
+ if (flags & RBIT_DEMAND)
+ strlcat(buf, "demand ", sizeof(buf));
+ if (flags & RBIT_MP)
+ strlcat(buf, "mp ", sizeof(buf));
+
+ /* Remove the last space to make things prettier. */
+ rv = (int)strlen(buf);
+ if (rv > 0)
+ buf[rv - 1] = 0;
+
+ zlog_debug(
+ " [lid=%u rid=%u flags=0x%02x{%s} state=%s "
+ "diagnostics=%s mult=%d tx=%u rx=%u erx=%u]",
+ ntohl(msg->data.state.lid), ntohl(msg->data.state.rid),
+ flags, buf, state_list[msg->data.state.state].str,
+ diag2str(msg->data.state.diagnostics),
+ msg->data.state.detection_multiplier,
+ ntohl(msg->data.state.desired_tx),
+ ntohl(msg->data.state.required_rx),
+ ntohl(msg->data.state.required_echo_rx));
+ break;
+
+ case DP_REQUEST_SESSION_COUNTERS:
+ zlog_debug(" [lid=%u]", ntohl(msg->data.counters_req.lid));
+ break;
+
+ case BFD_SESSION_COUNTERS:
+ zlog_debug(
+ " [lid=%u "
+ "control{in %" PRIu64 " bytes (%" PRIu64
+ " packets), "
+ "out %" PRIu64 " bytes (%" PRIu64
+ " packets)} "
+ "echo{in %" PRIu64 " bytes (%" PRIu64
+ " packets), "
+ "out %" PRIu64 " bytes (%" PRIu64 " packets)}]",
+ ntohl(msg->data.session_counters.lid),
+ (uint64_t)be64toh(
+ msg->data.session_counters.control_input_bytes),
+ (uint64_t)be64toh(msg->data.session_counters
+ .control_input_packets),
+ (uint64_t)be64toh(msg->data.session_counters
+ .control_output_bytes),
+ (uint64_t)be64toh(msg->data.session_counters
+ .control_output_packets),
+ (uint64_t)be64toh(msg->data.session_counters.echo_input_bytes),
+ (uint64_t)be64toh(
+ msg->data.session_counters.echo_input_packets),
+ (uint64_t)be64toh(
+ msg->data.session_counters.echo_output_bytes),
+ (uint64_t)be64toh(msg->data.session_counters
+ .echo_output_packets));
+ break;
+ }
+}
+
+/**
+ * Gets the next unused non zero identification.
+ *
+ * \param bdc the data plane context.
+ *
+ * \returns next usable id.
+ */
+static uint16_t bfd_dplane_next_id(struct bfd_dplane_ctx *bdc)
+{
+ bdc->last_id++;
+
+ /* Don't use reserved id `0`. */
+ if (bdc->last_id == 0)
+ bdc->last_id = 1;
+
+ return bdc->last_id;
+}
+
+static ssize_t bfd_dplane_flush(struct bfd_dplane_ctx *bdc)
+{
+ ssize_t total = 0;
+ int rv;
+
+ while (STREAM_READABLE(bdc->outbuf)) {
+ /* Flush buffer contents to socket. */
+ rv = stream_flush(bdc->outbuf, bdc->sock);
+ if (rv == -1) {
+ /* Interruption: try again. */
+ if (errno == EAGAIN || errno == EWOULDBLOCK
+ || errno == EINTR)
+ continue;
+
+ zlog_warn("%s: socket failed: %s", __func__,
+ strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return 0;
+ }
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_info("%s: connection closed", __func__);
+
+ bfd_dplane_ctx_free(bdc);
+ return 0;
+ }
+
+ /* Account total written. */
+ total += rv;
+
+ /* Account output bytes. */
+ bdc->out_bytes += (uint64_t)rv;
+
+ /* Forward pointer. */
+ stream_forward_getp(bdc->outbuf, (size_t)rv);
+ }
+
+ /* Make more space for new data. */
+ stream_pulldown(bdc->outbuf);
+
+ /* Disable write ready events. */
+ THREAD_OFF(bdc->outbufev);
+
+ return total;
+}
+
+static int bfd_dplane_write(struct thread *t)
+{
+ struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
+
+ /* Handle connection stage. */
+ if (bdc->connecting && bfd_dplane_client_connecting(bdc))
+ return 0;
+
+ bfd_dplane_flush(bdc);
+
+ return 0;
+}
+
+static void
+bfd_dplane_session_state_change(struct bfd_dplane_ctx *bdc,
+ const struct bfddp_state_change *state)
+{
+ struct bfd_session *bs;
+ uint32_t flags;
+ int old_state;
+
+ /* Look up session. */
+ bs = bfd_id_lookup(ntohl(state->lid));
+ if (bs == NULL) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: failed to find session to update",
+ __func__);
+ return;
+ }
+
+ flags = ntohl(state->remote_flags);
+ old_state = bs->ses_state;
+
+ /* Update session state. */
+ bs->ses_state = state->state;
+ bs->remote_diag = state->diagnostics;
+ bs->discrs.remote_discr = ntohl(state->rid);
+ bs->remote_cbit = !!(flags & RBIT_CPI);
+ bs->remote_detect_mult = state->detection_multiplier;
+ bs->remote_timers.desired_min_tx = ntohl(state->desired_tx);
+ bs->remote_timers.required_min_rx = ntohl(state->required_rx);
+ bs->remote_timers.required_min_echo = ntohl(state->required_echo_rx);
+
+ /* Notify and update counters. */
+ control_notify(bs, bs->ses_state);
+
+ /* No state change. */
+ if (old_state == bs->ses_state)
+ return;
+
+ switch (bs->ses_state) {
+ case PTM_BFD_ADM_DOWN:
+ case PTM_BFD_DOWN:
+ /* Both states mean down. */
+ if (old_state == PTM_BFD_ADM_DOWN || old_state == PTM_BFD_DOWN)
+ break;
+
+ monotime(&bs->downtime);
+ bs->stats.session_down++;
+ break;
+ case PTM_BFD_UP:
+ monotime(&bs->uptime);
+ bs->stats.session_up++;
+ break;
+ case PTM_BFD_INIT:
+ /* NOTHING */
+ break;
+
+ default:
+ zlog_warn("%s: unhandled new state %d", __func__,
+ bs->ses_state);
+ break;
+ }
+
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: [data plane: %s] %s -> %s",
+ bs_to_string(bs), state_list[old_state].str,
+ state_list[bs->ses_state].str);
+}
+
+/**
+ * Enqueue message in output buffer.
+ *
+ * \param[in,out] bdc data plane client context.
+ * \param[in] buf the message to buffer.
+ * \param[in] buflen the amount of bytes to buffer.
+ *
+ * \returns `-1` on failure (buffer full) or `0` on success.
+ */
+static int bfd_dplane_enqueue(struct bfd_dplane_ctx *bdc, const void *buf,
+ size_t buflen)
+{
+ size_t rlen;
+
+ /* Handle not connected yet client. */
+ if (bdc->client && bdc->sock == -1)
+ return -1;
+
+ /* Not enough space. */
+ if (buflen > STREAM_WRITEABLE(bdc->outbuf)) {
+ bdc->out_fullev++;
+ return -1;
+ }
+
+ /* Show debug message if active. */
+ bfd_dplane_debug_message((struct bfddp_message *)buf);
+
+ /* Buffer the message. */
+ stream_write(bdc->outbuf, buf, buflen);
+
+ /* Account message as sent. */
+ bdc->out_msgs++;
+ /* Register peak buffered bytes. */
+ rlen = STREAM_READABLE(bdc->outbuf);
+ if (bdc->out_bytes_peak < rlen)
+ bdc->out_bytes_peak = rlen;
+
+ /* Schedule if it is not yet. */
+ if (bdc->outbufev == NULL)
+ thread_add_write(master, bfd_dplane_write, bdc, bdc->sock,
+ &bdc->outbufev);
+
+ return 0;
+}
+
+static void bfd_dplane_echo_request_handle(struct bfd_dplane_ctx *bdc,
+ const struct bfddp_message *bm)
+{
+ struct bfddp_message msg = {};
+ uint16_t msglen = sizeof(msg.header) + sizeof(msg.data.echo);
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ /* Prepare header. */
+ msg.header.version = BFD_DP_VERSION;
+ msg.header.type = htons(ECHO_REPLY);
+ msg.header.length = htons(msglen);
+
+ /* Prepare payload. */
+ msg.data.echo.dp_time = bm->data.echo.dp_time;
+ msg.data.echo.bfdd_time =
+ htobe64((uint64_t)((tv.tv_sec * 1000000) + tv.tv_usec));
+
+ /* Enqueue for output. */
+ bfd_dplane_enqueue(bdc, &msg, msglen);
+}
+
+static void bfd_dplane_handle_message(struct bfddp_message *msg, void *arg)
+{
+ enum bfddp_message_type bmt;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ /* Call the appropriated handler. */
+ bmt = ntohs(msg->header.type);
+ switch (bmt) {
+ case ECHO_REQUEST:
+ bfd_dplane_echo_request_handle(bdc, msg);
+ break;
+ case BFD_STATE_CHANGE:
+ bfd_dplane_session_state_change(bdc, &msg->data.state);
+ break;
+ case ECHO_REPLY:
+ /* NOTHING: we don't do anything with this information. */
+ break;
+ case DP_ADD_SESSION:
+ case DP_DELETE_SESSION:
+ case DP_REQUEST_SESSION_COUNTERS:
+ /* NOTHING: we are not supposed to receive this. */
+ break;
+ case BFD_SESSION_COUNTERS:
+ /*
+ * NOTHING: caller of DP_REQUEST_SESSION_COUNTERS should
+ * handle this with `bfd_dplane_expect`.
+ */
+ break;
+
+ default:
+ zlog_debug("%s: unhandled message type %d", __func__, bmt);
+ break;
+ }
+}
+
+/**
+ * Reads the socket immediately to receive data plane answer to query.
+ *
+ * \param bdc the data plane context.
+ * \param id the message ID waiting response.
+ * \param cb the callback to call when ready.
+ * \param arg the callback argument.
+ *
+ * \return
+ * `-2` on unavailability (try again), `-1` on failure or `0` on success.
+ */
+static int bfd_dplane_expect(struct bfd_dplane_ctx *bdc, uint16_t id,
+ bfd_dplane_expect_cb cb, void *arg)
+{
+ struct bfddp_message_header *bh;
+ size_t rlen = 0, reads = 0;
+ ssize_t rv;
+
+ /*
+ * Don't attempt to read if buffer is full, otherwise we'll get a
+ * bogus 'connection closed' signal (rv == 0).
+ */
+ if (bdc->inbuf->endp == bdc->inbuf->size)
+ goto skip_read;
+
+read_again:
+ /* Attempt to read message from client. */
+ rv = stream_read_try(bdc->inbuf, bdc->sock,
+ STREAM_WRITEABLE(bdc->inbuf));
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_info("%s: socket closed", __func__);
+
+ bfd_dplane_ctx_free(bdc);
+ return -1;
+ }
+ if (rv == -1) {
+ zlog_warn("%s: socket failed: %s", __func__, strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return -1;
+ }
+
+ /* We got interrupted, reschedule read. */
+ if (rv == -2)
+ return -2;
+
+ /* Account read bytes. */
+ bdc->in_bytes += (uint64_t)rv;
+ /* Register peak buffered bytes. */
+ rlen = STREAM_READABLE(bdc->inbuf);
+ if (bdc->in_bytes_peak < rlen)
+ bdc->in_bytes_peak = rlen;
+
+skip_read:
+ while (rlen > 0) {
+ bh = (struct bfddp_message_header *)stream_pnt(bdc->inbuf);
+ /* Not enough data read. */
+ if (ntohs(bh->length) > rlen)
+ goto read_again;
+
+ /* Account full message read. */
+ bdc->in_msgs++;
+
+ /* Account this message as whole read for buffer reorganize. */
+ reads++;
+
+ /* Check for bad version. */
+ if (bh->version != BFD_DP_VERSION) {
+ zlog_err("%s: bad data plane client version: %d",
+ __func__, bh->version);
+ return -1;
+ }
+
+ /* Show debug message if active. */
+ bfd_dplane_debug_message((struct bfddp_message *)bh);
+
+ /*
+ * Handle incoming message with callback if the ID matches,
+ * otherwise fallback to default handler.
+ */
+ if (id && ntohs(bh->id) == id)
+ cb((struct bfddp_message *)bh, arg);
+ else
+ bfd_dplane_handle_message((struct bfddp_message *)bh,
+ bdc);
+
+ /* Advance current read pointer. */
+ stream_forward_getp(bdc->inbuf, ntohs(bh->length));
+
+ /* Reduce the buffer available bytes. */
+ rlen -= ntohs(bh->length);
+
+ /* Reorganize buffer to handle more bytes read. */
+ if (reads >= 3) {
+ stream_pulldown(bdc->inbuf);
+ reads = 0;
+ }
+
+ /* We found the message, return to caller. */
+ if (id && ntohs(bh->id) == id)
+ break;
+ }
+
+ return 0;
+}
+
+static int bfd_dplane_read(struct thread *t)
+{
+ struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
+ int rv;
+
+ rv = bfd_dplane_expect(bdc, 0, bfd_dplane_handle_message, NULL);
+ if (rv == -1)
+ return 0;
+
+ stream_pulldown(bdc->inbuf);
+ thread_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
+ return 0;
+}
+
+static void _bfd_session_register_dplane(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session *bs = hb->data;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ if (bs->bdc != NULL)
+ return;
+
+ /* Disable software session. */
+ bfd_session_disable(bs);
+
+ /* Move session to data plane. */
+ _bfd_dplane_add_session(bdc, bs);
+}
+
+static struct bfd_dplane_ctx *bfd_dplane_ctx_new(int sock)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ bdc = XCALLOC(MTYPE_BFDD_DPLANE_CTX, sizeof(*bdc));
+ if (bdc == NULL)
+ return NULL;
+
+ bdc->sock = sock;
+ bdc->inbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
+ bdc->outbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
+
+ /* If not socket ready, skip read and session registration. */
+ if (sock == -1)
+ return bdc;
+
+ thread_add_read(master, bfd_dplane_read, bdc, sock, &bdc->inbufev);
+
+ /* Register all unattached sessions. */
+ bfd_key_iterate(_bfd_session_register_dplane, bdc);
+
+ return bdc;
+}
+
+static void _bfd_session_unregister_dplane(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session *bs = hb->data;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ if (bs->bdc != bdc)
+ return;
+
+ bs->bdc = NULL;
+
+ /* Fallback to software. */
+ bfd_session_enable(bs);
+}
+
+static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc)
+{
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: terminating data plane client %d", __func__,
+ bdc->sock);
+
+ /* Client mode has special treatment. */
+ if (bdc->client) {
+ /* Disable connection event if any. */
+ THREAD_OFF(bdc->connectev);
+
+ /* Normal treatment on shutdown. */
+ if (bglobal.bg_shutdown)
+ goto free_resources;
+
+ /* Attempt reconnection. */
+ socket_close(&bdc->sock);
+ THREAD_OFF(bdc->inbufev);
+ THREAD_OFF(bdc->outbufev);
+ thread_add_timer(master, bfd_dplane_client_connect, bdc, 3,
+ &bdc->connectev);
+ return;
+ }
+
+free_resources:
+ /* Remove from the list of attached data planes. */
+ TAILQ_REMOVE(&bglobal.bg_dplaneq, bdc, entry);
+
+ /* Detach all associated sessions. */
+ if (bglobal.bg_shutdown == false)
+ bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
+
+ /* Free resources. */
+ socket_close(&bdc->sock);
+ stream_free(bdc->inbuf);
+ stream_free(bdc->outbuf);
+ THREAD_OFF(bdc->inbufev);
+ THREAD_OFF(bdc->outbufev);
+ XFREE(MTYPE_BFDD_DPLANE_CTX, bdc);
+}
+
+static void _bfd_dplane_session_fill(const struct bfd_session *bs,
+ struct bfddp_message *msg)
+{
+ uint16_t msglen = sizeof(msg->header) + sizeof(msg->data.session);
+
+ /* Message header. */
+ msg->header.version = BFD_DP_VERSION;
+ msg->header.length = ntohs(msglen);
+ msg->header.type = ntohs(DP_ADD_SESSION);
+
+ /* Message payload. */
+ msg->data.session.dst = bs->key.peer;
+ msg->data.session.src = bs->key.local;
+ msg->data.session.detect_mult = bs->detect_mult;
+
+ if (bs->ifp) {
+ msg->data.session.ifindex = htonl(bs->ifp->ifindex);
+ strlcpy(msg->data.session.ifname, bs->ifp->name,
+ sizeof(msg->data.session.ifname));
+ }
+ if (bs->flags & BFD_SESS_FLAG_MH) {
+ msg->data.session.flags |= SESSION_MULTIHOP;
+ msg->data.session.ttl = bs->mh_ttl;
+ } else
+ msg->data.session.ttl = BFD_TTL_VAL;
+
+ if (bs->flags & BFD_SESS_FLAG_IPV6)
+ msg->data.session.flags |= SESSION_IPV6;
+ if (bs->flags & BFD_SESS_FLAG_ECHO)
+ msg->data.session.flags |= SESSION_ECHO;
+ if (bs->flags & BFD_SESS_FLAG_CBIT)
+ msg->data.session.flags |= SESSION_CBIT;
+ if (bs->flags & BFD_SESS_FLAG_PASSIVE)
+ msg->data.session.flags |= SESSION_PASSIVE;
+ if (bs->flags & BFD_SESS_FLAG_SHUTDOWN)
+ msg->data.session.flags |= SESSION_SHUTDOWN;
+
+ msg->data.session.flags = htonl(msg->data.session.flags);
+ msg->data.session.lid = htonl(bs->discrs.my_discr);
+ msg->data.session.min_tx = htonl(bs->timers.desired_min_tx);
+ msg->data.session.min_rx = htonl(bs->timers.required_min_rx);
+ msg->data.session.min_echo_rx = htonl(bs->timers.required_min_echo);
+}
+
+static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
+ struct bfd_session *bs)
+{
+ int rv;
+
+ /* Associate session. */
+ bs->bdc = bdc;
+
+ /* Reset previous state. */
+ bs->remote_diag = 0;
+ bs->local_diag = 0;
+ bs->ses_state = PTM_BFD_DOWN;
+
+ /* Enqueue message to data plane client. */
+ rv = bfd_dplane_update_session(bs);
+ if (rv != 0)
+ bs->bdc = NULL;
+
+ return rv;
+}
+
+static void _bfd_dplane_update_session_counters(struct bfddp_message *msg,
+ void *arg)
+{
+ struct bfd_session *bs = arg;
+
+ bs->stats.rx_ctrl_pkt =
+ be64toh(msg->data.session_counters.control_input_packets);
+ bs->stats.tx_ctrl_pkt =
+ be64toh(msg->data.session_counters.control_output_packets);
+ bs->stats.rx_echo_pkt =
+ be64toh(msg->data.session_counters.echo_input_packets);
+ bs->stats.tx_echo_pkt =
+ be64toh(msg->data.session_counters.echo_output_bytes);
+}
+
+/**
+ * Send message to data plane requesting the session counters.
+ *
+ * \param bs the BFD session.
+ *
+ * \returns `0` on failure or the request id.
+ */
+static uint16_t bfd_dplane_request_counters(const struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+ size_t msglen = sizeof(msg.header) + sizeof(msg.data.counters_req);
+
+ /* Fill header information. */
+ msg.header.version = BFD_DP_VERSION;
+ msg.header.length = htons(msglen);
+ msg.header.type = htons(DP_REQUEST_SESSION_COUNTERS);
+ msg.header.id = htons(bfd_dplane_next_id(bs->bdc));
+
+ /* Session to get counters. */
+ msg.data.counters_req.lid = htonl(bs->discrs.my_discr);
+
+ /* If enqueue failed, let caller know. */
+ if (bfd_dplane_enqueue(bs->bdc, &msg, msglen) == -1)
+ return 0;
+
+ /* Flush socket. */
+ bfd_dplane_flush(bs->bdc);
+
+ return ntohs(msg.header.id);
+}
+
+/*
+ * Data plane listening socket.
+ */
+static int bfd_dplane_accept(struct thread *t)
+{
+ struct bfd_global *bg = THREAD_ARG(t);
+ struct bfd_dplane_ctx *bdc;
+ int sock;
+
+ /* Accept new connection. */
+ sock = accept(bg->bg_dplane_sock, NULL, 0);
+ if (sock == -1) {
+ zlog_warn("%s: accept failed: %s", __func__, strerror(errno));
+ goto reschedule_and_return;
+ }
+
+ /* Create and handle new connection. */
+ bdc = bfd_dplane_ctx_new(sock);
+ TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
+
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: new data plane client connected", __func__);
+
+reschedule_and_return:
+ thread_add_read(master, bfd_dplane_accept, bg, bg->bg_dplane_sock,
+ &bglobal.bg_dplane_sockev);
+ return 0;
+}
+
+/*
+ * Data plane connecting socket.
+ */
+static void _bfd_dplane_client_bootstrap(struct bfd_dplane_ctx *bdc)
+{
+ bdc->connecting = false;
+
+ /* Clean up buffers. */
+ stream_reset(bdc->inbuf);
+ stream_reset(bdc->outbuf);
+
+ /* Ask for read notifications. */
+ thread_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
+
+ /* Remove all sessions then register again to send them all. */
+ bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
+ bfd_key_iterate(_bfd_session_register_dplane, bdc);
+}
+
+static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc)
+{
+ int rv;
+ socklen_t rvlen = sizeof(rv);
+
+ /* Make sure `errno` is reset, then test `getsockopt` success. */
+ errno = 0;
+ if (getsockopt(bdc->sock, SOL_SOCKET, SO_ERROR, &rv, &rvlen) == -1)
+ rv = -1;
+
+ /* Connection successful. */
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: connected to server: %d", __func__,
+ bdc->sock);
+
+ _bfd_dplane_client_bootstrap(bdc);
+ return false;
+ }
+
+ switch (rv) {
+ case EINTR:
+ case EAGAIN:
+ case EALREADY:
+ case EINPROGRESS:
+ /* non error, wait more. */
+ return true;
+
+ default:
+ zlog_warn("%s: connection failed: %s", __func__,
+ strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return true;
+ }
+}
+
+static int bfd_dplane_client_connect(struct thread *t)
+{
+ struct bfd_dplane_ctx *bdc = THREAD_ARG(t);
+ int rv, sock;
+ socklen_t rvlen = sizeof(rv);
+
+ /* Allocate new socket. */
+ sock = socket(bdc->addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock == -1) {
+ zlog_warn("%s: failed to initialize socket: %s", __func__,
+ strerror(errno));
+ goto reschedule_connect;
+ }
+
+ /* Set non blocking socket. */
+ set_nonblocking(sock);
+
+ /* Set 'no delay' (disables nagle algorithm) for IPv4/IPv6. */
+ rv = 1;
+ if (bdc->addr.sa.sa_family != AF_UNIX
+ && setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &rv, rvlen) == -1)
+ zlog_warn("%s: TCP_NODELAY: %s", __func__, strerror(errno));
+
+ /* Attempt to connect. */
+ rv = connect(sock, &bdc->addr.sa, bdc->addrlen);
+ if (rv == -1 && (errno != EINPROGRESS && errno != EAGAIN)) {
+ zlog_warn("%s: data plane connection failed: %s", __func__,
+ strerror(errno));
+ goto reschedule_connect;
+ }
+
+ bdc->sock = sock;
+ if (rv == -1) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: server connection in progress: %d",
+ __func__, sock);
+
+ /* If we are not connected yet, ask for write notifications. */
+ bdc->connecting = true;
+ thread_add_write(master, bfd_dplane_write, bdc, bdc->sock,
+ &bdc->outbufev);
+ } else {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: server connection: %d", __func__, sock);
+
+ /* Otherwise just start accepting data. */
+ _bfd_dplane_client_bootstrap(bdc);
+ }
+
+ return 0;
+
+reschedule_connect:
+ THREAD_OFF(bdc->inbufev);
+ THREAD_OFF(bdc->outbufev);
+ socket_close(&sock);
+ thread_add_timer(master, bfd_dplane_client_connect, bdc, 3,
+ &bdc->connectev);
+ return 0;
+}
+
+static void bfd_dplane_client_init(const struct sockaddr *sa, socklen_t salen)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ /* Allocate context and copy address for reconnection. */
+ bdc = bfd_dplane_ctx_new(-1);
+ if (salen <= sizeof(bdc->addr)) {
+ memcpy(&bdc->addr, sa, salen);
+ bdc->addrlen = sizeof(bdc->addr);
+ } else {
+ memcpy(&bdc->addr, sa, sizeof(bdc->addr));
+ bdc->addrlen = sizeof(bdc->addr);
+ zlog_warn("%s: server address truncated (from %d to %d)",
+ __func__, salen, bdc->addrlen);
+ }
+
+ bdc->client = true;
+
+ thread_add_timer(master, bfd_dplane_client_connect, bdc, 0,
+ &bdc->connectev);
+
+ /* Insert into data plane lists. */
+ TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
+}
+
+/**
+ * Termination phase of the distributed BFD infrastructure: free all allocated
+ * resources.
+ */
+static int bfd_dplane_finish_late(void)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: terminating distributed BFD", __func__);
+
+ /* Free all data plane client contexts. */
+ while ((bdc = TAILQ_FIRST(&bglobal.bg_dplaneq)) != NULL)
+ bfd_dplane_ctx_free(bdc);
+
+ /* Cancel accept thread and close socket. */
+ THREAD_OFF(bglobal.bg_dplane_sockev);
+ close(bglobal.bg_dplane_sock);
+
+ return 0;
+}
+
+/*
+ * Data plane exported functions.
+ */
+void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client)
+{
+ int sock;
+
+ zlog_info("initializing distributed BFD");
+
+ /* Initialize queue header. */
+ TAILQ_INIT(&bglobal.bg_dplaneq);
+
+ /* Initialize listening socket. */
+ bglobal.bg_dplane_sock = -1;
+
+ /* Observe shutdown events. */
+ hook_register(frr_fini, bfd_dplane_finish_late);
+
+ /* Handle client mode. */
+ if (client) {
+ bfd_dplane_client_init(sa, salen);
+ return;
+ }
+
+ /*
+ * Data plane socket creation:
+ * - Set REUSEADDR option for taking over previously open socket.
+ * - Bind to address requested (maybe IPv4, IPv6, UNIX etc...).
+ * - Listen on that address for new connections.
+ * - Ask to be waken up when a new connection comes.
+ */
+ sock = socket(sa->sa_family, SOCK_STREAM, 0);
+ if (sock == -1) {
+ zlog_warn("%s: failed to initialize socket: %s", __func__,
+ strerror(errno));
+ return;
+ }
+
+ if (sockopt_reuseaddr(sock) == -1) {
+ zlog_warn("%s: failed to set reuseaddr: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ /* Handle UNIX socket: delete previous socket if any. */
+ if (sa->sa_family == AF_UNIX)
+ unlink(((struct sockaddr_un *)sa)->sun_path);
+
+ if (bind(sock, sa, salen) == -1) {
+ zlog_warn("%s: failed to bind socket: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ if (listen(sock, SOMAXCONN) == -1) {
+ zlog_warn("%s: failed to put socket on listen: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ bglobal.bg_dplane_sock = sock;
+ thread_add_read(master, bfd_dplane_accept, &bglobal, sock,
+ &bglobal.bg_dplane_sockev);
+}
+
+int bfd_dplane_add_session(struct bfd_session *bs)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ /* Select a data plane client to install session. */
+ TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
+ if (_bfd_dplane_add_session(bdc, bs) == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+int bfd_dplane_update_session(const struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+
+ if (bs->bdc == NULL)
+ return 0;
+
+ _bfd_dplane_session_fill(bs, &msg);
+
+ /* Enqueue message to data plane client. */
+ return bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
+}
+
+int bfd_dplane_delete_session(struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+ int rv;
+
+ /* Not using data plane, just return success. */
+ if (bs->bdc == NULL)
+ return 0;
+
+ /* Fill most of the common fields. */
+ _bfd_dplane_session_fill(bs, &msg);
+
+ /* Change the message type. */
+ msg.header.type = ntohs(DP_DELETE_SESSION);
+
+ /* Enqueue message to data plane client. */
+ rv = bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
+
+ /* Remove association. */
+ bs->bdc = NULL;
+
+ return rv;
+}
+
+/*
+ * Data plane CLI.
+ */
+void bfd_dplane_show_counters(struct vty *vty)
+{
+ struct bfd_dplane_ctx *bdc;
+
+#define SHOW_COUNTER(label, counter, formatter) \
+ vty_out(vty, "%28s: %" formatter "\n", (label), (counter))
+
+ vty_out(vty, "%28s\n%28s\n", "Data plane", "==========");
+ TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
+ SHOW_COUNTER("File descriptor", bdc->sock, "d");
+ SHOW_COUNTER("Input bytes", bdc->in_bytes, PRIu64);
+ SHOW_COUNTER("Input bytes peak", bdc->in_bytes_peak, PRIu64);
+ SHOW_COUNTER("Input messages", bdc->in_msgs, PRIu64);
+ SHOW_COUNTER("Input current usage", STREAM_READABLE(bdc->inbuf),
+ "zu");
+ SHOW_COUNTER("Output bytes", bdc->out_bytes, PRIu64);
+ SHOW_COUNTER("Output bytes peak", bdc->out_bytes_peak, PRIu64);
+ SHOW_COUNTER("Output messages", bdc->out_msgs, PRIu64);
+ SHOW_COUNTER("Output full events", bdc->out_fullev, PRIu64);
+ SHOW_COUNTER("Output current usage",
+ STREAM_READABLE(bdc->inbuf), "zu");
+ vty_out(vty, "\n");
+ }
+#undef SHOW_COUNTER
+}
+
+int bfd_dplane_update_session_counters(struct bfd_session *bs)
+{
+ uint16_t id;
+ int rv;
+
+ /* If session is not using data plane, then just return success. */
+ if (bs->bdc == NULL)
+ return 0;
+
+ /* Make the request. */
+ id = bfd_dplane_request_counters(bs);
+ if (id == 0) {
+ zlog_debug("%s: counters request failed", __func__);
+ return -1;
+ }
+
+ /* Handle interruptions. */
+ do {
+ rv = bfd_dplane_expect(bs->bdc, id,
+ _bfd_dplane_update_session_counters, bs);
+ } while (rv == -2);
+
+ return rv;
+}
diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c
index 90e2df236..44519c47b 100644
--- a/bfdd/ptm_adapter.c
+++ b/bfdd/ptm_adapter.c
@@ -703,7 +703,7 @@ static void bfdd_sessions_disable_interface(struct interface *ifp)
continue;
bfd_session_disable(bs);
-
+ bs->ifp = NULL;
}
}
@@ -752,6 +752,7 @@ void bfdd_sessions_disable_vrf(struct vrf *vrf)
continue;
bfd_session_disable(bs);
+ bs->vrf = NULL;
}
}
diff --git a/bfdd/subdir.am b/bfdd/subdir.am
index d0e3c1e8d..e572d4a3c 100644
--- a/bfdd/subdir.am
+++ b/bfdd/subdir.am
@@ -22,10 +22,18 @@ bfdd_libbfd_a_SOURCES = \
bfdd/bfd_packet.c \
bfdd/config.c \
bfdd/control.c \
+ bfdd/dplane.c \
bfdd/event.c \
bfdd/ptm_adapter.c \
# end
+# Install headers so it can be used by external data plane
+# implementations.
+bfdd_headersdir = $(pkgincludedir)/bfdd
+bfdd_headers_HEADERS = \
+ bfdd/bfddp_packet.h \
+ # end
+
clippy_scan += \
bfdd/bfdd_cli.c \
bfdd/bfdd_vty.c \