test_dhcp.sh (4180B)
1 #!/bin/sh 2 # `method dhcp`: command rendering, and the drop-in config fallback. 3 # 4 # PATH is pinned to an empty directory so nothing can reach a real dhcp client 5 # on the developer's host. 6 set -eu 7 HERE=$(cd "$(dirname "$0")" && pwd) 8 ROOT=$(cd "${HERE}/../.." && pwd) 9 . "${HERE}/../helpers.sh" 10 11 echo "==> test_dhcp: method dhcp" 12 13 BUILD_DIR=$(ensure_built "${ROOT}") 14 BIN="${BUILD_DIR}/linkd" 15 FIXTURE="${ROOT}/tests/fixtures/dhcp-fake.sh" 16 assert_path_exists "${FIXTURE}" "fake dhcp client fixture present" 17 18 T=$(mktemp -d) 19 trap 'rm -rf "${T}"' EXIT 20 mkdir -p "${T}/net" "${T}/emptybin" 21 22 # `auto` or nothing is applied. swp-test does not exist, so the link-up fails 23 # and is logged; dhcp_start still runs, which is what is under test. 24 cat > "${T}/net/interfaces" <<EOF 25 auto swp-test 26 iface swp-test inet dhcp 27 28 auto swp-static 29 iface swp-static inet static 30 address 10.9.9.9/24 31 EOF 32 33 # An empty netns, because reaping is driven by SIGCHLD waking poll(2): on a 34 # host with ambient netlink traffic the loop spins anyway and a broken reap 35 # still passes. 3s for the same reason -- the wake is causal, not timed. 36 if unshare --user --map-root-user --net true 2>/dev/null; then 37 in_ns() { unshare --user --map-root-user --net "$@"; } 38 else 39 in_ns() { "$@"; } 40 fi 41 42 run_linkd() { 43 in_ns timeout 3 env PATH="${T}/emptybin" DHCP_LOG="${T}/dhcp.log" \ 44 "${BIN}" --config "$1" --resync-interval 0 \ 45 >"${T}/linkd.log" 2>&1 || true 46 cat "${T}/linkd.log" 47 } 48 49 # --- rendering -------------------------------------------------------------- 50 cat > "${T}/render.cnf" <<EOF 51 config_iface ${T}/net/interfaces 52 config_ports /nonexistent 53 listen unix://${T}/linkd.sock 54 dhcp_client ${FIXTURE} --dev {{interface}} --pair {{interface}}:{{interface}} --literal plain 55 EOF 56 57 : > "${T}/dhcp.log" 58 run_linkd "${T}/render.cnf" >/dev/null 59 60 assert_file_contains "${T}/dhcp.log" "--dev swp-test" \ 61 "{{interface}} renders the interface name" 62 assert_file_contains "${T}/dhcp.log" "--pair swp-test:swp-test" \ 63 "every occurrence in one argument is substituted" 64 assert_file_contains "${T}/dhcp.log" "--literal plain" \ 65 "non-template arguments pass through" 66 67 n=$(wc -l < "${T}/dhcp.log" | tr -d ' ') 68 assert_eq "${n}" "1" "only the dhcp interface spawns a client" 69 70 out=$(cat "${T}/linkd.log") 71 assert_contains "${out}" "lease will not be renewed" \ 72 "a client that exits is reaped and reported" 73 74 # --- nothing configured ----------------------------------------------------- 75 cat > "${T}/noclient.cnf" <<EOF 76 config_iface ${T}/net/interfaces 77 config_ports /nonexistent 78 listen unix://${T}/linkd.sock 79 EOF 80 81 out=$(run_linkd "${T}/noclient.cnf") 82 assert_contains "${out}" "no dhcp_client configured" \ 83 "an unconfigured dhcp interface is an error, not a silent no-op" 84 85 # --- drop-in supplies the client -------------------------------------------- 86 # Real fallback path, with /etc bind-mounted in a user namespace so the 87 # hardcoded /etc/linkd is exercised rather than a stand-in. No --config: the 88 # fallback only fires when the path was not given explicitly. 89 mkdir -p "${T}/etc/linkd" 90 cat > "${T}/etc/linkd/udhcpc.cnf" <<EOF 91 config_iface ${T}/net/interfaces 92 config_ports /nonexistent 93 listen unix://${T}/linkd.sock 94 dhcp_client ${FIXTURE} --from-dropin {{interface}} 95 EOF 96 97 if unshare --user --map-root-user --mount true 2>/dev/null; then 98 : > "${T}/dhcp.log" 99 unshare --user --map-root-user --mount sh -c " 100 mount --bind '${T}/etc' /etc || exit 1 101 timeout 15 env PATH='${T}/emptybin' DHCP_LOG='${T}/dhcp.log' \ 102 '${BIN}' --resync-interval 0 103 " >"${T}/linkd.log" 2>&1 || true 104 105 assert_file_contains "${T}/dhcp.log" "--from-dropin swp-test" \ 106 "/etc/linkd/*.cnf is read when /etc/linkd.cnf is absent" 107 else 108 echo "SKIP: user namespaces unavailable, cannot bind-mount /etc" 109 fi 110 111 # --- unresolvable command --------------------------------------------------- 112 cat > "${T}/missing.cnf" <<EOF 113 config_iface ${T}/net/interfaces 114 config_ports /nonexistent 115 listen unix://${T}/linkd.sock 116 dhcp_client ${T}/emptybin/definitely-not-here {{interface}} 117 EOF 118 119 out=$(run_linkd "${T}/missing.cnf") 120 assert_contains "${out}" "not found or not executable" \ 121 "a configured client that does not exist fails loudly" 122 123 echo "==> test_dhcp done"