Skip to content

Commit

Permalink
add "mode = unconnected" and xlat function for replication
Browse files Browse the repository at this point in the history
%replicate.sendto.ipaddr(ip, port, secret)
  • Loading branch information
alandekok committed Dec 20, 2024
1 parent 0457664 commit 4092449
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 34 deletions.
141 changes: 131 additions & 10 deletions src/modules/rlm_radius2/bio.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ typedef struct {

rlm_radius_t const *inst; //!< our instance

trunk_t *trunk; //!< trunk handler
union {
trunk_t *trunk; //!< trunk handler
struct {
fr_bio_t *fd; //!< writing
fr_bio_fd_info_t const *info; //!< for replication
uint32_t id; //!< for replication
} bio;
};
} bio_thread_t;

typedef struct {
Expand All @@ -59,11 +66,6 @@ typedef struct {

typedef struct bio_request_s bio_request_t;

typedef struct {
struct iovec out; //!< Describes buffer to send.
trunk_request_t *treq; //!< Used for signalling.
} bio_coalesced_t;

/** Track the handle, which is tightly correlated with the FD
*
*/
Expand Down Expand Up @@ -2321,6 +2323,7 @@ static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, rlm_radius_t const *in
*/
switch (inst->mode) {
case RLM_RADIUS_MODE_INVALID:
case RLM_RADIUS_MODE_UNCONNECTED: /* unconnected sockets are UDP, and bypass the trunk */
RETURN_MODULE_FAIL;

/*
Expand Down Expand Up @@ -2385,7 +2388,7 @@ static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, rlm_radius_t const *in
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
{
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);
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,
Expand All @@ -2402,9 +2405,127 @@ static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)

thread->el = mctx->el;
thread->inst = inst;
thread->trunk = trunk_alloc(thread, mctx->el, &io_funcs,
&inst->trunk_conf, inst->name, thread, false);
if (!thread->trunk) return -1;

if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED) {
thread->trunk = trunk_alloc(thread, mctx->el, &io_funcs,
&inst->trunk_conf, inst->name, thread, false);
if (!thread->trunk) return -1;
return 0;
}

thread->bio.fd = fr_bio_fd_alloc(thread, &thread->inst->fd_config, 0);
if (!thread->bio.fd) {
PERROR("%s - failed opening socket - ", inst->name);
return CONNECTION_STATE_FAILED;
}

thread->bio.fd->uctx = thread;
thread->bio.info = fr_bio_fd_info(thread->bio.fd);

DEBUG("Opened replication socket %s", thread->bio.info->name);

return 0;
}

static xlat_arg_parser_t const xlat_radius_send_args[] = {
{ .required = true, .single = true, .type = FR_TYPE_COMBO_IP_ADDR },
{ .required = true, .single = true, .type = FR_TYPE_UINT16 },
{ .required = true, .single = true, .type = FR_TYPE_STRING },
XLAT_ARG_PARSER_TERMINATOR
};

/*
* %replicate.sendto.ipaddr(ipaddr, port, secret)
*/
static xlat_action_t xlat_radius_replicate(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out,
xlat_ctx_t const *xctx,
request_t *request, fr_value_box_list_t *args)
{
bio_thread_t *thread = talloc_get_type_abort(xctx->mctx->thread, bio_thread_t);
fr_value_box_t *ipaddr, *port, *secret;
ssize_t packet_len;
uint8_t buffer[4096];
fr_radius_ctx_t radius_ctx;
fr_radius_encode_ctx_t encode_ctx;
fr_bio_fd_packet_ctx_t addr;

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;
}

/*
* Warn if we're not replicating accounting data. It likely won't wokr/
*/
if (request->packet->code != FR_RADIUS_CODE_ACCOUNTING_REQUEST) {
RWDEBUG("Replication of packets other then Accounting-Request will likely not do what you want.");
}

/*
* Set up various context things.
*/
radius_ctx = (fr_radius_ctx_t) {
.secret = secret->vb_strvalue,
.secret_length = secret->vb_length,
.proxy_state = 0,
};

encode_ctx = (fr_radius_encode_ctx_t) {
.common = &radius_ctx,
.rand_ctx = (fr_fast_rand_t) {
.a = fr_rand(),
.b = fr_rand(),
},
.code = request->packet->code,
.id = thread->bio.id++ & 0xff,
.add_proxy_state = false,
};

/*
* Encode the entire packet.
*/
packet_len = fr_radius_encode(&FR_DBUFF_TMP(buffer, sizeof(buffer)),
&request->request_pairs, &encode_ctx);
if (fr_pair_encode_is_error(packet_len)) {
RPERROR("Failed encoding packet");
return XLAT_ACTION_FAIL;
}

/*
* Sign it.
*/
if (fr_radius_sign(buffer, NULL, (uint8_t const *) radius_ctx.secret, radius_ctx.secret_length) < 0) {
RERROR("Failed signing packet");
return XLAT_ACTION_FAIL;
}

/*
* Prepare destination address.
*/
addr = (fr_bio_fd_packet_ctx_t) {
.socket = thread->bio.info->socket,
};
addr.socket.inet.dst_ipaddr = ipaddr->vb_ip;
addr.socket.inet.dst_port = port->vb_uint16;

RDEBUG("Replicating packet to %pV:%u", ipaddr, port->vb_uint16);

/*
* We either send it, or fail.
*/
packet_len = fr_bio_write(thread->bio.fd, &addr, buffer, packet_len);
if (packet_len < 0) {
RPERROR("Failed sending packet to %pV:%u", ipaddr, port->vb_uint16);
return XLAT_ACTION_FAIL;
}

/*
* No return value.
*/
return XLAT_ACTION_DONE;
}
106 changes: 83 additions & 23 deletions src/modules/rlm_radius2/rlm_radius.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ RCSID("$Id$")

#include <freeradius-devel/io/application.h>
#include <freeradius-devel/server/modpriv.h>
#include <freeradius-devel/unlang/xlat_func.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/dlist.h>

Expand Down Expand Up @@ -101,6 +102,20 @@ static conf_parser_t const transport_config[] = {
CONF_PARSER_TERMINATOR
};

/*
* We only parse the pool options if we're connected.
*/
static conf_parser_t const connected_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 ) },

