. "$BST_LIB" # netcat is GNU Netcat 0.7.1 (meta-networking recipes-support). busybox ships an # nc applet at the SAME update-alternatives link name (/usr/bin/nc), so a bare # `nc` exists even on an image where this port failed to install -- `require` # below therefore only guards ABSENCE, and the identity case straight after it # is what proves priority 100 took the link. A present-but-busybox nc is # `not ok`, never `skip`. # # What the port ADDS is the LISTENER: this defconfig builds the busybox applet # with CONFIG_NC_SERVER and CONFIG_NC_EXTRA unset, so the applet can only # connect. `nc -l` is new capability, not a second spelling of an old one, and # section 6 of data/netcat-loopback.tcl is where that is proven. # # The machine has no route off loopback, so every transfer below runs against a # peer this suite stands up. See data/netcat-loopback.tcl for why peer and tool # share one process. require nc netcat/present # check_fail -- netcat 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 } # EVERY netcat invocation in this file gets an explicit empty stdin, via these # two wrappers. Not defensive style -- a hard requirement: given no hostname # netcat PROMPTS for its command line on stdin ("Cmd line: "), and once # connected it copies stdin to the socket. An invocation that inherits the # harness' stdin therefore does not fail, it HANGS, and this target has no # working relative timer to cut it loose (the runner's own /proc/uptime bound # would eventually kill the whole suite and lose every later case). This cost # the native calibration run twice before it was pinned. nc0() { nc "$@" &1); _rc=$? if [ "$_rc" -eq 1 ] && [ -z "$_out" ]; then bst_ok netcat/refused-quiet else bst_notok netcat/refused-quiet "rc=$_rc want=1 want-empty got:'$(_bst_1line "$_out")'"; fi check_fail netcat/outfile-unwritable 1 "Failed to open output file" \ nc0 -n -w 2 -o /nonexistent/dir/x 127.0.0.1 45999 # --- the resolver ------------------------------------------------------------- # These four are deliberate, not incidental. Every sibling suite on this image # skips name resolution against BRINTOS-77 ("traps in static-glibc NSS"), but # that trap was FIXED upstream on 2026-07-14 -- Joel's glibc dl-libc.c change # makes a libc-internal dlopen FAIL the load instead of trapping. So the `files` # backend, which is compiled into static glibc, resolves /etc/hosts names, and # the `dns` backend -- a libnss_dns.so this image does not carry -- fails # cleanly instead of taking the process down. Asserting both halves is what # keeps that fix from silently regressing. check_fail netcat/resolve-hosts-file 1 "localhost [127.0.0.1]" nc0 -v -w 2 localhost 45999 check_fail netcat/resolve-no-dns 1 "Couldn't resolve host" nc0 -v -w 2 nosuchhost.invalid 45999 # -n is numeric-only: it must REFUSE a name it could otherwise have resolved. check_fail netcat/resolve-numeric-only 1 "Couldn't resolve host" nc0 -v -n -w 2 localhost 45999 # getservbyname reads /etc/services (netbase). A port given by service name has # to come back as the number. `discard` (9) rather than `http` (80): nothing # ever listens on 9, so the case reads the same on a machine that happens to # run a web server. check_fail netcat/service-name 1 "9 (discard)" nc0 -v -n -w 2 127.0.0.1 discard # An address off lo. Only the STATUS is asserted, because the diagnostic is a # property of the environment rather than of netcat: this machine has lo and # nothing else, so connect(2) fails outright with "Network is unreachable", # while a host with a default route instead runs out the -w window. Either way # netcat must fail and RETURN -- not hang, which is the thing worth pinning. check_rc netcat/unroutable 1 nc0 -v -n -w 2 10.99.99.99 80 # --- the network path: transfers over lo ------------------------------------- if command -v tclsh >/dev/null 2>&1; then out=$(tclsh "$BST_DATA/netcat-loopback.tcl" 2>&1); rc=$? 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 payload only # prints if it got that far. case "$out" in *ALL-NETCAT-OK*) all=1 ;; *) all=0 ;; esac if [ "$rc" -eq 0 ] && [ "$all" -eq 1 ]; then bst_ok netcat/payload else bst_notok netcat/payload "rc=$rc $(_bst_1line "$out")"; fi # client -> peer, and the peer's bytes back out of netcat's stdout. check_eq netcat/client-rc 0 "$(n client-rc)" check_eq netcat/client-got "nc-client-ok" "$(n client-got)" check_eq netcat/reply-rc 0 "$(n reply-rc)" has netcat/reply-out "server-said-hi" "$(n reply-out)" # 4 KiB back, so the receive side is a read LOOP and not one short read. check_eq netcat/bigreply-len 4096 "$(n bigreply-len)" # -c is "close on EOF from stdin", and on an EMPTY stdin that EOF is # immediate -- so netcat may close before draining what the peer already # sent, losing the reply the case above asserts. Whether it does is a RACE # between the stdin EOF and select(2) reporting the socket readable: measured # BOTH ways on the target AND on a native x86_64 build of the same tarball. # So only the invariant is asserted -- netcat still exits 0 and does not hang # -- and the outcome is not, because a suite that asserted either answer # would fail at random. This is also why the reply case above does not use # -c, and why section 1 of the payload uses a peer that never writes: closing # with unread inbound data makes the close abortive (RST), which discards # netcat's own just-sent payload too. check_eq netcat/close-on-eof-rc 0 "$(n ceof-rc)" # -p and -s are the two ways to pin the LOCAL end of the connection; the peer # is the only place either is observable. check_eq netcat/local-port 1 "$(n lport-match)" check_eq netcat/source-addr "127.0.0.1" "$(n saddr-seen)" # binary transparency: all 256 byte values, byte-exact. This is the assertion # that separates a working data path from one that happens to carry text. check_eq netcat/binary-len 256 "$(n bin-len)" check_eq netcat/binary-head "0001020304050607" "$(n bin-head)" check_eq netcat/binary-tail "f8f9fafbfcfdfeff" "$(n bin-tail)" # -o writes a hexdump of the traffic beside the transfer. check_eq netcat/hexdump-rc 0 "$(n odump-rc)" has netcat/hexdump-line "Sent 13 bytes to the socket" "$(n odump-line1)" # THE LISTENER -- the capability busybox's applet does not have. check_eq netcat/listen-connected 1 "$(n listen-connected)" check_eq netcat/listen-got "nc-listen-ok" "$(n listen-out)" # A listener that has served its peer exits 1 upstream (measured identically # on a native x86_64 build of the same tarball), so 1 is the correct # expectation here and 0 would be the surprise. check_eq netcat/listen-rc 1 "$(n listen-rc)" # UDP, both directions, plus the one-peer semantics of a UDP listener. check_eq netcat/udp-client-rc 0 "$(n udp-c1-rc)" check_eq netcat/udp-got "nc-client-ok" "$(n udp-got)" check_eq netcat/udp-2nd-peer 1 "$(n udp-c2-rc)" # -e replaces netcat with the program, whose stdout IS the socket. It is a # bare execl(3) with no fork behind it, which is why it works at all on an arm # where popen(3) and system(3) are ENOSYS (brintos/linux#14) -- this is the # only shell-out on the image that does. check_eq netcat/exec-rc 0 "$(n exec-rc)" check_eq netcat/exec-got "nc-exec-ok" "$(n exec-got)" # ...but WHICH program runs is decided by argv[0]. netcat runs # `execl("/bin/sh", basename(PROGRAM), "-c", PROGRAM)`, and /bin/sh here is # busybox, a multi-call binary that dispatches on argv[0]. So a payload not # named `sh` never reaches the shell at all: busybox looks for an applet by # that name. (With `-e /bin/date` it FINDS one, and runs busybox date with # `-c` as an argument -- the failure mode is a wrong program, not an error.) # bash and dash ignore argv[0], so the same command works on a native host; # this is upstream netcat meeting busybox, not a wasm32 defect, and it would # reproduce on any busybox-/bin/sh distro. case "$(n sh)" in busybox*) check_eq netcat/exec-argv0-rc 127 "$(n execbad-rc)" has netcat/exec-argv0-applet "applet not found" "$(n execbad-out)$(n execbad-got)" ;; *) check_eq netcat/exec-argv0-rc 0 "$(n execbad-rc)" check_eq netcat/exec-argv0-applet "nc-exec-ok" "$(n execbad-got)" ;; esac # -z scanning: status is the contract, and it differs between a single port # and a range. check_eq netcat/scan-open-rc 0 "$(n scan-open-rc)" has netcat/scan-open-out "open" "$(n scan-open-out)" check_eq netcat/scan-closed-rc 1 "$(n scan-closed-rc)" check_eq netcat/range-open-rc 0 "$(n range-open-rc)" check_eq netcat/range-closed-rc 1 "$(n range-closed-rc)" # -v -v adds the byte counters; 13 is the payload this suite sent. has netcat/verbose-stats "Total sent bytes: 13" "$(n vv-out)" # -T answers TELNET negotiation; the payload carries none, so the peer must # still receive the plain bytes. check_eq netcat/telnet-rc 0 "$(n telnet-rc)" check_eq netcat/telnet-got "nc-client-ok" "$(n telnet-got)" else bst_skip netcat/payload "tclsh not installed; no peer on this image to talk to" fi # --- what this image cannot cover -------------------------------------------- if command -v busybox >/dev/null 2>&1; then # The applet on THIS image is built with CONFIG_NC_SERVER unset, so -l is not # in its option surface at all -- direct evidence that the listener proven # above is netcat's and not a capability the image already had. # # Read that off the USAGE TEXT, never by invoking `busybox nc -l`: a busybox # built WITH NC_SERVER would bind and block forever on it, with no timer on # this target to cut it loose. (That is not hypothetical -- it hung the native # control run this suite was calibrated against.) Where the applet does carry # -l the contrast does not hold, so the honest verb is skip, not a red. if busybox nc --help 2>&1 | grep -qE '(^|[^-[:alnum:]])-l([^-[:alnum:]]|$)'; then bst_skip netcat/busybox-cannot-listen "this busybox nc applet has -l (CONFIG_NC_SERVER set); the contrast is specific to this image's defconfig" else bst_ok netcat/busybox-cannot-listen fi else bst_skip netcat/busybox-cannot-listen "no busybox here to contrast the applet against" fi bst_skip netcat/off-lo "lo is the only interface and there is no route off it; no off-machine peer exists" bst_skip netcat/dns "the dns NSS backend needs a libnss_dns.so this image does not carry, and there is no /etc/resolv.conf and no route to a server" bst_skip netcat/tunnel "-L forwards a local port to a REMOTE address; with no route off lo there is nothing to tunnel to"