. "$BST_LIB" # BST-TIMEOUT: 600 # traceroute 2.1.5 -- Dmitry Butskoy's "modern traceroute for Linux" # (meta-networking recipes-support/traceroute), the classic hop-by-hop path # tracer. NOT GNU inetutils' traceroute, which this image also ships and which # 88-inetutils covers; the two are different programs and this suite is # accountable for exactly one of them. # # WHICH ONE OWNS /usr/bin/traceroute IS THE POINT OF THE FIRST TWO CASES, not # bookkeeping. Both packages register the same update-alternatives name, and the # bare `traceroute` on $PATH resolves by priority: 100 here against inetutils' # 79. So this port is expected to win the link -- and every functional case # below runs the CONCRETE FILE /usr/bin/traceroute.traceroute rather than the # bare name, so that a future package outbidding us produces a red on the # symlink case instead of silently re-pointing this whole suite at a different # program. # # WHY EVERY INVOCATION IS WRAPPED IN `timeout`. traceroute's whole job is to # wait for replies that may never arrive: a probe that draws nothing costs -w # seconds, and the default 30 hops x 3 probes is minutes of dead air before it # gives up. Where a platform gap swallows the replies, an unbounded case would # eat the suite's 600 s budget and take the cases after it down with it. The # bound is therefore part of the assertion, not a safety net bolted on: a case # that trips it reports rc=124 and is a red with a named cause. require /usr/bin/traceroute.traceroute traceroute/present TR=/usr/bin/traceroute.traceroute TCPTR=/usr/bin/tcptraceroute # tb -- run bounded, capturing stdout+stderr; sets $out and $rc. # `timeout` returns 124 on expiry, which every caller below distinguishes from # the tool's own non-zero exits. tb() { _t=$1; shift; out=$(timeout "$_t" "$@" 2>&1); rc=$?; } # trace_verdict -- adjudicate one trace attempt against the # ticket's criterion: the trace COMPLETES and REPORTS THE LOOPBACK HOP. Both # halves are required. A run that prints the header and then three asterisks has # "completed" in the sense of exiting 0, and is precisely the failure this # checks for -- the probes went out and nothing came back. trace_verdict() { _id=$1; _o=$2; _rc=$3 if [ "$_rc" -eq 124 ]; then bst_notok "$_id" "timed out with no verdict: probes drew no reply within the bound -- $(_bst_1line "$_o")" return fi if bst_execfail "$_o"; then bst_unrun "$_id" "$_o"; return; fi # The hop line is " ms ...". Anchoring on hop 1 carrying the # loopback address is the criterion stated literally; a bare substring match # would also pass on the header line, which names 127.0.0.1 before a single # probe has been sent. if printf '%s\n' "$_o" | grep -qE '^[[:space:]]*1[[:space:]]+127\.0\.0\.1'; then if [ "$_rc" -eq 0 ]; then bst_ok "$_id" else bst_notok "$_id" "hop 1 reported correctly but exit status was $_rc -- $(_bst_1line "$_o")"; fi elif printf '%s\n' "$_o" | grep -qE '^[[:space:]]*1[[:space:]]+\*'; then bst_notok "$_id" "no reply to any probe -- hop 1 is '*', so the probe left but nothing came back on the receive path -- rc=$_rc $(_bst_1line "$_o")" else bst_notok "$_id" "rc=$_rc no hop line at all: $(_bst_1line "$_o")" fi } # --- inventory: what the package put on the image ----------------------------- # traceroute-doc is not in EMCRAFT_TOOLS, so traceroute.8 is deliberately NOT # expected here -- asserting on it would red every build for a packaging split # this image makes on purpose. inv="" for f in "$TR" "$TCPTR" /usr/bin/traceroute; do [ -e "$f" ] || inv="$inv $f" done check_eq traceroute/inventory "" "$inv" # --- the alternative resolves to this port, not inetutils' -------------------- # Priority 100 (this recipe) against 79 (poky's inetutils). Asserted rather than # assumed: it is the only thing that makes the bare `traceroute` a user types # mean this package, and a silent flip is otherwise invisible. check_eq traceroute/alternative-is-ours "traceroute.traceroute" \ "$(readlink /usr/bin/traceroute 2>/dev/null)" # inetutils' traceroute must still be reachable under its own name -- the two # coexist by design, and 88-inetutils asserts against this exact path. check_eq traceroute/inetutils-still-present "yes" \ "$([ -x /usr/bin/traceroute.inetutils ] && echo yes)" # --- the binary runs ---------------------------------------------------------- tb 20 "$TR" --version check_eq traceroute/version "0" "$rc" case "$out" in *"version 2.1.5"*) bst_ok traceroute/version-is-2.1.5 ;; *) bst_notok traceroute/version-is-2.1.5 "want 'version 2.1.5', got '$(_bst_1line "$out")'" ;; esac # --- the probe modules registered --------------------------------------------- # Each mod-*.c registers itself through TR_MODULE(), an __attribute__((constructor)) # that runs before main and links the module onto a list. Nothing in the build # fails if those constructors never run -- traceroute would simply report that # every method is unknown at runtime. So this case is the wasm-specific one in # the suite: it proves .init_array actually executed in the guest, by asking the # tool to select a module by name and observing that it did not reject it. tb 20 "$TR" -M nosuchmethod -m 1 -q 1 127.0.0.1 case "$out" in *"nosuchmethod"*|*"Unknown"*|*"unknown"*|*"bad module"*) bst_ok traceroute/rejects-unknown-module ;; *) bst_notok traceroute/rejects-unknown-module "expected a complaint naming the bogus method -- rc=$rc $(_bst_1line "$out")" ;; esac # --- THE TICKET'S NAMED CRITERION --------------------------------------------- # "A trace to 127.0.0.1 completes and reports the loopback hop -- deterministic # and offline, with no external peer." # # The default method is UDP probes to an unlikely high port, with the ICMP # replies collected off the socket's ERROR QUEUE (IP_RECVERR + recvmsg # MSG_ERRQUEUE). That is a different receive path from the raw ICMP socket # inetutils' traceroute uses, which is why this case can fail on an image where # 88-inetutils' traceroute-loopback passes -- the two exercise different halves # of the stack, and this one is the modern default a user gets. # # -m 5 bounds the walk (the destination is one hop away; 30 would only lengthen # a failure), -n keeps DNS out of it, -w 2 caps the per-probe wait. tb 90 "$TR" -n -m 5 -w 2 127.0.0.1 trace_verdict traceroute/loopback-default "$out" "$rc" # --- the other two methods a user reaches for --------------------------------- # -I is ICMP ECHO on a raw socket: the same receive path inetutils' traceroute # uses, so it isolates "the ICMP machinery works" from "the error queue works". # Running both is what turns a red above into a located one. tb 90 "$TR" -I -n -m 5 -w 2 127.0.0.1 trace_verdict traceroute/loopback-icmp "$out" "$rc" # -T is a TCP SYN to port 80, again read back off the error queue. Nothing is # listening on 80 here, which is fine: the RST/unreachable that comes back is # still a reply from the loopback hop and is what the tool reports. tb 90 "$TR" -T -n -m 5 -w 2 127.0.0.1 trace_verdict traceroute/loopback-tcp "$out" "$rc" # --- the wrapper the package also installs ------------------------------------ # tcptraceroute is a /bin/sh wrapper that re-invokes traceroute with -T -O info. # It is packaged beside the binary and is the one shipped file that is not the # binary, so a case that never ran it would leave it entirely unexercised. tb 90 "$TCPTR" -n -m 5 -w 2 127.0.0.1 trace_verdict traceroute/tcptraceroute-wrapper "$out" "$rc" # --- argument handling -------------------------------------------------------- # A name that cannot resolve must fail cleanly rather than hang or trace # something else. This also exercises the resolver path the numeric cases above # deliberately avoid. tb 30 "$TR" -n -m 1 -q 1 no-such-host.invalid if [ "$rc" -eq 124 ]; then bst_notok traceroute/unresolvable-host-fails "hung instead of failing: $(_bst_1line "$out")" elif [ "$rc" -ne 0 ]; then bst_ok traceroute/unresolvable-host-fails else bst_notok traceroute/unresolvable-host-fails "exited 0 on an unresolvable name -- $(_bst_1line "$out")" fi