#
src_ipaddr = *
+ #
+ # src_port_start:: Start of source port range.
+ #
+ # The outgoing proxy normally uses random source
+ # ports. When all of the RADIUS IDs are used for one
+ # connection, it opens up another random source port.
+ #
+ # However, it is sometimes useful to restrict the
+ # range of source ports to known values. The
+ # `src_port_start` and `src_port_end` configuration
+ # flags allow the port range to be controlled. The
+ # module will then restrict the source ports it is
+ # using to be within this range.
+ #
+ # When all ports in this range are used, the module
+ # will not be able to open any more outgoing
+ # connections.x
+ #
+ # These two configuratiuon items can only be used for
+ # UDP sockets.
+ #
+ src_port_start = 10000
+
+ #
+ # src_port_end:: End of source port range.
+ #
+ src_port_end = 11000
+
#
# `src_port` cannot be used. If it is used here, the
# module will refuse to start. Instead, the module
rlm_radius_t const *inst; //!< our instance
fr_event_list_t *el; //!< Event list.
trunk_t *trunk; //!< trunk handler
- fr_bio_fd_config_t *fd_config; //!< for threads or sockets
+ 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;
} bio_handle_ctx_t;
typedef struct {
- bio_handle_ctx_t ctx; //!< for copying to bio_handle_t
-
+ bio_handle_ctx_t ctx; //!< common struct for home servers and BIO handles
struct {
fr_bio_t *fd; //!< writing
*
*/
typedef struct {
- bio_handle_ctx_t ctx; //! from thread or home server
+ bio_handle_ctx_t ctx; //!< common struct for home servers and BIO handles
int fd; //!< File descriptor.
typedef struct {
bio_handle_ctx_t ctx; //!< for copying to bio_handle_t
- fr_bio_fd_config_t fd_config; //!< fil descriptor configuration
-
fr_rb_expire_node_t expire;
} home_server_t;
MEM(h->tt = radius_track_alloc(h));
- h->bio.fd = fr_bio_fd_alloc(h, h->ctx.fd_config, 0);
+ /*
+ * Limit the source port to the given range.
+ */
+ if (h->ctx.inst->src_port_start) {
+ DEBUG("WARNING - src_port_start and src_port_end not currently supported. A random source port will be chosen");
+ }
+
+ 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);
fail:
* way we don't need a memory BIO for UDP sockets, but we can still add a verification layer for
* UDP sockets?
*/
- if (h->ctx.fd_config->socket_type == SOCK_STREAM) {
+ if (h->ctx.fd_config.socket_type == SOCK_STREAM) {
h->bio.mem = fr_bio_mem_alloc(h, 8192, 0, h->bio.fd);
if (!h->bio.mem) {
PERROR("%s - Failed allocating memory buffer - ", h->ctx.module_name);
h = talloc_get_type_abort(u->treq->tconn->conn->h, bio_handle_t);
- if (h->ctx.fd_config->socket_type != SOCK_DGRAM) {
+ if (h->ctx.fd_config.socket_type != SOCK_DGRAM) {
RDEBUG("Using stream sockets - suppressing retransmission");
return;
}
/*
* We don't retransmit over TCP.
*/
- if (h->ctx.fd_config->socket_type != SOCK_DGRAM) return;
+ if (h->ctx.fd_config.socket_type != SOCK_DGRAM) return;
/*
* If we only send one datagram packet, then don't bother saving it.
.request_cancel = request_cancel,
};
-
/** Instantiate thread data for the submodule.
*
*/
thread->ctx.el = mctx->el;
thread->ctx.inst = inst;
- thread->ctx.fd_config = &inst->fd_config;
+ thread->ctx.fd_config = inst->fd_config;
thread->ctx.radius_ctx = inst->common_ctx;
if ((inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
return 0;
}
+ /*
+ * If we have a port range, allocate the source IP 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 (thread->ctx.inst->src_port_start) {
+ thread->ctx.fd_config.src_port = thread->ctx.inst->src_port_start + fr_schedule_worker_id();
+ }
+
/*
* Allocate the unconnected replication socket.
*/
- thread->bio.fd = fr_bio_fd_alloc(thread, &thread->ctx.inst->fd_config, 0);
+ thread->bio.fd = fr_bio_fd_alloc(thread, &thread->ctx.fd_config, 0);
if (!thread->bio.fd) {
PERROR("%s - failed opening socket", inst->name);
return CONNECTION_STATE_FAILED;
home_server_t const *b = two;
int8_t rcode;
- rcode = fr_ipaddr_cmp(&a->fd_config.dst_ipaddr, &b->fd_config.dst_ipaddr);
+ rcode = fr_ipaddr_cmp(&a->ctx.fd_config.dst_ipaddr, &b->ctx.fd_config.dst_ipaddr);
if (rcode != 0) return rcode;
- return CMP(a->fd_config.dst_port, b->fd_config.dst_port);
+ return CMP(a->ctx.fd_config.dst_port, b->ctx.fd_config.dst_port);
}
static xlat_action_t xlat_sendto_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
}
home = fr_rb_find(&thread->bio.expires.tree, &(home_server_t) {
- .fd_config = (fr_bio_fd_config_t) {
- .dst_ipaddr = ipaddr->vb_ip,
- .dst_port = port->vb_uint16,
+ .ctx = {
+ .fd_config = (fr_bio_fd_config_t) {
+ .dst_ipaddr = ipaddr->vb_ip,
+ .dst_port = port->vb_uint16,
+ },
},
});
if (!home) {
},
};
- home->fd_config = inst->fd_config;
- home->ctx.fd_config = &home->fd_config;
-
- home->fd_config.type = FR_BIO_FD_CONNECTED;
- home->fd_config.dst_ipaddr = ipaddr->vb_ip;
- home->fd_config.dst_port = port->vb_uint32;
+ /*
+ * Copy the home server configuration from the root configuration. Then update it with
+ * the needs of the home server.
+ */
+ home->ctx.fd_config = inst->fd_config;
+ home->ctx.fd_config.type = FR_BIO_FD_CONNECTED;
+ home->ctx.fd_config.dst_ipaddr = ipaddr->vb_ip;
+ home->ctx.fd_config.dst_port = port->vb_uint32;
home->ctx.radius_ctx = (fr_radius_ctx_t) {
.secret = talloc_strdup(home, secret->vb_strvalue),
CONF_PARSER_TERMINATOR
};
+static conf_parser_t const transport_unconnected_config[] = {
+ { FR_CONF_OFFSET("src_port_start", rlm_radius_t, src_port_start) },
+ { FR_CONF_OFFSET("src_port_end", rlm_radius_t, src_port_end) },
+
+ CONF_PARSER_TERMINATOR
+};
+
/*
* We only parse the pool options if we're connected.
*/
CONF_PARSER_TERMINATOR
};
+static conf_parser_t const unconnected_config[] = {
+ { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+ { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+ CONF_PARSER_TERMINATOR
+};
+
/*
* We only parse the pool options if we're connected.
*/
{ FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
+ { FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
+ { FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_unconnected_config },
+
CONF_PARSER_TERMINATOR
};
case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
+ if (cf_section_rules_push(cs, unconnected_config) < 0) return -1;
break;
}
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
- if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) {
+ /*
+ * Check invalid configurations.
+ */
+ switch (inst->mode) {
+ default:
/*
- * These limits are specific to RADIUS, and cannot be over-ridden
+ * Filenames are write-only, and cannot get response packets.
+ */
+ if (inst->fd_config.filename) {
+ cf_log_err(conf, "Cannot set 'filename' here - it is only supported for 'mode=replicate'.");
+ return -1;
+ }
+
+ /*
+ * For normal proxying or originating client packets, we need to be able to open multiple
+ * source ports. So the admin can't force a particular source port.
+ */
+ if (inst->fd_config.src_port && (inst->mode != RLM_RADIUS_MODE_XLAT_PROXY)) {
+ cf_log_err(conf, "Cannot set 'src_port' when sending packets to a static destination");
+ return -1;
+ }
+
+ /*
+ * No src_port range, we don't need to check any other settings.
+ */
+ if (!inst->src_port_start && !inst->src_port_end) break;
+
+ if (inst->fd_config.path) {
+ cf_log_err(conf, "Cannot set 'src_port_start' or 'src_port_end' for outgoing Unix sockets");
+ return -1;
+ }
+
+ /*
+ * We can only have one method of allocating source ports.
+ */
+ if (inst->fd_config.src_port) {
+ cf_log_err(conf, "Cannot set 'src_port' and 'src_port_start' or 'src_port_end'");
+ return -1;
+ }
+
+ /*
+ * Cross-check src_port, src_port_start, and src_port_end.
+ */
+ if (inst->src_port_start) {
+ if (!inst->src_port_end) {
+ cf_log_err(conf, "Range has 'src_port_start', but is missing 'src_port_end'");
+ return -1;
+ }
+
+ if (inst->src_port_start >= inst->src_port_end) {
+ cf_log_err(conf, "Range has invalid values for 'src_port_start' ... 'src_port_end'");
+ return -1;
+ }
+
+ } else if (inst->src_port_end) {
+ cf_log_err(conf, "Range has 'src_port_end', but is missing 'src_port_start'");
+ return -1;
+ }
+
+ /*
+ * Encorce limits per trunk, due to the 8-bit ID space.
*/
FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, >=, 2);
FR_INTEGER_BOUND_CHECK("trunk.per_connection_max", inst->trunk_conf.max_req_per_conn, <=, 255);
FR_INTEGER_BOUND_CHECK("trunk.per_connection_target", inst->trunk_conf.target_req_per_conn, <=, inst->trunk_conf.max_req_per_conn / 2);
- }
+ break;
- if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
- (inst->mode == RLM_RADIUS_MODE_XLAT_PROXY)) {
- if (inst->fd_config.src_port != 0) {
- cf_log_err(conf, "Cannot set 'src_port' when using this 'mode'");
+ case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
+ /*
+ * Replication to dynamic filenames or dynamic unix sockets isn't supported.
+ */
+ if (inst->fd_config.filename || inst->fd_config.path) {
+ cf_log_err(conf, "Cannot set 'filename' or 'path' when using 'mode=unconnected-replicate'");
+ return -1;
+ }
+ FALL_THROUGH;
+
+ case RLM_RADIUS_MODE_REPLICATE:
+ /*
+ * We can force the source port, but then we have to set SO_REUSEPORT.
+ */
+ inst->fd_config.reuse_port = (inst->fd_config.src_port != 0);
+
+ /*
+ * Files and unix sockets are OK. The src_port can be set (or not), and that's fine.
+ */
+ if (inst->src_port_start || inst->src_port_end) {
+ cf_log_err(conf, "Cannot set 'src_port_start' or 'src_port_end' when replicating packets");
return -1;
}
+ break;
}
+ /*
+ * We allow what may otherwise be conflicting configurations, because the BIO code will pick one
+ * path, and the conflicts won't affect anything else. Only the src_port range is special.
+ */
+
FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, >=, fr_time_delta_from_sec(1));
FR_TIME_DELTA_BOUND_CHECK("response_window", inst->zombie_period, <=, fr_time_delta_from_sec(120));
uint32_t max_packet_size; //!< Maximum packet size.
uint16_t max_send_coalesce; //!< Maximum number of packets to coalesce into one mmsg call.
+ uint16_t src_port_start; //!< control the source port range for dynamic destinations
+ uint16_t src_port_end;
+
fr_radius_ctx_t common_ctx;
bool replicate; //!< Ignore responses.