From 7dacf78673c0fe7b715f2edea6efe7aedf18fe4a Mon Sep 17 00:00:00 2001 From: "Alan T. DeKok" Date: Mon, 4 Aug 2025 17:52:37 -0400 Subject: [PATCH] first attempt at limiting the source port for %radius.sendto.ipaddr() the code is commented out for now, as it is a change of behavior --- src/modules/rlm_radius/bio.c | 99 ++++++++++++++++++++++++++++- src/modules/rlm_radius/rlm_radius.c | 21 ++++++ 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/modules/rlm_radius/bio.c b/src/modules/rlm_radius/bio.c index 5bb48c46b0..01e0ab3e7e 100644 --- a/src/modules/rlm_radius/bio.c +++ b/src/modules/rlm_radius/bio.c @@ -44,7 +44,10 @@ typedef struct { trunk_t *trunk; //!< trunk handler fr_bio_fd_config_t fd_config; //!< for threads or sockets fr_bio_fd_info_t const *fd_info; //!< status of the FD. - fr_radius_ctx_t radius_ctx; + fr_radius_ctx_t radius_ctx; //!< for signing packets +#ifdef REUSE_CONN + bool limit_source_ports; +#endif } bio_handle_ctx_t; typedef struct { @@ -133,8 +136,14 @@ typedef struct { bio_handle_ctx_t ctx; //!< for copying to bio_handle_t fr_rb_expire_node_t expire; + +#ifdef REUSE_CONN + int num_ports; + connection_t *connections[]; //!< for tracking outbound connections +#endif } home_server_t; + /** Turn a reply code into a module rcode; * */ @@ -713,6 +722,9 @@ static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx int fd; bio_handle_t *h; bio_handle_ctx_t *ctx = uctx; /* thread or home server */ +#ifdef REUSE_CONN + connection_t **to_save = NULL; +#endif MEM(h = talloc_zero(conn, bio_handle_t)); h->ctx = *ctx; @@ -725,6 +737,37 @@ static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx MEM(h->tt = radius_track_alloc(h)); +#ifdef REUSE_CONN + /* + * We are proxying to multiple home servers, but using a limited port range. We must track the + * source port for each home server, so that we only can select the right unused source port for + * this home server. + */ + if (ctx->limit_source_ports) { + int i; + home_server_t *home = talloc_get_type_abort(ctx, home_server_t); + + for (i = 0; i < home->num_ports; i++) { + if (!home->connections[i]) { + to_save = &home->connections[i]; + + /* + * Set the source port, but also leave the src_port_start and + * src_port_end alone. + */ + h->ctx.fd_config.src_port = h->ctx.fd_config.src_port_start + i; + break; + } + } + + if (!to_save) { + ERROR("%s - Failed opening socket to home server %pV:%u - source port range is full", + h->ctx.module_name, fr_box_ipaddr(h->ctx.fd_config.dst_ipaddr), h->ctx.fd_config.dst_port); + goto fail; + } + } +#endif + h->bio.fd = fr_bio_fd_alloc(h, &h->ctx.fd_config, 0); if (!h->bio.fd) { PERROR("%s - failed opening socket", h->ctx.module_name); @@ -823,13 +866,21 @@ static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx *h_out = h; +#ifdef REUSE_CONN + if (to_save) *to_save = conn; +#endif + return CONNECTION_STATE_CONNECTING; } /** Shutdown/close a file descriptor * */ -static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx) +static void conn_close(UNUSED fr_event_list_t *el, void *handle, +#ifndef REUSE_CONN + UNUSED +#endif + void *uctx) { bio_handle_t *h = talloc_get_type_abort(handle, bio_handle_t); @@ -847,6 +898,27 @@ static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uc fr_bio_shutdown(h->bio.mem); + /* + * We have opened a limited number of outbound source ports. This means that when we close a + * port, we have to mark it unused. + */ +#ifdef REUSE_CONN + if (h->ctx.limit_source_ports) { + int offset; + home_server_t *home = talloc_get_type_abort(uctx, home_server_t); + + fr_assert(h->ctx.fd_config.src_port >= h->ctx.fd_config.src_port_start); + fr_assert(h->ctx.fd_config.src_port < h->ctx.fd_config.src_port_end); + + offset = h->ctx.fd_config.src_port - h->ctx.fd_config.src_port_start; + fr_assert(offset < home->num_ports); + + fr_assert(home->connections[offset] == h->conn); + + home->connections[offset] = NULL; + } +#endif + DEBUG4("Freeing handle %p", handle); talloc_free(h); @@ -2588,7 +2660,7 @@ static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx) } /* - * If we have a port range, allocate the source IP based + * If we have a port range, allocate the source port based * on the range start, plus the thread ID. This means * that we can avoid "hunt and peck" attempts to open up * the source port. @@ -2807,14 +2879,35 @@ static xlat_action_t xlat_radius_client(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcurso }, }); if (!home) { +#ifdef REUSE_CONN + size_t num_ports = 0; + + /* + * Track which connections are made to this home server from which open ports. + */ + if ((inst->fd_config.src_port_start > 0) && (inst->fd_config.src_port_end > 0)) { + num_ports = inst->fd_config.src_port_end - inst->fd_config.src_port_start; + } + + MEM(home = (home_server_t *) talloc_zero_array(thread, uint8_t, sizeof(home_server_t) + sizeof(connection_t *) * num_ports)); + talloc_set_type(home, home_server_t); +#else MEM(home = talloc(thread, home_server_t)); +#endif *home = (home_server_t) { .ctx = (bio_handle_ctx_t) { .el = unlang_interpret_event_list(request), .module_name = inst->name, .inst = inst, +#ifdef REUSE_CONN + .limit_source_ports = (num_ports > 0), +#endif }, + +#ifdef REUSE_CONN + .num_ports = num_ports, +#endif }; /* diff --git a/src/modules/rlm_radius/rlm_radius.c b/src/modules/rlm_radius/rlm_radius.c index 5dabe03abe..6c92b7fc00 100644 --- a/src/modules/rlm_radius/rlm_radius.c +++ b/src/modules/rlm_radius/rlm_radius.c @@ -703,6 +703,27 @@ check_others: inst->fd_config.flags = O_RDWR; } + /* + * When using %radius.sendto.ipaddr(), we can then often re-use the source port, as we connect() + * the outbound sockets. + */ + if (inst->mode == RLM_RADIUS_MODE_XLAT_PROXY) { + if (inst->fd_config.src_port != 0) { + cf_log_err(conf, "Cannot set a fixed source port for mode=dynamic-proxy"); + return -1; + } + +#ifdef REUSE_CONN + /* + * If there is a limited source port range, then set the reuse port flag. This lets us + * bind multiple sockets to the same port before we connect() them. + */ + if ((inst->fd_config.src_port_start > 0) && (inst->fd_config.src_port_end > 0)) { + inst->fd_config.reuse_port = 1; + } +#endif + } + if (fr_bio_fd_check_config(&inst->fd_config) < 0) { cf_log_perr(conf, "Invalid configuration"); return -1; -- 2.47.2