. "$BST_LIB" # BST-TIMEOUT: 300 # ndisc6 1.0.8 -- Remi Denis-Courmont's IPv6 diagnostic set (meta-networking # recipes-support/ndisc6), ported under BRINTOS-684. Test-plan entry 556. Four # programs ship: ndisc6 (Neighbour Discovery), rdisc6 (Router Discovery), # rltraceroute6 (the tracer) and tcptraceroute6 (a front end that re-execs it). # # THIS SUITE HAS TWO HALVES AND THEY ARE SCORED SEPARATELY ON PURPOSE. # # * THE PROGRAMS' OWN MACHINERY -- do they start, parse arguments, print their # version and usage, and does the tcptraceroute6 -> rltraceroute6 exec chain # work? All of that is expected GREEN, and every case of it is a real # regression if it reds. # * WHAT THEY EXIST TO DO -- every socket this package opens is AF_INET6, and # this kernel is built `# CONFIG_IPV6 is not set`, so socket(2) refuses the # family outright with EAFNOSUPPORT (brintos/linux#54). The four functional # cases are therefore expected RED, each attributed to that issue, and each # flips green on its own the day CONFIG_IPV6 is enabled. Nothing here needs # changing for that to happen. # # THE FIRST HALF ONLY EXISTS BECAUSE OF THE PORT'S ONE PATCH, which is why # `ndisc6/starts-at-all` is not the trivial case it looks like. All four programs # are installed setuid-root for the raw socket and drop back to the invoking user # with setuid(getuid()) at the top of main(), exiting if it returns non-zero. # This kernel wires no credential setters at all (brintos/linux#19), so before # the patch every one of them exited 1 having printed NOTHING -- `ndisc6 # --version` included. A red on the four version/usage cases below, with the # output empty and rc=1, means that patch is not in the image. # # WHY THE SOCKET CASES ARE `not ok` AND NOT `skip`. `skip` is for a tool that is # legitimately absent. These tools are present and correct; the platform # withholds the address family. Per the project's partially-red rule that is a # landing result provided every red names its cause, and naming it is what # `ndisc6/socket-refusal-is-eafnosupport` does -- it asserts the tools' own # diagnostic reports the ADDRESS FAMILY as unsupported rather than, say, EPERM # from a capability model. That distinction is the ticket's second question, and # it is asked as a green case so a future kernel that gains IPv6 but still # refuses raw ICMPv6 reds HERE, differently, instead of being absorbed into #54. # # EVERY INVOCATION IS BOUNDED, and this is the same deliberate divergence from # the "do not bound a client with timeout(1)" rule that 88-traceroute.test # carries (test plan 86.3). Signal delivery is unreliable here # (brintos/linux#21), so a bound that must fire is a bound that cannot be # trusted -- but nothing below is expected to reach its bound. Each case exits in # well under a second, either on the socket refusal or on its own output, and the # bound exists only so that a future platform regression which swallows a probe # cannot eat the suite's budget and take the cases after it down. A case that # reports rc=124 is a red with its own cause, distinguished from the tool's # non-zero exits, and is exactly the "rather than hanging" half of the ticket's # criterion. require /usr/bin/ndisc6 ndisc6/present ND=/usr/bin/ndisc6 RD=/usr/bin/rdisc6 RL=/usr/bin/rltraceroute6 TT=/usr/bin/tcptraceroute6 # nb -- run bounded, capturing stdout+stderr; sets $out and $rc. nb() { _t=$1; shift; out=$(timeout "$_t" "$@" 2>&1); rc=$?; } # no_ipv6 -- true when the kernel carries no IPv6 stack at all. Used to ATTRIBUTE # a failure whose wording this suite does not recognise: on a kernel with no # AF_INET6, nothing that needs an IPv6 socket can succeed whatever it printed, so # the cause is #54 regardless of the message. It is deliberately a check of the # kernel and not of the message, because the messages come from four different # programs and one of them is socat's. no_ipv6() { [ ! -e /proc/net/if_inet6 ]; } # --- inventory --------------------------------------------------------------- # ndisc6-misc and ndisc6-rdnssd are deliberately NOT in EMCRAFT_TOOLS -- misc # carries an RDEPENDS on perl for two shell aliases, and rdnssd is a resolver # daemon for router advertisements this kernel cannot receive -- so asserting on # their files would red every build for a packaging split made on purpose. inv="" for f in "$ND" "$RD" "$RL" "$TT"; do [ -e "$f" ] || inv="$inv $f" done check_eq ndisc6/inventory "" "$inv" # --- the setuid bits the ticket asks about ----------------------------------- # The three raw-socket users install 4555 (r-sr-xr-x); tcptraceroute6 opens no # socket of its own and is an ordinary 0755. Read off `ls -l` rather than probed # with `test -x`, which on this image aborts the whole suite when it reaches a # credential lookup (brintos/linux#19). # # A red here is about the IMAGE, not about ndisc6: it means the setuid bit did # not survive packaging or the read-only virtio-fs root. It does not change what # the functional cases below report, because the guest runs as root and the bit # buys nothing there -- it is recorded because the ticket asks whether the # image's capability model lets these tools open their socket at all, and the # mode they carry is half of that answer. sm="" for f in "$ND" "$RD" "$RL"; do case "$(ls -l "$f" 2>/dev/null)" in -r-sr-xr-x*) ;; *) sm="$sm $f:$(ls -l "$f" 2>/dev/null | cut -d' ' -f1)" ;; esac done check_eq ndisc6/setuid-bits "" "$sm" # --- the programs start at all (see the header: this is the patch's case) ----- nb 20 "$ND" --version if [ "$rc" -eq 0 ]; then case "$out" in *"IPv6 Neighbor/Router Discovery userland tool 1.0.8"*) bst_ok ndisc6/starts-at-all ;; *) bst_notok ndisc6/starts-at-all "ran but the banner is not 1.0.8's: $(_bst_1line "$out")" ;; esac elif [ -z "$out" ] && [ "$rc" -eq 1 ]; then bst_notok ndisc6/starts-at-all "exited 1 with no output -- the privilege-drop guard fired, so the port's ENOSYS patch (brintos/linux#19) is not in this image" else bst_notok ndisc6/starts-at-all "rc=$rc $(_bst_1line "$out")" fi check_out ndisc6/rdisc6-starts "IPv6 Neighbor/Router Discovery userland tool 1.0.8" \ timeout 20 "$RD" --version check_out ndisc6/rltraceroute6-starts "TCP & UDP IPv6 traceroute tool 1.0.8" \ timeout 20 "$RL" --version # tcptraceroute6 is a separate binary whose whole job is to rewrite its argv and # execvp() rltraceroute6 out of its own directory. Asking it for a version is the # cheapest way to prove that chain runs on this arm: what comes back is # rltraceroute6's banner, not one of its own. check_out ndisc6/tcptraceroute6-execs-rltraceroute6 "TCP & UDP IPv6 traceroute tool 1.0.8" \ timeout 20 "$TT" -V # --- argument handling, which is the wasm-relevant part of "it runs" --------- # ndisc6 and rdisc6 are ONE SOURCE FILE built twice (-DRDISC), and the usage text # is the only thing that distinguishes the two binaries at runtime -- the version # banner is shared. So these two cases are what establish that the image ships # both programs and not the same one twice. check_out ndisc6/help-is-neighbour-discovery "Neighbor Discovery" timeout 20 "$ND" -h check_out ndisc6/rdisc6-help-is-router-discovery "Router Discovery" timeout 20 "$RD" -h nb 20 "$ND" --no-such-option if [ "$rc" -eq 2 ]; then case "$out" in *'Try "'*) bst_ok ndisc6/rejects-unknown-option ;; *) bst_notok ndisc6/rejects-unknown-option "rc=2 but no 'Try \"...\" for more information' line: $(_bst_1line "$out")" ;; esac else bst_notok ndisc6/rejects-unknown-option "want rc=2 from quick_usage, got rc=$rc $(_bst_1line "$out")" fi # --- the attribution control: WHY the functional cases below are red --------- # Green today, and it is the case a reader should look at first when one of the # four reds changes shape. `-1` takes the first answer, `-r 1` sends one # solicitation, `-w 200` waits 200 ms -- none of which is reached, because the # raw socket is refused before any of it. nb 20 "$ND" -1 -n -r 1 -w 200 ::1 lo sockdiag=$out case "$out" in *"Raw IPv6 socket"*"Address family not supported by protocol"*) bst_ok ndisc6/socket-refusal-is-eafnosupport ;; *"Raw IPv6 socket"*"Operation not permitted"*) bst_notok ndisc6/socket-refusal-is-eafnosupport "the family is supported and the socket is refused for PRIVILEGE (EPERM) -- that is a capability-model finding, not brintos/linux#54, and needs its own issue: $(_bst_1line "$out")" ;; *"Raw IPv6 socket"*) bst_notok ndisc6/socket-refusal-is-eafnosupport "raw socket refused with an errno this suite does not recognise -- attribute it before assuming #54: $(_bst_1line "$out")" ;; *) bst_notok ndisc6/socket-refusal-is-eafnosupport "no 'Raw IPv6 socket' diagnostic at all -- rc=$rc $(_bst_1line "$out")" ;; esac # The kernel's own side of the same statement, independent of anything ndisc6 # says. /proc/net/if_inet6 is created by the IPv6 stack; its absence is the # CONFIG_IPV6=n that #54 reports, and 88-inetutils asks the same question for # ping6. Checked rather than inferred, so that "the tools cannot open a socket" # and "the kernel has no IPv6" are two measurements and not one restated twice. if [ -e /proc/net/if_inet6 ]; then bst_ok ndisc6/kernel-has-ipv6 else bst_notok ndisc6/kernel-has-ipv6 "brintos/linux#54: /proc/net/if_inet6 absent -- the kernel is built CONFIG_IPV6=n, so there is no AF_INET6 for any of these tools to open" fi # --- the ticket's named criteria --------------------------------------------- # verdict # Adjudicates one run of a tool that needs the raw socket. The pass arm is what # the case asserts once the platform has IPv6; every other arm names a cause. verdict() { _id=$1; _pat=$2 if [ "$rc" -eq 124 ]; then bst_notok "$_id" "hung: no verdict within the bound, which is worse than a clean refusal -- $(_bst_1line "$out")" return fi if bst_execfail "$out"; then bst_unrun "$_id" "$out"; return; fi if printf '%s\n' "$out" | grep -qE "$_pat"; then if [ "$rc" -eq 0 ]; then bst_ok "$_id" else bst_notok "$_id" "reported correctly but exit status was $rc -- $(_bst_1line "$out")"; fi return fi case "$out" in *"Raw IPv6 socket"*"Address family not supported by protocol"*) bst_notok "$_id" "brintos/linux#54 (kernel CONFIG_IPV6=n): the tool ran and reported its own refusal -- $(_bst_1line "$out")" ;; "") bst_notok "$_id" "rc=$rc and no output at all -- if rc=1 this is the privilege-drop guard (brintos/linux#19) and the port's patch is missing" ;; *) if no_ipv6; then bst_notok "$_id" "brintos/linux#54 (kernel CONFIG_IPV6=n, /proc/net/if_inet6 absent): rc=$rc $(_bst_1line "$out")" else bst_notok "$_id" "rc=$rc $(_bst_1line "$out")" fi ;; esac } # ndisc6: "answers or times out with its documented exit code rather than # hanging". The documented codes are 0 when an advertisement arrives, 2 when none # does, 1 on error; a link-layer address on stdout is the answer. nb 30 "$ND" -1 -n -r 1 -w 1000 ::1 lo verdict ndisc6/loopback-neighbour '^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$' # rdisc6 solicits routers on an interface. There is no router on lo and there # never will be, so even with IPv6 this asks only that the solicitation goes out # and the tool reports its own documented "no response" rather than an error. nb 30 "$RD" -1 -n -r 1 -w 1000 lo if [ "$rc" -eq 124 ]; then bst_notok ndisc6/rdisc6-solicit "hung: no verdict within the bound -- $(_bst_1line "$out")" elif [ "$rc" -eq 0 ] || [ "$rc" -eq 2 ]; then bst_ok ndisc6/rdisc6-solicit else case "$out" in *"Raw IPv6 socket"*"Address family not supported by protocol"*) bst_notok ndisc6/rdisc6-solicit "brintos/linux#54 (kernel CONFIG_IPV6=n): $(_bst_1line "$out")" ;; *) if no_ipv6; then bst_notok ndisc6/rdisc6-solicit "brintos/linux#54 (kernel CONFIG_IPV6=n): want rc 0 (answered) or 2 (no response), got rc=$rc $(_bst_1line "$out")" else bst_notok ndisc6/rdisc6-solicit "want rc 0 (answered) or 2 (no response), got rc=$rc $(_bst_1line "$out")" fi ;; esac fi # rltraceroute6 and tcptraceroute6 to ::1. The ticket's criterion is one hop to a # listener on ::1; the pass pattern is the hop-1 line naming the loopback # address, in either of the two forms the tool prints it. nb 60 "$RL" -n -m 3 -q 1 -w 2 ::1 verdict ndisc6/rltraceroute6-loopback '^[[:space:]]*1[[:space:]]+(::1|0:0:0:0:0:0:0:1)' nb 60 "$TT" -n -m 3 -q 1 -w 2 ::1 80 verdict ndisc6/tcptraceroute6-loopback '^[[:space:]]*1[[:space:]]+(::1|0:0:0:0:0:0:0:1)' # --- the listener the criterion names, and why it cannot exist here ---------- # "tcptraceroute6 to a listener on ::1" needs something bound to ::1 first. On a # kernel with no AF_INET6 nothing on this image can create one, and recording # that from a SECOND program is what keeps the case above from resting on # ndisc6's own account of the platform. socat is used because it is already on # this image and reports the errno verbatim; if it ever leaves, this skips rather # than inventing a substitute. if command -v socat >/dev/null 2>&1; then nb 20 socat -u TCP6-LISTEN:38684,bind=[::1] /dev/null case "$out" in *"Address family not supported by protocol"*) bst_notok ndisc6/ipv6-listener "brintos/linux#54: socat cannot bind [::1]:38684 either, so the ticket's listener is unreachable for the platform's reason and not for this port's -- $(_bst_1line "$out")" ;; *) if [ "$rc" -eq 124 ] || [ "$rc" -eq 0 ]; then bst_ok ndisc6/ipv6-listener elif no_ipv6; then bst_notok ndisc6/ipv6-listener "brintos/linux#54 (kernel CONFIG_IPV6=n): socat refused the [::1] listener in wording this suite does not recognise, but the kernel has no IPv6 stack at all -- rc=$rc $(_bst_1line "$out")" else bst_notok ndisc6/ipv6-listener "rc=$rc $(_bst_1line "$out")"; fi ;; esac else bst_skip ndisc6/ipv6-listener "socat is not on this image, so the listener half of the criterion cannot be measured independently" fi # --- "rather than hanging", asked once about the package as a whole ---------- # Every bounded run above is expected to finish on its own. This case says so in # one place, because it is the half of the ticket's criterion that is about the # platform's liveness rather than about IPv6, and it must stay green even while # the four functional cases are red. if [ -n "$sockdiag" ]; then bst_ok ndisc6/refuses-promptly else bst_notok ndisc6/refuses-promptly "the raw-socket probe produced no diagnostic within its bound -- the tools are not failing fast" fi