/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2012 Vincent Bernat * * 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 #include #include #include #include #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; }