. "$BST_LIB" # BST-TIMEOUT: 300 # ~60 cases, one dnsmasq start, three adig spawns and ONE # # probe spawn that carries 34 of them. Provisional pending # # the first on-target measurement; the 60 s default is not # # close, because the server-readiness wait alone is bounded # # at 40 s and each adig carries a 20 s timeout. # c-ares 1.27.0 (meta-oe recipes-support/c-ares) -- the asynchronous DNS # resolver library curl, Node.js and a long tail of daemons resolve names # through. What ships is /usr/lib/libcares.a, seven headers, libcares.pc, the # two upstream tools adig and ahost, and the cross-linked probe this suite # drives. # # --------------------------------------------------------------------------- # WHY A PROBE WHEN THE PORT SHIPS TWO EXECUTABLES # # adig and ahost are resolver clients: they build a query, put it on a socket # and print the answer. Neither can reach ares_parse_a_reply or # ares_parse_ptr_reply over a CALLER-SUPPLIED BUFFER, which is the offline half # BRINTOS-667 names as its pass criterion -- and which is also the half whose # failure mode is answering WRONGLY rather than failing. An off-by-one in a # length-prefixed label walk or a byte-order slip in a 16-bit count compiles, # packages and installs perfectly; BRINTOS-217 is this layer's precedent, where # oniguruma returned silently wrong offsets to every caller on this arm. So # /usr/libexec/c-ares-tests/c-ares-probe is cross-linked against the very # libcares.a that ships (recipes-support/c-ares/c-ares_%.bbappend builds it in # the recipe, so it cannot be the sysroot's stale copy) and run here. Its source # is recipes-support/c-ares/c-ares/c-ares_probe.c. # # --------------------------------------------------------------------------- # THE SERVER RUNS ON PORT 15353, NOT 53, AND THAT IS NOT A PREFERENCE # # 88-dnsmasq.test starts a dnsmasq on 127.0.0.1:53 and CANNOT STOP IT: this # kernel delivers no signal at all, SIGKILL included (brintos/linux#21), which # that suite asserts directly. Whatever the runner's file order, a suite that # also wanted port 53 would therefore bind it or not depending on which of the # two ran first -- a green that is a property of the directory listing. A port # of our own removes the question entirely, and costs nothing: c-ares takes the # server and the port as options (ARES_OPT_SERVERS/ARES_OPT_UDP_PORT in the # probe, -s/-U on adig), so nothing about the criterion depends on 53. # # It also means this suite leaves a second unkillable dnsmasq behind. That is # the platform's doing rather than this suite's, it is bounded (one per boot), # and it is on a port nothing else uses. # # --------------------------------------------------------------------------- # WHAT THE RESOLVER CASES ARE A STATEMENT ABOUT # # glibc's getaddrinfo(3) sends no query on this image -- 88-dnsmasq.test scores # that red against brintos/linux-in-the-browser#96, with gethostbyname and # getnameinfo reaching the same server fine. c-ares goes through NONE of them: # ares_getaddrinfo is c-ares's own resolver, composing the query itself and # putting it on its own UDP socket. So a green here is not in tension with that # red, and it is precisely the capability a caller ports c-ares FOR on a # platform whose libc resolver is unreliable. # # --------------------------------------------------------------------------- # EVERY EXPECTED VALUE IN THE OFFLINE HALF IS A NATIVE x86_64 CONTROL'S # # The probe was developed against a native build of the identical release (same # tarball sha256 0a72be66..b420b, same CARES_SHARED=OFF/CARES_STATIC=ON # switches) driven against a host dnsmasq, and scored 26/26 offline and 8/8 # online. It was then mutation-tested rather than merely run, because a suite # that cannot fail is not evidence: changing one A record's last octet reddens # parse-a-addr1 and parse-a-ttladdr1 and nothing else; changing one TTL by one # reddens parse-a-ttl1 alone; dropping the second PTR record from the fixture # reddens the four ptr cases; asking for the wrong address reddens query-address # alone; and pointing the probe at a port with no server reddens the four # positive query cases. That last run is also what earned the conditional # scoring of query-unknown-name-refused -- see the case below. # $BST_CARES_ROOT prefixes every installed path, and is EMPTY on target -- it # exists so the whole suite can be driven against a native x86_64 tree of the # identical release before it ever ships, which is where its expected values # come from. One knob rather than one per path, so a control run cannot cover # half the payload by accident. R=${BST_CARES_ROOT:-} ARCHIVE=$R/usr/lib/libcares.a PCFILE=$R/usr/lib/pkgconfig/libcares.pc INC=$R/usr/include PROBE=$R/usr/libexec/c-ares-tests/c-ares-probe ADIG=$R/usr/bin/adig AHOST=$R/usr/bin/ahost # The version the recipe pins. Asserted rather than read back from the library, # so a recipe bumped without updating this suite is a red here instead of a # silent change of subject. WANT_VERSION=1.27.0 PORT=15353 NAME=bst-cares.test ADDR=10.11.12.13 # PTR: 13.12.11.10.in-addr.arpa if [ ! -f "$ARCHIVE" ]; then bst_skip c-ares/present "c-ares not installed on this image" exit 0 fi bst_ok c-ares/present tab=$(printf '\t') # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } # run : sets $out (stdout+stderr) and $rc. NOTHING ON THIS # IMAGE CAN BE KILLED (brintos/linux#21), so `timeout` cannot stop a command # that has already blocked -- it bounds a command that finishes, and nothing # more. Every command below is one that returns on its own. run() { _t=$1; shift; out=$(timeout "$_t" "$@" 2>&1); rc=$?; } # --- the shipped payload ----------------------------------------------------- hasfile c-ares/staticlib "$ARCHIVE" hasfile c-ares/pkgconfig "$PCFILE" hasfile c-ares/hdr-ares "$INC/ares.h" hasfile c-ares/hdr-build "$INC/ares_build.h" hasfile c-ares/hdr-dns "$INC/ares_dns.h" hasfile c-ares/hdr-dnsrec "$INC/ares_dns_record.h" hasfile c-ares/hdr-nameser "$INC/ares_nameser.h" hasfile c-ares/hdr-rules "$INC/ares_rules.h" hasfile c-ares/hdr-version "$INC/ares_version.h" # ares_build.h is GENERATED by this configure and describes the integer types # this arm actually has; the tarball ALSO ships a hand-written fallback of the # same name under include/, for build systems that cannot run configure. # Installing the wrong one would leave every consumer disagreeing with the # archive about ares_ssize_t and ares_socklen_t. # # THE DISCRIMINATOR IS THE FALLBACK'S OWN PROSE, not a #define: both files # define CARES_TYPEOF_ARES_SSIZE_T -- that is the whole point of the fallback -- # so a case asserting the macro was inert, and was measured to be, by swapping # the two headers and watching nothing go red. What only the fallback carries is # its instruction to ignore itself. run 20 grep -c 'run configure, and forget that this file' "$INC/ares_build.h" if [ "$rc" -eq 0 ]; then bst_notok c-ares/hdr-build-generated "the installed ares_build.h is the tarball's non-configure fallback, not this build's generated one" else bst_ok c-ares/hdr-build-generated fi # The pinned version, read out of the header the port installs. run 20 grep 'define ARES_VERSION_STR' "$INC/ares_version.h" has c-ares/version-header "\"$WANT_VERSION\"" "$out" # There is deliberately no c-ares/no-shared-object case: shared objects are # refused distro-wide on this arm, so an absent libcares.so is the distro's # property and not this port's, and every sibling library would assert it too. # --- the two tools ----------------------------------------------------------- # The stock recipe routes ${bindir} into c-ares-utils (PACKAGE_BEFORE_PN), so # these two arriving is also the on-image half of that packaging decision. # The bare name a user types must be this binary; `command -v` reads root's # actual PATH, so this also catches an image on which /usr/bin lost its place. # Skipped under a native control root, where the tools are deliberately not on # PATH and the question is meaningless. if [ -z "$R" ]; then check_eq c-ares/adig-on-path "$ADIG" "$(command -v adig 2>/dev/null)" check_eq c-ares/ahost-on-path "$AHOST" "$(command -v ahost 2>/dev/null)" else bst_skip c-ares/adig-on-path "native control root: PATH is not the subject" bst_skip c-ares/ahost-on-path "native control root: PATH is not the subject" fi # -h is answered inside the option loop, before any channel is created or any # socket opened, so a red here separates "the binary is broken" from every # resolver case below. run 30 "$ADIG" -h check_eq c-ares/adig-help-rc "0" "$rc" has c-ares/adig-help-version "adig version $WANT_VERSION" "$out" has c-ares/adig-help-usage "usage: adig" "$out" run 30 "$AHOST" -h check_eq c-ares/ahost-help-rc "0" "$rc" has c-ares/ahost-help-version "ahost, version $WANT_VERSION" "$out" has c-ares/ahost-help-usage "usage: ahost" "$out" # A validator that only ever says OK is not one. adig prints its usage and a # named diagnostic for an option it does not know. run 30 "$ADIG" -Z if [ "$rc" -eq 0 ]; then bst_notok c-ares/adig-rejects-unknown-option "accepted an unknown option -- $(_bst_1line "$out")" elif bst_execfail "$out"; then bst_unrun c-ares/adig-rejects-unknown-option "$out" else has c-ares/adig-rejects-unknown-option "unrecognized option: Z" "$out" fi # ahost's resolving path is NOT exercised here, and the reason is a missing # option rather than a missing capability: ahost takes -s but has no port flag # (usage above), so it can only ever query port 53 -- which this suite # deliberately does not use, see the header. adig covers the same channel # machinery with -U, and the probe covers ares_getaddrinfo, which is the entry # point ahost itself calls. # --- the server the resolver cases talk to ------------------------------------ if ! command -v dnsmasq >/dev/null 2>&1; then bst_skip c-ares/server-available "dnsmasq is not on this image, so no server could be started" exit 0 fi D=${TMPDIR:-/tmp}/bst-c-ares.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok c-ares/workdir "cannot create $D"; exit 0; } # no-resolv + no-hosts means there is no upstream and no /etc/hosts behind any # answer, so a reply can only have come from this host-record. -d is the only # mode that issues no credential syscall at all on this image -- 88-dnsmasq.test # carries that diagnosis (brintos/linux#19) and scores it; here it is a # precondition, not the subject. cat > "$D/dm.conf" < "$D/dm.log" 2>&1 & dpid=$! # Readiness comes from dnsmasq's own "started, version" line, not a fixed sleep: # it is logged after the listening sockets are bound, so no query can race it. i=0; up=0 while [ $i -lt 40 ]; do if grep -q "started, version" "$D/dm.log" 2>/dev/null; then up=1; break; fi kill -0 "$dpid" 2>/dev/null || break i=$((i+1)); sleep 1 done if [ "$up" -eq 1 ]; then bst_ok c-ares/server-started else bst_notok c-ares/server-started "no 'started, version' after ${i}s -- $(_bst_1line "$(cat "$D/dm.log" 2>/dev/null)")" fi # The proof that every answer below is LOCAL: with no --server and no # /etc/resolv.conf to read, dnsmasq has nowhere to forward and says so. if grep -q "no upstream servers configured" "$D/dm.log" 2>/dev/null; then bst_ok c-ares/server-offline else bst_notok c-ares/server-offline "expected the 'no upstream servers configured' warning -- $(_bst_1line "$(cat "$D/dm.log" 2>/dev/null)")" fi # --- adig: the resolver through the shipped TOOL ------------------------------ run 30 "$ADIG" -s 127.0.0.1 -U "$PORT" -t A "$NAME" if bst_execfail "$out"; then bst_unrun c-ares/adig-a-query "$out" bst_unrun c-ares/adig-a-answer "$out" bst_unrun c-ares/adig-a-noerror "$out" else check_eq c-ares/adig-a-query "0" "$rc" # The needle is built with a tab variable rather than written literally: adig # separates the presentation-format fields with tabs, and a literal tab in # this file is one editor away from becoming spaces and silently never # matching. has c-ares/adig-a-answer "IN${tab}A${tab}$ADDR" "$out" has c-ares/adig-a-noerror "status: NOERROR" "$out" has c-ares/adig-a-count "ANSWER: 1" "$out" fi # --host-record installs the PTR alongside the A, so the reverse direction goes # through the same server and a different parser inside c-ares. run 30 "$ADIG" -s 127.0.0.1 -U "$PORT" -t PTR 13.12.11.10.in-addr.arpa if bst_execfail "$out"; then bst_unrun c-ares/adig-ptr-answer "$out" else has c-ares/adig-ptr-answer "$NAME" "$out" fi # The complement: a name this server was never told about must not come back as # a configured address. What dnsmasq says about an unknown name with no upstream # is its own business, so only the ADDRESS is scored -- an assertion on the # rcode would be a red that reads like a lost packet. run 30 "$ADIG" -s 127.0.0.1 -U "$PORT" -t A nope.bst.test case "$out" in *"$ADDR"*) bst_notok c-ares/adig-unknown-name "an unconfigured name got the configured address -- $(_bst_1line "$out")" ;; *) bst_ok c-ares/adig-unknown-name ;; esac # The log is where an answer's PROVENANCE lives; an answer alone cannot say who # produced it. The literal is "config", not "reply": dnsmasq's log_query picks # the source word from the answer flags, and a --host-record hit is F_CONFIG. if grep -qF "config $NAME is $ADDR" "$D/dm.log" 2>/dev/null; then bst_ok c-ares/answered-from-config else bst_notok c-ares/answered-from-config "dnsmasq's log carries no 'config $NAME is $ADDR' line -- $(_bst_1line "$(grep -i "$NAME" "$D/dm.log" 2>/dev/null)")" fi # --- the library RUNNING, via the cross-linked probe -------------------------- # The probe is installed 0755 by the recipe and runs where it lies -- unlike the # data/ payloads next door, which arrive 0644 on a read-only rootfs and have to # be staged to /tmp first. hasfile c-ares/probe-payload "$PROBE" if [ -x "$PROBE" ]; then out=$("$PROBE" 127.0.0.1 "$PORT" "$NAME" "$ADDR" 2>&1) # the library's own identity, read from the linked archive rather than a header has c-ares/run-version "CARES-version OK" "$out" has c-ares/run-version-number "CARES-version-number OK" "$out" has c-ares/run-library-init "CARES-library-init OK" "$out" has c-ares/run-library-initialized "CARES-library-initialized OK" "$out" # --- the offline half: ares_parse_a_reply over a canned wire buffer has c-ares/run-parse-a-status "CARES-parse-a-status OK" "$out" has c-ares/run-parse-a-name "CARES-parse-a-name OK" "$out" has c-ares/run-parse-a-family "CARES-parse-a-family OK" "$out" has c-ares/run-parse-a-addrlen "CARES-parse-a-addrlen OK" "$out" has c-ares/run-parse-a-count "CARES-parse-a-count OK" "$out" has c-ares/run-parse-a-ttl-count "CARES-parse-a-ttl-count OK" "$out" has c-ares/run-parse-a-addr1 "CARES-parse-a-addr1 OK" "$out" has c-ares/run-parse-a-addr2 "CARES-parse-a-addr2 OK" "$out" has c-ares/run-parse-a-ttl1 "CARES-parse-a-ttl1 OK" "$out" has c-ares/run-parse-a-ttl2 "CARES-parse-a-ttl2 OK" "$out" has c-ares/run-parse-a-ttladdr1 "CARES-parse-a-ttladdr1 OK" "$out" has c-ares/run-parse-a-ttladdr2 "CARES-parse-a-ttladdr2 OK" "$out" # --- the offline half: ares_parse_ptr_reply over a canned wire buffer # run-parse-ptr-name pins an UPSTREAM QUIRK rather than an obvious value: # ares_parse_ptr_reply.c reassigns its `hostname` on every PTR record ("Save # most recent PTR record as the hostname") and strdups the survivor into # h_name after the loop, so h_name is the LAST record, not the first. A caller # that assumes the first gets the last, silently, for any address with more # than one PTR. Pinned so a release that changes the choice tells us. has c-ares/run-parse-ptr-status "CARES-parse-ptr-status OK" "$out" has c-ares/run-parse-ptr-name "CARES-parse-ptr-name OK" "$out" has c-ares/run-parse-ptr-family "CARES-parse-ptr-family OK" "$out" has c-ares/run-parse-ptr-alias-count "CARES-parse-ptr-alias-count OK" "$out" has c-ares/run-parse-ptr-alias1 "CARES-parse-ptr-alias1 OK" "$out" has c-ares/run-parse-ptr-alias2 "CARES-parse-ptr-alias2 OK" "$out" # --- the offline half's negative controls, and what makes the rest mean # something: a parser returning ARES_SUCCESS unconditionally passes none of # these. A truncated buffer and a header-only buffer must be refused, and a # well-formed response carrying only the OTHER record type must be # ARES_ENODATA rather than a silently empty success. has c-ares/run-parse-short-refused "CARES-parse-short-refused OK" "$out" has c-ares/run-parse-header-only-refused "CARES-parse-header-only-refused OK" "$out" has c-ares/run-parse-a-of-ptr-nodata "CARES-parse-a-of-ptr-nodata OK" "$out" has c-ares/run-parse-ptr-of-a-nodata "CARES-parse-ptr-of-a-nodata OK" "$out" # The offline half is a pure function of bytes compiled into the probe, so its # tally must be the native control's exactly. This is the one case that goes # red if a case were dropped from the probe without this suite noticing. has c-ares/run-parse-summary "CARES-PARSE-SUMMARY 26/26" "$out" # --- the resolver half, through ares_getaddrinfo against the dnsmasq above has c-ares/run-query-server-literal "CARES-query-server-literal OK" "$out" has c-ares/run-query-channel-init "CARES-query-channel-init OK" "$out" has c-ares/run-query-completed "CARES-query-completed OK" "$out" has c-ares/run-query-status "CARES-query-status OK" "$out" has c-ares/run-query-name "CARES-query-name OK" "$out" has c-ares/run-query-inet-count "CARES-query-inet-count OK" "$out" has c-ares/run-query-address "CARES-query-address OK" "$out" # SCORED CONDITIONALLY ON THE POSITIVE INSIDE THE PROBE, and that is the point: # "the lookup failed" is also what an unreachable server produces, so on its # own this case passes trivially in exactly the situation where it proves # nothing. Measured natively against a port with no server, the four positive # cases went red and this one stayed green until the probe was taught to # require the positive first. has c-ares/run-query-unknown-name-refused "CARES-query-unknown-name-refused OK" "$out" has c-ares/run-query-summary "CARES-QUERY-SUMMARY 8/8" "$out" # The probe's own tally and its exit status. 34/34 is what the same source # scores natively against a host build of the identical release driven at a # host dnsmasq. The status is three-valued on purpose -- 2 means the parsers # answered wrongly, 1 means only the resolver half failed -- so a red here # says WHICH half without reading the case list. has c-ares/run-summary "CARES-SUMMARY 34/34" "$out" check_rc c-ares/run-exit0 0 "$PROBE" 127.0.0.1 "$PORT" "$NAME" "$ADDR" else bst_notok c-ares/probe-runnable "$PROBE is not present and executable" fi rm -rf "$D"