#!/bin/sh # Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC") # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # shellcheck disable=SC1091 # SC1091: Not following: ... was not specified as input (see shellcheck -x). # shellcheck disable=SC2039 # SC2039: In POSIX sh, 'local' is undefined. # Exit with error if commands exit with non-zero and if undefined variables are # used. set -eu # Path to the temporary configuration file. CFG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_config.json" # Path to the Kea log file. LOG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test.log" # Path to the Kea lease file. LEASE_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_leases.csv" # Path to the Kea LFC application export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc" # Path to test hooks library HOOK_PATH="@abs_top_builddir@/src/bin/dhcp6/tests/.libs/libco3.so" # Kea configuration to be stored in the configuration file. CONFIG="{ \"Dhcp6\": { \"interfaces-config\": { \"interfaces\": [ ] }, \"server-id\": { \"type\": \"LLT\", \"persist\": false }, \"preferred-lifetime\": 3000, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"name\": \"$LEASE_FILE\", \"persist\": false, \"lfc-interval\": 0 }, \"subnet6\": [ { \"subnet\": \"2001:db8:1::/64\", \"id\": 1, \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ] } ], \"dhcp-ddns\": { \"enable-updates\": true, \"qualifying-suffix\": \"\" }, \"loggers\": [ { \"name\": \"kea-dhcp6\", \"output_options\": [ { \"output\": \"$LOG_FILE\" } ], \"severity\": \"INFO\" } ] } }" # Invalid configuration (syntax error) to check that Kea can check syntax. # This config has following errors: # - it should be interfaces-config/interfaces, not interfaces # - it should be subnet6/pools, no subnet6/pool CONFIG_BAD_SYNTAX="{ \"Dhcp6\": { \"interfaces\": [ ], \"preferred-lifetime\": 3000, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"persist\": false }, \"subnet6\": [ { \"subnet\": \"2001:db8:1::/64\", \"id\": 1, \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ] } ], \"loggers\": [ { \"name\": \"kea-dhcp6\", \"output_options\": [ { \"output\": \"$LOG_FILE\" } ], \"severity\": \"INFO\" } ] } }" # Invalid configuration (negative preferred-lifetime) to check that Kea # gracefully handles reconfiguration errors. CONFIG_INVALID="{ \"Dhcp6\": { \"interfaces-config\": { \"interfaces\": [ ] }, \"preferred-lifetime\": -3, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"persist\": false }, \"subnet6\": [ { \"subnet\": \"2001:db8:1::/64\", \"id\": 1, \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ] } ], \"loggers\": [ { \"name\": \"kea-dhcp6\", \"output_options\": [ { \"output\": \"$LOG_FILE\" } ], \"severity\": \"INFO\" } ] } }" # This config has bad pool values. The pool it out of scope for the subnet # it is defined in. Syntactically the config is correct, though. CONFIG_BAD_VALUES="{ \"Dhcp6\": { \"interfaces-config\": { \"interfaces\": [ ] }, \"server-id\": { \"type\": \"LLT\", \"persist\": false }, \"preferred-lifetime\": 3000, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"name\": \"$LEASE_FILE\", \"persist\": false, \"lfc-interval\": 0 }, \"subnet6\": [ { \"subnet\": \"2001:db8::/64\", \"id\": 1, \"pools\": [ { \"pool\": \"3000::-3000::ffff\" } ] } ], \"dhcp-ddns\": { \"enable-updates\": true, \"qualifying-suffix\": \"\" } } }" # Invalid configuration (hook explicitly fails to load) to check that performing # extra configuration checks detects the error. INVALID_CONFIG_HOOKS_LOAD="{ \"Dhcp6\": { \"interfaces-config\": { \"interfaces\": [ ] }, \"multi-threading\": { \"enable-multi-threading\": false }, \"server-id\": { \"type\": \"LLT\", \"persist\": false }, \"preferred-lifetime\": 3000, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"name\": \"$LEASE_FILE\", \"persist\": false, \"lfc-interval\": 0 }, \"subnet6\": [ { \"subnet\": \"2001:db8:1::/64\", \"id\": 1, \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ] } ], \"dhcp-ddns\": { \"enable-updates\": true, \"qualifying-suffix\": \"\" }, \"hooks-libraries\": [ { \"library\": \"$HOOK_PATH\", \"parameters\": { \"mode\": \"fail-on-load\" } } ], \"loggers\": [ { \"name\": \"kea-dhcp6\", \"output_options\": [ { \"output\": \"$LOG_FILE\" } ], \"severity\": \"INFO\" } ] } }" # Invalid configuration (hook point returns error) to check that performing # extra configuration checks detects the error. INVALID_CONFIG_HOOKS_CALLOUT_FAIL="{ \"Dhcp6\": { \"interfaces-config\": { \"interfaces\": [ ] }, \"multi-threading\": { \"enable-multi-threading\": false }, \"server-id\": { \"type\": \"LLT\", \"persist\": false }, \"preferred-lifetime\": 3000, \"valid-lifetime\": 4000, \"renew-timer\": 1000, \"rebind-timer\": 2000, \"lease-database\": { \"type\": \"memfile\", \"name\": \"$LEASE_FILE\", \"persist\": false, \"lfc-interval\": 0 }, \"subnet6\": [ { \"subnet\": \"2001:db8:1::/64\", \"id\": 1, \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ] } ], \"dhcp-ddns\": { \"enable-updates\": true, \"qualifying-suffix\": \"\" }, \"hooks-libraries\": [ { \"library\": \"$HOOK_PATH\", \"parameters\": { \"mode\": \"fail-without-error\" } } ], \"loggers\": [ { \"name\": \"kea-dhcp6\", \"output_options\": [ { \"output\": \"$LOG_FILE\" } ], \"severity\": \"INFO\" } ] } }" # Set the location of the executable. bin="kea-dhcp6" bin_path="@abs_top_builddir@/src/bin/dhcp6" # Import common test library. . "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh" # This test verifies that syntax checking works properly. This function # requires 3 parameters: # test_name # config - string with a content of the config (will be written to a file) # expected_code - expected exit code returned by kea (0 - success, 1 - failure) syntax_check_test() { local test_name="${1}" local config="${2}" local expected_code="${3}" local check_type="${4}" # Log the start of the test and print test name. test_start "${test_name}" # Create correct configuration file. create_config "${config}" # Check it printf "Running command %s.\n" "\"${bin_path}/${bin} ${check_type} ${CFG_FILE}\"" run_command \ "${bin_path}/${bin}" "${check_type}" "${CFG_FILE}" if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}" clean_exit 1 fi test_finish 0 } # This test verifies that DHCPv6 can be reconfigured with a SIGHUP signal. dynamic_reconfiguration_test() { # Log the start of the test and print test name. test_start "dhcpv6_srv.dynamic_reconfiguration" # Create new configuration file. create_config "${CONFIG}" # Instruct Kea to log to the specific file. set_logger # Start Kea. start_kea ${bin_path}/${bin} # Wait up to 20s for Kea to start. wait_for_kea 20 if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then printf "ERROR: timeout waiting for Kea to start.\n" clean_exit 1 fi # Check if it is still running. It could have terminated (e.g. as a result # of configuration failure). get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: expected one Kea process to be started. Found %d processes\ started.\n" "${_GET_PIDS_NUM}" clean_exit 1 fi # Check in the log file, how many times server has been configured. It should # be just once on startup. get_reconfigs if [ "${_GET_RECONFIGS}" -ne 1 ]; then printf "ERROR: server hasn't been configured.\n" clean_exit 1 else printf "Server successfully configured.\n" fi # Now use invalid configuration. create_config "${CONFIG_INVALID}" # Try to reconfigure by sending SIGHUP send_signal 1 ${bin} # The configuration should fail and the error message should be there. wait_for_message 10 "DHCP6_CONFIG_LOAD_FAIL" 1 # After receiving SIGHUP the server should try to reconfigure itself. # The configuration provided is invalid so it should result in # reconfiguration failure but the server should still be running. get_reconfigs if [ "${_GET_RECONFIGS}" -ne 1 ]; then printf "ERROR: server has been reconfigured despite bogus configuration.\n" clean_exit 1 elif [ "${_GET_RECONFIG_ERRORS}" -ne 1 ]; then printf "ERROR: server did not report reconfiguration error despite attempt\ to configure it with invalid configuration.\n" clean_exit 1 fi # Make sure the server is still operational. get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: Kea process was killed when attempting reconfiguration.\n" clean_exit 1 fi # Restore the good configuration. create_config "${CONFIG}" # Reconfigure the server with SIGHUP. send_signal 1 ${bin} # There should be two occurrences of the DHCP6_CONFIG_COMPLETE messages. # Wait for it up to 10s. wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2 # After receiving SIGHUP the server should get reconfigured and the # reconfiguration should be noted in the log file. We should now # have two configurations logged in the log file. if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: server hasn't been reconfigured.\n" clean_exit 1 else printf "Server successfully reconfigured.\n" fi # Make sure the server is still operational. get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: Kea process was killed when attempting reconfiguration.\n" clean_exit 1 fi # When the server receives a signal the call to select() function is # interrupted. This should not be logged as an error. get_log_messages "DHCP6_PACKET_RECEIVE_FAIL" assert_eq 0 "${_GET_LOG_MESSAGES}" \ "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \ returned %d." # All ok. Shut down Kea and exit. test_finish 0 } # This test verifies that DHCPv6 server is shut down gracefully when it # receives a SIGINT or SIGTERM signal. shutdown_test() { local test_name="${1}" # Test name local signum="${2}" # Signal number # Log the start of the test and print test name. test_start "${test_name}" # Create new configuration file. create_config "${CONFIG}" # Instruct Kea to log to the specific file. set_logger # Start Kea. start_kea ${bin_path}/${bin} # Wait up to 20s for Kea to start. wait_for_kea 20 if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then printf "ERROR: timeout waiting for Kea to start.\n" clean_exit 1 fi # Check if it is still running. It could have terminated (e.g. as a result # of configuration failure). get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: expected one Kea process to be started. Found %d processes\ started.\n" "${_GET_PIDS_NUM}" clean_exit 1 fi # Check in the log file, how many times server has been configured. It should # be just once on startup. get_reconfigs if [ "${_GET_RECONFIGS}" -ne 1 ]; then printf "ERROR: server hasn't been configured.\n" clean_exit 1 else printf "Server successfully configured.\n" fi # Send signal to Kea (SIGTERM, SIGINT etc.) send_signal "${signum}" "${bin}" # Wait up to 10s for the server's graceful shutdown. The graceful shut down # should be recorded in the log file with the appropriate message. wait_for_message 10 "DHCP6_SHUTDOWN" 1 if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: Server did not record shutdown in the log.\n" clean_exit 1 fi # Make sure the server is down. wait_for_server_down 5 ${bin} assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \ "Expected wait_for_server_down return %d, returned %d" # When the server receives a signal the call to select() function is # interrupted. This should not be logged as an error. get_log_messages "DHCP6_PACKET_RECEIVE_FAIL" assert_eq 0 "${_GET_LOG_MESSAGES}" \ "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \ returned %d." test_finish 0 } # This test verifies that DHCPv6 can be configured to run lease file cleanup # periodically. lfc_timer_test() { # Log the start of the test and print test name. test_start "dhcpv6_srv.lfc_timer_test" # Create a configuration with the LFC enabled, by replacing the section # with the lfc-interval and persist parameters. LFC_CONFIG=$(printf '%s' "${CONFIG}" | sed -e 's/\"lfc-interval\": 0/\"lfc-interval\": 3/g' \ | sed -e 's/\"persist\": false,/\"persist\": true,/g') # Create new configuration file. create_config "${LFC_CONFIG}" # Instruct Kea to log to the specific file. set_logger # Start Kea. start_kea ${bin_path}/${bin} # Wait up to 20s for Kea to start. wait_for_kea 20 if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then printf "ERROR: timeout waiting for Kea to start.\n" clean_exit 1 fi # Check if it is still running. It could have terminated (e.g. as a result # of configuration failure). get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: expected one Kea process to be started. Found %d processes\ started.\n" "${_GET_PIDS_NUM}" clean_exit 1 fi # Check if Kea emits the log message indicating that LFC is started. wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 1 if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: Server did not execute LFC.\n" clean_exit 1 fi # Give it a short time to run. sleep 1 # Modify the interval. LFC_CONFIG=$(printf '%s' "${LFC_CONFIG}" | sed -e 's/\"lfc-interval\": 3/\"lfc-interval\": 4/g') # Create new configuration file. create_config "${LFC_CONFIG}" # Reconfigure the server with SIGHUP. send_signal 1 ${bin} # There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages. # Wait for it up to 10s. wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2 # After receiving SIGHUP the server should get reconfigured and the # reconfiguration should be noted in the log file. We should now # have two configurations logged in the log file. if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: server hasn't been reconfigured.\n" clean_exit 1 else printf "Server successfully reconfigured.\n" fi # Make sure the server is still operational. get_pid ${bin} if [ "${_GET_PIDS_NUM}" -ne 1 ]; then printf "ERROR: Kea process was killed when attempting reconfiguration.\n" clean_exit 1 fi # Wait for the LFC to run the second time. wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 2 if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: Server did not execute LFC.\n" clean_exit 1 fi # Send signal to Kea SIGTERM send_signal 15 ${bin} # Wait up to 10s for the server's graceful shutdown. The graceful shut down # should be recorded in the log file with the appropriate message. wait_for_message 10 "DHCP6_SHUTDOWN" 1 if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then printf "ERROR: Server did not record shutdown in the log.\n" clean_exit 1 fi # Make sure the server is down. wait_for_server_down 5 ${bin} assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \ "Expected wait_for_server_down return %d, returned %d" # All ok. Shut down Kea and exit. test_finish 0 } server_pid_file_test "${CONFIG}" DHCP6_ALREADY_RUNNING dynamic_reconfiguration_test shutdown_test "dhcpv6.sigterm_test" 15 shutdown_test "dhcpv6.sigint_test" 2 version_test "dhcpv6.version" logger_vars_test "dhcpv6.variables" lfc_timer_test syntax_check_test "dhcpv6.syntax_check_success" "${CONFIG}" 0 -t syntax_check_test "dhcpv6.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1 -t syntax_check_test "dhcpv6.syntax_check_bad_values" "${CONFIG_BAD_VALUES}" 1 -t syntax_check_test "dhcpv6.syntax_check_hooks_load_fail" "${INVALID_CONFIG_HOOKS_LOAD}" 1 -T syntax_check_test "dhcpv6.syntax_check_hooks_callout_fail" "${INVALID_CONFIG_HOOKS_CALLOUT_FAIL}" 1 -T password_redact_test "dhcpv6.password_redact_test" "$(kea_dhcp_config 6)" 0