/********************************************************************* * 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 */ #include #include #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; }