summaryrefslogtreecommitdiffstats
path: root/src/lib/connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/connection.c')
-rw-r--r--src/lib/connection.c306
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;
+}