#include <sys/stat.h>
#endif
+#ifdef HAVE_GLOB_H
+#include <glob.h>
+#endif
+
#include <fcntl.h>
+#ifdef WITH_DETAIL
+
#define USEC (1000000)
-typedef struct listen_detail_t {
- int delay_time; /* should be first entry */
- char *filename;
- VALUE_PAIR *vps;
- FILE *fp;
- int state;
- time_t timestamp;
- fr_ipaddr_t client_ip;
- int load_factor; /* 1..100 */
-
- int has_rtt;
- int srtt;
- int rttvar;
- struct timeval last_packet;
- RADCLIENT detail_client;
-} listen_detail_t;
-
-
-#define STATE_UNOPENED (0)
-#define STATE_UNLOCKED (1)
-#define STATE_HEADER (2)
-#define STATE_READING (3)
-#define STATE_QUEUED (4)
-#define STATE_RUNNING (5)
-#define STATE_NO_REPLY (6)
-#define STATE_REPLIED (7)
+static FR_NAME_NUMBER state_names[] = {
+ { "unopened", STATE_UNOPENED },
+ { "unlocked", STATE_UNLOCKED },
+ { "header", STATE_HEADER },
+ { "reading", STATE_READING },
+ { "queued", STATE_QUEUED },
+ { "running", STATE_RUNNING },
+ { "no-reply", STATE_NO_REPLY },
+ { "replied", STATE_REPLIED },
+
+ { NULL, 0 }
+};
/*
* If we're limiting outstanding packets, then mark the response
* caller it's OK to read more "detail" file stuff.
*/
if (request->reply->code == 0) {
- radius_signal_self(RADIUS_SIGNAL_SELF_DETAIL);
+ data->delay_time = data->retry_interval * USEC;
+ data->signal = 1;
data->state = STATE_NO_REPLY;
+
+ RDEBUG("Detail - No response configured for request %d. Will retry in %d seconds",
+ request->number, data->retry_interval);
+
+ radius_signal_self(RADIUS_SIGNAL_SELF_DETAIL);
return 0;
}
/*
- * We call gettimeofday a lot. But here it should be OK,
+ * We call gettimeofday a lot. But it should be OK,
* because there's nothing else to do.
*/
gettimeofday(&now, NULL);
*/
data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
- DEBUG2("RTT %d\tdelay %d", data->srtt, data->delay_time);
-
+ /*
+ * Cap delay at 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("Received response for request %d. Will read the next packet in %d seconds",
+ request->number, data->delay_time / USEC);
+
data->last_packet = now;
+ data->signal = 1;
data->state = STATE_REPLIED;
+ radius_signal_self(RADIUS_SIGNAL_SELF_DETAIL);
return 0;
}
static int detail_open(rad_listen_t *this)
{
struct stat st;
- char buffer[2048];
listen_detail_t *data = this->data;
+ char *filename = data->filename;
rad_assert(data->state == STATE_UNOPENED);
- snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
+ data->delay_time = USEC;
/*
* Open detail.work first, so we don't lose
* 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.
*/
- this->fd = open(buffer, O_RDWR);
+ this->fd = open(data->filename_work, O_RDWR);
if (this->fd < 0) {
+ DEBUG2("Polling for detail file %s", filename);
+
/*
* Try reading the detail file. If it
* doesn't exist, we can't do anything.
* exists, even if we don't have permissions
* to read it.
*/
- if (stat(data->filename, &st) < 0) {
+ if (stat(filename, &st) < 0) {
+#ifdef HAVE_GLOB_H
+ unsigned int i;
+ int found;
+ time_t chtime;
+ glob_t files;
+
+ memset(&files, 0, sizeof(files));
+ if (glob(filename, 0, NULL, &files) != 0) {
+ return 0;
+ }
+
+ 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) {
+ globfree(&files);
+ return 0;
+ }
+
+ filename = strdup(files.gl_pathv[found]);
+ globfree(&files);
+#else
return 0;
+#endif
}
/*
* Open it BEFORE we rename it, just to
* be safe...
*/
- this->fd = open(data->filename, O_RDWR);
+ this->fd = open(filename, O_RDWR);
if (this->fd < 0) {
- radlog(L_ERR, "Failed to open %s: %s",
- data->filename, strerror(errno));
+ radlog(L_ERR, "Detail - Failed to open %s: %s",
+ filename, strerror(errno));
+ if (filename != data->filename) free(filename);
return 0;
}
/*
* Rename detail to detail.work
*/
- if (rename(data->filename, buffer) < 0) {
+ DEBUG("Detail - Renaming %s -> %s", filename, data->filename_work);
+ if (rename(filename, data->filename_work) < 0) {
+ if (filename != data->filename) free(filename);
close(this->fd);
this->fd = -1;
return 0;
}
+ if (filename != data->filename) free(filename);
} /* else detail.work existed, and we opened it */
rad_assert(data->vps == NULL);
-
rad_assert(data->fp == NULL);
- data->fp = fdopen(this->fd, "r");
- if (!data->fp) {
- radlog(L_ERR, "Failed to re-open %s: %s",
- data->filename, strerror(errno));
- return 0;
- }
data->state = STATE_UNLOCKED;
data->client_ip.af = AF_UNSPEC;
data->timestamp = 0;
+ data->offset = 0;
+ data->packets = 0;
+ data->tries = 0;
return 1;
}
int detail_recv(rad_listen_t *listener,
RAD_REQUEST_FUNP *pfun, REQUEST **prequest)
{
- char key[256], value[1024];
+ char key[256], op[8], value[1024];
VALUE_PAIR *vp, **tail;
RADIUS_PACKET *packet;
char buffer[2048];
listen_detail_t *data = listener->data;
+ /*
+ * We may be in the main thread. It needs to update the
+ * timers before we try to read from the file again.
+ */
+ if (data->signal) return 0;
+
switch (data->state) {
case STATE_UNOPENED:
+ open_file:
rad_assert(listener->fd < 0);
- /*
- * FIXME: If the file doesn't exist, then
- * return "sleep for 1s", to avoid busy
- * looping.
- */
if (!detail_open(listener)) return 0;
rad_assert(data->state == STATE_UNLOCKED);
* radrelay.
*/
if (rad_lockfd_nonblock(listener->fd, 0) < 0) {
+ /*
+ * Close the FD. The main loop
+ * will wake up in a second and
+ * try again.
+ */
+ close(listener->fd);
+ listener->fd = -1;
+ data->state = STATE_UNOPENED;
return 0;
}
+
+ data->fp = fdopen(listener->fd, "r");
+ if (!data->fp) {
+ radlog(L_ERR, "FATAL: Failed to re-open detail file %s: %s",
+ data->filename, strerror(errno));
+ exit(1);
+ }
+
/*
* Look for the header
*/
data->state = STATE_HEADER;
+ data->delay_time = USEC;
data->vps = NULL;
/* FALL-THROUGH */
case STATE_HEADER:
do_header:
+ data->tries = 0;
+ if (!data->fp) {
+ data->state = STATE_UNOPENED;
+ goto open_file;
+ }
+
+ {
+ struct stat buf;
+
+ fstat(listener->fd, &buf);
+ if (((off_t) ftell(data->fp)) == buf.st_size) {
+ goto cleanup;
+ }
+ }
+
/*
* End of file. Delete it, and re-set
* everything.
*/
if (feof(data->fp)) {
cleanup:
- snprintf(buffer, sizeof(buffer),
- "%s.work", data->filename);
- unlink(buffer);
- fclose(data->fp); /* closes listener->fd */
+ DEBUG("Detail - unlinking %s",
+ data->filename_work);
+ unlink(data->filename_work);
+ if (data->fp) fclose(data->fp);
data->fp = NULL;
listener->fd = -1;
data->state = STATE_UNOPENED;
* we have.
*/
case STATE_READING:
- if (!feof(data->fp)) break;
+ if (data->fp && !feof(data->fp)) break;
data->state = STATE_QUEUED;
/* FALL-THROUGH */
goto alloc_packet;
/*
- * We still have an outstanding packet.
- * Don't read any more.
+ * Periodically check what's going on.
+ * If the request is taking too long,
+ * retry it.
*/
case STATE_RUNNING:
- return 0;
+ if (time(NULL) < (data->running + data->retry_interval)) {
+ return 0;
+ }
+
+ DEBUG("No response to detail request. Retrying");
+ data->state = STATE_NO_REPLY;
+ /* FALL-THROUGH */
/*
* If there's no reply, keep
* Read a header, OR a value-pair.
*/
while (fgets(buffer, sizeof(buffer), data->fp)) {
+ data->offset = ftell(data->fp); /* for statistics */
+
/*
* Badly formatted file: delete it.
*
*
* FIXME: print an error for badly formatted attributes?
*/
- if (sscanf(buffer, "%255s = %1023s", key, value) != 2) {
+ if (sscanf(buffer, "%255s %8s %1023s", key, op, value) != 3) {
+ DEBUG2("WARNING: Skipping badly formatted line %s",
+ buffer);
continue;
}
/*
+ * Should be =, :=, +=, ...
+ */
+ if (!strchr(op, '=')) continue;
+
+ /*
* Skip non-protocol attributes.
*/
if (!strcasecmp(key, "Request-Authenticator")) continue;
*/
if (!strcasecmp(key, "Timestamp")) {
data->timestamp = atoi(value);
+
+ vp = paircreate(PW_PACKET_ORIGINAL_TIMESTAMP,
+ PW_TYPE_DATE);
+ if (vp) {
+ vp->vp_date = (uint32_t) data->timestamp;
+ *tail = vp;
+ tail = &(vp->next);
+ }
continue;
}
*/
if (ferror(data->fp)) goto cleanup;
+ data->tries = 0;
+ data->packets++;
+
/*
* Process the packet.
*/
alloc_packet:
- rad_assert(data->state == STATE_QUEUED);
+ 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->state != STATE_QUEUED) {
+ radlog(L_ERR, "Truncated record: treating it as EOF for detail file %s", data->filename_work);
+ goto cleanup;
+ }
/*
* We're done reading the file, but we didn't read
*/
if (!data->vps) {
data->state = STATE_HEADER;
+ if (feof(data->fp)) goto cleanup;
return 0;
}
*/
packet = rad_alloc(1);
if (!packet) {
- data->state = STATE_NO_REPLY; /* try again later */
- return 0; /* maybe memory will magically free up... */
+ radlog(L_ERR, "FATAL: Failed allocating memory for detail");
+ exit(1);
}
memset(packet, 0, sizeof(*packet));
vp->vp_integer += time(NULL) - data->timestamp;
}
+ /*
+ * Set the transmission count.
+ */
+ vp = pairfind(packet->vps, PW_PACKET_TRANSMIT_COUNTER);
+ if (!vp) {
+ vp = paircreate(PW_PACKET_TRANSMIT_COUNTER, PW_TYPE_INTEGER);
+ rad_assert(vp != NULL);
+ pairadd(&packet->vps, vp);
+ }
+ vp->vp_integer = data->tries;
+
*pfun = rad_accounting;
if (debug_flag) {
- printf("detail_recv: Read packet from %s\n", data->filename);
+ fr_printf_log("detail_recv: Read packet from %s\n", data->filename_work);
for (vp = packet->vps; vp; vp = vp->next) {
- putchar('\t');
- vp_print(stdout, vp);
- putchar('\n');
+ debug_pair(vp);
}
}
}
data->state = STATE_RUNNING;
+ data->running = packet->timestamp;
return 1;
}
listen_detail_t *data = this->data;
free(data->filename);
+ data->filename = NULL;
pairfree(&data->vps);
- if (data->fp != NULL) fclose(data->fp);
+ if (data->fp != NULL) {
+ fclose(data->fp);
+ data->fp = NULL;
+ }
}
-int detail_print(rad_listen_t *this, char *buffer, size_t bufsize)
+int detail_print(const rad_listen_t *this, char *buffer, size_t bufsize)
{
if (!this->server) {
return snprintf(buffer, bufsize, "%s",
this->server);
}
-int detail_encode(UNUSED rad_listen_t *this, UNUSED REQUEST *request)
+/*
+ * Overloaded to return delay times.
+ */
+int detail_encode(rad_listen_t *this, UNUSED REQUEST *request)
{
+ listen_detail_t *data = this->data;
+
/*
- * We never encode responses "sent to" the detail file.
+ * We haven't sent a packet... delay things a bit.
*/
- return 0;
+ if (!data->signal) {
+ int delay = (data->poll_interval - 1) * USEC;
+
+ /*
+ * Add +/- 0.25s of jitter
+ */
+ delay += (USEC * 3) / 4;
+ delay += fr_rand() % (USEC / 2);
+
+ DEBUG2("Detail listener %s state %s signalled %d waiting %d.%06d sec",
+ data->filename,
+ fr_int2str(state_names, data->state, "?"), data->signal,
+ (delay / USEC), delay % USEC);
+
+ return delay;
+ }
+
+ data->signal = 0;
+
+ DEBUG2("Detail listener %s state %s signalled %d waiting %d.%06d sec",
+ data->filename, fr_int2str(state_names, data->state, "?"),
+ data->signal,
+ data->delay_time / USEC,
+ data->delay_time % USEC);
+
+ return data->delay_time;
}
-int detail_decode(UNUSED rad_listen_t *this, UNUSED REQUEST *request)
+
+/*
+ * Overloaded to return "should we fix delay times"
+ */
+int detail_decode(rad_listen_t *this, UNUSED REQUEST *request)
{
- /*
- * We never decode responses read from the detail file.
- */
- return 0;
+ listen_detail_t *data = this->data;
+
+ return data->signal;
}
offsetof(listen_detail_t, filename), NULL, NULL },
{ "load_factor", PW_TYPE_INTEGER,
offsetof(listen_detail_t, load_factor), NULL, Stringify(10)},
+ { "poll_interval", PW_TYPE_INTEGER,
+ offsetof(listen_detail_t, poll_interval), NULL, Stringify(1)},
+ { "retry_interval", PW_TYPE_INTEGER,
+ offsetof(listen_detail_t, retry_interval), NULL, Stringify(30)},
{ NULL, -1, 0, NULL, NULL } /* end the list */
};
+extern int check_config;
/*
* Parse a detail section.
int rcode;
listen_detail_t *data;
RADCLIENT *client;
+ char buffer[2048];
+
+ if (check_config) return 0;
if (!this->data) {
- this->data = rad_malloc(sizeof(listen_detail_t));
- memset(this->data, 0, sizeof(listen_detail_t));
+ this->data = rad_malloc(sizeof(*data));
+ memset(this->data, 0, sizeof(*data));
}
data = this->data;
return -1;
}
+ if ((data->poll_interval < 1) || (data->poll_interval > 20)) {
+ cf_log_err(cf_sectiontoitem(cs), "poll_interval must be between 1 and 20");
+ return -1;
+ }
+
+ /*
+ * 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
+ radlog(L_INFO, "WARNING: Detail file \"%s\" appears to use file globbing, but it is not supported on this system.", data->filename);
+#endif
+ strlcpy(buffer, data->filename, sizeof(buffer));
+ p = strrchr(buffer, FR_DIR_SEP);
+ if (p) {
+ p[1] = '\0';
+ } else {
+ buffer[0] = '\0';
+ }
+ strlcat(buffer, "detail.work",
+ sizeof(buffer) - strlen(buffer));
+
+ } else {
+ snprintf(buffer, sizeof(buffer), "%s.work", data->filename);
+ }
+
+ free(data->filename_work);
+ data->filename_work = strdup(buffer); /* FIXME: leaked */
+
data->vps = NULL;
data->fp = NULL;
data->state = STATE_UNOPENED;
+ 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.ipaddr.ip4addr.s_addr = INADDR_NONE;
client->prefix = 0;
client->longname = client->shortname = data->filename;
client->secret = client->shortname;
- client->nastype = "none";
+ client->nastype = strdup("none");
return 0;
}
+#endif