--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more loads.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file proto_load.c
+ * @brief Load master protocol handler.
+ *
+ * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2016 Alan DeKok (aland@freeradius.org)
+ */
+#include <freeradius-devel/io/application.h>
+#include <freeradius-devel/io/listen.h>
+#include <freeradius-devel/io/schedule.h>
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/server/virtual_servers.h>
+#include <freeradius-devel/util/debug.h>
+
+#include "proto_load.h"
+
+extern fr_app_t proto_load;
+static int type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, CONF_PARSER const *rule);
+static int transport_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, CONF_PARSER const *rule);
+
+/** How to parse a Load listen section
+ *
+ */
+static CONF_PARSER const proto_load_config[] = {
+ { FR_CONF_OFFSET("type", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY | FR_TYPE_REQUIRED, proto_load_t,
+ type), .func = type_parse },
+ { FR_CONF_OFFSET("transport", FR_TYPE_VOID, proto_load_t, io.submodule),
+ .func = transport_parse, .dflt = "step" },
+
+ /*
+ * Add this as a synonym so normal humans can understand it.
+ */
+ { FR_CONF_OFFSET("max_entry_size", FR_TYPE_UINT32, proto_load_t, max_packet_size) } ,
+
+ /*
+ * For performance tweaking. NOT for normal humans.
+ */
+ { FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_load_t, max_packet_size) } ,
+ { FR_CONF_OFFSET("num_messages", FR_TYPE_UINT32, proto_load_t, num_messages) } ,
+
+ { FR_CONF_OFFSET("priority", FR_TYPE_UINT32, proto_load_t, priority) },
+
+ CONF_PARSER_TERMINATOR
+};
+
+static fr_dict_t const *dict_freeradius;
+
+extern fr_dict_autoload_t proto_load_dict[];
+fr_dict_autoload_t proto_load_dict[] = {
+ { .out = &dict_freeradius, .proto = "freeradius" },
+
+ { NULL }
+};
+
+static fr_dict_attr_t const *attr_packet_dst_ip_address;
+static fr_dict_attr_t const *attr_packet_dst_ipv6_address;
+static fr_dict_attr_t const *attr_packet_dst_port;
+static fr_dict_attr_t const *attr_packet_original_timestamp;
+static fr_dict_attr_t const *attr_packet_src_ip_address;
+static fr_dict_attr_t const *attr_packet_src_ipv6_address;
+static fr_dict_attr_t const *attr_packet_src_port;
+static fr_dict_attr_t const *attr_protocol;
+
+extern fr_dict_attr_autoload_t proto_load_dict_attr[];
+fr_dict_attr_autoload_t proto_load_dict_attr[] = {
+ { .out = &attr_packet_dst_ip_address, .name = "Packet-Dst-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_freeradius },
+ { .out = &attr_packet_dst_ipv6_address, .name = "Packet-Dst-IPv6-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_freeradius },
+ { .out = &attr_packet_dst_port, .name = "Packet-Dst-Port", .type = FR_TYPE_UINT16, .dict = &dict_freeradius },
+ { .out = &attr_packet_original_timestamp, .name = "Packet-Original-Timestamp", .type = FR_TYPE_DATE, .dict = &dict_freeradius },
+ { .out = &attr_packet_src_ip_address, .name = "Packet-Src-IP-Address", .type = FR_TYPE_IPV4_ADDR, .dict = &dict_freeradius },
+ { .out = &attr_packet_src_ipv6_address, .name = "Packet-Src-IPv6-Address", .type = FR_TYPE_IPV6_ADDR, .dict = &dict_freeradius },
+ { .out = &attr_packet_src_port, .name = "Packet-Src-Port", .type = FR_TYPE_UINT16, .dict = &dict_freeradius },
+ { .out = &attr_protocol, .name = "Protocol", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
+
+ { NULL }
+};
+
+/** Wrapper around dl_instance which translates the packet-type into a submodule name
+ *
+ * @param[in] ctx to allocate data in (instance of proto_load).
+ * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
+ * @param[in] parent Base structure address.
+ * @param[in] ci #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int type_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+ proto_load_t *inst = talloc_get_type_abort(parent, proto_load_t);
+ fr_dict_enum_t const *type_enum;
+ CONF_PAIR *cp = cf_item_to_pair(ci);
+ char const *value = cf_pair_value(cp);
+
+ *((char const **) out) = value;
+
+ inst->dict = virtual_server_namespace_by_ci(ci);
+ if (!inst->dict) {
+ cf_log_err(ci, "Please define 'namespace' in this virtual server");
+ return -1;
+ }
+
+ inst->attr_packet_type = fr_dict_attr_by_name(NULL, fr_dict_root(inst->dict), "Packet-Type");
+ if (!inst->attr_packet_type) {
+ cf_log_err(ci, "Failed to find 'Packet-Type' attribute");
+ return -1;
+ }
+
+ if (!value) {
+ cf_log_err(ci, "No value given for 'type'");
+ return -1;
+ }
+
+ type_enum = fr_dict_enum_by_name(inst->attr_packet_type, value, -1);
+ if (!type_enum) {
+ cf_log_err(ci, "Invalid type \"%s\"", value);
+ return -1;
+ }
+
+ inst->code = type_enum->value->vb_uint32;
+ return 0;
+}
+
+/** Wrapper around dl_instance
+ *
+ * @param[in] ctx to allocate data in (instance of proto_load).
+ * @param[out] out Where to write a dl_module_inst_t containing the module handle and instance.
+ * @param[in] parent Base structure address.
+ * @param[in] ci #CONF_PAIR specifying the name of the type module.
+ * @param[in] rule unused.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int transport_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
+ CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+ char const *name = cf_pair_value(cf_item_to_pair(ci));
+ dl_module_inst_t *parent_inst;
+ CONF_SECTION *listen_cs = cf_item_to_section(cf_parent(ci));
+ CONF_SECTION *transport_cs;
+
+ transport_cs = cf_section_find(listen_cs, name, NULL);
+
+ /*
+ * Allocate an empty section if one doesn't exist
+ * this is so defaults get parsed.
+ */
+ if (!transport_cs) transport_cs = cf_section_alloc(listen_cs, listen_cs, name, NULL);
+
+ parent_inst = cf_data_value(cf_data_find(listen_cs, dl_module_inst_t, "proto_load"));
+ fr_assert(parent_inst);
+
+ return dl_module_instance(ctx, out, transport_cs, parent_inst, name, DL_MODULE_TYPE_SUBMODULE);
+}
+
+/** Decode the packet, and set the request->process function
+ *
+ */
+static int mod_decode(void const *instance, request_t *request, uint8_t *const data, size_t data_len)
+{
+ proto_load_t const *inst = talloc_get_type_abort_const(instance, proto_load_t);
+
+ request->dict = inst->dict;
+ request->packet->code = inst->code;
+
+ /*
+ * Set default addresses
+ */
+ request->packet->socket.fd = -1;
+ request->packet->socket.inet.src_ipaddr.af = AF_INET;
+ request->packet->socket.inet.src_ipaddr.addr.v4.s_addr = htonl(INADDR_NONE);
+ request->packet->socket.inet.dst_ipaddr = request->packet->socket.inet.src_ipaddr;
+
+ request->reply->socket.inet.src_ipaddr = request->packet->socket.inet.src_ipaddr;
+ request->reply->socket.inet.dst_ipaddr = request->packet->socket.inet.src_ipaddr;
+
+ /*
+ * The app_io is responsible for decoding all of the data.
+ */
+ return inst->io.app_io->decode(inst->io.app_io_instance, request, data, data_len);
+}
+
+/*
+ * We don't need to encode any of the replies. We just go "yeah, it's fine".
+ */
+static ssize_t mod_encode(UNUSED void const *instance, request_t *request, uint8_t *buffer, size_t buffer_len)
+{
+ if (buffer_len < 1) return -1;
+
+ *buffer = request->reply->code;
+ return 1;
+}
+
+/** Open listen sockets/connect to external event source
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] sc to add our file descriptor to.
+ * @param[in] conf Listen section parsed to give us isntance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_open(void *instance, fr_schedule_t *sc, UNUSED CONF_SECTION *conf)
+{
+ proto_load_t *inst = talloc_get_type_abort(instance, proto_load_t);
+
+ inst->io.app = &proto_load;
+ inst->io.app_instance = instance;
+
+ /*
+ * io.app_io should already be set
+ */
+ return fr_master_io_listen(inst, &inst->io, sc,
+ inst->max_packet_size, inst->num_messages);
+}
+
+
+/** Instantiate the application
+ *
+ * Instantiate I/O and type submodules.
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] conf Listen section parsed to give us isntance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_instantiate(void *instance, CONF_SECTION *conf)
+{
+ proto_load_t *inst = talloc_get_type_abort(instance, proto_load_t);
+
+ fr_assert(inst->io.submodule);
+
+ /*
+ * These configuration items are not printed by default,
+ * because normal people shouldn't be touching them.
+ */
+ if (!inst->max_packet_size && inst->io.app_io) inst->max_packet_size = inst->io.app_io->default_message_size;
+
+ if (!inst->num_messages) inst->num_messages = 256;
+
+ FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, >=, 32);
+ FR_INTEGER_BOUND_CHECK("num_messages", inst->num_messages, <=, 65535);
+
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 1024);
+ FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
+
+ /*
+ * Instantiate the master io submodule
+ */
+ return fr_master_app_io.instantiate(&inst->io, conf);
+}
+
+/** Bootstrap the application
+ *
+ * Bootstrap I/O and type submodules.
+ *
+ * @param[in] instance Ctx data for this application.
+ * @param[in] conf Listen section parsed to give us instance.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
+{
+ proto_load_t *inst = talloc_get_type_abort(instance, proto_load_t);
+
+ /*
+ * Ensure that the server CONF_SECTION is always set.
+ */
+ inst->io.server_cs = cf_item_to_section(cf_parent(conf));
+
+ /*
+ * No IO module, it's an empty listener.
+ */
+ if (!inst->io.submodule) {
+ cf_log_err(conf, "The load generator MUST have a 'transport = ...' set");
+ return -1;
+ }
+
+ /*
+ * Tell the master handler about the main protocol instance.
+ */
+ inst->io.app = &proto_load;
+ inst->io.app_instance = inst;
+
+ /*
+ * The listener is inside of a virtual server.
+ */
+ inst->server_cs = cf_item_to_section(cf_parent(conf));
+ inst->cs = conf;
+ inst->self = &proto_load;
+
+ virtual_server_dict_set(inst->server_cs, inst->dict, false);
+
+ /*
+ * We will need this for dynamic clients and connected sockets.
+ */
+ inst->io.dl_inst = dl_module_instance_by_data(inst);
+ fr_assert(inst != NULL);
+
+ /*
+ * Bootstrap the master IO handler.
+ */
+ return fr_master_app_io.bootstrap(&inst->io, conf);
+}
+
+
+fr_app_t proto_load = {
+ .magic = RLM_MODULE_INIT,
+ .name = "load",
+ .config = proto_load_config,
+ .inst_size = sizeof(proto_load_t),
+
+ .bootstrap = mod_bootstrap,
+ .instantiate = mod_instantiate,
+ .open = mod_open,
+ .decode = mod_decode,
+ .encode = mod_encode,
+};
/**
* $Id$
* @file proto_load_step.c
- * @brief RADIUS load generator
+ * @brief Generic protocol load generator
*
* @copyright 2019 The FreeRADIUS server project.
* @copyright 2019 Network RADIUS SARL (legal@networkradius.com)
#include <freeradius-devel/io/load.h>
#include <freeradius-devel/util/debug.h>
+#include "proto_load.h"
+
extern fr_app_io_t proto_load_step;
typedef struct proto_load_step_s proto_load_step_t;
} proto_load_step_thread_t;
struct proto_load_step_s {
- CONF_SECTION *cs; //!< our configuration
+ proto_load_t *parent;
- fr_dict_t const *dict; //!< dictionary to use
- fr_dict_attr_t const *attr_packet_type; //!< packet type in the dictionary
+ CONF_SECTION *cs; //!< our configuration
- char const *filename; //!< where to read input packets from
- uint8_t *packet; //!< encoded packet read from the file
- size_t packet_len; //!< length of packet
+ char const *filename; //!< where to read input packet from
+ fr_pair_list_t pair_list; //!< for input packet
- uint32_t max_packet_size; //!< for message ring buffer.
+ int code;
uint32_t max_attributes; //!< Limit maximum decodable attributes
RADCLIENT *client; //!< static client
static const CONF_PARSER load_listen_config[] = {
- { FR_CONF_OFFSET("filename", FR_TYPE_FILE_INPUT, proto_load_step_t, filename) },
+ { FR_CONF_OFFSET("filename", FR_TYPE_FILE_INPUT | FR_TYPE_REQUIRED | FR_TYPE_NOT_EMPTY, proto_load_step_t, filename) },
{ FR_CONF_OFFSET("csv", FR_TYPE_STRING, proto_load_step_t, csv) },
- { FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_load_step_t, max_packet_size), .dflt = "4096" } ,
{ FR_CONF_OFFSET("max_attributes", FR_TYPE_UINT32, proto_load_step_t, max_attributes), .dflt = STRINGIFY(RADIUS_MAX_ATTRIBUTES) } ,
{ FR_CONF_OFFSET("start_pps", FR_TYPE_UINT32, proto_load_step_t, load.start_pps) },
static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t *recv_time_p, uint8_t *buffer, size_t buffer_len, size_t *leftover, UNUSED uint32_t *priority, UNUSED bool *is_dup)
{
- proto_load_step_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_load_step_t);
+ proto_load_step_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_load_step_t);
proto_load_step_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_load_step_thread_t);
fr_io_address_t *address, **address_p;
- size_t packet_len;
-
if (thread->done) return -1;
*leftover = 0; /* always for load generation */
*recv_time_p = thread->recv_time;
- if (buffer_len < inst->packet_len) {
+ if (buffer_len < 1) {
DEBUG2("proto_load_step read buffer is too small for input packet");
return 0;
}
- memcpy(buffer, inst->packet, inst->packet_len);
- packet_len = inst->packet_len;
-
- // @todo - try for some variation in the packet
-
- /*
- * The packet is always OK for RADIUS.
- */
-
- /*
- * proto_radius sets the priority
- */
+ buffer[0] = 0;
/*
* Print out what we received.
*/
- DEBUG2("proto_load_step - Received %s ID %d length %d %s",
- fr_packet_codes[buffer[0]], buffer[1],
- (int) packet_len, thread->name);
+ DEBUG2("proto_load_step - reading packet for %s",
+ thread->name);
- return packet_len;
+ return 1;
}
}
-/** Open a load listener for RADIUS
+/** Open a load listener
*
*/
static int mod_open(fr_listen_t *li)
{
- proto_load_step_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_load_step_t);
+ proto_load_step_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_load_step_t);
proto_load_step_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_load_step_thread_t);
fr_ipaddr_t ipaddr;
}
-/** Open a load listener for RADIUS
+/** Open a load listener
*
*/
static int mod_close(fr_listen_t *li)
}
+/** Decode the packet
+ *
+ */
+static int mod_decode(void const *instance, request_t *request, UNUSED uint8_t *const data, UNUSED size_t data_len)
+{
+ proto_load_step_t const *inst = talloc_get_type_abort_const(instance, proto_load_step_t);
+ fr_io_track_t const *track = talloc_get_type_abort_const(request->async->packet_ctx, fr_io_track_t);
+ fr_io_address_t const *address = track->address;
+
+ /*
+ * Set the request dictionary so that we can do
+ * generic->protocol attribute conversions as
+ * the request runs through the server.
+ */
+ request->dict = inst->parent->dict;
+
+ /*
+ * Hacks for now until we have a lower-level decode routine.
+ */
+ if (inst->code) request->packet->code = inst->code;
+ request->packet->id = fr_rand() & 0xff;
+ request->reply->id = request->packet->id;
+ memset(request->packet->vector, 0, sizeof(request->packet->vector));
+
+ request->packet->data = talloc_zero_array(request->packet, uint8_t, 1);
+ request->packet->data_len = 1;
+
+ /*
+ * Note that we don't set a limit on max_attributes here.
+ * That MUST be set and checked in the underlying
+ * transport, via a call to fr_radius_ok().
+ */
+ (void) fr_pair_list_copy(request->request_ctx, &request->request_pairs, &inst->pair_list);
+
+ /*
+ * Set the rest of the fields.
+ */
+ request->client = UNCONST(RADCLIENT *, address->radclient);
+
+ request->packet->socket = address->socket;
+ fr_socket_addr_swap(&request->reply->socket, &address->socket);
+
+ REQUEST_VERIFY(request);
+
+ return 0;
+}
+
/** Set the event list for a new socket
*
* @param[in] li the listener
static int mod_bootstrap(void *instance, CONF_SECTION *cs)
{
proto_load_step_t *inst = talloc_get_type_abort(instance, proto_load_step_t);
+ dl_module_inst_t const *dl_inst;
- inst->cs = cs;
+ /*
+ * Find the dl_module_inst_t holding our instance data
+ * so we can find out what the parent of our instance
+ * was.
+ */
+ dl_inst = dl_module_instance_by_data(instance);
+ fr_assert(dl_inst);
- FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 20);
- FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65536);
+ inst->parent = talloc_get_type_abort(dl_inst->parent->data, proto_load_t);
+
+ inst->cs = cs;
FR_INTEGER_BOUND_CHECK("start_pps", inst->load.start_pps, >=, 10);
FR_INTEGER_BOUND_CHECK("start_pps", inst->load.start_pps, <, 400000);
{
proto_load_step_t *inst = talloc_get_type_abort(instance, proto_load_step_t);
RADCLIENT *client;
-
- bool done;
fr_pair_t *vp;
- fr_pair_list_t vps;
-// ssize_t packet_len;
- int code = -1;
- fr_pair_list_init(&vps);
+ fr_pair_list_init(&inst->pair_list);
inst->client = client = talloc_zero(inst, RADCLIENT);
if (!inst->client) return 0;
if (inst->filename) {
FILE *fp;
+ bool done = false;
fp = fopen(inst->filename, "r");
if (!fp) {
return -1;
}
- if (fr_pair_list_afrom_file(inst, inst->dict, &vps, fp, &done) < 0) {
+ if (fr_pair_list_afrom_file(inst, inst->parent->dict, &inst->pair_list, fp, &done) < 0) {
cf_log_perr(cs, "Failed reading %s", inst->filename);
fclose(fp);
return -1;
fclose(fp);
}
- MEM(inst->packet = talloc_zero_array(inst, uint8_t, inst->max_packet_size));
-
- vp = fr_pair_find_by_da(&vps, inst->attr_packet_type, 0);
- if (vp) code = vp->vp_uint32;
-
- return -1; /* not done yet! */
-
-#if 0
- /*
- * Encode the packet.
- */
- packet_len = fr_radius_encode(inst->packet, inst->max_packet_size, NULL,
- client->secret, talloc_array_length(client->secret),
- code, 0, &vps);
- if (packet_len <= 0) {
- cf_log_perr(cs, "Failed encoding packet from %s",
- inst->filename);
- return -1;
- }
-
- inst->packet_len = packet_len;
-#endif
+ vp = fr_pair_find_by_da(&inst->pair_list, inst->parent->attr_packet_type, 0);
+ if (vp) inst->code = vp->vp_uint32;
return 0;
}
.event_list_set = mod_event_list_set,
.client_find = mod_client_find,
.get_name = mod_name,
+
+ .decode = mod_decode,
};