. "$BST_LIB" # BST-TIMEOUT: 300 # ipvsadm 1.31 -- the Linux Virtual Server administration utility, from # meta-networking recipes-support/ipvsadm. It is the userspace front end to # IPVS, the in-kernel layer-4 load balancer. Test-plan entry 553. # # --------------------------------------------------------------------------- # THIS KERNEL CARRIES NO IPVS, AND -- UNLIKE EVERY PORT BEFORE IT -- THAT WALL # STANDS IN FRONT OF THE WHOLE PROGRAM RATHER THAN BEHIND ITS RUNTIME HALF. # # ipvsadm.c main() opens: # # if (ipvs_init()) { # if (modprobe_ipvs() || ipvs_init()) # fail(2, "Can't initialize ipvs: %s\n" # "Are you sure that IP Virtual Server is " # "built in the kernel or as module?", # ipvs_strerror(errno)); # } # # ipvs_init() is the FIRST statement. getopt has not run, argv has not been # looked at, and there is no early exit for --help or --version -- process_options() # is four statements further down and is never reached. So on a kernel without # IPVS, `ipvsadm --help` and `ipvsadm --version` exit 2 with the initialisation # diagnostic exactly as `ipvsadm -A -t ...` does. This is the finding the port # surfaces, and the suite asserts it as a positive fact rather than working # around it: the wall is UNIFORM over argv. # # Contrast babeld (entry 506), whose IPv6 wall sits after getopt and after the # configuration parser, which is why that port could assert its usage text, its # version string and its argument validation against the binary itself. Here # none of the three is reachable through /sbin/ipvsadm on this image, and the # suite says so with a named cause instead of quietly asserting less. # # WHY IPVS IS ABSENT. arch/wasm32/configs/wasm32_defconfig sets CONFIG_NET=y, # CONFIG_INET=y and CONFIG_UNIX=y and stops there: it carries no CONFIG_NETFILTER # and therefore no CONFIG_IP_VS, IPVS being a netfilter subsystem. The same file # has `# CONFIG_MODULES is not set`, so ipvsadm's own modprobe_ipvs() fallback -- # fork + execv("/sbin/modprobe", "--", "ip_vs") -- cannot succeed either, and # there is no runtime path by which this image could acquire IPVS. # # HOW ipvs_init() FAILS HERE, precisely, because the suite asserts the tail of # the diagnostic and the tail is the errno. Built against libnl (the recipe # DEPENDS on libnl and popt, both already on this image), libipvs tries genetlink # first: genl_ctrl_resolve(sock, "IPVS") cannot resolve a family the kernel never # registered, so the netlink attempt fails and libipvs falls back to # # socket(AF_INET, SOCK_RAW, IPPROTO_RAW) # getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO, ...) # # Two outcomes are reachable on this platform and the suite accepts either: # the raw socket opens and getsockopt returns ENOPROTOOPT, because ip_getsockopt() # has no netfilter hook to hand the unknown option to -- ipvs_strerror maps that # to the exact string "Protocol not available"; or the raw socket does not open at # all, in which case ipvs_init() returns before getsockopt and the tail is # whatever strerror() gives for that failure. Both are "no IPVS"; which one this # kernel produces is recorded rather than assumed. # # --------------------------------------------------------------------------- # WHAT IS STILL ASSERTED FOR REAL, AND IT IS NOT NOTHING. # # The package ships three programs, and only one of them is the C binary. The # wrappers ipvsadm-save and ipvsadm-restore are POSIX shell, and their own logic # runs to completion on this image before ipvsadm is ever invoked: # # * ipvsadm-save parses argv ITSELF -- `for arg; do case "$arg" in -n) ...;; # -*) help ;; esac; done` -- so its argument validation and its usage text # are reachable, exactly, with no kernel involvement. That is where this # suite gets the "usage text" and "argument validation" the ticket asks to be # asserted for real. # * ipvsadm-restore runs `INPUT="$(grep -v '^#')"`, then `ipvsadm -C`, then # `echo "$INPUT" | ipvsadm -R`. The rule file is read to EOF and the two-step # clear-then-restore flow happens for real; only the two ipvsadm calls hit the # wall. The suite asserts the flow by counting the diagnostic, which appears # once per call and therefore exactly twice. # # Both wrappers are #!/bin/sh rather than #!/bin/bash, and that is not cosmetic: # meta-networking carries 0003-ipvsadm-remove-dependency-on-bash.patch, and # THERE IS NO BASH ON THIS IMAGE. Without that patch both scripts would fail to # exec. The suite asserts the shebang and then proves it by running them. # # --------------------------------------------------------------------------- # WHAT IS PROBED RATHER THAN ASSUMED. Everything that needs IPVS itself -- # listing the virtual-service table, adding a service, saving the rules -- is # skipped ONLY where the probe below confirms the subsystem is absent, and runs # for real on any image that has it. /proc/net/ip_vs is created by the IPVS # module's own init and exists if and only if IPVS is present; ipvsadm's exit-2 # diagnostic is the second, independent reading of the same fact. A skip here can # therefore never be a suite that gave up. # # THE GAP ITSELF IS SCORED AS ONE ATTRIBUTED RED at the end, not hidden behind # the skips it causes. ipvsadm is present and correctly built; what stops it is # the platform, and that is the finding this port exists to record. # ------------------------------------------------------------------ locate -- # /sbin, not /usr/sbin: meta-networking installs with SBIN=$(BUILD_ROOT)$base_sbindir # and this distro sets DISTRO_FEATURES = "", so there is no usrmerge and # base_sbindir is /sbin. A red on the path cases means the distro grew usrmerge, # which matters to every package that splits the same way. find_prog() { _fp=$(command -v "$1" 2>/dev/null || true) if [ -z "$_fp" ]; then for _p in /sbin/"$1" /usr/sbin/"$1" /bin/"$1" /usr/bin/"$1"; do [ -x "$_p" ] && { _fp=$_p; break; } done fi printf '%s' "$_fp" } IPVSADM=$(find_prog ipvsadm) SAVE=$(find_prog ipvsadm-save) RESTORE=$(find_prog ipvsadm-restore) require "${IPVSADM:-ipvsadm}" ipvsadm/present D=${TMPDIR:-/tmp}/bst-ipvsadm.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok ipvsadm/workdir "cannot create $D"; exit 0; } WANT_VERSION="1.31" # run : bounded, stderr folded into stdout, text in $out and # status in $rc. Nothing on this image can be killed once it blocks # (brintos/linux#21), and ipvsadm's modprobe fallback forks and execs, so every # invocation is bounded even where it is expected to exit at once. run() { _t=$1; shift; out=$(timeout "$_t" "$@" 2>&1); rc=$?; } # 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 } # count_lines : how many lines of contain . count_lines() { printf '%s\n' "$1" | grep -c "$2" 2>/dev/null; } DIAG_HEAD="Can't initialize ipvs:" DIAG_TAIL="Are you sure that IP Virtual Server is built in the kernel or as module?" # ---------------------------------------------------------------- inventory -- check_eq ipvsadm/inventory "yes" "$([ -x "$IPVSADM" ] && echo yes)" check_eq ipvsadm/install-path "/sbin/ipvsadm" "$IPVSADM" check_eq ipvsadm/on-path "$IPVSADM" "$(command -v ipvsadm 2>/dev/null)" # The two wrappers are part of the package, not extras: upstream's install target # ships all three from the same rule. check_eq ipvsadm/save-install-path "/sbin/ipvsadm-save" "$SAVE" check_eq ipvsadm/restore-install-path "/sbin/ipvsadm-restore" "$RESTORE" # The SysV init script the recipe redirects from /etc/rc.d/init.d to # ${sysconfdir}/init.d. Nothing on this image runs it -- there is no sysvinit -- # but it is what the package installs and a silent disappearance would mean the # recipe's sed stopped matching. check ipvsadm/initscript-shipped test -f /etc/init.d/ipvsadm # --------------------------------------------------------------- man pages -- # MANDIR is usr/share/man only because meta-networking carries # 0001-Modify-the-Makefile-for-cross-compile.patch; upstream's own Makefile says # `MANDIR = usr/man`, which would land the pages outside ${mandir}, leave them # unpackaged and fail installed-vs-shipped QA. Every "documented" claim below is # read from these pages, so their absence would leave the suite citing files the # image does not have. for m in ipvsadm ipvsadm-save ipvsadm-restore; do MP=/usr/share/man/man8/$m.8 check ipvsadm/manpage-$m test -f "$MP" done MP=/usr/share/man/man8/ipvsadm.8 if [ -f "$MP" ]; then case "$(cat "$MP" 2>/dev/null)" in *".TH IPVSADM 8"*) bst_ok ipvsadm/manpage-is-roff ;; *) bst_notok ipvsadm/manpage-is-roff "want a roff '.TH IPVSADM 8' header, got '$(_bst_1line "$(head -n 1 "$MP" 2>/dev/null)")'" ;; esac else bst_notok ipvsadm/manpage-is-roff "no man page to read (see ipvsadm/manpage-ipvsadm)" fi # ------------------------------------------------------------------- probe -- # /proc/net/ip_vs is created by IPVS's own module init and exists iff IPVS is # present. This is the probe every skip below cites; the exit-2 diagnostic # further down is the second, independent reading. if [ -e /proc/net/ip_vs ]; then IPVS_PRESENT=yes; else IPVS_PRESENT=no; fi check_eq ipvsadm/probe-proc-ip_vs-absent "no" "$IPVS_PRESENT" # CONFIG_MODULES is not set, so ipvsadm's fork+execv("/sbin/modprobe") fallback # has nothing it could load even if modprobe is present. Recorded because it is # the other half of "there is no runtime path to IPVS on this image". if [ -e /proc/modules ]; then MODULES_PRESENT=yes; else MODULES_PRESENT=no; fi check_eq ipvsadm/probe-no-loadable-modules "no" "$MODULES_PRESENT" # ------------------------------------------------- the wall, asserted exactly -- # `ipvsadm -L` is the most ordinary thing a user types. On this kernel it is the # initialisation failure, and both lines of it are asserted verbatim. run 30 "$IPVSADM" -L check_eq ipvsadm/list-rc "2" "$rc" if has "$out" "$DIAG_HEAD"; then bst_ok ipvsadm/diag-head else bst_notok ipvsadm/diag-head "want '$DIAG_HEAD', got '$(_bst_1line "$out")'"; fi if has "$out" "$DIAG_TAIL"; then bst_ok ipvsadm/diag-tail else bst_notok ipvsadm/diag-tail "want '$DIAG_TAIL', got '$(_bst_1line "$out")'"; fi # The errno tail. Two outcomes are reachable here and both mean "no IPVS": the # raw socket opens and getsockopt returns ENOPROTOOPT, which ipvs_strerror maps # to exactly "Protocol not available"; or the raw socket does not open, and the # tail is strerror() of that failure. Accepting either keeps the case a test of # the platform rather than of one branch, and a red is then a genuine surprise # worth reading. ERRNO_TAIL=$(printf '%s\n' "$out" | sed -n "s/^.*$DIAG_HEAD *//p" | head -n 1) case "$ERRNO_TAIL" in "Protocol not available") bst_ok ipvsadm/diag-errno-enoprotoopt ;; "") bst_notok ipvsadm/diag-errno-enoprotoopt "no errno text after '$DIAG_HEAD' in '$(_bst_1line "$out")'" ;; *) bst_notok ipvsadm/diag-errno-enoprotoopt "want 'Protocol not available' (ENOPROTOOPT from getsockopt) or a raw-socket strerror, got '$(_bst_1line "$ERRNO_TAIL")' -- read the comment above this case before attributing" ;; esac # Everything goes to stderr; stdout is empty. A future release that moved the # diagnostic to stdout would be reported rather than silently absorbed by the # 2>&1 the other cases fold. STDOUT_ONLY=$(timeout 30 "$IPVSADM" -L 2>/dev/null) check_eq ipvsadm/diag-not-on-stdout "" "$STDOUT_ONLY" # ------------------------------------------------ the wall is uniform over argv -- # THIS IS THE PORT'S CENTRAL FINDING. ipvs_init() precedes getopt, so argv is # never examined: --help, --version, a syntactically invalid option and a valid # service-add all produce the same rc and the same diagnostic. Each is asserted # separately so that a future ipvsadm which added an early --help exit would show # up as exactly one red, naming itself. for a in --help --version -h -v; do id=ipvsadm/wall-uniform-$(printf '%s' "$a" | sed 's/^-*//') run 30 "$IPVSADM" "$a" if [ "$rc" -eq 2 ] && has "$out" "$DIAG_HEAD"; then bst_ok "$id" else bst_notok "$id" "rc=$rc want=2 with '$DIAG_HEAD', got '$(_bst_1line "$out")'" fi done # An option ipvsadm does not have. getopt would reject it with its own message # and rc 255; it never runs, so this is the initialisation failure too. run 30 "$IPVSADM" --no-such-option-at-all if [ "$rc" -eq 2 ] && has "$out" "$DIAG_HEAD"; then bst_ok ipvsadm/wall-precedes-getopt else bst_notok ipvsadm/wall-precedes-getopt "an unknown option reached getopt (rc=$rc): '$(_bst_1line "$out")'" fi # A bare `ipvsadm` with no argv at all takes the argc==1 list_all() path -- also # behind the wall. run 30 "$IPVSADM" check_eq ipvsadm/wall-bare-invocation "2" "$rc" # ------------------------------------- the version, and why it is not asserted -- # `ipvsadm --version` is gated by the same wall, so the binary cannot be asked # what it is. The version is still checked, from the one place on the image that # can answer: the package manifest. This is a weaker statement than babeld's # `babeld -V` assertion and is deliberately labelled as such. # This image is PACKAGE_CLASSES = package_deb (conf/distro/brintos.conf), so the # manifest is dpkg's, not opkg's: /var/lib/dpkg is populated and queryable -- the # property 80-dpkg.test already asserts as `dpkg/status-db` and `dpkg/query-field`. # opkg is kept as a fallback so the case does not become a statement about which # package manager the distro happens to use. PKGTOOL= if command -v dpkg-query >/dev/null 2>&1; then PKGTOOL=dpkg-query PKGVER=$(timeout 60 dpkg-query -W -f='${Version}' ipvsadm 2>/dev/null) elif command -v opkg >/dev/null 2>&1; then PKGTOOL=opkg PKGVER=$(timeout 60 opkg list-installed ipvsadm 2>/dev/null | head -n 1) fi if [ -z "$PKGTOOL" ]; then bst_skip ipvsadm/version-from-package "no dpkg-query and no opkg on this image; the binary's own --version is behind the IPVS wall (see ipvsadm/wall-uniform-version)" else case "$PKGVER" in *"$WANT_VERSION"*) bst_ok ipvsadm/version-from-package ;; "") bst_notok ipvsadm/version-from-package "$PKGTOOL reports ipvsadm as not installed" ;; *) bst_notok ipvsadm/version-from-package "want $WANT_VERSION, $PKGTOOL says '$(_bst_1line "$PKGVER")'" ;; esac fi # ------------------------------------ ipvsadm-save: real argv validation + usage -- # help() is pure shell and runs before ipvsadm is invoked, so this is the # ticket's "usage text" and "argument validation" asserted for real, on this # image, with no kernel involvement. if [ -x "$SAVE" ]; then case "$(head -n 1 "$SAVE" 2>/dev/null)" in "#!/bin/sh") bst_ok ipvsadm/save-shebang-sh ;; *) bst_notok ipvsadm/save-shebang-sh "want '#!/bin/sh' (there is no bash on this image; meta-networking patches it), got '$(_bst_1line "$(head -n 1 "$SAVE" 2>/dev/null)")'" ;; esac USAGE1="ipvsadm-save: Script to save the IPVS table to stdout." USAGE2="With the -n option, prints out the table in numeric format." # Any unrecognised dash option takes the `-*) help` branch: usage to stderr, # exit 1, ipvsadm never invoked. for a in -x --help -q; do id=ipvsadm/save-rejects-$(printf '%s' "$a" | sed 's/^-*//') run 30 "$SAVE" "$a" if [ "$rc" -eq 1 ] && has "$out" "$USAGE1" "$USAGE2"; then bst_ok "$id" else bst_notok "$id" "rc=$rc want=1 with the usage text, got '$(_bst_1line "$out")'" fi done # help() does `exec 1>&2` first, so the usage text is on stderr and stdout is # empty -- asserted directly rather than inferred from the folded capture. SAVE_STDOUT=$(timeout 30 "$SAVE" -x 2>/dev/null) check_eq ipvsadm/save-usage-on-stderr "" "$SAVE_STDOUT" # `$0` reaches the message through `basename $0`, so the program names itself # correctly only when it was found on PATH as ipvsadm-save. run 30 "$SAVE" -x FIRST=$(printf '%s\n' "$out" | head -n 1) check_eq ipvsadm/save-usage-first-line "$USAGE1" "$FIRST" # -n is ACCEPTED by the wrapper (it is the one option it has) and therefore # reaches `ipvsadm -S -n`, which is the wall. rc 2, not 1: the distinction # between "the wrapper rejected argv" and "the wrapper accepted argv and the # kernel stopped it" is exactly what makes the two cases above meaningful. run 30 "$SAVE" -n if [ "$rc" -eq 2 ] && has "$out" "$DIAG_HEAD"; then bst_ok ipvsadm/save-accepts-n-then-wall else bst_notok ipvsadm/save-accepts-n-then-wall "rc=$rc want=2 with '$DIAG_HEAD', got '$(_bst_1line "$out")'" fi else bst_notok ipvsadm/save-shebang-sh "ipvsadm-save is not installed (see ipvsadm/save-install-path)" fi # ------------------------------- ipvsadm-restore: the rule file is really read -- if [ -x "$RESTORE" ]; then case "$(head -n 1 "$RESTORE" 2>/dev/null)" in "#!/bin/sh") bst_ok ipvsadm/restore-shebang-sh ;; *) bst_notok ipvsadm/restore-shebang-sh "want '#!/bin/sh' (there is no bash on this image; meta-networking patches it), got '$(_bst_1line "$(head -n 1 "$RESTORE" 2>/dev/null)")'" ;; esac # A rule file in the format ipvsadm -S emits, with comments for grep -v '^#' # to strip. RULES=$D/rules.txt { echo '# saved by ipvsadm-save' echo '-A -t 10.0.0.1:80 -s rr' echo '-a -t 10.0.0.1:80 -r 10.0.0.2:80 -m -w 1' echo '# end' } > "$RULES" # The wrapper reads stdin to EOF through grep, then runs `ipvsadm -C` and # `echo "$INPUT" | ipvsadm -R`. Both hit the wall, so the diagnostic appears # once per call -- exactly twice. That count is what proves the two-step # clear-then-restore flow ran; a wrapper that stopped after -C would print it # once, and one that never started would print it not at all. # # The redirect is performed by a SUBSHELL, not by this shell: a stdin redirect # the top-level suite shell performs on the virtiofs root makes dup2(2) return # 513 and ash exits (brintos/linux#73, and see bst_local in bst.sh). out=$( (timeout 60 "$RESTORE" < "$RULES") 2>&1 ); rc=$? check_eq ipvsadm/restore-rc "2" "$rc" N=$(count_lines "$out" "$DIAG_HEAD") check_eq ipvsadm/restore-two-step "2" "$N" # Reading stdin to EOF is the wrapper's own work and completes regardless of # IPVS: an empty rule file must behave the same way rather than block. : > "$D/empty.txt" out=$( (timeout 60 "$RESTORE" < "$D/empty.txt") 2>&1 ); rc=$? check_eq ipvsadm/restore-empty-rc "2" "$rc" check_eq ipvsadm/restore-empty-two-step "2" "$(count_lines "$out" "$DIAG_HEAD")" # A file of nothing but comments: grep -v '^#' matches no line and exits 1, # but the wrapper does not check it, so the flow is unchanged. Asserting this # pins the one branch of the comment filter that IS observable from outside. printf '# only\n# comments\n' > "$D/comments.txt" out=$( (timeout 60 "$RESTORE" < "$D/comments.txt") 2>&1 ); rc=$? check_eq ipvsadm/restore-comments-only-rc "2" "$rc" check_eq ipvsadm/restore-comments-only-two-step "2" "$(count_lines "$out" "$DIAG_HEAD")" # What the comment filter DOES to the text is visible only to `ipvsadm -R`, # which the missing IPVS stops before it can read a byte. There is no # observer for it on this image, and substituting one would mean shadowing # /sbin/ipvsadm from /bin -- which comes first on the wrapper's hardcoded # PATH -- on an image shared with the rest of this batch. Not worth a # sibling's suite. bst_skip ipvsadm/restore-comment-stripping "the filtered text is read only by 'ipvsadm -R', which exits 2 before parsing (see ipvsadm/probe-proc-ip_vs-absent)" else bst_notok ipvsadm/restore-shebang-sh "ipvsadm-restore is not installed (see ipvsadm/restore-install-path)" fi # ------------------------------------------- the runtime half, probe-gated -- # These run for real on any image whose kernel has IPVS, and are skipped only # where the probe above confirmed it does not. Nothing here is skipped on a # guess. if [ "$IPVS_PRESENT" = yes ]; then run 30 "$IPVSADM" -L -n check_eq ipvsadm/list-empty-rc "0" "$rc" run 30 "$IPVSADM" -A -t 10.0.0.1:80 -s rr check_eq ipvsadm/add-service-rc "0" "$rc" run 30 "$IPVSADM" -a -t 10.0.0.1:80 -r 10.0.0.2:80 -m -w 1 check_eq ipvsadm/add-dest-rc "0" "$rc" run 30 "$IPVSADM" -L -n if has "$out" "10.0.0.1:80" "10.0.0.2:80"; then bst_ok ipvsadm/list-shows-service else bst_notok ipvsadm/list-shows-service "want the service and its destination, got '$(_bst_1line "$out")'"; fi run 30 "$SAVE" -n if [ "$rc" -eq 0 ] && has "$out" "-A -t 10.0.0.1:80"; then bst_ok ipvsadm/save-emits-rules else bst_notok ipvsadm/save-emits-rules "rc=$rc, got '$(_bst_1line "$out")'"; fi run 30 "$IPVSADM" -C check_eq ipvsadm/clear-rc "0" "$rc" else SKIPWHY="no IPVS in this kernel: /proc/net/ip_vs absent and ipvsadm exits 2 with '$DIAG_HEAD' (wasm32_defconfig has no CONFIG_NETFILTER, hence no CONFIG_IP_VS, and no CONFIG_MODULES to load one)" for c in list-empty add-service add-dest list-shows-service save-emits-rules clear; do bst_skip "ipvsadm/$c" "$SKIPWHY" done fi # ------------------------------------------------------- the gap, as one red -- # Scored, not hidden. ipvsadm is present, correctly built and correctly packaged; # the subsystem it administers is not in this kernel, so the program cannot # perform its function -- nor even print its own usage. This is the port's # finding and it is attributed to the kernel configuration, which is a platform # gap rather than a defect in the package or in the recipe. It flips green by # itself on any image whose kernel gains CONFIG_IP_VS. if [ "$IPVS_PRESENT" = yes ]; then bst_ok ipvsadm/ipvs-available else bst_notok ipvsadm/ipvs-available "IPVS is absent from this kernel (wasm32_defconfig: no CONFIG_NETFILTER and so no CONFIG_IP_VS; no CONFIG_MODULES), so ipvsadm cannot administer anything and exits 2 before reading argv -- see test-plan entry 553" fi rm -rf "$D"