. "$BST_LIB" # BST-TIMEOUT: 600 # keepalived 2.2.8 (meta-networking recipes-daemons/keepalived) -- Alexandre # Cassen's VRRP failover and health-check daemon. What ships is /usr/sbin/keepalived, # the /usr/bin/genhash hash tool its HTTP_GET checkers use, the bundled # /etc/keepalived/keepalived.conf this port adds, and upstream's sample tree under # /etc/keepalived/samples. # # THE SUBJECT IS A CONFIGURATION LANGUAGE, AND ON THIS IMAGE IT IS ONLY THAT. # The criterion this port is taken on is that `keepalived -t` accepts the bundled # configuration and refuses a broken one non-zero. The VRRP state machine is NOT # part of the criterion here, and the two paragraphs below say why -- that is a # fact about the image, established by measurement, not a scope this suite chose. # # KEEPALIVED REFUSES A LOOPBACK INTERFACE FOR VRRP, AND `lo` IS THE ONLY ONE THIS # IMAGE HAS. Fed `interface lo`, keepalived says so in as many words -- "(VI_1) # cannot use a loopback interface (lo) for vrrp - ignoring", then "(VI_1) Unknown # interface!" -- and `-t` exits 5. That is upstream's own check, not a platform # gap, and it applies to every configuration this image could write, because the # net stack here is loopback only (the same defconfig fact 88-dhcpcd records from # the DHCP side: "no veth, no dummy, nothing to put a second interface on"). So # the bundled configuration carries global_defs and NO vrrp_instance: a shipped # config with one would be a file the daemon rejects on every start. # # `-t` IS NOT PARSER-ONLY, WHICH IS WHY THE CASES BELOW READ ITS LOG AND NOT ONLY # ITS EXIT CODE. An earlier revision of this suite asserted that -t "opens no # socket, so this is a statement about the port and never about the kernel". That # is wrong for 2.2.8: -t runs validate_config() -> vrrp_validate_config() -> # start_vrrp(), the same init the daemon runs, minus kernel_netlink_init(). Its # exit code is not a boolean either -- config_test_exit() maps a config that # merely warns onto KEEPALIVED_CHK_EXIT_CONFIG_TEST (5) and one keepalived cannot # run onto KEEPALIVED_CHK_EXIT_CONFIG (4), so a bare "non-zero" hides which. # # THE STATE MACHINE IS NOT EXERCISED AND IS NOT SILENTLY DROPPED. Two independent # things withhold it, either of which alone would be enough: there is no legal # interface to put a vrrp_instance on (above), and epoll_create/epoll_create1 are # ENOSYS on this kernel (brintos/linux#53), so keepalived's scheduler cannot # start at all -- on the 2026-08-25 batch image it said "scheduler: Error creating # EPOLL instance (Function not implemented)", then "BUG - signal_fd update failed # - 38", and then trapped the runtime with a wasm memory-access-out-of-bounds. # The cases are reported as attributed skips rather than reds, in the shape # 81-libubox uses for the same #53. Restore them the day either arm clears. # # THE IPVS ARM IS NOT EXERCISED AND IS NOT SILENTLY DROPPED EITHER. keepalived's # load-balancing half programs IPVS through the kernel's ip_vs module; this # kernel is built with no CONFIG_NETFILTER at all (the same absence # 79-libnetfilter-queue attributes to brintos/linux#86), so ip_vs cannot be # there to program. What is asserted instead is that the binary was BUILT with # the LVS half compiled in, read off its own banner -- so the day the kernel # gains it, nothing here has to be rebuilt to use it. # # NOTHING IS KILLED. kill(2) on this image reports success and delivers no # signal, SIGKILL included (brintos/linux#21), so a daemon this suite started # would outlive it. This revision starts none, which sidesteps that as well as # the trap above; the red for kill(2) is already carried where it was found # (88-dnsmasq/sigterm-stops) and a second copy would add to the batch's fail # count without adding a finding. require keepalived keepalived/present KA=/usr/sbin/keepalived CONF=/etc/keepalived/keepalived.conf D=${TMPDIR:-/tmp}/bst-keepalived.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok keepalived/workdir "cannot create $D"; exit 0; } # has ... : true iff EVERY needle is a substring. has() { _h=$1; shift for _n in "$@"; do case "$_h" in *"$_n"*) ;; *) return 1 ;; esac; done return 0 } # ---------------------------------------------------------------- inventory -- # The daemon is in sbindir and genhash in bindir; the recipe splits the sample # tree into keepalived-samples, so a name missing here means an image list picked # a subset, not that the build ran short. check keepalived/inventory-keepalived test -x /usr/sbin/keepalived check keepalived/inventory-genhash test -x /usr/bin/genhash # keepalived installs to sbindir. Asserting where the bare name resolves catches # a packaging change that leaves it off the PATH the runner actually has -- which # would otherwise surface as a skip and read like an absent package. check_eq keepalived/resolves-to-sbin "$KA" "$(command -v keepalived 2>/dev/null)" # The bundled configuration this port adds, and the criterion's first half is # about exactly this file. A red here makes every -t case below meaningless. check keepalived/conf-bundled test -s "$CONF" # Upstream's own sample tree, from keepalived-samples. check keepalived/conf-sample test -s /etc/keepalived/keepalived.conf.sample check keepalived/samples-dir test -d /etc/keepalived/samples # ----------------------------------------------------------------- identity -- # -v pins the series and, on keepalived, also prints what the build enabled. # 2.2 and 2.3 differ in the configuration keywords this suite writes below, so a # major bump would leave the parser cases asserting against a language this port # does not ship. ver=$("$KA" -v 2>&1); vrc=$? check_eq keepalived/version-rc "0" "$vrc" if has "$ver" "Keepalived v2.2.8"; then bst_ok keepalived/version-is-2.2.8 else bst_notok keepalived/version-is-2.2.8 "want 'Keepalived v2.2.8', got '$(_bst_1line "$ver")'"; fi # The two halves of keepalived, read off the binary rather than off the recipe. # VRRP is what the state-machine cases exercise; LVS is the IPVS half this # kernel cannot lend a module to, asserted as BUILT so the day it can, nothing # has to be rebuilt. if has "$ver" "VRRP"; then bst_ok keepalived/built-with-vrrp else bst_notok keepalived/built-with-vrrp "no VRRP in the -v banner -- $(_bst_1line "$ver")"; fi if has "$ver" "LVS"; then bst_ok keepalived/built-with-lvs else bst_notok keepalived/built-with-lvs "no LVS in the -v banner -- $(_bst_1line "$ver")"; fi # The two PACKAGECONFIG defaults the recipe leaves on, both of which pull a # ported dependency of their own (libnl, net-snmp). Read back from the banner's # own `configure options:` line, so a PACKAGECONFIG that silently lost one shows # up here and not as a mystery when a later port depends on it. if has "$ver" "--enable-libnl"; then bst_ok keepalived/configured-libnl else bst_notok keepalived/configured-libnl "no --enable-libnl in the -v banner"; fi if has "$ver" "--enable-snmp"; then bst_ok keepalived/configured-snmp else bst_notok keepalived/configured-snmp "no --enable-snmp in the -v banner"; fi # ------------------------------------------- THE BUNDLED CONFIG IS ACCEPTED -- # THE FIRST HALF OF THE TICKET'S PASS CRITERION. -t parses the whole file, # validates every block against the keyword table and the value ranges, and # exits 0 when it is satisfied. ( "$KA" -t --log-console -f "$CONF" > "$D/t-bundled.log" 2>&1 ); trc=$? check_eq keepalived/confcheck-bundled-rc "0" "$trc" # ...and it did not merely exit 0 while saying something was wrong. The needle is # widened from the old "Configuration file contains errors" because keepalived # reports per-line faults in its own words -- see confcheck-refusal-is-spoken. if [ -s "$D/t-bundled.log" ]; then bst_notok keepalived/confcheck-bundled-clean \ "-t exited $trc but wrote diagnostics -- $(_bst_1line "$(head -3 "$D/t-bundled.log")")" else bst_ok keepalived/confcheck-bundled-clean fi # THE REASON THE BUNDLED FILE CARRIES NO vrrp_instance, asserted rather than # asserted-about-in-a-comment. keepalived refuses a loopback interface for VRRP, # and `lo` is the only interface this image has, so this is what a shipped # vrrp_instance would do on every start. It is a regression case for the port's # packaging decision: the day it stops refusing, the bundled file can gain one. cat > "$D/onlo.conf" <<'EOF' vrrp_instance VI_ONLO { state MASTER interface lo virtual_router_id 51 priority 255 advert_int 1 } EOF ( "$KA" -t --log-console -f "$D/onlo.conf" > "$D/t-onlo.log" 2>&1 ); lrc=$? if [ "$lrc" -ne 0 ] && has "$(cat "$D/t-onlo.log")" "loopback interface"; then bst_ok keepalived/confcheck-loopback-refused else bst_notok keepalived/confcheck-loopback-refused \ "want a non-zero exit naming the loopback refusal, got rc=$lrc -- $(_bst_1line "$(head -3 "$D/t-onlo.log")")" fi # ------------------------------------------------- UPSTREAM'S OWN SAMPLE ----- # The sample is a second, much larger consumer of the keyword table. It cannot # be ACCEPTED on this image and the reasons are not this port's: it puts its # vrrp_instance on eth0, which the loopback-only net stack does not have # ("WARNING - interface eth0 for vrrp_instance VI_1 doesn't exist", then # "Non-existent interface specified in configuration"), and upstream ships it # with vrrp_garp_interval 0 / vrrp_gna_interval 0, which keepalived's own # validator then rejects as "outside range [0.000001, 4294.967295]". So the rc # is an attributed skip, and what is asserted instead is the thing that is # genuinely about the parser: it read the file and reached the interface check. ( "$KA" -t --log-console -f /etc/keepalived/keepalived.conf.sample \ > "$D/t-sample.log" 2>&1 ); src=$? bst_skip keepalived/confcheck-sample-rc \ "the sample names eth0 and the net stack is loopback-only; it also carries upstream's own out-of-range vrrp_garp_interval 0 (rc=$src)" if has "$(cat "$D/t-sample.log")" "eth0"; then bst_ok keepalived/confcheck-sample-parsed else bst_notok keepalived/confcheck-sample-parsed \ "-t never reached the sample's interface check -- $(_bst_1line "$(head -3 "$D/t-sample.log")")" fi # ------------------------------------------------- A BROKEN CONFIG IS REFUSED # THE OTHER HALF OF THE CRITERION, and the control that gives the case above its # meaning: a -t that exited 0 unconditionally would pass everything so far. # (1) The file is not there at all. THIS ONE EXITS 0, AND THAT IS UPSTREAM'S # BEHAVIOUR, NOT THIS PLATFORM'S. glob() answers GLOB_NOMATCH; keepalived's # open_and_check_glob() (lib/parser.c) then returns false having reported # nothing through report_config_error() -- "Config files missing '...'" goes # out through file_config_error(), which does not set the config status -- # so check_conf_file() fails, main.c's config_test_exit() reads the status as # CONFIG_OK and exits KEEPALIVED_CHK_EXIT_OK. An earlier revision of this # suite called this "the most certain of the three"; it is in fact the one # case -t gets wrong, and it is asserted here as what upstream does so a # change in either direction is visible. ( "$KA" -t --log-console -f "$D/no-such-file.conf" > "$D/t-missing.log" 2>&1 ); mrc=$? if [ "$mrc" -eq 0 ] && has "$(cat "$D/t-missing.log")" "missing"; then bst_ok keepalived/confcheck-missing-file-is-quiet-rc0 else bst_notok keepalived/confcheck-missing-file-is-quiet-rc0 \ "upstream 2.2.8 exits 0 saying 'Config files missing'; got rc=$mrc -- $(_bst_1line "$(head -3 "$D/t-missing.log")")" fi # (2) Syntactically broken -- a block that is opened and never closed. This is # the lexer's error, not the validator's. cat > "$D/unclosed.conf" <<'EOF' vrrp_instance VI_BROKEN { state MASTER interface lo virtual_router_id 51 EOF ( "$KA" -t --log-console -f "$D/unclosed.conf" > "$D/t-unclosed.log" 2>&1 ); urc=$? if [ "$urc" -ne 0 ]; then bst_ok keepalived/confcheck-unclosed-block-rc else bst_notok keepalived/confcheck-unclosed-block-rc \ "-t exited 0 on an unterminated vrrp_instance block -- $(_bst_1line "$(tail -3 "$D/t-unclosed.log")")"; fi # (3) Syntactically fine and semantically impossible -- a VRID outside [1,255]. # This one is the validator, and it is the interesting case: a parser that # merely tokenised would accept it. cat > "$D/badvrid.conf" <<'EOF' vrrp_instance VI_BADVRID { state MASTER interface lo virtual_router_id 300 priority 255 advert_int 1 } EOF ( "$KA" -t --log-console -f "$D/badvrid.conf" > "$D/t-badvrid.log" 2>&1 ); brc=$? if [ "$brc" -ne 0 ]; then bst_ok keepalived/confcheck-bad-vrid-rc else bst_notok keepalived/confcheck-bad-vrid-rc \ "-t exited 0 on virtual_router_id 300, which is outside [1,255] -- $(_bst_1line "$(tail -3 "$D/t-badvrid.log")")"; fi # The refusal is spoken, not merely returned: a non-zero exit with a silent log # would leave an operator nothing to act on. The needles are what keepalived # actually says -- "There are 1 missing '}'s or extra '{'s" for the lexer and # "VRID '300' not valid" for the validator. The old revision looked for the # substring "onfig", which keepalived never prints for either, so the case was # red on a daemon that was in fact perfectly explicit. if has "$(cat "$D/t-unclosed.log")" "missing '}'" && \ has "$(cat "$D/t-badvrid.log")" "not valid"; then bst_ok keepalived/confcheck-refusal-is-spoken else bst_notok keepalived/confcheck-refusal-is-spoken \ "unclosed said '$(_bst_1line "$(head -1 "$D/t-unclosed.log")")'; bad-vrid said '$(_bst_1line "$(head -1 "$D/t-badvrid.log")")'" fi # ------------------------------------------------------ THE STATE MACHINE ---- # WITHHELD BY THE PLATFORM ON TWO INDEPENDENT COUNTS, either of which alone would # be enough -- see the header. No daemon is started: with no legal interface # there is nothing for it to take MASTER on, and with epoll ENOSYS its scheduler # traps the runtime rather than merely failing. Reported as attributed skips in # the shape 81-libubox uses for the same #53. # BRINTOS-WORKAROUND: brintos/linux#53 remove -- restore the four cases below # once epoll_create is wired AND this image has a non-loopback interface. VRRP_WHY="no interface a vrrp_instance may legally name (keepalived refuses loopback; the net stack is loopback-only) and epoll_create is ENOSYS (brintos/linux#53), so keepalived's scheduler cannot start" bst_skip keepalived/daemon-read-suite-config "$VRRP_WHY" bst_skip keepalived/netlink-reflector "$VRRP_WHY" bst_skip keepalived/vrrp-enters-master "$VRRP_WHY" bst_skip keepalived/vrrp-sets-vip "$VRRP_WHY" # --------------------------------------------------------------- genhash ----- # The hash tool keepalived's HTTP_GET checkers call. It needs a server to hash, # which this image's loopback-only stack can host but this suite does not stand # up; what is asserted is that the binary runs and prints its own usage, which is # the whole of what can be said about it offline. gh=$(genhash 2>&1); ghrc=$? if has "$gh" "genhash"; then bst_ok keepalived/genhash-usage elif bst_execfail "$gh"; then bst_unrun keepalived/genhash-usage "$gh" else bst_notok keepalived/genhash-usage "rc=$ghrc, no usage text -- $(_bst_1line "$gh")"; fi rm -rf "$D"