diff options
Diffstat (limited to 'src/lib/connection.c')
-rw-r--r-- | src/lib/connection.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/src/lib/connection.c b/src/lib/connection.c new file mode 100644 index 0000000..43f46cb --- /dev/null +++ b/src/lib/connection.c @@ -0,0 +1,306 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "lldpctl.h" +#include "atom.h" +#include "../compat/compat.h" +#include "../ctl.h" +#include "../log.h" + +const char * +lldpctl_get_default_transport(void) +{ + return LLDPD_CTL_SOCKET; +} + +/* Connect to the remote end */ +static int +sync_connect(lldpctl_conn_t *lldpctl) +{ + return ctl_connect(lldpctl->ctlname); +} + +/* Synchronously send data to remote end. */ +static ssize_t +sync_send(lldpctl_conn_t *lldpctl, const uint8_t *data, size_t length, void *user_data) +{ + struct lldpctl_conn_sync_t *conn = user_data; + ssize_t nb; + + if (conn->fd == -1 && ((conn->fd = sync_connect(lldpctl)) == -1)) { + return LLDPCTL_ERR_CANNOT_CONNECT; + } + + while ((nb = write(conn->fd, data, length)) == -1) { + if (errno == EAGAIN || errno == EINTR) continue; + return LLDPCTL_ERR_CALLBACK_FAILURE; + } + return nb; +} + +/* Statically receive data from remote end. */ +static ssize_t +sync_recv(lldpctl_conn_t *lldpctl, const uint8_t *data, size_t length, void *user_data) +{ + struct lldpctl_conn_sync_t *conn = user_data; + ssize_t nb; + size_t remain, offset = 0; + + if (conn->fd == -1 && ((conn->fd = sync_connect(lldpctl)) == -1)) { + lldpctl->error = LLDPCTL_ERR_CANNOT_CONNECT; + return LLDPCTL_ERR_CANNOT_CONNECT; + } + + remain = length; + do { + if ((nb = read(conn->fd, (unsigned char *)data + offset, remain)) == + -1) { + if (errno == EAGAIN || errno == EINTR) continue; + return LLDPCTL_ERR_CALLBACK_FAILURE; + } + remain -= nb; + offset += nb; + } while (remain > 0 && nb != 0); + return offset; +} + +lldpctl_conn_t * +lldpctl_new(lldpctl_send_callback send, lldpctl_recv_callback recv, void *user_data) +{ + return lldpctl_new_name(lldpctl_get_default_transport(), send, recv, user_data); +} + +lldpctl_conn_t * +lldpctl_new_name(const char *ctlname, lldpctl_send_callback send, + lldpctl_recv_callback recv, void *user_data) +{ + lldpctl_conn_t *conn = NULL; + struct lldpctl_conn_sync_t *data = NULL; + + /* Both callbacks are mandatory or should be NULL. */ + if (send && !recv) return NULL; + if (recv && !send) return NULL; + + if ((conn = calloc(1, sizeof(lldpctl_conn_t))) == NULL) return NULL; + + conn->ctlname = strdup(ctlname); + if (conn->ctlname == NULL) { + free(conn); + return NULL; + } + if (!send && !recv) { + if ((data = malloc(sizeof(struct lldpctl_conn_sync_t))) == NULL) { + free(conn->ctlname); + free(conn); + return NULL; + } + data->fd = -1; + conn->send = sync_send; + conn->recv = sync_recv; + conn->user_data = data; + } else { + conn->send = send; + conn->recv = recv; + conn->user_data = user_data; + } + + return conn; +} + +int +lldpctl_release(lldpctl_conn_t *conn) +{ + if (conn == NULL) return 0; + free(conn->ctlname); + if (conn->send == sync_send) { + struct lldpctl_conn_sync_t *data = conn->user_data; + if (data->fd != -1) close(data->fd); + free(conn->user_data); + } + free(conn->input_buffer); + free(conn->output_buffer); + free(conn); + return 0; +} + +/** + * Request some bytes if they are not already here. + * + * @param conn The connection to lldpd. + * @param length The number of requested bytes. + * @return A negative integer if we can't have the bytes or the number of bytes we got. + */ +ssize_t +_lldpctl_needs(lldpctl_conn_t *conn, size_t length) +{ + uint8_t *buffer; + ssize_t rc; + + if ((buffer = calloc(1, length)) == NULL) + return SET_ERROR(conn, LLDPCTL_ERR_NOMEM); + rc = conn->recv(conn, buffer, length, conn->user_data); + if (rc < 0) { + free(buffer); + return SET_ERROR(conn, rc); + } + if (rc == 0) { + free(buffer); + return SET_ERROR(conn, LLDPCTL_ERR_EOF); + } + rc = lldpctl_recv(conn, buffer, rc); + free(buffer); + if (rc < 0) return SET_ERROR(conn, rc); + RESET_ERROR(conn); + return rc; +} + +static int +check_for_notification(lldpctl_conn_t *conn) +{ + struct lldpd_neighbor_change *change; + void *p; + int rc; + lldpctl_change_t type; + lldpctl_atom_t *interface = NULL, *neighbor = NULL; + rc = ctl_msg_recv_unserialized(&conn->input_buffer, &conn->input_buffer_len, + NOTIFICATION, &p, &MARSHAL_INFO(lldpd_neighbor_change)); + if (rc != 0) return rc; + change = p; + + /* We have a notification, call the callback */ + if (conn->watch_cb || conn->watch_cb2) { + switch (change->state) { + case NEIGHBOR_CHANGE_DELETED: + type = lldpctl_c_deleted; + break; + case NEIGHBOR_CHANGE_ADDED: + type = lldpctl_c_added; + break; + case NEIGHBOR_CHANGE_UPDATED: + type = lldpctl_c_updated; + break; + default: + log_warnx("control", "unknown notification type (%d)", + change->state); + goto end; + } + interface = _lldpctl_new_atom(conn, atom_interface, change->ifname); + if (interface == NULL) goto end; + neighbor = + _lldpctl_new_atom(conn, atom_port, 0, NULL, change->neighbor, NULL); + if (neighbor == NULL) goto end; + if (conn->watch_cb) + conn->watch_cb(conn, type, interface, neighbor, + conn->watch_data); + else + conn->watch_cb2(type, interface, neighbor, conn->watch_data); + conn->watch_triggered = 1; + goto end; + } + +end: + if (interface) lldpctl_atom_dec_ref(interface); + if (neighbor) + lldpctl_atom_dec_ref(neighbor); + else { + lldpd_chassis_cleanup(change->neighbor->p_chassis, 1); + lldpd_port_cleanup(change->neighbor, 1); + free(change->neighbor); + } + free(change->ifname); + free(change); + + /* Indicate if more data remains in the buffer for processing */ + return (rc); +} + +ssize_t +lldpctl_recv(lldpctl_conn_t *conn, const uint8_t *data, size_t length) +{ + + RESET_ERROR(conn); + + if (length == 0) return 0; + + /* Received data should be appended to the input buffer. */ + if (conn->input_buffer == NULL) { + conn->input_buffer_len = 0; + if ((conn->input_buffer = malloc(length)) == NULL) + return SET_ERROR(conn, LLDPCTL_ERR_NOMEM); + } else { + uint8_t *new = + realloc(conn->input_buffer, conn->input_buffer_len + length); + if (new == NULL) return SET_ERROR(conn, LLDPCTL_ERR_NOMEM); + conn->input_buffer = new; + } + memcpy(conn->input_buffer + conn->input_buffer_len, data, length); + conn->input_buffer_len += length; + + /* Read all notifications */ + while (!check_for_notification(conn)) + ; + + RESET_ERROR(conn); + + return conn->input_buffer_len; +} + +int +lldpctl_process_conn_buffer(lldpctl_conn_t *conn) +{ + int rc; + + rc = check_for_notification(conn); + + RESET_ERROR(conn); + + return rc; +} + +ssize_t +lldpctl_send(lldpctl_conn_t *conn) +{ + /* Send waiting data. */ + ssize_t rc; + + RESET_ERROR(conn); + + if (!conn->output_buffer) return 0; + rc = conn->send(conn, conn->output_buffer, conn->output_buffer_len, + conn->user_data); + if (rc < 0) return SET_ERROR(conn, rc); + + /* "Shrink" the output buffer. */ + if (rc == conn->output_buffer_len) { + free(conn->output_buffer); + conn->output_buffer = NULL; + conn->output_buffer_len = 0; + RESET_ERROR(conn); + return rc; + } + conn->output_buffer_len -= rc; + memmove(conn->output_buffer, conn->output_buffer + rc, conn->output_buffer_len); + /* We don't shrink the buffer. It will be either freed or shrinked later */ + RESET_ERROR(conn); + return rc; +} |