. "$BST_LIB" # BST-TIMEOUT: 300 # # ruli 0.36 (meta-openembedded meta-networking recipes-support) -- RULI, the # Resolver User Layer Interface: a DNS stub resolver built on liboop's event # loop and specialised in SRV lookup (RFC 2782), plus eight sample query # drivers. # # WHAT SHIPS, AND WHY THAT SHAPES THIS SUITE # # /usr/lib/libruli.a, 23 headers, eight programs in /usr/bin and the man pages. # There is no libruli.so: this arm is static-only (conf/distro/brintos.conf # sets BRINTOS_PACKAGE_SHARED = "0" and -lfoo resolves only to libfoo.a), so # the port builds the archive alone and the suite asserts the archive. A case # looking for libruli.so would be asserting something the image is designed not # to have. # # Every one of the eight programs is a thin main() that reads names on stdin # and asks a REAL nameserver. That makes them almost useless as coverage here: # there is no resolver on this image to reach, and an answer from the outside # world would not be ours to assert. What the port actually delivers is the # library, and a stub resolver's failure mode is answering WRONGLY rather than # crashing -- a label walk off by one, a 16-bit count read little-endian, an # RFC 2782 weight ladder that always picks the first target. Every one of those # compiles, packages and installs perfectly; BRINTOS-217 is the precedent this # layer keeps citing, where oniguruma did exactly that on this arm and returned # silently wrong offsets to every caller. # # So data/ruli-probe is cross-linked with wasmcc-glibc against THE VERY # ARCHIVES THAT SHIP (/usr/lib/libruli.a and liboop.a) and driven here. Its # source is src/ruli_probe.c. It runs in ONE process on ONE liboop event loop: # the stub nameserver is a UDP socket bound on 127.0.0.1:15353 and registered # on that same loop, so there is no fork and no scheduling race, and the # resolver's nameserver list comes from a pluggable ruli_conf_handler_t rather # than /etc/resolv.conf. Nothing outside loopback is contacted, so the result # does not depend on the machine's network. # # dname-* name encoding and decoding. dname-encode-wire pins the label # encoding octet by octet (3 www 7 example 3 com 0), which is what # separates a real length-prefixed walk from a routine that copied # the text through. dname-encode-toolong pins the REFUSAL of a # 200-octet label -- the range check whose absence is how a hostile # answer reads past a buffer. # # msg-* a SRV query composed with ruli_msg_build, its 12-octet header and # question section asserted verbatim, parsed back with # ruli_msg_parse_header field by field, then re-composed and # compared byte for byte. msg-build-header pins the ID as # big-endian on the wire, which a same-endian round trip through # parse_header alone would not catch. # # parse-* a 311-octet answer built by the probe (four SRV records and an A # record per target, no compression) parsed with # ruli_parse_message. Each SRV record's priority, weight and port # are asserted in the order the wire carries them, and each A # record's address is rendered back. # # srv-* the same answer served over loopback and resolved for real, so # the SRV path runs end to end: submit, receive, parse, RFC 2782 # target selection, address attachment. # # THE RAW TALLY IS NOT ASSERTED, and the reason is worth stating # because the obvious expectation is wrong. ISAAC is integer-only # and ruli_rand_init() seeds it from a zeroed state with a # constant, so the raw stream IS identical on every host -- but # ruli_rand_next() then scales the draw through SINGLE PRECISION, # `(float)(1.0 + max - min) * u / (float)(ISAAC_UB4MAXVAL + min)', # where u is a full 32-bit unsigned and a float carries 24 bits of # mantissa. A draw sitting on a bucket boundary can therefore fall # either side of it depending on how the compiler evaluates the # expression. Measured, over the same 60 queries: x86_64/gcc # 41/11/8/0, wasm32/clang 41/10/9/0 -- one draw of sixty, same # total, same priority ordering. This suite pinned the exact counts # on its first draft and that case, and only that case, went red on # the first target run. # # So what is asserted is the specification's own properties, which # are arm-independent: the priority-20 target is never chosen while # a priority-10 target is available (srv-lowprio-first, the case a # weight-only ladder fails); the weight-60 target wins more often # than either weight-20 target (srv-weight-order); both lighter # targets are still reached (srv-weight-spread, which separates a # real weighted draw from one pinned to the head of the band); and # the heavy target's share sits near 60/100 of the rounds # (srv-weight-band). Determinism is asserted too, and portably: # srv-tally-repeat compares two tallies taken on two FRESH # resolvers, which must agree because each ruli_res_t restarts the # same ISAAC stream -- that pins the seeding without pinning the # arithmetic. The counts themselves are printed for diagnosis. # # nx-* / nxzone-* / timeout-* # the three documented negative paths, which ruli does not # conflate. RFC 2782 says a client that finds no SRV records falls # back to an address query on the domain, and ruli does -- so the # caller sees the FALLBACK's outcome. nx-* is an SRV NXDOMAIN whose # domain exists but has no address (FALL_EMPTY, 16); nxzone-* is a # zone where the fallback is refused too, so the DNS RCODE survives # to the caller (FALL_RCODE, 17, rcode 3); timeout-* is a query the # stub receives and deliberately never answers (ALARM, 2), with the # drop counted on the stub side so a case that passed by never # sending anything is distinguishable from a real timeout. # # EVERY EXPECTED VALUE IS THE VALUE A NATIVE x86_64 BUILD OF THE IDENTICAL # RELEASE PRODUCES -- same tarball (sha256 11d32def..dbfa), meta-oe's same five # patches plus this port's three, built with the same RULI_SHARED=no. That # control was run twice and was byte-identical both times. Every one of its # values also matched on wasm32 first time, with the single exception of the # raw RFC 2782 tally discussed above. # # It was then MUTATION-TESTED rather than merely run. The control arm is green # and each mutation reddens where it should: # # M1 query ID written little-endian -> msg-build-header, msg-hdr-id # (and every resolver case, since # the stub matches on the ID) # M2 SRV rdata priority/weight swapped -> parse-srv0..3, srv-first-* # M3 RFC 2782 draw always yields 0 -> srv-weight-spread and # srv-weight-band (the tally goes # 60/0/0/0), the degenerate # always-pick-the-first ladder. # Note srv-weight-order still # passes there, which is why the # spread and band cases exist. # M4 all SRV priorities forced to 10 -> parse-srv3 alone # M5 encoded size short by the root label-> dname-encode-size alone # # M4 is worth reading carefully: it reddens parse-srv3 and does NOT redden # srv-lowprio-first, because the fourth target also carries weight 0 and RFC # 2782 puts zero-weight entries at the tail of their band, so 60 draws never # reached it anyway. srv-lowprio-first is therefore exercised but only weakly # pinned -- it would catch a resolver that ignored priority AND weight order, # not one that ignored priority alone. # # WHY BUILTIN MATCHING: the probe runs ONCE and its output is read into one # shell variable; every assertion is a `case` glob against it, so there is no # fork per case. On this runtime the forking is the cost, not the work. lib=/usr/lib/libruli.a inc=/usr/include nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } hasexec() { if [ -x "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or not executable: $2"; fi; } # eqline -- ok iff carries it as a # WHOLE line. A failure reports the line the probe ACTUALLY printed for that # key, not just the expectation: without it every difference reads the same and # the value has to be recovered by re-running the probe by hand on a guest. # The fork is on the failure path only, so the green path stays fork-free. eqline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "want: $2 -- got: $(printf '%s\n' "$3" | grep -m1 "^${2%% *} " || echo '(key absent)')" ;; esac } # --- the shipped payload ------------------------------------------------------ hasfile ruli/archive "$lib" hasfile ruli/header-ruli "$inc/ruli.h" hasfile ruli/header-srv "$inc/ruli_srv.h" hasfile ruli/header-msg "$inc/ruli_msg.h" hasfile ruli/header-parse "$inc/ruli_parse.h" hasfile ruli/header-res "$inc/ruli_res.h" # There is deliberately NO libruli.so on this image -- see the header above. if [ -e /usr/lib/libruli.so ] || [ -e /usr/lib/libruli.so.4 ]; then bst_notok ruli/no-shared "a shared libruli exists; this arm is static-only" else bst_ok ruli/no-shared fi # --- the eight sample drivers ------------------------------------------------- for p in srvsearch sync_srvsearch smtpsearch sync_smtpsearch \ httpsearch sync_httpsearch ruli-getaddrinfo getaddrinfo; do hasexec "ruli/bin-$p" "/usr/bin/$p" done # --- the library, exercised --------------------------------------------------- tmp=${TMPDIR:-/tmp} probe=$tmp/bst-ruli-probe.$$ if cp "$BST_DATA/ruli-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then out=$("$probe" 2>&1) # the probe reached its own end; without this a truncated run would make # every case below fail for one reason and report it as many eqline ruli/probe-end "probe-end ok" "$out" # name encoding eqline ruli/dname-encode-size "dname-encode-size 17" "$out" eqline ruli/dname-encode-len "dname-encode-len 17" "$out" eqline ruli/dname-encode-wire "dname-encode-wire 03777777076578616d706c6503636f6d00" "$out" eqline ruli/dname-decode "dname-decode www.example.com." "$out" eqline ruli/dname-decode-len "dname-decode-len 16" "$out" eqline ruli/dname-encode-toolong "dname-encode-toolong refused" "$out" # query composition eqline ruli/msg-build-len "msg-build-len 35" "$out" eqline ruli/msg-build-header "msg-build-header 123401000001000000000000" "$out" eqline ruli/msg-build-question "msg-build-question 055f74657374045f74637002657803636f6d0000210001" "$out" eqline ruli/msg-hdr-id "msg-hdr-id 4660" "$out" eqline ruli/msg-hdr-flags "msg-hdr-flags 256" "$out" eqline ruli/msg-hdr-qdcount "msg-hdr-qdcount 1" "$out" eqline ruli/msg-hdr-ancount "msg-hdr-ancount 0" "$out" eqline ruli/msg-rebuild-bytes "msg-rebuild-bytes same" "$out" # answer parsing, offline eqline ruli/parse-fixture-len "parse-fixture-len 311" "$out" eqline ruli/parse-ancount "parse-ancount 4" "$out" eqline ruli/parse-arcount "parse-arcount 4" "$out" eqline ruli/parse-answer-list "parse-answer-list 4" "$out" eqline ruli/parse-additional "parse-additional-list 4" "$out" eqline ruli/parse-srv0 "parse-srv0 10 60 5060" "$out" eqline ruli/parse-srv1 "parse-srv1 10 20 5061" "$out" eqline ruli/parse-srv2 "parse-srv2 10 20 5062" "$out" eqline ruli/parse-srv3 "parse-srv3 20 0 5063" "$out" eqline ruli/parse-srv-count "parse-srv-count 4" "$out" eqline ruli/parse-a0 "parse-a0 192.0.2.10" "$out" eqline ruli/parse-a3 "parse-a3 192.0.2.13" "$out" # the resolver, over loopback eqline ruli/stub-bind "stub-bind ok" "$out" eqline ruli/res-new "res-new ok" "$out" eqline ruli/res-servers "res-servers 1" "$out" eqline ruli/srv-code "srv-code 0" "$out" eqline ruli/srv-entries "srv-entries 4" "$out" eqline ruli/srv-first-prio "srv-first-prio 10" "$out" eqline ruli/srv-first-port "srv-first-port 5060" "$out" eqline ruli/srv-first-target "srv-first-target a.ex.com" "$out" eqline ruli/srv-first-naddr "srv-first-naddr 1" "$out" eqline ruli/srv-first-addr "srv-first-addr 192.0.2.10" "$out" # RFC 2782 weighted selection. The raw counts are NOT asserted -- see the # header: ruli_rand_next() scales the ISAAC draw through single precision, so # one draw of sixty falls the other side of a bucket boundary on wasm32 than # on x86_64. What is asserted is the specification's properties plus the # determinism of the seeding, all of which are arm-independent. eqline ruli/srv-tally-repeat "srv-tally-repeat same" "$out" eqline ruli/srv-tally-total "srv-tally-total 60" "$out" eqline ruli/srv-lowprio-first "srv-lowprio-first 0" "$out" eqline ruli/srv-weight-order "srv-weight-order ok" "$out" eqline ruli/srv-weight-spread "srv-weight-spread ok" "$out" eqline ruli/srv-weight-band "srv-weight-band ok" "$out" # the negative paths eqline ruli/nx-code "nx-code 16" "$out" eqline ruli/nx-code-name "nx-code-name fall-empty" "$out" eqline ruli/nxzone-code "nxzone-code 17" "$out" eqline ruli/nxzone-code-name "nxzone-code-name fall-rcode" "$out" eqline ruli/nxzone-rcode "nxzone-rcode 3" "$out" eqline ruli/timeout-code "timeout-code 2" "$out" eqline ruli/timeout-code-name "timeout-code-name alarm" "$out" eqline ruli/timeout-dropped "timeout-dropped 1" "$out" eqline ruli/stub-queries "stub-queries 127" "$out" eqline ruli/stub-dropped "stub-dropped 1" "$out" else bst_notok ruli/probe-stage "cannot stage $BST_DATA/ruli-probe" fi rm -f "$probe"