1#!/bin/sh
2
3# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0. If a copy of the MPL was not distributed with this
7# file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9# shellcheck disable=SC1091
10# SC1091: Not following: ... was not specified as input (see shellcheck -x).
11
12# shellcheck disable=SC2039
13# SC2039: In POSIX sh, 'local' is undefined.
14
15# Exit with error if commands exit with non-zero and if undefined variables are
16# used.
17set -eu
18
19# Path to the temporary configuration file.
20CFG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_config.json"
21# Path to the Kea log file.
22LOG_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test.log"
23# Path to the Kea lease file.
24LEASE_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/test_leases.csv"
25# Path to the Kea LFC application
26export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc"
27# Kea configuration to be stored in the configuration file.
28CONFIG="{
29    \"Dhcp6\":
30    {   \"interfaces-config\": {
31          \"interfaces\": [ ]
32        },
33        \"server-id\": {
34          \"type\": \"LLT\",
35          \"persist\": false
36        },
37        \"preferred-lifetime\": 3000,
38        \"valid-lifetime\": 4000,
39        \"renew-timer\": 1000,
40        \"rebind-timer\": 2000,
41        \"lease-database\":
42        {
43            \"type\": \"memfile\",
44            \"name\": \"$LEASE_FILE\",
45            \"persist\": false,
46            \"lfc-interval\": 0
47        },
48        \"subnet6\": [
49        {
50            \"subnet\": \"2001:db8:1::/64\",
51            \"pools\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
52        } ],
53        \"dhcp-ddns\": {
54            \"enable-updates\": true,
55            \"qualifying-suffix\": \"\"
56        },
57        \"loggers\": [
58        {
59            \"name\": \"kea-dhcp6\",
60            \"output_options\": [
61                {
62                    \"output\": \"$LOG_FILE\"
63                }
64            ],
65            \"severity\": \"INFO\"
66        }
67        ]
68    }
69}"
70# Invalid configuration (syntax error) to check that Kea can check syntax.
71# This config has following errors:
72# - it should be interfaces-config/interfaces, not interfaces
73# - it should be subnet6/pools, no subnet6/pool
74CONFIG_BAD_SYNTAX="{
75    \"Dhcp6\":
76    {
77        \"interfaces\": [ ],
78        \"preferred-lifetime\": 3000,
79        \"valid-lifetime\": 4000,
80        \"renew-timer\": 1000,
81        \"rebind-timer\": 2000,
82        \"lease-database\":
83        {
84            \"type\": \"memfile\",
85            \"persist\": false
86        },
87        \"subnet6\": [
88        {
89            \"subnet\": \"2001:db8:1::/64\",
90            \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
91        } ],
92        \"loggers\": [
93        {
94            \"name\": \"kea-dhcp6\",
95            \"output_options\": [
96                {
97                    \"output\": \"$LOG_FILE\"
98                }
99            ],
100            \"severity\": \"INFO\"
101        }
102        ]
103    }
104}"
105# Invalid configuration (negative preferred-lifetime) to check that Kea
106# gracefully handles reconfiguration errors.
107CONFIG_INVALID="{
108    \"Dhcp6\":
109    {
110        \"interfaces-config\": {
111          \"interfaces\": [ ]
112        },
113        \"preferred-lifetime\": -3,
114        \"valid-lifetime\": 4000,
115        \"renew-timer\": 1000,
116        \"rebind-timer\": 2000,
117        \"lease-database\":
118        {
119            \"type\": \"memfile\",
120            \"persist\": false
121        },
122        \"subnet6\": [
123        {
124            \"subnet\": \"2001:db8:1::/64\",
125            \"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
126        } ],
127        \"loggers\": [
128        {
129            \"name\": \"kea-dhcp6\",
130            \"output_options\": [
131                {
132                    \"output\": \"$LOG_FILE\"
133                }
134            ],
135            \"severity\": \"INFO\"
136        }
137        ]
138    }
139}"
140
141# This config has bad pool values. The pool it out of scope for the subnet
142# it is defined in. Syntactically the config is correct, though.
143CONFIG_BAD_VALUES="{
144    \"Dhcp6\":
145    {   \"interfaces-config\": {
146          \"interfaces\": [ ]
147        },
148        \"server-id\": {
149          \"type\": \"LLT\",
150          \"persist\": false
151        },
152        \"preferred-lifetime\": 3000,
153        \"valid-lifetime\": 4000,
154        \"renew-timer\": 1000,
155        \"rebind-timer\": 2000,
156        \"lease-database\":
157        {
158            \"type\": \"memfile\",
159            \"name\": \"$LEASE_FILE\",
160            \"persist\": false,
161            \"lfc-interval\": 0
162        },
163        \"subnet6\": [
164        {
165            \"subnet\": \"2001:db8::/64\",
166            \"pools\": [ { \"pool\": \"3000::-3000::ffff\" } ]
167        } ],
168        \"dhcp-ddns\": {
169            \"enable-updates\": true,
170            \"qualifying-suffix\": \"\"
171        }
172    }
173}"
174
175
176# Set the location of the executable.
177bin="kea-dhcp6"
178bin_path="@abs_top_builddir@/src/bin/dhcp6"
179
180# Import common test library.
181. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
182
183# This test verifies that syntax checking works properly. This function
184# requires 3 parameters:
185# test_name
186# config - string with a content of the config (will be written to a file)
187# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
188syntax_check_test() {
189    local test_name="${1}"
190    local config="${2}"
191    local expected_code="${3}"
192
193    # Log the start of the test and print test name.
194    test_start "${test_name}"
195    # Create correct configuration file.
196    create_config "${config}"
197    # Check it
198    printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
199    run_command \
200        "${bin_path}/${bin}" -t "${CFG_FILE}"
201    if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
202        printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
203        clean_exit 1
204    fi
205
206    test_finish 0
207}
208
209# This test verifies that DHCPv6 can be reconfigured with a SIGHUP signal.
210dynamic_reconfiguration_test() {
211    # Log the start of the test and print test name.
212    test_start "dhcpv6_srv.dynamic_reconfiguration"
213    # Create new configuration file.
214    create_config "${CONFIG}"
215    # Instruct Kea to log to the specific file.
216    set_logger
217    # Start Kea.
218    start_kea ${bin_path}/${bin}
219    # Wait up to 20s for Kea to start.
220    wait_for_kea 20
221    if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
222        printf "ERROR: timeout waiting for Kea to start.\n"
223        clean_exit 1
224    fi
225
226    # Check if it is still running. It could have terminated (e.g. as a result
227    # of configuration failure).
228    get_pid ${bin}
229    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
230        printf "ERROR: expected one Kea process to be started. Found %d processes\
231 started.\n" "${_GET_PIDS_NUM}"
232        clean_exit 1
233    fi
234
235    # Check in the log file, how many times server has been configured. It should
236    # be just once on startup.
237    get_reconfigs
238    if [ "${_GET_RECONFIGS}" -ne 1 ]; then
239        printf "ERROR: server hasn't been configured.\n"
240        clean_exit 1
241    else
242        printf "Server successfully configured.\n"
243    fi
244
245    # Now use invalid configuration.
246    create_config "${CONFIG_INVALID}"
247
248    # Try to reconfigure by sending SIGHUP
249    send_signal 1 ${bin}
250
251    # The configuration should fail and the error message should be there.
252    wait_for_message 10 "DHCP6_CONFIG_LOAD_FAIL" 1
253
254    # After receiving SIGHUP the server should try to reconfigure itself.
255    # The configuration provided is invalid so it should result in
256    # reconfiguration failure but the server should still be running.
257    get_reconfigs
258    if [ "${_GET_RECONFIGS}" -ne 1 ]; then
259        printf "ERROR: server has been reconfigured despite bogus configuration.\n"
260        clean_exit 1
261    elif [ "${_GET_RECONFIG_ERRORS}" -ne 1 ]; then
262        printf "ERROR: server did not report reconfiguration error despite attempt\
263 to configure it with invalid configuration.\n"
264        clean_exit 1
265    fi
266
267    # Make sure the server is still operational.
268    get_pid ${bin}
269    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
270        printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
271        clean_exit 1
272    fi
273
274    # Restore the good configuration.
275    create_config "${CONFIG}"
276
277    # Reconfigure the server with SIGHUP.
278    send_signal 1 ${bin}
279
280    # There should be two occurrences of the DHCP6_CONFIG_COMPLETE messages.
281    # Wait for it up to 10s.
282    wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
283
284    # After receiving SIGHUP the server should get reconfigured and the
285    # reconfiguration should be noted in the log file. We should now
286    # have two configurations logged in the log file.
287    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
288        printf "ERROR: server hasn't been reconfigured.\n"
289        clean_exit 1
290    else
291        printf "Server successfully reconfigured.\n"
292    fi
293
294    # Make sure the server is still operational.
295    get_pid ${bin}
296    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
297        printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
298        clean_exit 1
299    fi
300
301    # When the server receives a signal the call to select() function is
302    # interrupted. This should not be logged as an error.
303    get_log_messages "DHCP6_PACKET_RECEIVE_FAIL"
304    assert_eq 0 "${_GET_LOG_MESSAGES}" \
305        "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \
306returned %d."
307
308    # All ok. Shut down Kea and exit.
309    test_finish 0
310}
311
312# This test verifies that DHCPv6 server is shut down gracefully when it
313# receives a SIGINT or SIGTERM signal.
314shutdown_test() {
315    local test_name="${1}"  # Test name
316    local signum="${2}"     # Signal number
317
318    # Log the start of the test and print test name.
319    test_start "${test_name}"
320    # Create new configuration file.
321    create_config "${CONFIG}"
322    # Instruct Kea to log to the specific file.
323    set_logger
324    # Start Kea.
325    start_kea ${bin_path}/${bin}
326    # Wait up to 20s for Kea to start.
327    wait_for_kea 20
328    if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
329        printf "ERROR: timeout waiting for Kea to start.\n"
330        clean_exit 1
331    fi
332
333    # Check if it is still running. It could have terminated (e.g. as a result
334    # of configuration failure).
335    get_pid ${bin}
336    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
337        printf "ERROR: expected one Kea process to be started. Found %d processes\
338 started.\n" "${_GET_PIDS_NUM}"
339        clean_exit 1
340    fi
341
342    # Check in the log file, how many times server has been configured. It should
343    # be just once on startup.
344    get_reconfigs
345    if [ "${_GET_RECONFIGS}" -ne 1 ]; then
346        printf "ERROR: server hasn't been configured.\n"
347        clean_exit 1
348    else
349        printf "Server successfully configured.\n"
350    fi
351
352    # Send signal to Kea (SIGTERM, SIGINT etc.)
353    send_signal "${signum}" "${bin}"
354
355    # Wait up to 10s for the server's graceful shutdown. The graceful shut down
356    # should be recorded in the log file with the appropriate message.
357    wait_for_message 10 "DHCP6_SHUTDOWN" 1
358    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
359        printf "ERROR: Server did not record shutdown in the log.\n"
360        clean_exit 1
361    fi
362
363    # Make sure the server is down.
364    wait_for_server_down 5 ${bin}
365    assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
366        "Expected wait_for_server_down return %d, returned %d"
367
368    # When the server receives a signal the call to select() function is
369    # interrupted. This should not be logged as an error.
370    get_log_messages "DHCP6_PACKET_RECEIVE_FAIL"
371    assert_eq 0 "${_GET_LOG_MESSAGES}" \
372        "Expected get_log_messages DHCP6_PACKET_RECEIVE_FAIL return %d, \
373returned %d."
374
375    test_finish 0
376}
377
378# This test verifies that DHCPv6 can be configured to run lease file cleanup
379# periodically.
380lfc_timer_test() {
381    # Log the start of the test and print test name.
382    test_start "dhcpv6_srv.lfc_timer_test"
383    # Create a configuration with the LFC enabled, by replacing the section
384    # with the lfc-interval and persist parameters.
385    LFC_CONFIG=$(printf '%s' "${CONFIG}" | sed -e 's/\"lfc-interval\": 0/\"lfc-interval\": 3/g' \
386                        | sed -e 's/\"persist\": false,/\"persist\": true,/g')
387    # Create new configuration file.
388    create_config "${LFC_CONFIG}"
389    # Instruct Kea to log to the specific file.
390    set_logger
391    # Start Kea.
392    start_kea ${bin_path}/${bin}
393    # Wait up to 20s for Kea to start.
394    wait_for_kea 20
395    if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
396        printf "ERROR: timeout waiting for Kea to start.\n"
397        clean_exit 1
398    fi
399
400    # Check if it is still running. It could have terminated (e.g. as a result
401    # of configuration failure).
402    get_pid ${bin}
403    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
404        printf "ERROR: expected one Kea process to be started. Found %d processes\
405 started.\n" "${_GET_PIDS_NUM}"
406        clean_exit 1
407    fi
408
409    # Check if Kea emits the log message indicating that LFC is started.
410    wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 1
411    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
412        printf "ERROR: Server did not execute LFC.\n"
413        clean_exit 1
414    fi
415
416    # Give it a short time to run.
417    sleep 1
418
419    # Modify the interval.
420    LFC_CONFIG=$(printf '%s' "${LFC_CONFIG}" | sed -e 's/\"lfc-interval\": 3/\"lfc-interval\": 4/g')
421    # Create new configuration file.
422    create_config "${LFC_CONFIG}"
423
424    # Reconfigure the server with SIGHUP.
425    send_signal 1 ${bin}
426
427    # There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages.
428    # Wait for it up to 10s.
429    wait_for_message 10 "DHCP6_CONFIG_COMPLETE" 2
430
431    # After receiving SIGHUP the server should get reconfigured and the
432    # reconfiguration should be noted in the log file. We should now
433    # have two configurations logged in the log file.
434    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
435        printf "ERROR: server hasn't been reconfigured.\n"
436        clean_exit 1
437    else
438        printf "Server successfully reconfigured.\n"
439    fi
440
441    # Make sure the server is still operational.
442    get_pid ${bin}
443    if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
444        printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
445        clean_exit 1
446    fi
447
448    # Wait for the LFC to run the second time.
449    wait_for_message 10 "DHCPSRV_MEMFILE_LFC_EXECUTE" 2
450    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
451        printf "ERROR: Server did not execute LFC.\n"
452        clean_exit 1
453    fi
454
455    # Send signal to Kea SIGTERM
456    send_signal 15 ${bin}
457
458    # Wait up to 10s for the server's graceful shutdown. The graceful shut down
459    # should be recorded in the log file with the appropriate message.
460    wait_for_message 10 "DHCP6_SHUTDOWN" 1
461    if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
462        printf "ERROR: Server did not record shutdown in the log.\n"
463        clean_exit 1
464    fi
465
466    # Make sure the server is down.
467    wait_for_server_down 5 ${bin}
468    assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
469        "Expected wait_for_server_down return %d, returned %d"
470
471    # All ok. Shut down Kea and exit.
472    test_finish 0
473}
474
475server_pid_file_test "${CONFIG}" DHCP6_ALREADY_RUNNING
476dynamic_reconfiguration_test
477shutdown_test "dhcpv6.sigterm_test" 15
478shutdown_test "dhcpv6.sigint_test" 2
479version_test "dhcpv6.version"
480logger_vars_test "dhcpv6.variables"
481lfc_timer_test
482syntax_check_test "dhcpv6.syntax_check_success" "${CONFIG}" 0
483syntax_check_test "dhcpv6.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
484syntax_check_test "dhcpv6.syntax_check_bad_values" "${CONFIG_BAD_VALUES}" 1
485password_redact_test "dhcpv6.password_redact_test" "$(kea_dhcp_config 6)" 0
486