# BST-TIMEOUT: 300 # MEASURED: see the ==BST-SUITE== line on any run. The # # scoring forks almost nothing -- every case below is a # # shell string compare against values awk lifted from ONE # # probe run -- so the cost is the single exec of the probe. # # 300 is the bound the ticket gates and the nightly pass. . "$BST_LIB" # libnetfilter_log 1.0.2 -- the userspace library for the kernel's packet # logging subsystem, i.e. the API for binding a log group over the # NFNL_SUBSYS_ULOG message family and reading the packets nfnetlink_log # delivers on it. What ships under --disable-shared is # /usr/lib/libnetfilter_log.a and /usr/lib/libnetfilter_log_libipulog.a, three # headers under /usr/include/libnetfilter_log/ and two .pc files. There is no # CLI and no daemon; the tarball's utils/ are check_PROGRAMS that `make` does # not build and `make install` discards, and two of the three open a netlink # socket and then block forever in a recv() loop, so none can self-drive. On an # image with no compiler that leaves nothing that ships able to drive a line of # this library -- hence the probe, built in the recipe against the archive that # ships. # # --------------------------------------------------------------------------- # THIS LIBRARY OWNS A SOCKET, AND THAT DECIDES THE SHAPE OF THIS FILE. # # It is the same case as the sibling port libnetfilter_conntrack (suite # 79-libnetfilter-conntrack, BRINTOS-1243) and the opposite of # libnetfilter_cthelper (79-libnetfilter-cthelper, BRINTOS-964), which owns no # descriptor at all and is therefore fully measurable on a kernel with no # netfilter. Here the library is two layers and only one of them runs on this # image: # # * A FRAMING layer -- nflog_nlmsg_put_header, nflog_attr_put_cfg_cmd, # nflog_attr_put_cfg_mode, nflog_nlmsg_parse, nflog_nlmsg_snprintf and the # twenty-odd nflog_get_* accessors. None of it touches a descriptor. It is # 150 of the 165 probe cases below and every one of them runs here. # * A TRANSPORT layer -- mnl_socket_open(NETLINK_NETFILTER) and everything # that needs the kernel to answer. It begins at # socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER). That is the S group, # twelve cases, and it is expected red here for a named reason. # # --------------------------------------------------------------------------- # WHAT THIS PORT ESTABLISHES THAT THE CONNTRACK ONE DID NOT: THE WHOLE CLASSIC # HANDLE API IS UNREACHABLE HERE, NOT JUST ITS QUERY PATH. # # SINCE hwjs-cc#15's fix: nfnl_step's VLA, and the two others in # src/libnfnetlink.c, no longer refuse. Whether nflog_open() now links was # not measured -- test plan ยง486.4 records that the nfl and nfq refusals # have different discriminators -- and this probe was not changed. # # On the sibling port the older libnfnetlink transport was found UNLINKABLE on # this arm, not merely unavailable at run time: nfnl_step()'s VLA # `struct nfattr *tb[ssh->cb[type].attr_count]` (src/libnfnetlink.c:1347) makes # the coroutinize pass REFUSE that frame, and a refused frame on glibc's static # setjmp/longjmp unwind path takes the whole link down -- # brintos/hwjs-cc#15 (https://brintos.io/brintos/hwjs-cc/issues/15). There, # nfct_open() still linked and only nfct_query() did not. # # HERE THE LINE FALLS EARLIER, AND READING IT OFF THAT TICKET WOULD HAVE BEEN # WRONG IN BOTH DIRECTIONS. nflog_open() itself cannot be linked. It is not # that it reaches nfnl_step() on a call path -- it does not -- but that # nflog_open_nfnl() calls nfnl_callback_register() to install the # NFULNL_MSG_PACKET handler, which makes the callback-dispatch machinery live # in the merged module under the open-world indirect-dispatch analysis; # nfnl_step() then comes with it and refuses. # # MEASURED, not deduced, with the recipe's own do_compile command line: # # arm B probe as written FATAL x2, REFUSE fn=nfnl_step # arm C ONE statement changed, h = nflog_open() # replaced by h = NULL LINKED x2, nfnl_step absent # arm D arm C plus a direct nfnl_open() / # nfnl_close() pair LINKED x2 # # D is the arm that identifies the mechanism rather than merely reproducing the # effect: libnfnetlink's SOCKET layer links perfectly well on this arm, so the # trigger is the callback registration and nothing else. Without D the obvious # and wrong conclusion is "libnfnetlink does not link here", which would have # ruled out a transport that in fact works. # # CONSEQUENCE FOR THIS SUITE. nflog_open, nflog_bind_pf, nflog_bind_group, # nflog_set_mode, nflog_unbind_group, nflog_fd and nflog_close cannot be called # by anything on this image, so the ticket's pass criterion -- bind a log # group, set the copy mode and range, unbind cleanly, report each return value, # expected errno on an invalid group -- is driven over libmnl instead, which is # the transport upstream's own utils/nf-log.c uses. The messages are composed # by THIS LIBRARY (nflog_nlmsg_put_header + nflog_attr_put_cfg_cmd + # nflog_attr_put_cfg_mode), so the criterion is still exercised through the # library under test; only the write syscall is somebody else's. # # Every S case ACKs, so the value it reports is the KERNEL'S, lifted out of # NLMSG_ERROR by mnl_cb_run -- not a local success that only says the write # left the process. That is what makes S08 and S10 mean anything. # # The classic entry points are still asserted PRESENT in the archive by the # symbol census below. What is in the archive and what can be linked against it # are different questions, and the census answers the first one. # # --------------------------------------------------------------------------- # THE ORACLE. # # Every composed buffer and every parsed value is scored against output pinned # from a native x86-64 build of this same 1.0.2 source against libmnl 1.0.5 and # libnfnetlink 1.0.2. Netlink's TLV encoding is a wire format -- 4-byte # alignment, a 4-byte struct nlattr of {u16 len, u16 type}, struct nlmsghdr 16 # bytes and struct nfgenmsg 4 on both a 32- and a 64-bit build -- and this # library puts its scalars on the wire in NETWORK byte order, so the two # architectures MUST agree exactly and a disagreement is a real defect. # Set-then-get through the library's own parser would prove only that its # writer and its reader agree: a port that placed nla_len at the wrong offset # would round-trip through its own mistake and report every field correct. # # THE ORACLE REACHES THE TRANSPORT LAYER TOO, so the S values below are # MEASURED against a kernel that HAS nfnetlink_log and not merely asserted as # the criterion a configured kernel ought to meet. Run under `unshare -rn` the # same probe source, on x86-64, binds protocol family AF_INET, binds log group # 42, sets NFULNL_COPY_PACKET/0xffff and then NFULNL_COPY_META/0, unbinds, and # unbinds the family -- all ACKed ok. # # THE TWO NEGATIVE CASES ARE MEASURED THE SAME WAY, which is the only reason # this file can pin them. S08 sets the copy mode on group 4242, never bound, # and the kernel answers ENODEV; S10 unbinds group 42 a second time and gets # ENODEV again, which is also what proves S09's unbind took effect rather than # being silently accepted twice. S12 sends NFULNL_CFG_CMD_NONE and the kernel # answers 524 -- ENOTSUPP, a KERNEL-INTERNAL code with no name in errno.h at # all, leaked through netlink unchanged. It is pinned by number for that # reason: an errname() that folded it into "other" would report two different # kernel refusals as the same answer. # # A THIRD arm is worth knowing about when reading a red here: run natively but # WITHOUT the namespace, the same binary answers S01-S03 ok and S04-S12 EPERM. # Permission and absence are therefore distinguishable in this probe's own # output, and the errno in each detail line is what says which one this image # hit. (Both native arms produce byte-identical Z/H/B/P/G/X/K output, which is # the control saying the framing layer does not depend on kernel or permission # state.) # # --------------------------------------------------------------------------- # THE EXPECTED REDS: S01-S13, HELD BY TWO GAPS, NOT ONE. # # S01/S02 open and bind NETLINK_NETFILTER. On this image that answers # EPROTONOSUPPORT: wasm32_defconfig sets CONFIG_NET=y, CONFIG_INET=y, # CONFIG_UNIX=y and NO CONFIG_NETFILTER of any kind, so # net/netfilter/nfnetlink.c is not built and netlink protocol 12 is never # registered. That is brintos/linux#86 # (https://brintos.io/brintos/linux/issues/86), filed under the sibling port # BRINTOS-963. S05 (nflog_open) is the same socket call one layer up. # # S04-S12 NEED MORE THAN #86, AND THIS IS THE PART THAT IS EASY TO GET WRONG. # Protocol 12 alone is not enough for any message to be ANSWERED: the log # subsystem NFNL_SUBSYS_ULOG is registered by net/netfilter/nfnetlink_log.c # under CONFIG_NETFILTER_NETLINK_LOG, and #86's proposed diff turns on # CONFIG_NETFILTER + CONFIG_NETFILTER_NETLINK only -- it says so itself, that # neither pulls in "any hook, table or conntrack". So the day #86 lands, S01, # S02 and S03 go green and the group-binding cases stay red, and that is NOT # this port regressing: it is the second, separately-named gap surfacing, which # is exactly what the ticket asked this suite to establish. The two are scored # as one group but attributed by which one the detail line's errno names, and # want_s below switches that attribution by itself -- no edit to this file is # needed on the day. # # They are reported failing and NOT skipped so they flip green by themselves # the day the options land -- that is the regression signal this suite is # bought for. # # K01-K03 ARE THE CONTROL AND MUST STAY GREEN. They open NETLINK_ROUTE -- # protocol 0, registered unconditionally under CONFIG_NET. K green with S red # says "netlink protocol 12 is not registered", a kernel .config fact. BOTH red # would say "AF_NETLINK is unavailable", which is a different fault in a # different place, and without this pair a reader could not tell them apart # from S alone. # # --------------------------------------------------------------------------- # TWO UPSTREAM BEHAVIOURS PINNED HERE RATHER THAN REPORTED. # # nflog_get_ctid IS EXPORTED AND IS DECLARED IN NO INSTALLED HEADER. # libnetfilter_log.h stops at nflog_get_seq_global; the symbol is defined in # src/libnetfilter_log.c, is reached by nflog_snprintf_xml's NFLOG_XML_CTID # branch, and is in the archive that ships -- so a consumer here can link it # but cannot see it. (Its own doxygen block names a third spelling, # nflog_get_ct_id.) The symbol census below asserts it is present and the probe # declares it locally to drive it. # # NFLOG_XML_TIME PRINTS THE WALL CLOCK, NOT THE PACKET'S TIMESTAMP. # nflog_snprintf_xml's block calls time(NULL) + localtime_r and ignores # NFULA_TIMESTAMP entirely, so // describe when the string was # formatted rather than when the packet was logged. It is why the X group pins # ALL-minus-TIME and scores structurally. # # Per the project's no-public-upstream-reports rule neither is filed against # netfilter; both are pinned here and written up in the test plan. # # --------------------------------------------------------------------------- # ONE THING THIS SUITE DELIBERATELY DOES NOT DO. It does not drive the classic # handle API or the libipulog compat entry points beyond asserting their # symbols are in the archives. The first CANNOT BE LINKED on this arm at all # (above), so there is no program that could call it and therefore no case that # could report it; libipulog is the same story one layer up, since # ipulog_create_handle() goes through the same nflog_open(). R=${BST_LIBNETFILTER_LOG_ROOT:-} PROBE=${BST_LIBNETFILTER_LOG_PROBE:-$R/usr/libexec/libnetfilter-log-tests/libnetfilter-log-probe} ARCHIVE=$R/usr/lib/libnetfilter_log.a IPULOG_ARCHIVE=$R/usr/lib/libnetfilter_log_libipulog.a PCFILE=$R/usr/lib/pkgconfig/libnetfilter_log.pc IPULOG_PCFILE=$R/usr/lib/pkgconfig/libnetfilter_log_libipulog.pc INC=$R/usr/include/libnetfilter_log # Asserted against the .pc rather than assumed: a recipe bumped without # regenerating the pinned bytes below must go red on ONE named case instead of # silently changing the subject of the other hundred and fifty. WANT_VERSION=1.0.2 if [ ! -f "$ARCHIVE" ]; then bst_skip libnetfilter-log/present "libnetfilter-log not installed on this image" exit 0 fi bst_ok libnetfilter-log/present check libnetfilter-log/staticlib test -s "$ARCHIVE" check libnetfilter-log/staticlib-ipulog test -s "$IPULOG_ARCHIVE" check libnetfilter-log/pkgconfig test -s "$PCFILE" check libnetfilter-log/pkgconfig-ipulog test -s "$IPULOG_PCFILE" check libnetfilter-log/hdr-main test -s "$INC/libnetfilter_log.h" # The uapi copy the package ships itself. It is a pkginclude_HEADER and it # matters here more than on a normal distro: this image installs NO kernel # headers at all, so it is the only definition of the nfnetlink_log attribute # numbers anything on target could compile against. check libnetfilter-log/hdr-uapi test -s "$INC/linux_nfnetlink_log.h" check libnetfilter-log/hdr-ipulog test -s "$INC/libipulog.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 libnetfilter-log/no-shared "$R/usr/lib/libnetfilter_log.so" "$R/usr/lib/libnetfilter_log.so.1" "$R/usr/lib/libnetfilter_log_libipulog.so" pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libnetfilter-log/pkgconfig-version "$WANT_VERSION" "$pcver" ipcver=$(sed -n 's/^Version: *//p' "$IPULOG_PCFILE" 2>/dev/null | head -1) check_eq libnetfilter-log/pkgconfig-ipulog-version "$WANT_VERSION" "$ipcver" check_out libnetfilter-log/pkgconfig-libs "-lnetfilter_log" cat "$PCFILE" check_out libnetfilter-log/pkgconfig-ipulog-libs "-lnetfilter_log_libipulog" cat "$IPULOG_PCFILE" # BOTH DEPENDENCIES ARE UNDER Requires.private, WHICH IS THE FACT A CONSUMER ON # THIS IMAGE HAS TO KNOW. pkg-config expands Requires.private only under # --static, and on a --disable-shared distro every link IS static -- so # `pkg-config --libs libnetfilter_log` answers `-L/usr/lib -lnetfilter_log` and # a consumer built from it fails to link. Measured on the x86-64 oracle build # of this same source: 47 undefined references from the plain form, zero from # `pkg-config --static --libs`. Pinned as one case per .pc so a version that # promotes them to Requires (which would fix it) is a visible, deliberate # change rather than a silent one. # # This is NOT the sibling libnetfilter_conntrack finding, whose .pc genuinely # omits libmnl altogether. Checked against the installed file here rather than # carried over. # Continuation lines are joined, so the case pins the WHOLE field -- the point # is that libmnl IS named here, and a one-line read would have stopped at # libnfnetlink and pinned the sibling's finding by accident. _pcreq() { awk '/^Requires\.private:/ { sub(/^Requires\.private:[ \t]*/, ""); v = $0 while (v ~ /\\$/) { sub(/[ \t]*\\$/, "", v); if ((getline nx) <= 0) break sub(/^[ \t]+/, "", nx); v = v " " nx } print v; exit }' "$1" 2>/dev/null } check_eq libnetfilter-log/pkgconfig-requires-private \ "libnfnetlink >= 0.0.41, libmnl >= 1.0.3" "$(_pcreq "$PCFILE")" check_eq libnetfilter-log/pkgconfig-ipulog-requires-private \ "libnetfilter_log >= 1.0.2, libnfnetlink >= 0.0.41" "$(_pcreq "$IPULOG_PCFILE")" # --------------------------------------------------------------------------- # The symbol census. This library exports 40 entry points and libipulog seven, # which is few enough to be EXHAUSTIVE rather than the deliberate sample the # conntrack sibling's suite had to settle for -- so a red here names the exact # entry point that did not ship instead of saying that one arbitrary member of # a subsystem went missing. # # nflog_handle_packet and nflog_callback_register ARE asserted present. The arm # cannot LINK a program that calls the first (see the header comment); what is # in the archive is a different question from what can be linked against it, # and this census answers the archive one. # grep -a: the archive is binary and some greps refuse it without it. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in nflog_open nflog_open_nfnl nflog_close nflog_fd nflog_nfnlh \ nflog_bind_pf nflog_unbind_pf \ nflog_bind_group nflog_unbind_group \ nflog_set_mode nflog_set_timeout nflog_set_flags \ nflog_set_qthresh nflog_set_nlbufsiz \ nflog_callback_register nflog_handle_packet \ nflog_get_msg_packet_hdr nflog_get_hwtype \ nflog_get_msg_packet_hwhdrlen nflog_get_msg_packet_hwhdr \ nflog_get_nfmark nflog_get_timestamp \ nflog_get_indev nflog_get_physindev \ nflog_get_outdev nflog_get_physoutdev \ nflog_get_packet_hw nflog_get_payload nflog_get_prefix \ nflog_get_uid nflog_get_gid \ nflog_get_seq nflog_get_seq_global nflog_get_ctid \ nflog_snprintf_xml \ nflog_nlmsg_put_header nflog_nlmsg_parse nflog_nlmsg_snprintf \ nflog_attr_put_cfg_cmd nflog_attr_put_cfg_mode; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnetfilter-log/sym-$sym" else bst_notok "libnetfilter-log/sym-$sym" "$sym absent from $ARCHIVE" fi done for sym in ipulog_create_handle ipulog_destroy_handle ipulog_get_packet \ ipulog_group2gmask ipulog_perror ipulog_read ipulog_strerror; do if $_grepq "$sym" "$IPULOG_ARCHIVE" 2>/dev/null; then bst_ok "libnetfilter-log/ipulog-sym-$sym" else bst_notok "libnetfilter-log/ipulog-sym-$sym" "$sym absent from $IPULOG_ARCHIVE" fi done # --------------------------------------------------------------------------- # The probe. Both it and the archive are this ticket's deliverable, so a skip # on either is a broken port and not a legitimate absence -- but an exec that # the shell could not stage at all is platform noise, and bst_unrun is what # tells those two apart. if [ ! -x "$PROBE" ]; then bst_skip libnetfilter-log/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libnetfilter-log-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libnetfilter-log/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libnetfilter-log/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libnetfilter-log/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi # A probe killed part-way still emits usable lines, so the run is scored either # way -- but PROBE-END is what separates "these values are the whole story" # from "the rest is missing", and want() reports an absent value as its own red # rather than as a mismatch. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libnetfilter-log/probe-complete else bst_notok libnetfilter-log/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # One awk pass lifts every case line into a V_ shell variable, so the # hundred and sixty-odd comparisons below fork nothing. First occurrence wins. eval "$(awk ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } $1 ~ /^[ZHBPGXKS][0-9][0-9]$/ && seen[$1]++ == 0 { id = $1; v = $0; sub(/^[^ ]+ ?/, "", v); print "V_" id "=" q(v) } ' "$pout")" # want : the strict comparison. An absent # value is its own message -- "the probe stopped before here" and "the probe # answered differently" are different findings and a bare mismatch would hide # the first behind an empty got. want() { eval "_got=\${V_$1-}" if [ -z "$_got" ]; then bst_notok "libnetfilter-log/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libnetfilter-log/$2" else bst_notok "libnetfilter-log/$2" "want:'$3' got:'$_got'" fi } # --------------------------------------------------------------------------- # Z -- THE WIRE FORMAT ITSELF. Scored first because a disagreement here # explains every hex mismatch after it. struct nlmsghdr is 16 bytes and struct # nfgenmsg 4 on a 32- and a 64-bit build alike, attributes align to 4, and the # packed config structs put their scalars on the wire in network order -- so # the arm and the oracle MUST agree and a difference is a real defect, never an # expected arch difference. Z26 is the one most worth reading: the mode struct # is packed with the __be32 range FIRST, which is the opposite of the order # nflog_attr_put_cfg_mode's own initialiser names the fields in. want Z01 sizeof-nlmsghdr '16' want Z02 sizeof-nfgenmsg '4' want Z03 sizeof-nlattr '4' want Z04 sizeof-cfg-cmd '1' want Z05 sizeof-cfg-mode '6' want Z06 sizeof-packet-hdr '4' want Z07 sizeof-packet-hw '12' want Z08 sizeof-packet-timestamp '16' want Z09 nfula-max '19' want Z10 nfula-cfg-max '6' want Z11 mnl-alignto '4' want Z12 htonl-32 '01020304' want Z13 htons-16 '0102' want Z14 subsys-ulog '4' want Z15 msgtype-packet '0' want Z16 msgtype-config '1' want Z17 cmd-bind '1' want Z18 cmd-unbind '2' want Z19 cmd-pf-bind '3' want Z20 cmd-pf-unbind '4' want Z21 copy-none '0' want Z22 copy-meta '1' want Z23 copy-packet '2' want Z24 attr-cfg-cmd '1' want Z25 attr-cfg-mode '2' want Z26 cfg-mode-wire-layout '112233445500' # --------------------------------------------------------------------------- # H -- nflog_nlmsg_put_header. The 20 bytes every message of this protocol # opens with, and the only place the subsystem id and the group number reach # the wire. H08 binds group 65535 deliberately: res_id is a __be16, and a port # that got its byte order wrong would still round-trip a small group through # its own reader and show up only at the top of the range. want H01 put-header-bytes '140000000104010000000000000000000200002a' want H02 put-header-len '20' want H03 put-header-type '1025' want H04 put-header-flags '1' want H05 put-header-seq '0' want H06 put-header-pid '0' want H07 put-header-inet6-group0 '140000000104010000000000000000000a000000' want H08 put-header-group-max '140000000104010000000000000000000000ffff' want H09 put-header-bridge-packet '1400000000040100000000000000000007000102' want H10 nfgen-family '2' want H11 nfgen-version '0' want H12 nfgen-resid '42' # --------------------------------------------------------------------------- # B -- THE COMPOSE HALF OF THE TICKET'S PASS CRITERION. Bind a group, set the # copy mode and range, unbind -- as bytes. This half runs on a kernel with no # netfilter at all, because composing a buffer touches no descriptor, and it is # where the port is actually proven: B03/B06/B07 are the three messages a real # consumer sends, byte for byte. # # B22/B23 are not decoration. A composed buffer whose bytes all match can # still carry a length field one word short, which reproduces every hex case # above and fails only when a receiver tries to walk it -- so mnl_nlmsg_ok is # asserted true at the stated length and FALSE one byte below it. want B01 cfg-cmd-rc '0' want B02 bind-len '28' want B03 bind-bytes '1c0000000104010000000000000000000000002a0500010001000000' want B04 cfg-mode-rc '0' want B05 bind-mode-len '40' want B06 bind-mode-bytes '280000000104010000000000000000000000002a05000100010000000a0002000000ffff02000000' want B07 unbind-bytes '1c0000000104010000000000000000000000002a0500010002000000' want B08 pf-bind-bytes '1c000000010401000000000000000000020000000500010003000000' want B09 pf-unbind-bytes '1c000000010401000000000000000000020000000500010004000000' want B10 mode-none-bytes '20000000010401000000000000000000000000010a000200deadbeef00000000' want B11 mode-meta-bytes '20000000010401000000000000000000000000010a0002000000000001000000' want B12 mode-packet-64k-bytes '20000000010401000000000000000000000000010a0002000001000002000000' want B13 cmd-attr-type '1' want B14 cmd-attr-len '5' want B15 cmd-attr-payload-len '1' want B16 cmd-attr-value '1' want B17 mode-attr-type '2' want B18 mode-attr-len '10' want B19 mode-attr-payload-len '6' want B20 mode-attr-copy-mode '2' want B21 mode-attr-copy-range '65535' want B22 nlmsg-ok '1' want B23 nlmsg-ok-truncated '0' # --------------------------------------------------------------------------- # P -- nflog_nlmsg_parse over a synthetic NFULNL_MSG_PACKET. # # Building the packet here rather than receiving one is what lets this group # run on a kernel that registers no netfilter, and it is also a STRONGER test # than a received packet: every field is a known constant rather than whatever # the kernel happened to log. P02 pins the input itself, so a whole group # going red says "the question changed" rather than "the answer changed" -- # which is the distinction that costs the most time. # # P27/P28 are the negative control. NFULA_CT was never put on the message, so # a parser that filled its table by position rather than by attribute type # would answer every other case here correctly and show up only there. want P01 packet-len '220' want P02 packet-bytes 'dc000000000401000000000000000000020000070800010008000300080002001234abcd14000300000000005f5e1000000000000001e2400800040000000002080005000000000308000600000000040800070000000005100008000006000000112233445500001800090045000014abcd4000400600000a0000010a0000020d000a006272696e746f733a0000000008000b00000003e808000e00000003e908000c000000000708000d000000000906000f000001000006001100000e00001200100000112233445566778899aabb080000000800130000000000' want P03 parse-rc '1' want P04 parse-present-bitmap '000bfffe' want P05 parse-mark '305441741' want P06 parse-indev '2' want P07 parse-outdev '3' want P08 parse-physindev '4' want P09 parse-physoutdev '5' want P10 parse-uid '1000' want P11 parse-gid '1001' want P12 parse-seq '7' want P13 parse-seq-global '9' want P14 parse-hwtype '1' want P15 parse-hwlen '14' want P16 parse-prefix 'brintos:' want P17 parse-payload-len '20' want P18 parse-payload-bytes '45000014abcd4000400600000a0000010a000002' want P19 parse-hwheader-len '14' want P20 parse-hwheader-bytes '00112233445566778899aabb0800' want P21 parse-hw-protocol '0800' want P22 parse-hook '3' want P23 parse-hwaddr-len '6' want P24 parse-hwaddr-bytes '0011223344550000' want P25 parse-timestamp-bytes '000000005f5e1000000000000001e240' want P26 parse-ct-info '0' want P27 parse-ct-absent '1' want P28 parse-unspec-absent '1' want P29 parse-unknown-attr-rc '1' want P30 parse-unknown-attr-mark '85' want P31 parse-empty-rc '1' want P32 parse-empty-table '0' # --------------------------------------------------------------------------- # G -- THE CLASSIC ACCESSORS, DRIVEN WITHOUT A SOCKET. # # struct nflog_data is one member, a struct nfattr **, and upstream's own # nflog_nlmsg_snprintf builds one on its stack from a parsed nlattr table # (`.nfa = (struct nfattr **)&attr[1]`; the +1 is because the classic table is # indexed by attrtype-1 and the mnl one by attrtype). The probe does the same, # which is what lets twenty-odd getters written for a received packet be # measured on a kernel that will never deliver one. # # This is the group that would silently pass if it were written as # set-then-get. There is no writer here at all: the input is bytes composed by # libmnl and pinned in P02, so a getter reading the wrong offset has nothing to # agree with. G27-G35 are the absent-attribute control -- an empty table must # make every getter answer its own "not there" value rather than dereference, # and without them a getter that ignored its presence check would pass # everything above. want G01 get-packet-hdr '1' want G02 get-hw-protocol '0800' want G03 get-hook '3' want G04 get-hwtype '1' want G05 get-hwhdrlen '14' want G06 get-hwhdr '1' want G07 get-hwhdr-bytes '00112233445566778899aabb0800' want G08 get-nfmark '305441741' want G09 get-timestamp-rc '0' want G10 get-timestamp-sec '1600000000' want G11 get-timestamp-usec '123456' want G12 get-indev '2' want G13 get-outdev '3' want G14 get-physindev '4' want G15 get-physoutdev '5' want G16 get-packet-hw '1' want G17 get-hwaddr-len '6' want G18 get-hwaddr-bytes '0011223344550000' want G19 get-payload-len '20' want G20 get-payload-bytes '45000014abcd4000400600000a0000010a000002' want G21 get-prefix 'brintos:' want G22 get-uid '0 1000' want G23 get-gid '0 1001' want G24 get-seq '0 7' want G25 get-seq-global '0 9' want G26 get-ctid-absent '-1' want G27 empty-packet-hdr '1' want G28 empty-nfmark '0' want G29 empty-indev '0' want G30 empty-packet-hw '1' want G31 empty-prefix '1' want G32 empty-payload '-1' want G33 empty-uid '-1' want G34 empty-seq '-1' want G35 empty-timestamp '-1' # --------------------------------------------------------------------------- # X -- nflog_nlmsg_snprintf / nflog_snprintf_xml, the library's only formatter. # # ONE FLAG IS NOT BYTE-PINNED AND THE REASON IS UPSTREAM'S. NFLOG_XML_TIME # makes nflog_snprintf_xml call time(NULL) + localtime_r and print the WALL # CLOCK -- not the packet's own NFULA_TIMESTAMP, which it ignores. So # NFLOG_XML_ALL is not a pinnable value: X01/X02 pin ALL-minus-TIME, and # X15-X18 score the branch structurally (it ran, it produced a balanced # element, it emitted all seven of its documented tags). Pinning the whole of # ALL would have bought a case that goes red once a second and reads as a port # defect. That upstream prints a receive-time clock in a field a reader takes # for the packet's timestamp is pinned as behaviour, not reported: per the # project's no-public-upstream-reports rule nothing is filed against netfilter. # # X19/X20 are the only errno this library sets on its own behalf, and X21/X22 # are the truncation path -- a port that got the remaining-space arithmetic in # upstream's SNPRINTF_FAILURE macro wrong would overrun rather than truncate, # and nothing else here would say so. want X01 xml-all-len '266' want X02 xml-all 'brintos:30800001122334455305441741234545000014abcd4000400600000a0000010a000002' want X03 xml-prefix-len '50' want X04 xml-prefix 'brintos:3' want X05 xml-mark-len '47' want X06 xml-mark '3305441741' want X07 xml-dev-len '59' want X08 xml-dev '323' want X09 xml-hw-len '76' want X10 xml-hw '30800001122334455' want X11 xml-payload-len '84' want X12 xml-payload '345000014abcd4000400600000a0000010a000002' want X13 xml-none-len '25' want X14 xml-none '3' want X15 xml-time-ran '1' want X16 xml-time-element '1' want X17 xml-time-year '1' want X18 xml-time-tags '7' want X19 xml-bad-type-rc '-1' want X20 xml-bad-type-errno 'EOPNOTSUPP' want X21 xml-tiny-buffer-rc '1' want X22 xml-tiny-buffer-truncated '1' # --------------------------------------------------------------------------- # K -- THE CONTROL. These use plain want(): they are a hard requirement, not an # attributed red. NETLINK_ROUTE is protocol 0 and is registered unconditionally # under CONFIG_NET, so a red here does NOT mean "netfilter is missing" -- it # means AF_NETLINK itself, or the socket syscall, is broken on this image, and # every S red below would then be a consequence rather than a finding. want K01 rtnetlink-socket 'ok' want K02 rtnetlink-bind 'ok' want K03 rtnetlink-socket-raw 'ok' # --------------------------------------------------------------------------- # S -- THE TRANSPORT, and the run half of the ticket's pass criterion. # # Composed by this library, carried by libmnl, ANSWERED BY THE KERNEL: each # case sends its config message with NLM_F_ACK and reports what mnl_cb_run # lifted out of NLMSG_ERROR, so "ok" means the kernel accepted it and an errno # is the kernel's own. See the header comment for why the classic handle API is # not used and cannot be. # # The attribution below is COMPUTED from the probe's own output rather than # written in, so it re-aims itself as the platform moves and this file needs no # edit on the day brintos/linux#86 lands: # # the failing case answered EPERM/EACCES -> PERMISSION. Both the protocol and # the subsystem are present and we may not use # them. Takes priority over everything below, # whatever S01 said. # S01 not ok, EPROTONOSUPPORT -> protocol 12 is unregistered. #86. # S01 not ok, EPERM/EACCES -> the protocol EXISTS and we may not open it. # S01 ok -> the protocol is registered and a red past # here is the LOG SUBSYSTEM missing # (CONFIG_NETFILTER_NETLINK_LOG), which is the # second gap #86 explicitly does not close. # # Getting that wrong in the obvious direction -- hard-coding "#86" into every # red -- is what would make this suite quietly lie the day half of it goes # green. Getting it wrong in the SUBTLE direction is keying only on S01, which # is what the first version of this file did; see _attrib below for the arm # that caught it. NF_SOCK=${V_S01-} # THE ATTRIBUTION KEYS ON THE FAILING CASE'S OWN ERRNO FIRST, and only then on # S01. Keying on S01 alone is the obvious way to write this and it is WRONG: # run the same probe natively without a namespace and S01 opens fine while # every case past it answers EPERM, so an S01-only rule prints "protocol 12 is # registered, therefore the log subsystem is missing" over a red whose actual # cause is that we may not use it. That is worse than no attribution -- it # sends the reader to a kernel .config for a permissions problem. Caught by # running the no-namespace arm on the build host, which is why that arm is part # of the port's evidence and not an afterthought. _attrib() { case "$1" in EPERM|EACCES) printf '%s' "held by PERMISSION, not absence: the kernel answered $1, so netlink protocol 12 AND the log subsystem are both present and this process lacks CAP_NET_ADMIN in its net namespace -- do not attribute this to brintos/linux#86" return ;; esac case "$NF_SOCK" in ok) printf '%s' "held by the LOG SUBSYSTEM, not by the protocol: S01 opened netlink protocol 12, so brintos/linux#86 has landed, but net/netfilter/nfnetlink_log.c (CONFIG_NETFILTER_NETLINK_LOG) does not register NFNL_SUBSYS_ULOG -- the second gap, which #86's own diff states it does not close" ;; EPERM|EACCES) printf '%s' "held by PERMISSION at the socket: S01 answered $NF_SOCK, so protocol 12 IS registered and this process lacks CAP_NET_ADMIN -- do not attribute this to brintos/linux#86" ;; '') printf '%s' "unattributed: the probe emitted no S01, so the transport was never reached and the cause is unknown" ;; *) printf '%s' "held by S01 netfilter-socket ($NF_SOCK): wasm32_defconfig sets no CONFIG_NETFILTER, so netlink protocol 12 is unregistered (brintos/linux#86)" ;; esac } # want_s # Same comparison as want(), with the computed attribution appended to a red so # no S failure can reach a reviewer without naming what holds it. want_s() { eval "_got=\${V_$1-}" if [ -z "$_got" ]; then bst_notok "libnetfilter-log/$2" "no value on target (probe stopped before $1); want='$3' -- $(_attrib '')" elif [ "$_got" = "$3" ]; then bst_ok "libnetfilter-log/$2" else bst_notok "libnetfilter-log/$2" "want:'$3' got:'$_got' -- $(_attrib "$_got")" fi } # Opening and binding the socket. S03 asserts the kernel assigned a portid, # which is what distinguishes "bind returned 0" from "bind actually happened". want_s S01 netfilter-socket 'ok' want_s S02 netfilter-bind 'ok' want_s S03 netfilter-portid '1' # THE TICKET'S PASS CRITERION, in order and in its own words: bind a log group, # set the copy mode and range, unbind cleanly, report each return value. want_s S04 pf-bind 'ok' want_s S05 group-bind 'ok' want_s S06 group-set-mode-packet 'ok' want_s S07 group-set-mode-meta 'ok' # "...with the expected errno on an invalid group." # # ENODEV, and it is MEASURED rather than chosen. Every uint16 is a valid group # NUMBER, so there is no such thing as a malformed group id to send; what the # kernel actually rejects is a group with no instance behind it. Group 4242 was # never bound, so nfulnl_recv_config has nothing to configure and answers # ENODEV. Pinned from the x86-64 oracle run under `unshare -rn` against a # kernel that has nfnetlink_log -- not from reading net/netfilter/, and not # from a guess at EINVAL, which is the errno this case looks like it ought to # return and does not. want_s S08 set-mode-unbound-group 'ENODEV' want_s S09 group-unbind 'ok' # The same ENODEV from the other direction, and this case is why S09 is # trustworthy: unbinding group 42 a second time must FAIL. If it answered ok, # S09's unbind would not have taken effect and the whole bind/unbind pair would # be a pair of accepted no-ops that this suite had scored green. want_s S10 unbind-unbound-group 'ENODEV' want_s S11 pf-unbind 'ok' # NFULNL_CFG_CMD_NONE -- a command the enum defines and the subsystem does not # implement. The answer is 524, which is the KERNEL'S OWN ENOTSUPP: it is # defined in include/linux/errno.h, is documented there as "should never be # seen by user programs", has no name in userspace errno.h at all, and reaches # us through netlink unchanged. Pinned by number because that is the only # honest way to write it, and pinned at all because "accepted as a no-op" and # "rejected" are different contracts that a consumer cannot tell apart without # asking. Per the project's no-public-upstream-reports rule the leak is not # filed against netfilter; it is recorded here and in the test plan. want_s S12 cmd-none 'errno-524' rm -f "$pout"