]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add "process test" state machine
authorAlan T. DeKok <aland@freeradius.org>
Fri, 24 Feb 2023 15:42:45 +0000 (10:42 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 24 Feb 2023 15:44:07 +0000 (10:44 -0500)
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

src/bin/unit_test_module.c
src/process/test/all.mk [new file with mode: 0644]
src/process/test/base.c [new file with mode: 0644]
src/tests/process/all.mk [new file with mode: 0644]
src/tests/process/dictionary [new file with mode: 0644]
src/tests/process/radius/access_accept [new file with mode: 0644]
src/tests/process/test.attrs [new file with mode: 0644]
src/tests/process/unit_test_module.conf [new file with mode: 0644]

index 4734e259955d2e89988de049674c6c50d1b7da8e..873f7158b382dc658262c7460697e963d9aedb07 100644 (file)
@@ -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 (file)
index 0000000..0ebd9b6
--- /dev/null
@@ -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 (file)
index 0000000..99cffab
--- /dev/null
@@ -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 <freeradius-devel/server/protocol.h>
+#include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/arp/arp.h>
+
+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 <freeradius-devel/server/process.h>
+
+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 (file)
index 0000000..41cbc73
--- /dev/null
@@ -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 (file)
index 0000000..24a624c
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/src/tests/process/test.attrs b/src/tests/process/test.attrs
new file mode 100644 (file)
index 0000000..693c933
--- /dev/null
@@ -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 (file)
index 0000000..5550e39
--- /dev/null
@@ -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
+       }
+}