#include <fcntl.h>
+#ifdef WITH_DETAIL
+
#define USEC (1000000)
-typedef struct listen_detail_t {
- int delay_time; /* should be first entry */
- char *filename;
- char *filename_work;
- VALUE_PAIR *vps;
- FILE *fp;
- int state;
- time_t timestamp;
- fr_ipaddr_t client_ip;
- int load_factor; /* 1..100 */
- int signal;
-
- 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) {
- data->delay_time = USEC;
+ 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);
* rtt / (rtt + delay) = load_factor / 100
*/
data->delay_time = (data->srtt * (100 - data->load_factor)) / (data->load_factor);
- if (data->delay_time == 0) data->delay_time = USEC / 10;
/*
* 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;
-
-#if 0
- DEBUG2("RTT %d\tdelay %d", data->srtt, data->delay_time);
-#endif
-
+
+ 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;
return 0;
}
-int detail_delay(rad_listen_t *listener)
-{
- listen_detail_t *data = listener->data;
-
- if (!data->signal) return 0;
-
- data->signal = 0;
-
- return data->delay_time;
-}
-
/*
* Open the detail file, if we can.
*/
if (stat(filename, &st) < 0) {
#ifdef HAVE_GLOB_H
- int i, found;
- time_t ctime;
+ unsigned int i;
+ int found;
+ time_t chtime;
glob_t files;
memset(&files, 0, sizeof(files));
return 0;
}
- ctime = 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 < ctime)) {
- ctime = st.st_ctime;
+ (st.st_ctime < chtime)) {
+ chtime = st.st_ctime;
found = i;
}
}
*/
this->fd = open(filename, O_RDWR);
if (this->fd < 0) {
- radlog(L_ERR, "Failed to open %s: %s",
+ 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
*/
- DEBUG("detail_recv: Renaming %s -> %s", filename, data->filename_work);
+ 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);
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:
* 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;
*/
if (feof(data->fp)) {
cleanup:
+ DEBUG("Detail - unlinking %s",
+ data->filename_work);
unlink(data->filename_work);
if (data->fp) fclose(data->fp);
data->fp = NULL;
* 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,
+ vp = paircreate(PW_PACKET_ORIGINAL_TIMESTAMP, 0,
PW_TYPE_DATE);
if (vp) {
vp->vp_date = (uint32_t) data->timestamp;
*/
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->src_ipaddr = data->client_ip;
}
- vp = pairfind(packet->vps, PW_PACKET_SRC_IP_ADDRESS);
+ vp = pairfind(packet->vps, PW_PACKET_SRC_IP_ADDRESS, 0);
if (vp) {
packet->src_ipaddr.af = AF_INET;
packet->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
} else {
- vp = pairfind(packet->vps, PW_PACKET_SRC_IPV6_ADDRESS);
+ vp = pairfind(packet->vps, PW_PACKET_SRC_IPV6_ADDRESS, 0);
if (vp) {
packet->src_ipaddr.af = AF_INET6;
memcpy(&packet->src_ipaddr.ipaddr.ip6addr,
}
}
- vp = pairfind(packet->vps, PW_PACKET_DST_IP_ADDRESS);
+ vp = pairfind(packet->vps, PW_PACKET_DST_IP_ADDRESS, 0);
if (vp) {
packet->dst_ipaddr.af = AF_INET;
packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
} else {
- vp = pairfind(packet->vps, PW_PACKET_DST_IPV6_ADDRESS);
+ vp = pairfind(packet->vps, PW_PACKET_DST_IPV6_ADDRESS, 0);
if (vp) {
packet->dst_ipaddr.af = AF_INET6;
memcpy(&packet->dst_ipaddr.ipaddr.ip6addr,
* Look for Acct-Delay-Time, and update
* based on Acct-Delay-Time += (time(NULL) - timestamp)
*/
- vp = pairfind(packet->vps, PW_ACCT_DELAY_TIME);
+ vp = pairfind(packet->vps, PW_ACCT_DELAY_TIME, 0);
if (!vp) {
- vp = paircreate(PW_ACCT_DELAY_TIME, PW_TYPE_INTEGER);
+ vp = paircreate(PW_ACCT_DELAY_TIME, 0, PW_TYPE_INTEGER);
rad_assert(vp != NULL);
pairadd(&packet->vps, vp);
}
vp->vp_integer += time(NULL) - data->timestamp;
}
+ /*
+ * Set the transmission count.
+ */
+ vp = pairfind(packet->vps, PW_PACKET_TRANSMIT_COUNTER, 0);
+ if (!vp) {
+ vp = paircreate(PW_PACKET_TRANSMIT_COUNTER, 0, PW_TYPE_INTEGER);
+ rad_assert(vp != NULL);
+ pairadd(&packet->vps, vp);
+ }
+ vp->vp_integer = data->tries;
+
*pfun = rad_accounting;
if (debug_flag) {
}
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.
RADCLIENT *client;
char buffer[2048];
+ if (check_config) return 0;
+
if (!this->data) {
this->data = rad_malloc(sizeof(*data));
memset(this->data, 0, sizeof(*data));
}
data = this->data;
- data->delay_time = USEC;
rcode = cf_section_parse(cs, data, detail_config);
if (rcode < 0) {
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.
data->vps = NULL;
data->fp = NULL;
data->state = STATE_UNOPENED;
+ data->delay_time = data->poll_interval * USEC;
+ data->signal = 1;
/*
* Initialize the fake client.
return 0;
}
+#endif