#!/bin/bash set -e set -o pipefail bridge="keabr0" bridge_ip="192.168.127.1/24" subnetcidr="192.168.127.0/24" pool_range="192.168.127.10 - 192.168.127.250" test_domain="example.autopkgtest" server_iface="p1" client_iface="client0" client_ns="clientns" declare -A dhcp4_config resolv_conf_bkp=$(mktemp) kea_password_file="/etc/kea/kea-api-password" # kea-ctrl-agent needs a password file, or else it won't start # this also tests the debconf mechanism debconf-set-selections << eof kea-ctrl-agent kea-ctrl-agent/make_a_choice select configured_random_password eof dpkg-reconfigure kea-ctrl-agent [ -s "${kea_password_file}" ] || { echo "ERROR, debconf-set-selections failed to set a password for kea-ctrl-agent" exit 1 } auth_params="--auth-user kea-api --auth-password $(cat ${kea_password_file})" cleanup() { rc=$? set +e # so we don't exit midcleanup if [ ${rc} -ne 0 ]; then echo "## FAIL" echo echo "## dmesg" dmesg -T | tail -n 500 echo echo "## kea logs" journalctl -u kea-dhcp4-server.service fi echo echo "## Cleaning up" ip link set "${server_iface}" down ip link del "${server_iface}" ip link set "${bridge}" down brctl delbr "${bridge}" ip netns delete "${client_ns}" sed -r -i "/example.autopkgtest/d" /etc/hosts if [ -s "${resolv_conf_bkp}" ]; then cat "${resolv_conf_bkp}" > /etc/resolv.conf fi rm -f "${resolv_conf_bkp}" # restore it for when we are called from the main script, and not the trap set -e } trap cleanup EXIT run_on_client() { ip netns exec "${client_ns}" "$@" } setup() { cleanup 2>/dev/null # so we don't have to worry about it being a symlink cat /etc/resolv.conf > "${resolv_conf_bkp}" echo "127.0.1.1 $(hostname).${test_domain} $(hostname)" >> /etc/hosts ip netns add "${client_ns}" ip link add "${server_iface}" type veth peer "${client_iface}" netns "${client_ns}" brctl addbr "${bridge}" brctl addif "${bridge}" "${server_iface}" ip link set "${server_iface}" up ip link set "${bridge}" up ip addr add "${bridge_ip}" dev "${bridge}" } render_dhcp4_conf() { local -n config="${1}" local -r service="dhcp4" template="debian/tests/kea-${service}.conf.template" [ -f "${template}" ] || return 1 output="/etc/kea/kea-${service}.conf" cat "${template}" | sed -r \ -e "s,@interface@,${config[interface]}," \ -e "s,@dnsip@,${config[dnsip]}," \ -e "s,@domain@,${config[domain]}," \ -e "s/@domainsearch@/${config[domainsearch]}/" \ -e "s,@router@,${config[router]}," \ -e "s,@subnetcidr@,${config[subnetcidr]}," \ -e "s,@poolrange@,${config[poolrange]}," \ -e "s,@multiarch@,$(dpkg-architecture -qDEB_HOST_MULTIARCH)," \ > "${output}" } json_get_length() { echo "${1}" | jq '. | length' } kea_get_leases_by_mac() { local mac="${1}" echo "\"hw-address\": \"${mac}\"" | kea-shell ${auth_params} --service dhcp4 lease4-get-by-hw-address } get_result_from_lease() { echo "${1}" | jq -r '.[0].result' } get_number_of_leases() { echo "${1}" | jq '.[0].arguments.leases | length' } get_ip_from_lease() { echo "${1}" | jq -r '.[0]["arguments"]["leases"][0]["ip-address"]' } get_mac_from_lease() { echo "${1}" | jq -r '.[0]["arguments"]["leases"][0]["hw-address"]' } get_valid_lifetime_from_lease() { echo "${1}" | jq -r '.[0]["arguments"]["leases"][0]["valid-lft"]' } check_leases() { local data="${1}" local if_mac="${2}" local if_ip="${3}" local res res=$(json_get_length "${data}") if [ ${res} != 1 ]; then echo "## ERROR" echo "## Expected 1 result, got ${res}:" return 1 fi res=$(get_result_from_lease "${data}") if [ ${res} != 0 ]; then echo "## ERROR" echo "## Failed to obtain leases from server, code ${res}" return 1 fi res=$(get_number_of_leases "${data}") if [ ${res} -ne 1 ]; then echo "## ERROR" echo "## Expected 1 lease, got ${res}:" return 1 fi res=$(get_ip_from_lease "${data}") if [ "${if_ip}" != "${res}" ]; then echo "## ERROR" echo "## IP from lease (${res}) does not match IP from interface: ${if_ip}" run_on_client ip a show return 1 fi res=$(get_mac_from_lease "${data}") if [ "${if_mac}" != "${res}" ]; then echo "## ERROR" echo "## MAC from lease (${res}) does not match MAC from client interface: ${if_mac}" run_on_client ip l show return 1 fi } setup dhcp4_config["interface"]="${bridge}" # get rid of the CIDR part at the end dhcp4_config["dnsip"]="${bridge_ip%%/*}" dhcp4_config["domain"]="${test_domain}" dhcp4_config["domainsearch"]="${test_domain}" # get rid of the CIDR part at the end dhcp4_config["router"]="${bridge_ip%%/*}" dhcp4_config["subnetcidr"]="${subnetcidr}" dhcp4_config["poolrange"]="${pool_range}" echo echo "## Configuring kea-dhcp4 and restarting the service" render_dhcp4_conf dhcp4_config systemctl restart kea-dhcp4-server.service sleep 2s echo echo "## Obtaining IP via dhclient" run_on_client timeout -v 60s dhclient -v "${client_iface}" echo "## OK" ip=$(run_on_client ip -4 -o addr show dev "${client_iface}" | awk '{print $4}') ip=${ip%%/*} # remove the CIDR part mac=$(run_on_client ip -4 link show dev "${client_iface}" | grep "link/ether" | awk '{print $2}') echo echo "## Got ip=${ip}" echo echo "## Checking leases that match client's ethernet address ${mac}" # this will break if/when we close LP: #2007312 leases=$(kea_get_leases_by_mac "${mac}") echo "## Leases:" echo "${leases}" | jq . check_leases "${leases}" "${mac}" "${ip}" echo "## OK" echo echo "## INFO: Networking in the ${client_ns} namespace:" echo echo "## Interfaces" run_on_client ip a echo echo "## Routes" run_on_client ip route echo echo "## DNS" if command -v resolvectl > /dev/null 2>&1; then run_on_client resolvectl status else echo "## Skipping DNS info (no resolvectl installed)" fi echo echo "## Checking that the DNS domain \"${test_domain}\" was added to resolv.conf" if grep -E "^search[[:blank:]]" /etc/resolv.conf | grep -q -w -F "${test_domain}"; then echo "## OK" else echo "## ERROR" echo "## /etc/resolv.conf does not contain ${test_domain}" cat /etc/resolv.conf exit 1 fi echo echo "## Releasing IP via dhclient -r" run_on_client timeout -v 60s dhclient -v -r echo "## OK" echo # As per entry 2072 in # https://downloads.isc.org/isc/kea/2.4.0/Kea-2.4.0-ReleaseNotes.txt, starting # from kea 2.3.2, a lease is no longer deleted from the lease database after a # release request. Instead, it is expired to enable lease affinity. It is kept # for `hold-reclaimed-time` seconds. Its default value is 3600 seconds. # https://kea.readthedocs.io/en/kea-2.4.0/arm/lease-expiration.html echo "## Checking that the lease was expired" leases=$(kea_get_leases_by_mac "${mac}") echo "${leases}" | jq . n_results=$(json_get_length "${leases}") if [ ${n_results} -ne 1 ]; then echo "## ERROR, expected 1 result, got ${n_results}" echo "${leases}" | jq . exit 1 fi n_leases=$(get_number_of_leases "${leases}") if [ ${n_leases} -ne 1 ]; then echo "## ERROR" echo "## Expected 1 lease, got ${n_leases}:" echo "${leases}" | jq . exit 1 fi lft=$(get_valid_lifetime_from_lease "${leases}") if [ ${lft} -gt 0 ]; then echo "## ERROR" echo "## Expected expired lease lifetime (0), got ${lft}" echo "${leases}" | jq . exit 1 fi echo "## OK"