# BST-TIMEOUT: 300 # MEASURED: BST-SUITE ebtables elapsed=65s cases=80 on the # # local functional verify (2026-08-22). 80 cases, nearly all # # of them one exec of a 4.8 MB static wasm binary that exits # # inside its own option parser; the four kernel-gated ones # # fail on a getsockopt and return at once. 300 is the bound # # the ticket gates and the nightly passes, at ~4.6x the # # measured cost. . "$BST_LIB" # ebtables 2.0.11 -- meta-openembedded/meta-networking recipes-filter/ebtables. # # WHICH ebtables THIS IS, BECAUSE THE TWO ARE NOT THE SAME PROGRAM. This is # LEGACY ebtables: Bart De Schuymer's own C implementation, one binary at # /usr/sbin/ebtables-legacy (reached as /sbin/ebtables), with its own kernel ABI # and its own extensions compiled from extensions/ebt_*.c. It is NOT the # ebtables-nft wrapper iptables ships -- that one is a front-end over # xtables-nft-multi, has none of the arm this suite scores, and is not built # anywhere in this distro (BRINTOS-1362 ships the legacy backend only). # # --------------------------------------------------------------------------- # GROUP B IS WHY THIS PORT NEEDED A BBAPPEND AT ALL. # # Every match, watcher, target and table registers itself from a # `static void __attribute__((constructor)) _init(void)` in its own translation # unit; nothing references any other symbol in those units. Upstream puts them # in a SHARED libebtc, which is loaded whole. This arm has no shared libraries, # and an archive member is pulled only to resolve an undefined symbol -- so a # stock --enable-static link drops all twenty-eight of them and produces a # binary that SEGFAULTS on --version (measured natively; ebt_initialize_entry() # dereferences the standard target ebt_find_target() could not find). # --whole-archive is the portable answer and wasmld does not implement it on a # bitcode archive: it explodes the archive into a need-only pool, and the flag # falls through to the final wasm-ld stage where no archive is left. The # bbappend links the objects directly instead. Asking an extension for its OWN # help text is the only thing that distinguishes the two builds, and B/ext-bogus # is the control that makes the rest of the group mean something. # # --------------------------------------------------------------------------- # GROUP C IS THE OFFLINE ARM, AND IT IS THE ATOMIC FILE. # # With --atomic-file , every rule command reads the table from and writes # it back to instead of talking to the bridge tables. That makes the whole # rule parser, all twenty-one extensions and the printer reachable with no # socket -- which on this kernel is the only way to reach them at all, because # arch/wasm32 has no CONFIG_NETFILTER (brintos/linux#86). # # THE FIRST FILE IS THE CATCH, AND data/ebtables-seed IS WHY THIS WORKS. # ebtables can only ever create one from the kernel: --atomic-init and # --atomic-save are both getsockopt() on a raw socket, and ebt_get_table() calls # ebt_print_bug("Getting initial table data from a file is impossible") if you # ask for init with a filename set. Every other command opens the file "r+b" and # stops with "Could not open file". So the seed writes the empty table directly, # in exactly the layout communication.c:store_table_in_file() writes -- and # because it is compiled for THIS arch it gets sizeof(struct ebt_replace) right # without anyone hard-coding 80. A wrong seed cannot pass quietly: ebtables # answers "File has wrong size" or "is corrupt" and every case below goes # red with that text. S/atomic-init asserts that the tool's OWN bootstrap is # still red, so the seed is never mistaken for the kernel working. # # THE COMPARISON IS BYTE FOR BYTE against a native x86_64 build of this same # tarball, linked the same way, applying the same data/ebtables-ref.rules # (src/ebtables_mknative.sh). ebtables' printer re-derives every rule from the # packed kernel-ABI blob, so a wrong struct offset, a wrong endianness or a # dropped extension surfaces as a rule that prints differently rather than as a # crash -- and the output is pure text with no address, size or byte-order # content, so the two architectures must agree exactly. The oracle's quirks are # the most sensitive part of it: the empty --string-algo, the missing space # before --nflog-group, the trailing comma in --among-src, "-d BGA". # # --------------------------------------------------------------------------- # GROUP D SCORES THE REJECTIONS, WHICH GROUP C CANNOT. # # A rule that parses proves an extension registered. A rule that is REJECTED # with the right words proves the CHECKER ran -- the chain/hook validity rules, # the range and enum parsers, the mac parser, the "protocol must be IPv4" # cross-check. A build that accepted every malformed rule would still list the # good set identically. D/bad-09 is upstream's own defect kept as a control: # ebt_string.c declares --string-icase with required_argument although it # documents it as a flag, and x86_64 says exactly the same thing. # # --------------------------------------------------------------------------- # THE EXPECTED REDS: FOUR CASES, ONE KERNEL GAP, LEFT RED ON PURPOSE. # # S01-S04 are every path that reaches the bridge tables: -L, --atomic-init, # ebtables-legacy-restore and ebtables-legacy-save. Legacy ebtables gets its # handle from get_sockfd() + getsockopt(IPPROTO_IP, EBT_SO_GET_INFO); # arch/wasm32/configs/wasm32_defconfig sets CONFIG_NET=y with no # CONFIG_NETFILTER at all, so there is no ebtables table to ask and nothing # registers the option. brintos/linux#86 # (https://brintos.io/brintos/linux/issues/86) -- the same gap iptables, # libnfnetlink and the two libnetfilter_* ports are held by. All four are # reported failing and NOT skipped, so they flip green by themselves the day the # option lands. # # S04 ASSERTS OUTPUT, NOT EXIT STATUS, AND THAT IS THE POINT. ebtables-save is a # shell script that enumerates tables by grepping /proc/modules for ebtable_*. # This kernel has no loadable modules, so the loop body never runs: it prints # its "# Generated by" header, exits 0, and a bare `check` on it PASSES -- for # the one reason that makes it worthless. Asserting the "*filter" line a working # kernel must emit is what makes the case mean something. (99-iptables hit the # identical trap with iptables-save.) # # WHAT SEPARATES "THIS KERNEL HAS NO NETFILTER" FROM "ebtables IS BROKEN" is # group C: the very rule set S03 cannot commit parses offline and prints byte # for byte like the native oracle. PATH="$PATH:/usr/sbin:/sbin"; export PATH require ebtables ebtables/present tmp=${TMPDIR:-/tmp}/bst-ebtables.$$ mkdir -p "$tmp" 2>/dev/null # A suite's FIRST BST result line does not reach the host -- the kernel's benign # post-==BST-BEGIN== audit line displaces it (BRINTOS-1149 host-side, # brintos/linux#96 and linux-in-the-browser#138 for the console loss). The # harness counts total-minus-failures, so a dropped line is counted as a PASS, # which means the eaten position must never carry an assertion anyone depends # on. Remove when BRINTOS-1149 is fixed. bst_ok ebtables/suite-canary # --- A: identity, install paths, and the shape of the port ----------------- check_out ebtables/version "ebtables v2.0.11 (legacy)" ebtables -V check_out ebtables/usage "Usage:" ebtables -h check ebtables/legacy-path test -x /usr/sbin/ebtables-legacy check ebtables/sbin-symlink test -x /sbin/ebtables check ebtables/restore-path test -x /usr/sbin/ebtables-legacy-restore check ebtables/save-path test -x /usr/sbin/ebtables-legacy-save check ebtables/daemon-path test -x /usr/sbin/ebtablesd check ebtables/daemon-u-path test -x /usr/sbin/ebtablesu check ebtables/lib-static test -f /usr/lib/libebtc.a # netbase owns /etc/ethertypes -- the recipe patches out its own copy, and every # symbolic protocol name in the reference rules resolves through it. check ebtables/ethertypes test -f /etc/ethertypes check_out ebtables/ethertypes-ipv4 "IPv4" grep -w IPv4 /etc/ethertypes # The nft front-end is deliberately not part of this distro. Assert its absence # so a later change that quietly restores it has to come past this case. check_rc ebtables/no-nft 1 test -e /usr/sbin/ebtables-nft # --- B: the registry is LINKED IN, not loaded from a shared object --------- check_out ebtables/tbl-filter "Supported chains for the filter table:" ebtables -t filter -h check_out ebtables/tbl-nat "PREROUTING OUTPUT POSTROUTING" ebtables -t nat -h check_out ebtables/tbl-broute "Supported chain for the broute table:" ebtables -t broute -h check_out ebtables/ext-ip "ip options:" ebtables -h ip check_out ebtables/ext-ip6 "ip6 options:" ebtables -h ip6 check_out ebtables/ext-arp "arp options:" ebtables -h arp check_out ebtables/ext-vlan "vlan options:" ebtables -h vlan check_out ebtables/ext-802_3 "802_3 options:" ebtables -h 802_3 check_out ebtables/ext-among "\`among' options:" ebtables -h among check_out ebtables/ext-limit "limit options:" ebtables -h limit check_out ebtables/ext-mark_m "mark option:" ebtables -h mark_m check_out ebtables/ext-pkttype "pkttype options:" ebtables -h pkttype check_out ebtables/ext-stp "stp options:" ebtables -h stp check_out ebtables/ext-string "string options:" ebtables -h string check_out ebtables/wat-log "log options:" ebtables -h log check_out ebtables/wat-nflog "nflog options:" ebtables -h nflog check_out ebtables/wat-ulog "ulog options:" ebtables -h ulog check_out ebtables/tgt-mark "mark target options:" ebtables -h mark check_out ebtables/tgt-audit "AUDIT target options:" ebtables -h AUDIT check_out ebtables/tgt-redirect "redirect option:" ebtables -h redirect # dnat, snat and arpreply are nat-table targets: -h resolves them only with # the table that carries them selected. check_out ebtables/tgt-dnat "dnat options:" ebtables -t nat -h dnat check_out ebtables/tgt-snat "snat options:" ebtables -t nat -h snat check_out ebtables/tgt-arpreply "arpreply target options:" ebtables -t nat -h arpreply # expect_fail : for a case whose whole # point is that the tool REFUSES. check_out insists on rc=0 and would red these # for the behaviour they exist to assert; check alone would ignore the words. expect_fail() { _id=$1; _wrc=$2; _wout=$3; shift 3 if _out=$("$@" 2>&1); then _rc=0; else _rc=$?; fi check_eq "$_id" "$_wrc|$_wout" "$_rc|$_out" } # THE CONTROL. If this reported an extension, every case above would be # meaningless -- it would mean -h prints unconditionally rather than from the # extension's own registered handler. expect_fail ebtables/ext-bogus 255 "Extension 'brintos-no-such-ext' not found." \ ebtables -h brintos-no-such-ext # --- C: the offline arm -- the atomic file against a native x86_64 oracle --- # data/ is installed 0644 and the rootfs is read-only at browser runtime, so the # payload is copied to a writable path and made executable there. Never assert # test -x on the shipped payload: that passes only against a hand-placed copy. seed=$tmp/ebtables-seed if cp "$BST_DATA/ebtables-seed" "$seed" 2>/dev/null && chmod +x "$seed" 2>/dev/null; then bst_ok ebtables/seed-stage else bst_notok ebtables/seed-stage "cannot stage $BST_DATA/ebtables-seed" fi check ebtables/seed-filter "$seed" "$tmp/t.filter" filter check ebtables/seed-nat "$seed" "$tmp/t.nat" nat check ebtables/seed-broute "$seed" "$tmp/t.broute" broute # The seed is only useful if ebtables agrees it is a table. A wrong # sizeof(struct ebt_replace) stops here, loudly, with "has wrong size". check_out ebtables/seed-readable "Bridge chain: FORWARD, entries: 0, policy: ACCEPT" \ ebtables -t filter --atomic-file "$tmp/t.filter" -L # Apply every reference rule, each as its own exec, and report the first that # does not take (with the rule and the tool's own words). applied=0; badline=""; badmsg=""; badrc=0 REFRULES=$(bst_local "$BST_DATA/ebtables-ref.rules") # tmpfs copy: this loop runs in this shell (brintos/linux#73) while IFS= read -r line; do case "$line" in ''|'#'*) continue ;; esac tbl=${line%% *}; args=${line#* } if out=$(ebtables -t "$tbl" --atomic-file "$tmp/t.$tbl" $args 2>&1); then if [ -n "$out" ]; then [ -n "$badline" ] || { badline=$line; badmsg=$out; badrc=0; } else applied=$((applied + 1)) fi else rc=$? [ -n "$badline" ] || { badline=$line; badmsg=$out; badrc=$rc; } fi done < "$REFRULES" if [ -z "$badline" ]; then bst_ok ebtables/ref-apply else bst_notok ebtables/ref-apply "rc=$badrc on [$badline]: $(_bst_1line "$badmsg")" fi check_eq ebtables/ref-applied-count 34 "$applied" { for t in filter nat broute; do echo "##### $t -L"; ebtables -t "$t" --atomic-file "$tmp/t.$t" -L echo "##### $t -L --Lc --Ln --Lmac2"; ebtables -t "$t" --atomic-file "$tmp/t.$t" -L --Lc --Ln --Lmac2 echo "##### $t -L --Lx"; ebtables -t "$t" --atomic-file "$tmp/t.$t" -L --Lx done } > "$tmp/got.out" 2>&1 # The whole-file case is the ticket's criterion; the nine per-section cases # below exist so a disagreement names the table and the listing form. check ebtables/list-identical cmp -s "$tmp/got.out" "$BST_DATA/ebtables-ref.out" sec() { awk -v t="$1" 'index($0,"##### ")==1 { p = (substr($0,7) == t); next } p' "$2"; } # id|form pairs: a BST result line is whitespace-delimited, so the case id # cannot carry the option string verbatim. for t in filter nat broute; do for pair in "plain:-L" "counters:-L --Lc --Ln --Lmac2" "save:-L --Lx"; do id=${pair%%:*}; form=${pair#*:} check_eq "ebtables/list-$t-$id" \ "$(sec "$t $form" "$BST_DATA/ebtables-ref.out")" \ "$(sec "$t $form" "$tmp/got.out")" done done # --- D: the rejections -- exact diagnostic and exit status ------------------ n=0 while IFS= read -r line; do case "$line" in ''|'#'*) continue ;; esac n=$((n + 1)) tbl=${line%% *}; args=${line#* } want=$(sed -n "${n}p" "$BST_DATA/ebtables-bad.out") "$seed" "$tmp/b.$tbl" "$tbl" >/dev/null 2>&1 if out=$(ebtables -t "$tbl" --atomic-file "$tmp/b.$tbl" $args 2>&1); then rc=0; else rc=$?; fi got=$(printf '%d\t%s' "$rc" "$out") check_eq "ebtables/bad-$(printf '%02d' "$n")" "$want" "$got" done < "$BST_DATA/ebtables-bad.rules" check_eq ebtables/bad-count 19 "$n" # Two diagnostics that need no table at all, so they run even where the atomic # file cannot be created. expect_fail ebtables/diag-badtable 255 "Bad table name 'brintos-no-such-table'." \ ebtables -t brintos-no-such-table -L expect_fail ebtables/diag-nofile 255 "Could not open file $tmp/brintos-no-such-file." \ ebtables --atomic-file "$tmp/brintos-no-such-file" -L # --- S: expected red -- brintos/linux#86, no CONFIG_NETFILTER in this kernel - check ebtables/list ebtables -L check ebtables/atomic-init ebtables --atomic-file "$tmp/init.f" --atomic-init printf '*filter\n:INPUT ACCEPT\nCOMMIT\n' > "$tmp/restore.in" check ebtables/restore sh -c 'ebtables-legacy-restore < "$0"' "$tmp/restore.in" # Output, not exit status -- see the header. ebtables-legacy-save enumerates # tables from /proc/modules and exits 0 having printed only its header here. check_out ebtables/save "*filter" ebtables-legacy-save rm -rf "$tmp"