. "$BST_LIB" # BST-TIMEOUT: 240 # eight bounded probe invocations, none of which waits on a # clock, a socket or a peer. Nothing here is large. # libslirp 4.7.0 -- the userspace TCP/IP stack QEMU and friends use to give a # guest networking with no tap device and no privilege. # # WHY THIS SUITE IS ALMOST ENTIRELY ONE PROGRAM. libslirp's whole interface is # a set of callbacks and two functions: slirp_input takes a raw Ethernet frame # in, and the send_packet callback hands one back out. There is no command, no # daemon and no file format, so there is nothing for a shell to drive -- the # bytes ARE the interface. libslirp-probe is therefore the consumer, built by # this port's own recipe against the very archive it produced, which is also # what rules out this static-only port shipping unlinkable: an undefined symbol # in a static archive is not an error until something links it, and that is how # glog shipped green and unusable (BRINTOS-1101). # # THE STACK IS DRIVEN IN RESTRICTED MODE, which is what makes every case here # hermetic rather than merely quick. With cfg.restricted set, tcp_input answers # an unmatched SYN with dropwithreset, udp_input drops, and icmp_input reflects # an echo aimed at vhost or vnameserver instead of calling socreate -- so ARP, # ICMP, TCP-reset, DHCP and TFTP are all answered INSIDE the library, with no # descriptor of any kind. That is why a target with no network can measure a # network stack in full. # # TWO KINDS OF ASSERTION, and the split is deliberate: # # 1. The PACKET phases (arp, icmp, tcp, udp) are diffed whole against # libslirp-native.txt -- the same probe source built natively at # do_compile. Those phases emit nothing but protocol bytes, which are # architecture-independent by construction, so the two arms must agree # exactly and anything that differs is a real divergence rather than an # arch fact. A handful of individual fields are ALSO asserted by name # immediately below each diff, so that a failure says which field went # wrong rather than only that the block did. # 2. version, lifecycle, state, info and hostfwd are asserted by NAME against # values derived from the source, and are excluded from the diff. Three # different reasons: sizeof(SlirpConfig) differs between a 32-bit target # and the native twin by construction; the state blob's size is not # something a suite should pin; and hostfwd is the one phase that opens a # real socket, so its answer is a statement about the platform rather than # about libslirp. tmp=${TMPDIR:-/tmp}/bst-libslirp.$$ mkdir -p "$tmp" # The first result line of a suite is dropped in transport # (brintos/linux#96), so the first case is one whose loss costs nothing. bst_ok libslirp/suite-canary # =========================================================================== # 0. The shipped tree. # # This image installs no -dev and no -staticdev, so libslirp.a, libslirp.h and # slirp.pc are NOT on the target and are deliberately not asserted -- a # `test -f /usr/lib/libslirp.a' would be red on every image that ever built # this recipe. The probe existing at all is what establishes the archive is # linkable, because do_compile cross-linked it against exactly that archive. # =========================================================================== ORACLE=$BST_DATA/libslirp-native.txt check libslirp/probe-shipped test -f "$BST_DATA/libslirp-probe" check libslirp/oracle-shipped test -f "$ORACLE" probe=$tmp/libslirp-probe if cp "$BST_DATA/libslirp-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then bst_ok libslirp/probe-stage else bst_notok libslirp/probe-stage "cannot stage $BST_DATA/libslirp-probe" rm -rf "$tmp" exit 0 fi # diff_phase : the target's block for against the oracle's. # # Both sides are stripped of their `== ' markers so a phase rename cannot make # the diff pass by emptying both sides. An EMPTY expectation is a red, never a # pass: a missing oracle block would otherwise assert nothing at all and report # success for it, which is the one outcome a comparison must never produce. diff_phase() { _dp_id=$1; _dp_ph=$2 sed -n "/^== $_dp_ph\$/,/^== /p" "$ORACLE" | grep -v '^== ' > "$tmp/$_dp_ph.want" grep -v '^== ' "$tmp/$_dp_ph.out" > "$tmp/$_dp_ph.got" if [ ! -s "$tmp/$_dp_ph.want" ]; then bst_notok "$_dp_id" "no $_dp_ph block in $ORACLE" elif diff "$tmp/$_dp_ph.want" "$tmp/$_dp_ph.got" > "$tmp/$_dp_ph.diff" 2>&1; then bst_ok "$_dp_id" else bst_notok "$_dp_id" "$(_bst_1line "$(cat "$tmp/$_dp_ph.diff")")" fi } # =========================================================================== # 1. version -- the pinned release, and the ABI a caller compiles against. # =========================================================================== # STDERR IS CAPTURED SEPARATELY THROUGHOUT. libslirp reports misuse through # glib, and glib prefixes every g_critical/g_warning line with a WALL-CLOCK # TIME and the pid -- so folding it into the asserted output would make the # oracle diff below differ from itself on every run. Everything this suite # asserts is a value the probe prints on stdout. bst_bound 30 "$probe" version > "$tmp/version.out" 2>"$tmp/version.err" check_eq libslirp/version-rc 0 "$?" # slirp_version_string() is generated at BUILD time by build-aux/git-version-gen # from the git checkout's own describe output, not from the meson project # version -- so this case is what catches the recipe fetching a different # SRCREV than the one the port was written against, which no other case here # could see. check_out libslirp/version-string "VERSION_STRING=4.7.0" cat "$tmp/version.out" check_out libslirp/version-major "MAJOR=4" cat "$tmp/version.out" check_out libslirp/version-minor "MINOR=7" cat "$tmp/version.out" check_out libslirp/version-micro "MICRO=0" cat "$tmp/version.out" check_out libslirp/version-pv "BUILT_AGAINST=4.7.0" cat "$tmp/version.out" # The SlirpConfig version range this build accepts. A caller passes cfg.version # and the library reads only the fields that version declares, so the maximum # is the ABI contract; the probe configures at 4 and would be reading a struct # the library did not understand if this were lower. check_out libslirp/config-version-max "CONFIG_VERSION_MAX=4" cat "$tmp/version.out" check_out libslirp/config-version-min "CONFIG_VERSION_MIN=1" cat "$tmp/version.out" # The state format version, which a caller must save alongside the blob. check_out libslirp/state-version "STATE_VERSION=4" cat "$tmp/version.out" # =========================================================================== # 2. lifecycle -- creation, refusal, teardown, and two stacks at once. # =========================================================================== bst_bound 30 "$probe" lifecycle > "$tmp/lifecycle.out" 2>"$tmp/lifecycle.err" check_eq libslirp/lifecycle-rc 0 "$?" # A config version ABOVE what this build understands must be REFUSED. Reading # such a struct would mean reading fields that are not there, so a non-NULL # answer here is a library that has stopped checking. diff_phase libslirp/lifecycle-matches-native lifecycle check_out libslirp/new-rejects-future-version "NEW_BAD_VERSION=null" cat "$tmp/lifecycle.out" check_out libslirp/new-ok "NEW=ok" cat "$tmp/lifecycle.out" # ZERO TIMERS ON AN IPv4-ONLY STACK, and that is the measured contract rather # than a disappointment: the only timer libslirp arms is the IPv6 router # advertisement. Asserting it makes the v6 arm below visibly the thing that # exercises the timer vtable -- without these two cases both arms would read # zero and nobody would notice the callbacks were never called. check_out libslirp/timers-none-on-v4 "TIMERS_CREATED_V4=0" cat "$tmp/lifecycle.out" check_out libslirp/timers-freed-v4 "TIMERS_FREED_V4=0" cat "$tmp/lifecycle.out" # The vtable actually called. slirp_new dereferences these entries without # checking, so a NULL one is a trap rather than a diagnostic -- which is why a # configuration that reaches them is worth having. check_out libslirp/v6-stack-created "NEW_V6=ok" cat "$tmp/lifecycle.out" check_out libslirp/v6-timer-created "TIMERS_CREATED_V6=1" cat "$tmp/lifecycle.out" check_out libslirp/v6-timer-armed "TIMER_ARMED_V6=1" cat "$tmp/lifecycle.out" check_out libslirp/v6-timer-freed "TIMERS_FREED_V6=1" cat "$tmp/lifecycle.out" # Nothing in libslirp is documented as a singleton, and a hypervisor embeds one # stack per guest. The failure mode if it were -- shared global state -- is # invisible from a single instance, which is why two are created. check_out libslirp/two-stacks "TWO_STACKS=ok" cat "$tmp/lifecycle.out" check_out libslirp/two-stacks-free "TWO_STACKS_CLEANED=ok" cat "$tmp/lifecycle.out" # =========================================================================== # 3. arp -- address resolution, which every other IP case depends on. # =========================================================================== bst_bound 30 "$probe" arp > "$tmp/arp.out" 2>"$tmp/arp.err" check_eq libslirp/arp-rc 0 "$?" diff_phase libslirp/arp-matches-native arp # The gateway reply. This is the case the whole port turns on: a guest that # cannot resolve 10.0.2.2 has no default route and no networking at all. check_out libslirp/arp-gw-one-reply "GW_FRAMES=1" cat "$tmp/arp.out" check_out libslirp/arp-gw-is-arp "GW_IS_ARP=1" cat "$tmp/arp.out" check_out libslirp/arp-gw-opcode "GW_OP=2" cat "$tmp/arp.out" # 52:55 then the four bytes of the address being asked about. slirp SYNTHESISES # a MAC per virtual address rather than holding a table, so 10.0.2.2 is always # 52:55:0a:00:02:02 -- the documented address, and the one a guest's ARP cache # ends up holding for its default gateway. check_out libslirp/arp-gw-sha "GW_SHA=52:55:0a:00:02:02" cat "$tmp/arp.out" check_out libslirp/arp-gw-spa "GW_SPA=10.0.2.2" cat "$tmp/arp.out" check_out libslirp/arp-gw-tpa "GW_TPA=10.0.2.15" cat "$tmp/arp.out" check_out libslirp/arp-gw-tha "GW_THA=52:54:00:12:34:56" cat "$tmp/arp.out" # 64, not 42. arp_input sizes its buffer MAX(ETH_HLEN + arphdr, 64), so the # frame that reaches the guest carries 22 bytes of zero padding -- which is what # a real NIC's minimum-frame padding looks like, and a change here would mean # the buffer was resized under it. check_out libslirp/arp-gw-padded-to-64 "GW_LEN=64" cat "$tmp/arp.out" # The virtual nameserver answers ARP too -- it has to, or a guest configured # with 10.0.2.3 cannot address its resolver at all. check_out libslirp/arp-dns-sha "DNS_SHA=52:55:0a:00:02:03" cat "$tmp/arp.out" check_out libslirp/arp-dns-spa "DNS_SPA=10.0.2.3" cat "$tmp/arp.out" # SILENCE IS THE CONTRACT for everything else, and each of these is a separate # reason. slirp is not a proxy ARP for its whole subnet: a stack that answered # for 10.0.2.99 would blackhole every guest-to-guest address on the segment. check_out libslirp/arp-inside-silent "INSIDE_FRAMES=0" cat "$tmp/arp.out" check_out libslirp/arp-outside-silent "OUTSIDE_FRAMES=0" cat "$tmp/arp.out" # A gratuitous ARP updates the table and is NOT answered; a reply would be an # ARP storm between two hosts that both announce themselves. check_out libslirp/arp-gratuitous-silent "GRAT_FRAMES=0" cat "$tmp/arp.out" # Malformed input: dropped without being read past. check_out libslirp/arp-short-silent "SHORT_FRAMES=0" cat "$tmp/arp.out" check_out libslirp/arp-badop-silent "BADOP_FRAMES=0" cat "$tmp/arp.out" # =========================================================================== # 4. icmp -- echo reflection, which needs no socket and no privilege. # =========================================================================== bst_bound 30 "$probe" icmp > "$tmp/icmp.out" 2>"$tmp/icmp.err" check_eq libslirp/icmp-rc 0 "$?" diff_phase libslirp/icmp-matches-native icmp check_out libslirp/icmp-echo-one-reply "ECHO_FRAMES=1" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-is-icmp "ECHO_IS_ICMP=1" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-type "ECHO_TYPE=0" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-code "ECHO_CODE=0" cat "$tmp/icmp.out" # The identifier and sequence come back unchanged -- that is how ping matches a # reply to the request it sent, and a stack that renumbered them would look # alive to a packet capture and dead to every client. check_out libslirp/icmp-echo-id "ECHO_ID=48879" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-seq "ECHO_SEQ=7" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-payload "ECHO_PAYLOAD=brintos-2227" cat "$tmp/icmp.out" # Source and destination are swapped, which is the whole of "reflect". check_out libslirp/icmp-echo-src "ECHO_SRC=10.0.2.2" cat "$tmp/icmp.out" check_out libslirp/icmp-echo-dst "ECHO_DST=10.0.2.15" cat "$tmp/icmp.out" # A reflected reply must itself be checksum-correct or the guest's own stack # discards it and the ping merely appears to be lost -- a failure that looks # like a dead network rather than like a broken library. check_out libslirp/icmp-ip-cksum "ECHO_IP_CKSUM_OK=1" cat "$tmp/icmp.out" check_out libslirp/icmp-icmp-cksum "ECHO_ICMP_CKSUM_OK=1" cat "$tmp/icmp.out" # The nameserver address is the second one icmp_input reflects for, and the one # a guest pings to check its resolver is reachable. check_out libslirp/icmp-dns-type "DNSECHO_TYPE=0" cat "$tmp/icmp.out" check_out libslirp/icmp-dns-src "DNSECHO_SRC=10.0.2.3" cat "$tmp/icmp.out" # The three drop paths, each a different line of the source and each silent. check_out libslirp/icmp-outside-dropped "OUTECHO_FRAMES=0" cat "$tmp/icmp.out" check_out libslirp/icmp-badsum-dropped "BADICMP_FRAMES=0" cat "$tmp/icmp.out" check_out libslirp/icmp-badip-dropped "BADIP_FRAMES=0" cat "$tmp/icmp.out" # =========================================================================== # 5. tcp -- the reset path, and the two silences beside it. # =========================================================================== bst_bound 30 "$probe" tcp > "$tmp/tcp.out" 2>"$tmp/tcp.err" check_eq libslirp/tcp-rc 0 "$?" diff_phase libslirp/tcp-matches-native tcp check_out libslirp/tcp-syn-one-reply "SYN_FRAMES=1" cat "$tmp/tcp.out" check_out libslirp/tcp-syn-is-tcp "SYN_IS_TCP=1" cat "$tmp/tcp.out" # 0x14 = RST|ACK. A REFUSAL the guest can act on immediately; the alternative, # silence, makes it retry for its whole SYN timeout, which is why this # distinction is worth a case rather than a comment. check_out libslirp/tcp-syn-rst-ack "SYN_FLAGS=0x14" cat "$tmp/tcp.out" # The reset acknowledges seq+1 -- RFC 793's rule for a RST answering a SYN, and # the thing a guest checks before it accepts the refusal rather than ignoring it. check_out libslirp/tcp-syn-ack-seq-plus-1 "SYN_ACK=0x11223345" cat "$tmp/tcp.out" # The ports are swapped, so the reset arrives on the connection that opened. check_out libslirp/tcp-syn-sport "SYN_SPORT=80" cat "$tmp/tcp.out" check_out libslirp/tcp-syn-dport "SYN_DPORT=40000" cat "$tmp/tcp.out" check_out libslirp/tcp-syn-src "SYN_SRC=10.0.2.2" cat "$tmp/tcp.out" check_out libslirp/tcp-syn-dst "SYN_DST=10.0.2.15" cat "$tmp/tcp.out" check_out libslirp/tcp-syn-ip-cksum "SYN_IP_CKSUM_OK=1" cat "$tmp/tcp.out" # DNS is UDP, so a TCP SYN at the nameserver is still an unmatched connection # and still a reset. check_out libslirp/tcp-dns-rst-ack "DNSSYN_FLAGS=0x14" cat "$tmp/tcp.out" # A SEGMENT WITH A BAD TCP CHECKSUM IS DROPPED IN SILENCE, NOT RESET, and the # difference matters: a reset would let anyone able to inject one corrupt frame # tear down an established connection. The two negative paths therefore look # different on the wire, and asserting only one of them would miss the swap. check_out libslirp/tcp-badsum-silent "BADSUM_FRAMES=0" cat "$tmp/tcp.out" check_out libslirp/tcp-trunc-silent "TRUNC_FRAMES=0" cat "$tmp/tcp.out" # =========================================================================== # 6. udp -- the two services answered in process, and the malformed inputs. # =========================================================================== bst_bound 30 "$probe" udp > "$tmp/udp.out" 2>"$tmp/udp.err" udp_rc=$? check_eq libslirp/udp-rc 0 "$udp_rc" diff_phase libslirp/udp-matches-native udp # TFTP is handled BEFORE the restricted check, so it is the one UDP service # this suite can drive with no host behind slirp at all. With no tftp_path # configured a read request must come back as a TFTP ERROR rather than as # silence -- a client that gets silence retries for its whole timeout instead # of reporting the failure. check_out libslirp/tftp-one-reply "TFTP_FRAMES=1" cat "$tmp/udp.out" check_out libslirp/tftp-is-udp "TFTP_IS_UDP=1" cat "$tmp/udp.out" check_out libslirp/tftp-opcode-error "TFTP_OPCODE=5" cat "$tmp/udp.out" # Error code 2, "Access violation" -- the RFC 1350 code for a refused read, # which is the right answer when no tftp_path is configured. Code 1, "File not # found", would be the WRONG one: it says the server is serving and this file # is missing, which sends a client looking for a file rather than for a server. check_out libslirp/tftp-errcode "TFTP_ERRCODE=2" cat "$tmp/udp.out" check_out libslirp/tftp-errmsg "TFTP_ERRMSG=Access violation" cat "$tmp/udp.out" # The reply comes FROM port 69 and back to the port the request came from. check_out libslirp/tftp-sport "TFTP_SPORT=69" cat "$tmp/udp.out" check_out libslirp/tftp-dport "TFTP_DPORT=41000" cat "$tmp/udp.out" # DHCP, ASSERTED IN BOTH MODES, because the option set is the only place # restricted mode is visible to a guest at all. # # THE RESTRICTED PREFIX IS `RDHCP', NOT `DHCP', and that is load-bearing: # check_out matches by SUBSTRING, and "DHCP_OP=2" is a substring of # "UDHCP_OP=2". Every restricted case would then pass on the unrestricted run's # line while the restricted run said something else -- a case that passes for # the wrong reason, which is worse than one that fails. Neither prefix is a # suffix of the other. # # bootp_input answers a DISCOVER entirely in process, so both runs are hermetic # and neither needs anything behind slirp -- which is the entire reason a # hypervisor embeds this library rather than a real stack. check_out libslirp/dhcp-one-reply "RDHCP_FRAMES=1" cat "$tmp/udp.out" check_out libslirp/dhcp-bootreply "RDHCP_OP=2" cat "$tmp/udp.out" # The transaction id is echoed; a client that got a different one discards the # offer and never configures. check_out libslirp/dhcp-xid "RDHCP_XID=0x11223344" cat "$tmp/udp.out" check_out libslirp/dhcp-magic "RDHCP_MAGIC=0x63825363" cat "$tmp/udp.out" check_out libslirp/dhcp-offer "RDHCP_MSGTYPE=2" cat "$tmp/udp.out" # The address offered is vdhcp_start and the mask is vnetmask -- the config's # own values arriving back as the options a client configures from. check_out libslirp/dhcp-yiaddr "RDHCP_YIADDR=10.0.2.15" cat "$tmp/udp.out" check_out libslirp/dhcp-mask "RDHCP_MASK=255.255.255.0" cat "$tmp/udp.out" check_out libslirp/dhcp-srvid "RDHCP_SRVID=10.0.2.2" cat "$tmp/udp.out" check_out libslirp/dhcp-lease "RDHCP_LEASE=86400" cat "$tmp/udp.out" # Option 12 is the one option carrying a value the CALLER configured -- # slirp_new copies cfg.vhostname into client_hostname -- rather than one # bootp.c derived from the network numbers. It is what proves a config field # reaches the guest at all. check_out libslirp/dhcp-hostname "RDHCP_HOSTNAME=slirp-probe" cat "$tmp/udp.out" # RESTRICTED MODE OFFERS NO DEFAULT ROUTE AND NO RESOLVER, and this is the pair # of cases that pins it. bootp.c wraps options 3 (router) and 6 (DNS) in # `if (!slirp->restricted)', so a restricted guest is handed an address and a # netmask and nothing it could leave the segment with -- which is restricted # mode working, not a stack that forgot two options. # # ASSERTED THROUGH THE OPTION CENSUS, not through the absence of a printed # line: "the option was not offered" and "the phase died before reaching it" # produce identical output otherwise, and only the first is correct here. check_out libslirp/dhcp-restricted-options "RDHCP_OPT_SEEN=53,54,1,51,12" cat "$tmp/udp.out" # The same DISCOVER against an UNRESTRICTED stack, which is still hermetic -- # bootp needs no socket either way. Two more options appear, and they carry # vhost and vnameserver. Running both is what makes the line above a statement # about restricted mode rather than about this build of bootp.c. check_out libslirp/dhcp-unrestricted-one-reply "UDHCP_FRAMES=1" cat "$tmp/udp.out" check_out libslirp/dhcp-unrestricted-options \ "UDHCP_OPT_SEEN=53,54,1,3,6,51,12" cat "$tmp/udp.out" check_out libslirp/dhcp-unrestricted-router "UDHCP_ROUTER=10.0.2.2" cat "$tmp/udp.out" check_out libslirp/dhcp-unrestricted-dns "UDHCP_DNS=10.0.2.3" cat "$tmp/udp.out" # The malformed inputs the ticket names, each dropped without a reply and # without taking the process with it. SURVIVED=1 is printed after all of them, # so it is the single case that says the stack was still standing at the end. check_out libslirp/udp-outside-dropped "OUTUDP_FRAMES=0" cat "$tmp/udp.out" # A first fragment that never completes: slirp holds it and answers nothing. # The case exists because a reassembler's failure mode is a leak or a crash on # the timeout, neither of which shows up as a reply. check_out libslirp/frag-incomplete-silent "FRAG_FRAMES=0" cat "$tmp/udp.out" # A frame shorter than an Ethernet header -- the one input that reaches # slirp_input with nothing valid in it at all. check_out libslirp/short-frame-silent "TINY_FRAMES=0" cat "$tmp/udp.out" check_out libslirp/zero-length-silent "ZERO_FRAMES=0" cat "$tmp/udp.out" check_out libslirp/unknown-ethertype-silent "UNKETH_FRAMES=0" cat "$tmp/udp.out" check_out libslirp/malformed-survived "SURVIVED=1" cat "$tmp/udp.out" # =========================================================================== # 7. state -- save and load, which is what live migration is built on. # # Asserted by NAME rather than diffed: the blob's byte count is not something a # suite should pin, and what matters is that a second stack can read back what # the first wrote and that the three refusal paths refuse. # =========================================================================== bst_bound 30 "$probe" state > "$tmp/state.out" 2>"$tmp/state.err" check_eq libslirp/state-rc 0 "$?" check_out libslirp/state-save-nonempty "SAVE_NONEMPTY=1" cat "$tmp/state.out" check_out libslirp/state-save-no-overflow "SAVE_OVERFLOW=0" cat "$tmp/state.out" # Loaded into a DIFFERENT stack than the one that saved. Loading back into the # same one would pass on a library that simply kept its own state and ignored # the blob entirely; a second stack has nothing but the bytes. check_out libslirp/state-load-ok "LOAD_RC=0" cat "$tmp/state.out" # Every byte written was read. A load that stopped early would leave the stack # half-populated and report success. check_out libslirp/state-load-consumed-all "LOAD_CONSUMED_ALL=1" cat "$tmp/state.out" # A mismatched version must be refused: the layout is versioned precisely # because reading it wrong would put garbage into live pointers. check_out libslirp/state-badversion-refused "LOAD_BADVER_REFUSED=1" cat "$tmp/state.out" # A TRUNCATED BLOB IS ACCEPTED, and this case is asserted at that MEASURED # value rather than at the one the API's shape suggests. # # The read callback returns short -- exactly what a migration stream cut # mid-transfer looks like -- and slirp_state_load returns 0 anyway. It is an # UPSTREAM ROBUSTNESS GAP, not something this port introduced or could fix # here: libslirp's vmstate reader checks the version field and the field types # but never that the stream had the bytes it asked for, so a caller that trusts # the return value resumes a guest from a half-read state. The wrong-version # path beside it DOES refuse (-22), which is what makes this visible as an # omission rather than as a reader with no checking at all. # # Pinned at the measured behaviour on purpose: a libslirp release that starts # refusing a short read turns this case RED, which is how this port finds out # the gap closed. Nothing is filed for it -- this project reports no defects # against third-party public repositories. check_out libslirp/state-truncated-accepted-upstream-gap \ "LOAD_TRUNC_REFUSED=0" cat "$tmp/state.out" check_out libslirp/state-survived "SURVIVED=1" cat "$tmp/state.out" # =========================================================================== # 8. info -- the two dumps, and the only API that returns owned memory. # =========================================================================== bst_bound 30 "$probe" info > "$tmp/info.out" 2>"$tmp/info.err" check_eq libslirp/info-rc 0 "$?" diff_phase libslirp/info-matches-native info check_out libslirp/conn-info-returns "CONN_INFO_NULL=0" cat "$tmp/info.out" check_out libslirp/conn-info-nonempty "CONN_INFO_LEN_NONZERO=1" cat "$tmp/info.out" check_out libslirp/conn-info-header "CONN_INFO_HAS_PROTO=1" cat "$tmp/info.out" check_out libslirp/conn-info-freed "CONN_INFO_FREED=1" cat "$tmp/info.out" check_out libslirp/neigh-info-returns "NEIGH_INFO_NULL=0" cat "$tmp/info.out" check_out libslirp/neigh-info-nonempty "NEIGH_INFO_LEN_NONZERO=1" cat "$tmp/info.out" # The guest is in the table because the phase primed ARP first. An empty table # here would mean the ARP learning the other phases depend on recorded nothing, # which no other case in this suite can see. check_out libslirp/neigh-info-has-guest "NEIGH_INFO_HAS_GUEST=1" cat "$tmp/info.out" check_out libslirp/neigh-info-freed "NEIGH_INFO_FREED=1" cat "$tmp/info.out" check_out libslirp/info-survived "SURVIVED=1" cat "$tmp/info.out" # =========================================================================== # 9. hostfwd -- THE ONE PHASE THAT NEEDS THE PLATFORM. # # slirp_add_hostfwd opens a listening socket in the real world: socket(2), # bind(2), listen(2). So unlike everything above, its result is a statement # about this image's kernel as much as about libslirp -- which is why it is not # in the oracle diff, and why the cases below are written to MEASURE what the # platform does rather than to assume it. # # THE PROBE SEARCHES FOR A FREE PORT RATHER THAN ASKING FOR 0, and the reason # is a measurement rather than a preference. bind(2) reads 0 as "any free port", # which is the obvious way to avoid colliding with whatever else is on the # machine -- but libslirp records a forward under the port it was ASKED for, not # the one it got. So two adds of "port 0" are not duplicates and are both # accepted, and a remove of "port 0" matches nothing and fails. Measured on the # native arm before this was fixed: DUP_RC=0 and REMOVE_RC=-1, which reads # exactly like libslirp failing to detect a duplicate and failing to remove a # forward it had just made. Neither was true; the phase was asking the wrong # question, and two of its three cases were asserting nothing. # # The native twin DOES run this phase and its output IS in libslirp-native.txt, # last -- but nothing here diffs that block, because the build host has ordinary # sockets and this target may not. What recording it buys is the guestfwd half # below: those return values are measured on the native arm rather than being # numbers somebody expected. # =========================================================================== bst_bound 30 "$probe" hostfwd > "$tmp/hostfwd.out" 2>"$tmp/hostfwd.err" hostfwd_rc=$? check_eq libslirp/hostfwd-rc 0 "$hostfwd_rc" # The guestfwd half needs NO host socket -- the data goes to a callback in the # probe's own process -- so it is the half of forwarding that is measurable # whatever this kernel does about bind(2), and it is asserted unconditionally. check_out libslirp/guestfwd-add "GUESTFWD_ADD_RC=0" cat "$tmp/hostfwd.out" check_out libslirp/guestfwd-remove "GUESTFWD_REMOVE_RC=0" cat "$tmp/hostfwd.out" # Removing what is no longer there is an ERROR, not a no-op: a caller that # cannot tell the two apart cannot reference-count its forwards. check_out libslirp/guestfwd-remove-again-refused \ "GUESTFWD_REMOVE_AGAIN_RC=-1" cat "$tmp/hostfwd.out" check_out libslirp/hostfwd-survived "SURVIVED=1" cat "$tmp/hostfwd.out" # The host-socket half is graded on what it returned. A success is asserted # through its whole documented shape -- add, duplicate refused, remove, second # remove refused. A failure is reported as a red naming the errno, because a # listening socket is a capability this image may not have and the suite must # say which of the two it measured rather than passing either way. if grep -q '^ADD_RC=0$' "$tmp/hostfwd.out"; then bst_ok libslirp/hostfwd-add check_out libslirp/hostfwd-found-port "ADD_FOUND_PORT=1" cat "$tmp/hostfwd.out" # A DUPLICATE must be refused. Two forwards for one host port would leave # the second bind failing silently and traffic going to whichever of them # the lookup happened to find first. check_out libslirp/hostfwd-duplicate-refused "DUP_RC=-1" cat "$tmp/hostfwd.out" check_out libslirp/hostfwd-remove "REMOVE_RC=0" cat "$tmp/hostfwd.out" check_out libslirp/hostfwd-remove-again-refused \ "REMOVE_AGAIN_RC=-1" cat "$tmp/hostfwd.out" else bst_notok libslirp/hostfwd-add \ "slirp_add_hostfwd could not open a listening socket on this image: $(_bst_1line "$(grep -E '^ADD_(RC|ERRNO)=' "$tmp/hostfwd.out")")" fi rm -rf "$tmp"