]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
start of detail file reader again
authorAlan T. DeKok <aland@freeradius.org>
Mon, 25 Sep 2017 20:15:28 +0000 (16:15 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 27 Sep 2017 16:25:23 +0000 (12:25 -0400)
src/modules/proto_detail/all.mk
src/modules/proto_detail/proto_detail_file.c
src/modules/proto_detail/proto_detail_file.h [deleted file]
src/modules/proto_detail/proto_detail_file.mk

index 8beba1c590aa8035db9090b9b7889a699f0cda61..fc5bc537f632a7bc5a0a96e949a6715f2a06d533 100644 (file)
@@ -1 +1 @@
-SUBMAKEFILES := proto_detail.mk proto_detail_process.mk
+SUBMAKEFILES := proto_detail.mk proto_detail_file.mk proto_detail_process.mk
index a0bbd67fc5872df347a862bab4bb96dc66530716..368aea1be0e564b5fc952a8fa1c2a2d97f4ed6f4 100644 (file)
@@ -1,8 +1,4 @@
 /*
- * proto_detail.c      Process the detail file
- *
- * Version:    $Id$
- *
  *   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
  *   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
- *
- * Copyright 2007  The FreeRADIUS server project
- * Copyright 2007  Alan DeKok <aland@deployingradius.com>
  */
 
-RCSID("$Id$")
-
+/**
+ * $Id$
+ * @file proto_detail_file.c
+ * @brief Detail handler for files
+ *
+ * @copyright 2016 The FreeRADIUS server project.
+ * @copyright 2016 Alan DeKok (aland@deployingradius.com)
+ */
+#include <netdb.h>
 #include <freeradius-devel/radiusd.h>
-#include <freeradius-devel/modules.h>
 #include <freeradius-devel/protocol.h>
-#include <freeradius-devel/process.h>
+#include <freeradius-devel/udp.h>
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/io/io.h>
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/io/listen.h>
 #include <freeradius-devel/rad_assert.h>
-
-#include "detail.h"
-
-#ifdef HAVE_SYS_STAT_H
-#include <sys/stat.h>
-#endif
-
-#ifdef HAVE_GLOB_H
-#include <glob.h>
-#endif
-
-#include <pthread.h>
+#include "proto_detail.h"
 
 #include <fcntl.h>
 
-#define USEC (1000000)
+typedef struct {
+       fr_time_t                       timestamp;              //!< when we read the entry.
+       off_t                           done_offset;            //!< where we're tracking the status
+} fr_detail_entry_t;
 
-static FR_NAME_NUMBER state_names[] = {
-       { "unopened", STATE_UNOPENED },
-       { "unlocked", STATE_UNLOCKED },
-       { "processing", STATE_PROCESSING },
+typedef struct {
+       proto_detail_t  const           *parent;                //!< The module that spawned us!
+       char const                      *name;                  //!< debug name for printing
 
-       { "header", STATE_HEADER },
-       { "vps", STATE_VPS },
-       { "queued", STATE_QUEUED },
-       { "running", STATE_RUNNING },
-       { "no-reply", STATE_NO_REPLY },
-       { "replied", STATE_REPLIED },
-
-       { NULL, 0 }
-};
+       int                             fd;                     //!< file descriptor
 
+       fr_event_list_t                 *el;                    //!< for various timers
+       fr_schedule_t                   *sc;                    //!< the scheduler, where we insert new readers
 
-/*
- *     If we're limiting outstanding packets, then mark the response
- *     as being sent.
- */
-static int detail_send(rad_listen_t *listener, REQUEST *request)
-{
-       char c = 0;
-       listen_detail_t *data = listener->data;
+       char const                      *filename;              //!< file name, usually with wildcards
+       char const                      *filename_work;         //!< work file name
 
-       rad_assert(request->listener == listener);
-       rad_assert(listener->send == detail_send);
+       bool                            vnode;                  //!< are we the vnode instance, or the filename_work instance?
+       bool                            eof;                    //!< are we at EOF on reading?
 
-       /*
-        *      This request timed out.  Remember that, and tell the
-        *      caller it's OK to read more "detail" file stuff.
-        */
-       if (request->reply->code == 0) {
-               data->delay_time = data->retry_interval * USEC;
-               data->signal = 1;
-               data->entry_state = STATE_NO_REPLY;
+       off_t                           header_offset;          //!< offset of the current header we're reading
+       off_t                           read_offset;            //!< where we're reading from in filename_work
+} proto_detail_file_t;
 
-               RDEBUG("detail (%s): No response to request.  Will retry in %d seconds",
-                      data->name, data->retry_interval);
-       } else {
-               int rtt;
-               struct timeval now;
-               /*
-                *      We call gettimeofday a lot.  But it should be OK,
-                *      because there's nothing else to do.
-                */
-               gettimeofday(&now, NULL);
+static const CONF_PARSER file_listen_config[] = {
+       { FR_CONF_OFFSET("filename", FR_TYPE_STRING | FR_TYPE_REQUIRED, proto_detail_file_t, filename ) },
 
-               /*
-                *      If we haven't sent a packet in the last second, reset
-                *      the RTT.
-                */
-               now.tv_sec -= 1;
-               if (fr_timeval_cmp(&data->last_packet, &now) < 0) {
-                       data->has_rtt = false;
-               }
-               now.tv_sec += 1;
-
-               /*
-                *      Only one detail packet may be outstanding at a time,
-                *      so it's safe to update some entries in the detail
-                *      structure.
-                *
-                *      We keep smoothed round trip time (SRTT), but not round
-                *      trip timeout (RTO).  We use SRTT to calculate a rough
-                *      load factor.
-                */
-               rtt = now.tv_sec - request->packet->timestamp.tv_sec;
-               rtt *= USEC;
-               rtt += now.tv_usec;
-               rtt -= request->packet->timestamp.tv_usec;
-
-               /*
-                *      If we're proxying, the RTT is our processing time,
-                *      plus the network delay there and back, plus the time
-                *      on the other end to process the packet.  Ideally, we
-                *      should remove the network delays from the RTT, but we
-                *      don't know what they are.
-                *
-                *      So, to be safe, we over-estimate the total cost of
-                *      processing the packet.
-                */
-               if (!data->has_rtt) {
-                       data->has_rtt = true;
-                       data->srtt = rtt;
-                       data->rttvar = rtt / 2;
-
-               } else {
-                       data->rttvar -= data->rttvar >> 2;
-                       data->rttvar += (data->srtt - rtt);
-                       data->srtt -= data->srtt >> 3;
-                       data->srtt += rtt >> 3;
-               }
-
-               /*
-                *      Calculate the time we wait before sending the next
-                *      packet.
-                *
-                *      rtt / (rtt + delay) = load_factor / 100
-                */
-               data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
+       CONF_PARSER_TERMINATOR
+};
 
-               /*
-                *      Cap delay at no less than 4 packets/s.  If the
-                *      end system can't handle this, then it's very
-                *      broken.
-                */
-               if (data->delay_time > (USEC / 4)) data->delay_time= USEC / 4;
 
-               RDEBUG3("detail (%s): Received response for request %" PRIu64 ".  "
-                       "Will read the next packet in %d seconds",
-                       data->name, request->number, data->delay_time / USEC);
+static int mod_decode(UNUSED void const *instance, REQUEST *request, UNUSED uint8_t *const data, UNUSED size_t data_len)
+{
 
-               data->last_packet = now;
-               data->signal = 1;
-               data->entry_state = STATE_REPLIED;
-               data->counter++;
-       }
+//     fr_detail_entry_t const                 *track = request->async->packet_ctx;
 
-       if (write(data->child_pipe[1], &c, 1) < 0) {
-               RERROR("detail (%s): Failed writing ack to reader thread: %s", data->name, fr_syserror(errno));
-       }
+       request->root = &main_config;
+       REQUEST_VERIFY(request);
 
        return 0;
 }
 
-
 /*
- *     Open the detail file, if we can.
- *
- *     FIXME: create it, if it's not already there, so that the main
- *     server select() will wake us up if there's anything to read.
+ *     @todo - put these into configuration!
  */
-static int detail_open(rad_listen_t *this)
-{
-       struct stat st;
-       listen_detail_t *data = this->data;
-
-       rad_assert(data->file_state == STATE_UNOPENED);
-       data->delay_time = USEC;
-
-       /*
-        *      Open detail.work first, so we don't lose
-        *      accounting packets.  It's probably better to
-        *      duplicate them than to lose them.
-        *
-        *      Note that we're not writing to the file, but
-        *      we've got to open it for writing in order to
-        *      establish the lock, to prevent rlm_detail from
-        *      writing to it.
-        *
-        *      This also means that if we're doing globbing,
-        *      this file will be read && processed before the
-        *      file globbing is done.
-        */
-       data->fp = NULL;
-       data->work_fd = open(data->filename_work, O_RDWR);
-
-       /*
-        *      Couldn't open it for a reason OTHER than "it doesn't
-        *      exist".  Complain and tell the admin.
-        */
-       if ((data->work_fd < 0) && (errno != ENOENT)) {
-               ERROR("Failed opening detail file %s: %s",
-                     data->filename_work, fr_syserror(errno));
-               return 0;
-       }
-
-       /*
-        *      The file doesn't exist.  Poll for it again.
-        */
-       if (data->work_fd < 0) {
-#ifndef HAVE_GLOB_H
-               return 0;
-#else
-               unsigned int    i;
-               int             found;
-               time_t          chtime;
-               char const      *filename;
-               glob_t          files;
-
-               DEBUG2("detail (%s): Polling for detail file", data->name);
-
-               memset(&files, 0, sizeof(files));
-               if (glob(data->filename, 0, NULL, &files) != 0) {
-               noop:
-                       globfree(&files);
-                       return 0;
-               }
-
-               /*
-                *      Loop over the glob'd files, looking for the
-                *      oldest one.
-                */
-               chtime = 0;
-               found = -1;
-               for (i = 0; i < files.gl_pathc; i++) {
-                       if (stat(files.gl_pathv[i], &st) < 0) continue;
-
-                       if ((i == 0) || (st.st_ctime < chtime)) {
-                               chtime = st.st_ctime;
-                               found = i;
-                       }
-               }
-
-               if (found < 0) goto noop;
-
-               /*
-                *      Rename detail to detail.work
-                */
-               filename = files.gl_pathv[found];
-
-               DEBUG("detail (%s): Renaming %s -> %s", data->name, filename, data->filename_work);
-               if (rename(filename, data->filename_work) < 0) {
-                       ERROR("detail (%s): Failed renaming %s to %s: %s",
-                             data->name, filename, data->filename_work, fr_syserror(errno));
-                       goto noop;
-               }
-
-               globfree(&files);       /* Shouldn't be using anything in files now */
-
-               /*
-                *      And try to open the filename.
-                */
-               data->work_fd = open(data->filename_work, O_RDWR);
-               if (data->work_fd < 0) {
-                       ERROR("detail (%s): Failed opening %s: %s",
-                             data->name, data->filename_work, fr_syserror(errno));
-                       return 0;
-               }
-#endif
-       } /* else detail.work existed, and we opened it */
-
-       rad_assert(data->vps == NULL);
-       rad_assert(data->fp == NULL);
-
-       data->file_state = STATE_UNLOCKED;
-
-       data->client_ip.af = AF_UNSPEC;
-       data->timestamp = 0;
-       data->offset = data->last_offset = data->timestamp_offset = 0;
-       data->packets = 0;
-       data->tries = 0;
-       data->done_entry = false;
-
-       return 1;
-}
-
+static uint32_t priorities[FR_MAX_PACKET_CODE] = {
+       [FR_CODE_ACCESS_REQUEST] = PRIORITY_HIGH,
+       [FR_CODE_ACCOUNTING_REQUEST] = PRIORITY_LOW,
+       [FR_CODE_COA_REQUEST] = PRIORITY_NORMAL,
+       [FR_CODE_DISCONNECT_REQUEST] = PRIORITY_NORMAL,
+       [FR_CODE_STATUS_SERVER] = PRIORITY_NOW,
+};
 
-/*
- *     FIXME: add a configuration "exit when done" so that the detail
- *     file reader can be used as a one-off tool to update stuff.
- *
- *     The time sequence for reading from the detail file is:
- *
- *     t_0             signalled that the server is idle, and we
- *                     can read from the detail file.
- *
- *     t_rtt           the packet has been processed successfully,
- *                     wait for t_delay to enforce load factor.
- *
- *     t_rtt + t_delay wait for signal that the server is idle.
- *
- */
-static int detail_recv(rad_listen_t *listener)
+static ssize_t mod_read(void *instance, void **packet_ctx, fr_time_t **recv_time, uint8_t *buffer, size_t buffer_len, size_t *leftover, uint32_t *priority)
 {
-       char c = 0;
-       ssize_t rcode;
-       RADIUS_PACKET *packet;
-       listen_detail_t *data = listener->data;
-       RAD_REQUEST_FUNP fun = NULL;
-
-       /*
-        *      Block until there's a packet ready.
-        */
-       rcode = read(data->master_pipe[0], &packet, sizeof(packet));
-       if (rcode <= 0) return rcode;
+       proto_detail_file_t             *inst = talloc_get_type_abort(instance, proto_detail_file_t);
 
-       rad_assert(packet != NULL);
+       ssize_t                         data_size;
+       size_t                          packet_len;
+       fr_detail_entry_t               *track;
+       uint8_t                         *partial, *end, *next, *p;
+       size_t                          room;
 
-       switch (packet->code) {
-       case FR_CODE_ACCOUNTING_REQUEST:
-               fun = rad_accounting;
-               break;
-
-       case FR_CODE_COA_REQUEST:
-       case FR_CODE_DISCONNECT_REQUEST:
-               fun = rad_coa_recv;
-               break;
-
-       default:
-               data->entry_state = STATE_REPLIED;
-               goto signal_thread;
-       }
-
-       if (!request_receive(NULL, listener, packet, &data->detail_client, fun)) {
-               data->entry_state = STATE_NO_REPLY;     /* try again later */
-
-       signal_thread:
-               fr_radius_free(&packet);
-               if (write(data->child_pipe[1], &c, 1) < 0) {
-                       ERROR("detail (%s): Failed writing ack to reader thread: %s", data->name,
-                             fr_syserror(errno));
-               }
-       }
+       rad_assert(*leftover < buffer_len);
 
        /*
-        *      Wait for the child thread to write an answer to the pipe
+        *      There will be "leftover" bytes left over in the buffer
+        *      from any previous read.  At the start of the file,
+        *      "leftover" will be zero.
         */
-       return 0;
-}
-
-static RADIUS_PACKET *detail_poll(rad_listen_t *listener)
-{
-       char            key[256], op[8], value[1024];
-       vp_cursor_t     cursor;
-       VALUE_PAIR      *vp;
-       RADIUS_PACKET   *packet;
-       char            buffer[2048];
-       listen_detail_t *data = listener->data;
-
-       switch (data->file_state) {
-       case STATE_UNOPENED:
-open_file:
-               rad_assert(data->work_fd < 0);
-
-               if (!detail_open(listener)) return NULL;
-
-               rad_assert(data->file_state == STATE_UNLOCKED);
-               rad_assert(data->work_fd >= 0);
-
-               /* FALL-THROUGH */
+        partial= buffer + *leftover;
+       room = buffer_len - *leftover;
 
        /*
-        *      Try to lock fd.  If we can't, return.
-        *      If we can, continue.  This means that
-        *      the server doesn't block while waiting
-        *      for the lock to open...
+        *      Try to read as much data as possible.
         */
-       case STATE_UNLOCKED:
-               /*
-                *      Note that we do NOT block waiting for
-                *      the lock.  We've re-named the file
-                *      above, so we've already guaranteed
-                *      that any *new* detail writer will not
-                *      be opening this file.  The only
-                *      purpose of the lock is to catch a race
-                *      condition where the execution
-                *      "ping-pongs" between radiusd &
-                *      radrelay.
-                */
-               if (rad_lockfd_nonblock(data->work_fd, 0) < 0) {
-                       /*
-                        *      Close the FD.  The main loop
-                        *      will wake up in a second and
-                        *      try again.
-                        */
-                       close(data->work_fd);
-                       data->fp = NULL;
-                       data->work_fd = -1;
-                       data->file_state = STATE_UNOPENED;
-                       return NULL;
-               }
+       if (!inst->eof) {
+               data_size = read(inst->fd, partial, room);
+               if (data_size < 0) return -1;
 
                /*
-                *      Only open for writing if we're
-                *      marking requests as completed.
+                *      Remember the read offset, or EOF.
                 */
-               data->fp = fdopen(data->work_fd, data->track ? "r+" : "r");
-               if (!data->fp) {
-                       ERROR("detail (%s): FATAL: Failed to re-open detail file: %s",
-                             data->name, fr_syserror(errno));
-                       fr_exit(1);
+               if (data_size > 0) {
+                       inst->read_offset = lseek(inst->fd, 0, SEEK_CUR);
+               } else {
+                       inst->eof = true;
                }
-
-               /*
-                *      Look for the header
-                */
-               data->file_state = STATE_PROCESSING;
-               data->entry_state = STATE_HEADER;
-               data->delay_time = USEC;
-               data->vps = NULL;
-               break;
-
+       } else {
                /*
-                *      Go to the next switch statement.
+                *      We didn't read any more data.
                 */
-       case STATE_PROCESSING:
-               break;
+               data_size = 0;
        }
 
-
-       switch (data->entry_state) {
-       case STATE_HEADER:
-       do_header:
-               data->done_entry = false;
-               data->timestamp_offset = 0;
-
-               data->tries = 0;
-               if (!data->fp) {
-                       data->file_state = STATE_UNOPENED;
-                       goto open_file;
-               }
-
-               {
-                       struct stat buf;
-
-                       if (fstat(data->work_fd, &buf) < 0) {
-                               ERROR("detail (%s): Failed to stat detail file: %s",
-                                     data->name, fr_syserror(errno));
-
-                               goto cleanup;
-                       }
-                       if (((off_t) ftell(data->fp)) == buf.st_size) { //-V595
-                               goto cleanup;
-                       }
-               }
-
-               /*
-                *      End of file.  Delete it, and re-set
-                *      everything.
-                */
-               if (feof(data->fp)) {
-               cleanup:
-                       DEBUG("detail (%s): Unlinking %s", data->name, data->filename_work);
-                       unlink(data->filename_work);
-                       if (data->fp) fclose(data->fp);
-                       data->fp = NULL;
-                       data->work_fd = -1;
-                       data->file_state = STATE_UNOPENED;
-                       rad_assert(data->vps == NULL);
-
-                       if (data->one_shot) {
-                               INFO("detail (%s): Finished reading \"one shot\" detail file - Exiting", data->name);
-                               radius_signal_self(RADIUS_SIGNAL_SELF_EXIT);
-                       }
-
-                       return NULL;
-               }
-
-               /*
-                *      Else go read something.
-                */
-               break;
-
-       /*
-        *      Read more value-pair's, unless we're
-        *      at EOF.  In that case, queue whatever
-        *      we have.
-        */
-       case STATE_VPS:
-               if (data->fp && !feof(data->fp)) break;
-               data->entry_state = STATE_QUEUED;
-
-               /* FALL-THROUGH */
-
-       case STATE_QUEUED:
-               goto alloc_packet;
-
        /*
-        *      Periodically check what's going on.
-        *      If the request is taking too long,
-        *      retry it.
+        *      Remember where the end of all of the data is.
         */
-       case STATE_RUNNING:
-               if (time(NULL) < (data->running + (int)data->retry_interval)) {
-                       return NULL;
-               }
-
-               DEBUG("detail (%s): No response to detail request.  Retrying", data->name);
-               /* FALL-THROUGH */
+       end = partial + data_size;
 
        /*
-        *      If there's no reply, keep
-        *      retransmitting the current packet
-        *      forever.
+        *      Look for "end of record" marker.  We've already
+        *      searched "leftover" bytes for \n\n, so we only have to
+        *      search the remaining bytes.
+        *
+        *      We MIGHT have the last character of the previously
+        *      read data as \n, so we back up one character here.
+        *      That lets us catch "\n\n" which crosses a read()
+        *      boundary.
         */
-       case STATE_NO_REPLY:
-               data->entry_state = STATE_QUEUED;
-               goto alloc_packet;
+       if (*leftover > 0) partial--;
 
        /*
-        *      We have a reply.  Clean up the old
-        *      request, and go read another one.
+        *      Note that all of the data MUST be printable, and raw
+        *      LFs are forbidden in attribute contents.
         */
-       case STATE_REPLIED:
-               if (data->track) {
-                       rad_assert(data->fp != NULL);
-
-                       if (fseek(data->fp, data->timestamp_offset, SEEK_SET) < 0) {
-                               WARN("detail (%s): Failed seeking to timestamp offset: %s",
-                                    data->name, fr_syserror(errno));
-                       } else if (fwrite("\tDone", 1, 5, data->fp) < 5) {
-                               WARN("detail (%s): Failed marking request as done: %s",
-                                    data->name, fr_syserror(errno));
-                       } else if (fflush(data->fp) != 0) {
-                               WARN("detail (%s): Failed flushing marked detail file to disk: %s",
-                                    data->name, fr_syserror(errno));
-                       }
-
-                       if (fseek(data->fp, data->offset, SEEK_SET) < 0) {
-                               WARN("detail (%s): Failed seeking to next detail request: %s",
-                                    data->name, fr_syserror(errno));
-                       }
+       next = NULL;
+       for (p = partial; p < end; p++) {
+               if (p[0] != '\n') continue;
+               if ((p + 1) == end) break; /* no more data */
+               if (p[1] == '\n') {
+                       next = p + 2;
+                       break;
                }
-
-               fr_pair_list_free(&data->vps);
-               data->entry_state = STATE_HEADER;
-               goto do_header;
        }
 
-       fr_pair_cursor_init(&cursor, &data->vps);
-
        /*
-        *      Read a header, OR a value-pair.
+        *      If there is a next record, remember how large this
+        *      record is, and update "leftover" bytes.
         */
-       while (fgets(buffer, sizeof(buffer), data->fp)) {
-               data->last_offset = data->offset;
-               data->offset = ftell(data->fp); /* for statistics */
-
-               /*
-                *      Badly formatted file: delete it.
-                *
-                *      FIXME: Maybe flag an error?
-                */
-               if (!strchr(buffer, '\n')) {
-                       fr_pair_list_free(&data->vps);
-                       goto cleanup;
-               }
-
-               /*
-                *      We're reading VP's, and got a blank line.
-                *      Queue the packet.
-                */
-               if ((data->entry_state == STATE_VPS) &&
-                   (buffer[0] == '\n')) {
-                       data->entry_state = STATE_QUEUED;
-                       break;
-               }
-
-               /*
-                *      Look for date/time header, and read VP's if
-                *      found.  If not, keep reading lines until we
-                *      find one.
-                */
-               if (data->entry_state == STATE_HEADER) {
-                       int y;
-
-                       if (sscanf(buffer, "%*s %*s %*d %*d:%*d:%*d %d", &y)) {
-                               data->entry_state = STATE_VPS;
-                       }
-                       continue;
-               }
-
-               /*
-                *      We have a full "attribute = value" line.
-                *      If it doesn't look reasonable, skip it.
-                *
-                *      FIXME: print an error for badly formatted attributes?
-                */
-               if (sscanf(buffer, "%255s %7s %1023s", key, op, value) != 3) {
-                       WARN("detail (%s): Skipping badly formatted line %s", data->name, buffer);
-                       continue;
-               }
-
-               /*
-                *      Should be =, :=, +=, ...
-                */
-               if (!strchr(op, '=')) {
-                       WARN("detail (%s): Skipping line without operator - %s", data->name, buffer);
-                       continue;
-               }
-
-               /*
-                *      Skip non-protocol attributes.
-                */
-               if (!strcasecmp(key, "Request-Authenticator")) continue;
+       if (next) {
+               packet_len = next - buffer;
+               *leftover = end - next;
 
+       } else if (!inst->eof) {
                /*
-                *      Set the original client IP address, based on
-                *      what's in the detail file.
-                *
-                *      Hmm... we don't set the server IP address.
-                *      or port.  Oh well.
+                *      We're not at EOF, and there is no "next"
+                *      entry.  Remember all of the leftover data in
+                *      the buffer, and ask the caller to call us when
+                *      there's more data.
                 */
-               if (!strcasecmp(key, "Client-IP-Address")) {
-                       data->client_ip.af = AF_INET;
-                       if (fr_inet_hton(&data->client_ip, AF_INET, value, false) < 0) {
-                               ERROR("detail (%s): Failed parsing Client-IP-Address", data->name);
-
-                               fr_pair_list_free(&data->vps);
-                               goto cleanup;
-                       }
-                       continue;
-               }
-
-               /*
-                *      The original time at which we received the
-                *      packet.  We need this to properly calculate
-                *      Acct-Delay-Time.
-                */
-               if (!strcasecmp(key, "Timestamp")) {
-                       data->timestamp = atoi(value);
-                       data->timestamp_offset = data->last_offset;
-
-                       vp = fr_pair_afrom_num(data, 0, FR_PACKET_ORIGINAL_TIMESTAMP);
-                       if (vp) {
-                               vp->vp_date = (uint32_t) data->timestamp;
-                               vp->type = VT_DATA;
-                               fr_pair_cursor_append(&cursor, vp);
-                       }
-                       continue;
-               }
-
-               if (!strcasecmp(key, "Donestamp")) {
-                       data->timestamp = atoi(value);
-                       data->done_entry = true;
-                       continue;
-               }
-
-               DEBUG3("detail (%s): Trying to read VP from line - %s", data->name, buffer);
+               *leftover = end - buffer;
+               return 0;
 
+       } else {
                /*
-                *      Read one VP.
-                *
-                *      FIXME: do we want to check for non-protocol
-                *      attributes like radsqlrelay does?
+                *      Else we're at EOF, it's OK to miss an "end of
+                *      record" marker.  We just eat all of the
+                *      remaining data.
                 */
-               vp = NULL;
-               if ((fr_pair_list_afrom_str(data, buffer, &vp) > 0) &&
-                   (vp != NULL)) {
-                       fr_pair_cursor_merge(&cursor, vp);
-               } else {
-                       WARN("detail (%s): Failed reading VP from line - %s", data->name, buffer);
-               }
+               packet_len = end - buffer;
+               *leftover = 0;
        }
 
        /*
-        *      Some kind of error.
-        *
-        *      FIXME: Leave the file in-place, and warn the
-        *      administrator?
+        *      Allocate the tracking entry.
         */
-       if (ferror(data->fp)) goto cleanup;
+       track = talloc(instance, fr_detail_entry_t);
+       track->timestamp = fr_time();
 
-       data->tries = 0;
-       data->packets++;
+       track->done_offset = 0;
 
        /*
-        *      Process the packet.
+        *      Search for the "Timestamp" attribute.  We overload
+        *      that to track which entries have been used.
         */
- alloc_packet:
-       if (data->done_entry) {
-               DEBUG2("detail (%s): Skipping record for timestamp %lu", data->name, data->timestamp);
-               fr_pair_list_free(&data->vps);
-               data->entry_state = STATE_HEADER;
-               goto do_header;
-       }
+       end = buffer + packet_len;
+       for (p = buffer; p < end; p++) {
+               if (p[0] != '\n') continue;
 
-       data->tries++;
 
-       /*
-        *      The writer doesn't check that the record was
-        *      completely written.  If the disk is full, this can
-        *      result in a truncated record.  When that happens,
-        *      treat it as EOF.
-        */
-       if (data->entry_state != STATE_QUEUED) {
-               ERROR("detail (%s): Truncated record: treating it as EOF for detail file %s",
-                     data->name, data->filename_work);
-               fr_pair_list_free(&data->vps);
-               goto cleanup;
-       }
+               if (((end - p) >= 5) &&
+                   (memcmp(p, "\tDone", 5) == 0)) {
+                       /*
+                        *      @todo - try to grab another packet
+                        *      from the buffer.  If there is a
+                        *      packet, memmove() the data to the
+                        *      start of the buffer, which is what
+                        *      fr_network_read() expects to see.
+                        */
+                       rad_assert(0 == 1);
+               }
 
-       /*
-        *      We're done reading the file, but we didn't read
-        *      anything.  Clean up, and don't return anything.
-        */
-       if (!data->vps) {
-               WARN("detail (%s): Read empty packet from file %s",
-                    data->name, data->filename_work);
-               data->entry_state = STATE_HEADER;
-               if (!data->fp || feof(data->fp)) goto cleanup;
-               return NULL;
+               if (((end - p) > 10) &&
+                   (memcmp(p, "\tTimestamp", 10) == 0)) {
+                       p += 2;
+                       track->done_offset = inst->header_offset + (p - buffer);
+               }
        }
 
        /*
-        *      Allocate the packet.  If we fail, it's a serious
-        *      problem.
+        *      Too big?  Ignore it.
         */
-       packet = fr_radius_alloc(NULL, true);
-       if (!packet) {
-               ERROR("detail (%s): FATAL: Failed allocating memory for detail", data->name);
-               fr_exit(1);
+       if (packet_len > inst->parent->max_packet_size) {
+               DEBUG("Ignoring 'too large' entry at offset %llu of %s",
+                     inst->header_offset, inst->filename_work);
+               DEBUG("Entry size %lu is greater than allowed maximum %u",
+                     packet_len, inst->parent->max_packet_size);
+               return 0;
        }
 
-       memset(packet, 0, sizeof(*packet));
-       packet->sockfd = -1;
-       packet->src_ipaddr.af = AF_INET;
-       packet->src_ipaddr.addr.v4.s_addr = htonl(INADDR_NONE);
-
        /*
-        *      If everything's OK, this is a waste of memory.
-        *      Otherwise, it lets us re-send the original packet
-        *      contents, unmolested.
+        *      We've read one more packet.
         */
-       packet->vps = fr_pair_list_copy(packet, data->vps);
-
-       packet->code = FR_CODE_ACCOUNTING_REQUEST;
-       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_TYPE, TAG_ANY);
-       if (vp) packet->code = vp->vp_uint32;
+       inst->header_offset += packet_len;
 
-       gettimeofday(&packet->timestamp, NULL);
+       *packet_ctx = track;
+       *recv_time = &track->timestamp;
+       *priority = priorities[buffer[0]];
 
        /*
-        *      Remember where it came from, so that we don't
-        *      proxy it to the place it came from...
+        *      We're done reading the file, but not the buffer.  Back
+        *      up one byte so that the network code will try to read
+        *      the byte again, which lets us then finish reading the
+        *      buffer.
+        *
+        *      We could make the network code smarter, to call our
+        *      read() routine again if there are leftover bytes.  But
+        *      that logic doesn't integrate well into the event loop.
+        *      So this hack is the next best thing.
         */
-       if (data->client_ip.af != AF_UNSPEC) {
-               packet->src_ipaddr = data->client_ip;
-       }
-
-       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_SRC_IP_ADDRESS, TAG_ANY);
-       if (vp) {
-               packet->src_ipaddr.af = AF_INET;
-               packet->src_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr;
-               packet->src_ipaddr.prefix = 32;
-       } else {
-               vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_SRC_IPV6_ADDRESS, TAG_ANY);
-               if (vp) {
-                       packet->src_ipaddr.af = AF_INET6;
-                       memcpy(&packet->src_ipaddr.addr.v6,
-                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
-                       packet->src_ipaddr.prefix = 128;
-               }
-       }
+       if (inst->eof && (*leftover > 0)) {
+               off_t hack;
 
-       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_DST_IP_ADDRESS, TAG_ANY);
-       if (vp) {
-               packet->dst_ipaddr.af = AF_INET;
-               packet->dst_ipaddr.addr.v4.s_addr = vp->vp_ipv4addr;
-               packet->dst_ipaddr.prefix = 32;
-       } else {
-               vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_DST_IPV6_ADDRESS, TAG_ANY);
-               if (vp) {
-                       packet->dst_ipaddr.af = AF_INET6;
-                       memcpy(&packet->dst_ipaddr.addr.v6,
-                              &vp->vp_ipv6addr, sizeof(vp->vp_ipv6addr));
-                       packet->dst_ipaddr.prefix = 128;
-               }
+               hack = inst->read_offset - 1;
+               (void) lseek(inst->fd, 0, SEEK_SET);
        }
 
-       /*
-        *      Generate packet ID, ports, IP via a counter.
-        */
-       packet->id = data->counter & 0xff;
-       packet->src_port = 1024 + ((data->counter >> 8) & 0xff);
-       packet->dst_port = 1024 + ((data->counter >> 16) & 0xff);
+       return packet_len;
+}
 
-       packet->dst_ipaddr.af = AF_INET;
-       packet->dst_ipaddr.addr.v4.s_addr = htonl((INADDR_LOOPBACK & ~0xffffff) | ((data->counter >> 24) & 0xff));
+static ssize_t mod_write(void *instance, void *packet_ctx,
+                        UNUSED fr_time_t request_time, uint8_t *buffer, size_t buffer_len)
+{
+       proto_detail_file_t             *inst = talloc_get_type_abort(instance, proto_detail_file_t);
+       fr_detail_entry_t               *track = packet_ctx;
 
-       /*
-        *      Create / update accounting attributes.
-        */
-       if (packet->code == FR_CODE_ACCOUNTING_REQUEST) {
-               /*
-                *      Prefer the Event-Timestamp in the packet, if it
-                *      exists.  That is when the event occurred, whereas the
-                *      "Timestamp" field is when we wrote the packet to the
-                *      detail file, which could have been much later.
-                */
-               vp = fr_pair_find_by_num(packet->vps, 0, FR_EVENT_TIMESTAMP, TAG_ANY);
-               if (vp) {
-                       data->timestamp = vp->vp_uint32;
-               }
+       if (buffer_len < 1) return -1;
 
-               /*
-                *      Look for Acct-Delay-Time, and update
-                *      based on Acct-Delay-Time += (time(NULL) - timestamp)
-                */
-               vp = fr_pair_find_by_num(packet->vps, 0, FR_ACCT_DELAY_TIME, TAG_ANY);
-               if (!vp) {
-                       vp = fr_pair_afrom_num(packet, 0, FR_ACCT_DELAY_TIME);
-                       rad_assert(vp != NULL);
-                       fr_pair_add(&packet->vps, vp);
-               }
-               if (data->timestamp != 0) {
-                       vp->vp_uint32 += time(NULL) - data->timestamp;
-               }
+       if (buffer[0] == 0) {
+               DEBUG3("Got Do-Not-Respond, not writing reply");
+               talloc_free(track);
+               return buffer_len;
        }
 
        /*
-        *      Set the transmission count.
+        *      Seek to the entry, mark it as done, and then seek to
+        *      the point in the file where we were reading from.
         */
-       vp = fr_pair_find_by_num(packet->vps, 0, FR_PACKET_TRANSMIT_COUNTER, TAG_ANY);
-       if (!vp) {
-               vp = fr_pair_afrom_num(packet, 0, FR_PACKET_TRANSMIT_COUNTER);
-               rad_assert(vp != NULL);
-               fr_pair_add(&packet->vps, vp);
+       if (track->done_offset > 0) {
+               (void) lseek(inst->fd, track->done_offset, SEEK_SET);
+               (void) write(inst->fd, "Done", 4);
+               (void) lseek(inst->fd, inst->read_offset, SEEK_SET);
        }
-       vp->vp_uint32 = data->tries;
 
-       data->entry_state = STATE_RUNNING;
-       data->running = packet->timestamp.tv_sec;
+       /*
+        *      @todo - add a used / free pool for these
+        */
+       talloc_free(track);
 
-       return packet;
+       return buffer_len;
 }
 
-/*
- *     Free detail-specific stuff.
+/** Open a UDP listener for RADIUS
+ *
+ * @param[in] instance of the RADIUS UDP I/O path.
+ * @return
+ *     - <0 on error
+ *     - 0 on success
  */
-static int _detail_free(listen_detail_t *data)
+static int mod_open(void *instance)
 {
-       if (!check_config) {
-               ssize_t ret;
-               void *arg = NULL;
+       proto_detail_file_t *inst = talloc_get_type_abort(instance, proto_detail_file_t);
 
-               /*
-                *      Mark the child pipes as unusable
-                */
-               close(data->child_pipe[0]);
-               close(data->child_pipe[1]);
-               data->child_pipe[0] = -1;
+       inst->fd = open(inst->filename_work, O_RDWR);
+       if (inst->fd < 0) return -1;
 
-               /*
-                *      Tell it to stop (interrupting its sleep)
-                */
-               pthread_kill(data->pthread_id, SIGTERM);
-
-               /*
-                *      Wait for it to acknowledge that it's stopped.
-                */
-               ret = read(data->master_pipe[0], &arg, sizeof(arg));
-               if (ret < 0) {
-                       ERROR("detail (%s): Reader thread exited without informing the master: %s",
-                             data->name, fr_syserror(errno));
-               } else if (ret != sizeof(arg)) {
-                       ERROR("detail (%s): Invalid thread pointer received from reader thread during exit",
-                             data->name);
-                       ERROR("detail (%s): Expected %zu bytes, got %zi bytes", data->name, sizeof(arg), ret);
-               }
+       rad_assert(inst->name == NULL);
+       inst->name = talloc_asprintf(inst, "detail working file %s", inst->filename_work);
 
-               close(data->master_pipe[0]);
-               close(data->master_pipe[1]);
-
-               if (arg) pthread_join(data->pthread_id, &arg);
-       }
-
-       if (data->fp != NULL) {
-               fclose(data->fp);
-               data->fp = NULL;
-       }
+       DEBUG("Listening om %s bound to virtual server %s",
+             inst->name, cf_section_name2(inst->parent->server_cs));
 
        return 0;
 }
 
-
-static int detail_print(rad_listen_t const *this, char *buffer, size_t bufsize)
+/** Get the file descriptor for this socket.
+ *
+ * @param[in] instance of the RADIUS UDP I/O path.
+ * @return the file descriptor
+ */
+static int mod_fd(void const *instance)
 {
-       if (!this->server) {
-               return snprintf(buffer, bufsize, "%s",
-                               ((listen_detail_t *)(this->data))->filename);
-       }
+       proto_detail_file_t const *inst = talloc_get_type_abort_const(instance, proto_detail_file_t);
 
-       return snprintf(buffer, bufsize, "detail file %s as server %s",
-                       ((listen_detail_t *)(this->data))->filename,
-                       this->server);
+       return inst->fd;
 }
 
 
-/*
- *     Delay while waiting for a file to be ready
+/** Set the event list for a new socket
+ *
+ * @param[in] instance of the RADIUS UDP I/O path.
+ * @param[in] el the event list
  */
-static int detail_delay(listen_detail_t *data)
+static void mod_event_list_set(void *instance, fr_event_list_t *el)
 {
-       int delay = (data->poll_interval - 1) * USEC;
+       proto_detail_file_t *inst;
 
-       /*
-        *      Add +/- 0.25s of jitter
-        */
-       delay += (USEC * 3) / 4;
-       delay += fr_rand() % (USEC / 2);
+       memcpy(&inst, &instance, sizeof(inst)); /* const issues */
 
-       DEBUG2("detail (%s): Detail listener state %s waiting %d.%06d sec",
-              data->name,
-              fr_int2str(state_names, data->entry_state, "?"),
-              (delay / USEC), delay % USEC);
+       inst = talloc_get_type_abort(instance, proto_detail_file_t);
 
-       return delay;
+       inst->el = el;
 }
 
-static int detail_encode(rad_listen_t *this, REQUEST *request)
-{
-       listen_detail_t *data = this->data;
-
-       RDEBUG2("detail (%s): Finished %s packet", data->name,
-               fr_packet_codes[request->packet->code]);
 
-       return 0;
-}
-
-static int detail_decode(rad_listen_t *this, REQUEST *request)
+static int mod_instantiate(UNUSED void *instance, UNUSED CONF_SECTION *cs)
 {
-       listen_detail_t *data = this->data;
+//     proto_detail_file_t *inst = talloc_get_type_abort(instance, proto_detail_file_t);
 
-       if (DEBUG_ENABLED2) {
-               RDEBUG2("detail (%s): Read %s packet from %s", data->name,
-                       fr_packet_codes[request->packet->code], data->filename_work);
-               rdebug_pair_list(L_DBG_LVL_1, request, request->packet->vps, NULL);
-       }
 
        return 0;
 }
 
-
-static void *detail_handler_thread(void *arg)
+static int mod_bootstrap(void *instance, UNUSED CONF_SECTION *cs)
 {
-       char c;
-       rad_listen_t *this = arg;
-       listen_detail_t *data = this->data;
-
-       while (true) {
-               RADIUS_PACKET *packet;
-
-               while ((packet = detail_poll(this)) == NULL) {
-                       usleep(detail_delay(data));
-
-                       /*
-                        *      If we're supposed to exit then tell
-                        *      the master thread we've exited.
-                        */
-                       if (data->child_pipe[0] < 0) {
-                               packet = NULL;
-                               if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
-                                       ERROR("detail (%s): Failed writing exit status to master: %s",
-                                             data->name, fr_syserror(errno));
-                               }
-                               return NULL;
-                       }
-               }
-
-               /*
-                *      Keep retrying forever.
-                *
-                *      FIXME: cap the retries.
-                */
-               do {
-                       if (write(data->master_pipe[1], &packet, sizeof(packet)) < 0) {
-                               ERROR("detail (%s): Failed passing detail packet pointer to master: %s",
-                                     data->name, fr_syserror(errno));
-                       }
-
-                       if (read(data->child_pipe[0], &c, 1) < 0) {
-                               ERROR("detail (%s): Failed getting detail packet ack from master: %s",
-                                     data->name, fr_syserror(errno));
-                               break;
-                       }
-
-                       if (data->delay_time > 0) usleep(data->delay_time);
-
-                       packet = detail_poll(this);
-                       if (!packet) break;
-               } while (data->entry_state != STATE_REPLIED);
-       }
-
-       return NULL;
-}
-
-
-static const CONF_PARSER detail_config[] = {
-       { FR_CONF_OFFSET("detail", FR_TYPE_FILE_OUTPUT | FR_TYPE_DEPRECATED, listen_detail_t, filename) },
-       { FR_CONF_OFFSET("filename", FR_TYPE_FILE_OUTPUT | FR_TYPE_REQUIRED, listen_detail_t, filename) },
-       { FR_CONF_OFFSET("load_factor", FR_TYPE_UINT32, listen_detail_t, load_factor), .dflt = STRINGIFY(10) },
-       { FR_CONF_OFFSET("poll_interval", FR_TYPE_UINT32, listen_detail_t, poll_interval), .dflt = STRINGIFY(1) },
-       { FR_CONF_OFFSET("retry_interval", FR_TYPE_UINT32, listen_detail_t, retry_interval), .dflt = STRINGIFY(30) },
-       { FR_CONF_OFFSET("one_shot", FR_TYPE_BOOL, listen_detail_t, one_shot), .dflt = "no" },
-       { FR_CONF_OFFSET("track", FR_TYPE_BOOL, listen_detail_t, track), .dflt = "no" },
-       CONF_PARSER_TERMINATOR
-};
-
-/*
- *     Parse a detail section.
- */
-static int detail_parse(CONF_SECTION *cs, rad_listen_t *this)
-{
-       int             rcode;
-       listen_detail_t *data;
-       RADCLIENT       *client;
-       char            buffer[2048];
-
-       data = this->data;
-
-       if (cf_section_rules_push(cs, detail_config) < 0) return -1;
-
-       rcode = cf_section_parse(data, data, cs);
-       if (rcode < 0) {
-               cf_log_err(cs, "Failed parsing listen section");
-               return -1;
-       }
-
-       data->name = cf_section_name2(cs);
-       if (!data->name) data->name = data->filename;
-
-       /*
-        *      We don't do duplicate detection for "detail" sockets.
-        */
-       this->nodup = true;
-
-       if (!data->filename) {
-               cf_log_err(cs, "No detail file specified in listen section");
-               return -1;
-       }
-
-       FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, >=, 1);
-       FR_INTEGER_BOUND_CHECK("load_factor", data->load_factor, <=, 100);
-
-       FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, >=, 1);
-       FR_INTEGER_BOUND_CHECK("poll_interval", data->poll_interval, <=, 60);
-
-       FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, >=, 4);
-       FR_INTEGER_BOUND_CHECK("retry_interval", data->retry_interval, <=, 3600);
+       proto_detail_file_t     *inst = talloc_get_type_abort(instance, proto_detail_file_t);
+       dl_instance_t const     *dl_inst;
 
        /*
-        *      Only checking the config.  Don't start threads or anything else.
+        *      Find the dl_instance_t holding our instance data
+        *      so we can find out what the parent of our instance
+        *      was.
         */
-       if (check_config) return 0;
-
-       /*
-        *      If the filename is a glob, use "detail.work" as the
-        *      work file name.
-        */
-       if ((strchr(data->filename, '*') != NULL) ||
-           (strchr(data->filename, '[') != NULL)) {
-               char *p;
-
-#ifndef HAVE_GLOB_H
-               WARN("detail (%s): File \"%s\" appears to use file globbing, but it is not supported on this system",
-                    data->name, data->filename);
-#endif
-               strlcpy(buffer, data->filename, sizeof(buffer));
-               p = strrchr(buffer, FR_DIR_SEP);
-               if (p) {
-                       p[1] = '\0';
-               } else {
-                       buffer[0] = '\0';
-               }
-
-               /*
-                *      Globbing cannot be done across directories.
-                */
-               if ((strchr(buffer, '*') != NULL) ||
-                   (strchr(buffer, '[') != NULL)) {
-                       cf_log_err(cs, "Wildcard directories are not supported");
-                       return -1;
-               }
-
-               strlcat(buffer, "detail.work",
-                       sizeof(buffer) - strlen(buffer));
+       dl_inst = dl_instance_find(instance);
+       rad_assert(dl_inst);
 
-       } else {
-               snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
-       }
-
-       data->filename_work = talloc_strdup(data, buffer);
-
-       data->work_fd = -1;
-       data->vps = NULL;
-       data->fp = NULL;
-       data->file_state = STATE_UNOPENED;
-       data->entry_state = STATE_HEADER;
-       data->delay_time = data->poll_interval * USEC;
-       data->signal = 1;
-
-       /*
-        *      Initialize the fake client.
-        */
-       client = &data->detail_client;
-       memset(client, 0, sizeof(*client));
-       client->ipaddr.af = AF_INET;
-       client->ipaddr.addr.v4.s_addr = INADDR_NONE;
-       client->ipaddr.prefix = 0;
-       client->longname = client->shortname = data->filename;
-       client->secret = client->shortname;
-       client->nas_type = talloc_strdup(data, "none"); /* Part of 'data' not dynamically allocated */
-
-       this->server_cs = cf_item_to_section(cf_parent(this->cs));
-       client->server_cs = this->server_cs;
+       inst->parent = talloc_get_type_abort(dl_inst->parent->data, proto_detail_t);
 
        return 0;
 }
 
-/*
- *     Open detail files
- */
-static int detail_socket_open(UNUSED CONF_SECTION *cs, rad_listen_t *this)
+static int mod_detach(void *instance)
 {
-       listen_detail_t *data;
-
-       data = this->data;
-       talloc_set_destructor(data, _detail_free);
+       proto_detail_file_t     *inst = talloc_get_type_abort(instance, proto_detail_file_t);
 
        /*
-        *      Create the communication pipes.
+        *      @todo - have our OWN event loop for timers, and a
+        *      "copy timer from -> to, which means we only have to
+        *      delete our child event loop from the parent on close.
         */
-       if (pipe(data->master_pipe) < 0) {
-               ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
-               fr_exit(1);
-       }
-
-       if (pipe(data->child_pipe) < 0) {
-               ERROR("detail (%s): Error opening internal pipe: %s", data->name, fr_syserror(errno));
-               fr_exit(1);
-       }
-
-       if (pthread_create(&data->pthread_id, NULL, detail_handler_thread, this) != 0) {
-               ERROR("detail (%s): Error creating detail reader thread: %s", data->name, fr_syserror(errno));
-               fr_exit(1);
-       }
-
-       this->fd = data->master_pipe[0];
 
+       close(inst->fd);
        return 0;
 }
 
-extern rad_protocol_t proto_detail;
-rad_protocol_t proto_detail = {
-       .magic = RLM_MODULE_INIT,
-       .name = "detail",
-       .inst_size = sizeof(listen_detail_t),
-       .tls = false,
-       .parse = detail_parse,
-       .open = detail_socket_open,
-       .recv = detail_recv,
-       .send = detail_send,
-       .print = detail_print,
-       .debug = common_packet_debug,
-       .encode = detail_encode,
-       .decode = detail_decode
+
+/** Private interface for use by proto_detail_file
+ *
+ */
+extern fr_app_io_t proto_detail_file;
+fr_app_io_t proto_detail_file = {
+       .magic                  = RLM_MODULE_INIT,
+       .name                   = "detail_file",
+       .config                 = file_listen_config,
+       .inst_size              = sizeof(proto_detail_file_t),
+       .detach                 = mod_detach,
+       .bootstrap              = mod_bootstrap,
+       .instantiate            = mod_instantiate,
+
+       .default_message_size   = 65536,
+
+       .open                   = mod_open,
+       .read                   = mod_read,
+       .decode                 = mod_decode,
+       .write                  = mod_write,
+       .fd                     = mod_fd,
+       .event_list_set         = mod_event_list_set,
 };
diff --git a/src/modules/proto_detail/proto_detail_file.h b/src/modules/proto_detail/proto_detail_file.h
deleted file mode 100644 (file)
index 2fa24fb..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- *  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_DETAIL_H
-#define _FR_DETAIL_H
-/**
- * $Id$
- *
- * @file proto_detail_file.h
- * @brief API to deserialise packets in detail file format and inject them into the server.
- *
- * @copyright 2015  The FreeRADIUS server project
- */
-RCSIDH(detail_h, "$Id$")
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef enum detail_file_state_t {
-       STATE_UNOPENED = 0,
-       STATE_UNLOCKED,
-       STATE_PROCESSING,
-} detail_file_state_t;
-
-typedef enum detail_entry_state_t {
-       STATE_HEADER = 0,
-       STATE_VPS,
-       STATE_QUEUED,
-       STATE_RUNNING,
-       STATE_NO_REPLY,
-       STATE_REPLIED
-} detail_entry_state_t;
-
-typedef struct listen_detail_t {
-       fr_event_timer_t const  *ev;    /* has to be first entry (ugh) */
-       char const      *name;                  //!< Identifier used in log messages
-       int             delay_time;
-       char const      *filename;
-       char const      *filename_work;
-       VALUE_PAIR      *vps;
-       int             work_fd;
-
-       int             master_pipe[2];
-       int             child_pipe[2];
-       pthread_t       pthread_id;
-
-       FILE            *fp;
-       off_t           offset;
-       detail_file_state_t     file_state;
-       detail_entry_state_t    entry_state;
-       time_t          timestamp;
-       time_t          running;
-       fr_ipaddr_t     client_ip;
-
-       off_t           last_offset;
-       off_t           timestamp_offset;
-       bool            done_entry;             //!< Are we done reading this entry?
-       bool            track;                  //!< Do we track progress through the file?
-
-       uint32_t        load_factor; /* 1..100 */
-       uint32_t        poll_interval;
-       uint32_t        retry_interval;
-
-       int             signal;
-       int             packets;
-       int             tries;
-       bool            one_shot;
-       int             outstanding;
-       int             has_rtt;
-       int             srtt;
-       int             rttvar;
-       uint32_t        counter;
-       struct timeval  last_packet;
-       RADCLIENT       detail_client;
-} listen_detail_t;
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _FR_DETAIL_H */
index 9f0d5d356e5247a220e291fbcf94af9b5b223b1e..61cca14b98e1bfd1ba37adabd5f13d90346810a8 100644 (file)
@@ -1,9 +1,9 @@
-TARGETNAME     := proto_detail
+TARGETNAME     := proto_detail_file
 
 ifneq "$(TARGETNAME)" ""
 TARGET         := $(TARGETNAME).a
 endif
 
-SOURCES                := proto_detail.c
+SOURCES                := proto_detail_file.c
 
 TGT_PREREQS    := libfreeradius-util.a libfreeradius-radius.a