diff options
Diffstat (limited to 'bfdd/control.c')
-rw-r--r-- | bfdd/control.c | 845 |
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; +} |