. "$BST_LIB" # bridge-utils 1.7.1 (meta-networking, recipes-support) -- brctl, Lennert # Buytenhek's classic Linux bridge configuration tool, installed as # /usr/sbin/brctl. It is a 640-line front end over a four-file convenience # archive (libbridge), and every one of its sixteen subcommands ends in one of # exactly three kernel calls: a bridge ioctl on an AF_LOCAL socket # (SIOCBRADDBR/SIOCBRDELBR/SIOCSIFBR/SIOCGIFBR), a device-private ioctl # (SIOCDEVPRIVATE) on a named interface, or a read/write under # /sys/class/net//bridge. # # THE SHAPE OF THIS SUITE IS DECIDED BY ONE FACT: THIS KERNEL HAS NO BRIDGE. # CONFIG_BRIDGE is not set in wasm32_defconfig, so no bridge can be created on # this machine and there is no topology for `brctl show` to report. CONFIG_VETH, # CONFIG_DUMMY and CONFIG_TUN are not set either, so even a kernel that grew # CONFIG_BRIDGE tomorrow would still have nothing to enslave: lo is the only # registered netdev here, and br_add_if() refuses a loopback device by design. # That is the same class of fact 63-tunctl records for CONFIG_TUN and the # ifupdown entry for CONFIG_DUMMY, and a property of a browser machine whose # networking is whatever HardwareJS gives it. It is not a defect, and it is the # finding this port was asked to ESTABLISH rather than assume -- so it is # measured here from the target side, by the kernel's own answer, and not read # off a config file. # # WHAT THAT LEAVES IS MORE THAN IT SOUNDS, FOR THE SAME REASON tunctl's DID. # br_init() opens an AF_LOCAL SOCK_STREAM socket and CONFIG_UNIX is set here, so # it succeeds and every subcommand runs to its kernel boundary rather than dying # at setup. The kernel then answers the bridge ioctls itself: br_ioctl_call() # returns -ENOPKG when br_ioctl_hook is NULL, which is permanently so without # CONFIG_BRIDGE. So `brctl addbr` reports "Package not installed" -- the # kernel's own words for "there is no bridge code here" -- through a SIOCBRADDBR # that was genuinely issued. # # AND THAT GIVES THE SUITE ITS CENTRAL DIFFERENTIAL, which no single case # carries alone. Two different kernel paths answer with two different errnos, # and it is the PAIR that proves what happened: # # addbr -> ENOPKG ("Package not installed") br_ioctl_call, hook NULL # showmacs -> ENODEV ("No such device") dev_ioctl, no such netdev # # ENOPKG is not an errno userspace can manufacture and not one any other layer # here produces; it is emitted at exactly one place in the kernel and it means # the bridge hook is unset. ENODEV on the neighbouring call proves the second # path reached the device layer and got a per-device answer. Either message on # its own is consistent with "brctl is broken"; together they are only # consistent with "brctl works and this kernel has no bridge". # # Everything downstream of a SUCCESSFUL addbr is gated on a RUNTIME probe, so # the suite runs the real topology assertion -- create, enslave, show, showmacs, # delete, confirm gone, which is this ticket's stated pass criterion -- the day # this kernel grows CONFIG_BRIDGE, rather than reporting a stale skip forever. require brctl brctl/present tmp=${TMPDIR:-/tmp}/bst-brctl.$$ out=$tmp.out; out2=$tmp.out2 # A bridge name this machine certainly does not have. Kept short: IFNAMSIZ is 16 # and libbridge strlcpy()s into it, so a long name would be silently truncated # and the case would be asserting against a name it did not send. absent=bstbr0 # --------------------------------------------------------------- packaging --- # brctl installs to sbindir under update-alternatives (priority 100). Asserting # where it resolves catches a packaging change that leaves it off the PATH the # runner actually has, and there is no busybox or toybox brctl applet to mask # such a change: a bare `brctl` is this port. check_eq brctl/resolves-to-sbin "/usr/sbin/brctl" "$(command -v brctl)" # ------------------------------------------------------- version and usage --- # --help EXITS 0 here. That is worth a case of its own because it is the # OPPOSITE of the neighbouring 63-tunctl, whose Usage() ends in exit(1): brctl's # main() returns 0 for -h and reaches the same help text with rc 1 only by # falling through from an error. A suite that assumed one convention for both # ports would be red against a correct binary in one of the two. check_out brctl/help-rc0-usage "Usage: brctl [commands]" brctl --help check_out brctl/help-short "Usage: brctl [commands]" brctl -h check_rc brctl/help-rc 0 brctl --help # The help body is command_helpall() walking the sixteen-entry command table, so # asserting entries pins the table rather than a banner. brctl --help > "$out" 2>&1 check_out brctl/help-addbr "addbr" cat "$out" check_out brctl/help-delbr "delbr" cat "$out" check_out brctl/help-addif "add interface to bridge" cat "$out" check_out brctl/help-delif "delete interface from bridge" cat "$out" check_out brctl/help-show "show a list of bridges" cat "$out" check_out brctl/help-showmacs "show a list of mac addrs" cat "$out" check_out brctl/help-showstp "show bridge stp info" cat "$out" check_out brctl/help-hairpin "turn hairpin on/off" cat "$out" check_out brctl/help-stp "turn stp on/off" cat "$out" # All sixteen, counted. A table that grew or shrank is visible as a number # rather than as a missing case nobody notices. # # The pattern carries a LITERAL TAB and matches nothing else about the line. # command_helpall() prints "\t%-10s\t%s\n", so a name shorter than ten # characters is followed by SPACES before its tab -- an expression anchored on # name-then-tab counts only the three names that are longer than ten, which is # what it did until a native dry run said 3 instead of 16. `\t` is also not # portable inside a busybox sed pattern, and busybox sed is what this target # has, so the tab is written out rather than escaped. check_eq brctl/help-command-count 16 \ "$(brctl --help 2>&1 | grep -c '^ [a-z]')" # -V prints PACKAGE_NAME and PACKAGE_VERSION straight out of config.h. # # AND THE VERSION IT PRINTS IS NOT THE RECIPE'S PV. meta-networking packages # this as bridge-utils 1.7.1, but upstream's configure.ac at the pinned SRCREV # (75d949b, whose subject is "Chnage version to 1.7") still says # AC_INIT([bridge-utils],[1.7]) -- so the binary says 1.7 and the package says # 1.7.1. That is upstream's own inconsistency and not this port's; it is pinned # here so that a future recipe bump which DOES change the binary's string is # visible as a red rather than passing unnoticed. check_out brctl/version-name "bridge-utils" brctl -V check_eq brctl/version-string "bridge-utils, 1.7" "$(brctl -V 2>&1)" check_rc brctl/version-rc 0 brctl -V # No arguments at all: help, on stdout, rc 1. The rc is the whole point -- it is # the same text as --help through a different return path. check_rc brctl/noargs-rc 1 sh -c "brctl > '$out' 2>&1" check_out brctl/noargs-help "Usage: brctl [commands]" cat "$out" # getopt_long's rejection, then brctl's own "Unknown option" line, then help. # The pair is the assertion: brctl prints its OWN diagnostic in addition to # getopt's, and a change to the option table would drop one of the two. check_rc brctl/badopt-rc 1 sh -c "brctl -Z > '$out' 2>&1" check_out brctl/badopt-getopt "invalid option" cat "$out" check_out brctl/badopt-own "Unknown option" cat "$out" check_out brctl/badopt-usage "Usage: brctl [commands]" cat "$out" # An unrecognised subcommand is command_lookup() returning NULL. check_rc brctl/badcmd-rc 1 sh -c "brctl bstnosuchcmd > '$out' 2>&1" check_out brctl/badcmd-msg "never heard of command [bstnosuchcmd]" cat "$out" # Too few arguments is checked against the table's own nargs, BEFORE the # command's function runs, and it echoes that command's help string -- so this # also proves the entry brctl matched is the one it printed. check_rc brctl/nargs-rc 1 sh -c "brctl addbr > '$out' 2>&1" check_out brctl/nargs-msg "Incorrect number of arguments for command" cat "$out" check_out brctl/nargs-usage "Usage: brctl addbr" cat "$out" check_rc brctl/nargs-addif-rc 1 sh -c "brctl addif $absent > '$out2' 2>&1" check_out brctl/nargs-addif-usage "Usage: brctl addif" cat "$out2" # ------------------------------------ argument parsing, before any syscall --- # These four reject in brctl's own code and never reach the kernel, so they are # real assertions on this target rather than gated ones. Each is a DIFFERENT # parser: strcmp against a fixed vocabulary, sscanf("%lf"), sscanf("%i"). check_rc brctl/stp-badarg-rc 1 sh -c "brctl stp $absent maybe > '$out' 2>&1" check_out brctl/stp-badarg-msg "expect on/off for argument" cat "$out" check_rc brctl/setageing-badarg-rc 1 sh -c "brctl setageing $absent notanumber > '$out' 2>&1" check_out brctl/setageing-badarg-msg "bad ageing time value" cat "$out" check_rc brctl/setprio-badarg-rc 1 sh -c "brctl setbridgeprio $absent notanumber > '$out' 2>&1" check_out brctl/setprio-badarg-msg "bad priority" cat "$out" check_rc brctl/hairpin-badarg-rc 1 sh -c "brctl hairpin $absent lo maybe > '$out' 2>&1" check_out brctl/hairpin-badarg-msg "expect on/off for argument" cat "$out" # hairpin resolves BOTH names with if_nametoindex before it touches sysfs, and # it checks the PORT first. The pair below pins that ordering: a bogus port is # reported even when the bridge is also bogus, and a real port shifts the # complaint onto the bridge. Without the second case the first would pass on an # implementation that simply refused everything. check_out brctl/hairpin-badport "interface bstnosuchif does not exist!" \ sh -c "brctl hairpin $absent bstnosuchif on 2>&1; exit 0" if [ -d /sys/class/net/lo ]; then check_out brctl/hairpin-badbridge "bridge $absent does not exist!" \ sh -c "brctl hairpin $absent lo on 2>&1; exit 0" else bst_skip brctl/hairpin-badbridge "no lo interface on this image to use as a resolvable port name" fi # ------------------------------------------- the sysfs enumeration path --- # br_foreach_bridge() scandir()s /sys/class/net looking for a /bridge # subdirectory. Assert the directory it reads actually exists first, so a # missing sysfs mount is reported as itself rather than as a brctl failure. if [ -d /sys/class/net ]; then bst_ok brctl/sysfs-class-net else bst_notok brctl/sysfs-class-net "/sys/class/net is absent, so brctl's sysfs enumeration path cannot run and its ioctl fallback is all that is left" fi # `brctl show` with no bridges: scandir finds none, the ioctl fallback fails, # and br_cmd_show discards the error -- so the correct behaviour is EMPTY OUTPUT # AND RC 0, header included, because the header is printed inside the per-bridge # callback that never runs. Both halves are asserted: rc 0 alone would pass on a # build that printed a spurious header. check_rc brctl/show-rc 0 sh -c "brctl show > '$out' 2>&1" check_eq brctl/show-empty "" "$(brctl show 2>&1)" # ------------------------------------------------- THE KERNEL BOUNDARY --- # The two cases this port is really worth, and the reason it is worth porting to # a machine that cannot bridge. See the header: it is the PAIR that carries the # finding. # 1. addbr -> ENOPKG. br_add_bridge() issues ioctl(SIOCBRADDBR), which # net/socket.c routes to br_ioctl_call(); with br_ioctl_hook NULL that returns # -ENOPKG and nothing else in this stack produces that errno. glibc spells it # "Package not installed". check_rc brctl/addbr-rc 1 sh -c "brctl addbr $absent > '$out' 2>&1" check_out brctl/addbr-msg "add bridge failed:" cat "$out" case "$(cat "$out")" in *"Package not installed"*) bst_ok brctl/addbr-enopkg ;; *"Operation not permitted"*) bst_notok brctl/addbr-enopkg "SIOCBRADDBR was refused as EPERM, not ENOPKG: the runner is not privileged, so this says nothing about whether the kernel has bridge support -- $(_bst_1line "$(cat "$out")")" ;; *) bst_notok brctl/addbr-enopkg "SIOCBRADDBR failed with something other than ENOPKG, so the kernel's bridge hook was reached in a state this suite does not model -- $(_bst_1line "$(cat "$out")")" ;; esac # 2. showmacs -> ENODEV. br_read_fdb() cannot open # /sys/class/net/
/brforward, falls back to ioctl(SIOCDEVPRIVATE), and # dev_ioctl answers for the NAMED DEVICE -- which does not exist. A different # errno from a different kernel path, on the same socket, in the same run. check_rc brctl/showmacs-rc 1 sh -c "brctl showmacs $absent > '$out2' 2>&1" check_out brctl/showmacs-msg "read of forward table failed:" cat "$out2" check_out brctl/showmacs-enodev "No such device" cat "$out2" # The differential itself, asserted as one case: the two errnos must DIFFER. # Equal errnos would mean one of the two calls never reached the layer this # suite claims it did, and neither case above can see that on its own. a=$(brctl addbr "$absent" 2>&1) b=$(brctl showmacs "$absent" 2>&1) case "$a::$b" in *"Package not installed"*"::"*"No such device"*) bst_ok brctl/kernel-boundary-differential ;; *) bst_notok brctl/kernel-boundary-differential "expected ENOPKG from the bridge hook and ENODEV from the device layer; got addbr:'$(_bst_1line "$a")' showmacs:'$(_bst_1line "$b")'" ;; esac # delbr takes the same br_ioctl_call() path as addbr and must report the same # errno through a different message. Its ENXIO and EBUSY arms are unreachable # here for the same reason addbr's EEXIST arm is. check_rc brctl/delbr-rc 1 sh -c "brctl delbr $absent > '$out' 2>&1" check_out brctl/delbr-msg "can't delete bridge $absent:" cat "$out" check_out brctl/delbr-enopkg "Package not installed" cat "$out" # addif/delif resolve the DEVICE with if_nametoindex first and only then issue # SIOCBRADDIF, so a bogus device stops before the kernel and a real device # carries the call through to the bridge that is not there. That ordering is the # assertion, and it is why both spellings are here. check_out brctl/addif-badif "interface bstnosuchif does not exist!" \ sh -c "brctl addif $absent bstnosuchif 2>&1; exit 0" if [ -d /sys/class/net/lo ]; then check_out brctl/addif-badbridge "bridge $absent does not exist!" \ sh -c "brctl addif $absent lo 2>&1; exit 0" check_out brctl/delif-badbridge "bridge $absent does not exist!" \ sh -c "brctl delif $absent lo 2>&1; exit 0" else bst_skip brctl/addif-badbridge "no lo interface to use as a resolvable device name" bst_skip brctl/delif-badbridge "no lo interface to use as a resolvable device name" fi # showstp on a name that is not a device, and on a device that is not a bridge. # br_get_bridge_info() distinguishes them by errno: ENODEV from the device layer # means "no such device", anything else means "that device has no bridge". lo is # a real registered netdev here, so the second arm is a genuine exercise of the # SIOCDEVPRIVATE path against a device that exists. check_rc brctl/showstp-absent-rc 1 sh -c "brctl showstp $absent > '$out' 2>&1" check_out brctl/showstp-absent-msg "bridge $absent does not exist!" cat "$out" if [ -d /sys/class/net/lo ]; then check_rc brctl/showstp-lo-rc 1 sh -c "brctl showstp lo > '$out2' 2>&1" case "$(cat "$out2")" in *"device lo is not a bridge!"*) bst_ok brctl/showstp-lo-notabridge ;; *"bridge lo does not exist!"*) bst_notok brctl/showstp-lo-notabridge "brctl reported lo as a MISSING DEVICE, so the device-private ioctl answered ENODEV for an interface that is registered -- $(_bst_1line "$(cat "$out2")")" ;; *) bst_notok brctl/showstp-lo-notabridge "unexpected showstp output for lo: $(_bst_1line "$(cat "$out2")")" ;; esac # `show ` funnels through the same br_get_bridge_info(), so it must # agree with showstp. A disagreement would mean one of the two reached a # different kernel path than the other. check_out brctl/show-lo-notabridge "device lo is not a bridge!" \ sh -c "brctl show lo 2>&1; exit 0" check_rc brctl/show-lo-rc 1 sh -c "brctl show lo > /dev/null 2>&1" else bst_skip brctl/showstp-lo-rc "no lo interface on this image" bst_skip brctl/showstp-lo-notabridge "no lo interface on this image" bst_skip brctl/show-lo-notabridge "no lo interface on this image" bst_skip brctl/show-lo-rc "no lo interface on this image" fi # The sysfs SETTER path. br_set() write()s /sys/class/net/
/bridge/ # and falls back to SIOCDEVPRIVATE; with no bridge both fail, and the value it # would have written is parsed first, so this is a real exercise of the write # path's failure reporting rather than of the parser (which is covered above). check_rc brctl/setageing-rc 1 sh -c "brctl setageing $absent 10 > '$out' 2>&1" check_out brctl/setageing-msg "set ageing time failed:" cat "$out" check_rc brctl/setprio-rc 1 sh -c "brctl setbridgeprio $absent 32768 > '$out' 2>&1" check_out brctl/setprio-msg "set bridge priority failed:" cat "$out" check_rc brctl/stp-on-rc 1 sh -c "brctl stp $absent on > '$out' 2>&1" check_out brctl/stp-on-msg "set stp status failed:" cat "$out" check_rc brctl/setfd-rc 1 sh -c "brctl setfd $absent 4 > '$out' 2>&1" check_out brctl/setfd-msg "set forward delay failed:" cat "$out" # ------------- no bridge driver, or no privilege? say which, from the target --- # The header's claim is CONFIG_BRIDGE, and the one other thing that could # produce a uniformly refusing brctl is running unprivileged. Settle it here # rather than assert it: id -u is the whole question, because every failure # above is at a call that root is allowed to make. if [ "$(id -u 2>/dev/null)" = "0" ]; then bst_ok brintos/brctl-refusals-are-not-privilege else bst_notok brintos/brctl-refusals-are-not-privilege "the runner is uid $(id -u), so the bridge ioctls may have been refused for lack of CAP_NET_ADMIN and this suite cannot attribute them to CONFIG_BRIDGE" fi # ------------------------------------------- bridge-driver-gated cases --- # THIS TICKET'S STATED PASS CRITERION, gated on a runtime probe so it runs for # real the day this kernel is built with CONFIG_BRIDGE. The probe is addbr # itself: nothing weaker distinguishes a kernel that can bridge from one that # cannot, and a probe that read the config file would be asserting against a # document rather than against the machine. if brctl addbr "$absent" >/dev/null 2>&1; then # create -> it appears in `show` and in sysfs check_out brctl/create-shows "$absent" sh -c "brctl show 2>&1" check_out brctl/create-header "bridge name" sh -c "brctl show 2>&1" if [ -d "/sys/class/net/$absent/bridge" ]; then bst_ok brctl/create-sysfs else bst_notok brctl/create-sysfs "addbr succeeded but /sys/class/net/$absent/bridge is absent" fi check_rc brctl/create-showstp-rc 0 sh -c "brctl showstp $absent > /dev/null 2>&1" check_out brctl/create-showmacs-header "port no" sh -c "brctl showmacs $absent 2>&1" # a second addbr of the same name is EEXIST, which is the one arm of # br_cmd_addbr's switch that only a working bridge layer can reach check_out brctl/create-eexist "already exists" \ sh -c "brctl addbr $absent 2>&1; exit 0" # enslave. There is no veth and no dummy on this arm, and lo cannot be # enslaved (br_add_if refuses IFF_LOOPBACK), so report which of those two # is the reason rather than leaving a bare skip. if [ -d /sys/class/net/bstveth0 ]; then check_rc brctl/addif-rc 0 sh -c "brctl addif $absent bstveth0 > /dev/null 2>&1" check_out brctl/addif-shows "bstveth0" sh -c "brctl show 2>&1" check_rc brctl/delif-rc 0 sh -c "brctl delif $absent bstveth0 > /dev/null 2>&1" else bst_skip brctl/addif-rc "no non-loopback interface exists to enslave: CONFIG_VETH and CONFIG_DUMMY are both unset on this kernel, and lo is refused by br_add_if (IFF_LOOPBACK)" bst_skip brctl/addif-shows "same: nothing can be added, so no port can appear in brctl show" bst_skip brctl/delif-rc "same: nothing was added, so nothing can be removed" fi # delete -> and confirm it is gone, which is the half of the criterion a # successful delbr does not prove on its own check_rc brctl/delete-rc 0 sh -c "brctl delbr $absent > /dev/null 2>&1" check_eq brctl/delete-gone "" "$(brctl show 2>&1 | grep "$absent")" if [ -d "/sys/class/net/$absent" ]; then bst_notok brctl/delete-sysfs-gone "delbr succeeded but /sys/class/net/$absent still exists" else bst_ok brctl/delete-sysfs-gone fi else gate="this kernel has no bridge support (CONFIG_BRIDGE is not set), so SIOCBRADDBR returns ENOPKG and no bridge can be created" bst_skip brctl/create-shows "$gate" bst_skip brctl/create-header "$gate" bst_skip brctl/create-sysfs "$gate: /sys/class/net/
/bridge is created by the bridge driver" bst_skip brctl/create-showstp-rc "$gate: STP state is read out of the bridge's own sysfs directory" bst_skip brctl/create-showmacs-header "$gate: the forwarding table belongs to a bridge that cannot exist" bst_skip brctl/create-eexist "$gate: EEXIST is reachable only after one addbr has succeeded" bst_skip brctl/addif-rc "$gate, and CONFIG_VETH/CONFIG_DUMMY are unset so there is nothing to enslave either" bst_skip brctl/addif-shows "$gate: no port can appear under a bridge that cannot exist" bst_skip brctl/delif-rc "$gate: nothing can be added, so nothing can be removed" bst_skip brctl/delete-rc "$gate: delbr takes the same br_ioctl_call path and fails identically" bst_skip brctl/delete-gone "$gate: nothing was created, so 'gone' is not a state this machine can enter" bst_skip brctl/delete-sysfs-gone "$gate: the bridge's sysfs directory is never created in the first place" fi rm -f "$out" "$out2" 2>/dev/null