. "$BST_LIB" # BST-TIMEOUT: 900 # data/netcat-openbsd-loopback.sh forks TWO nc processes per # # section over seven sections, and a client that retries # # until the listener's bind lands forks more. Nothing here # # waits on a clock -- the driver's own bounds are a packet # # count, an EOF and /proc/uptime -- so the cost is exec # # latency alone, and the bound is sized for a guest under # # memory pressure rather than for the 2.1s an x86_64 control # # run of the same driver takes. NOT MEASURED ON TARGET YET: # # this port's first on-target run is the batch verify, and # # the measured elapsed replaces this note in the test plan # # rather than in this comment, which would cost an image # # build to keep the shipped suite byte-identical. # # netcat-openbsd 1.195 (meta-networking recipes-support/netcat, # netcat-openbsd_1.195.bb -- the Debian packaging of OpenBSD's nc, `inherit # pkgconfig' over netcat.inc's `inherit update-alternatives gettext pkgconfig'). # The OpenBSD rewrite of netcat: the general-purpose TCP and UDP plumbing tool. # # ============================================================================ # THIS PORT DOES NOT WIN THE `nc' NAME, AND THAT IS THE CORRECT OUTCOME. # ============================================================================ # # BRINTOS-1885 asks this suite to assert "that the `nc' alternative resolves to # it". It does not and it must not. Three providers register the SAME # update-alternatives link /usr/bin/nc on this image: # # busybox 50 (the connect-only applet) # netcat-openbsd 60 (this port -- netcat.inc, ALTERNATIVE_PRIORITY 60) # netcat 100 (GNU Netcat 0.7.1 -- BRINTOS's earlier port) # # 100 is the highest, so /usr/bin/nc is GNU netcat's and stays GNU netcat's. # Asserting the ticket's version of it would be a case that fails by # construction on every green image, and "fixing" it by raising this recipe's # priority would silently re-point /usr/bin/nc under 61-netcat.test, whose # identity cases assert "The GNU Netcat" from a bare `nc'. So the contract # asserted below is the one the packaging actually states: this port is # REGISTERED at 60, is REACHABLE at its own name, and LOSES the link to the # higher priority -- and the loss is asserted directly (alt-loses-to-gnu, # nc-is-gnu) rather than left as an absence, because a future recipe bump that # raised the priority would otherwise take /usr/bin/nc away from GNU netcat with # nothing on this image saying so. # # Everything below therefore drives /usr/bin/nc.netcat-openbsd BY ITS OWN PATH. # A suite that said `nc' would be testing GNU netcat and passing. # # ============================================================================ # WHY THE NETWORK HALF IS A DRIVER AND NOT THIS FILE. # ============================================================================ # # Every transfer case needs two processes alive at once, and this machine has no # route off loopback and no working relative timer -- sleep is a no-op and # timeout(1) never fires -- so a backgrounded listener plus a client is a race # the shell cannot close by waiting. data/netcat-openbsd-loopback.sh closes it # structurally instead (the client is retried until connect(2) stops being # refused, which is exactly when the bind landed) and terminates every section # on an EOF, a packet count or /proc/uptime rather than on a clock. It is run # under bst_probe, so a driver that wedged anyway is a red with its last line # quoted, never a suite that stops mid-transcript. # # EVERY VALUE THE DRIVER SECTION ASSERTS WAS MEASURED, on x86_64, against THIS # EXACT DRIVER and a native build of the same patched 1.195 tarball (three # consecutive runs, identical, 2.1s each, recorded 2026-09-01 and kept in the # ticket's design doc). They are a control, not a prediction: what rides through # the loopback path is a byte count and a byte-for-byte comparison, and neither # depends on pointer width, byte order or the platform string. require nc.netcat-openbsd netcat-openbsd/present # check_fail -- nc signals through its exit # STATUS as much as its diagnostic, and check_out cannot express it: it requires # rc 0. check_fail() { _id=$1; _want=$2; _needle=$3; shift 3 _out=$("$@" 2>&1); _rc=$? case "$_out" in *"$_needle"*) _hit=1 ;; *) _hit=0 ;; esac if [ "$_rc" -eq "$_want" ] && [ "$_hit" -eq 1 ]; then bst_ok "$_id" else bst_notok "$_id" "rc=$_rc want=$_want needle:'$_needle' got:'$(_bst_1line "$_out")'"; fi } # has -- substring assertion over a value already read. has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$2' got:'$(_bst_1line "$3")'" ;; esac } NCO=/usr/bin/nc.netcat-openbsd # Explicit empty stdin on every argv-only invocation. nc exits before reading # stdin on all of them, so this is belt-and-braces rather than load-bearing -- # but a suite that inherits the harness' stdin and meets a version that DOES # read it does not fail, it hangs, and there is no timer here to cut it loose. nco() { "$NCO" "$@" /dev/null) has netcat-openbsd/dpkg-status "Package: netcat-openbsd" "$st" has netcat-openbsd/dpkg-version "Version: 1.195" "$st" # The whole payload is ONE file. That is not a formality: the recipe's do_install # installs exactly ${S}/nc as ${bindir}/nc.${BPN} and nothing else -- no man page # (the Debian packaging's nc.1 is not installed), no /etc, no data. A release or # a recipe change that grew a second file would still pass every behavioural case # below while the packaging this image ships had changed shape, and only this # case would notice. # The three directories the list names first -- /., /usr, /usr/bin -- carry no # trailing slash in dpkg's format, so nothing in the spelling separates them # from the payload; the filesystem is asked instead. The tmpfs copy is what # makes the `done < "$LL"' safe to run in this shell (brintos/linux#73). LL=$(bst_local $PKGLIST) payload= while IFS= read -r p; do [ -n "$p" ] || continue [ -d "$p" ] && continue payload="$payload$p " done < "$LL" files=$(printf '%s' "$payload" | grep -c .) check_eq netcat-openbsd/dpkg-one-file 1 "$files" check_eq netcat-openbsd/dpkg-names-binary "$NCO" "$(printf '%s' "$payload" | head -1)" # ...and it is NOT /usr/bin/nc. update-alternatives owns that path; a package # that shipped it directly would fight the alternatives system for it. if grep -qx '/usr/bin/nc' "$LL" 2>/dev/null; then bst_notok netcat-openbsd/dpkg-no-bare-nc "netcat-openbsd.list names /usr/bin/nc, which update-alternatives owns" else bst_ok netcat-openbsd/dpkg-no-bare-nc fi # --- the alternatives contract ------------------------------------------------ # The admin file's first line is the LINK; every later line is ` ' # (99-opkg-utils-alts.test pins that format from the tool's own side). ALT=/usr/lib/opkg/alternatives/nc if [ -s "$ALT" ]; then alt=$(cat "$ALT") check_eq netcat-openbsd/alt-link "/usr/bin/nc" "$(head -1 "$(bst_local $ALT)")" has netcat-openbsd/alt-registered "$NCO 60" "$alt" # GNU netcat's own entry, read from the same file: it is what this port loses # to, and asserting it here means a red names the reason rather than just the # symptom. has netcat-openbsd/alt-gnu-outranks "/usr/bin/nc.netcat 100" "$alt" else bst_notok netcat-openbsd/alt-link "$ALT is absent or empty: update-alternatives did not record this provider at rootfs assembly" bst_notok netcat-openbsd/alt-registered "$ALT unreadable" bst_notok netcat-openbsd/alt-gnu-outranks "$ALT unreadable" fi # The link itself. 100 beats 60, so /usr/bin/nc must NOT be this port's binary. tgt=$(readlink /usr/bin/nc 2>/dev/null) case "$tgt" in "") bst_notok netcat-openbsd/alt-loses-to-gnu "/usr/bin/nc is not a symlink, so update-alternatives did not place it" ;; *nc.netcat-openbsd) bst_notok netcat-openbsd/alt-loses-to-gnu "/usr/bin/nc resolves to '$tgt' -- this port took the link from GNU netcat at priority 100" ;; *nc.netcat) bst_ok netcat-openbsd/alt-loses-to-gnu ;; *) bst_notok netcat-openbsd/alt-loses-to-gnu "want '*nc.netcat' got '$(_bst_1line "$tgt")'" ;; esac # Stated a second way, from the running program rather than from the filesystem: # -V is a GNU netcat option that this port does not accept at all, so the string # coming back from a bare `nc' is the assertion. check_out netcat-openbsd/nc-is-gnu "The GNU Netcat" sh -c "nc -V &1" # --- the CLI surface, driven by this port's own path ------------------------- check_out netcat-openbsd/usage-line "usage: nc [-46CDdFhklNnrStUuvZz]" nco -h # No destination is an ERROR here, not a prompt -- which is the first visible # difference from GNU netcat, whose bare invocation reads a command line off # stdin and blocks a harness forever. check_fail netcat-openbsd/no-args 1 "usage: nc" nco check_fail netcat-openbsd/bad-option 1 "invalid option" nco --nosuchopt # The six options the network half below depends on. A version bump that dropped # any of them would otherwise show up as an unexplained transfer failure. check_out netcat-openbsd/opt-listen "-l Listen mode, for inbound connects" nco -h check_out netcat-openbsd/opt-udp "-u UDP mode" nco -h check_out netcat-openbsd/opt-zero "-z Zero-I/O mode" nco -h check_out netcat-openbsd/opt-wait "-w timeout" nco -h check_out netcat-openbsd/opt-shutdown "-N Shutdown the network socket after EOF on stdin" nco -h check_out netcat-openbsd/opt-recvlimit "-W recvlimit" nco -h # --- the network half: nc against itself over 127.0.0.1 ---------------------- work=${TMPDIR:-/tmp}/bst-nco-suite.$$ mkdir -p "$work" bst_probe netcat-openbsd/driver "$work/drv" 600 sh "$BST_DATA/netcat-openbsd-loopback.sh" drc=$? if [ "$drc" -eq 0 ]; then out=$(cat "$work/drv") n() { printf '%s\n' "$out" | awk -v k="N-$1" '$1==k{sub(/^[^ ]+ /,"");print;exit}'; } # whole-payload gate first: everything after it reads values the driver only # prints if it got that far. case "$out" in *ALL-NCO-OK*) all=1 ;; *) all=0 ;; esac check_eq netcat-openbsd/driver-complete 1 "$all" # 1. client -> listener. The listener EXITS rather than lingering, because the # client's -N shuts the socket down on stdin EOF; `exited' is that assertion # and is the difference between a listener that returned and one the driver # had to kill. check_eq netcat-openbsd/tcp-client-rc 0 "$(n tcp-client-rc)" check_eq netcat-openbsd/tcp-listen-exits exited "$(n tcp-listen-reaped)" check_eq netcat-openbsd/tcp-listen-rc 0 "$(n tcp-listen-rc)" check_eq netcat-openbsd/tcp-payload same "$(n tcp-payload)" check_eq netcat-openbsd/tcp-payload-len 18 "$(n tcp-payload-len)" # 2. the other direction, which is a different code path and not a symmetry: # here -N is on the LISTENER and the client's stdin is empty. check_eq netcat-openbsd/rev-client-rc 0 "$(n rev-client-rc)" check_eq netcat-openbsd/rev-listen-exits exited "$(n rev-listen-reaped)" check_eq netcat-openbsd/rev-listen-rc 0 "$(n rev-listen-rc)" check_eq netcat-openbsd/rev-out "nc-openbsd-reply-ok" "$(n rev-out)" # 3. 4 KiB the same way, so the receive side is a read LOOP rather than one # short read. check_eq netcat-openbsd/big-client-rc 0 "$(n big-client-rc)" check_eq netcat-openbsd/big-payload same "$(n big-payload)" check_eq netcat-openbsd/big-len 4096 "$(n big-len)" # 4. binary transparency: all 256 byte values, byte-exact. This is what # separates a working data path from one that happens to carry text -- NUL, # the 0x80-0xff range and a bare CR all ride through it. check_eq netcat-openbsd/bin-client-rc 0 "$(n bin-client-rc)" check_eq netcat-openbsd/bin-payload same "$(n bin-payload)" check_eq netcat-openbsd/bin-len 256 "$(n bin-len)" # 5. UDP, both ends -u. The listener is bounded by -W 1 -- a packet COUNT, not # a clock -- because UDP has no EOF for -N to signal on and this target has no # timer that would fire. check_eq netcat-openbsd/udp-client-rc 0 "$(n udp-client-rc)" check_eq netcat-openbsd/udp-listen-exits exited "$(n udp-listen-reaped)" check_eq netcat-openbsd/udp-listen-rc 0 "$(n udp-listen-rc)" check_eq netcat-openbsd/udp-payload same "$(n udp-payload)" check_eq netcat-openbsd/udp-out "nc-openbsd-udp-ok" "$(n udp-out)" # 6. -z scanning. The status is the contract and the diagnostic is the second # half of it; the open port is one this run stood up and the closed one is a # port no section of the driver ever binds. check_eq netcat-openbsd/scan-open-rc 0 "$(n scan-open-rc)" has netcat-openbsd/scan-open-out "succeeded!" "$(n scan-open-out)" check_eq netcat-openbsd/scan-closed-rc 1 "$(n scan-closed-rc)" has netcat-openbsd/scan-closed-out "Connection refused" "$(n scan-closed-out)" # 7. -w against a closed port RETURNS rather than blocking, and says nothing # without -v. On loopback the connect is refused outright rather than running # the -w window down, so what is pinned is that -w does not turn a refusal # into a wait -- a tool that printed anyway would pass the case above and fail # this one. check_eq netcat-openbsd/w-closed-rc 1 "$(n w-quiet-rc)" check_eq netcat-openbsd/w-closed-quiet "" "$(n w-quiet-out)" else for c in driver-complete tcp-client-rc tcp-listen-exits tcp-listen-rc \ tcp-payload tcp-payload-len rev-client-rc rev-listen-exits \ rev-listen-rc rev-out big-client-rc big-payload big-len \ bin-client-rc bin-payload bin-len udp-client-rc udp-listen-exits \ udp-listen-rc udp-payload udp-out scan-open-rc scan-open-out \ scan-closed-rc scan-closed-out w-closed-rc w-closed-quiet; do bst_notok "netcat-openbsd/$c" "the loopback driver did not complete (rc=$drc); see netcat-openbsd/driver" done fi rm -rf "$work" # --- what this image cannot cover -------------------------------------------- bst_skip netcat-openbsd/off-lo "lo is the only interface and there is no route off it; every peer this suite talks to is one it stood up itself" bst_skip netcat-openbsd/proxy "-X/-x drive a SOCKS or CONNECT proxy, and there is no proxy on this image and no route to one" bst_skip netcat-openbsd/ipv6 "-6 needs an IPv6 address on lo; this image configures 127.0.0.1/8 only" bst_skip netcat-openbsd/tls "this recipe applies Debian's build-without-TLS-support.patch, so the -c/-H/-R TLS surface is not compiled in and there is nothing here to assert"