. "$BST_LIB" # BST-TIMEOUT: 180 # DECLARED, not measured -- this suite has never run ON TARGET: # # phase A of a batched Epic builds at recipe scope and cannot # # boot an image. (Groups B and C were run host-side; see below.) ~12 execs of a 1.25 MB wasm binary plus a handful of # # shell fixtures; nothing here waits on a network, since every # # invocation ends in the option parser, in the config parser, # # or at the netfilter table. Sized between 88-igmpproxy's # # measured 90 (53 cases, a 0.3 MB daemon) and 99-iptables' # # 300 (a 9.4 MB binary). Re-declare it from the ==BST-SUITE== # # line of the first real run. # miniupnpd 2.1.20191006 (meta-networking recipes-connectivity/miniupnpd) -- the # UPnP IGD daemon. What ships is /usr/sbin/miniupnpd, the five netfilter helper # scripts and the sample miniupnpd.conf under /etc/miniupnpd/, and an # /etc/init.d/miniupnpd script. # # WHAT THIS MACHINE CAN AND CANNOT TEST. miniupnpd exists to program a NAT: it # advertises an internet gateway over SSDP and installs the DNAT rules UPnP # clients ask for, through libiptc. init() reaches, in this order (miniupnpd.c): # read the config file, parse argv, daemonize (skipped under -d), openlog, # checkforrunning, install signal handlers, init_redirect(), writepidfile(), # reload_from_lease_file(). Everything up to and including the signal handlers is # reachable on this image and is scored below BY VALUE. init_redirect() is not, # for a reason that is the platform's and not this port's, and it is scored # separately and attributed. # # THERE IS NO NETFILTER TABLE ON THIS KERNEL. netfilter/iptcrdr.c's # init_redirect() is `iptc_init("nat")' and nothing else -- getsockopt(SO_GET_INFO) # on a raw socket. arch/wasm32/configs/wasm32_defconfig sets CONFIG_NET=y and no # CONFIG_NETFILTER at all, so ip_tables is never built and there is nothing to # ask; 99-iptables.test measures the same wall as "can't initialize iptables # table `filter': Table does not exist". brintos/linux#86 # (https://brintos.io/brintos/linux/issues/86). # # AND THAT IS THE POINT OF THIS PORT'S CRITERION, NOT AN OBSTACLE TO IT. A daemon # that cannot get its redirection engine must SAY SO and RETURN -- the failure # this suite is written to catch is a miniupnpd that stays resident with no way # to program anything, or that dies without naming what it could not open. The # absence is asserted, in miniupnpd's own words, under a bound. # # THE ORDER OF init() IS WHY TWO SURFACES THE TICKET NAMES ARE NOT REACHED HERE, # AND EACH IS SCORED AS ITS OWN CONSEQUENCE RATHER THAN LEFT UNSAID: # * the external interface. main() calls getifaddr(ext_if_name) and logs # "Cannot get IP address for ext interface %s. Network is down" -- but that is # AFTER init() returns, and init() returns non-zero at init_redirect(). The # netfilter wall is hit first, so this image has never reached the interface # lookup. Case D06. # * the lease file. reload_from_lease_file() is three statements after # init_redirect() in the same function, so ENABLE_LEASEFILE is reachable here # only as far as the config parser -- `lease_file=' is accepted as a known # key (C06) and nothing ever opens it (D07). # Both flip by themselves the day #86 lands. Neither is a defect in this port. # # THE COMPILED FEATURE SET IS ASSERTED THROUGH THE PARSER'S OWN OPTION TABLE, AND # THAT IS GROUP C's NEGATIVE CONTROL. options.c builds `optionids[]' behind the # same #ifdefs config.h sets, so a key's acceptance or rejection is a direct # readout of what genconfig.sh was told. # # WHAT genconfig.sh WAS TOLD IS NOT WHAT THE RECIPE APPEARS TO SAY, AND THIS # SUITE HAS BEEN WRONG ABOUT IT TWICE -- ONCE IN EACH DIRECTION. The recipe # passes `${IPV6} --leasefile --vendorcfg', where IPV6 is # `bb.utils.contains('DISTRO_FEATURES', 'ipv6', '--ipv6', '', d)'. Read the # distro file and you conclude ipv6 is off; read the generated local.conf and, up # to BRINTOS-2246, you found `DISTRO_FEATURES:append = " ipv6"' put there by # scripts/build-image.sh, behind the distro file. The first draft of this suite # took the distro file and was wrong; the second took the generated config and was # right for that build. Neither is a way to answer the question. THE BINARY IS. # # WHERE IT STANDS NOW, MEASURED IN THE SHIPPED wasm BINARY (2026-09-06, # usr/sbin/miniupnpd out of build-BRINTOS-2246/machine-stage): # # absent : [-I ext_ifname6] <- print_usage, #ifdef ENABLE_IPV6 # absent : ext_ifname6, ipv6_listening_ip <- optionids[], same #ifdef # absent : "as valid IPv6 listening address" # absent : "please specify LAN network interface by name instead of IPv4 address" # absent : "has no IPv4 address" <- the tolerant unknown-iface arm # PRESENT : "can't parse \"%s\" as a valid LAN address or interface name" # PRESENT : "Error parsing address/mask (or interface name)" # PRESENT : [-N] [-i ext_ifname] <- ENABLE_NATPMP, unchanged # absent : [-H https_port] <- ENABLE_HTTPS, unchanged # # So ENABLE_IPV6 is OFF. BRINTOS-2246 removed build-image.sh's append, the recipe # now passes no `--ipv6', and the seven cases that rested on the ON reading have # been re-measured against this binary and inverted: miniupnpd/help-ipv6, # conf-good-no-invalid, the two ipv6 key cases (now negative controls in bad.conf), # conf-unknown-lan-iface and its paired arm, and the ipv4-literal case, which # reverses outright. Do not re-derive the answer from a config file; run the # `strings' readout above against the binary this image ships. # # So this build is: ENABLE_LEASEFILE, ENABLE_MANUFACTURER_INFO_CONFIGURATION, # ENABLE_NATPMP, ENABLE_PCP, ENABLE_EVENTS and ENABLE_UPNPPINHOLE on; ENABLE_IPV6, # ENABLE_HTTPS and IGD_V2 off. The accepted keys are `lease_file' and # `friendly_name'; the four that must be REJECTED are `https_port' (ENABLE_HTTPS), # `force_igd_desc_v1' (IGD_V2) and `ext_ifname6'/`ipv6_listening_ip' (ENABLE_IPV6), # and they are what keeps the group from passing against a parser that accepted # everything. # # THE FIREWALL BACKEND IS NOT CHOSEN BY AN OPTION EITHER. The recipe passes no # `--firewall=', and genconfig.sh's generic Linux arm would then PROBE THE BUILD # HOST -- `command -v nft' -- and pick nftables where nft is installed, which it is # on this one. What makes the result deterministic is meta-networking's own # 0001-Add-OpenEmbedded-cross-compile-case.patch: it adds an `OpenEmbedded)' case # that sets `FW=iptables' outright, and the recipe's do_configure:prepend writes # ${S}/os.openembedded to select it. The nft probe is therefore never reached. # Verified in the binary, which links libiptc (`iptables-1.8.10/libiptc/libiptc.c', # `%s() : iptc_init() failed : %s') and carries no nftables backend. # # THE SHIPPED CONFIG IS NOT AT THE COMPILED-IN DEFAULT PATH, AND B10 SAYS SO. # miniupnpd.c defines DEFAULT_CONFIG as "/etc/miniupnpd.conf"; the recipe installs # upstream's sample at /etc/miniupnpd/miniupnpd.conf. A bare `miniupnpd' therefore # finds no configuration, which is upstream OE's packaging and is left alone -- # every case here that needs a config passes -f. Asserting the default path is # what keeps that gap visible rather than folklore. # # GROUPS B, C AND THE SHAPE OF D WERE MEASURED BEFORE THIS SUITE EVER BOOTED, ON # A HOST-SIDE BUILD, AND THAT IS WHY SIX ASSERTIONS ABOVE READ THE WAY THEY DO. # Phase A of a batched Epic cannot boot an image, but it can build the SAME source # with the SAME config.h natively: miniupnpd 2.1.20191006 with meta-networking's # 0001-Add-OpenEmbedded-cross-compile-case.patch applied, `os.openembedded' in # place, CONFIG_OPTIONS="--ipv6 --leasefile --vendorcfg", against a libiptc built # from the same iptables-1.8.10 this image carries -- config.h coming out with # `USE_IPTABLES 1' and `#define ENABLE_IPV6', matching the target build. Every # case in groups B and C was run against that binary on 2026-09-02 and holds as # written, including the fixture line numbers. # # That is CORROBORATION OF THE PARSER AND OPTION SURFACES, NOT A TARGET RESULT. # The parser, the option switch and print_usage are the same C compiled the same # way, so what they print and return is portable. What is NOT portable is group # D's cause: natively `iptc_init("nat")' fails with `Permission denied (you must # be root)', while on this image it fails because there is no ip_tables at all. # The two produce the same two lines -- `iptc_init() failed' and `Failed to init # redirection engine. EXITING' -- which is what group D asserts, but only the # target run establishes WHY. Nothing here substitutes for that. # # EVERY GROUP-E EXPECTATION IS A PREDICTION READ OFF THE SHELL SOURCE, NOT A # MEASUREMENT. The two helper scripts source miniupnpd_functions.sh, which resolves # `ip' and `iptables' through which(1) and then reads the four chain states through # iptables. On this image each of those iptables calls fails at the same table, so # both scripts are expected to run to completion having changed nothing and to # report the absent backend on stderr. That reasoning is derived; the first real # run is what confirms or refutes it. bst_ok miniupnpd/suite-canary # first line: absorbs brintos/linux#96, which drops it MU=/usr/sbin/miniupnpd ETC=/etc/miniupnpd require "$MU" miniupnpd/present work=${TMPDIR:-/tmp}/bst-miniupnpd.$$ if ! mkdir -p "$work" 2>/dev/null; then bst_notok miniupnpd/workdir "cannot create $work -- the config fixtures below have nowhere to live" exit 0 fi bst_ok miniupnpd/workdir # rb : a bounded run, output and status in $out / $rc. The same # helper 88-igmpproxy.test uses, and for the same reason -- every case here needs # both halves and several read one run many ways. rb() { bst_run "$@"; } # lib/bst.sh: a bound that FIRES # (timeout(1) did not, before the # brintos/linux#77 fix) and a file capture # no wedged child can hold open. has() { case "$2" in *"$1"*) return 0 ;; *) return 1 ;; esac; } # --- A: inventory -- what the package put on the image ------------------------ check_eq miniupnpd/inventory "yes" "$([ -x "$MU" ] && echo yes)" # The bare name a user types must be this binary; `command -v' reads root's real # PATH, so this also catches an image where /usr/sbin is not on it. check_eq miniupnpd/on-path "$MU" "$(command -v miniupnpd 2>/dev/null)" # The five netfilter helpers and the sample config are a do_install list in the # recipe, one `install' line each, so any one of them can go missing on its own # without the binary noticing. The ip6tables pair ships even though this build has # no IPv6 arm -- upstream installs it unconditionally, and that is asserted rather # than assumed so a future --ipv6 build is not read as having added them. for f in iptables_init.sh iptables_removeall.sh ip6tables_init.sh \ ip6tables_removeall.sh miniupnpd_functions.sh; do check_eq "miniupnpd/helper/$f" "yes" "$([ -x "$ETC/$f" ] && echo yes)" done check_eq miniupnpd/conf-installed "yes" "$([ -r "$ETC/miniupnpd.conf" ] && echo yes)" # Upstream's sample, every setting commented out -- so nothing in it is live. check_eq miniupnpd/conf-is-upstream-sample "yes" \ "$(grep -q '^#ext_ifname=' "$ETC/miniupnpd.conf" 2>/dev/null && echo yes)" check_eq miniupnpd/initscript "yes" "$([ -x /etc/init.d/miniupnpd ] && echo yes)" # do_install writes ${systemd_unitdir}/system/miniupnpd.service unconditionally; # systemd.bbclass's rm_systemd_unitdir is what removes it again, DISTRO_FEATURES # being empty here. Asserted on the image so a regression shows up as a red rather # than as a packaging QA failure nobody reads. check_eq miniupnpd/no-systemd-unit "yes" \ "$([ ! -e /lib/systemd/system/miniupnpd.service ] && echo yes)" # --- B: version, usage and argument handling ---------------------------------- # # main() answers `--version' from a loop over argv that runs BEFORE init(), so # this is the one invocation that touches neither the config file nor a socket. rb 20 "$MU" --version ver_out=$out if [ "$rc" -eq 124 ]; then bst_notok miniupnpd/version-rc "miniupnpd --version did not return within 20 s (rc=124)" elif [ "$rc" -eq 0 ]; then bst_ok miniupnpd/version-rc elif bst_execfail "$ver_out"; then bst_unrun miniupnpd/version-rc "$ver_out" else bst_notok miniupnpd/version-rc "want rc=0 (the --version arm returns 0 from main); got rc=$rc -- $(_bst_1line "$ver_out")"; fi # MINIUPNPD_VERSION is `cat VERSION' at genconfig time, so this pins the tarball # the image carries to the one this port was written against. check_eq miniupnpd/version-string "yes" \ "$(has "miniupnpd 2.1.20191006" "$ver_out" && echo yes)" # `-h' PRINTS THE HELP AND RETURNS 1, AND BOTH HALVES ARE ASSERTED. main() opens # with a dedicated scan -- `/* only print usage if -h is used */ for(i=1;iifname[0] != '\0') { ... if_nametoindex ... } # #ifdef ENABLE_IPV6 # else { fprintf(stderr, "Error: please specify LAN network interface by name # instead of IPv4 address : %s\n", str); return -1; } # #else # else { syslog(LOG_NOTICE, "it is advised to use network interface name ..."); } # #endif # # -- so a LITERAL ADDRESS, which is what this fixture used to carry, is ACCEPTED # with a nag when ENABLE_IPV6 is off and REJECTED outright when it is on. This # build has it on (see the header). With the literal, no lan_addr is added, init() # takes `goto print_usage' at the `!lan_addrs.lh_first' check and returns 1 # WITHOUT EVER REACHING init_redirect() -- which would have made every case in # group D below measure the wrong thing, two of them going falsely GREEN on this # port's headline criterion by finding no iptc_init failure to report. The # interface-name form reaches init_redirect(), which is what group D is about. # # The rejection of the literal form is not discarded; it is asserted on its own, # with its own message, as C12 below. good=$work/good.conf printf '%s\n' \ '# a comment' \ 'ext_ifname=lo' \ 'listening_ip=lo' \ "lease_file=$work/leases" \ 'friendly_name=brintOS selftest' \ 'allow 1024-65535 192.168.1.0/24 1024-65535' \ 'deny 0-65535 0.0.0.0/0 0-65535' > "$good" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$good" good_out=$out; good_rc=$rc if bst_execfail "$good_out"; then bst_unrun miniupnpd/conf-good-ran "$good_out" rm -rf "$work" exit 0 fi # The one case that must be true before any of the rest means anything: the run # happened and returned. rc=124 is reported here rather than absorbed, because a # daemon that did not return is precisely what D01 exists to catch. if [ "$good_rc" -eq 124 ]; then bst_notok miniupnpd/conf-good-ran "miniupnpd did not return within 30 s over a well-formed config -- every case below reads this run's output, so they establish nothing" else bst_ok miniupnpd/conf-good-ran fi # A well-formed file draws NEITHER diagnostic. Both are asserted: "invalid # option" is the unknown-key arm and "parsing error" the malformed-line arm, and # a parser that had lost one of them would still pass a test that checked only # the other. check_eq miniupnpd/conf-good-no-invalid "yes" \ "$(has "invalid option in file" "$good_out" || echo yes)" check_eq miniupnpd/conf-good-no-parse-error "yes" \ "$(has "parsing error file" "$good_out" || echo yes)" # `lease_file' and `friendly_name' are in optionids[] only under ENABLE_LEASEFILE # and ENABLE_MANUFACTURER_INFO_CONFIGURATION, which is what --leasefile and # --vendorcfg select. Their silent acceptance above is the positive half; the # negative half is C08-C10 below. check_eq miniupnpd/conf-leasefile-key-known "yes" \ "$(has "invalid option in file $good line 4" "$good_out" || echo yes)" check_eq miniupnpd/conf-vendorcfg-key-known "yes" \ "$(has "invalid option in file $good line 5" "$good_out" || echo yes)" # `ext_ifname6' and `ipv6_listening_ip' USED TO BE LINES 8 AND 9 OF THIS FIXTURE, # asserted as accepted keys. They are in optionids[] only under ENABLE_IPV6, which # this build no longer sets, so they are unknown keys now and have moved to # bad.conf as negative controls -- the parser-side half of what the usage string # reports at miniupnpd/help-ipv6. Leaving them here is what made # miniupnpd/conf-good-no-invalid red, since a well-formed file drew two # "invalid option" lines. bad=$work/bad.conf printf '%s\n' \ 'bogus_key=1' \ 'this line has no equals sign' \ 'allow zzz 1 2' \ 'https_port=443' \ 'force_igd_desc_v1=yes' \ 'ext_ifname=lo' \ 'listening_ip=lo' \ 'ext_ifname6=lo' \ 'ipv6_listening_ip=::1' > "$bad" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$bad" bad_out=$out if bst_execfail "$bad_out"; then bst_unrun miniupnpd/conf-bad-ran "$bad_out" else bst_ok miniupnpd/conf-bad-ran # An unknown key is DIAGNOSED AND SKIPPED, not fatal: readoptionsfile prints and # `continue's, and returns 0. The case asserts the message and deliberately does # not assert a status -- treating this as a rejection would be asserting a # parser upstream does not have. check_eq miniupnpd/conf-unknown-key "yes" \ "$(has "invalid option in file $bad line 1 : bogus_key=1" "$bad_out" && echo yes)" # No '=' at all: strchr returns NULL and the line is a parse error, quoted back # whole. The trailing text is what tells this case apart from C04 below, since # both produce the same "parsing error file" prefix. check_eq miniupnpd/conf-no-equals "yes" \ "$(has ": this line has no equals sign" "$bad_out" && echo yes)" # A permission line is parsed by read_permission_line(), which wants # (allow|deny) eport[-eport] ip/mask iport[-iport]; `zzz' fails at the first # isdigit and the whole line comes back in the diagnostic. check_eq miniupnpd/conf-malformed-allow "yes" \ "$(has ": allow zzz 1 2" "$bad_out" && echo yes)" # THE NEGATIVE CONTROL, and the reason group C means anything. Both keys are # documented in upstream's own miniupnpd.conf and are in optionids[] only behind # ENABLE_HTTPS and IGD_V2, neither of which this build sets. A parser that # accepted every name would pass C02 and C06 and fail here. # # `ext_ifname6' and `ipv6_listening_ip' are the other two, added by BRINTOS-2246 # when the ipv6 append came out of the generated local.conf. They have been in # both files across this suite's history -- accepted keys in good.conf while # ENABLE_IPV6 was on, controls here now that it is off -- so read them together # with the header's strings readout rather than as a settled fact. check_eq miniupnpd/conf-rejects-https-key "yes" \ "$(has "invalid option in file $bad line 4 : https_port=443" "$bad_out" && echo yes)" check_eq miniupnpd/conf-rejects-igdv2-key "yes" \ "$(has "invalid option in file $bad line 5 : force_igd_desc_v1=yes" "$bad_out" && echo yes)" check_eq miniupnpd/conf-rejects-ipv6-ifname-key "yes" \ "$(has "invalid option in file $bad line 8 : ext_ifname6=lo" "$bad_out" && echo yes)" check_eq miniupnpd/conf-rejects-ipv6-listening-key "yes" \ "$(has "invalid option in file $bad line 9 : ipv6_listening_ip=::1" "$bad_out" && echo yes)" fi # The interface-name branch of parselanaddr(), taken successfully. `lo' is the # only interface this image has, the kernel auto-assigns it 127.0.0.1/8, and # getifaddr() reads it with SIOCGIFFLAGS + SIOCGIFADDR -- the same two ioctls # 60-net-tools.test scores green through ifconfig. Asserted as the POSITIVE # control for the two cases below: without it, a getifaddr() that failed for every # name would pass them just as well. # # Under ENABLE_IPV6 a getifaddr() failure here is NOT fatal -- the arm prints # `interface "%s" has no IPv4 address', zeroes the address and carries on -- so # the failure to catch is that message, not a parse error. good.conf reaches # init_redirect() through this same branch, so group D rests on this case. withif=$work/withif.conf printf '%s\n' 'ext_ifname=lo' 'listening_ip=lo' > "$withif" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$withif" if bst_execfail "$out"; then bst_unrun miniupnpd/conf-lan-iface-by-name "$out" elif has "interface \"lo\" has no IPv4 address" "$out"; then bst_notok miniupnpd/conf-lan-iface-by-name "getifaddr(\"lo\") failed, so listening_ip=lo resolved to no IPv4 address on this image: rc=$rc $(_bst_1line "$out")" elif has "Usage:" "$out"; then bst_notok miniupnpd/conf-lan-iface-by-name "no lan_addr was added from listening_ip=lo, so init() reached print_usage: rc=$rc $(_bst_1line "$out")" else bst_ok miniupnpd/conf-lan-iface-by-name fi # A LAN interface that does not exist. parselanaddr() takes the non-digit branch # and getifaddr() fails, and with ENABLE_IPV6 OFF that is FATAL, not tolerated: # the `#else' arm prints `Error parsing address/mask (or interface name) : # bstnosuchif0' and returns -1, so no lan_addr is added and init() falls through # the `!lan_addrs.lh_first' check to print_usage. # # THIS CASE HAS BEEN WRITTEN BOTH WAYS. It asserted this arm, then the tolerant # ENABLE_IPV6 arm (`interface "bstnosuchif0" has no IPv4 address', address set to # 0.0.0.0/32, parselanaddr returning 0 and the run continuing to init_redirect), # and now this one again -- because BRINTOS-2246 took the ipv6 append out of the # generated local.conf. Measured 2026-09-06: `has no IPv4 address' is absent from # the shipped binary and `Error parsing address/mask' is present, so the tolerant # arm is not merely unused here, it is not compiled in. nolan=$work/nolan.conf printf '%s\n' 'ext_ifname=lo' 'listening_ip=bstnosuchif0' > "$nolan" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$nolan" nolan_out=$out if bst_execfail "$nolan_out"; then bst_unrun miniupnpd/conf-unknown-lan-iface "$nolan_out" elif [ "$rc" -eq 1 ] && has "Error parsing address/mask (or interface name) : bstnosuchif0" "$nolan_out"; then bst_ok miniupnpd/conf-unknown-lan-iface else bst_notok miniupnpd/conf-unknown-lan-iface "want rc=1 and the ENABLE_IPV6-off arm naming bstnosuchif0; got rc=$rc -- $(_bst_1line "$nolan_out")"; fi # ...and it is fatal rather than merely diagnosed: the run MUST stop at # print_usage, which is what tells this apart from the ENABLE_IPV6 tolerant arm. # Asserted through `Usage:', the same readout miniupnpd/conf-requires-listening-ip # below uses for the other route into print_usage. check_eq miniupnpd/conf-unknown-lan-iface-fatal "yes" \ "$(has "Usage:" "$nolan_out" && echo yes)" # THE LITERAL ADDRESS IS THE FORM THIS BUILD ACCEPTS, and it is the exact mirror # of the case above: with ENABLE_IPV6 on, parselanaddr() takes the address through # inet_aton(), finds ifname empty, and the `#ifdef ENABLE_IPV6' arm demands a name # ("please specify LAN network interface by name instead of IPv4 address", a hard # -1); with it off, the `#else' arm keeps the address and returns 0. Measured # 2026-09-06: that demand string is ABSENT from the shipped binary. # # So the assertion is that the address is ACCEPTED, and the only way to see that # from outside is that the run got PAST init() -- it must not reach print_usage. # It still exits 1, because init_redirect() then fails on brintos/linux#86; that # is asserted at miniupnpd/netfilter-table and deliberately not re-asserted here, # so this case keeps saying something about the parser once #86 lands. litaddr=$work/litaddr.conf printf '%s\n' 'ext_ifname=lo' 'listening_ip=127.0.0.1/8' > "$litaddr" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$litaddr" litaddr_out=$out if bst_execfail "$litaddr_out"; then bst_unrun miniupnpd/conf-accepts-ipv4-literal "$litaddr_out" elif has "Usage:" "$litaddr_out" || has "Error parsing address/mask" "$litaddr_out"; then bst_notok miniupnpd/conf-accepts-ipv4-literal "with ENABLE_IPV6 off a literal listening_ip must be kept by parselanaddr, not sent to print_usage; got rc=$rc -- $(_bst_1line "$litaddr_out")" else bst_ok miniupnpd/conf-accepts-ipv4-literal; fi # ...and the by-name demand is the #ifdef's other side: its ABSENCE is what says # the address was kept rather than merely tolerated by a different code path. check_eq miniupnpd/conf-no-ipv6-by-name-demand "yes" \ "$(has "please specify LAN network interface by name" "$litaddr_out" || echo yes)" # ext_ifname without a listening_ip is not a configuration miniupnpd will run: # `if(!ext_if_name || !lan_addrs.lh_first) goto print_usage'. Asserted because it # is the one place the two mandatory settings are enforced, and it is enforced # after BOTH the file and argv have had their say. nolisten=$work/nolisten.conf printf '%s\n' 'ext_ifname=lo' > "$nolisten" rb 30 "$MU" -d -P "$work/miniupnpd.pid" -f "$nolisten" if [ "$rc" -eq 1 ] && has "Usage:" "$out"; then bst_ok miniupnpd/conf-requires-listening-ip elif bst_execfail "$out"; then bst_unrun miniupnpd/conf-requires-listening-ip "$out" else bst_notok miniupnpd/conf-requires-listening-ip "a config with ext_ifname and no listening_ip must reach print_usage; got rc=$rc -- $(_bst_1line "$out")"; fi # --- D: where this image and miniupnpd part company --------------------------- # # All of D reads the group-C good.conf run above -- one bounded foreground start # over a configuration miniupnpd has no reason to refuse. `-d' is what makes it # readable: without it USE_DAEMON's daemon(0,0) forks and the parent returns 0 # having established nothing, and openlog() is opened without LOG_PERROR so the # syslog lines below would never reach a stream this suite can see. # THE CRITERION. A daemon with no way to program anything must return, not sit # there. Everything else in this group describes WHY it returned; this case is # the assertion that it did. if [ "$good_rc" -eq 124 ]; then bst_notok miniupnpd/exits-not-resident "miniupnpd stayed resident for 30 s on an image with no netfilter table -- it must report what it cannot open and return" else bst_ok miniupnpd/exits-not-resident fi # ...and it must return a FAILING status while doing so. init() returning # non-zero makes main() return 1; a zero here would mean it decided the start had # succeeded. if [ "$good_rc" -eq 124 ]; then bst_notok miniupnpd/exit-status "did not return, so there is no status to read -- see miniupnpd/exits-not-resident" elif [ "$good_rc" -ne 0 ]; then bst_ok miniupnpd/exit-status else bst_notok miniupnpd/exit-status "miniupnpd returned 0 on an image where init_redirect() cannot succeed" fi # The netfilter table itself. init_redirect() is iptc_init("nat"), which is # getsockopt(SO_GET_INFO) on a raw socket; wasm32_defconfig sets CONFIG_NET=y and # no CONFIG_NETFILTER, so ip_tables is never built and there is nothing to ask. # The same wall 99-iptables.test measures as "Table does not exist". # # BRINTOS-WORKAROUND: brintos/linux#86 revisit -- this arm exists only to tolerate # that gap. When #86 lands the case goes green by itself with no change here, and # this arm becomes the surprise-reporter it reads as. if has "iptc_init() failed" "$good_out"; then bst_notok miniupnpd/netfilter-table "CONFIG_NETFILTER is not set in arch/wasm32/configs/wasm32_defconfig, so ip_tables is never built and iptc_init(\"nat\") has no table to open -- the same kernel gap 99-iptables.test carries. brintos/linux#86 (https://brintos.io/brintos/linux/issues/86). rc=$good_rc $(_bst_1line "$good_out")" else bst_ok miniupnpd/netfilter-table fi # And that it is reported in miniupnpd's OWN voice rather than only the library's. # This is the line a reader of the console sees, and it is what makes the exit # above legible instead of silent. # # BRINTOS-WORKAROUND: brintos/linux#86 revisit -- goes green with the case above. if has "Failed to init redirection engine. EXITING" "$good_out"; then bst_notok miniupnpd/redirect-engine "miniupnpd names the engine it could not initialize and exits, which is the documented and correct behaviour on this image -- red only because it is the consequence of brintos/linux#86 and must flip when that lands. rc=$good_rc" else bst_ok miniupnpd/redirect-engine fi # writepidfile() is the statement AFTER init_redirect(), so a failed start must # leave no pid file behind. Green here, and it stays green when #86 lands only if # a successful start also cleans up -- which is why it is asserted at the path # this suite controls (-P) rather than at /var/run. check_eq miniupnpd/no-stale-pidfile "yes" \ "$([ ! -e "$work/miniupnpd.pid" ] && echo yes)" # The external interface. main() logs "Cannot get IP address for ext interface" # from getifaddr(), but only AFTER init() returns 0 -- which it does not here. # Scored as its own case, and taking the CONSEQUENTIAL arm, so that a reader does # not conclude from D03's red that the interface lookup was tried and failed. if has "Cannot get IP address for ext interface" "$good_out"; then bst_ok miniupnpd/ext-interface elif has "Failed to init redirection engine" "$good_out"; then bst_notok miniupnpd/ext-interface "consequential, not independent: init() returned at init_redirect() before main() reached getifaddr(), so this run establishes nothing about the external interface. See miniupnpd/netfilter-table" else bst_notok miniupnpd/ext-interface "neither the interface diagnostic nor the redirect-engine failure appeared, so it is not known where this start stopped: rc=$good_rc $(_bst_1line "$good_out")" fi # The lease file, for the same reason and in the same shape. # reload_from_lease_file() is three statements past init_redirect(), so the path # named by lease_file= is never opened. C06 is as far as ENABLE_LEASEFILE is # reachable on this image. if [ -e "$work/leases" ]; then bst_ok miniupnpd/lease-file elif has "Failed to init redirection engine" "$good_out"; then bst_notok miniupnpd/lease-file "consequential, not independent: reload_from_lease_file() follows init_redirect(), which returned first, so lease_file=$work/leases was accepted by the parser (see miniupnpd/conf-leasefile-key-known) and never opened. See miniupnpd/netfilter-table" else bst_notok miniupnpd/lease-file "no lease file and no redirect-engine failure, so it is not known why the lease path was not reached: rc=$good_rc $(_bst_1line "$good_out")" fi # --- E: the netfilter helper scripts ------------------------------------------ # # PREDICTED, NOT MEASURED -- see the header. Both scripts source # miniupnpd_functions.sh, which resolves ip(1) and iptables(1) through which(1) # and then reads the four chain states through `iptables -t X -L -n | awk'. Each # of those calls fails at the same table D03 names, awk sees nothing, and every # chain reads as absent -- so both scripts are expected to run to completion, # change nothing, and leave the backend's complaint on stderr. rb 60 "$ETC/iptables_init.sh" init_out=$out; init_rc=$rc if bst_execfail "$init_out"; then bst_unrun miniupnpd/helper-init-ran "$init_out" elif [ "$init_rc" -eq 124 ]; then bst_notok miniupnpd/helper-init-ran "iptables_init.sh did not return within 60 s" else bst_ok miniupnpd/helper-init-ran # It reached its own body, which means which(1) found both ip and iptables -- # the failure arms in miniupnpd_functions.sh exit before printing any of this. check_eq miniupnpd/helper-init-reached-body "yes" \ "$(has "table clean" "$init_out" && echo yes)" # ...and it REPORTED the absent backend rather than changing nothing in silence. check_eq miniupnpd/helper-init-names-backend "yes" \ "$(has "Table does not exist" "$init_out" && echo yes)" fi rb 60 "$ETC/iptables_removeall.sh" rm_out=$out; rm_rc=$rc if bst_execfail "$rm_out"; then bst_unrun miniupnpd/helper-removeall-ran "$rm_out" elif [ "$rm_rc" -eq 124 ]; then bst_notok miniupnpd/helper-removeall-ran "iptables_removeall.sh did not return within 60 s" else bst_ok miniupnpd/helper-removeall-ran # Every chain reads as absent, so all four branches are skipped and the script # removes nothing -- which is correct, and is only distinguishable from a silent # no-op by the backend's complaint the state probes leave on stderr. check_eq miniupnpd/helper-removeall-names-backend "yes" \ "$(has "Table does not exist" "$rm_out" && echo yes)" fi rm -rf "$work"