# BST-TIMEOUT: 200 # MEASURED: elapsed=46s for 103 cases on an idle host # # (2026-08-17, local functional verify). 200 is ~4.3x that, # # which is more headroom than the sibling 79-libndp carries # # (3.6x) and is what the nightly needs on a loaded host. # # The marker is NOT optional here: the stock 60 s default is # # only 1.3x the measured run and would be a coin toss. # # Nothing in this file suggests 46 s -- there is exactly ONE # # process spawn in the whole suite (the probe, which runs # # every assertion in one module while the shell scores its # # output), nothing waits on a timer, a peer or a descriptor, # # and the identical suite runs in ~10 ms natively. The cost # # is this guest staging a 3.6 MB wasm module plus the ~140 # # shell forks that score the probe's output: the case count # # is nearly free here, the spawns are not. . "$BST_LIB" # libnet 1.3 -- the packet construction and injection library (meta-oe # recipes-connectivity/libnet, SRCREV deaebdfe), the thing scanners and traffic # generators build their frames with. What ships here is /usr/lib/libnet.a, # /usr/lib/pkgconfig/libnet.pc, /usr/include/libnet.h + /usr/include/libnet/*, # and /usr/bin/libnet-config. It installs no program of its own. # # --------------------------------------------------------------------------- # THE LIBRARY DIVIDES IN TWO, AND ONE HALF CANNOT EXIST ON THIS KERNEL. # # * CONSTRUCTION -- libnet_build_() appends a protocol block, and # libnet_pblock_coalesce() assembles the chain into the wire frame and # computes every checksum that was requested. Not one of those touches a # descriptor. This is where every B/G/K/P/U/I/R/N case below sits. # * INJECTION -- libnet_init(LIBNET_LINK/LINK_ADV) opens # socket(PF_PACKET, SOCK_RAW, ETH_P_ALL) and libnet_write() sends on it. # This kernel is built `# CONFIG_PACKET is not set`, so AF_PACKET does not # exist and no link-layer context can be created at all. Measured in the C # group and asserted, not assumed. # # THE TICKET ASKS FOR THE FRAME ON A LIBNET_LINK_ADV CONTEXT, AND THAT IS THE # ONE THING THIS PLATFORM CANNOT GIVE. libnet_init(LIBNET_LINK_ADV, ...) is not # a construction-only mode -- it selects a device and opens the packet socket # before it returns, so libnet_adv_cull_packet() is unreachable here for want of # a context rather than for anything to do with culling. The probe therefore # builds the same frame on LIBNET_NONE ("no injection type, only construct # packets") and culls it with libnet_pblock_coalesce(), which is the public # entry point libnet_adv_cull_packet() itself delegates to after a one-line mode # check -- and it asserts the ticket's literal path anyway, in the A group, so # the platform's answer is recorded rather than argued away. # # The two paths produce IDENTICAL bytes. LINK_ADV differs only in l->aligner, a # malloc alignment pad coalesce() steps back over before returning, and # calculate_ip_offset() -- which is what positions the checksums -- walks the # pblock list rather than reading l->link_offset. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86-64 RUN, AND HERE THAT IS THE RIGHT POSTURE # # Every frame below is pinned to the bytes a native x86-64 build of # libnet_probe.c produced against libnet at this same SRCREV. Ethernet, IPv4, # TCP, UDP, ICMP and ARP are wire formats fixed by IEEE 802.3 and RFC 791/793/ # 768/792/826, so the two architectures MUST agree byte for byte and a mismatch # is a real defect -- the same posture as 79-libndp and the opposite of 79-libuv # next door, where the values the port turns on are REQUIRED to differ. # # It is also the assertion a target-only run cannot make. Setting a field and # reading it back proves the setter and the getter agree; it does not prove # either is right. A port that wrote the IPv4 total length in host byte order # would round-trip through its own mistake. Only bytes from a second build of # the same source catch that, and they were hand-checked against the RFCs rather # than merely copied out of the native run: # # B06 = 66778899aabb 001122334455 0800 # ^dst MAC ^src MAC ^ethertype 0x0800 IPv4 (RFC 894) # 45 00 0028 beef 4000 40 06 8fa9 c0000201 c6336402 # ^v4/IHL5 ^tos ^len 40 ^DF ^ttl 64 ^198.51.100.2 # ^id 0xbeef ^proto 6 TCP # ^header checksum, computed by libnet # ^192.0.2.1 # 3039 0050 01020304 05060708 50 12 7fff 02ff 0000 # ^sport 12345 ^dport 80 ^SYN|ACK ^checksum, computed # ^seq ^ack ^doff 5 ^win 32767 ^urg 0 # # Both checksums were verified by hand against the one's-complement sums: # 0x8fa9 over the IPv4 header with the field zeroed, and 0x02ff over the RFC 793 # pseudo-header plus the segment. K01/K02 are their negative control -- the same # builders with checksumming switched OFF must leave both fields 0000, which is # what separates "libnet computed this" from "libnet copied the caller's zero # through". PROBE=${BST_LIBNET_PROBE:-/usr/libexec/libnet-tests/libnet-probe} ARCHIVE=/usr/lib/libnet.a PCFILE=/usr/lib/pkgconfig/libnet.pc INC=/usr/include NETCONFIG=/usr/bin/libnet-config # The version the recipe pins. Asserted rather than read back, so a recipe # bumped without updating this suite is a red here instead of a silent change of # subject -- the pinned bytes above are the oracle for THIS version only. WANT_VERSION=1.3 if [ ! -f "$ARCHIVE" ]; then bst_skip libnet/present "libnet not installed on this image" exit 0 fi bst_ok libnet/present # --- the package is whole ----------------------------------------------------- check libnet/staticlib test -s "$ARCHIVE" check libnet/pkgconfig test -s "$PCFILE" check libnet/hdr-libnet test -s "$INC/libnet.h" check libnet/hdr-functions test -s "$INC/libnet/libnet-functions.h" check libnet/hdr-headers test -s "$INC/libnet/libnet-headers.h" check libnet/hdr-macros test -s "$INC/libnet/libnet-macros.h" check libnet/hdr-structures test -s "$INC/libnet/libnet-structures.h" check libnet/hdr-types test -s "$INC/libnet/libnet-types.h" check libnet/hdr-asn1 test -s "$INC/libnet/libnet-asn1.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libnet/no-shared "/usr/lib/libnet.so" "/usr/lib/libnet.so.9" # The .pc must name the version the recipe pinned, or a consumer resolving # libnet through pkg-config gets a different answer from the one this suite # pinned its bytes against. pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -n 1) check_eq libnet/pkgconfig-version "$WANT_VERSION" "$pcver" check_out libnet/pkgconfig-libs "-lnet" cat "$PCFILE" # libnet-config is the third deliverable the ticket names, and it is a shell # script whose paths Yocto's binconfig class rewrites for the target -- so the # thing worth asserting is that the rewrite landed on /usr and not on a build # host's sysroot path, which is the way that class fails. if [ -x "$NETCONFIG" ]; then bst_ok libnet/config-present out=$("$NETCONFIG" --libs 2>&1); rc=$? if [ "$rc" -eq 0 ] && [ "${out#*-lnet}" != "$out" ]; then bst_ok libnet/config-libs else bst_notok libnet/config-libs "rc=$rc $(_bst_1line "$out")" fi case "$out" in *recipe-sysroot*|*/tmp-glibc/*|*/tmp-musl/*|*/workdir/*) bst_notok libnet/config-no-buildpaths "libnet-config --libs leaks a build path: $(_bst_1line "$out")" ;; *) bst_ok libnet/config-no-buildpaths ;; esac check_out libnet/config-cflags "-I" "$NETCONFIG" --cflags check_out libnet/config-defines "-D_DEFAULT_SOURCE" "$NETCONFIG" --defines else bst_notok libnet/config-present "$NETCONFIG absent or not executable" fi # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. Probing keeps this measuring libnet # rather than measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in libnet_init libnet_destroy libnet_clear_packet libnet_version \ libnet_build_tcp libnet_build_ipv4 libnet_build_ethernet \ libnet_build_udp libnet_build_icmpv4_echo libnet_build_arp \ libnet_build_tcp_options libnet_build_ipv4_options \ libnet_pblock_coalesce libnet_adv_cull_packet libnet_adv_cull_header \ libnet_adv_free_packet libnet_adv_write_link \ libnet_getpbuf libnet_getpbuf_size libnet_getpacket_size \ libnet_name2addr4 libnet_addr2name4 libnet_toggle_checksum \ libnet_do_checksum libnet_geterror libnet_getdevice \ libnet_write libnet_open_link libnet_plist_chain_new; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnet/sym-$sym" else bst_notok "libnet/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the probe ---------------------------------------------------------------- if [ ! -x "$PROBE" ]; then bst_skip libnet/probe-runs "$PROBE absent from the image" exit 0 fi pout=${TMPDIR:-/tmp}/bst-libnet-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libnet/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libnet/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libnet/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libnet/probe-complete else bst_notok libnet/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -n 1 "$pout" 2>/dev/null))" fi # val -- the probe's value for one case, empty when the probe stopped # first. libnet writes diagnostics into the caller's errbuf and the probe's # output is captured with 2>&1, so this matches on the ID in field 1 rather than # taking the line by position. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libnet/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libnet/$2" else bst_notok "libnet/$2" "want:'$3' got:'$_got'" fi } # The library's own idea of what it is. If this moves, every pinned byte below # is an oracle for a different version and the suite is measuring nothing. want V01 version "libnet version $WANT_VERSION" # --- what the platform grants, reported before anything depends on it --------- # LIBNET_NONE opens nothing, so a failure here is not a platform question and # every case after it would be meaningless. want C01 ctx-none-opens "OK" # An unsupported injection type must be refused, not half-built. want C06 ctx-bogus-refused "REFUSED" printf '#diag: libnet C02D=%s D02=%s ifaces=%s\n' \ "$(val C02D)" "$(val D02)" \ "$(awk '$1=="D01"{printf "%s ", $3}' "$pout")" # AF_PACKET is the whole of libnet's injection half, and this kernel is built # without it. Scored the way 79-libndp scores its IPv6 group: OK is GREEN, so # the case flips by itself the day CONFIG_PACKET lands and needs no edit here; # the exact tracked errno is a red that names its issue; anything else is an # UNTRACKED red, because an attribution matching "anything not OK" would swallow # a genuinely new failure (the native x86-64 control returns EPERM for an # unprivileged caller and must not be absorbed into this). c03=$(val C03) case "$c03" in OK) bst_ok libnet/plat-af-packet ;; EAFNOSUPPORT) bst_notok libnet/plat-af-packet "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL) -> EAFNOSUPPORT; this kernel is built '# CONFIG_PACKET is not set' -- brintos/linux#81 (https://brintos.io/brintos/linux/issues/81)" ;; "") bst_notok libnet/plat-af-packet "no value on target (probe stopped before C03)" ;; *) bst_notok libnet/plat-af-packet "socket(AF_PACKET, SOCK_RAW, ETH_P_ALL) -> $c03, which is neither OK nor the tracked EAFNOSUPPORT" ;; esac # Controls. Without both of these a refusal at C03 could be read as "this guest # has no networking", which would misfile the finding: C04 shows raw sockets as # such are granted, C05 that there is a socket layer at all. want C04 plat-inet-raw "OK" want C05 plat-inet-dgram "OK" # The assertion that holds on EVERY platform, and the one that is actually about # libnet rather than about the kernel: whatever the packet socket answers, # libnet_init(LIBNET_LINK_ADV) must agree with it. That catches the defect that # would be libnet's -- translating, swallowing or inventing a result on the way # out -- and it needs no edit when the platform's answer changes. c02=$(val C02) if [ -z "$c02" ] || [ -z "$c03" ]; then bst_notok libnet/init-agrees-with-syscall "no value on target (C02='$c02' C03='$c03')" elif [ "$c03" = "OK" ] && [ "$c02" = "OK" ]; then bst_ok libnet/init-agrees-with-syscall elif [ "$c03" != "OK" ] && [ "$c02" = "FAIL" ]; then bst_ok libnet/init-agrees-with-syscall elif [ "$c03" = "OK" ] && [ "$c02" = "FAIL" ]; then bst_notok libnet/init-agrees-with-syscall "the packet socket opens but libnet_init(LIBNET_LINK_ADV) still failed: $(val C02D) -- read the D01 interface diag above, libnet_select_device runs first and needs an interface carrying an AF_INET address" else bst_notok libnet/init-agrees-with-syscall "libnet_init reported '$c02' while socket(AF_PACKET,...) reported '$c03'" fi # --- the ticket's frame, byte for byte ---------------------------------------- FRAME=66778899aabb001122334455080045000028beef400040068fa9c0000201c633640230390050010203040506070850127fff02ff0000 want B01 build-tcp "OK" want B02 build-ipv4 "OK" want B03 build-ethernet "OK" # The chain's size BEFORE assembly: 14 + 20 + 20. A mismatch here and a matching # B05 would mean coalesce silently resized the chain. want B12 chain-size "54" want B04 cull-rc "1" want B05 cull-size "54" # The headline: the whole assembled Ethernet + IPv4 + TCP frame, with both # checksums libnet computed, identical to the x86-64 reference. want B06 frame-bytes "$FRAME" # The same frame split at its header boundaries, so a mismatch at B06 localises # to one layer instead of to 54 bytes. want B07 frame-ethernet "66778899aabb0011223344550800" want B08 frame-ipv4 "45000028beef400040068fa9c0000201c6336402" want B09 frame-tcp "30390050010203040506070850127fff02ff0000" # Pinned separately because they are the fields the caller did NOT supply. want B10 cksum-ipv4 "8fa9" want B11 cksum-tcp "02ff" # --- the per-block buffers, read back one at a time --------------------------- # libnet_getpbuf is the public per-ptag accessor and needs no advanced mode, so # it reaches here where libnet_adv_cull_header does not. It returns each block's # OWN buffer, into which no checksum has been written -- the checksums are # computed during coalesce, into the assembled copy. G04 and G06 therefore carry # zeros where B08/B09 carry 8fa9 and 02ff, and that difference is pinned rather # than smoothed over: it is the proof that the checksum pass is a pass and not # something the builders did. want G01 pbuf-size-eth "14" want G02 pbuf-eth "66778899aabb0011223344550800" want G03 pbuf-size-ipv4 "20" want G04 pbuf-ipv4 "45000028beef400040060000c0000201c6336402" want G05 pbuf-size-tcp "20" want G06 pbuf-tcp "30390050010203040506070850127fff00000000" # --- the ticket's literal cull path ------------------------------------------- # On a non-advanced context both cull entry points must REFUSE. That is also how # this suite establishes the shipped library was built stock: upstream's # --enable-tests defines LIBNET_ENABLE_TESTS, which compiles both checks out, and # a library carrying it would answer ACCEPTED here. The port deliberately does # not set it -- weakening an API guard in the shipped library to make a test # easier would ship a different libnet from every other distro's. want A01 cull-packet-refused-without-adv "REFUSED" want A02 cull-header-refused-without-adv "REFUSED" # A ptag that was never issued must be refused, not answered with whatever the # list happens to end on. want A06 cull-header-bad-ptag-refused "REFUSED" # LIBNET_LINK_ADV + libnet_adv_cull_packet, exactly as the ticket words it. # Scored against the platform's own answer at C03 so it needs no edit either # way: where the packet socket opens this is a hard byte-exact assertion against # the same frame the LIBNET_NONE path produced; where it does not, the only # acceptable value is NO-CONTEXT -- a BUILD-FAIL or CULL-FAIL there would mean # libnet failed for some OTHER reason and must stay an untracked red. a03=$(val A03); a04=$(val A04); a05=$(val A05) if [ "$c03" = "OK" ]; then want A03 adv-cull-size "54" want A04 adv-cull-frame "$FRAME" want A05 adv-cull-header "66778899aabb0011223344550800" elif [ "$a03" = "NO-CONTEXT" ] && [ "$a04" = "NO-CONTEXT" ] && [ "$a05" = "NO-CONTEXT" ]; then bst_notok libnet/adv-cull-path "libnet_init(LIBNET_LINK_ADV) cannot be created on this kernel, so libnet_adv_cull_packet/_header are unreachable: no AF_PACKET (# CONFIG_PACKET is not set) -- brintos/linux#81 (https://brintos.io/brintos/linux/issues/81)" else bst_notok libnet/adv-cull-path "no LINK_ADV context, but the A group reports A03='$a03' A04='$a04' A05='$a05' rather than NO-CONTEXT -- that is a different failure from the tracked one" fi # --- the checksums are libnet's, not the caller's ----------------------------- # The negative control for B10/B11. Same three builders, same zero in the sum # argument, but with checksumming toggled OFF: the fields must then STAY zero. # Without this, B10 would pass just as happily against a library that copied the # caller's zero straight through, and the ticket's "including the checksums # libnet computes itself" would be unproven. want P01 clear-empties-chain "0" want K01 cksum-off-ipv4 "0000" want K02 cksum-off-tcp "0000" want K03 cksum-off-frame "66778899aabb001122334455080045000028beef400040060000c0000201c633640230390050010203040506070850127fff00000000" # --- the builder chain is repeatable and ptag-addressable --------------------- # P02 rebuilds from a cleared context and must reproduce B06 exactly: a library # carrying state across libnet_clear_packet would differ here and nowhere else. want P02 rebuild-is-identical "$FRAME" # P03..P05 rewrite ONE header through its ptag. The frame must stay 54 bytes, # the destination port must become 01bb (443), the TCP checksum must move to # 0194, and the IPv4 checksum must NOT move -- the IP header did not change. # A library that recomputed everything, or nothing, differs here. want P03 ptag-reuse "SAME-PTAG" want P04 ptag-rewrite-size "54" want P05 ptag-rewrite-frame "66778899aabb001122334455080045000028beef400040068fa9c0000201c6336402303901bb010203040506070850127fff01940000" # --- three more protocols, so the codec is measured beyond one frame ---------- # UDP over IPv4 with an 8-byte payload: the payload path plus the UDP checksum, # which unlike TCP's is optional in IPv4 and libnet computes anyway. want U01 udp-build "OK" want U02 udp-size "50" want U03 udp-frame "66778899aabb001122334455080045000024beef400040118fa2c0000201c6336402003514e90010a9226c69626e6574210a" want U04 udp-cksum-ipv4 "8fa2" want U05 udp-cksum-udp "a922" # ICMPv4 echo request with a 16-byte payload -- a different checksum algorithm # again (RFC 792 covers the whole message, with no pseudo-header). want I01 icmp-build "OK" want I02 icmp-size "58" want I03 icmp-frame "66778899aabb00112233445508004500002cbeef400040018faac0000201c633640208002a0912340001deadbeef000102030405060708090a0b" want I04 icmp-cksum-ipv4 "8faa" want I05 icmp-cksum-icmp "2a09" # ARP over Ethernet -- the one frame here with no IP header and no checksum at # all, so it exercises coalesce's path for a chain that needs no checksum pass. want R01 arp-build "OK" want R02 arp-size "42" want R03 arp-frame "66778899aabb00112233445508060001080006040001001122334455c0000201000000000000c6336402" # --- the address helpers the builders are fed from ---------------------------- want N01 name2addr4 "c0000201" want N02 addr2name4 "192.0.2.1" want N03 name2addr4-refuses-bad "REFUSED" # A LIBNET_NONE context selects no device, so libnet_getdevice must report none. # A name here would mean the construction-only mode opened something. want N04 none-ctx-has-no-device "NULL" rm -f "$pout"