# - You CANNOT use the module "in place" as with normal proxying.
# - It is only supported via the function %replicate.sendto.ipaddr(ipaddr, port, secret)
#
- # unconnected-proxy - proxy packets to dynamic destinations
- # - For unconnected UDP sockets only.
- # - It MUST have `transport = udp`
+ # dynamic-proxy - proxy packets to dynamic destinations
+ # - RFC 7585 dynamic DNS lookups are not supported
# - It MUST have `src_ipaddr = *` and no `src_port`
# - You CANNOT use the module "in place" as with normal proxying.
- # - It is only supported via the function %proxy.sendto.ipaddr(ipaddr, port, secret)
+ # - Proxying is only supported via the function %proxy.sendto.ipaddr(ipaddr, port, secret)
#
# The server can still be used to create (i.e. originate)
# packets via this module when `mode = proxy` is set. The
# along with all per-packet timeouts.
#
}
+
+#
+# A dynamic proxy module
+#
+radius proxy {
+ type = Access-Request
+
+ #
+ # We are not opening a socket from our server to their
+ # server. We are replicating packets.
+ #
+ mode = dynamic-proxy
+
+ #
+ # Both UDP and TCP are supported.
+ #
+ transport = udp
+
+ #
+ # ### UDP Transport
+ #
+ udp {
+ #
+ # src_ipaddr:: The source IP address used by the module.
+ #
+ src_ipaddr = *
+
+ #
+ # `src_port` cannot be used. If it is used here, the
+ # module will refuse to start. Instead, the module
+ # will open a unique source port per thread.
+ #
+ # `secret` cannot be used. If it is used, the value
+ # will be ignored.
+ #
+ }
+
+ #
+ # ## Connection trunking
+ #
+ # See above for documentation on connection trunking.
+ #
+ pool {
+ #
+ # start:: Connections to create during module instantiation.
+ #
+ # If the server cannot create specified number of connections during instantiation
+ # it will exit.
+ #
+ # Set to `0` to allow the server to start without the database being available.
+ #
+ start = 0
+
+ #
+ # min:: Minimum number of connections to keep open.
+ #
+ min = 1
+
+ #
+ # max:: Maximum number of connections.
+ #
+ # If these connections are all in use and a new one is requested, the request
+ # will NOT get a connection.
+ #
+ max = 8
+
+ #
+ # connecting:: Maximum number of sockets to have in the "connecting" state.
+ #
+ # If a home server goes down, the module will close
+ # old / broken connections, and try to open new ones.
+ # In order to avoid flooding the home server with
+ # connection attempts, set the `connecting` value to
+ # a small number.
+ #
+ connecting = 1
+
+ #
+ # uses:: number of packets which will use the connection.
+ #
+ # After `uses` packets have been sent the connection
+ # will be closed, and a new one opened. For no
+ # limits, set `uses = 0`.
+ #
+ uses = 0
+
+ #
+ # lifetime:: lifetime of a connection, in seconds.
+ #
+ # After `lifetime` seconds have passed, no new
+ # packets will be sent on the connection. When all
+ # replies have been received, the connection will be
+ # closed.
+ #
+ # For no limits, set `lifetime = 0`.
+ #
+ # It is possible to use precise times, such as
+ # `lifetime = 1.023`, or even qualifiers such as
+ # `lifetime = 400ms`.
+ #
+ lifetime = 0
+
+ #
+ # open_delay:: How long (in seconds) a connection
+ # must be above `per_connection_target` before a new
+ # connection is opened.
+ #
+ # Parsing of this field is the same as for
+ # `lifetime`.
+ #
+ open_delay = 0.2
+
+ #
+ # close_delay:: How long (in seconds) a connection
+ # must be below `per_connection_target` before a
+ # connection is closed.
+ #
+ close_delay = 1.0
+
+ #
+ # manage_interval:: How often (in seconds) the
+ # connections are checked for limits, in order to
+ # open / close connections.
+ #
+ manage_interval = 0.2
+
+ #
+ # connection { ... }:: Per-connection configuration.
+ #
+ connection {
+ #
+ # connection_timeout:: How long to wait
+ # before giving up on a connection which is
+ # being opened.
+ #
+ connection_timeout = 3.0
+
+ #
+ # reconnect_delay:: If opening a connection
+ # fails, or an open connection fails,
+ # we wait `reconnect_delay` seconds before
+ # attempting to open another
+ # connection.
+ #
+ reconnect_delay = 5
+ }
+
+ #
+ # request { ... }:: Per-request configuration.
+ #
+ request {
+ #
+ # per_connection_max:: The maximum number of requests
+ # which are "live" on a particular connection.
+ #
+ per_connection_max = 255
+
+ #
+ # per_connection_target:: The target number
+ # of requests which are "live" on a
+ # particular connection.
+ #
+ # There can be a balance between overloading
+ # a connection, and under-utilizing it. The
+ # default is to fill each connection before
+ # opening a new one.
+ #
+ per_connection_target = 255
+
+ #
+ # free_delay:: How long to wait before
+ # freeing internal resources associated with
+ # the connection.
+ #
+ free_delay = 10
+ }
+
+ }
+}
#include <freeradius-devel/server/connection.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/heap.h>
+#include <freeradius-devel/util/rb_expire.h>
#include <sys/socket.h>
fr_bio_t *fd; //!< writing
fr_bio_fd_info_t const *info; //!< for replication
uint32_t id; //!< for replication
+ fr_rb_expire_t expires; //!< for proxying / client sending
} bio;
} bio_thread_t;
fr_retry_t retry; //!< retransmission timers
};
+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;
+
/** Turn a reply code into a module rcode;
*
*/
static void protocol_error_reply(bio_request_t *u, bio_handle_t *h);
-static unlang_action_t mod_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request);
-static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action);
static void mod_write(request_t *request, trunk_request_t *treq, bio_handle_t *h);
static int _bio_request_free(bio_request_t *u);
+static int8_t home_server_cmp(void const *one, void const *two);
+
#ifndef NDEBUG
/** Log additional information about a tracking entry
*
if (h->status_u) fr_event_timer_delete(&h->status_u->ev);
- fr_event_fd_delete(h->ctx.el, h->fd, FR_EVENT_FILTER_IO);
-
- if (shutdown(h->fd, SHUT_RDWR) < 0) {
- DEBUG3("%s - Failed shutting down connection %s: %s",
- h->ctx.module_name, h->fd_info->name, fr_syserror(errno));
- }
-
/*
- * @todo - free the BIOs?
+ * The connection code will take care of deleting the FD from the event loop.
*/
- if (close(h->fd) < 0) {
- DEBUG3("%s - Failed closing connection %s: %s",
- h->ctx.module_name, h->fd_info->name, fr_syserror(errno));
- }
-
- h->fd = -1;
DEBUG("%s - Connection closed - %s", h->ctx.module_name, h->fd_info->name);
RETURN_MODULE_RCODE(rcode);
}
+static void do_signal(rlm_radius_t const *inst, bio_request_t *u, request_t *request, fr_signal_t action);
+
static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
{
rlm_radius_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_radius_t);
bio_request_t *u = talloc_get_type_abort(mctx->rctx, bio_request_t);
+
+ do_signal(inst, u, request, action);
+}
+
+static void do_signal(rlm_radius_t const *inst, bio_request_t *u, UNUSED request_t *request, fr_signal_t action)
+{
bio_handle_t *h;
/*
* unlang_request_is_scheduled will return false
* (don't use it).
*/
- if (!u->treq) {
- talloc_free(u);
- return;
- }
+ if (!u->treq) return;
switch (action) {
/*
case FR_SIGNAL_CANCEL:
trunk_request_signal_cancel(u->treq);
u->treq = NULL;
- talloc_free(u); /* Should be freed soon anyway, but better to be explicit */
return;
/*
return 0;
}
-static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, rlm_radius_t const *inst, void *thread, request_t *request)
+static int mod_enqueue(bio_request_t **p_u, fr_retry_config_t const **p_retry_config,
+ rlm_radius_t const *inst, trunk_t *trunk, request_t *request)
{
- bio_thread_t *t = talloc_get_type_abort(thread, bio_thread_t);
bio_request_t *u;
trunk_request_t *treq;
fr_retry_config_t const *retry_config;
if (request->packet->code == FR_RADIUS_CODE_STATUS_SERVER) {
RWDEBUG("Status-Server is reserved for internal use, and cannot be sent manually.");
- RETURN_MODULE_NOOP;
+ return 0;
}
- treq = trunk_request_alloc(t->ctx.trunk, request);
- if (!treq) RETURN_MODULE_FAIL;
+ treq = trunk_request_alloc(trunk, request);
+ if (!treq) {
+ REDEBUG("Failed allocating handler for request");
+ return -1;
+ }
MEM(u = talloc_zero(request, bio_request_t));
talloc_set_destructor(u, _bio_request_free);
u->rcode = RLM_MODULE_FAIL;
- switch(trunk_request_enqueue(&treq, t->ctx.trunk, request, u, u)) {
+ switch(trunk_request_enqueue(&treq, trunk, request, u, u)) {
case TRUNK_ENQUEUE_OK:
case TRUNK_ENQUEUE_IN_BACKLOG:
break;
fr_assert(!u->rr && !u->packet); /* Should not have been fed to the muxer */
trunk_request_free(&treq); /* Return to the free list */
talloc_free(u);
- RETURN_MODULE_FAIL;
+ return -1;
case TRUNK_ENQUEUE_DST_UNAVAILABLE:
REDEBUG("All destinations are down - cannot send packet");
switch (inst->mode) {
case RLM_RADIUS_MODE_INVALID:
case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE: /* unconnected sockets are UDP, and bypass the trunk */
- case RLM_RADIUS_MODE_UNCONNECTED_PROXY: /* unconnected sockets are UDP, and bypass the trunk */
- RETURN_MODULE_FAIL;
+ REDEBUG("Internal sanity check failed - connection trunking cannot be used for replication");
+ return -1;
/*
* We originate this packet if it was taken from the detail module, which doesn't have a
* packet code. This lets us receive Accounting-Request, and originate
* Disconnect-Request.
*/
+ case RLM_RADIUS_MODE_XLAT_PROXY:
case RLM_RADIUS_MODE_PROXY:
if (!request->parent) {
u->proxied = (request->client->cs != NULL);
* The event loop will take care of demux && sending the
* packet, along with any retransmissions.
*/
- return unlang_module_yield_to_retry(request, mod_resume, mod_retry, mod_signal, 0, u, retry_config);
+ *p_u = u;
+ *p_retry_config = retry_config;
+
+ return 1;
}
+static const trunk_io_funcs_t io_funcs = {
+ .connection_alloc = thread_conn_alloc,
+ .connection_notify = thread_conn_notify,
+ .request_prioritise = request_prioritise,
+ .request_mux = request_mux,
+ .request_demux = request_demux,
+ .request_conn_release = request_conn_release,
+ .request_complete = request_complete,
+ .request_fail = request_fail,
+ .request_cancel = request_cancel,
+};
+
+
/** Instantiate thread data for the submodule.
*
*/
rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
bio_thread_t *thread = talloc_get_type_abort(mctx->thread, bio_thread_t);
- static trunk_io_funcs_t io_funcs = {
- .connection_alloc = thread_conn_alloc,
- .connection_notify = thread_conn_notify,
- .request_prioritise = request_prioritise,
- .request_mux = request_mux,
- .request_demux = request_demux,
- .request_conn_release = request_conn_release,
- .request_complete = request_complete,
- .request_fail = request_fail,
- .request_cancel = request_cancel,
- };
-
thread->ctx.el = mctx->el;
thread->ctx.inst = inst;
thread->ctx.fd_config = &inst->fd_config;
thread->ctx.radius_ctx = inst->common_ctx;
if ((inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
- (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ (inst->mode != RLM_RADIUS_MODE_XLAT_PROXY)) {
thread->ctx.trunk = trunk_alloc(thread, mctx->el, &io_funcs,
&inst->trunk_conf, inst->name, thread, false);
if (!thread->ctx.trunk) return -1;
return 0;
}
+ /*
+ * Allocate the unconnected replication socket.
+ */
thread->bio.fd = fr_bio_fd_alloc(thread, &thread->ctx.inst->fd_config, 0);
if (!thread->bio.fd) {
PERROR("%s - failed opening socket - ", inst->name);
(void) fr_bio_fd_write_only(thread->bio.fd);
DEBUG("%s - Opened unconnected replication socket %s", inst->name, thread->bio.info->name);
- } else {
- DEBUG("%s - Opened unconnected proxy socket %s", inst->name, thread->bio.info->name);
+ return 0;
}
+ DEBUG("%s - Opened unconnected proxy socket %s", inst->name, thread->bio.info->name);
+
+ /*
+ * @todo - make lifetime configurable?
+ */
+ fr_rb_expire_inline_talloc_init(&thread->bio.expires, home_server_t, expire, home_server_cmp, NULL,
+ fr_time_delta_from_sec(60));
+
return 0;
}
*/
return XLAT_ACTION_DONE;
}
+
+// **********************************************************************
+
+/** Dynamic home server code
+ *
+ */
+
+static int8_t home_server_cmp(void const *one, void const *two)
+{
+ home_server_t const *a = one;
+ home_server_t const *b = two;
+ int8_t rcode;
+
+ rcode = fr_ipaddr_cmp(&a->fd_config.dst_ipaddr, &b->fd_config.dst_ipaddr);
+ if (rcode != 0) return rcode;
+
+ return CMP(a->fd_config.dst_port, b->fd_config.dst_port);
+}
+
+static xlat_action_t xlat_sendto_resume(TALLOC_CTX *ctx, fr_dcursor_t *out,
+ xlat_ctx_t const *xctx,
+ UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
+{
+ bio_request_t *u = talloc_get_type_abort(xctx->rctx, bio_request_t);
+ fr_value_box_t *dst;
+
+ if (u->rcode == RLM_MODULE_FAIL) return XLAT_ACTION_FAIL;
+
+ MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_UINT32, NULL)); /* @todo - attr for rcode? */
+ dst->vb_uint32 = u->rcode;
+
+ fr_dcursor_append(out, dst);
+
+ return XLAT_ACTION_DONE;
+}
+
+static void xlat_sendto_signal(xlat_ctx_t const *xctx, request_t *request, fr_signal_t action)
+{
+ rlm_radius_t const *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_radius_t);
+ bio_request_t *u = talloc_get_type_abort(xctx->rctx, bio_request_t);
+
+ do_signal(inst, u, request, action);
+}
+
+/*
+ * @todo - change this to mod_retry
+ */
+static void xlat_sendto_timeout(xlat_ctx_t const *xctx, request_t *request, UNUSED fr_time_t fired)
+{
+// rlm_radius_t const *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_radius_t);
+ bio_request_t *u = talloc_get_type_abort(xctx->rctx, bio_request_t);
+
+ RDEBUG2("XXX Timeout sending packet");
+
+ fr_assert(u->treq != NULL);
+
+ u->rcode = RLM_MODULE_FAIL;
+ trunk_request_signal_fail(u->treq);
+
+ unlang_interpret_mark_runnable(request);
+}
+
+/*
+ * %proxy.sendto.ipaddr(ipaddr, port, secret)
+ */
+static xlat_action_t xlat_radius_client(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out,
+ xlat_ctx_t const *xctx,
+ request_t *request, fr_value_box_list_t *args)
+{
+ rlm_radius_t const *inst = talloc_get_type_abort(xctx->mctx->mi->data, rlm_radius_t);
+ bio_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, bio_thread_t);
+ fr_value_box_t *ipaddr, *port, *secret;
+ home_server_t *home;
+ bio_request_t *u = NULL;
+ fr_retry_config_t const *retry_config = NULL;
+ fr_time_t timeout;
+ int rcode;
+
+ XLAT_ARGS(args, &ipaddr, &port, &secret);
+
+ /*
+ * Can't change IP address families.
+ */
+ if (ipaddr->vb_ip.af != thread->bio.info->socket.af) {
+ RDEBUG("Invalid destination IP address family in %pV", ipaddr);
+ return -1;
+ }
+
+ 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,
+ },
+ });
+ if (!home) {
+ MEM(home = talloc(thread, home_server_t));
+
+ *home = (home_server_t) {
+ .ctx = (bio_handle_ctx_t) {
+ .el = unlang_interpret_event_list(request),
+ .module_name = inst->name,
+ .inst = inst,
+ },
+ };
+
+ 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;
+
+ home->ctx.radius_ctx = (fr_radius_ctx_t) {
+ .secret = talloc_strdup(home, secret->vb_strvalue),
+ .secret_length = secret->vb_length,
+ .proxy_state = inst->common_ctx.proxy_state,
+ };
+
+ /*
+ * Allocate the trunk and start it up.
+ */
+ home->ctx.trunk = trunk_alloc(thread, unlang_interpret_event_list(request), &io_funcs,
+ &inst->trunk_conf, inst->name, home, false);
+ if (!home->ctx.trunk) {
+ fail:
+ talloc_free(home);
+ return XLAT_ACTION_FAIL;
+ }
+
+ if (!fr_rb_expire_insert(&thread->bio.expires, home, fr_time())) goto fail;
+ } else {
+ fr_rb_expire_update(&thread->bio.expires, home, fr_time());
+
+ /*
+ * @todo - expire old entries, but only if they're not used?
+ */
+ }
+
+ /*
+ * Enqueue the packet on the per-home-server trunk.
+ */
+ rcode = mod_enqueue(&u, &retry_config, inst, home->ctx.trunk, request);
+ if (rcode <= 0) {
+ REDEBUG("Failed enqueuing packet");
+ return XLAT_ACTION_FAIL;
+ }
+ fr_assert(u != NULL);
+ fr_assert(retry_config != NULL);
+
+ timeout = fr_time_add(fr_time(), fr_time_delta_from_sec(2));
+
+ if (unlang_xlat_timeout_add(request, xlat_sendto_timeout, u, timeout) < 0) {
+ RPEDEBUG("Adding event failed");
+ return XLAT_ACTION_FAIL;
+ }
+
+ return unlang_xlat_yield(request, xlat_sendto_resume, xlat_sendto_signal, ~(FR_SIGNAL_CANCEL | FR_SIGNAL_DUP), u);
+}
CONF_PARSER_TERMINATOR
};
+/*
+ * We only parse the pool options if we're connected.
+ */
+static conf_parser_t const pool_config[] = {
+ { FR_CONF_POINTER("status_check", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_check_config },
+
+ { FR_CONF_OFFSET_SUBSECTION("pool", 0, rlm_radius_t, trunk_conf, trunk_config ) },
+
+ CONF_PARSER_TERMINATOR
+};
+
/*
* A mapping of configuration file names to internal variables.
*/
static fr_table_num_sorted_t mode_names[] = {
{ L("client"), RLM_RADIUS_MODE_CLIENT },
+ { L("dynamic-proxy"), RLM_RADIUS_MODE_XLAT_PROXY },
{ L("proxy"), RLM_RADIUS_MODE_PROXY },
{ L("replicate"), RLM_RADIUS_MODE_REPLICATE },
{ L("unconnected-replicate"), RLM_RADIUS_MODE_UNCONNECTED_REPLICATE },
-// { L("unconnected-proxy"), RLM_RADIUS_MODE_UNCONNECTED_PROXY },
};
static size_t mode_names_len = NUM_ELEMENTS(mode_names);
char const *name = cf_pair_value(cf_item_to_pair(ci));
rlm_radius_mode_t mode;
rlm_radius_t *inst = talloc_get_type_abort(parent, rlm_radius_t);
+ CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
mode = fr_table_value_by_str(mode_names, name, RLM_RADIUS_MODE_INVALID);
*(rlm_radius_mode_t *) out = mode;
/*
- * Normally we want connected sockets, in which case we push additional configuration for connected sockets.
+ * Normally we want connected sockets, in which case we push additional configuration for
+ * connected sockets.
*/
- if ((mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) &&
- (mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));
-
+ switch (mode) {
+ default:
inst->fd_config.type = FR_BIO_FD_CONNECTED;
if (cf_section_rules_push(cs, connected_config) < 0) return -1;
+ break;
- } else {
+ case RLM_RADIUS_MODE_XLAT_PROXY:
+ inst->fd_config.type = FR_BIO_FD_UNCONNECTED; /* reset later when the home server is allocated */
+
+ if (cf_section_rules_push(cs, pool_config) < 0) return -1;
+ break;
+
+ case RLM_RADIUS_MODE_UNCONNECTED_REPLICATE:
inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
+ break;
}
return 0;
static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
{
rlm_radius_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_radius_t);
- rlm_rcode_t rcode;
+ bio_thread_t *thread = talloc_get_type_abort(mctx->thread, bio_thread_t);
fr_client_t *client;
+ int rcode;
+ bio_request_t *u = NULL;
+ fr_retry_config_t const *retry_config = NULL;
if (!request->packet->code) {
REDEBUG("You MUST specify a packet code");
* or %radius.sendto(ip, port, secret)
*/
if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
- (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ (inst->mode == RLM_RADIUS_MODE_XLAT_PROXY)) {
REDEBUG("When using 'mode = unconnected-*', this module cannot be used in-place. Instead, it must be called via a function call");
RETURN_MODULE_FAIL;
}
* return another code which indicates what happened to
* the request...
*/
- return mod_enqueue(&rcode, inst,
- module_thread(mctx->mi)->data, request);
+ rcode = mod_enqueue(&u, &retry_config, inst, thread->ctx.trunk, request);
+ if (rcode == 0) RETURN_MODULE_NOOP;
+ if (rcode < 0) RETURN_MODULE_FAIL;
+
+ return unlang_module_yield_to_retry(request, mod_resume, mod_retry, mod_signal, 0, u, retry_config);
}
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) &&
- (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
+ if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) {
/*
* These limits are specific to RADIUS, and cannot be over-ridden
*/
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);
- } else {
+ }
+
+ 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 'mode = unconnected'");
+ cf_log_err(conf, "Cannot set 'src_port' when using this 'mode'");
return -1;
}
}
inst->status_check = false;
} else if ((inst->mode == RLM_RADIUS_MODE_UNCONNECTED_REPLICATE) ||
- (inst->mode == RLM_RADIUS_MODE_UNCONNECTED_PROXY)) {
- cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = unconnected-*'",
+ (inst->mode == RLM_RADIUS_MODE_XLAT_PROXY)) {
+ cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode' setting",
fr_radius_packet_name[inst->status_check]);
inst->status_check = false;
}
xlat_func_args_set(xlat, xlat_radius_send_args);
break;
- case RLM_RADIUS_MODE_UNCONNECTED_PROXY:
- fr_assert(0); /* not implemented */
+ case RLM_RADIUS_MODE_XLAT_PROXY:
+ xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "sendto.ipaddr", xlat_radius_client, FR_TYPE_UINT32);
+ xlat_func_args_set(xlat, xlat_radius_send_args);
break;
default: