# 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_cttimeout 1.0.1 -- the userspace library for the kernel's # nfnetlink_cttimeout subsystem, i.e. for defining fine-grain # connection-tracking timeout policies that iptables' CT target attaches to a # flow, instead of the one global timeout per protocol that preceded it. What # ships under --disable-shared is /usr/lib/libnetfilter_cttimeout.a, one header # under /usr/include/libnetfilter_cttimeout/ and libnetfilter_cttimeout.pc. # There is no CLI and no daemon; the tarball's examples/ are a check_PROGRAMS # that `make` does not build and `make install` discards, and both take their # arguments on argv and open a netlink socket in their first ten lines, so # neither 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 IS NOT libnfnetlink, AND THE DIFFERENCE IS THE WHOLE SHAPE OF THIS FILE. # # libnfnetlink (suite 79-libnfnetlink, BRINTOS-963) owns its own socket: # nfnl_open() IS socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER), and fifteen # of its cases are unreachable on this kernel because of it. # libnetfilter_cttimeout owns NO socket at all. Every entry point takes a plain # struct nlmsghdr * or a struct nfct_timeout * and touches no descriptor: it is # a pure message composer and parser layered over libmnl, and the socket in # upstream's examples belongs to libmnl. So the framing layer here is not the # half that happens to survive -- it is the entire library, and it is fully # measurable on a kernel with no netfilter. That is why only THREE cases below # are expected red, against fifteen next door. # # --------------------------------------------------------------------------- # THE LIBRARY SHIPS NO GETTER, AT ALL, AND THAT DECIDES HOW "EACH VALUE" IS # CHECKED. # # struct nfct_timeout is opaque and the API is thirteen symbols of which not # one reads a field back -- attr_set{,_u8,_u16}, attr_unset, # policy_attr_set_u32, policy_attr_unset, and no counterpart for any of them. # Its sibling libnetfilter_cthelper at least ships nfct_helper_attr_get*(); # this one ships nothing. So the ticket's "parse back and check each value and # the object's name and l4proto" is answered through the two public read paths # that do exist, and it needs both: # # * nfct_timeout_snprintf(), the library's own renderer, which prints .name, # .l3proto, .l4proto and every per-state timeout. T01-T11 and W01-W03 lift # one state each out of it, so a red names the state rather than the whole # render, and P05/N02/N04 pin the render entire. # * RE-SERIALISATION -- parse into a second object, rebuild its payload, and # require the bytes to be identical to the original (R01-R03). A field that # did not survive cannot produce identical bytes. # # Neither alone would do. The renderer walks the same array the parser filled, # so it would agree with a parser that stored SYN_RECV where FIN_WAIT belongs; # re-serialisation is byte-exact but says nothing about WHICH field moved. # Together they pin the value and its identity. # # --------------------------------------------------------------------------- # THE ORACLE, AND WHY A TARGET-ONLY RUN COULD NOT SUPPLY IT. # # Every composed buffer is scored byte for byte against hex pinned from a # native x86-64 build of this same 1.0.1 tarball against libmnl 1.0.5. # 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 (htonl/htons), so the two architectures MUST # agree exactly and a disagreement is a real defect, never an expected # difference. 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 Z group is scored first because a disagreement there explains # every hex mismatch after it. # # --------------------------------------------------------------------------- # THE EXPECTED REDS: S01-S03, ONE CAUSE, LEFT RED ON PURPOSE. # # The netlink round trip the ticket asks for needs netlink protocol 12, # registered by net/netfilter/nfnetlink.c -- which wasm32_defconfig does not # build, because it sets CONFIG_NET=y and no CONFIG_NETFILTER at all. So # mnl_socket_open(NETLINK_NETFILTER) answers EPROTONOSUPPORT and the round # trip cannot start. Filed as brintos/linux#86 # (https://brintos.io/brintos/linux/issues/86) under the sibling ports. They # are reported failing and NOT skipped so they flip green by themselves the # day the option lands -- that is the regression signal this suite is bought # for. # # S03 IS AN ORACLE-MEASURED CRITERION HERE, NOT AN ASSERTED ONE, AND THAT IS A # REAL DIFFERENCE FROM THE SIBLING PORT. The native x86-64 control must run # under `unshare -rn` so it cannot touch the build host's real conntrack # configuration. For libnetfilter_cthelper that made cb=0 unreachable even # natively -- nfnetlink_cthelper.c gates on plain capable(), the INITIAL user # namespace, so the control was refused EPERM before the kernel parsed the # message, and BRINTOS-964 had to write cb=0 as the criterion for a configured # kernel rather than as a value it had seen. nfnetlink_cttimeout.c takes the # ordinary nfnetlink path and is reachable from a user namespace that owns its # netns, so the control really did complete the round trip and really did # answer `cb=0 errno=none`. That is the value pinned on S03. # # On top of #86, a green S03 on target still needs CONFIG_NFT_CT_TIMEOUT (or # CONFIG_NF_CONNTRACK_TIMEOUT) over nf_conntrack, so if S01 and S02 go green # and S03 stays red that is not this port regressing: it is a second, NAMED # gap, and the detail line carries the errno that says which. # # C01/C02 ARE THE CONTROL AND MUST STAY GREEN. They open NETLINK_ROUTE -- # protocol 0, registered unconditionally under CONFIG_NET. C green with S red # says "protocol 12 is not registered", a kernel .config fact with a one-line # fix. BOTH red would say "AF_NETLINK is unavailable", a different fault in a # different place that would make the attribution above wrong. Without the # control the two are indistinguishable from this file's output, so C01/C02 # are scored strictly and carry no attribution: they are not held by the gap, # they are what gives the gap its meaning. # # --------------------------------------------------------------------------- # FOUR UPSTREAM BEHAVIOURS ARE PINNED RATHER THAN FIXED. # # U03/U04 -- nfct_timeout_policy_attr_unset(t, state) is documented as # unsetting one POLICY STATE and clears bit `state` of ATTRSET, the OBJECT # attribute bitset, not of polset. The two numbering spaces overlap at 0..3, # so asking it to forget a TCP state instead forgets an object attribute of # the same index and leaves the state on the wire. U03 pins the consequence # for state 2 (ESTABLISHED), which drops the L4PROTO attribute -- 8 bytes, # 140 -> 132 -- while every policy value stays; U04 pins state 3 (FIN_WAIT), # which clears NFCT_TIMEOUT_ATTR_POLICY and drops the whole 92-byte nest. # U05 reaches that same 48 the correct way, via nfct_timeout_attr_unset() on # NFCT_TIMEOUT_ATTR_POLICY, which is what makes U04 a defect rather than a # coincidence. # # N01/N03 -- nfct_timeout_snprintf()'s doc comment promises "-1 in case that # some mandatory attributes are missing. On sucess, it returns 0"; it in fact # returns the LAST snprintf's return value, which is strlen("};") == 2, for # every object. N05 pins the rendered length separately, because that is the # number a consumer sizing a buffer actually needs and the return value is not # it. # # M01 -- nfct_timeout_nlmsg_parse_payload() carries the same "returns -1 if # mandatory attributes are missing" promise and has no code path that returns # anything but 0. M01 hands it a message with no attributes at all and pins # the 0, so a future version that adds the check is a visible, deliberate # change of subject. # # THE THIRTEENTH SYMBOL EXISTS ONLY BECAUSE THIS DISTRO IS STATIC. # src/libnetfilter_cttimeout.map exports `nfct_timeout_policy_unset`, which # the source does not define, and does NOT export # `nfct_timeout_policy_attr_unset`, which it does -- so `local: *` hides it. A # --enable-shared build of 1.0.1 therefore exports twelve symbols and the # INSTALLED PUBLIC HEADER declares a thirteenth you cannot link against; # measured on a native shared build, not inferred from reading the map. Our # --disable-shared archive carries all thirteen, so sym-nfct_timeout_policy_- # attr_unset below is green here and would be red on a shared distro. # no-map-ghost pins the other half: the name the map invents is in no object. # # All four are third-party behaviour on a package we are porting, not brintOS # defects and not something this port changes. Per the project's # no-public-upstream-reports rule nothing is filed for them; they are pinned # here and written up in the test plan. # # --------------------------------------------------------------------------- # TWO CALLS THIS SUITE DELIBERATELY DOES NOT DRIVE. Both are every- # architecture faults inside the library, so driving them would measure the # platform's fault handling rather than this port. # # * nfct_timeout_snprintf() into a buffer too small for the render. # nfct_timeout_snprintf_default() subtracts each snprintf's return value # from its remaining `size`, and snprintf returns what it WOULD have # written -- so the first truncating call drives a size_t through zero to # SIZE_MAX and every later call writes unbounded past the caller's buffer. # The sibling port could pin its truncation contract (BRINTOS-964 N05/N06) # because that library's renderer clamps; this one's does not. N05 pins # the length that has to fit instead. # * nfct_timeout_policy_attr_set_u32(t, attr_max, v). The bounds test is # `type > attr_max` where the array holds exactly attr_max entries, so the # boundary index is accepted and writes one uint32_t past the allocation. # # --------------------------------------------------------------------------- # ONE CASE THAT LOOKS LIKE NOISE AND IS THE ASSERTION. # attr-to-name-stdout-diagnostic greps the probe's own output for the line # `no array state name`. That line is not the probe's: it is # nfct_timeout_policy_attr_to_name() PRINTING TO STDOUT when handed a protocol # with no tracker (A04's arm). A library that writes to a consumer's stdout is # worth pinning in both directions -- it is why this suite parses its probe's # output by leading id rather than by line number, and a future version that # removes it should show up as a named red rather than as a silently different # transcript. R=${BST_LIBNETFILTER_CTTIMEOUT_ROOT:-} PROBE=${BST_LIBNETFILTER_CTTIMEOUT_PROBE:-$R/usr/libexec/libnetfilter-cttimeout-tests/libnetfilter-cttimeout-probe} ARCHIVE=$R/usr/lib/libnetfilter_cttimeout.a PCFILE=$R/usr/lib/pkgconfig/libnetfilter_cttimeout.pc INC=$R/usr/include/libnetfilter_cttimeout # 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. WANT_VERSION=1.0.1 if [ ! -f "$ARCHIVE" ]; then bst_skip libnetfilter-cttimeout/present "libnetfilter-cttimeout not installed on this image" exit 0 fi bst_ok libnetfilter-cttimeout/present check libnetfilter-cttimeout/staticlib test -s "$ARCHIVE" check libnetfilter-cttimeout/pkgconfig test -s "$PCFILE" check libnetfilter-cttimeout/hdr-main test -s "$INC/libnetfilter_cttimeout.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-cttimeout/no-shared "$R/usr/lib/libnetfilter_cttimeout.so" "$R/usr/lib/libnetfilter_cttimeout.so.1" pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libnetfilter-cttimeout/pkgconfig-version "$WANT_VERSION" "$pcver" check_out libnetfilter-cttimeout/pkgconfig-libs "-lnetfilter_cttimeout" cat "$PCFILE" # Upstream's .pc carries an EMPTY Requires: even though the library cannot # link without libmnl. On a --disable-shared distro that matters: a consumer # built from `pkg-config --libs libnetfilter_cttimeout` alone gets no -lmnl and # fails to resolve every mnl_* symbol. It is pinned because it is a fact a # consumer has to know, and because it is why the recipe's own probe link line # names libmnl explicitly rather than trusting pkg-config. pcreq=$(sed -n 's/^Requires: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libnetfilter-cttimeout/pkgconfig-requires-empty "" "$pcreq" # --------------------------------------------------------------------------- # The symbol census is EXHAUSTIVE here, not a sample. This library exports 13 # names in total, so the whole API fits and a red means the archive did not # ship the API rather than that one sampled name went missing. # 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 nfct_timeout_alloc nfct_timeout_free \ nfct_timeout_attr_set nfct_timeout_attr_set_u8 \ nfct_timeout_attr_set_u16 nfct_timeout_attr_unset \ nfct_timeout_policy_attr_set_u32 \ nfct_timeout_policy_attr_unset \ nfct_timeout_policy_attr_to_name \ nfct_timeout_snprintf \ nfct_timeout_nlmsg_build_hdr nfct_timeout_nlmsg_build_payload \ nfct_timeout_nlmsg_parse_payload; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnetfilter-cttimeout/sym-$sym" else bst_notok "libnetfilter-cttimeout/sym-$sym" "$sym absent from $ARCHIVE" fi done # The other half of the map/header mismatch: the name the version script # invents is defined nowhere. Scored as its own case so "the map is wrong" and # "a symbol went missing" cannot be confused for each other. grep -w, because # nfct_timeout_policy_unset is a prefix of nothing but is a SUBSTRING of # nothing either -- the -w is what stops a future rename from matching by # accident. if $_grepq "nfct_timeout_policy_unset" "$ARCHIVE" 2>/dev/null; then bst_notok libnetfilter-cttimeout/no-map-ghost "nfct_timeout_policy_unset (a name only src/libnetfilter_cttimeout.map has) is defined in $ARCHIVE" else bst_ok libnetfilter-cttimeout/no-map-ghost fi # --------------------------------------------------------------------------- # 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-cttimeout/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libnetfilter-cttimeout-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libnetfilter-cttimeout/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libnetfilter-cttimeout/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libnetfilter-cttimeout/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. It matters more here than in the sibling # suite: the M group runs LAST because it drives a zero-length VLA inside the # library, and a fault there would take PROBE-END with it while leaving every # earlier group scored. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libnetfilter-cttimeout/probe-complete else bst_notok libnetfilter-cttimeout/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # Not the probe's line -- the library's. See the header comment. if grep -q '^no array state name$' "$pout" 2>/dev/null; then bst_ok libnetfilter-cttimeout/attr-to-name-stdout-diagnostic else bst_notok libnetfilter-cttimeout/attr-to-name-stdout-diagnostic "nfct_timeout_policy_attr_to_name() no longer prints 'no array state name' to stdout for a protocol with no tracker" fi # One awk pass lifts every case line into a V_ shell variable, so the # hundred comparisons below fork nothing. First occurrence wins, which is also # what makes the library's own stray stdout line above harmless: it carries no # leading id and is never lifted. eval "$(awk ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } $1 ~ /^[ABCHMNPRSTUWZ][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-cttimeout/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libnetfilter-cttimeout/$2" else bst_notok "libnetfilter-cttimeout/$2" "want:'$3' got:'$_got'" fi } NF_PROTO=${V_S01-} NF_HELD="held by S01 netfilter-socket ($NF_PROTO): wasm32_defconfig sets no CONFIG_NETFILTER, so netlink protocol 12 is unregistered (brintos/linux#86)" # want_s : same comparison, but a red while # the netfilter socket is unavailable carries its cause. Green is scored green # by the same test as want(), so these flip by themselves the day the option # lands rather than needing this file edited. want_s() { eval "_got=\${V_$1-}" if [ "$_got" = "$3" ]; then bst_ok "libnetfilter-cttimeout/$2" elif [ "$NF_PROTO" != "ok" ]; then bst_notok "libnetfilter-cttimeout/$2" "want:'$3' got:'$_got' -- $NF_HELD" elif [ -z "$_got" ]; then bst_notok "libnetfilter-cttimeout/$2" "no value on target (probe stopped before $1); want='$3'" else bst_notok "libnetfilter-cttimeout/$2" "want:'$3' got:'$_got'" fi } # --- Z: the wire-format constants ----------------------------------------- # Scored first: a disagreement here explains every hex mismatch after it. # Z09-Z12 are the uapi numbering -- what goes on the wire. Z13-Z15 are the # LIBRARY numbering -- an index into its own bitset. They are a different # space and they are off by one, because build_payload writes state index i as # attribute type i+1; confusing them is the single easiest way to write a # wrong consumer, so both are pinned. want Z01 sizeof-nlmsghdr 16 want Z02 sizeof-nfgenmsg 4 want Z03 sizeof-nlattr 4 want Z04 mnl-alignto 4 want Z05 mnl-nlmsg-hdrlen 16 want Z06 mnl-attr-hdrlen 4 want Z07 subsys-cttimeout 8 want Z08 nfnetlink-v0 0 want Z09 uapi-attr-numbers '1 2 3 4 5' want Z10 uapi-attr-max 5 want Z11 uapi-tcp-numbers '1 2 3 4 5 6 7 8 9 10 11' want Z12 uapi-udp-numbers '1 2' want Z13 lib-attr-numbers '0 1 2 3 4' want Z14 lib-tcp-attr-numbers '0 1 2 3 4 5 6 7 8 9 10 11' want Z15 lib-udp-attr-numbers '0 1 2' want Z16 msg-types '0 1 2 3 4' want Z17 name-max 32 # --- H: nfct_timeout_nlmsg_build_hdr --------------------------------------- # 16 bytes of nlmsghdr + the 4-byte nfgenmsg extra header. H03/H04 decompose # what H02 pins, so a red in the hex is readable without a hex editor. subsys 8 # is NFNL_SUBSYS_CTNETLINK_TIMEOUT, which is what distinguishes this message # from the cthelper sibling's otherwise identically shaped header. want H01 hdr-len 20 want H02 hdr-bytes 1400000000080504443322110000000000000000 want H03 hdr-fields 'type=0x0800 subsys=8 cmd=0 flags=0x0405 seq=0x11223344' want H04 hdr-nfgenmsg 'family=0 version=0 res_id=0' # --- B: nfct_timeout_nlmsg_build_payload, one attribute at a time ---------- # Incremental rather than only the full message: a single wrong encoding # reddens exactly its own pair and everything after it, and the FIRST red # names the attribute that broke. # # B05/B06 are where byte order becomes visible. l3num is an address family and # still goes on the wire htons()'d, so AF_INET appears as 0002 and not as the # host's 0200 -- on a little-endian wasm32 target a port that dropped the # htons would produce a message the kernel reads as family 512. # # B09/B10 add ONE policy state: the CTA_TIMEOUT_DATA nest appears here and not # before, because build_payload emits it only when the POLICY bit is set AND # polset is non-zero, and only policy_attr_set_u32 sets either. B11/B12 are all # eleven TCP states, written in ascending order of state index regardless of # the order they were set -- build_payload walks the array, not a list. # # B13/B14 are UDP: a second protocol tracker with a two-entry array instead of # eleven, which is what proves the per-protocol table is consulted rather than # TCP assumed. want B01 build-empty-len 20 want B02 build-empty-bytes 1400000000080504443322110000000000000000 want B03 build-name-len 32 want B04 build-name-bytes 20000000000805044433221100000000000000000c0001007765622d74637000 want B05 build-l3-len 40 want B06 build-l3-bytes 28000000000805044433221100000000000000000c0001007765622d746370000600020000020000 want B07 build-l4-len 48 want B08 build-l4-bytes 30000000000805044433221100000000000000000c0001007765622d7463700006000200000200000500030006000000 want B09 build-1policy-len 60 want B10 build-1policy-bytes 3c000000000805044433221100000000000000000c0001007765622d74637000060002000002000005000300060000000c0004800800030000000067 want B11 build-tcp-full-len 140 want B12 build-tcp-full-bytes 8c000000000805044433221100000000000000000c0001007765622d74637000060002000002000005000300060000005c00048008000100000000650800020000000066080003000000006708000400000000680800050000000069080006000000006a080007000000006b080008000000006c080009000000006d08000a000000006e08000b000000006f want B13 build-udp-full-len 68 want B14 build-udp-full-bytes 44000000000805044433221100000000000000000c000100646e732d75647000060002000002000005000300110000001400048008000100000000c908000200000000ca # --- P: nfct_timeout_nlmsg_parse_payload, read back through the render ----- # The library has no getter, so P02-P04 are lifted out of # nfct_timeout_snprintf()'s text by field name and P05 pins the whole render # on one line (the probe escapes newlines and tabs so each case is one line). # l3proto 2 is AF_INET and l4proto 6 is IPPROTO_TCP; both are asserted as the # numbers that were put in, not as the macros. want P01 parse-rc 0 want P02 parse-name web-tcp want P03 parse-l3 2 want P04 parse-l4 6 want P05 parse-render '.web-tcp = {\n\t.l3proto = 2,\n\t.l4proto = 6,\n\t.policy = {\n\t\t.SYN_SENT = 101,\n\t\t.SYN_RECV = 102,\n\t\t.ESTABLISHED = 103,\n\t\t.FIN_WAIT = 104,\n\t\t.CLOSE_WAIT = 105,\n\t\t.LAST_ACK = 106,\n\t\t.TIME_WAIT = 107,\n\t\t.CLOSE = 108,\n\t\t.SYN_SENT2 = 109,\n\t\t.RETRANS = 110,\n\t\t.UNACKNOWLEDGED = 111,\n\t},\n};' # --- T: every TCP timeout survived the round trip -------------------------- # The ticket's "check each value", one case per state. The eleven values are # 101..111 -- distinct rather than realistic -- so a parser that transposed two # states reddens two named cases instead of none. want T01 tcp-syn-sent 101 want T02 tcp-syn-recv 102 want T03 tcp-established 103 want T04 tcp-fin-wait 104 want T05 tcp-close-wait 105 want T06 tcp-last-ack 106 want T07 tcp-time-wait 107 want T08 tcp-close 108 want T09 tcp-syn-sent2 109 want T10 tcp-retrans 110 want T11 tcp-unack 111 # --- W: the same for UDP, plus the object name ---------------------------- want W01 udp-name dns-udp want W02 udp-unreplied 201 want W03 udp-replied 202 # --- R: re-serialisation, the byte-exact half of "every field survives" ---- # UNLIKE THE cthelper SIBLING, THE ROUND TRIP HERE IS SYMMETRIC, and equal=yes # is the assertion rather than a shortfall. In libnetfilter_cthelper the parse # path assigns expect_policy[] directly and never sets the bits build_payload # gates on, so a parsed helper re-serialises WITHOUT its policies and # BRINTOS-964 R03 pins a 40-byte deficit. This library's # timeout_parse_attr_data() goes back in through # nfct_timeout_policy_attr_set_u32(), which sets polset AND # NFCT_TIMEOUT_ATTR_POLICY -- so the bytes come back identical. The difference # between the two suites is deliberate, not an expectation left uncopied. # R02 is byte-for-byte B12 by construction. want R01 reserialise-tcp 'len=140 equal=yes' want R02 reserialise-tcp-bytes 8c000000000805044433221100000000000000000c0001007765622d74637000060002000002000005000300060000005c00048008000100000000650800020000000066080003000000006708000400000000680800050000000069080006000000006a080007000000006b080008000000006c080009000000006d08000a000000006e08000b000000006f want R03 reserialise-udp 'len=68 equal=yes' # --- A: nfct_timeout_policy_attr_to_name ---------------------------------- # The one part of the API that is pure lookup. A03 is the generic tracker # upstream falls back to for a protocol it does not model. A04 drives the arm # with no tracker at all, which returns NULL -- and prints to stdout on the way # out; attr-to-name-stdout-diagnostic above scores that line. want A01 state-names-tcp 'SYN_SENT SYN_RECV ESTABLISHED FIN_WAIT CLOSE_WAIT LAST_ACK TIME_WAIT CLOSE SYN_SENT2 RETRANS UNACKNOWLEDGED' want A02 state-names-udp 'UNREPLIED REPLIED' want A03 state-names-generic TIMEOUT want A04 state-names-unsupported null # --- N: nfct_timeout_snprintf --------------------------------------------- # N01/N03 pin the RETURN VALUE, which is strlen("};") and not the 0 the doc # comment promises -- see the header. N05 pins the rendered length, which is # the number a consumer sizing a buffer actually needs, because a buffer one # byte short is a write past the end here rather than a truncation. N06 pins # the unknown-output-type arm, the one case in which the documented return is # the real one: nothing rendered, rc 0, buffer untouched. want N01 snprintf-rc-tcp 2 want N02 snprintf-tcp '.web-tcp = {\n\t.l3proto = 2,\n\t.l4proto = 6,\n\t.policy = {\n\t\t.SYN_SENT = 101,\n\t\t.SYN_RECV = 102,\n\t\t.ESTABLISHED = 103,\n\t\t.FIN_WAIT = 104,\n\t\t.CLOSE_WAIT = 105,\n\t\t.LAST_ACK = 106,\n\t\t.TIME_WAIT = 107,\n\t\t.CLOSE = 108,\n\t\t.SYN_SENT2 = 109,\n\t\t.RETRANS = 110,\n\t\t.UNACKNOWLEDGED = 111,\n\t},\n};' want N03 snprintf-rc-udp 2 want N04 snprintf-udp '.dns-udp = {\n\t.l3proto = 2,\n\t.l4proto = 17,\n\t.policy = {\n\t\t.UNREPLIED = 201,\n\t\t.REPLIED = 202,\n\t},\n};' want N05 snprintf-render-len 280 want N06 snprintf-unknown-type 'rc=0 buf=x' # --- U: the two unset calls, and the upstream defect between them ---------- # U01/U02: nfct_timeout_attr_unset(NAME) removes the 12-byte name attribute # from the wire (140 - 12 = 128), which is the call working as documented. # U03/U04: nfct_timeout_policy_attr_unset(state) clears bit `state` of ATTRSET # rather than of polset, so it forgets an OBJECT attribute of the same index # and leaves the state on the wire -- state 2 (ESTABLISHED) drops L4PROTO # (140 - 8 = 132) and state 3 (FIN_WAIT) drops the entire 92-byte policy nest # (140 - 92 = 48). U05 reaches that same 48 the correct way, which is what # makes U04 a defect rather than a coincidence. want U01 unset-name-len 128 want U02 unset-name-bytes 8000000000080504443322110000000000000000060002000002000005000300060000005c00048008000100000000650800020000000066080003000000006708000400000000680800050000000069080006000000006a080007000000006b080008000000006c080009000000006d08000a000000006e08000b000000006f want U03 policy-unset-drops-l4proto 132 want U04 policy-unset-drops-policy-nest 48 want U05 unset-policy-len 48 # --- C: the control, scored strictly -------------------------------------- want C01 rtnetlink-socket ok want C02 rtnetlink-bind ok # --- S: the netfilter round trip, expected red on this image -------------- # S03's value is what the native x86-64 control actually measured under # `unshare -rn`, not a criterion asserted for a kernel nobody ran -- see the # header. want_s S01 netfilter-socket ok want_s S02 netfilter-bind ok want_s S03 netfilter-roundtrip 'cb=0 errno=none' # --- M: parse tolerance, scored last because the probe runs it last -------- # M01: parse_payload returns 0 for a message with no attributes at all, # against a doc comment promising -1 -- there is no path in it that returns # anything else. # M02: a policy nest with no L4PROTO beside it. timeout_parse_attr_data() # sizes `struct nlattr *tb[nlattr_max]` -- a VLA -- from the parsed l4num, so # an unset protocol makes it zero-length and every attribute is rejected # before the array is read: the policy is silently dropped. M03 is the same # message WITH L4PROTO, so M02's "dropped" reads as the consequence of the # missing attribute rather than of the nest. want M01 parse-empty-rc 0 want M02 parse-policy-without-l4proto 'rc=0 policy=dropped' want M03 parse-policy-with-l4proto 'rc=0 policy=kept' rm -f "$pout"