From: Alan T. DeKok Date: Fri, 24 Feb 2023 15:42:45 +0000 (-0500) Subject: add "process test" state machine X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d83764f94a5bcd606ce0d536ea7990cf92196fd2;p=thirdparty%2Ffreeradius-server.git add "process test" state machine and the beginnings of "make test.process" We will be running the protocol state machines from the "test" virtual server, which lets us to multi-round challenge / response where necessary --- diff --git a/src/bin/unit_test_module.c b/src/bin/unit_test_module.c index 4734e259955..873f7158b38 100644 --- a/src/bin/unit_test_module.c +++ b/src/bin/unit_test_module.c @@ -778,10 +778,19 @@ int main(int argc, char *argv[]) EXIT_WITH_FAILURE; } - if (fr_dict_autoload(unit_test_module_dict) < 0) { - fr_perror("%s", config->name); - EXIT_WITH_FAILURE; + /* + * Manually load the protocol dictionary, unless it's "test" + */ + if (strcmp(PROTOCOL_NAME, "test") != 0) { + if (fr_dict_autoload(unit_test_module_dict) < 0) { + fr_perror("%s", config->name); + EXIT_WITH_FAILURE; + } + } else { + dict_protocol = fr_dict_internal(); + dict_freeradius = dict_protocol; } + if (fr_dict_attr_autoload(unit_test_module_dict_attr) < 0) { fr_perror("%s", config->name); EXIT_WITH_FAILURE; @@ -855,6 +864,7 @@ int main(int argc, char *argv[]) * Do some sanity checking. */ dict_check = virtual_server_dict_by_name("default"); + if (!dict_check || (dict_check != dict_protocol)) { ERROR("Virtual server namespace does not match requested namespace '%s'", PROTOCOL_NAME); EXIT_WITH_FAILURE; @@ -1140,7 +1150,8 @@ cleanup: /* * Free our explicitly loaded internal dictionary */ - if (fr_dict_free(&dict, __FILE__) < 0) { + if ((dict_protocol != dict_freeradius) && + (fr_dict_free(&dict, __FILE__) < 0)) { fr_perror("unit_test_module - dict"); ret = EXIT_FAILURE; } diff --git a/src/process/test/all.mk b/src/process/test/all.mk new file mode 100644 index 00000000000..0ebd9b624fc --- /dev/null +++ b/src/process/test/all.mk @@ -0,0 +1,8 @@ +TARGETNAME := process_test + +TARGET := $(TARGETNAME)$(L) + +SOURCES := base.c +TGT_PREREQS := libfreeradius-util$(L) + +TGT_INSTALLDIR := diff --git a/src/process/test/base.c b/src/process/test/base.c new file mode 100644 index 00000000000..99cffab23ee --- /dev/null +++ b/src/process/test/base.c @@ -0,0 +1,135 @@ +/* + * 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 details. + * + * 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 src/process/arp/base.c + * @brief ARP processing. + * + * @copyright 2020 Network RADIUS SAS (legal@networkradius.com) + */ +#include +#include +#include + +static fr_dict_t const *dict_freeradius; + +extern fr_dict_autoload_t process_test_dict[]; +fr_dict_autoload_t process_test_dict[] = { + { .out = &dict_freeradius, .proto = "freeradius" }, + { NULL } +}; + +static fr_dict_attr_t const *attr_packet_type; + +extern fr_dict_attr_autoload_t process_test_dict_attr[]; +fr_dict_attr_autoload_t process_test_dict_attr[] = { + { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius}, + { NULL } +}; + +typedef struct { + uint64_t nothing; // so that the next field isn't at offset 0 + + CONF_SECTION *recv_request; + CONF_SECTION *send_reply; +} process_test_sections_t; + +typedef struct { + bool test; + + process_test_sections_t sections; +} process_test_t; + +typedef enum { + FR_TEST_INVALID = 0, + FR_TEST_REQUEST, + FR_TEST_REPLY, +} fr_test_packet_code_t; +#define FR_TEST_CODE_MAX (2) + +#define FR_TEST_PACKET_CODE_VALID(_code) ((_code == FR_TEST_REQUEST) || (_code == FR_TEST_REPLY)) + +#define PROCESS_PACKET_TYPE fr_test_packet_code_t +#define PROCESS_CODE_MAX FR_TEST_CODE_MAX +#define PROCESS_PACKET_CODE_VALID FR_TEST_PACKET_CODE_VALID +#define PROCESS_INST process_test_t +#include + +static fr_process_state_t const process_state[] = { + [ FR_TEST_REQUEST ] = { + .default_reply = FR_TEST_REPLY, + .rcode = RLM_MODULE_NOOP, + .recv = recv_generic, + .resume = resume_recv_generic, + .section_offset = offsetof(process_test_sections_t, recv_request), + }, + [ FR_TEST_REPLY ] = { + .default_reply = FR_TEST_REPLY, + .rcode = RLM_MODULE_NOOP, + .send = send_generic, + .resume = resume_send_generic, + .section_offset = offsetof(process_test_sections_t, send_reply), + }, +}; + +static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request) +{ + fr_process_state_t const *state; + + PROCESS_TRACE; + + (void)talloc_get_type_abort_const(mctx->inst->data, process_test_t); + fr_assert(PROCESS_PACKET_CODE_VALID(request->packet->code)); + + request->component = "test"; + request->module = NULL; + fr_assert(request->dict == dict_freeradius); + + UPDATE_STATE(packet); + + return state->recv(p_result, mctx, request); +} + +static const virtual_server_compile_t compile_list[] = { + { + .name = "recv", + .name2 = "Request", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(recv_request), + }, + { + .name = "send", + .name2 = "Reply", + .component = MOD_POST_AUTH, + .offset = PROCESS_CONF_OFFSET(send_reply), + }, + + COMPILE_TERMINATOR +}; + + +extern fr_process_module_t process_test; +fr_process_module_t process_test = { + .common = { + .magic = MODULE_MAGIC_INIT, + .name = "test", + .inst_size = sizeof(process_test_t), + }, + .process = mod_process, + .compile_list = compile_list, + .dict = &dict_freeradius, +}; diff --git a/src/tests/process/all.mk b/src/tests/process/all.mk new file mode 100644 index 00000000000..41cbc734711 --- /dev/null +++ b/src/tests/process/all.mk @@ -0,0 +1,92 @@ +# +# Unit tests for process state machines +# + + +# +# Test name +# +TEST := test.process + +# +# The test files are files without extensions. +# The list is unordered. The order is added in the next step by looking +# at precursors. +# +# * search ALL_TGTS +# * for process_foo targets +# * strip add "process_" prefix +# * strip off ".whatever" suffix +# * add directory name and wildcard file +# * use wildcard to find existing files +# * strip off directory name +# * filter out files we don't care about +# +# We're left with a set of files to run the tests on. +# +FILES := $(filter-out %.ignore %.conf %.md %.attrs %.mk %~ %.rej,$(subst $(DIR)/,,$(wildcard $(patsubst %,$(DIR)/%/*,$(basename $(subst process_,,$(filter process%,$(ALL_TGTS)))))))) + +$(eval $(call TEST_BOOTSTRAP)) + +# -S parse_new_conditions=yes -S use_new_conditions=yes -S forbid_update=yes + + +# +# For sheer laziness, allow "make test.process.foo" +# +define PROCESS_TEST +test.process.${1}: $(addprefix $(OUTPUT)/,${1}) + +test.process.help: TEST_PROCESS_HELP += test.process.${1} + +# +# The output depends on the process_foo state machine, +# and on the "test" process state machine. +# +# With filenames added for the output files +# +$(OUTPUT)/${1}: $(patsubst %,${BUILD_DIR}/lib/local/process_%.la,$(subst /,,$(dir ${1})) test) +endef +$(foreach x,$(FILES),$(eval $(call PROCESS_TEST,$x))) + +# +# Files in the output dir depend on the unit tests +# +# src/tests/process/radius/FOO unlang for the test +# build/tests/process/radius/FOO updated if the test succeeds +# build/tests/process/radius/FOO.log debug output for the test +# +# If the test fails, then look for ERROR in the input. No error +# means it's unexpected, so we die. +# +# Otherwise, check the log file for a parse error which matches the +# ERROR line in the input. +# +# NOTE: Grepping for $< is not safe cross platform, as on Linux it +# expands to the full absolute path, and on macOS it appears to be relative. +# +# To quickly find all failing tests, run: +# +# (make -k test.process 2>&1) | grep 'PROCESS=' | sed 's/PROCESS=//;s/ .*$//' +# +PROCESS_ARGS := -p test +PROCESS_ARGS += -D share/dictionary -d $(DIR)/ +PROCESS_ARGS += -S parse_new_conditions=yes -S use_new_conditions=yes -S forbid_update=yes +PROCESS_ARGS += -i $(DIR)/test.attrs + +$(OUTPUT)/%: $(DIR)/% $(TEST_BIN_DIR)/unit_test_module $(DIR)/unit_test_module.conf + $(eval CMD:=PROCESS=$< $(TEST_BIN)/unit_test_module $(PROCESS_ARGS) -r "$@" -xx) + @echo PROCESS-TEST $(notdir $@) + @mkdir -p $(dir $@) + @if ! $(CMD) > "$@.log" 2>&1 || ! test -f "$@"; then \ + cat $@.log; \ + echo "# $@.log"; \ + echo $(CMD); \ + exit 1; \ + fi + +$(TEST): + @touch $(BUILD_DIR)/tests/$@ + +$(TEST).help: + @echo make $(TEST_PROCESS_HELP) diff --git a/src/tests/process/dictionary b/src/tests/process/dictionary new file mode 100644 index 00000000000..24a624cf80f --- /dev/null +++ b/src/tests/process/dictionary @@ -0,0 +1,8 @@ +# +# Only for tests. +# +FLAGS internal + +DEFINE Packet-Type uint32 +VALUE Packet-Type Request 1 +VALUE Packet-Type Reply 2 diff --git a/src/tests/process/radius/access_accept b/src/tests/process/radius/access_accept new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tests/process/test.attrs b/src/tests/process/test.attrs new file mode 100644 index 00000000000..693c933d811 --- /dev/null +++ b/src/tests/process/test.attrs @@ -0,0 +1,10 @@ +# +# Input packet +# +Packet-Type = Request + +# +# Expected answer +# +Packet-Type == Reply +Result-Status == "success" diff --git a/src/tests/process/unit_test_module.conf b/src/tests/process/unit_test_module.conf new file mode 100644 index 00000000000..5550e39f6bf --- /dev/null +++ b/src/tests/process/unit_test_module.conf @@ -0,0 +1,33 @@ +# +# Minimal radiusd.conf for testing process state machines. +# + +raddb = raddb + +modconfdir = ${raddb}/mods-config + +modules { +$INCLUDE ../../../raddb/mods-available/always + +} + +server default { + namespace = test + + # + # process-specific configuration. Currently unused. + # + test { + } + + recv Request { + +$INCLUDE $ENV{PROCESS} + + ok + } + + send Reply { + ok + } +}