*
*/
typedef struct rlm_radius_udp_t {
+ rlm_radius_t *parent; //!< rlm_radius instance
+
fr_ipaddr_t dst_ipaddr; //!< IP of the home server
fr_ipaddr_t src_ipaddr; //!< IP we open our socket on
uint16_t dst_port; //!< port of the home server
fr_dlist_t queued; //!< queued requests for some new connection
fr_dlist_t active; //!< active connections
+ fr_dlist_t frozen; //!< frozen connections
+ fr_dlist_t opening; //!< opening connections
} rlm_radius_udp_thread_t;
typedef struct rlm_radius_udp_connection_t {
rlm_radius_udp_t const *inst; //!< our module instance
+ rlm_radius_udp_thread_t *thread; //!< our thread-specific data
fr_connection_t *conn; //!< Connection to our destination.
+ char const *name; //!< from IP PORT to IP PORT
fr_dlist_t entry; //!< in the linked list of connections
typedef struct rlm_radius_udp_request_t {
fr_dlist_t entry; //!< in the connection list of packets
- rlm_radius_udp_connection_t *c; //!< the connection
+ int code; //!< packet code
+ rlm_radius_udp_connection_t *c; //!< the connection
+ rlm_radius_link_t *link; //!< more link stuff
rlm_radius_request_t *rr; //!< the ID tracking, resend count, etc.
} rlm_radius_udp_request_t;
CONF_PARSER_TERMINATOR
};
+static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx);
+static void conn_read(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, void *uctx);
+static void conn_writable(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, void *uctx);
+
+
+/** Set the socket to idle
+ *
+ * But keep the read event open, just in case the other end sends us
+ * data That way we can process it.
+ *
+ * @param[in] c Connection data structure
+ */
+static void fd_idle(rlm_radius_udp_connection_t *c)
+{
+ rlm_radius_udp_thread_t *t = c->thread;
+
+ DEBUG3("Marking socket %s as idle", c->name);
+ if (fr_event_fd_insert(c->conn, t->el, c->fd,
+ conn_read, NULL, conn_error, c) < 0) {
+ PERROR("Failed inserting FD event");
+ talloc_free(c);
+ }
+}
+
+/** Set the socket to active
+ *
+ * We have messages we want to send, so need to know when the socket is writable.
+ *
+ * @param[in] c Connection data structure
+ */
+static void fd_active(rlm_radius_udp_connection_t *c)
+{
+ rlm_radius_udp_thread_t *t = c->thread;
+
+ DEBUG3("Marking socket %s as active - Draining requests", c->name);
+
+ if (fr_event_fd_insert(c->conn, t->el, c->fd,
+ conn_read, conn_writable, conn_error, c) < 0) {
+ PERROR("Failed inserting FD event");
+ talloc_free(c);
+ }
+}
+
+
+/** Connection errored
+ *
+ */
+static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
+{
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+
+ ERROR("Connection failed %s: %s", c->name, fr_syserror(fd_errno));
+
+ /*
+ * Something bad happened... Fix it...
+ */
+ fr_connection_reconnect(c->conn);
+}
+
+
+/** Read reply packets.
+ *
+ */
+static void conn_read(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
+{
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+ decode_fail_t reason;
+ size_t packet_len;
+ ssize_t data_len;
+
+ data_len = read(fd, c->buffer, c->buflen);
+ if (data_len == 0) return;
+
+ if (data_len < 0) {
+ conn_error(el, fd, 0, errno, c);
+ return;
+ }
+
+ packet_len = data_len;
+ if (!fr_radius_ok(c->buffer, &packet_len, false, &reason)) {
+ DEBUG("Ignoring malformed packet");
+ return;
+ }
+
+ // look the packet up by ID, and find the matching one
+ // allowing multiple packet codes in the same socket is annoying,
+ // because each request code can have one of multiple reply codes..
+ // and they all share the same ID space. :(
+
+ // verify it
+ // get the REQUEST for it
-static rlm_radius_udp_connection_t *mod_connect(rlm_radius_udp_t *inst, rlm_radius_udp_thread_t *t)
+ // remove it from c->sent
+ // update the status of this connection
+ // unlang_resumable(request);
+}
+
+/** There's space available to write data, so do that...
+ *
+ */
+static void conn_writable(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
+{
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+ fr_dlist_t *entry, *next;
+
+ /*
+ * Clear our backlog
+ */
+ for (entry = FR_DLIST_FIRST(c->queued);
+ entry != NULL;
+ entry = next) {
+ rlm_radius_udp_request_t *u;
+ ssize_t packet_len;
+ ssize_t rcode;
+
+ next = FR_DLIST_NEXT(c->queued, entry);
+
+ u = fr_ptr_to_type(rlm_radius_udp_request_t, entry, entry);
+
+ packet_len = fr_radius_encode(c->buffer, c->buflen, NULL,
+ c->inst->secret, u->rr->id, u->code, 0,
+ u->link->request->packet->vps);
+ if (packet_len <= 0) break;
+
+ rcode = write(fd, c->buffer, packet_len);
+ if (rcode < 0) {
+ if (errno == EWOULDBLOCK) return;
+
+ conn_error(el, fd, 0, errno, c);
+ return;
+ }
+
+ fr_dlist_remove(&u->entry);
+ fr_dlist_insert_tail(&c->sent, &u->entry);
+ }
+
+ /*
+ * Check if we have to enable or disable writing on the socket.
+ */
+ entry = FR_DLIST_FIRST(c->queued);
+ if (!entry) {
+ c->pending = false;
+ fd_idle(c);
+
+ } else if (!c->pending) {
+ c->pending = true;
+ fd_active(c);
+ }
+}
+
+/** Shutdown/close a file descriptor
+ *
+ */
+static void conn_close(int fd, void *uctx)
+{
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+
+ DEBUG3("Closing socket %s", c->name);
+ if (shutdown(fd, SHUT_RDWR) < 0) DEBUG3("Shutdown on socket %s failed: %s", c->name, fr_syserror(errno));
+ if (close(fd) < 0) DEBUG3("Closing socket %s failed: %s", c->name, fr_syserror(errno));
+
+ c->fd = -1;
+}
+
+/** Process notification that fd is open
+ *
+ */
+static fr_connection_state_t conn_open(UNUSED fr_event_list_t *el, UNUSED int fd, void *uctx)
+{
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+ rlm_radius_udp_thread_t *t = c->thread;
+ fr_dlist_t *entry, *next;
+
+ talloc_const_free(&c->name);
+ c->name = talloc_strdup(c, "connected");
+
+ /*
+ * Remove the connection from the "opening" list, and add
+ * it to the "active" list.
+ */
+ fr_dlist_remove(&c->entry);
+ fr_dlist_insert_tail(&t->active, &c->entry);
+
+ /*
+ * Clear the global backlog first.
+ */
+ for (entry = FR_DLIST_FIRST(t->queued);
+ entry != NULL;
+ entry = next) {
+ rlm_radius_udp_request_t *u;
+
+ next = FR_DLIST_NEXT(t->queued, entry);
+
+ u = fr_ptr_to_type(rlm_radius_udp_request_t, entry, entry);
+
+ // @todo - figure out if we can the request to the
+ // connection, by tracking used codes, etc.
+
+ fr_dlist_remove(entry);
+ fr_dlist_insert_tail(&c->queued, &u->entry);
+ c->pending = true;
+ }
+
+
+ /*
+ * If we have data pending, add the writable event immediately
+ */
+ if (c->pending) {
+ fd_active(c);
+ } else {
+ fd_idle(c);
+ }
+
+ return FR_CONNECTION_STATE_CONNECTED;
+}
+
+
+/** Initialise a new outbound connection
+ *
+ * @param[out] fd_out Where to write the new file descriptor.
+ * @param[in] uctx A #rlm_radius_thread_t.
+ */
+static fr_connection_state_t conn_init(int *fd_out, void *uctx)
+{
+ int fd;
+ rlm_radius_udp_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_udp_connection_t);
+
+ /*
+ * Open the outgoing socket.
+ *
+ * @todo - pass src_ipaddr, and remove later call to fr_socket_bind()
+ * which does return the src_port, but doesn't set the "don't fragment" bit.
+ */
+ fd = fr_socket_client_udp(&c->src_ipaddr, &c->dst_ipaddr, c->dst_port, true);
+ if (fd < 0) {
+ DEBUG("Failed opening RADIUS client UDP socket: %s", fr_strerror());
+ return FR_CONNECTION_STATE_FAILED;
+ }
+
+#if 0
+ if (fr_socket_bind(fd, &io->src_ipaddr, &io->src_port, inst->interface) < 0) {
+ DEBUG("Failed binding RADIUS client UDP socket: %s FD %d %pV port %u interface %s", fr_strerror(), fd, fr_box_ipaddr(io->src_ipaddr),
+ io->src_port, inst->interface);
+ return FR_CONNECTION_STATE_FAILED;
+ }
+#endif
+
+ // @todo - set name properly
+ c->name = talloc_strdup(c, "connecting...");
+
+ // @todo - set recv_buff and send_buff socket options
+
+ c->fd = fd;
+
+ // @todo - initialize the tracking memory, etc.
+
+ *fd_out = fd;
+
+ return FR_CONNECTION_STATE_CONNECTING;
+}
+
+static void mod_connect(rlm_radius_udp_t *inst, rlm_radius_udp_thread_t *t)
{
rlm_radius_udp_connection_t *c;
c = talloc_zero(t, rlm_radius_udp_connection_t);
+ c->inst = inst;
+ c->thread = t;
c->dst_ipaddr = inst->dst_ipaddr;
c->dst_port = inst->dst_port;
c->src_ipaddr = inst->src_ipaddr;
c->src_port = 0;
+ c->max_packet_size = inst->max_packet_size;
+
+ c->buffer = talloc_array(c, uint8_t, c->max_packet_size);
+ if (!c->buffer) {
+ talloc_free(c);
+ return;
+ }
+ c->buflen = c->max_packet_size;
+
+ c->conn = fr_connection_alloc(c, t->el, &inst->parent->connection_timeout, &inst->parent->reconnection_delay,
+ conn_init, conn_open, conn_close, inst->parent->name, c);
+ if (!c->conn) return;
+
+ fr_connection_start(c->conn);
+
+ fr_dlist_insert_head(&t->opening, &c->entry);
+
+ // @todo - set destructor for connection which removes it from the various lists?
+}
+
+static rlm_radius_udp_connection_t *mod_connection_get(rlm_radius_udp_thread_t *t, UNUSED int code)
+{
+ rlm_radius_udp_connection_t *c;
+ fr_dlist_t *entry;
+
+ entry = FR_DLIST_FIRST(t->active);
+ if (!entry) return NULL;
+
+ c = fr_ptr_to_type(rlm_radius_udp_connection_t, entry, entry);
+ (void) talloc_get_type_abort(c, rlm_radius_udp_connection_t);
return c;
}
+static void mod_connection_add(UNUSED rlm_radius_udp_connection_t *c, UNUSED rlm_radius_udp_request_t *u)
+{
+ // do stuff
+}
+
+
+static void mod_clear_backlog(rlm_radius_udp_thread_t *t)
+{
+ fr_dlist_t *entry, *next;
+
+ for (entry = FR_DLIST_FIRST(t->queued);
+ entry != NULL;
+ entry = next) {
+ rlm_radius_udp_request_t *u;
+ rlm_radius_udp_connection_t *c;
+
+ next = FR_DLIST_NEXT(t->queued, entry);
+
+ u = fr_ptr_to_type(rlm_radius_udp_request_t, entry, entry);
+ c = mod_connection_get(t, u->code);
+ if (!c) return;
+
+ fr_dlist_remove(entry);
+ mod_connection_add(c, u);
+ }
+}
+
static int mod_push(void *instance, REQUEST *request, rlm_radius_link_t *link, void *thread)
{
rlm_radius_udp_thread_t *t = talloc_get_type_abort(thread, rlm_radius_udp_thread_t);
rlm_radius_udp_request_t *u = link->request_io_ctx;
rlm_radius_udp_connection_t *c;
- fr_dlist_t *entry;
rad_assert(request->packet->code > 0);
rad_assert(request->packet->code < FR_MAX_PACKET_CODE);
- entry = FR_DLIST_FIRST(t->active);
- if (!entry) {
- c = mod_connect(inst, t);
- if (!c) {
- RDEBUG("Failed initializing new connection");
- return -1;
- }
- }
-
/*
- * Now that we have a connection, use it to send packets.
+ * Clear the backlog before sending any new packets.
*/
- c = fr_ptr_to_type(rlm_radius_udp_connection_t, entry, entry);
- (void) talloc_get_type_abort(c, rlm_radius_udp_connection_t);
+ if (t->pending) mod_clear_backlog(t);
- u->c = c;
+ u->link = link;
+ u->code = request->packet->code;
- if (c->pending) {
- fr_dlist_insert_head(&c->queued, &u->entry);
+ /*
+ * Get a connection. If they're all full, try to open a
+ * new one.
+ */
+ c = mod_connection_get(t, u->code);
+ if (!c) {
+ fr_dlist_t *entry;
+
+ entry = FR_DLIST_FIRST(t->opening);
+ if (!entry) mod_connect(inst, t);
+
+ /*
+ * Add the request to the backlog. It will be
+ * sent either when the new connection is open,
+ * or when an existing connection has
+ * availability.
+ */
+ t->pending = true;
+ fr_dlist_insert_head(&t->queued, &u->entry);
return 0;
}
- // @todo - try to write to the socket. If we can, return instead of adding it to the queue
-
- c->pending = true;
+ /*
+ * Insert it into the pending queue
+ */
fr_dlist_insert_head(&c->queued, &u->entry);
-// mod_fd_active(c);
+
+ /*
+ * If there are no active packets, try to write one
+ * immediately. This avoids a few context switches in
+ * the case where the socket is writable.
+ */
+ if (!c->pending) {
+ conn_writable(t->el, c->fd, 0, c);
+ }
return 0;
}
*/
static int mod_bootstrap(UNUSED void *instance, UNUSED CONF_SECTION *conf)
{
-// rlm_radius_udp_t *inst = talloc_get_type_abort(instance, rlm_radius_udp_t);
+ rlm_radius_udp_t *inst = talloc_get_type_abort(instance, rlm_radius_udp_t);
+
+ (void) talloc_set_type(inst, rlm_radius_udp_t);
return 0;
}
* - 0 on success.
* - -1 on failure.
*/
-static int mod_instantiate(void *instance, CONF_SECTION *conf)
+static int mod_instantiate(rlm_radius_t *parent, void *instance, CONF_SECTION *conf)
{
rlm_radius_udp_t *inst = talloc_get_type_abort(instance, rlm_radius_udp_t);
+ inst->parent = parent;
+
/*
* Ensure that we have a destination address.
*/
t->pending = false;
FR_DLIST_INIT(t->queued);
FR_DLIST_INIT(t->active);
+ FR_DLIST_INIT(t->frozen);
+ FR_DLIST_INIT(t->opening);
// @todo - get parent, and initialize the list of IDs by code, from what is permitted by rlm_radius
- // start the connection
+ mod_connect(t->inst, t);
return 0;
}