. "$BST_LIB" # BST-TIMEOUT: 600 # radvd 2.19 (meta-networking recipes-daemons/radvd) -- Lars Fenneberg / Reuben # Hawkins' IPv6 router-advertisement daemon. What ships is /usr/sbin/radvd, the # /usr/sbin/radvdump RA decoder, and the /etc/radvd.conf this port bundles. # # THE SUBJECT IS THE CONFIGURATION PATH, BECAUSE IT IS THE HALF THIS KERNEL CAN # REACH. radvd's main() runs, in this order: open the log, check the config # file's permissions, readin_config(), and -- only then -- open the raw ICMPv6 # socket it advertises through (radvd.c, `int sock = open_icmpv6_socket()' # immediately after the configtest exit). Everything before that socket is # reachable on this image and is scored below by value. Everything after it is # not, for one reason that is the platform's and not this port's, and those # cases are scored separately and attributed. # # THERE IS NO AF_INET6 ON THIS KERNEL. open_icmpv6_socket() asks for # socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) (socket.c:35) and this kernel is # built `# CONFIG_IPV6 is not set' -- brintos/linux#54, established directly # from a compiled probe by 79-libndp.test (libndp/control-inet6-dgram) and hit # by inetutils' ping6 in 88-inetutils.test. So the call answers EAFNOSUPPORT, # radvd logs `can't create socket(AF_INET6)' and exits 1, and no advertisement # is ever composed. radvd is an IPv6-only daemon, so this is not a corner of it: # it is every line past the config parser. A red in that group with the config # group green is the platform, and it flips by itself the day CONFIG_IPV6 is # turned on -- nothing here needs changing then. # # UPSTREAM'S OWN /etc/radvd.conf IS ONE radvd REJECTS, WHICH IS WHY THIS PORT # BUNDLES ANOTHER. meta-networking installs a fully commented-out worked # example; readin_config() parses it, builds no interface, and returns a NULL # IfaceList, which radvd.c reads as `exiting, failed to read config file' and # exits 1. The criterion this port was taken on -- that `radvd -c -C ' # ACCEPTS a bundled config -- has no subject on a stock image, so the bbappend # overrides the file. That the shipped file is accepted is asserted below, and a # regression to upstream's empty one would surface there rather than silently. # # HOW THE CONFIGURED VALUES ARE ESTABLISHED BY VALUE, GIVEN radvd PRINTS NO # CONFIG DUMP. radvd has no mode that echoes what it parsed: -c forces the debug # level to 1 (radvd.c's `if (configtest) set_debuglevel(1)'), and the only # per-value logging past that lives in send.c, past the socket. So the values are # established the other way round, through the parser's own PARSE-TIME semantic # checks, each of which fires only if the numbers it names were read: # * AdvValidLifetime vs AdvPreferredLifetime are compared in gram.y's # prefixdef rule, so a file whose preferred exceeds its valid is rejected by # line -- which proves both numbers reached the parser. # * A prefix length above 128 is rejected in the prefixhead rule, which proves # the /NN was read rather than defaulted. # * A second `interface lo' block is rejected by NAME in the ifacehead rule. # Each is a distinct case below. They are stronger than an accept-only assertion # precisely because a parser that ignored the file entirely would pass that one. # # NOTHING IS KILLED AND NOTHING IS LEFT RUNNING. Every invocation below exits on # its own -- at the configtest exit, at a parse error, or at the ICMPv6 socket -- # and each is bounded by `timeout' so that an image which one day HAS AF_INET6 # reports rc=124 here rather than spending the suite's whole budget in radvd's # main loop. No case scores a signal: kill(2) reports success and delivers # nothing on this image (brintos/linux#21), and that red is already carried where # it was found (88-dnsmasq/sigterm-stops). require /usr/sbin/radvd radvd/present RADVD=/usr/sbin/radvd CONF=/etc/radvd.conf D=${TMPDIR:-/tmp}/bst-radvd.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok radvd/workdir "cannot create $D"; exit 0; } # rb -- run bounded, capturing stdout+stderr; sets $out and $rc. # radvd writes every diagnostic to stderr (log_open with -m stderr), so both # streams are merged and read together. rb() { _t=$1; shift; out=$(timeout "$_t" "$@" 2>&1); rc=$?; } has() { case "$2" in *"$1"*) return 0 ;; *) return 1 ;; esac; } # --- inventory: what the package put on the image ----------------------------- check_eq radvd/inventory "yes" "$([ -x "$RADVD" ] && echo yes)" # The bare name a user types must be this binary; `command -v' reads root's real # PATH, so this also catches an image where /usr/sbin is not on it. check_eq radvd/on-path "$RADVD" "$(command -v radvd 2>/dev/null)" # radvdump ships from the same recipe and is the decoder half of the pair. It is # asserted present but not exercised: it reads RAs off the same raw ICMPv6 socket # radvd cannot open, so every functional case for it would be a second copy of # the same #54 red under a different name. check_eq radvd/radvdump-present "yes" "$([ -x /usr/sbin/radvdump ] && echo yes)" # The bundled config must be installed, and must be the file this port wrote # rather than upstream's commented-out example -- which is exactly a file with no # uncommented `interface' line. check_eq radvd/conf-installed "yes" "$([ -f "$CONF" ] && echo yes)" check_eq radvd/conf-not-upstream-example "yes" \ "$(grep -q '^[[:space:]]*interface[[:space:]]' "$CONF" 2>/dev/null && echo yes)" # `radvd -v' prints the compiled-in paths and exits ONE, not zero: version() ends # in exit(1) (radvd.c). That is upstream's behaviour and the case asserts the # code a user on this image actually gets, not the code the man page implies. rb 20 "$RADVD" -v if [ "$rc" -eq 124 ]; then bst_notok radvd/version-rc "radvd -v did not return within 20 s (rc=124)" elif [ "$rc" -eq 1 ]; then bst_ok radvd/version-rc elif bst_execfail "$out"; then bst_unrun radvd/version-rc "$out" else bst_notok radvd/version-rc "want rc=1 (version() ends in exit(1)); got rc=$rc -- $(_bst_1line "$out")"; fi # The same output names the config path radvd was COMPILED with. Worth asserting # on its own: the usage text one line above it in the source says the default is # `/etc/radvd.d', which is an upstream documentation bug -- PATH_RADVD_CONF is # /etc/radvd.conf. A build that packaged the config somewhere else would put the # bundled file out of radvd's reach and show up here. check_eq radvd/version-conf-path "yes" \ "$(has 'default config file' "$out" && has "\"$CONF\"" "$out" && echo yes)" # --- the configuration path: accept ------------------------------------------- # The criterion's first half. The SHIPPED file, at its packaged path, through the # documented config-check invocation. rb 30 "$RADVD" -c -C "$CONF" shipped_out=$out; shipped_rc=$rc if [ "$shipped_rc" -eq 0 ]; then bst_ok radvd/configtest-shipped elif bst_execfail "$shipped_out"; then bst_unrun radvd/configtest-shipped "$shipped_out" else bst_notok radvd/configtest-shipped "radvd -c -C $CONF rejected the bundled config: rc=$shipped_rc -- $(_bst_1line "$shipped_out")"; fi # -c forces debug level 1, at which readin_config() logs the file it accepted by # NAME. Asserting the line and not only the status distinguishes "parsed this # file" from "exited zero for some other reason". check_eq radvd/configtest-syntax-ok "yes" \ "$(has "config file, $CONF, syntax ok" "$shipped_out" && echo yes)" # --- the configuration path: reject ------------------------------------------- # The criterion's other half: a broken config must be rejected NON-ZERO. Four # distinct breakages, each derived from the shipped file so that the only # difference between an accepted config and a rejected one is the defect. # 1. A plain syntax error. yyerror() prints `: error: syntax error', # so the diagnostic is asserted to name the file and the word `error' rather # than merely to be non-empty. sed 's/AdvSendAdvert on;/AdvSendAdvert yes;/' "$CONF" > "$D/syntax.conf" chmod 644 "$D/syntax.conf" rb 30 "$RADVD" -c -C "$D/syntax.conf" if [ "$rc" -eq 124 ]; then bst_notok radvd/reject-syntax "radvd -c did not return within 30 s on a syntactically broken config (rc=124)" elif [ "$rc" -ne 0 ] && has "error" "$out"; then bst_ok radvd/reject-syntax elif bst_execfail "$out"; then bst_unrun radvd/reject-syntax "$out" else bst_notok radvd/reject-syntax "an unparsable AdvSendAdvert value was accepted: rc=$rc -- $(_bst_1line "$out")"; fi # 2. AdvPreferredLifetime raised above AdvValidLifetime. gram.y's prefixdef rule # compares the two AFTER both have been assigned, so this rejection is only # reachable if BOTH numbers were read from the file by value -- which is what # establishes that the lifetimes the criterion names reached the parser. The # file is otherwise the shipped one, so nothing else can explain the reject. sed 's/AdvPreferredLifetime 3600;/AdvPreferredLifetime 7300;/' "$CONF" > "$D/lifetimes.conf" chmod 644 "$D/lifetimes.conf" rb 30 "$RADVD" -c -C "$D/lifetimes.conf" if [ "$rc" -eq 124 ]; then bst_notok radvd/reject-lifetimes "radvd -c did not return within 30 s (rc=124)" elif [ "$rc" -ne 0 ] && has "AdvValidLifeTime must be" "$out"; then bst_ok radvd/reject-lifetimes elif [ "$rc" -ne 0 ]; then bst_notok radvd/reject-lifetimes "rejected, but not by the lifetime comparison -- so this does not establish that both lifetimes were read: rc=$rc $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun radvd/reject-lifetimes "$out" else bst_notok radvd/reject-lifetimes "AdvPreferredLifetime 7300 > AdvValidLifetime 7200 was accepted, so the two lifetimes were not compared: rc=$rc -- $(_bst_1line "$out")"; fi # 3. The prefix length pushed past 128. Rejected in the prefixhead rule, which # proves the /NN was read from the file rather than defaulted. sed 's#2001:db8:d5f0:1::/64#2001:db8:d5f0:1::/129#' "$CONF" > "$D/prefixlen.conf" chmod 644 "$D/prefixlen.conf" rb 30 "$RADVD" -c -C "$D/prefixlen.conf" if [ "$rc" -eq 124 ]; then bst_notok radvd/reject-prefixlen "radvd -c did not return within 30 s (rc=124)" elif [ "$rc" -ne 0 ] && has "invalid prefix length" "$out"; then bst_ok radvd/reject-prefixlen elif [ "$rc" -ne 0 ]; then bst_notok radvd/reject-prefixlen "rejected, but not by the prefix-length check: rc=$rc $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun radvd/reject-prefixlen "$out" else bst_notok radvd/reject-prefixlen "a /129 prefix was accepted, so the prefix length was not read: rc=$rc -- $(_bst_1line "$out")"; fi # 4. The interface block repeated. ifacehead rejects it by NAME, so this is the # case that establishes the interface the configuration names was read. { cat "$CONF"; printf 'interface lo\n{\n\tAdvSendAdvert on;\n};\n'; } > "$D/dupiface.conf" chmod 644 "$D/dupiface.conf" rb 30 "$RADVD" -c -C "$D/dupiface.conf" if [ "$rc" -eq 124 ]; then bst_notok radvd/reject-duplicate-iface "radvd -c did not return within 30 s (rc=124)" elif [ "$rc" -ne 0 ] && has "duplicate interface definition for lo" "$out"; then bst_ok radvd/reject-duplicate-iface elif [ "$rc" -ne 0 ]; then bst_notok radvd/reject-duplicate-iface "rejected, but not by the duplicate-name check: rc=$rc $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun radvd/reject-duplicate-iface "$out" else bst_notok radvd/reject-duplicate-iface "a second 'interface lo' block was accepted: rc=$rc -- $(_bst_1line "$out")"; fi # 5. A config file that is not there at all. check_conffile_perm() warns and # readin_config()'s fopen fails, so radvd exits 1 with `failed to read config # file'. Scored because "rejects a broken config" must not be satisfiable only # by files that exist. rb 30 "$RADVD" -c -C "$D/absent.conf" if [ "$rc" -eq 124 ]; then bst_notok radvd/reject-missing "radvd -c did not return within 30 s (rc=124)" elif [ "$rc" -ne 0 ] && has "failed to read config file" "$out"; then bst_ok radvd/reject-missing elif [ "$rc" -ne 0 ]; then bst_notok radvd/reject-missing "rejected, but not with the documented diagnostic: rc=$rc $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun radvd/reject-missing "$out" else bst_notok radvd/reject-missing "an absent config file was accepted: rc=$rc -- $(_bst_1line "$out")"; fi # --- the foreground run ------------------------------------------------------- # `radvd -d 5 -m stderr -n' -- the criterion's foreground invocation, and the # point at which this image and radvd part company. One run, read three ways: # what it logged BEFORE the socket (this port), and where it stopped (#54). # # -d 5 rather than -c is deliberate: -c would clamp the debug level back to 1 and # the debug-4 parse line below would never be emitted. rb 60 "$RADVD" -d 5 -m stderr -n -C "$CONF" fg_out=$out; fg_rc=$rc # Emitted before the config is even opened, so its absence would mean the run did # not start rather than that it failed. Asserted unconditionally, rc=124 included: # `timeout' killing a radvd that is still running is what SUCCESS looks like on a # kernel that has AF_INET6, and the command substitution keeps everything the # process wrote before the kill, so the banner is there either way. check_eq radvd/foreground-banner "yes" "$(has "version 2.19 started" "$fg_out" && echo yes)" # dlog(LOG_DEBUG, 4, "%s interface definition ok") in gram.y's ifacedef rule, # reached only at debug level 4 or above and only once the whole block has # parsed. It names the interface, so it says the configuration was read for `lo' # specifically -- and it is emitted on the FOREGROUND path, i.e. the invocation # the criterion names, not just under -c. check_eq radvd/foreground-parsed-iface "yes" \ "$(has "lo interface definition ok" "$fg_out" && echo yes)" check_eq radvd/foreground-syntax-ok "yes" \ "$(has "config file, $CONF, syntax ok" "$fg_out" && echo yes)" # BRINTOS-WORKAROUND: brintos/linux#54 remove -- the raw ICMPv6 socket. This is # the port's boundary with the platform and the one red this suite expects. When # CONFIG_IPV6 is turned on, radvd gets its socket and this case goes green with # no change here. if has "can't create socket(AF_INET6)" "$fg_out"; then bst_notok radvd/icmpv6-socket "brintos/linux#54 (kernel built CONFIG_IPV6=n): socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) is refused with EAFNOSUPPORT, so radvd exits immediately after reading the config it just accepted -- rc=$fg_rc $(_bst_1line "$fg_out")" elif [ "$fg_rc" -eq 124 ]; then bst_ok radvd/icmpv6-socket elif [ "$fg_rc" -eq 0 ]; then bst_ok radvd/icmpv6-socket elif bst_execfail "$fg_out"; then bst_unrun radvd/icmpv6-socket "$fg_out" else bst_notok radvd/icmpv6-socket "the foreground run failed, but NOT at the AF_INET6 socket -- this is not the known platform gap and wants reading: rc=$fg_rc $(_bst_1line "$fg_out")" fi # BRINTOS-WORKAROUND: brintos/linux#54 remove -- consequential, not independent. # The advertisement itself: the prefix, lifetimes and flags this configuration # composes are logged and transmitted from send.c, which main_loop() reaches only # through the socket above. With no socket there is no RA to observe, so this # case reports the consequence rather than re-establishing the cause. if has "can't create socket(AF_INET6)" "$fg_out"; then bst_notok radvd/advertise "brintos/linux#54, consequential: radvd exited at open_icmpv6_socket ($fg_rc), so setup_ifaces/main_loop never ran and the RA carrying prefix 2001:db8:d5f0:1::/64, lifetimes 7200/3600 and the M/O flags was never composed. Nothing is asserted about send.c here" elif has "AdvSendAdvert is off" "$fg_out"; then bst_notok radvd/advertise "radvd reached the send path but found AdvSendAdvert off, which the bundled config sets ON -- that is this port, not the platform" elif [ "$fg_rc" -eq 124 ] || [ "$fg_rc" -eq 0 ]; then bst_ok radvd/advertise elif bst_execfail "$fg_out"; then bst_unrun radvd/advertise "$fg_out" else bst_notok radvd/advertise "the foreground run ended for a reason that is not the AF_INET6 socket: rc=$fg_rc $(_bst_1line "$fg_out")" fi rm -rf "$D"