+/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
* Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
*
- * Permission to use, copy, modify, and distribute this software for any
+ * 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.
*
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "lldpd.h"
-
+#include <stdlib.h>
#include <unistd.h>
+#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <string.h>
+
+#include "ctl.h"
+#include "marshal.h"
+#include "log.h"
+#include "compat/compat.h"
+/**
+ * Create a new listening Unix socket for control protocol.
+ *
+ * @param name The name of the Unix socket.
+ * @return The socket when successful, -1 otherwise.
+ */
int
-ctl_create(char *name)
+ctl_create(const char *name)
{
int s;
struct sockaddr_un su;
int rc;
+ log_debug("control", "create control socket %s", name);
+
if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
return -1;
+ if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
+ close(s);
+ return -1;
+ }
su.sun_family = AF_UNIX;
- strlcpy(su.sun_path, name, UNIX_PATH_MAX);
+ strlcpy(su.sun_path, name, sizeof(su.sun_path));
if (bind(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) {
rc = errno; close(s); errno = rc;
return -1;
}
+
+ log_debug("control", "listen to control socket %s", name);
if (listen(s, 5) == -1) {
rc = errno; close(s); errno = rc;
+ log_debug("control", "cannot listen to control socket %s", name);
return -1;
}
return s;
}
+/**
+ * Connect to the control Unix socket.
+ *
+ * @param name The name of the Unix socket.
+ * @return The socket when successful, -1 otherwise.
+ */
int
-ctl_connect(char *name)
+ctl_connect(const char *name)
{
int s;
struct sockaddr_un su;
int rc;
+ log_debug("control", "connect to control socket %s", name);
+
if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
return -1;
su.sun_family = AF_UNIX;
- strlcpy(su.sun_path, name, UNIX_PATH_MAX);
+ strlcpy(su.sun_path, name, sizeof(su.sun_path));
if (connect(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) {
rc = errno;
- LLOG_WARN("unable to connect to socket " LLDPD_CTL_SOCKET);
+ log_warn("control", "unable to connect to socket %s", name);
+ close(s);
errno = rc; return -1;
}
return s;
}
+/**
+ * Remove the control Unix socket.
+ *
+ * @param name The name of the Unix socket.
+ */
void
-ctl_msg_init(struct hmsg *t, enum hmsg_type type)
-{
- memset(t, 0, MAX_HMSGSIZE);
- t->hdr.type = type;
- t->hdr.len = 0;
- t->hdr.pid = getpid();
-}
-
-int
-ctl_msg_send(int fd, struct hmsg *t)
-{
- return write(fd, t, t->hdr.len + sizeof(struct hmsg_hdr));
-}
-
-int
-ctl_msg_recv(int fd, struct hmsg *t)
-{
- int n;
- if ((n = read(fd, t, MAX_HMSGSIZE)) == -1) {
- return -1;
- }
- if (n < sizeof(struct hmsg_hdr)) {
- LLOG_WARNX("message received too short");
- errno = 0;
- return -1;
- }
- if (n != sizeof(struct hmsg_hdr) + t->hdr.len) {
- LLOG_WARNX("message from %d seems to be truncated (or too large)",
- t->hdr.pid);
- errno = 0;
- return -1;
- }
- return 1;
-}
-
-void
-ctl_cleanup(char *name)
+ctl_cleanup(const char *name)
{
+ log_debug("control", "cleanup control socket");
if (unlink(name) == -1)
- LLOG_WARN("unable to unlink %s", name);
+ log_warn("control", "unable to unlink %s", name);
}
-/* Packing/unpacking */
-
-/* This structure is used to track memory allocation when unpacking */
-struct gc {
- TAILQ_ENTRY(gc) next;
- void *pointer;
-};
-TAILQ_HEAD(gc_l, gc);
-
-typedef struct { char c; int16_t x; } st_int16;
-typedef struct { char c; int32_t x; } st_int32;
-typedef struct { char c; time_t x; } st_timet;
-typedef struct { char c; void *x; } st_void_p;
-
-#define INT16_ALIGN (sizeof(st_int16) - sizeof(int16_t))
-#define INT32_ALIGN (sizeof(st_int32) - sizeof(int32_t))
-#define TIMET_ALIGN (sizeof(st_timet) - sizeof(time_t))
-#define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *))
-
-struct formatdef {
- char format;
- int size;
- int alignment;
- int (*pack)(struct hmsg*, void **, void *,
- const struct formatdef *);
- int (*unpack)(struct hmsg*, void **, void *,
- const struct formatdef *, struct gc_l *);
-};
-
-/* void** is a pointer to a pointer to the end of struct hmsg*. It should be
- * updated. void* is a pointer to the entity to pack */
-
-static int
-ctl_alloc_pointer(struct gc_l *pointers, void *pointer)
+/**
+ * Serialize and "send" a structure through the control protocol.
+ *
+ * This function does not really send the message but outputs it to a buffer.
+ *
+ * @param output_buffer A pointer to a buffer to which the message will be
+ * appended. Can be @c NULL. In this case, the buffer will
+ * be allocated.
+ * @param[in,out] output_len The length of the provided buffer. Will be updated
+ * with the new length
+ * @param type The type of message we want to send.
+ * @param t The structure to be serialized and sent.
+ * @param mi The appropriate marshal structure for serialization.
+ * @return -1 in case of failure, 0 in case of success.
+ *
+ * Make sure this function logic matches the server-side one: @c levent_ctl_recv().
+ */
+int
+ctl_msg_send_unserialized(uint8_t **output_buffer, size_t *output_len,
+ enum hmsg_type type,
+ void *t, struct marshal_info *mi)
{
- struct gc *gpointer;
- if (pointers != NULL) {
- if ((gpointer = (struct gc *)calloc(1,
- sizeof(struct gc))) == NULL) {
- LLOG_WARN("unable to allocate memory for garbage collector");
+ ssize_t len = 0, newlen;
+ void *buffer = NULL;
+
+ log_debug("control", "send a message through control socket");
+ if (t) {
+ len = marshal_serialize_(mi, t, &buffer, 0, NULL, 0);
+ if (len <= 0) {
+ log_warnx("control", "unable to serialize data");
return -1;
}
- gpointer->pointer = pointer;
- TAILQ_INSERT_TAIL(pointers, gpointer, next);
- }
- return 0;
-}
-
-static void
-ctl_free_pointers(struct gc_l *pointers, int listonly)
-{
- struct gc *pointer, *pointer_next;
- for (pointer = TAILQ_FIRST(pointers);
- pointer != NULL;
- pointer = pointer_next) {
- pointer_next = TAILQ_NEXT(pointer, next);
- TAILQ_REMOVE(pointers, pointer, next);
- if (!listonly)
- free(pointer->pointer);
- free(pointer);
- }
-}
-
-static int
-pack_copy(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct)
-{
- if (h->hdr.len + ct->size > MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) {
- LLOG_WARNX("message became too large");
- return -1;
}
- memcpy(*p, s, ct->size);
- *p += ct->size;
- h->hdr.len += ct->size;
- return ct->size;
-}
-static int
-unpack_copy(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct, struct gc_l *pointers)
-{
- memcpy(s, *p, ct->size);
- *p += ct->size;
- return ct->size;
-}
-
-static int
-pack_string(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct)
-{
- int len, ss;
- if ((*(char**)s) == NULL)
- len = -1;
- else
- len = strlen(*(char**)s);
- if (h->hdr.len + len + sizeof(int) > MAX_HMSGSIZE -
- sizeof(struct hmsg_hdr)) {
- LLOG_WARNX("message became too large");
- return -1;
- }
- memcpy(*p, &len, sizeof(int));
- *p += sizeof(int);
- ss = sizeof(int);
- if (len != -1) {
- memcpy(*p, *(char **)s, len);
- *p += len;
- ss += len;
- }
- h->hdr.len += ss;
- return ss;
-}
+ newlen = len + sizeof(struct hmsg_header);
-static int
-unpack_string(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct, struct gc_l *pointers)
-{
- char *string;
- int len;
- memcpy(&len, *p, sizeof(int));
- *p += sizeof(int);
- if (len == -1) {
- string = NULL;
- } else {
- if ((string = (char *)calloc(1, len + 1)) == NULL) {
- LLOG_WARNX("unable to allocate new string");
+ if (*output_buffer == NULL) {
+ *output_len = 0;
+ if ((*output_buffer = malloc(newlen)) == NULL) {
+ log_warn("control", "no memory available");
+ free(buffer);
return -1;
}
- if (ctl_alloc_pointer(pointers, string) == -1) {
- free(string);
+ } else {
+ void *new = realloc(*output_buffer, *output_len + newlen);
+ if (new == NULL) {
+ log_warn("control", "no memory available");
+ free(buffer);
return -1;
}
- memcpy(string, *p, len);
- *p += len;
+ *output_buffer = new;
}
- memcpy(s, &string, sizeof(char *));
- return sizeof(char*);
+
+ struct hmsg_header hdr;
+ memset(&hdr, 0, sizeof(struct hmsg_header));
+ hdr.type = type;
+ hdr.len = len;
+ memcpy(*output_buffer + *output_len, &hdr, sizeof(struct hmsg_header));
+ if (t)
+ memcpy(*output_buffer + *output_len + sizeof(struct hmsg_header), buffer, len);
+ *output_len += newlen;
+ free(buffer);
+ return 0;
}
-static int
-pack_chars(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct)
+/**
+ * "Receive" and unserialize a structure through the control protocol.
+ *
+ * Like @c ctl_msg_send_unserialized(), this function uses buffer to receive the
+ * incoming message.
+ *
+ * @param[in,out] input_buffer The buffer with the incoming message. Will be
+ * updated once the message has been unserialized to
+ * point to the remaining of the message or will be
+ * freed if all the buffer has been consumed. Can be
+ * @c NULL.
+ * @param[in,out] input_len The length of the provided buffer. Will be updated
+ * to the length of remaining data once the message
+ * has been unserialized.
+ * @param expected_type The expected message type.
+ * @param[out] t Will contain a pointer to the unserialized structure.
+ * Can be @c NULL if we don't want to store the
+ * answer.
+ * @param mi The appropriate marshal structure for unserialization.
+ *
+ * @return -1 in case of error, 0 in case of success and the number of bytes we
+ * request to complete unserialization.
+ *
+ * When requesting a notification, the input buffer is left untouched if we
+ * don't get one and we fail silently.
+ */
+size_t
+ctl_msg_recv_unserialized(uint8_t **input_buffer, size_t *input_len,
+ enum hmsg_type expected_type,
+ void **t, struct marshal_info *mi)
{
- char *string;
- int string_len;
- string = *(char **)s;
- s += sizeof(char *);
- memcpy(&string_len, s, sizeof(int));
+ struct hmsg_header hdr;
+ int rc = -1;
- if (h->hdr.len + string_len + sizeof(int) > MAX_HMSGSIZE -
- sizeof(struct hmsg_hdr)) {
- LLOG_WARNX("message became too large");
- return -1;
+ if (*input_buffer == NULL ||
+ *input_len < sizeof(struct hmsg_header)) {
+ /* Not enough data. */
+ return sizeof(struct hmsg_header) - *input_len;
}
- memcpy(*p, &string_len, sizeof(int));
- *p += sizeof(int);
- memcpy(*p, string, string_len);
- *p += string_len;
- h->hdr.len += sizeof(int) + string_len;
- return sizeof(int) + string_len;
-}
-static int
-unpack_chars(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct, struct gc_l *pointers)
-{
- char *string;
- struct {
- char *string;
- int len;
- } reals __attribute__ ((__packed__));
- int len;
- memcpy(&len, *p, sizeof(int));
- *p += sizeof(int);
- if ((string = (char *)malloc(len)) == NULL) {
- LLOG_WARN("unable to allocate new string");
+ log_debug("control", "receive a message through control socket");
+ memcpy(&hdr, *input_buffer, sizeof(struct hmsg_header));
+ if (hdr.len > HMSG_MAX_SIZE) {
+ log_warnx("control", "message received is too large");
+ /* We discard the whole buffer */
+ free(*input_buffer);
+ *input_buffer = NULL;
+ *input_len = 0;
return -1;
}
- if (ctl_alloc_pointer(pointers, string) == -1) {
- free(string);
- return -1;
+ if (*input_len < sizeof(struct hmsg_header) + hdr.len) {
+ /* Not enough data. */
+ return sizeof(struct hmsg_header) + hdr.len - *input_len;
}
- memcpy(string, *p, len);
- *p += len;
- reals.string = string;
- reals.len = len;
- memcpy(s, &reals, sizeof(reals));
- return sizeof(char*);
-}
-
-static int
-pack_zero(struct hmsg *h, void **p, void *s,
- const struct formatdef *ct)
-{
- if (h->hdr.len + ct->size > MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) {
- LLOG_WARNX("message became too large");
- return -1;
+ if (hdr.type != expected_type) {
+ if (expected_type == NOTIFICATION) return -1;
+ log_warnx("control", "incorrect received message type (expected: %d, received: %d)",
+ expected_type, hdr.type);
+ goto end;
}
- memset(*p, 0, ct->size);
- *p += ct->size;
- h->hdr.len += ct->size;
- return ct->size;
-}
-
-static struct formatdef conv_table[] = {
- {'b', 1, 0,
- pack_copy, unpack_copy},
- {'w', 2, INT16_ALIGN,
- pack_copy, unpack_copy},
- {'l', 4, INT32_ALIGN,
- pack_copy, unpack_copy},
- {'t', sizeof(time_t), TIMET_ALIGN,
- pack_copy, unpack_copy},
- /* Null terminated string */
- {'s', sizeof(void*), VOID_P_ALIGN,
- pack_string, unpack_string},
- /* Pointer (is packed with 0) */
- {'P', sizeof(void*), VOID_P_ALIGN,
- pack_zero, unpack_copy},
- /* A list (same as pointer), should be at the beginning */
- {'L', sizeof(void*)*2, VOID_P_ALIGN,
- pack_zero, unpack_copy},
- /* Non null terminated string, followed by an int for the size */
- {'C', sizeof(void*) + sizeof(int), VOID_P_ALIGN,
- pack_chars, unpack_chars},
- {0}
-};
-/* Lists can be packed only if the "next" member is the first one of the
- * structure! No check is done for this. */
-struct fakelist_m {
- TAILQ_ENTRY(fakelist_m) next;
- void *data;
-};
-TAILQ_HEAD(fakelist_l, fakelist_m);
-
-static int ctl_msg_get_alignment(char *format)
-{
- char *f;
- int maxalign = 0, align;
- int paren = 0;
- struct formatdef *ce;
-
- /* We just want to get the maximum required alignment for the
- * structure. Instead of going recursive, we just count parentheses to
- * get the end of the structure. */
- for (f = format; *f != 0; f++) {
- if (*f == ')') {
- paren--;
- if (!paren)
- return maxalign;
- continue;
- } else if (*f == '(') {
- paren++;
- continue;
- } else {
- for (ce = conv_table;
- (ce->format != 0) && (ce->format != *f);
- ce++);
- align = ce->alignment;
- }
- if (align != 0)
- maxalign = (maxalign>align)?maxalign:align;
+ if (t && !hdr.len) {
+ log_warnx("control", "no payload available in answer");
+ goto end;
}
- if (paren)
- LLOG_WARNX("unbalanced parenthesis in format '%s'",
- format);
- return maxalign;
-}
-
-/* Define a stack of align values */
-struct stack_align {
- SLIST_ENTRY(stack_align) next;
- int align;
-};
-
-static int
-ctl_msg_packunpack_structure(char *format, void *structure, unsigned int size,
- struct hmsg *h, void **p, struct gc_l *pointers, int pack)
-{
- char *f;
- struct formatdef *ce = NULL;
- unsigned int csize = 0;
- uintptr_t offset;
- struct stack_align *align, *align_next;
- int talign;
- SLIST_HEAD(, stack_align) aligns;
-
- SLIST_INIT(&aligns);
- for (f = format; *f != 0; f++) {
- /* If we have a substructure, when entering into the structure,
- * we get the alignment and push it to the stack. When exiting
- * the structure, we pop the alignment from the stack and we do
- * the padding. This means that the whole structure should be
- * enclosed into parentheses, otherwise the padding won't
- * occur. */
- ce = NULL;
- if (*f == '(') {
- /* We need to align, compute the needed alignment */
- if ((align = calloc(1,
- sizeof(struct stack_align))) == NULL) {
- LLOG_WARN("unable to allocate memory "
- "for alignment stack");
- goto packunpack_error;
- }
- talign = align->align = ctl_msg_get_alignment(f);
- SLIST_INSERT_HEAD(&aligns, align, next);
- } else if (*f == ')') {
- /* We need to pad, retrieve the needed alignment */
- align = SLIST_FIRST(&aligns);
- talign = align->align;
- align_next = SLIST_NEXT(align, next);
- SLIST_REMOVE_HEAD(&aligns, next);
- free(align);
- } else {
- for (ce = conv_table;
- (ce->format != 0) && (ce->format != *f);
- ce++);
- if (ce->format != *f) {
- LLOG_WARNX("unknown format char %c", *f);
- goto packunpack_error;
- }
- talign = ce->alignment;
+ if (t) {
+ /* We have data to unserialize. */
+ if (marshal_unserialize_(mi, *input_buffer + sizeof(struct hmsg_header),
+ hdr.len, t, NULL, 0, 0) <= 0) {
+ log_warnx("control", "unable to deserialize received data");
+ goto end;
}
-
- /* Align the structure member */
- if (talign != 0) {
- offset = (uintptr_t)structure % talign;
- if (offset != 0) {
- structure += talign - offset;
- csize += talign - offset;
- }
- }
-
- if (!ce) continue;
-
- /* Check that the size is still ok */
- csize += ce->size;
- if (csize > size) {
- LLOG_WARNX("size of structure is too small for given "
- "format (%d vs %d)", size, csize);
- goto packunpack_error;
- }
-
- /* Pack or unpack */
- if (pack) {
- if (ce->pack(h, p, structure, ce) == -1) {
- LLOG_WARNX("error while packing %c in %s", *f,
- format);
- goto packunpack_error;
- }
- } else {
- if (ce->unpack(h, p, structure, ce, pointers) == -1) {
- LLOG_WARNX("error while unpacking %c", *f);
- goto packunpack_error;
- }
- }
- structure += ce->size;
- }
-
- if (size < csize) {
- LLOG_WARNX("size of structure does not match its "
- "declaration (%d vs %d)", size, csize);
- goto packunpack_error;
}
- if (!SLIST_EMPTY(&aligns)) {
- LLOG_WARNX("format is badly balanced ('%s')", format);
- goto packunpack_error;
- }
- return 0;
-
-packunpack_error:
- for (align = SLIST_FIRST(&aligns);
- align != NULL;
- align = align_next) {
- align_next = SLIST_NEXT(align, next);
- SLIST_REMOVE_HEAD(&aligns, next);
- free(align);
- }
- return -1;
-
-}
-
-int
-ctl_msg_pack_structure(char *format, void *structure, unsigned int size,
- struct hmsg *h, void **p)
-{
- return ctl_msg_packunpack_structure(format, structure, size, h, p, NULL, 1);
-}
-int
-ctl_msg_unpack_structure(char *format, void *structure, unsigned int size,
- struct hmsg *h, void **p)
-{
- struct gc_l pointers;
- int rc;
- TAILQ_INIT(&pointers);
- if ((rc = ctl_msg_packunpack_structure(format, structure, size,
- h, p, &pointers, 0)) == -1) {
- LLOG_WARNX("unable to unpack structure, freeing");
- ctl_free_pointers(&pointers, 0);
- return -1;
- }
- ctl_free_pointers(&pointers, 1);
+ rc = 0;
+end:
+ /* Discard input buffer */
+ *input_len -= sizeof(struct hmsg_header) + hdr.len;
+ if (*input_len == 0) {
+ free(*input_buffer);
+ *input_buffer = NULL;
+ } else
+ memmove(*input_buffer,
+ *input_buffer + sizeof(struct hmsg_header) + hdr.len,
+ *input_len);
return rc;
}
-
-int
-ctl_msg_pack_list(char *format, void *list, unsigned int size, struct hmsg *h, void **p)
-{
- struct fakelist_m *member;
- struct fakelist_l *flist = (struct fakelist_l *)list;
- TAILQ_FOREACH(member, flist, next) {
- if (ctl_msg_pack_structure(format, member, size, h, p) == -1) {
- LLOG_WARNX("error while packing list, aborting");
- return -1;
- }
- }
- return 0;
-}
-
-int
-ctl_msg_unpack_list(char *format, void *list, unsigned int size, struct hmsg *h, void **p)
-{
- struct fakelist_m *member, *member_next;
- struct gc_l pointers;
- struct fakelist_l *flist = (struct fakelist_l *)list;
- TAILQ_INIT(flist);
- TAILQ_INIT(&pointers);
- while (*p - (void *)h - sizeof(struct hmsg_hdr) < h->hdr.len) {
- if ((member = calloc(1, size)) == NULL) {
- LLOG_WARN("unable to allocate memory for structure");
- return -1;
- }
- if (ctl_msg_packunpack_structure(format, member, size,
- h, p, &pointers, 0) == -1) {
- LLOG_WARNX("unable to unpack list, aborting");
- free(member);
- /* Free each list member */
- for (member = TAILQ_FIRST(flist);
- member != NULL;
- member = member_next) {
- member_next = TAILQ_NEXT(member, next);
- TAILQ_REMOVE(flist, member, next);
- free(member);
- }
- ctl_free_pointers(&pointers, 0);
- return -1;
- }
- TAILQ_INSERT_TAIL(flist, member, next);
- }
- ctl_free_pointers(&pointers, 1);
- return 0;
-}