summaryrefslogtreecommitdiffstats
path: root/debian/tests/kea-dhcp4
blob: 66ce9277cf5a94888462a4776f5cf843163c7d72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/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"