{ FR_CONF_POINTER("udp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },

{ FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config },

CONF_PARSER_TERMINATOR
};

/*
* A mapping of configuration file names to internal variables.
Expand All @@ -113,7 +128,7 @@ static conf_parser_t const module_config[] = {
{ FR_CONF_OFFSET_FLAGS("type", CONF_FLAG_NOT_EMPTY | CONF_FLAG_MULTI | CONF_FLAG_REQUIRED, rlm_radius_t, types),
.func = type_parse },

{ FR_CONF_OFFSET_FLAGS("replicate", CONF_FLAG_DEPRECATED, rlm_radius_t, replicate) },
{ FR_CONF_OFFSET_FLAGS("replicate", 0, rlm_radius_t, replicate) },

{ FR_CONF_OFFSET_FLAGS("synchronous", CONF_FLAG_DEPRECATED, rlm_radius_t, synchronous) },

Expand All @@ -122,8 +137,6 @@ static conf_parser_t const module_config[] = {
{ FR_CONF_OFFSET("max_packet_size", rlm_radius_t, max_packet_size), .dflt = "4096" },
{ FR_CONF_OFFSET("max_send_coalesce", rlm_radius_t, max_send_coalesce), .dflt = "1024" },

{ FR_CONF_POINTER("status_check", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = (void const *) status_check_config },

{ FR_CONF_OFFSET("max_attributes", rlm_radius_t, max_attributes), .dflt = STRINGIFY(RADIUS_MAX_ATTRIBUTES) },

{ FR_CONF_OFFSET("require_message_authenticator", rlm_radius_t, require_message_authenticator),
Expand All @@ -137,12 +150,6 @@ static conf_parser_t const module_config[] = {

{ FR_CONF_OFFSET("revive_interval", rlm_radius_t, revive_interval) },

{ 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_config },

{ FR_CONF_POINTER("tcp", 0, CONF_FLAG_SUBSECTION | CONF_FLAG_OPTIONAL, NULL), .subcs = (void const *) transport_config }
,
CONF_PARSER_TERMINATOR
};

Expand Down Expand Up @@ -204,6 +211,7 @@ static fr_table_num_sorted_t mode_names[] = {
{ L("client"), RLM_RADIUS_MODE_CLIENT },
{ L("proxy"), RLM_RADIUS_MODE_PROXY },
{ L("replicate"), RLM_RADIUS_MODE_REPLICATE },
{ L("unconnected"), RLM_RADIUS_MODE_UNCONNECTED },
};
static size_t mode_names_len = NUM_ELEMENTS(mode_names);

Expand Down Expand Up @@ -241,9 +249,18 @@ static int mode_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
*(rlm_radius_mode_t *) out = mode;

/*
* Normally we want connected sockets.
* Normally we want connected sockets, in which case we push additional configuration for connected sockets.
*/
inst->fd_config.type = FR_BIO_FD_CONNECTED;
if (mode != RLM_RADIUS_MODE_UNCONNECTED) {
CONF_SECTION *cs = cf_item_to_section(cf_parent(ci));

inst->fd_config.type = FR_BIO_FD_CONNECTED;

if (cf_section_rules_push(cs, connected_config) < 0) return -1;

} else {
inst->fd_config.type = FR_BIO_FD_UNCONNECTED;
}

return 0;
}
Expand Down Expand Up @@ -492,6 +509,15 @@ static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, modul
RETURN_MODULE_FAIL;
}

