]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add control plane API functions.
authorAlan T. DeKok <aland@freeradius.org>
Wed, 7 Dec 2016 20:18:29 +0000 (15:18 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 7 Dec 2016 22:32:28 +0000 (17:32 -0500)
Like channels, but much simpler, and used by channels.

because kevent() doesn't allow for sending multiple sets of data
in one "ident".  And, we don't want to have 1000 "ident"s registered.

src/util/all.mk
src/util/control.c [new file with mode: 0644]
src/util/control.h [new file with mode: 0644]

index 203d5c065444364c346eb9762224610692e726a3..70e1140a1ecbba8774398e8d263a1fb782c61587 100644 (file)
@@ -1,7 +1,7 @@
 TARGET := libfreeradius-util.a
 
 SOURCES        :=      ring_buffer.c message.c atomic_queue.c queue.c time.c channel.c track.c worker.c \
-               schedule.c receiver.c
+               schedule.c receiver.c control.c
 
 TGT_PREREQS    := libfreeradius-radius.la
 TGT_LDLIBS     := $(LIBS)
diff --git a/src/util/control.c b/src/util/control.c
new file mode 100644 (file)
index 0000000..f789a14
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ *   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; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @brief Control-plane signaling
+ * @file util/control.c
+ *
+ * @copyright 2016 Alan DeKok <aland@freeradius.org>
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/util/control.h>
+#include <freeradius-devel/util/ring_buffer.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <string.h>
+#include <sys/event.h>
+
+#define FR_CONTROL_SIGNAL      (1024)
+#define FR_CONTROL_MAX_MESSAGES (128)
+#define FR_CONTROL_MAX_SIZE    (64)
+
+/**
+ *  Status of control messages
+ */
+typedef enum fr_control_message_status_t {
+       FR_CONTROL_MESSAGE_FREE = 0,                    //!< the message is free
+       FR_CONTROL_MESSAGE_USED,                        //!< the message is used (set only by originator)
+       FR_CONTROL_MESSAGE_DONE                         //!< the message is done (set only by receiver)
+} fr_control_message_status_t;
+
+/**
+ *  The header for the control message
+ */
+typedef struct fr_control_message_t {
+       fr_control_message_status_t     status;         //!< status of this message
+       size_t                          data_size;      //!< size of the data we're sending
+} fr_control_message_t;
+
+/**
+ *  The control structure.
+ */
+struct fr_control_t {
+       int                     kq;                     //!< destination KQ
+
+       fr_atomic_queue_t       *aq;                    //!< destination AQ
+
+       fr_ring_buffer_t        *rb;                    //!< a ring buffer containing my messages to send.
+};
+
+
+/** Create a control-plane signaling path.
+ *
+ * @param[in] ctx the talloc context
+ * @param[in] kq the KQ descriptor where we will be sending signals
+ * @param[in] aq the atomic queue where we will be pushing message data
+ * @return
+ *     - NULL on error
+ *     - fr_control_t on success
+ */
+fr_control_t *fr_control_create(TALLOC_CTX *ctx, int kq, fr_atomic_queue_t *aq)
+{
+       fr_control_t *c;
+       struct kevent kev;
+
+       c = talloc_zero(ctx, fr_control_t);
+       if (!c) return NULL;
+
+       c->kq = kq;
+       c->aq = aq;
+
+       c->rb = fr_ring_buffer_create(c, FR_CONTROL_MAX_SIZE * FR_CONTROL_MAX_MESSAGES);
+       if (!c->rb) {
+               talloc_free(c);
+               return NULL;
+
+       }
+
+       /*
+        *      Tell the KQ to listen on our events.
+        *
+        *      We COULD overload the "ident" field with our channel
+        *      number, followed by the actual signal we're sending.
+        *      This would work.  The downside is that it would
+        *      require N*M EVFILT_USER kevents to be registered,
+        *      which is bad
+        *
+        *      The implementation here is perhaps a bit less optimal,
+        *      but it's clean, and it works.
+        */
+       EV_SET(&kev, FR_CONTROL_SIGNAL, EVFILT_USER, EV_ADD, NOTE_FFNOP, 0, NULL);
+       if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) {
+               talloc_free(c);
+               return NULL;
+       }
+
+       return c;
+}
+
+
+/** Clean up messages in a control-plane buffer
+ *
+ *  Find the oldest messages which are marked FR_CONTROL_MESSAGE_DONE,
+ *  and mark them FR_CONTROL_MESSAGE_FREE.
+ *
+ * @param[in] c the fr_control_t
+ * @return
+ *     - <0 there are still messages used
+ *     - 0 the control list is empty.
+ */
+int fr_control_gc(fr_control_t *c)
+{
+       while (true) {
+               size_t room, message_size;
+               fr_control_message_t *m;
+
+               (void) fr_ring_buffer_start(c->rb, (uint8_t **) &m, &room);
+               if (room == 0) break;
+
+               rad_assert(m != NULL);
+               rad_assert(room >= sizeof(*m));
+
+               rad_assert(m->status != FR_CONTROL_MESSAGE_FREE);
+
+               if (m->status != FR_CONTROL_MESSAGE_DONE) break;
+
+               m->status = FR_CONTROL_MESSAGE_FREE;
+
+               /*
+                *      Each message is aligned to a 64-byte boundary,
+                *      for cache contention issues.
+                */
+               message_size = sizeof(*m);
+               message_size += m->data_size;
+               message_size += 63;
+               message_size &= ~(size_t) 63;
+               fr_ring_buffer_free(c->rb, message_size);
+       }
+
+       /*
+        *      Maybe we failed to garbage collect everything?
+        */
+       if (fr_ring_buffer_used(c->rb) > 0) {
+               return -1;
+       }
+
+       return 0;
+}
+
+/** Free a control structure
+ *
+ *  This function really only calls the underlying "garbage collect".
+ *
+ * @param[in] c the control structure
+ */
+void fr_control_free(fr_control_t *c)
+{
+#ifndef NDEBUG
+       (void) talloc_get_type_abort(c, fr_control_t);
+#endif
+
+       (void) fr_control_gc(c);
+
+       rad_assert(fr_ring_buffer_used(c->rb) == 0);
+
+       talloc_free(c);
+}
+
+
+/** Allocate a control message
+ *
+ * @param[in] c the control structure
+ * @param[in] data the data to write to the control plane
+ * @param[in] data_size the size of the data to write to the control plane.
+ * @return
+ *     - NULL on error
+ *     - fr_message_t on success
+ */
+static fr_control_message_t *fr_control_message_alloc(fr_control_t *c, void *data, size_t data_size)
+{
+       size_t message_size;
+       fr_control_message_t *m;
+       uint8_t *p;
+
+       message_size = sizeof(*m);
+       message_size += data_size;
+       message_size += 63;
+       message_size &= ~(size_t) 63;
+
+       m = (fr_control_message_t *) fr_ring_buffer_alloc(c->rb, message_size);
+       if (!m) {
+               (void) fr_control_gc(c);
+               m = (fr_control_message_t *) fr_ring_buffer_alloc(c->rb, message_size);
+               if (!m) return NULL;
+       }
+
+       m->status = FR_CONTROL_MESSAGE_USED;
+       m->data_size = data_size;
+
+       p = (uint8_t *) m;
+       memcpy(p + sizeof(*m), data, data_size);
+
+       return m;
+
+}
+
+
+/** Send a control message with specific data.
+ *
+ *  This function is called ONLY from the originating thread.
+ *
+ * @param[in] c the control structure
+ * @param[in] data the data to write to the control plane
+ * @param[in] data_size the size of the data to write to the control plane.
+ * @return
+ *     - <0 on error
+ *     - 0 on success
+ */
+int fr_control_message_send(fr_control_t *c, void *data, size_t data_size)
+{
+       fr_control_message_t *m;
+       struct kevent kev;
+
+#ifndef NDEBUG
+       (void) talloc_get_type_abort(c, fr_control_t);
+#endif
+
+       /*
+        *      Get a message.  If we can't get one, do garbage
+        *      collection.  Get another, and if that fails, we're
+        *      done.
+        */
+       m = fr_control_message_alloc(c, data, data_size);
+       if (!m) {
+               (void) fr_control_gc(c);
+               m = fr_control_message_alloc(c, data, data_size);
+               if (!m) return -1;
+       }
+
+       if (!fr_atomic_queue_push(c->aq, m)) {
+               m->status = FR_CONTROL_MESSAGE_DONE;
+               return -1;
+       }
+
+       EV_SET(&kev, FR_CONTROL_SIGNAL, EVFILT_USER, EV_ENABLE, NOTE_TRIGGER, 0, NULL);
+       return kevent(c->kq, &kev, 1, NULL, 0, NULL);
+}
+
+
+/** Receive a control-plane message
+ *
+ *  This function is called ONLY from the receiving thread.
+ *
+ * @param[in] aq the recipients atomic queue for control-plane messages
+ * @param[in] kev the kevent for this receiver
+ * @param[in,out] data where the data is stored
+ * @param[in] data_size the size of the buffer where we store the data.
+ * @return
+ *     - <0 the size of the data we need to read the next message
+ *     - 0 this kevent is not for us.
+ *     - >0 the amount of data we've read
+ */
+ssize_t fr_control_message_receive(fr_atomic_queue_t *aq, struct kevent *kev, void *data, size_t data_size)
+{
+       uint8_t *p;
+       fr_control_message_t *m;
+
+       if (kev->ident != FR_CONTROL_SIGNAL) return 0;
+
+       if (!fr_atomic_queue_pop(aq, (void **) &m)) return 0;
+
+       rad_assert(m->status == FR_CONTROL_MESSAGE_USED);
+
+       /*
+        *      There isn't enough room to store the data, die.
+        */
+       if (data_size < m->data_size) {
+               return -(m->data_size);
+       }
+
+       p = (uint8_t *) m;
+       data_size = m->data_size;
+       memcpy(data, p + sizeof(*m), data_size);
+
+
+       m->status = FR_CONTROL_MESSAGE_DONE;
+       return data_size;
+}
diff --git a/src/util/control.h b/src/util/control.h
new file mode 100644 (file)
index 0000000..27bf86f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  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; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+#ifndef _FR_CONTROL_H
+#define _FR_CONTROL_H
+/**
+ * $Id$
+ *
+ * @file util/control.h
+ * @brief control-plane signaling
+ *
+ * @copyright 2016 Alan DeKok <aland@freeradius.org>
+ */
+RCSIDH(control_h, "$Id$")
+
+#include <freeradius-devel/util/atomic_queue.h>
+
+#include <sys/types.h>
+#include <sys/event.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ *  A one-way control plane signal.
+ *
+ *  Multiple-producer, single consumer.
+ */
+typedef struct fr_control_t fr_control_t;
+
+fr_control_t *fr_control_create(TALLOC_CTX *ctx, int kq, fr_atomic_queue_t *aq);
+void fr_control_free(fr_control_t *c);
+
+int fr_control_gc(fr_control_t *c) CC_HINT(nonnull);
+
+int fr_control_message_send(fr_control_t *c, void *data, size_t data_size);
+ssize_t fr_control_message_receive(fr_atomic_queue_t *aq, struct kevent *kev, void *data, size_t data_size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FR_CONTROL_H */