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 {
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;
*
*/
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;
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);
*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);
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);
}
/*
- * 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.
},
});
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
};
/*
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;