From: Alan T. DeKok Date: Fri, 7 Jul 2017 13:58:15 +0000 (-0400) Subject: more progress on getting RADIUS packets out of the server again X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=77bccff2aac0cecca96446f7c228653bb1e904be;p=thirdparty%2Ffreeradius-server.git more progress on getting RADIUS packets out of the server again --- diff --git a/src/modules/rlm_radius/rlm_radius.c b/src/modules/rlm_radius/rlm_radius.c index 5a0ced96f75..ae94454d030 100644 --- a/src/modules/rlm_radius/rlm_radius.c +++ b/src/modules/rlm_radius/rlm_radius.c @@ -40,7 +40,7 @@ typedef struct radius_instance { char const *name; //!< Module instance name. struct timeval connection_timeout; - struct timeval reconnect_delay; + struct timeval reconnection_delay; struct timeval idle_timeout; dl_instance_t *io_submodule; //!< As provided by the transport_parse @@ -49,11 +49,68 @@ typedef struct radius_instance { CONF_SECTION *client_io_conf; //!< Easy access to the client_io's config section } rlm_radius_t; +typedef struct rlm_radius_connection_t rlm_radius_connection_t; + + +/** Per-thread instance data + * + * Contains buffers and connection handles specific to the thread. + */ +typedef struct { + rlm_radius_t const *inst; //!< Instance of the module. + fr_event_list_t *el; //!< This thread's event list. + + bool pending; //!< We have pending messages to write. + fr_dlist_t queued; //!< re-queued when a connection fails + + rlm_radius_connection_t *connection; //!< @todo - should be list + + fr_dlist_t connected; //!< list of connected sockets + fr_dlist_t opening; //!< list of sockets not yet connected + fr_dlist_t closed; //!< list of closed sockets +} rlm_radius_thread_t; + +struct rlm_radius_connection_t { + char const *name; //!< humanly readable name of this connection + + fr_dlist_t entry; //!< in connected / opening list + rlm_radius_t const *inst; //!< Instance of the module. + rlm_radius_thread_t *thread; //!< thread instance + fr_event_list_t *el; //!< This thread's event list. + + fr_connection_t *conn; //!< Connection to our destination. + + void *client_io_ctx; //!< client IO context + + bool pending; //!< we have pending messages to write + int waiting; //!< written, but waiting for replies + + fr_dlist_t queued; //!< queued for sending + fr_dlist_t sent; //!< actually sent +}; + +typedef struct rlm_radius_link_t { + bool queued; //!< queued or live + REQUEST *request; //!< the request we are for + fr_dlist_t entry; //!< linked list of queued or sent + rlm_radius_connection_t *c; //!< which connection we're queued or sent + void *request_io_ctx; +} rlm_radius_link_t; + +#if 0 +#define REQUEST_WALK(list) for (entry = FR_DLIST_FIRST(list); \ + entry != NULL; \ + entry = FR_DLIST_NEXT(list, entry)) { \ + REQUEST *request; \ + request = fr_ptr_to_type(fr_radius_link_t, entry, entry); \ + } +#endif + static CONF_PARSER const timer_config[] = { { FR_CONF_OFFSET("connection", FR_TYPE_TIMEVAL, rlm_radius_t, connection_timeout), .dflt = STRINGIFY(5) }, - { FR_CONF_OFFSET("reconnect", FR_TYPE_TIMEVAL, rlm_radius_t, reconnect_delay), + { FR_CONF_OFFSET("reconnect", FR_TYPE_TIMEVAL, rlm_radius_t, reconnection_delay), .dflt = STRINGIFY(5) }, { FR_CONF_OFFSET("idle", FR_TYPE_TIMEVAL, rlm_radius_t, idle_timeout), @@ -107,12 +164,277 @@ static int transport_parse(TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CON return dl_instance(ctx, out, transport_cs, parent_inst, name, DL_TYPE_SUBMODULE); } + +static void mod_radius_fd_idle(rlm_radius_connection_t *c); + +static void mod_radius_fd_active(rlm_radius_connection_t *c); + + +/** Connection errored + * + */ +static void mod_radius_conn_error(UNUSED fr_event_list_t *el, int sock, UNUSED int flags, int fd_errno, void *uctx) +{ + rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + + ERROR("Connection failed (%i): %s", sock, fr_syserror(fd_errno)); + + /* + * Something bad happened... Fix it. The connection API + * will take care of deleting the FD from the event list, + * and will call our mod_radius_conn_close() routine. + */ + fr_connection_reconnect(c->conn); +} + +/** Drain any data we received + * + * We don't care about this data, we just don't want the kernel to + * signal the other side that our read buffer's full. + */ +static void mod_radius_conn_read(UNUSED fr_event_list_t *el, UNUSED int sock, UNUSED int flags, UNUSED void *uctx) +{ +// rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + + // read a reply from the sub-module + // find the original request + // check signature + // if OK, resume it +} + +/** There's space available to write data, so do that... + * + */ +static void mod_radius_conn_writable(UNUSED fr_event_list_t *el, UNUSED int sock, UNUSED int flags, void *uctx) +{ + rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + + // dequeue a REQUEST + // encode it + // write it to the socket + // if EWOULDBLOCK, return. + + mod_radius_fd_idle(c); +} + +/** Set the socket to idle + * + * But keep the read event open, just in case the other end sends us + * garbage. That way we can drain it. + * + * @param[in] c Connection data structure + */ +static void mod_radius_fd_idle(rlm_radius_connection_t *c) +{ + DEBUG3("Marking socket (%i) as idle", fr_connection_get_fd(c->conn)); + if (fr_event_fd_insert(c->el, fr_connection_get_fd(c->conn), + mod_radius_conn_read, NULL, mod_radius_conn_error, c) < 0) { + PERROR("Failed inserting FD event"); + } +} + +/** 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 mod_radius_fd_active(rlm_radius_connection_t *c) +{ + DEBUG3("Marking socket (%i) as active - Draining requests", fr_connection_get_fd(c->conn)); + if (fr_event_fd_insert(c->el, fr_connection_get_fd(c->conn), + mod_radius_conn_read, mod_radius_conn_writable, mod_radius_conn_error, c) < 0) { + PERROR("Failed inserting FD event"); + } +} + +/** Shutdown/close a file descriptor + * + */ +static void mod_radius_conn_close(int fd, UNUSED void *uctx) +{ + rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + rlm_radius_thread_t *t = c->thread; + rlm_radius_t const *inst = t->inst; + + /* + * Tell the IO handler to get rid of any pending requests + * for this socket. + */ + inst->client_io->close(c->client_io_ctx); + + DEBUG3("Closing socket (%i)", fd); + if (shutdown(fd, SHUT_RDWR) < 0) DEBUG3("Shutdown on socket (%i) failed: %s", fd, fr_syserror(errno)); + if (close(fd) < 0) DEBUG3("Closing socket (%i) failed: %s", fd, fr_syserror(errno)); + + /* + * Remove it from whatever list it's in, and add it to + * the "closed" list. + */ + fr_dlist_remove(&c->entry); + fr_dlist_insert_tail(&t->closed, &c->entry); + + /* + * Nothing to be written or waiting for replies. Return. + */ + if (!c->pending && !c->waiting) return; + + // loop over 'sent', and insert into 'queued' + + // if 'queued', set c->pending again +} + +/** Process notification that fd is open + * + */ +static fr_connection_state_t mod_radius_conn_open(UNUSED int fd, UNUSED fr_event_list_t *el, void *uctx) +{ + rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + rlm_radius_thread_t *t = c->thread; + rlm_radius_t const *inst = t->inst; + + c->name = inst->client_io->get_name(c, c->client_io_ctx); + + DEBUG2("Connected - %s", c->name); + + /* + * If we have data pending, add the writable event immediately + */ + if (c->pending) { + mod_radius_fd_active(c); + } else { + mod_radius_fd_idle(c); + } + + /* + * Remove the connection from the "opening" list, and add + * it to the "connected" list. + */ + fr_dlist_remove(&c->entry); + fr_dlist_insert_tail(&t->connected, &c->entry); + + 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 mod_radius_conn_init(int *fd_out, void *uctx) +{ + rlm_radius_connection_t *c = talloc_get_type_abort(uctx, rlm_radius_connection_t); + rlm_radius_thread_t *t = c->thread; + rlm_radius_t const *inst = t->inst; + + memset(c->client_io_ctx, 0, inst->client_io->io_inst_size); + + return inst->client_io->init(fd_out, c->client_io_ctx); +} + +/** Unlink a request from wherever it is + * + */ +static int mod_remove_link(rlm_radius_link_t *link) +{ + fr_dlist_remove(&link->entry); + if (link->queued) return 0; + + // remove it from the client IO + +// inst->client_io->remove(request, c->client_io_ctx, link->request_io_ctx); + + return 0; +} + + +static int CC_HINT(nonnull) mod_add(rlm_radius_t *inst, rlm_radius_connection_t *c, REQUEST *request, rlm_rcode_t *rcode) +{ + rlm_radius_link_t *link; + size_t size; + + /* + * The client IO module may need to store per-request + * data. Add it here for simpliciy. + */ + size = sizeof(rlm_radius_link_t); + if (inst->client_io->request_inst_size) { + size += 15; + size &= ~((size_t) 15); + + size += inst->client_io->request_inst_size; + } + + link = (rlm_radius_link_t *) talloc_zero_array(c, uint64_t, (size / sizeof(uint64_t))); + talloc_set_type(link, rlm_radius_link_t); + rad_assert(link != NULL); + + if (size > sizeof(rlm_radius_link_t)) { + link->request_io_ctx = (void *) (link + 1); + } + + /* + * Add the request to the outoging queue, and associate + * it with the request. + */ + fr_dlist_insert_tail(&c->queued, &link->entry); + link->request = request; + link->c = c; + link->queued = true; + + talloc_set_destructor(link, mod_remove_link); + + (void) request_data_add(request, c, 0, link, true, true, false); + +// inst->client_io->add(request, c->client_io_ctx, link->request_io_ctx); + + // insert resumption handler + // insert max_request_timeout + // retransmission timeouts, etc. MUST be handled by the IO handler, which gets REQUEST in it's write() routine + // return yield + + /* + * If there are no pending writes, enable the write + * callback. It will wake up and write the packets to + * the socket. + */ + if (!c->pending) { + c->pending = true; + mod_radius_fd_active(c); + } + + *rcode = RLM_MODULE_FAIL; + + return 0; +} + /** Send packets outbound. * */ -static rlm_rcode_t CC_HINT(nonnull) mod_process(UNUSED void *instance, UNUSED void *thread, UNUSED REQUEST *request) +static rlm_rcode_t CC_HINT(nonnull) mod_process(void *instance, void *thread, REQUEST *request) { -// rlm_radius_t *inst = instance; + int result; + rlm_rcode_t rcode; + rlm_radius_t *inst = instance; + rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); + rlm_radius_connection_t *c = t->connection; + + /* + * Another connection has closed and moved it's requests + * back to the main thread. Recycle them through to + * other connections. + */ + if (t->pending) { + + } + + // @todo - find the "most recently started" connection which has a response + // @todo - check the connection busy-ness before calling mod_add() + + result = mod_add(inst, c, request, &rcode); + if (result == 0) return rcode; + + // @todo - create or add another connection return RLM_MODULE_FAIL; } @@ -138,12 +460,14 @@ static int mod_bootstrap(void *instance, CONF_SECTION *conf) FR_TIMEVAL_BOUND_CHECK("timers.connection", &inst->connection_timeout, >=, 1, 0); FR_TIMEVAL_BOUND_CHECK("timers.connection", &inst->connection_timeout, <=, 30, 0); - FR_TIMEVAL_BOUND_CHECK("timers.reconnect", &inst->reconnect_delay, >=, 5, 0); - FR_TIMEVAL_BOUND_CHECK("timers.reconned", &inst->reconnect_delay, <=, 300, 0); + FR_TIMEVAL_BOUND_CHECK("timers.reconnect", &inst->reconnection_delay, >=, 5, 0); + FR_TIMEVAL_BOUND_CHECK("timers.reconned", &inst->reconnection_delay, <=, 300, 0); FR_TIMEVAL_BOUND_CHECK("timers.idle", &inst->connection_timeout, >=, 30, 0); FR_TIMEVAL_BOUND_CHECK("timers.idle", &inst->connection_timeout, <=, 600, 0); + rad_assert(inst->client_io->io_inst_size > 0); + if (!inst->client_io->bootstrap) return 0; if (inst->client_io->bootstrap(inst->client_io_instance, inst->client_io_conf) < 0) { @@ -181,19 +505,73 @@ static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf) return 0; } -static int mod_thread_instantiate(UNUSED CONF_SECTION const *cs, void *instance, fr_event_list_t *el, void *thread) +/** Unlink the connection from the thread list. + * + * The connection API will take care of calling out 'close' routine. + */ +static int mod_connection_free(rlm_radius_connection_t *c) +{ + fr_dlist_remove(&c->entry); + return 0; +} + + +static int mod_thread_detach(void *thread) { - rlm_radius_t *inst = talloc_get_type_abort(instance, rlm_radius_t); + rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); +// rlm_radius_t *inst = t->inst; - if (inst->client_io->thread_instantiate(inst->client_io_conf, inst->client_io_instance, el, thread) < 0) { - cf_log_err(inst->client_io_conf, "Thread instantiate failed for \"%s\"", - inst->client_io->name); + // @todo - multiple connections + // walk over all connections, freeing them + // once that's done, all of the requests should be in t->queued + // walk over t->queued, resuming requests + talloc_free(t->connection); + + return 0; +} + +static int mod_thread_instantiate(CONF_SECTION const *cs, void *instance, fr_event_list_t *el, void *thread) +{ + rlm_radius_t *inst = talloc_get_type_abort(instance, rlm_radius_t); + rlm_radius_thread_t *t = talloc_get_type_abort(thread, rlm_radius_thread_t); + rlm_radius_connection_t *c; + + c = talloc_zero(t, rlm_radius_connection_t); + c->name = ""; + c->inst = inst; + c->thread = t; + c->el = el; + + FR_DLIST_INIT(c->entry); + FR_DLIST_INIT(c->queued); + FR_DLIST_INIT(c->sent); + + c->client_io_ctx = talloc_zero_array(t, uint8_t, inst->client_io->io_inst_size); + if (!c->client_io_ctx) { + cf_log_err(cs, "Failed allocating IO instance"); return -1; - } + } + + talloc_set_destructor(c, mod_connection_free); + + // @todo - allow for multiple connections + t->connection = c; + FR_DLIST_INIT(t->queued); + FR_DLIST_INIT(t->connected); + FR_DLIST_INIT(t->opening); + FR_DLIST_INIT(t->closed); + + /* + * This opens the outbound connection + */ + c->conn = fr_connection_alloc(c, el, &inst->connection_timeout, &inst->reconnection_delay, + mod_radius_conn_init, mod_radius_conn_open, mod_radius_conn_close, + inst->name, c); + if (c->conn == NULL) return -1; + + fr_dlist_insert_tail(&t->opening, &c->entry); - // start up one connection - // - // add to per-thread data + fr_connection_start(c->conn); return 0; } @@ -218,6 +596,7 @@ rad_module_t rlm_radius = { .bootstrap = mod_bootstrap, .instantiate = mod_instantiate, .thread_instantiate = mod_thread_instantiate, + .thread_detach = mod_thread_detach, .methods = { [MOD_PREACCT] = mod_process, [MOD_AUTHENTICATE] = mod_process, diff --git a/src/modules/rlm_radius/rlm_radius.h b/src/modules/rlm_radius/rlm_radius.h index 0783dd6f950..3d42db99ed4 100644 --- a/src/modules/rlm_radius/rlm_radius.h +++ b/src/modules/rlm_radius/rlm_radius.h @@ -28,11 +28,19 @@ /** Process a request through a client socket. * - * This function typically encodes the packet, writes it to a socket, - * inserts itself into event list with a read / timeout, and returns - * RLM_MODULE_YIELD. */ -typedef rlm_rcode_t (*fr_radius_client_process)(void *instance, REQUEST *request); +typedef int (*fr_radius_client_process_t)(void *thread, REQUEST *request); + +/** Close a client IO socket + * + */ +typedef int (*fr_radius_client_close_t)(void *uctx); + +/** Get a printable name for a socket. + * + */ +typedef char *(*fr_radius_client_name_t)(TALLOC_CTX *ctx, void *uctx); + /** Public structure describing an I/O path for an outgoing socket. * @@ -43,12 +51,18 @@ typedef struct fr_radius_client_io_t { fr_app_bootstrap_t bootstrap; fr_app_instantiate_t instantiate; - module_thread_t thread_instantiate; //!< Callback to configure a module's instance for - //!< a new worker thread. + size_t io_inst_size; //!< Size of data to allocate to the IO handler + size_t request_inst_size; //!< size of the data to allocate per-request. + + fr_connection_init_t init; //!< initialize a socket using thread instance data fr_connection_open_t open; //!< open a socket using thread instance data - fr_connection_close_t close; //!< close a socket using thread instance data - fr_radius_client_process process; //!< process a packet through a socket using thread instance data + fr_radius_client_close_t close; //!< close a socket using thread instance data + fr_radius_client_name_t get_name; //!< get the name of this socket. + // get name + // write + // read + // error } fr_radius_client_io_t; #endif /* _RLM_RADIUS_H */