. "$BST_LIB" # BST-TIMEOUT: 90 # MEASURED, not declared: the first on-target run reported # # elapsed=14s over 53 cases (2026-08-26, batch-1214 image), # # and this is ~5x that rounded up -- the ratio # # 99-sg3-utils.test and 99-aoetools.test use. Nothing here # # waits on a network: every invocation ends at a parse error # # or at the multicast-routing socket, and each is bounded by # # its own `timeout' guard as well. # igmpproxy 0.3 (meta-networking recipes-daemons/igmpproxy) -- pali's IGMP-proxy # multicast router. What ships is /usr/sbin/igmpproxy and the /etc/igmpproxy.conf # this port bundles. # # THE SUBJECT IS THE CONFIGURATION IT COMPUTES, BECAUSE THAT IS THE HALF THIS # KERNEL CAN REACH. main() runs, in this order (igmpproxy.c): parse argv, check # euid, openlog, loadConfig(), and -- only then -- igmpProxyInit(). Everything up # to and including loadConfig() is reachable on this image and is scored below BY # VALUE. Everything igmpProxyInit() does is not, for reasons that are the # platform's and not this port's, and those cases are scored separately and # attributed. # # THERE IS NO MULTICAST ROUTING TABLE ON THIS KERNEL. igmpProxyInit() calls # enableMRouter(), which opens socket(AF_INET, SOCK_RAW, IPPROTO_IGMP) and then # claims the API with setsockopt(IPPROTO_IP, MRT_INIT) (mroute-api.c). That # setsockopt case is compiled out of ip_setsockopt() unless CONFIG_IP_MROUTE is # set, and arch/wasm32/configs/wasm32_defconfig does not set it: it brings the # net stack up LOOPBACK ONLY (CONFIG_NET, CONFIG_INET, CONFIG_UNIX, with IPV6 and # PACKET annotated beside them as deliberate v0.1 omissions) and IP_MROUTE # depends on IP_MULTICAST, which that file never enables. The same gap is what # 99-smcroute.test and 99-pimd.test are written around; this suite reaches it # through a third daemon and reports it the same way. # # AND THERE IS NO INTERFACE THIS DAEMON COULD USE EVEN IF THERE WERE. igmpproxy # only ever considers an interface with an address that is NOT IFF_LOOPBACK -- # the test is spelt out twice, in configureVifs() and again in igmpProxyInit()'s # VIF loop (config.c, igmpproxy.c). `lo' is the only interface this image has, so # no phyint block can ever become a VIF, upsvifcount stays 0, and # "There must be at least 1 Vif as upstream." is reached. That is a SECOND and # INDEPENDENT reason the vif table cannot be observed here, and it is scored as # its own case rather than folded into the first: fixing CONFIG_IP_MROUTE alone # would not turn it green. # # SO THE VIF TABLE IS THE ONE PART OF THE CRITERION THIS IMAGE CANNOT SHOW, and # saying which of the two walls the run actually hit is most of what this suite # is for. Both are read from igmpproxy's own diagnostics at runtime rather than # asserted from this comment, so each case turns back into a real assertion the # day the kernel grows the missing half -- with no change here. # # EVERY FATAL IS EXIT 255, AND ONLY THE MESSAGE TELLS THEM APART. syslog.c's # my_log() ends in `if (Severity <= LOG_ERR) exit(-1)', so a bad config, a # refused socket and a missing upstream VIF all leave the same status. That is # why every case below asserts the DIAGNOSTIC and not merely the status. # # HOW THE CONFIGURED VALUES ARE ESTABLISHED BY VALUE. igmpproxy has no mode that # dumps what it parsed, but at LOG_DEBUG -- two -v flags, since one only reaches # LOG_INFO -- loadConfig() and parsePhyintToken() echo each directive as they # read it, and loadConfig() then prints the assembled block field by field # ("IF name : %s", "Ratelimit : %d", "Threshold : %d", "State : %d"). Those four # are the strongest evidence available: they are printed from the STRUCT, after # parsing, so a file that was opened and ignored could not produce them. The # state numbers are igmpproxy.h's IF_STATE_UPSTREAM 1, IF_STATE_DOWNSTREAM 2 and # IF_STATE_DISABLED 0, which is the upstream/downstream classification the # criterion names, in the only form the program emits it. # # THE REJECT CASES ARE CHOSEN SO THAT EACH PROVES A VALUE WAS READ. A config that # is merely rejected proves only that something was parsed; a config rejected BY # THE RANGE CHECK ON A NUMBER proves that number reached the parser. threshold # 256 and ratelimit -1 are rejected by parsePhyintToken's own bounds tests, and # an altnet with a /33 by parseSubnetAddress's -- so the three cases together # establish that the threshold, the ratelimit and the prefix length are read from # the file rather than defaulted. # # NOTHING IS KILLED AND NOTHING IS LEFT RUNNING. Every invocation below exits on # its own -- at a parse error, at the routing socket, or at the missing upstream # VIF -- and each is bounded by `timeout' so that an image which one day HAS # multicast routing reports rc=124 here rather than spending the suite's whole # budget in igmpProxyRun(). No case scores a signal: kill(2) reports success and # delivers nothing on this image (brintos/linux#21), and that red is already # carried where it was found (88-dnsmasq/sigterm-stops). bst_ok igmpproxy/suite-canary # first line: absorbs brintos/linux#96, which drops it require /usr/sbin/igmpproxy igmpproxy/present IGMP=/usr/sbin/igmpproxy CONF=/etc/igmpproxy.conf D=${TMPDIR:-/tmp}/bst-igmpproxy.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok igmpproxy/workdir "cannot create $D"; exit 0; } bst_ok igmpproxy/workdir # rb -- run bounded, capturing stdout+stderr; sets $out and $rc. # Under -d igmpproxy writes every diagnostic to stderr (Log2Stderr), and the # usage text goes to stderr too, so both streams are merged and read together. 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; } # want_reject -- the shape every reject case shares: a # bounded run over a deliberately broken config must exit NON-ZERO and must say # why in igmpproxy's own words. Asserting the diagnostic and not just the status # is what keeps these cases distinct, since every fatal here is 255. want_reject() { _id=$1; _needle=$2; _cf=$3 rb 30 "$IGMP" -d "$_cf" if [ "$rc" -eq 124 ]; then bst_notok "$_id" "igmpproxy did not return within 30 s on a broken config (rc=124)" elif [ "$rc" -ne 0 ] && has "$_needle" "$out"; then bst_ok "$_id" elif [ "$rc" -ne 0 ]; then bst_notok "$_id" "rejected, but not by the check this case names -- so it does not establish what it was written to: want:'$_needle' rc=$rc got:'$(_bst_1line "$out")'" elif bst_execfail "$out"; then bst_unrun "$_id" "$out" else bst_notok "$_id" "a config broken as '$_needle' was ACCEPTED: rc=$rc -- $(_bst_1line "$out")" fi } # --- inventory: what the package put on the image ----------------------------- check_eq igmpproxy/inventory "yes" "$([ -x "$IGMP" ] && 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 igmpproxy/on-path "$IGMP" "$(command -v igmpproxy 2>/dev/null)" # The bundled config must be installed, and must be the file this port wrote # rather than upstream's example -- which is exactly a file with no `whitelist' # line. A regression to the shipped one would surface here rather than silently # taking the whitelist assertions below down with it. check_eq igmpproxy/conf-installed "yes" "$([ -f "$CONF" ] && echo yes)" check_eq igmpproxy/conf-not-upstream-example "yes" \ "$(grep -q '^[[:space:]]*whitelist[[:space:]]' "$CONF" 2>/dev/null && echo yes)" # --- usage and argument handling ---------------------------------------------- # `-h' prints the usage to STDERR and exits ZERO (igmpproxy.c: fputs(Usage, # stderr); exit(0)). Both halves are asserted: the stream is where a user on this # image actually finds it, and the status is the one the source returns rather # than the one a reader might assume from the man page. rb 20 "$IGMP" -h help_out=$out if [ "$rc" -eq 124 ]; then bst_notok igmpproxy/help-rc "igmpproxy -h did not return within 20 s (rc=124)" elif [ "$rc" -eq 0 ]; then bst_ok igmpproxy/help-rc elif bst_execfail "$help_out"; then bst_unrun igmpproxy/help-rc "$help_out" else bst_notok igmpproxy/help-rc "want rc=0 (-h ends in exit(0)); got rc=$rc -- $(_bst_1line "$help_out")"; fi check_eq igmpproxy/help-usage "yes" \ "$(has "Usage: igmpproxy [-h] [-n] [-d] [-v [-v]] " "$help_out" && echo yes)" # PACKAGE_STRING is the last line of the usage text, so this pins the version the # image carries to the one this port was written against. check_eq igmpproxy/help-version "yes" "$(has "igmpproxy 0.3" "$help_out" && echo yes)" # The configfile argument is mandatory: `optind != ArgCn - 1' is checked before # anything is opened, and the diagnostic is its own sentence. rb 20 "$IGMP" if [ "$rc" -ne 0 ] && has "You must specify the configuration file." "$out"; then bst_ok igmpproxy/requires-configfile elif bst_execfail "$out"; then bst_unrun igmpproxy/requires-configfile "$out" else bst_notok igmpproxy/requires-configfile "a bare invocation with no config file was accepted: rc=$rc -- $(_bst_1line "$out")"; fi # An unknown option lands in getopt's default arm, which is `exit(1)' -- a status # igmpproxy uses NOWHERE else (every other failure is my_log's exit(-1) = 255). # So this case pins the one place the two statuses differ. rb 20 "$IGMP" -Z "$CONF" if [ "$rc" -eq 1 ]; then bst_ok igmpproxy/unknown-option-rc elif bst_execfail "$out"; then bst_unrun igmpproxy/unknown-option-rc "$out" else bst_notok igmpproxy/unknown-option-rc "want rc=1 (getopt default arm ends in exit(1)); got rc=$rc -- $(_bst_1line "$out")"; fi # --- the criterion: a verbose foreground run over the bundled config ---------- # `igmpproxy -d -v -v /etc/igmpproxy.conf'. -d puts the log on stderr and stops # it daemonising; TWO -v are required and not one, because the first only moves # LogLevel from LOG_WARNING to LOG_INFO and every line below is LOG_DEBUG # (igmpproxy.c's -v arm). One run, read many ways. rb 60 "$IGMP" -d -v -v "$CONF" fg_out=$out; fg_rc=$rc if bst_execfail "$fg_out"; then bst_unrun igmpproxy/foreground-ran "$fg_out" rm -rf "$D" exit 0 fi bst_ok igmpproxy/foreground-ran # Emitted before the config is even opened, so its absence means the run did not # start rather than that it failed. Asserted unconditionally, rc=124 included: # the command substitution keeps everything the process wrote before a kill. check_eq igmpproxy/foreground-banner "yes" \ "$(has "Searching for config file at '$CONF'" "$fg_out" && echo yes)" # --- the common config, by value ---------------------------------------------- check_eq igmpproxy/config-quickleave "yes" \ "$(has "Config: Quick leave mode enabled." "$fg_out" && echo yes)" # Echoed as the raw token, so this says 512 reached the parser -- and 512 is not # the default (commonConfig.downstreamHostsHashTableSize starts at 32). check_eq igmpproxy/config-hashtablesize "yes" \ "$(has "Config: hashtablesize for quickleave is 512." "$fg_out" && echo yes)" # --- the upstream interface, by value ----------------------------------------- # parsePhyintToken() names the interface as it reads it... check_eq igmpproxy/config-iface-lo "yes" \ "$(has "Config: IF: Config for interface lo." "$fg_out" && echo yes)" check_eq igmpproxy/config-upstream-token "yes" \ "$(has "Config: IF: Got upstream token." "$fg_out" && echo yes)" check_eq igmpproxy/config-upstream-ratelimit "yes" \ "$(has "Config: IF: Got ratelimit token '128'." "$fg_out" && echo yes)" check_eq igmpproxy/config-upstream-threshold "yes" \ "$(has "Config: IF: Got threshold token '32'." "$fg_out" && echo yes)" # ...and loadConfig() then prints the assembled block FROM THE STRUCT, which is # the stronger statement: these four lines are produced after parsing, so a file # that was opened and ignored could not emit them. State 1 is IF_STATE_UPSTREAM. check_eq igmpproxy/struct-upstream-name "yes" \ "$(has "IF name : lo" "$fg_out" && echo yes)" check_eq igmpproxy/struct-upstream-ratelimit "yes" \ "$(has "Ratelimit : 128" "$fg_out" && echo yes)" check_eq igmpproxy/struct-upstream-threshold "yes" \ "$(has "Threshold : 32" "$fg_out" && echo yes)" # --- the altnet, by value ----------------------------------------------------- check_eq igmpproxy/config-altnet-token "yes" \ "$(has "Config: IF: Got altnet token 10.132.0.0/16." "$fg_out" && echo yes)" # parseSubnetAddress() logs what it PARSED, not what it was given, so this is the # case that establishes the subnet reached the address parser. inetFmts() drops # trailing zero octets, so a /16 prints as two octets -- see the exact-form case # below, which is where that derivation is pinned. check_eq igmpproxy/parsed-altnet "yes" \ "$(has "Parsed altnet to 10.132" "$fg_out" && has "/16." "$fg_out" && echo yes)" # --- the whitelist, by value -------------------------------------------------- # The half of the criterion upstream's own config file has no subject for. check_eq igmpproxy/config-whitelist-token "yes" \ "$(has "Config: IF: Got whitelist token 239.7.11.0/24." "$fg_out" && echo yes)" # A whitelist goes through the SAME parseSubnetAddress(), which logs under the # word "Altnet" whichever directive called it -- an upstream naming quirk, not a # sign the whitelist was read as an altnet: the two are stored in different lists # (allowedgroups vs allowednets) and only the log line is shared. # # The exact string is derived rather than guessed: inetFmts() formats # "%u.%u.%u/%d" when the mask's fourth byte is zero, and computes the prefix # length as 33 - ffs(ntohl(mask)). For 239.7.11.0/24 on a little-endian target # that is "239.7.11/24". Asserting the exact form is what proves BOTH the address # bytes and the /NN survived the parse; the looser case above keeps the whitelist # evidence if upstream ever changes the formatting. check_eq igmpproxy/parsed-whitelist "yes" \ "$(has "Parsed altnet to 239.7.11" "$fg_out" && echo yes)" check_eq igmpproxy/parsed-whitelist-exact "yes" \ "$(has "Parsed altnet to 239.7.11/24." "$fg_out" && echo yes)" # --- the downstream interface, by value --------------------------------------- check_eq igmpproxy/config-iface-eth0 "yes" \ "$(has "Config: IF: Config for interface eth0." "$fg_out" && echo yes)" check_eq igmpproxy/config-downstream-token "yes" \ "$(has "Config: IF: Got downstream token." "$fg_out" && echo yes)" check_eq igmpproxy/struct-downstream-name "yes" \ "$(has "IF name : eth0" "$fg_out" && echo yes)" check_eq igmpproxy/struct-downstream-ratelimit "yes" \ "$(has "Ratelimit : 64" "$fg_out" && echo yes)" check_eq igmpproxy/struct-downstream-threshold "yes" \ "$(has "Threshold : 48" "$fg_out" && echo yes)" # The three IF_STATE values the one config file exercises, read off the struct. # This is the upstream/downstream classification the criterion names, in the only # form igmpproxy emits it: 1 UPSTREAM, 2 DOWNSTREAM, 0 DISABLED (igmpproxy.h). check_eq igmpproxy/struct-state-upstream "yes" \ "$(has "State : 1" "$fg_out" && echo yes)" check_eq igmpproxy/struct-state-downstream "yes" \ "$(has "State : 2" "$fg_out" && echo yes)" check_eq igmpproxy/struct-state-disabled "yes" \ "$(has "State : 0" "$fg_out" && echo yes)" check_eq igmpproxy/config-iface-eth1 "yes" \ "$(has "Config: IF: Config for interface eth1." "$fg_out" && echo yes)" check_eq igmpproxy/config-disabled-token "yes" \ "$(has "Config: IF: Got disabled token." "$fg_out" && echo yes)" # --- the interface census, as igmpproxy itself sees it ------------------------ # buildIfVc() runs after loadConfig() and before the routing socket, and it is # reachable: despite its "RAW socket open" diagnostic it opens # socket(AF_INET, SOCK_DGRAM, 0) (ifvc.c) and walks SIOCGIFCONF. Its debug line # is therefore this suite's own evidence for what interfaces exist -- which is # what the two attributed cases below are read against, rather than a claim made # in this header. check_eq igmpproxy/ifvc-enumerated "yes" \ "$(has "buildIfVc: Interface lo" "$fg_out" && echo yes)" # --- where this image and igmpproxy part company ------------------------------ # One run, three walls, and which one was hit is the whole question. They are # ordered as igmpProxyInit() reaches them. # The raw IGMP socket. enableMRouter() opens socket(AF_INET, SOCK_RAW, # IPPROTO_IGMP) before it can ask about MRT_INIT, so a refusal here means the # question about multicast routing was never put. It is GREEN on this image -- # measured, so the MRT_INIT refusal below is genuinely MRT_INIT's own and not a # socket-creation failure one step earlier. No marker on this case: the raw-IGMP # path works and there is nothing here to accommodate. if has "IGMP socket open" "$fg_out"; then bst_notok igmpproxy/igmp-raw-socket "socket(AF_INET, SOCK_RAW, IPPROTO_IGMP) was refused, so enableMRouter() never reached MRT_INIT and nothing below says anything about CONFIG_IP_MROUTE -- rc=$fg_rc $(_bst_1line "$fg_out")" else bst_ok igmpproxy/igmp-raw-socket fi # The multicast routing table itself. setsockopt(IPPROTO_IP, MRT_INIT) is # compiled out of ip_setsockopt() unless CONFIG_IP_MROUTE is set, and # wasm32_defconfig does not set it (IP_MROUTE depends on IP_MULTICAST, which that # file never enables) -- the same gap 99-smcroute.test and 99-pimd.test are # written around. Confirmed on the image rather than read off the source: the # defconfig at packages/modified/linux c861e8bb6 carries CONFIG_INET=y and no # MROUTE or MULTICAST line at all. # # BRINTOS-WORKAROUND: brintos/linux#111 revisit -- this arm exists only to # tolerate that gap. When #111 lands the option, the case goes green by itself # with no change here, and this arm becomes the surprise-reporter it reads as. if has "MRT_INIT failed" "$fg_out"; then bst_notok igmpproxy/mrt-init "CONFIG_IP_MROUTE is not set in arch/wasm32/configs/wasm32_defconfig, so setsockopt(IPPROTO_IP, MRT_INIT) is refused and igmpproxy exits immediately after reading the config it just parsed -- the same kernel gap 99-smcroute.test and 99-pimd.test carry. rc=$fg_rc $(_bst_1line "$fg_out")" elif has "MC-Router API already in use" "$fg_out"; then bst_notok igmpproxy/mrt-init "MRT_INIT answered EADDRINUSE -- another multicast router holds the API on this image, which is a suite-interaction problem and NOT the known kernel gap: rc=$fg_rc $(_bst_1line "$fg_out")" else bst_ok igmpproxy/mrt-init fi # The second, INDEPENDENT wall, and the reason the vif table cannot be observed # here even with multicast routing working. igmpProxyInit()'s VIF loop skips any # interface that has no address or is IFF_LOOPBACK, and `lo' is the only # interface this image has, so upsvifcount stays 0. Scored separately because # turning on CONFIG_IP_MROUTE alone would leave this red exactly where it is. # # What the first on-target run actually took: the CONSEQUENTIAL arm. igmpproxy # exits at MRT_INIT before the VIF loop runs, so this image has never reached the # loopback wall and the run establishes nothing about it. The independent arm is # what #111 landing would expose, which is why both are kept. if has "There must be at least 1 Vif as upstream." "$fg_out"; then bst_notok igmpproxy/upstream-vif "the image has no non-loopback interface, so no phyint block can become a VIF (igmpProxyInit skips !InAdr and IFF_LOOPBACK) and igmpproxy has no upstream to proxy to. Independent of CONFIG_IP_MROUTE -- rc=$fg_rc $(_bst_1line "$fg_out")" elif has "MRT_INIT failed" "$fg_out" || has "IGMP socket open" "$fg_out"; then bst_notok igmpproxy/upstream-vif "consequential, not independent: igmpproxy exited at the routing socket before the VIF loop ran, so nothing was established about the interface census here. See igmpproxy/mrt-init" else bst_ok igmpproxy/upstream-vif fi # The vif table the criterion names. addVIF() logs "adding VIF, Ix %d ... %s, # Threshold: %d, Ratelimit: %d" at LOG_NOTICE, and is reached only past both # walls above. This case reports the consequence rather than re-establishing a # cause, so it says which wall stopped it. if has "adding VIF" "$fg_out"; then bst_ok igmpproxy/vif-table elif has "MRT_INIT failed" "$fg_out" || has "IGMP socket open" "$fg_out"; then bst_notok igmpproxy/vif-table "no vif table: igmpproxy exited at the multicast routing socket, so addVIF() never ran and the threshold 32 / ratelimit 128 this config sets were never programmed. Nothing is asserted about mroute-api.c here -- see igmpproxy/mrt-init" elif has "There must be at least 1 Vif as upstream." "$fg_out"; then bst_notok igmpproxy/vif-table "no vif table: no non-loopback interface exists, so addVIF() was never called for any phyint block -- see igmpproxy/upstream-vif" else bst_notok igmpproxy/vif-table "no 'adding VIF' line, and NEITHER known wall explains it -- this is not the expected platform gap and wants reading: rc=$fg_rc $(_bst_1line "$fg_out")" fi # The status every fatal on this image ends in. my_log() calls exit(-1) for # anything at LOG_ERR or worse, which the shell reports as 255. Asserted so that # a build whose failure convention changed shows up here rather than as a # confusing red somewhere else. rc=0 is accepted too: on an image that HAS # multicast routing this run would reach igmpProxyRun() and be killed by # `timeout' (124) or exit cleanly instead. case "$fg_rc" in 255|124|0) bst_ok igmpproxy/foreground-rc ;; *) bst_notok igmpproxy/foreground-rc "want 255 (my_log's exit(-1)), 124 (still running) or 0; got rc=$fg_rc -- $(_bst_1line "$fg_out")" ;; esac # --- the criterion's other half: a broken config must exit non-zero ----------- # Eight breakages, each derived from the SHIPPED file so that the only difference # between an accepted config and a rejected one is the defect itself. # 1. A token that is not a directive at all, at the top level. loadConfig()'s # final else arm. { cat "$CONF"; printf 'zzunknownzz\n'; } > "$D/unknown.conf" want_reject igmpproxy/reject-unknown-token "Unknown token 'zzunknownzz' in configfile" "$D/unknown.conf" # 2. hashtablesize without quickleave. An ORDERING rule rather than a value rule: # loadConfig() rejects the directive unless fastUpstreamLeave is already set, # so this establishes that quickleave was acted on and not merely seen. sed 's/^quickleave$/# quickleave removed for this case/' "$CONF" > "$D/nohashqleave.conf" want_reject igmpproxy/reject-hashtablesize-without-quickleave \ "hashtablesize is specified but quickleave not enabled." "$D/nohashqleave.conf" # 3. hashtablesize out of range. atoi()'d and bounds-checked, so a reject here # proves the NUMBER was read rather than the token merely recognised. sed 's/^hashtablesize 512$/hashtablesize 0/' "$CONF" > "$D/hashzero.conf" want_reject igmpproxy/reject-hashtablesize-range \ "hashtablesize must be between 1 and 536870912 bytes." "$D/hashzero.conf" # 4. threshold above 255. parsePhyintToken()'s own bounds test, which runs only # after atoi() -- so this is what establishes that the threshold the criterion # names reached the parser BY VALUE. The file is otherwise the shipped one, so # nothing else can explain the reject. sed 's/threshold 48/threshold 256/' "$CONF" > "$D/threshold.conf" want_reject igmpproxy/reject-threshold-range "Threshold must be between 1 and 255." "$D/threshold.conf" # 5. A negative ratelimit. Same shape, for the other number. sed 's/ratelimit 64/ratelimit -1/' "$CONF" > "$D/ratelimit.conf" want_reject igmpproxy/reject-ratelimit-range "Ratelimit must be 0 or more." "$D/ratelimit.conf" # 6. A prefix length past 32. Rejected inside parseSubnetAddress(), which proves # the /NN was read from the file rather than defaulted. sed 's#239.7.11.0/24#239.7.11.0/33#' "$CONF" > "$D/prefixlen.conf" want_reject igmpproxy/reject-prefixlen "Unable to parse subnet address." "$D/prefixlen.conf" # 7. An interface name at or past IF_NAMESIZE. parsePhyintToken() returns NULL on # the length check before it allocates anything, and loadConfig() then reports # the phyint token itself as unparsable -- which is the case that establishes # the interface NAME is read and measured. # The replacement name is exactly 20 characters: at or past IF_NAMESIZE (16), so # the length check fires, but well under MAX_TOKEN_LENGTH (30), so confread.c # does not truncate it -- which matters because the diagnostic prints the token # buffer, and parsePhyintToken has by then overwritten it with the NAME rather # than leaving `phyint' in it. sed 's/^phyint eth0 downstream/phyint eth0aaaaaaaaaaaaaaaa downstream/' "$CONF" > "$D/longname.conf" want_reject igmpproxy/reject-long-ifname "Unknown token 'eth0aaaaaaaaaaaaaaaa' in configfile" "$D/longname.conf" # 8. A config file that is not there at all. openConfigFile()'s fopen fails and # loadConfig() says so by NAME. Scored because "rejects a broken config" must # not be satisfiable only by files that exist. want_reject igmpproxy/reject-missing "Unable to open configfile from $D/absent.conf" "$D/absent.conf" # 9. An empty config file -- a file that exists, opens, and yields no token at # all. Its own arm in loadConfig(), and a different one from case 8. : > "$D/empty.conf" want_reject igmpproxy/reject-empty "Config file was empty." "$D/empty.conf" # The shipped config must still be ACCEPTED after all of the above -- i.e. every # reject case above was rejected for the defect it introduced and not for # something the shipped file carries too. Re-run rather than reusing $fg_out, # because that run was made with a different verbosity. # # "Accepted" here means "got past loadConfig", NOT "exited zero": on this image # igmpproxy always dies later, at the routing socket. So the assertion is that # NONE of the parser's own diagnostics appear. rb 60 "$IGMP" -d "$CONF" if bst_execfail "$out"; then bst_unrun igmpproxy/accepts-shipped "$out" elif has "Unable to load config file" "$out" || has "Unknown token" "$out" \ || has "Unable to parse subnet address" "$out"; then bst_notok igmpproxy/accepts-shipped "the SHIPPED /etc/igmpproxy.conf was rejected by the parser, which makes every reject case above meaningless: rc=$rc -- $(_bst_1line "$out")" else bst_ok igmpproxy/accepts-shipped fi rm -rf "$D"