summaryrefslogtreecommitdiffstats
path: root/bfdd/control.c
diff options
context:
space:
mode:
Diffstat (limited to 'bfdd/control.c')
-rw-r--r--bfdd/control.c845
1 files changed, 845 insertions, 0 deletions
diff --git a/bfdd/control.c b/bfdd/control.c
new file mode 100644
index 0000000..473843f
--- /dev/null
+++ b/bfdd/control.c
@@ -0,0 +1,845 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * 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
+ *
+ * control.c: implements the BFD daemon control socket. It will be used
+ * to talk with clients daemon/scripts/consumers.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include <sys/un.h>
+
+#include "bfd.h"
+
+/*
+ * Prototypes
+ */
+static int sock_set_nonblock(int fd);
+struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs);
+static void control_queue_free(struct bfd_control_socket *bcs,
+ struct bfd_control_queue *bcq);
+static int control_queue_dequeue(struct bfd_control_socket *bcs);
+static int control_queue_enqueue(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static int control_queue_enqueue_first(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+static void control_notifypeer_free(struct bfd_control_socket *bcs,
+ struct bfd_notify_peer *bnp);
+struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+
+
+struct bfd_control_socket *control_new(int sd);
+static void control_free(struct bfd_control_socket *bcs);
+static void control_reset_buf(struct bfd_control_buffer *bcb);
+static void control_read(struct thread *t);
+static void control_write(struct thread *t);
+
+static void control_handle_request_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_handle_request_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg);
+static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg);
+static void control_handle_notify_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_handle_notify_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void _control_handle_notify(struct hash_bucket *hb, void *arg);
+static void control_handle_notify(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_response(struct bfd_control_socket *bcs, uint16_t id,
+ const char *status, const char *error);
+
+static void _control_notify_config(struct bfd_control_socket *bcs,
+ const char *op, struct bfd_session *bs);
+static void _control_notify(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+
+
+/*
+ * Functions
+ */
+static int sock_set_nonblock(int fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ zlog_warn("%s: fcntl F_GETFL: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ flags |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ zlog_warn("%s: fcntl F_SETFL: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int control_init(const char *path)
+{
+ int sd;
+ mode_t umval;
+ struct sockaddr_un sun_ = {
+ .sun_family = AF_UNIX,
+ .sun_path = BFDD_CONTROL_SOCKET,
+ };
+
+ if (path)
+ strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path));
+
+ /* Remove previously created sockets. */
+ unlink(sun_.sun_path);
+
+ sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC);
+ if (sd == -1) {
+ zlog_err("%s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ umval = umask(0);
+ if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) {
+ zlog_err("%s: bind: %s", __func__, strerror(errno));
+ close(sd);
+ return -1;
+ }
+ umask(umval);
+
+ if (listen(sd, SOMAXCONN) == -1) {
+ zlog_err("%s: listen: %s", __func__, strerror(errno));
+ close(sd);
+ return -1;
+ }
+
+ sock_set_nonblock(sd);
+
+ bglobal.bg_csock = sd;
+
+ return 0;
+}
+
+void control_shutdown(void)
+{
+ struct bfd_control_socket *bcs;
+
+ thread_cancel(&bglobal.bg_csockev);
+
+ socket_close(&bglobal.bg_csock);
+
+ while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) {
+ bcs = TAILQ_FIRST(&bglobal.bg_bcslist);
+ control_free(bcs);
+ }
+}
+
+void control_accept(struct thread *t)
+{
+ int csock, sd = THREAD_FD(t);
+
+ csock = accept(sd, NULL, 0);
+ if (csock == -1) {
+ zlog_warn("%s: accept: %s", __func__, strerror(errno));
+ return;
+ }
+
+ control_new(csock);
+
+ thread_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev);
+}
+
+
+/*
+ * Client handling
+ */
+struct bfd_control_socket *control_new(int sd)
+{
+ struct bfd_control_socket *bcs;
+
+ bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs));
+
+ /* Disable notifications by default. */
+ bcs->bcs_notify = 0;
+
+ bcs->bcs_sd = sd;
+ thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev);
+
+ TAILQ_INIT(&bcs->bcs_bcqueue);
+ TAILQ_INIT(&bcs->bcs_bnplist);
+ TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry);
+
+ return bcs;
+}
+
+static void control_free(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+ struct bfd_notify_peer *bnp;
+
+ thread_cancel(&(bcs->bcs_ev));
+ thread_cancel(&(bcs->bcs_outev));
+
+ close(bcs->bcs_sd);
+
+ TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry);
+
+ /* Empty output queue. */
+ while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) {
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ control_queue_free(bcs, bcq);
+ }
+
+ /* Empty notification list. */
+ while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) {
+ bnp = TAILQ_FIRST(&bcs->bcs_bnplist);
+ control_notifypeer_free(bcs, bnp);
+ }
+
+ control_reset_buf(&bcs->bcs_bin);
+ XFREE(MTYPE_BFDD_CONTROL, bcs);
+}
+
+struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_notify_peer *bnp;
+
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ return bnp;
+
+ bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp));
+
+ TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry);
+ bnp->bnp_bs = bs;
+ bs->refcount++;
+
+ return bnp;
+}
+
+static void control_notifypeer_free(struct bfd_control_socket *bcs,
+ struct bfd_notify_peer *bnp)
+{
+ TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry);
+ bnp->bnp_bs->refcount--;
+ XFREE(MTYPE_BFDD_CONTROL, bnp);
+}
+
+struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_notify_peer *bnp;
+
+ TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) {
+ if (bnp->bnp_bs == bs)
+ return bnp;
+ }
+
+ return NULL;
+}
+
+struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+
+ bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq));
+
+ control_reset_buf(&bcq->bcq_bcb);
+ TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry);
+
+ return bcq;
+}
+
+static void control_queue_free(struct bfd_control_socket *bcs,
+ struct bfd_control_queue *bcq)
+{
+ control_reset_buf(&bcq->bcq_bcb);
+ TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry);
+ XFREE(MTYPE_BFDD_NOTIFICATION, bcq);
+}
+
+static int control_queue_dequeue(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+
+ /* List is empty, nothing to do. */
+ if (TAILQ_EMPTY(&bcs->bcs_bcqueue))
+ goto empty_list;
+
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ control_queue_free(bcs, bcq);
+
+ /* Get the next buffer to send. */
+ if (TAILQ_EMPTY(&bcs->bcs_bcqueue))
+ goto empty_list;
+
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ bcs->bcs_bout = &bcq->bcq_bcb;
+
+ bcs->bcs_outev = NULL;
+ thread_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+
+ return 1;
+
+empty_list:
+ thread_cancel(&(bcs->bcs_outev));
+ bcs->bcs_bout = NULL;
+ return 0;
+}
+
+static int control_queue_enqueue(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ struct bfd_control_queue *bcq;
+ struct bfd_control_buffer *bcb;
+
+ bcq = control_queue_new(bcs);
+
+ bcb = &bcq->bcq_bcb;
+ bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length);
+ bcb->bcb_pos = 0;
+ bcb->bcb_bcm = bcm;
+
+ /* If this is the first item, then dequeue and start using it. */
+ if (bcs->bcs_bout == NULL) {
+ bcs->bcs_bout = bcb;
+
+ /* New messages, active write events. */
+ thread_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ }
+
+ return 0;
+}
+
+static int control_queue_enqueue_first(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ struct bfd_control_queue *bcq, *bcqn;
+ struct bfd_control_buffer *bcb;
+
+ /* Enqueue it somewhere. */
+ if (control_queue_enqueue(bcs, bcm) == -1)
+ return -1;
+
+ /*
+ * The item is either the first or the last. So we must first
+ * check the best case where the item is already the first.
+ */
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ bcb = &bcq->bcq_bcb;
+ if (bcm == bcb->bcb_bcm)
+ return 0;
+
+ /*
+ * The item was not the first, so it is the last. We'll try to
+ * assign it to the head of the queue, however if there is a
+ * transfer in progress, then we have to make the item as the
+ * next one.
+ *
+ * Interrupting the transfer of in progress message will cause
+ * the client to lose track of the message position/data.
+ */
+ bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue);
+ TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry);
+ if (bcb->bcb_pos != 0) {
+ /*
+ * First position is already being sent, insert into
+ * second position.
+ */
+ TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry);
+ } else {
+ /*
+ * Old message didn't start being sent, we still have
+ * time to put this one in the head of the queue.
+ */
+ TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry);
+ bcb = &bcqn->bcq_bcb;
+ bcs->bcs_bout = bcb;
+ }
+
+ return 0;
+}
+
+static void control_reset_buf(struct bfd_control_buffer *bcb)
+{
+ /* Get ride of old data. */
+ XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf);
+ bcb->bcb_pos = 0;
+ bcb->bcb_left = 0;
+}
+
+static void control_read(struct thread *t)
+{
+ struct bfd_control_socket *bcs = THREAD_ARG(t);
+ struct bfd_control_buffer *bcb = &bcs->bcs_bin;
+ int sd = bcs->bcs_sd;
+ struct bfd_control_msg bcm;
+ ssize_t bread;
+ size_t plen;
+
+ /*
+ * Check if we have already downloaded message content, if so then skip
+ * to
+ * download the rest of it and process.
+ *
+ * Otherwise download a new message header and allocate the necessary
+ * memory.
+ */
+ if (bcb->bcb_buf != NULL)
+ goto skip_header;
+
+ bread = read(sd, &bcm, sizeof(bcm));
+ if (bread == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ zlog_warn("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ /* Validate header fields. */
+ plen = ntohl(bcm.bcm_length);
+ if (plen < 2) {
+ zlog_debug("%s: client closed due small message length: %d",
+ __func__, bcm.bcm_length);
+ control_free(bcs);
+ return;
+ }
+
+ if (bcm.bcm_ver != BMV_VERSION_1) {
+ zlog_debug("%s: client closed due bad version: %d", __func__,
+ bcm.bcm_ver);
+ control_free(bcs);
+ return;
+ }
+
+ /* Prepare the buffer to load the message. */
+ bcs->bcs_version = bcm.bcm_ver;
+ bcs->bcs_type = bcm.bcm_type;
+
+ bcb->bcb_pos = sizeof(bcm);
+ bcb->bcb_left = plen;
+ bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(bcm) + bcb->bcb_left + 1);
+ if (bcb->bcb_buf == NULL) {
+ zlog_warn("%s: not enough memory for message size: %zu",
+ __func__, bcb->bcb_left);
+ control_free(bcs);
+ return;
+ }
+
+ memcpy(bcb->bcb_buf, &bcm, sizeof(bcm));
+
+ /* Terminate data string with NULL for later processing. */
+ bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0;
+
+skip_header:
+ /* Download the remaining data of the message and process it. */
+ bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left);
+ if (bread == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ zlog_warn("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ bcb->bcb_pos += bread;
+ bcb->bcb_left -= bread;
+ /* We need more data, return to wait more. */
+ if (bcb->bcb_left > 0)
+ goto schedule_next_read;
+
+ switch (bcb->bcb_bcm->bcm_type) {
+ case BMT_REQUEST_ADD:
+ control_handle_request_add(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_REQUEST_DEL:
+ control_handle_request_del(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY:
+ control_handle_notify(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY_ADD:
+ control_handle_notify_add(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY_DEL:
+ control_handle_notify_del(bcs, bcb->bcb_bcm);
+ break;
+
+ default:
+ zlog_debug("%s: unhandled message type: %d", __func__,
+ bcb->bcb_bcm->bcm_type);
+ control_response(bcs, bcb->bcb_bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "invalid message type");
+ break;
+ }
+
+ bcs->bcs_version = 0;
+ bcs->bcs_type = 0;
+ control_reset_buf(bcb);
+
+schedule_next_read:
+ bcs->bcs_ev = NULL;
+ thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev);
+}
+
+static void control_write(struct thread *t)
+{
+ struct bfd_control_socket *bcs = THREAD_ARG(t);
+ struct bfd_control_buffer *bcb = bcs->bcs_bout;
+ int sd = bcs->bcs_sd;
+ ssize_t bwrite;
+
+ bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left);
+ if (bwrite == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bwrite < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ bcs->bcs_outev = NULL;
+ thread_add_write(master, control_write, bcs,
+ bcs->bcs_sd, &bcs->bcs_outev);
+ return;
+ }
+
+ zlog_warn("%s: write: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ bcb->bcb_pos += bwrite;
+ bcb->bcb_left -= bwrite;
+ if (bcb->bcb_left > 0) {
+ bcs->bcs_outev = NULL;
+ thread_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ return;
+ }
+
+ control_queue_dequeue(bcs);
+}
+
+
+/*
+ * Message processing
+ */
+static void control_handle_request_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_request_add(json) == 0)
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ else
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "request add failed");
+}
+
+static void control_handle_request_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_request_del(json) == 0)
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ else
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "request del failed");
+}
+
+static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc)
+{
+ struct peer_label *pl;
+
+ if (bpc->bpc_has_label) {
+ pl = pl_find(bpc->bpc_label);
+ if (pl)
+ return pl->pl_bs;
+ }
+
+ return bs_peer_find(bpc);
+}
+
+static void _control_handle_notify(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* Notify peer configuration. */
+ if (bcs->bcs_notify & BCM_NOTIFY_CONFIG)
+ _control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs);
+
+ /* Notify peer status. */
+ if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE)
+ _control_notify(bcs, bs);
+}
+
+static void control_handle_notify(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify));
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+
+ /*
+ * If peer asked for notification configuration, send everything that
+ * was configured until the moment to sync up.
+ */
+ if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE))
+ bfd_id_iterate(_control_handle_notify, bcs);
+}
+
+static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = _notify_find_peer(bpc);
+
+ if (bs == NULL)
+ return -1;
+
+ control_notifypeer_new(bcs, bs);
+
+ /* Notify peer status. */
+ _control_notify(bcs, bs);
+
+ return 0;
+}
+
+static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = _notify_find_peer(bpc);
+ struct bfd_notify_peer *bnp;
+
+ if (bs == NULL)
+ return -1;
+
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ control_notifypeer_free(bcs, bnp);
+
+ return 0;
+}
+
+static void control_handle_notify_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_notify_request(bcs, json, notify_add_cb) == 0) {
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ return;
+ }
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "failed to parse notify data");
+}
+
+static void control_handle_notify_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_notify_request(bcs, json, notify_del_cb) == 0) {
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ return;
+ }
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "failed to parse notify data");
+}
+
+
+/*
+ * Internal functions used by the BFD daemon.
+ */
+static void control_response(struct bfd_control_socket *bcs, uint16_t id,
+ const char *status, const char *error)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_response(status, error);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_response: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_RESPONSE;
+ bcm->bcm_id = id;
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue_first(bcs, bcm);
+}
+
+static void _control_notify(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_notify(bs);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_notify: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_NOTIFY;
+ bcm->bcm_id = htons(BCM_NOTIFY_ID);
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue(bcs, bcm);
+}
+
+int control_notify(struct bfd_session *bs, uint8_t notify_state)
+{
+ struct bfd_control_socket *bcs;
+ struct bfd_notify_peer *bnp;
+
+ /* Notify zebra listeners as well. */
+ ptm_bfd_notify(bs, notify_state);
+
+ /*
+ * PERFORMANCE: reuse the bfd_control_msg allocated data for
+ * all control sockets to avoid wasting memory.
+ */
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ /*
+ * Test for all notifications first, then search for
+ * specific peers.
+ */
+ if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) {
+ bnp = control_notifypeer_find(bcs, bs);
+ /*
+ * If the notification is not configured here,
+ * don't send it.
+ */
+ if (bnp == NULL)
+ continue;
+ }
+
+ _control_notify(bcs, bs);
+ }
+
+ return 0;
+}
+
+static void _control_notify_config(struct bfd_control_socket *bcs,
+ const char *op, struct bfd_session *bs)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_notify_config(op, bs);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_notify_config: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_NOTIFY;
+ bcm->bcm_id = htons(BCM_NOTIFY_ID);
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue(bcs, bcm);
+}
+
+int control_notify_config(const char *op, struct bfd_session *bs)
+{
+ struct bfd_control_socket *bcs;
+ struct bfd_notify_peer *bnp;
+
+ /* Remove the control sockets notification for this peer. */
+ if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) {
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ control_notifypeer_free(bcs, bnp);
+ }
+ }
+
+ /*
+ * PERFORMANCE: reuse the bfd_control_msg allocated data for
+ * all control sockets to avoid wasting memory.
+ */
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ /*
+ * Test for all notifications first, then search for
+ * specific peers.
+ */
+ if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0)
+ continue;
+
+ _control_notify_config(bcs, op, bs);
+ }
+
+ return 0;
+}