/** Read a packet and pairs from the network
*
* @param bio the packet-based bio
- * @param packet_p the output packet descriptor. Contains raw protocol data (IDs, counts, etc.)
- * @param ctx the talloc_ctx for the list
+ * @param request_ctx_p the request context associated with the response
+ * @param packet_p the response packet. Contains raw protocol data (IDs, counts, etc.)
+ * @param out_ctx talloc context for the list
* @param out the decoded pairs from the packet
* @return
* - <0 on error
* - 0 for success (*packet_p may still be NULL tho)
*/
-typedef int (*fr_bio_packet_read_t)(fr_bio_packet_t *bio, fr_packet_t **packet_p, TALLOC_CTX *ctx, fr_pair_list_t *out);
+typedef int (*fr_bio_packet_read_t)(fr_bio_packet_t *bio, void **request_ctx_p, fr_packet_t **packet_p, TALLOC_CTX *out_ctx, fr_pair_list_t *out);
/** Write a packet and pairs from the network
*
* @param bio the packet-based bio
- * @param packet the output packet descriptor. Contains raw protocol data (IDs, counts, etc.)
+ * @param request_ctx the request context
+ * @param packet the request packet. Contains raw protocol data (IDs, counts, etc.)
* @param list the pairs to encode in the packet
* @return
* - <0 on error (EOF, fail, etc,)
* - 0 for success
*/
-typedef int (*fr_bio_packet_write_t)(fr_bio_packet_t *bio, fr_packet_t *packet, fr_pair_list_t *list);
+typedef int (*fr_bio_packet_write_t)(fr_bio_packet_t *bio, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list);
/** Release an outgoing packet.
*
};
-static inline CC_HINT(nonnull) int fr_bio_packet_read(fr_bio_packet_t *bio, fr_packet_t **packet_p, TALLOC_CTX *ctx, fr_pair_list_t *out)
+static inline CC_HINT(nonnull) int fr_bio_packet_read(fr_bio_packet_t *bio, void **request_ctx_p, fr_packet_t **packet_p, TALLOC_CTX *out_ctx, fr_pair_list_t *out)
{
- return bio->read(bio, packet_p, ctx, out);
+ return bio->read(bio, request_ctx_p, packet_p, out_ctx, out);
}
-static inline CC_HINT(nonnull) int fr_bio_packet_write(fr_bio_packet_t *bio, fr_packet_t *packet, fr_pair_list_t *list)
+static inline CC_HINT(nonnull) int fr_bio_packet_write(fr_bio_packet_t *bio, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list)
{
- return bio->write(bio, packet, list);
+ return bio->write(bio, request_ctx, packet, list);
}
*/
#include <freeradius-devel/bio/bio_priv.h>
-#include <freeradius-devel/bio/retry.h>
#include <freeradius-devel/bio/null.h>
#include <freeradius-devel/bio/buf.h>
#include <freeradius-devel/util/rb.h>
#include <freeradius-devel/util/dlist.h>
+#define _BIO_RETRY_PRIVATE
+#include <freeradius-devel/bio/retry.h>
+
typedef struct fr_bio_retry_list_s fr_bio_retry_list_t;
typedef struct fr_bio_retry_s fr_bio_retry_t;
FR_DLIST_TYPES(fr_bio_retry_list)
struct fr_bio_retry_entry_s {
+ void *uctx;
+ void *packet_ctx;
+
union {
fr_rb_node_t node; //!< for the timers
FR_DLIST_ENTRY(fr_bio_retry_list) entry; //!< for the free list
fr_bio_retry_t *my; //!< so we can get to it from the event timer callback
fr_retry_t retry; //!< retry timers and counters
- void *packet_ctx;
uint8_t const *buffer;
size_t size;
size_t partial; //!< for partial writes :(
(void) fr_bio_retry_reset_timer(my);
}
- my->release((fr_bio_t *) my, item->packet_ctx, item->buffer, item->size, reason);
+ my->release((fr_bio_t *) my, item, reason);
#ifndef NDEBUG
item->buffer = NULL;
* @param item the retry context from #fr_bio_retry_save_t
* @return
* - <0 error
- * - 0 for success
+ * - 0 - didn't cancel
+ * - 1 - did cancel
*/
int fr_bio_retry_entry_cancel(fr_bio_t *bio, fr_bio_retry_entry_t *item)
{
item = fr_rb_last(&my->rb);
if (!item) return 0;
- if (!item->retry.replies) return -1; /* can't cancel it */
+ /*
+ * This item hasn't had a response, we can't cancel it.
+ */
+ if (!item->retry.replies) return 0;
}
fr_assert(item->buffer != NULL);
- if (item->cancelled) return 0;
+ if (item->cancelled) return 1;
/*
* If we've written a partial packet, jump through a bunch of hoops to cache the partial packet
if (my->first == item) my->first = NULL;
fr_bio_retry_release(my, item, item->have_reply ? FR_BIO_RETRY_DONE : FR_BIO_RETRY_CANCELLED);
- return 0;
+ return 1;
}
/** Set a per-packet retry config
talloc_const_free(my->ev);
/*
- * Cancel all outgoing packets.
+ * Cancel all outgoing packets. Don't bother updating the tree or the free list, as all of the
+ * entries will be deleted when the memory is freed.
*/
while ((item = fr_rb_iter_init_inorder(&iter, &my->rb)) != NULL) {
- (void) fr_rb_remove_by_inline_node(&my->rb, &item->node);
-#ifndef NDEBUG
- fr_bio_retry_list_insert_head(&my->free, item);
-#endif
- my->release((fr_bio_t *) my, item->packet_ctx, item->buffer, item->size, FR_BIO_RETRY_CANCELLED);
+ my->release((fr_bio_t *) my, item, FR_BIO_RETRY_CANCELLED);
}
my->first = NULL;
typedef struct fr_bio_retry_entry_s fr_bio_retry_entry_t;
+#ifndef _BIO_RETRY_PRIVATE
+struct fr_bio_retry_entry_s {
+ void *uctx; //!< user-writable context
+ void *packet_ctx; //!< packet_ctx from the write() call
+};
+#endif
+
typedef enum {
FR_BIO_RETRY_DONE = 0,
FR_BIO_RETRY_NO_REPLY,
* The packet will be cancelled after this call returns. The cancellation callback will NOT be run.
*
* @param bio the binary IO handler
- * @param packet_ctx per-packet context
- * @param buffer raw data for the packet
- * @param size size of the raw data
+ * @param retry_ctx the retry ctx to release
* @param reason why this packet is being released
*/
-typedef void (*fr_bio_retry_release_t)(fr_bio_t *bio, void *packet_ctx, const void *buffer, size_t size, fr_bio_retry_release_reason_t reason);
+typedef void (*fr_bio_retry_release_t)(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, fr_bio_retry_release_reason_t reason);
fr_bio_t *fr_bio_retry_alloc(TALLOC_CTX *ctx, size_t max_saved,
fr_bio_retry_sent_t sent,
return my;
}
-int fr_radius_client_fd_bio_write(fr_radius_client_fd_bio_t *my, fr_packet_t *packet, fr_pair_list_t *list)
+int fr_radius_client_fd_bio_write(fr_radius_client_fd_bio_t *my, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list)
{
ssize_t slen;
- fr_radius_client_packet_ctx_t *ctx;
+ fr_radius_id_ctx_t *id_ctx;
fr_assert(!packet->data);
return -1;
}
- if (fr_radius_code_id_pop(my->codes, packet) < 0) {
- fr_strerror_const("All IDs are in use");
- return -1;
- }
+ id_ctx = fr_radius_code_id_pop(my->codes, packet);
+ if (!id_ctx) {
+ /*
+ * Try to cancel the oldest one.
+ */
+ if (fr_bio_retry_entry_cancel(my->retry, NULL) < 1) {
+ all_ids_used:
+ fr_strerror_const("All IDs are in use");
+ return -1;
+ }
- /*
- * Initialize our client retry data structure.
- */
- ctx = packet->uctx;
- ctx->retry_ctx = NULL;
- ctx->packet = packet;
- ctx->reply = NULL;
+ id_ctx = fr_radius_code_id_pop(my->codes, packet);
+ if (!id_ctx) goto all_ids_used;
+ }
+ id_ctx->request_ctx = request_ctx;
+ fr_assert(id_ctx->packet == packet);
/*
* Encode the packet.
if (fr_packet_sign(packet, NULL, (char const *) my->cfg.verify.secret) < 0) goto fail;
- slen = fr_bio_write(my->common.bio, ctx, packet->data, packet->data_len);
+ slen = fr_bio_write(my->common.bio, &packet->socket, packet->data, packet->data_len);
if (slen <= 0) goto fail;
return 0;
}
-#if 0
+#if 1
static const fr_radius_packet_code_t allowed_replies[FR_RADIUS_CODE_MAX] = {
[FR_RADIUS_CODE_ACCESS_ACCEPT] = FR_RADIUS_CODE_ACCESS_REQUEST,
[FR_RADIUS_CODE_ACCESS_CHALLENGE] = FR_RADIUS_CODE_ACCESS_REQUEST,
[FR_RADIUS_CODE_PROTOCOL_ERROR] = FR_RADIUS_CODE_PROTOCOL_ERROR, /* Any */
};
-static void radius_client_retry_sent(UNUSED fr_bio_t *bio, void *packet_ctx, UNUSED const void *buffer, UNUSED size_t size,
+static void radius_client_retry_sent(fr_bio_t *bio, void *packet_ctx, const void *buffer, UNUSED size_t size,
fr_bio_retry_entry_t *retry_ctx)
{
- fr_radius_client_packet_ctx_t *ctx = packet_ctx;
+ fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio->uctx, fr_radius_client_fd_bio_t);
+ fr_radius_id_ctx_t *id_ctx;
+ uint8_t const *data = buffer;
- ctx->retry_ctx = retry_ctx;
+ id_ctx = fr_radius_code_id_find(my->codes, data[0], data[1]);
+ fr_assert(id_ctx != NULL);
+
+ id_ctx->packet = packet_ctx;
+ id_ctx->retry_ctx = retry_ctx;
+ retry_ctx->uctx = id_ctx;
}
-static bool radius_client_retry_response(fr_bio_t *bio, fr_bio_retry_entry_t **item_p, void *packet_ctx, const void *buffer, UNUSED size_t size)
+static bool radius_client_retry_response(fr_bio_t *bio, fr_bio_retry_entry_t **retry_ctx_p, UNUSED void *packet_ctx, const void *buffer, UNUSED size_t size)
{
fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio->uctx, fr_radius_client_fd_bio_t);
- fr_radius_client_packet_ctx_t *ctx, *reply_ctx;
unsigned int code;
uint8_t *data = UNCONST(uint8_t *, buffer); /* @todo - for verify() */
- fr_packet_t *packet;
+ fr_radius_id_ctx_t *id_ctx;
/*
* We now have a complete packet in our buffer. Check if it is one we expect.
*/
- if (!data[0] || (data[0] >= FR_RADIUS_CODE_MAX)) {
- return false;
- }
+ fr_assert(data[0] > 0);
+ fr_assert(data[0] < FR_RADIUS_CODE_MAX);
/*
* Is the code an allowed reply code?
/*
* It's a reply, but not a permitted reply to a particular request.
*
- * @todo - for protocol error, look up original packet code <sigh>
+ * @todo - Status-Server. And for protocol error, look up original packet code
*/
- packet = fr_radius_code_id_find(my->codes, code, data[1]);
- if (!packet) return false;
-
- ctx = packet->uctx;
- reply_ctx = packet_ctx;
+ id_ctx = fr_radius_code_id_find(my->codes, allowed_replies[code], data[1]);
+ if (!id_ctx) return false;
/*
* No reply yet, verify the response packet, and save it for later.
*/
- if (!ctx->reply) {
- if (fr_radius_verify(data, packet->data + 4,
+ if (!id_ctx->response) {
+ if (fr_radius_verify(data,id_ctx-> packet->data + 4,
my->cfg.verify.secret, my->cfg.verify.secret_len,
my->cfg.verify.require_message_authenticator) < 0) {
return false;
}
- *item_p = ctx->retry_ctx;
-
- reply_ctx->packet = packet;
+ *retry_ctx_p = id_ctx->retry_ctx;
return true;
}
* The reply has the correct ID / Code, but it's not the
* same as our previous reply: ignore it.
*/
- if (memcmp(buffer, ctx->reply, RADIUS_HEADER_LENGTH) != 0) return false;
+ if (memcmp(buffer, id_ctx->response->data, RADIUS_HEADER_LENGTH) != 0) return false;
/*
* Tell the caller that it's a duplicate reply.
*/
- *item_p = ctx->retry_ctx;
+ *retry_ctx_p = id_ctx->retry_ctx;
return false;
}
-static void radius_client_retry_release(fr_bio_t *bio, void *packet_ctx, UNUSED const void *buffer, UNUSED size_t size, UNUSED fr_bio_retry_release_reason_t reason)
+static void radius_client_retry_release(fr_bio_t *bio, fr_bio_retry_entry_t *retry_ctx, UNUSED fr_bio_retry_release_reason_t reason)
{
fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio->uctx, fr_radius_client_fd_bio_t);
- fr_radius_client_packet_ctx_t *ctx = packet_ctx;
+ fr_radius_id_ctx_t *id_ctx = retry_ctx->uctx;
- fr_radius_code_id_push(my->codes, ctx->packet);
+ fr_radius_code_id_push(my->codes, id_ctx->packet);
}
#endif
-int fr_radius_client_fd_bio_read(fr_bio_packet_t *bio, fr_packet_t **packet_p,
- TALLOC_CTX *pair_ctx, fr_pair_list_t *out)
+int fr_radius_client_fd_bio_read(fr_bio_packet_t *bio, void **request_ctx_p, fr_packet_t **packet_p,
+ TALLOC_CTX *out_ctx, fr_pair_list_t *out)
{
ssize_t slen;
fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_client_fd_bio_t);
- fr_packet_t *packet, *reply;
- fr_radius_client_packet_ctx_t ctx = {};
+ fr_packet_t *reply;
+ fr_radius_id_ctx_t *id_ctx;
+
+ /*
+ * We don't need to set up response.socket for connected bios.
+ */
+ fr_packet_t response = {};
/*
* We read the response packet ctx into our local structure. If we have a real response, we will
* swap to using the request context, and not the response context.
*/
- slen = fr_bio_read(my->common.bio, &ctx, &my->buffer, sizeof(my->buffer));
+ slen = fr_bio_read(my->common.bio, &response, &my->buffer, sizeof(my->buffer));
if (!slen) return 0;
if (slen < 0) {
return slen;
}
- packet = ctx.packet;
- fr_assert(packet != NULL);
+ /*
+ * Use the reply code to look up the original packet code.
+ *
+ * @todo - see above todo in response(). Maybe cache the id_ctx in "my"?
+ */
+ id_ctx = fr_radius_code_id_find(my->codes, allowed_replies[my->buffer[0]], my->buffer[1]);
+ fr_assert(id_ctx != NULL);
/*
* Allocate the new request data structure
*/
- reply = fr_packet_alloc(packet, false);
+ reply = fr_packet_alloc(id_ctx->packet, false);
if (!reply) return -1;
- ctx.reply = reply;
+
+ id_ctx->response = reply;
/*
* This is for connected sockets.
/*
* If this fails, we're out of memory.
*/
- if (fr_radius_decode_simple(pair_ctx, out, reply->data, reply->data_len,
- packet->vector, (char const *) my->cfg.verify.secret) < 0) {
+ if (fr_radius_decode_simple(out_ctx, out, reply->data, reply->data_len,
+ id_ctx->packet->vector, (char const *) my->cfg.verify.secret) < 0) {
fr_assert(0);
}
- reply->uctx = packet->uctx;
+ *request_ctx_p = id_ctx->request_ctx;
*packet_p = reply;
return 1;
fr_retry_config_t retry[FR_RADIUS_CODE_MAX]; //!< default retry configuration for each packet type
} fr_radius_client_config_t;
-typedef struct {
- fr_bio_retry_entry_t *retry_ctx;
-
- fr_packet_t *packet;
- fr_packet_t *reply;
-} fr_radius_client_packet_ctx_t;
-
fr_bio_packet_t *fr_radius_client_bio_alloc(TALLOC_CTX *ctx, fr_radius_client_config_t *cfg, fr_bio_fd_config_t const *fd_cfg) CC_HINT(nonnull);
fr_bio_t *fr_radius_client_bio_get_fd(fr_bio_packet_t *bio);
fr_radius_client_fd_bio_t *fr_radius_client_fd_bio_alloc(TALLOC_CTX *ctx, size_t read_size, fr_radius_client_config_t *cfg, fr_bio_fd_config_t const *fd_cfg) CC_HINT(nonnull);
-int fr_radius_client_fd_bio_write(fr_radius_client_fd_bio_t *my, fr_packet_t *packet, fr_pair_list_t *list);
+int fr_radius_client_fd_bio_write(fr_radius_client_fd_bio_t *my, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list);
-int fr_radius_client_fd_bio_read(fr_bio_packet_t *bio, fr_packet_t **packet_p, TALLOC_CTX *ctx, fr_pair_list_t *list);
+int fr_radius_client_fd_bio_read(fr_bio_packet_t *bio, void **request_ctx_p, fr_packet_t **packet_p, TALLOC_CTX *out_ctx, fr_pair_list_t *out);
/** Allocate an ID, and write one packet.
*
*/
-static int fr_radius_client_tcp_bio_write(fr_bio_packet_t *bio, fr_packet_t *packet, fr_pair_list_t *list)
+static int fr_radius_client_tcp_bio_write(fr_bio_packet_t *bio, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list)
{
fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_client_fd_bio_t);
*/
fr_assert(!packet->data);
- return fr_radius_client_fd_bio_write(my, packet, list);
+ return fr_radius_client_fd_bio_write(my, request_ctx, packet, list);
}
/** Allocate a RADIUS bio for writing client packets
/** Allocate an ID, and write one packet.
*
*/
-static int fr_radius_client_udp_bio_write(fr_bio_packet_t *bio, fr_packet_t *packet, fr_pair_list_t *list)
+static int fr_radius_client_udp_bio_write(fr_bio_packet_t *bio, void *request_ctx, fr_packet_t *packet, fr_pair_list_t *list)
{
ssize_t slen;
fr_radius_client_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_client_fd_bio_t);
- if (!packet->data) return fr_radius_client_fd_bio_write(my, packet, list);
+ if (!packet->data) return fr_radius_client_fd_bio_write(my, request_ctx, packet, list);
slen = fr_bio_write(my->common.bio, &packet->socket, packet->data, packet->data_len);
if (slen <= 0) return -1;
int free_start;
int free_end;
- fr_packet_t *packet[256]; //!< pointer to packet data
+ fr_radius_id_ctx_t id[256]; //!< pointers to request / reply data
int free_ids[256];
};
/** Allocate an ID for a packet, using LRU
*
*/
-int fr_radius_id_pop(fr_radius_id_t *track, fr_packet_t *packet)
+fr_radius_id_ctx_t *fr_radius_id_pop(fr_radius_id_t *track, fr_packet_t *packet)
{
int id;
fr_assert(id >= 0);
fr_assert(id < 256);
- fr_assert(!track->packet[id]);
+ fr_assert(!track->id[id].packet);
track->free_ids[track->free_start] = -1;
fr_assert(track->num_free_ids > 0);
track->num_free_ids--;
- track->packet[id] = packet;
+ track->id[id] = (fr_radius_id_ctx_t) {
+ .packet = packet,
+ };
packet->id = id;
- return 0;
+ return &track->id[id];
}
/** De-allocate an ID for a packet, using LRU
fr_assert(packet->id >= 0);
fr_assert(packet->id < 256);
- fr_assert(track->packet[packet->id] == packet);
+ fr_assert(track->id[packet->id].packet == packet);
fr_assert(track->num_free_ids < 256);
fr_assert(track->free_start != track->free_end);
fr_assert(track->free_end >= 0);
track->free_end++;
track->free_end &= 0xff;
- track->packet[packet->id] = NULL;
+ track->id[packet->id].packet = NULL;
track->num_free_ids++;
}
-fr_packet_t *fr_radius_id_find(fr_radius_id_t *track, int id)
+fr_radius_id_ctx_t *fr_radius_id_find(fr_radius_id_t *track, int id)
{
fr_assert(id >= 0);
fr_assert(id < 256);
- return track->packet[id];
+ return &track->id[id];
}
typedef fr_radius_id_t *fr_radius_code_id_t[FR_RADIUS_CODE_MAX];
+typedef struct {
+ void *request_ctx; //!< for the application to track
+ fr_packet_t *packet; //!< outgoing packet
+ fr_packet_t *response; //!< response to outgoing packet
+ void *retry_ctx; //!< to find the retry information
+} fr_radius_id_ctx_t;
+
fr_radius_id_t *fr_radius_id_alloc(TALLOC_CTX *ctx);
-int fr_radius_id_pop(fr_radius_id_t *track, fr_packet_t *packet) CC_HINT(nonnull);
+fr_radius_id_ctx_t *fr_radius_id_pop(fr_radius_id_t *track, fr_packet_t *packet) CC_HINT(nonnull);
void fr_radius_id_push(fr_radius_id_t *track, fr_packet_t const *packet) CC_HINT(nonnull);
-fr_packet_t *fr_radius_id_find(fr_radius_id_t *track, int id) CC_HINT(nonnull);
+fr_radius_id_ctx_t *fr_radius_id_find(fr_radius_id_t *track, int id) CC_HINT(nonnull);
static inline CC_HINT(nonnull) int fr_radius_code_id_alloc(TALLOC_CTX *ctx, fr_radius_code_id_t codes, int code)
{
return 0;
}
-static inline CC_HINT(nonnull) int fr_radius_code_id_pop(fr_radius_code_id_t codes, fr_packet_t *packet)
+static inline CC_HINT(nonnull) fr_radius_id_ctx_t *fr_radius_code_id_pop(fr_radius_code_id_t codes, fr_packet_t *packet)
{
fr_assert(packet->code > 0);
fr_assert(packet->code < FR_RADIUS_CODE_MAX);
fr_radius_id_push(codes[packet->code], packet);
}
-static inline CC_HINT(nonnull) fr_packet_t *fr_radius_code_id_find(fr_radius_code_id_t codes, int code, int id)
+static inline CC_HINT(nonnull) fr_radius_id_ctx_t *fr_radius_code_id_find(fr_radius_code_id_t codes, int code, int id)
{
fr_assert(code > 0);
fr_assert(code < FR_RADIUS_CODE_MAX);