From: Alan T. DeKok Date: Tue, 29 Jun 2021 19:23:15 +0000 (-0400) Subject: add proto_listen_load X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=da493d53a44dd74652662d1e060071d0d3cc1a9d;p=thirdparty%2Ffreeradius-server.git add proto_listen_load doesn't quite work yet for a few reasons, but that will come next. --- diff --git a/src/listen/load/all.mk b/src/listen/load/all.mk index 77a03949eba..59cc977b588 100644 --- a/src/listen/load/all.mk +++ b/src/listen/load/all.mk @@ -1,2 +1,3 @@ SUBMAKEFILES := \ + proto_load.mk \ proto_load_step.mk \ diff --git a/src/listen/load/proto_load.c b/src/listen/load/proto_load.c new file mode 100644 index 00000000000..fa6033c701e --- /dev/null +++ b/src/listen/load/proto_load.c @@ -0,0 +1,340 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#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, +}; diff --git a/src/listen/load/proto_load.mk b/src/listen/load/proto_load.mk new file mode 100644 index 00000000000..0c52e918fe6 --- /dev/null +++ b/src/listen/load/proto_load.mk @@ -0,0 +1,9 @@ +TARGETNAME := proto_load + +ifneq "$(TARGETNAME)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := proto_load.c + +TGT_PREREQS := $(LIBFREERADIUS_SERVER) libfreeradius-io.a diff --git a/src/listen/load/proto_load_step.c b/src/listen/load/proto_load_step.c index 8a604f3c8da..63a3ab99248 100644 --- a/src/listen/load/proto_load_step.c +++ b/src/listen/load/proto_load_step.c @@ -17,7 +17,7 @@ /** * $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) @@ -34,6 +34,8 @@ #include #include +#include "proto_load.h" + extern fr_app_io_t proto_load_step; typedef struct proto_load_step_s proto_load_step_t; @@ -60,16 +62,14 @@ typedef struct { } 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 @@ -81,10 +81,9 @@ struct proto_load_step_s { 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) }, @@ -101,12 +100,10 @@ static const CONF_PARSER load_listen_config[] = { 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 */ @@ -125,32 +122,20 @@ static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t *recv_time *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; } @@ -186,12 +171,12 @@ static ssize_t mod_write(fr_listen_t *li, UNUSED void *packet_ctx, fr_time_t req } -/** 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; @@ -216,7 +201,7 @@ static int mod_open(fr_listen_t *li) } -/** Open a load listener for RADIUS +/** Open a load listener * */ static int mod_close(fr_listen_t *li) @@ -265,6 +250,53 @@ static void write_stats(fr_event_list_t *el, fr_time_t now, void *uctx) } +/** 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 @@ -315,11 +347,19 @@ static char const *mod_name(fr_listen_t *li) 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); @@ -355,14 +395,9 @@ static int mod_instantiate(void *instance, CONF_SECTION *cs) { 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; @@ -376,6 +411,7 @@ static int mod_instantiate(void *instance, CONF_SECTION *cs) if (inst->filename) { FILE *fp; + bool done = false; fp = fopen(inst->filename, "r"); if (!fp) { @@ -384,7 +420,7 @@ static int mod_instantiate(void *instance, CONF_SECTION *cs) 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; @@ -393,28 +429,8 @@ static int mod_instantiate(void *instance, CONF_SECTION *cs) 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; } @@ -438,4 +454,6 @@ fr_app_io_t proto_load_step = { .event_list_set = mod_event_list_set, .client_find = mod_client_find, .get_name = mod_name, + + .decode = mod_decode, };