linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

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"