/*
* Unconnected sockets use %radius.replicate(ip, port, secret),
* or %radius.sendto(ip, port, secret)
*/
if (inst->mode == RLM_RADIUS_MODE_UNCONNECTED) {
REDEBUG("When using 'mode = unconnected', this module cannot be used in-place. Instead, it must be called via a function call");
RETURN_MODULE_FAIL;
}

if (!inst->allowed[request->packet->code]) {
REDEBUG("Packet code %s is disallowed by the configuration",
fr_radius_packet_name[request->packet->code]);
Expand Down Expand Up @@ -602,12 +628,24 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);

/*
* 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);
if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED) {
/*
* 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->fd_config.src_port != 0) {
cf_log_err(conf, "Cannot set 'src_port' when using 'mode = unconnected'");
return -1;
}

if (!inst->replicate) {
cf_log_err(conf, "Using 'mode = unconnected' also requires 'replicate = true'");
return -1;
}
}

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));
Expand All @@ -630,7 +668,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)

inst->common_ctx = (fr_radius_ctx_t) {
.secret = inst->secret,
.secret_length = talloc_array_length(inst->secret) - 1,
.secret_length = inst->secret ? talloc_array_length(inst->secret) - 1 : 0,
.proxy_state = fr_rand(),
};

Expand All @@ -653,10 +691,17 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
* If we're replicating, we don't care if the other end
* is alive.
*/
if (inst->status_check && (inst->mode == RLM_RADIUS_MODE_REPLICATE)) {
cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = replicate'",
fr_radius_packet_name[inst->status_check]);
inst->status_check = false;
if (inst->status_check) {
if (inst->mode == RLM_RADIUS_MODE_REPLICATE) {
cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = replicate'",
fr_radius_packet_name[inst->status_check]);
inst->status_check = false;

} else if (inst->mode == RLM_RADIUS_MODE_UNCONNECTED) {
cf_log_warn(conf, "Ignoring 'status_check = %s' due to 'mode = unconnected'",
fr_radius_packet_name[inst->status_check]);
inst->status_check = false;
}
}

/*
Expand Down Expand Up @@ -700,7 +745,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
/*
* Only check the async timers when we're acting as a client.
*/
if (inst->mode != RLM_RADIUS_MODE_CLIENT) return 0;
if ((inst->mode != RLM_RADIUS_MODE_CLIENT) && (inst->mode != RLM_RADIUS_MODE_UNCONNECTED)) return 0;

/*
* Set limits on retransmission timers
Expand Down Expand Up @@ -786,6 +831,20 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
return 0;
}

static int mod_bootstrap(module_inst_ctx_t const *mctx)
{
xlat_t *xlat;
rlm_radius_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);

if (inst->mode != RLM_RADIUS_MODE_UNCONNECTED) return 0;

xlat = module_rlm_xlat_register(mctx->mi->boot, mctx, "sendto.ipaddr", xlat_radius_replicate, FR_TYPE_VOID);
xlat_func_args_set(xlat, xlat_radius_send_args);

return 0;
}


static int mod_detach(module_detach_ctx_t const *mctx)
{
rlm_radius_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
Expand Down Expand Up @@ -828,6 +887,7 @@ module_rlm_t rlm_radius = {
.onload = mod_load,
.unload = mod_unload,

.bootstrap = mod_bootstrap,
.instantiate = mod_instantiate,
.detach = mod_detach,

Expand Down
1 change: 1 addition & 0 deletions src/modules/rlm_radius2/rlm_radius.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef enum {
RLM_RADIUS_MODE_PROXY,
RLM_RADIUS_MODE_CLIENT,
RLM_RADIUS_MODE_REPLICATE,
RLM_RADIUS_MODE_UNCONNECTED,
} rlm_radius_mode_t;

/*
Expand Down
2 changes: 1 addition & 1 deletion src/modules/rlm_radius2/track.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct radius_track_entry_s {
///< when its parent is freed. We also zero
///< out the tracking entry field in the parent.

request_t *request; //!< as always...
request_t *request; //!< as always...

void *uctx; //!< Result/resumption context.

Expand Down

0 comments on commit 4092449

Please sign in to comment.