# BST-TIMEOUT: 300 # MEASURED: elapsed 20 s for 87 cases (2026-08-26, local # # functional verify, glibc image). Every case is a # # short-lived fork of a 2-3 MB static binary; there is no # # daemon, no bus and no sleep, and the only blocking # # syscall anywhere is canconfig's netlink recvmsg, which # # the kernel answers. 300 is the ticket-gate/nightly # # default and ~15x the measured figure, which is the # # headroom the nightly needs on a loaded host. # # canutils 4.0.6 (PTX flavour, SRCREV 299dff7f, git.pengutronix.de/tools/ # canutils, packaged by meta-oe recipes-extended/socketcan) -- Pengutronix's # five SocketCAN tools: canconfig, canecho, candump, cansend, cansequence. # # --------------------------------------------------------------------------- # THIS IS NOT can-utils. 99-can-utils.test IS THE OTHER ONE. # # Same subsystem, different codebase, and three differences decide this file: # # 1. canconfig is this package alone, and it is a libsocketcan CLI. It is # the ONLY thing on the image that drives libsocketcan's netlink control # plane from a shipped binary -- 79-libsocketcan.test had to compile a # probe in its own recipe because "on an image with no compiler nothing # that ships can drive a line of it". Most of what this file asserts for # real runs through canconfig, and none of it needs a CAN device. # # 2. THIS candump does not call epoll_create. can-utils' candump.c:466 does, # before its socket, and the epoll family is ENOSYS on wasm32 # (brintos/linux#53) -- so that candump cannot start at all and its suite # carries a standing red for it. This one uses a blocking read(2) and # behaves like every other bus tool here. The case canutils/candump-no- # epoll asserts that positively, so the two tools' fates stay separable. # # 3. This package registers candump, cansend and cansequence through # update-alternatives at PRIORITY 90; meta-oe's can-utils recipe sets no # priority and inherits the class default of 10. So with both installed # the three bare names point HERE. That is asserted by readlink below -- # it is a real, observable change this port makes -- while every binary # is driven by its INSTALLED path, never through the link. # # --------------------------------------------------------------------------- # WHAT THE KERNEL HAS AND HAS NOT, AND HOW THIS FILE SCORES IT # # arch/wasm32/configs/wasm32_defconfig sets CONFIG_NET=y and carries no # CONFIG_CAN line, so socket(PF_CAN, SOCK_RAW, CAN_RAW) answers EAFNOSUPPORT, # there is no can0, and no vcan either -- vcan is itself a CAN driver. # # But NETLINK_ROUTE is registered unconditionally under CONFIG_NET. That is the # asymmetry this file is built on, and it is why the port is not simply # "blocked": # # OFFLINE -- version text, usage text, argument validation and exit status. # No socket of any kind. Asserted for real. # # PLUMBING -- the bus tools' -f/-t/-p switches make them open a NON-CAN # socket. socket(AF_INET, SOCK_DGRAM, 0) succeeds here and # ioctl(SIOCGIFINDEX) then runs, so the tool gets all the way to # bind() on a real name and to `ioctl: No such device` on a bogus # one. That is the proof these binaries are working code rather # than something that merely fails early. Asserted for real. # # NETLINK -- canconfig against a real, non-CAN interface (lo) completes a # full RTM_GETLINK/RTM_NEWLINK transaction and gets a real kernel # answer: EOPNOTSUPP for the setters, "no link data" for the # getters. Against a name that cannot exist, libsocketcan gives # up at if_nametoindex and says so in a DIFFERENT voice. The two # voices are what discriminate a working control plane from a # dead one, and both are asserted for real. # # BUS -- everything that binds AF_CAN, and every canconfig operation # against an actual CAN device. PROBED at run time and skipped # only on a probe that confirms no CAN interface exists. # # WHY canconfig lo start / stop ARE NOT RUN. They are the two libsocketcan # operations a non-CAN interface really honours: can_do_start/can_do_stop set # and clear IFF_UP, return 0, and canconfig then fails at the state readback. # Measured under root in a private netns. lo is this guest's only interface and # the suites that follow this one use it, so bringing it down would be a real # outage caused by a self-test. Their coverage sits in the bus group instead. # # --------------------------------------------------------------------------- # THE ORACLE IS AN x86-64 BUILD OF THE SAME SRCREV WITH THE SAME PATCHES # # Every expected string below was read off a native build of 299dff7f with # meta-oe's 0001 and our 0002 applied, against libsocketcan 0.0.12 at the # recipe's own SRCREV 077def39, in two configurations: # # ORACLE A unprivileged, host netns, CAN family PRESENT. # ORACLE B root in a private user+net namespace whose only link is lo, with # socket(PF_CAN, ...) forced to EAFNOSUPPORT by an LD_PRELOAD # interposer -- i.e. exactly this guest's world. # # ORACLE B is what is pinned. The distinction is load-bearing for the setters: # the kernel checks CAP_NET_ADMIN before it checks the link kind, so an # unprivileged run answers `Operation not permitted` and a root run answers # `Operation not supported`. The target runs its suites as root. Pinning the # unprivileged answer would have made three cases red for a reason that has # nothing to do with wasm32. . "$BST_LIB" B=/usr/bin CD=$B/candump.canutils CS=$B/cansend.canutils CQ=$B/cansequence.canutils CE=$B/canecho CC=/usr/sbin/canconfig require "$CS" canutils/present # expect_out # check_out insists on rc 0 and almost nothing here exits 0, so this is the # workhorse: one fork, both the exit status and a substring of the combined # output asserted together. Combined, deliberately -- stdout is block-buffered # when redirected and stderr is not, so the two streams interleave # unpredictably and only a substring match is stable. expect_out() { _eid=$1; _erc=$2; _en=$3; shift 3 _eo=$("$@" 2>&1 -- the needle must NOT appear. expect_absent() { _aid=$1; _arc=$2; _an=$3; shift 3 _ao=$("$@" 2>&1 10. can-utils # may or may not be installed beside it; either way the answer is the same, and # if can-utils ever raises its priority this is the case that says so. check_eq canutils/alt-candump candump.canutils "$(readlink "$B/candump")" check_eq canutils/alt-cansend cansend.canutils "$(readlink "$B/cansend")" check_eq canutils/alt-cansequence cansequence.canutils "$(readlink "$B/cansequence")" # canecho is unique to this package, so it is NOT in an alternatives group and # keeps its bare name -- a plain file, not a link. check canutils/canecho-not-a-link test ! -L "$CE" # --------------------------------------------------------------------------- # GROUP 2 -- version text. Four tools print a literal program name; canconfig # prints "Version:" instead. Upstream's inconsistency, asserted as measured so # a reader is not surprised and a change is visible. check_eq canutils/version-candump "candump 4.0.6" "$("$CD" --version 2>&1)" check_eq canutils/version-cansend "cansend 4.0.6" "$("$CS" --version 2>&1)" check_eq canutils/version-cansequence "cansequence 4.0.6" "$("$CQ" --version 2>&1)" check_eq canutils/version-canecho "canecho 4.0.6" "$("$CE" --version 2>&1)" check_eq canutils/version-canconfig "Version: 4.0.6" "$("$CC" --version 2>&1)" check_rc canutils/version-rc-candump 0 "$CD" --version check_rc canutils/version-rc-canconfig 0 "$CC" --version # --------------------------------------------------------------------------- # GROUP 3 -- usage text, on stderr, offline. # # The four bin_PROGRAMS print usage with basename(argv[0]), so driven by # installed path they announce the ALTERNATIVES-RENAMED name. That is asserted # rather than worked around: it pins the usage path and the rename in one case, # and it is what a user will actually see on this image. expect_out canutils/usage-cansend-name 0 "Usage: cansend.canutils [] [Options] " "$CS" expect_out canutils/usage-cansend-h 0 "Usage: cansend.canutils" "$CS" -h expect_out canutils/usage-cansend-body 0 "up to 8 bytes given as a space separated list" "$CS" -h expect_out canutils/usage-canecho-name 0 "Usage: canecho [] [Options]" "$CE" expect_out canutils/usage-canecho-body 0 "If is omitted" "$CE" -h expect_out canutils/usage-cansequence 0 "Usage: cansequence.canutils [] [Options]" "$CQ" -h expect_out canutils/usage-cansequence-body 0 "rising sequence number as payload" "$CQ" -h expect_out canutils/usage-candump-long 0 "Usage: candump.canutils [] [Options]" "$CD" --help # The usage text prints the values the tool was COMPILED with -- src/ # GNUmakefile.am passes -DPF_CAN=29 -DAF_CAN=PF_CAN, and SOCK_RAW/CAN_RAW come # from the headers. A wrong-arch or wrong-header build shows up here and nowhere # else offline. (canutils' own man page says PF_CAN is 30 and CAN_PROTO_RAW is # 0; the man page is wrong and the binary is right -- see the design doc.) expect_out canutils/usage-pfcan-29 0 "default PF_CAN = 29" "$CD" --help expect_out canutils/usage-sockraw-3 0 "default SOCK_RAW = 3" "$CD" --help expect_out canutils/usage-canraw-1 0 "default CAN_RAW = 1" "$CD" --help # meta-oe's own 0001 added this line to the usage text. expect_out canutils/usage-candump-error 0 "dump error frames along with data frames" "$CD" --help # canconfig's help() calls exit(EXIT_FAILURE), so both spellings exit 1. That is # upstream's choice; asserted as measured. expect_out canutils/usage-canconfig-help 1 "canconfig bitrate { BR } [sample-point { SP }]" "$CC" --help expect_out canutils/usage-canconfig-noargs 1 "canconfig bittiming-constants" "$CC" expect_out canutils/usage-canconfig-ctrlmode 1 "CTRLMODE := <[loopback | listen-only | triple-sampling | berr-reporting] [on|off]>" "$CC" --help # --------------------------------------------------------------------------- # GROUP 4 -- the two short options 0002 wired up. # # candump's usage advertises `-e, --error` and `-h, --help`, and the switch has # a case for each -- but before 0002 neither letter was in the getopt_long # option string "f:t:p:o:d". getopt rejected both, and because the default: arm # prints its complaint WITHOUT exiting, the run then continued into socket(). # So `candump -h` answered with a getopt error and `socket: Address family not # supported by protocol`, exit 1, where the tool's own documentation promises # usage and exit 0. # # Measured on x86-64 (299dff7f + 0001): unpatched, both letters take the getopt # path; patched, -h is identical to --help and -e reaches the socket cleanly. # --help, --error, --filter, --version and a bare invocation are byte-identical # either way, which is why only these three cases move. # # BRINTOS-UPSTREAM-FIX: not a workaround for anything brintOS does -- two short # options the tool documents were never wired to getopt. expect_out canutils/candump-short-h 0 "Usage: candump.canutils" "$CD" -h expect_absent canutils/candump-short-h-clean 0 "invalid option" "$CD" -h expect_absent canutils/candump-short-e-clean 1 "invalid option" "$CD" -e can0 # --------------------------------------------------------------------------- # GROUP 5 -- argument validation, all of it before any socket or netlink call. # candump's --filter parser rejects a spec with no ':' and exits 1 without # opening anything. expect_out canutils/filter-malformed 1 "filter must be applied in the form id:mask[:id:mask]..." "$CD" --filter=0x123 # A well-formed filter is echoed by add_filter() before the socket is opened, so # both the parse and the printf are asserted on a kernel with no CAN. expect_out canutils/filter-one 1 "id: 0x00000123 mask: 0x000007ff" "$CD" --filter=0x123:0x7ff can0 expect_out canutils/filter-two-first 1 "id: 0x00000123 mask: 0x000007ff" "$CD" --filter=0x123:0x7ff:0x200:0x700 can0 expect_out canutils/filter-two-second 1 "id: 0x00000200 mask: 0x00000700" "$CD" --filter=0x123:0x7ff:0x200:0x700 can0 # canconfig's two pure-validation refusals. Neither touches libsocketcan. # The misspelt "pleae" is upstream's, and is asserted verbatim: a suite that # quietly matched the corrected spelling would go red the day someone fixes it # and green for the wrong reason today. expect_out canutils/canconfig-baudrate-deprecated 1 "can0: baudrate is deprecated, pleae use bitrate" "$CC" can0 baudrate expect_out canutils/canconfig-bittiming-short 1 "can0: missing bittiming parameters, try help to figure out the correct format" "$CC" can0 bittiming tq 100 # An unrecognised subcommand falls off the end of main()'s dispatch loop and # exits EXIT_SUCCESS, having done nothing at all -- so `canconfig can0 bitrat # 500000` (one typo) succeeds silently. Upstream's behaviour, asserted as # measured rather than papered over; see the design doc's Issues Found. check_rc canutils/canconfig-unknown-subcmd-rc 0 "$CC" can0 nosuchsubcommand # The interface line every bus tool prints BEFORE its socket, with the defaults # and with -f/-t/-p overrides. This is the last thing that happens offline. expect_out canutils/preline-cansend 1 "interface = can0, family = 29, type = 3, proto = 1" "$CS" can0 0x11 0x22 expect_out canutils/preline-candump 1 "interface = can0, family = 29, type = 3, proto = 1" "$CD" expect_out canutils/preline-cansequence 1 "interface = can0, family = 29, type = 3, proto = 1" "$CQ" expect_out canutils/preline-canecho 1 "interface-in = can0, interface-out = can0, family = 29, type = 3, proto = 1" "$CE" can0 expect_out canutils/preline-canecho-two 1 "interface-in = can0, interface-out = can1, family = 29, type = 3, proto = 1" "$CE" can0 can1 expect_out canutils/preline-overrides 1 "interface = can0, family = 1, type = 2, proto = 0" "$CS" -f 1 -t 2 -p 0 can0 0x11 # --------------------------------------------------------------------------- # GROUP 6 -- PLUMBING. The tools open a socket and run an ioctl for real. # # -f/-t/-p make the bus tools use a family this kernel DOES have. That turns # them into a positive test of the very code path CONFIG_CAN=n hides: socket(2) # succeeds, ioctl(SIOCGIFINDEX) runs, and the tool reaches bind(2). A package # miscompiled into rubble cannot get there, and neither can one whose failure at # socket() is masking anything else. # # AF_INET/SOCK_DGRAM/proto 0 is the pair chosen because a UDP socket needs no # privilege and no route. On a bogus name the ioctl answers ENODEV; on lo it # succeeds and bind(2) then refuses the sockaddr_can laid over a sockaddr_in. # Both answers are proof the tool got past socket(). # cansend and candump take -f/-t/-p. cansequence does NOT -- its family, type # and protocol are fixed locals and its option string is "ehpqrvi:l:", so # `cansequence -f 2` answers `invalid option -- 'f'`. It has no plumbing case, # and its pre-socket line is covered in group 5 instead. expect_out canutils/plumbing-cansend-ioctl 1 "ioctl: No such device" "$CS" -f 2 -t 2 -p 0 bstnocan0 0x11 expect_out canutils/plumbing-candump-ioctl 1 "ioctl: No such device" "$CD" -f 2 -t 2 -p 0 bstnocan0 # Those two runs must NOT have failed at socket() -- that is the whole claim. expect_absent canutils/plumbing-cansend-past-socket 1 "socket: " "$CS" -f 2 -t 2 -p 0 bstnocan0 0x11 expect_absent canutils/plumbing-candump-past-socket 1 "socket: " "$CD" -f 2 -t 2 -p 0 bstnocan0 # The strongest case in this group: a REAL name resolves, so the tool gets past # ioctl() as well and reaches bind(2) and beyond. # # What happens after bind is NOT pinned, deliberately, and the reason is worth # recording: the first draft asserted `bind: Cannot assign requested address`, # which is what an unprivileged run in the host netns produces -- and the same # command as root in a private netns binds successfully and dies at # `write: Destination address required` instead, exit 0. Two environments, two # answers, one claim. The claim is that neither socket() nor the interface # lookup failed, so that is what is asserted; anything past bind belongs to the # sockaddr_can-over-sockaddr_in overlay and says nothing about the port. if [ -e /sys/class/net/lo ]; then po=$("$CS" -f 2 -t 2 -p 0 lo 0x11 2>&1 "`. # # L lo, a real interface that is really not a CAN device -- the name # resolves, so the library composes RTM_GETLINK, sends it, blocks in # recvmsg, walks the reply and decodes the ifinfomsg, and only then returns # -1 because lo carries no IFLA_LINKINFO/IFLA_INFO_DATA. libsocketcan # prints NOTHING; the only message is canconfig's own. The absence of the E # voice is what proves the transaction completed. # # The setters are stronger still: they are RTM_NEWLINK WRITES that reach the # kernel and come back with an error the kernel chose. `RTNETLINK answers: # Operation not supported` is EOPNOTSUPP -- lo is not a CAN device, so it has no # can_bittiming_const -- and getting that answer at all means the write path # works end to end. Nothing is changed by a refused setter, which is why these # are safe to run where start/stop are not. if [ -e /sys/class/net/lo ]; then LO_OK=1 else LO_OK=0 fi # Read lo's flags BEFORE the group runs, so "untouched" is a comparison and not # an assumption about what state the guest handed us. LO_FLAGS_BEFORE=$(cat /sys/class/net/lo/flags 2>/dev/null) # --- E group: a name that cannot exist. expect_out canutils/nl-nodev-voice 1 'Cannot find device "bstnocan0"' "$CC" bstnocan0 state expect_out canutils/nl-nodev-nodump 1 "Cannot send dump request: No such device" "$CC" bstnocan0 state expect_out canutils/nl-nodev-canconfig 1 "bstnocan0: failed to get state" "$CC" bstnocan0 state expect_out canutils/nl-nodev-bitrate 1 "failed to set bitrate of bstnocan0 to 500000" "$CC" bstnocan0 bitrate 500000 expect_out canutils/nl-nodev-showall 1 "bstnocan0: failed to get bitrate" "$CC" bstnocan0 if [ "$LO_OK" -eq 1 ]; then # --- L group: the getters complete a full transaction against lo. expect_out canutils/nl-lo-state 1 "lo: failed to get state" "$CC" lo state expect_absent canutils/nl-lo-state-nl-ok 1 "Cannot find device" "$CC" lo state expect_out canutils/nl-lo-showall 1 "lo: failed to get bitrate" "$CC" lo expect_out canutils/nl-lo-clockfreq 1 "lo: failed to get clock parameters" "$CC" lo clockfreq expect_out canutils/nl-lo-btconst 1 "lo: failed to get bittiming_const" "$CC" lo bittiming-constants expect_out canutils/nl-lo-berr 1 "lo: failed to get controlmode" "$CC" lo berr-counter expect_absent canutils/nl-lo-clock-nl-ok 1 "Cannot send dump request" "$CC" lo clockfreq # --- L group: the setters reach the kernel and are refused by it. # ORACLE B (root). An unprivileged run answers `Operation not permitted` # instead -- the kernel checks CAP_NET_ADMIN before it checks the link kind -- # so a red here that reads `not permitted` means the suite stopped running as # root, not that the port regressed. expect_out canutils/nl-lo-set-bitrate-kernel 1 "RTNETLINK answers: Operation not supported" "$CC" lo bitrate 500000 expect_out canutils/nl-lo-set-bitrate-msg 1 "failed to set bitrate of lo to 500000" "$CC" lo bitrate 500000 expect_out canutils/nl-lo-set-restartms-kernel 1 "RTNETLINK answers: Operation not supported" "$CC" lo restart-ms 100 expect_out canutils/nl-lo-set-restartms-msg 1 "failed to set restart_ms of lo to 100" "$CC" lo restart-ms 100 expect_out canutils/nl-lo-set-ctrlmode-kernel 1 "RTNETLINK answers: Operation not supported" "$CC" lo ctrlmode loopback on expect_out canutils/nl-lo-set-ctrlmode-msg 1 "lo: failed to set ctrlmode" "$CC" lo ctrlmode loopback on # can_do_start/can_do_stop are the two operations a non-CAN link really # honours -- they set and clear IFF_UP and return 0 -- which is why they are # not run above. This asserts that nothing in the group flipped the interface # anyway: the flags word is compared against the one read before the group, # so a guest that handed us a down lo is not blamed for it. LO_FLAGS_AFTER=$(cat /sys/class/net/lo/flags 2>/dev/null) if [ -z "$LO_FLAGS_BEFORE" ]; then bst_skip canutils/nl-lo-untouched "no readable /sys/class/net/lo/flags on this guest" else check_eq canutils/nl-lo-untouched "$LO_FLAGS_BEFORE" "$LO_FLAGS_AFTER" fi else R="this guest has no lo, so libsocketcan has no real interface to address" for c in nl-lo-state nl-lo-state-nl-ok nl-lo-showall nl-lo-clockfreq \ nl-lo-btconst nl-lo-berr nl-lo-clock-nl-ok \ nl-lo-set-bitrate-kernel nl-lo-set-bitrate-msg \ nl-lo-set-restartms-kernel nl-lo-set-restartms-msg \ nl-lo-set-ctrlmode-kernel nl-lo-set-ctrlmode-msg nl-lo-untouched; do bst_skip "canutils/$c" "$R" done fi # --------------------------------------------------------------------------- # GROUP 8 -- the runtime probe, and the bus cases it gates. # # cansend's own operation is the probe. Upstream's order in cansend.c is the # pre-socket printf -> socket() -> ioctl(SIOCGIFINDEX) -> bind(), so its two # failures are distinguishable and say different things: # # `socket: ...` the CAN protocol family is not built # `ioctl: No such device` the family is there; that interface is not # # Only the first justifies skipping the family outright. A third answer nobody # has seen is scored as a failure rather than folded into one of the two. probe=$("$CS" vcan0 0x11 2>&1 &1 &1 &1