Skip to content

wifipi: send a same-flow pure ACK heading a TX glom on its own (#89) - #115

Closed
tinic wants to merge 2 commits into
mainfrom
fix/89-glom-ack-head
Closed

tinic wants to merge 2 commits into
mainfrom
fix/89-glom-ack-head

Conversation

@tinic

@tinic tinic commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Integrated candidate for #89: moves third_party/wifipi to WiFiPi fix/89-glom-ack-head (29283c4, off maint/beta4 c0782c24). Nothing else changes. It is not merged and has not been run on hardware.

WiFiPi PR: not yet open (my token cannot create PRs on tinic/WiFiPi.device); branch https://github.com/tinic/WiFiPi.device/tree/fix/89-glom-ack-head

Rule (revised in 29283c4). No emitted glom of 3 or more frames starts with two pure ACKs of the same TCP flow. When a glom would, the driver ends it after the pair and starts the next glom from the third frame, so a same-flow ACK run goes out in pairs. The first version (517615d) split only once per glom, which left the same pattern in the remainder: 291 of 306 changed download gloms, as deepseek-v4 pointed out.

  • Pure ACK: IPv4 (not a fragment; IP options allowed), or IPv6 with TCP as the first next header. No TCP payload, ACK set, SYN, FIN and RST clear. TCP options such as timestamps and SACK count as header.
  • Same flow: equal source and destination addresses and ports.
  • Code: wifipi_glom_head_split() and wifipi_glom_take() in src/glomsplit.h (pure C, host-tested). The check sits in SendGlomOnce() in src/packet.c, at frame 1: it sets gh_LastItem on frame 1 and sets count = 2, and the normal chain padding applies. SendGlomDataPacket loops until the queue is empty.
  • Why pairs: two-frame gloms of a same-flow ACK pair lost no head ACK in the peer captures (A11 0 of 44, G2 0 of 54), while gloms of three or more lost it (A11 21 of 21, hw11 129 of 130).

Tests. cc -O2 -Wall -Wextra -Werror -o test_glomsplit tests/test_glomsplit.c && ./test_glomsplit gives RESULT pass=45 fail=0 with clang, clang with ASan/UBSan, and GCC 14. Emission checks cover ACK runs of 3, 4, 8 and 32 (with and without a trailing data frame), 2 ACKs followed by UDP, 2 ACKs followed by 31 data frames (2+31), and 32 data frames (one glom). 9 mutants of the decision are each caught.

Offline replay (playhouse2 ~/anxd-evidence/89-trace/hw5-audit/ackrun_census2.py, output in ackrun_census2.out). Extra transfers and CMD53 bytes compared with today:

corpus once (517615d) iterate (head alone) pairs (this) every ACK alone
A11 hw13 +22 tr +24 tr +23 tr, +6.2 KB +69 tr
hw11 +167 tr +230 tr +192 tr, +62 KB +669 tr
download hw7-ng (337 tr today) +306 tr, 291 residual +3439 tr, +20 KB +1784 tr, +555 KB (+118%) +3759 tr
G2 hw14 0 0 0 +54 tr

The pairs rule leaves 0 residual vulnerable gloms in every corpus. Cost to measure on download: about 6.3 times the CMD53 transfers and 2.2 times the CMD53 bytes (each pair is padded to 512 bytes). The iterate rule needs 11 times the transfers but adds few bytes. The A/B has to be read on download as well as upload.

Census. Missing ACKs are not only at the head:

  • A11 and hw11: loss is at the head of 3+ gloms.
  • Download hw7-ng: loss is spread across positions (runs of 5 or more: position 0 82%, 1 30%, 2 61%, 3 76%, newest 18%; single-ACK gloms 50%).
  • Every missing pure ACK in every corpus had a later ACK of its flow with a higher ack number.
  • Duplicate ACKs missing: 21 of 528 on download and 4 of 129 in hw11.

Build (pinned toolchain 3869b4427396, default preset, clean builds in one checkout; staged at playhouse2 ~/anxd-work/srv/glom/):

  • Candidate (eca5325 + 29283c4): 55896 bytes, sha256 427e31839685161ed33a4b017d13d4cab4d62ac7acff6b62180b09498da6971f.
  • Matched base (WiFiPi c0782c24): 55636 bytes, sha256 478ef4d1e7c0ecfeb8215256a8d874efd34418af68dbdb389250500d3434ef7f. It differs from the earlier base only in the 7-byte tree id in $VER.

🤖 Generated with Claude Code

Points third_party/wifipi at fix/89-glom-ack-head (517615d, off maint/beta4
c0782c24): in a glom of three or more frames headed by two pure ACKs of one
TCP flow, the head ACK goes in its own transfer and the rest as one glom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinic

tinic commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

A/B runner delta for this PR's hardware test (RAM-only driver binding, option (a)). Not in the repository; it lives on playhouse2 in ~/anxd-work/ack/.

3dd831b85425bc7c81f2032f0c8f7f5474d3b399de71ea292ca7ababef3b58c4  ackab-3dd831b8.sh
a6e6620a80ffbddcb77e1b683f2e1eab56f882b0158c3c03692fb4972f587ba2  ackab-115.sh
3dd831b85425bc7c81f2032f0c8f7f5474d3b399de71ea292ca7ababef3b58c4  ackab.sh

ackab.sh (3dd831b8) is unchanged. ackab-115.sh is a copy with the diff below. Without DRVF_C/DRVF_T set, it behaves exactly as 3dd831b8.

--- ackab-3dd831b8.sh	2026-09-28 05:01:46.752179781 +0000
+++ ackab-115.sh	2026-09-28 07:15:16.718112310 +0000
@@ -51,8 +51,21 @@
   local NIF=$NI_C; [ "$T" = T ] && NIF=$NI_T; local NIH; NIH=$(sha256sum < ~/anxd-work/srv/ackceil/$NIF | cut -c1-64)
   printf "MakeDir RAM:NI
fetch $A/$NIF TO RAM:NI/wifipi
fetch $S/wstat256-v2.bin TO RAM:wstat256
fetch $S/devnodes TO RAM:devnodes
Protect RAM:wstat256 +e
Protect RAM:devnodes +e
" | WS 60 > $H/$T-fetch.txt 2>&1
   for kv in "NI/wifipi $NIH" "wstat256 $WST" "devnodes $DVN"; do set -- $kv; h=$(sum "Ram%20Disk/$1"); echo "$1 $h" >> $H/$T-readback.txt; [ "$h" = "$2" ] || { lg "$T READBACK_FAIL $1"; return 1; }; done
+  # #115 driver A/B, RAM only: DRVF_<C|T> (served name), DRVS_<C|T> (sha256), DRVID_<C|T> (devnodes id) bind the leg to RAM:anxwifipi.device
+  local LDF LDS LDI WMDEV=SYS:AmiNetXDuo/Devs/Networks/anxwifipi.device
+  if [ "$T" = T ]; then LDF=${DRVF_T:-}; LDS=${DRVS_T:-}; LDI=${DRVID_T:-}; else LDF=${DRVF_C:-}; LDS=${DRVS_C:-}; LDI=${DRVID_C:-}; fi
+  if [ -n "$LDF" ]; then
+    [ -n "$LDS" ] && [ -n "$LDI" ] || { lg "$T DRIVER_ENV_INCOMPLETE"; return 1; }
+    WMDEV=RAM:anxwifipi.device
+    [ "$(sha256sum < ~/anxd-work/srv/ackceil/$LDF | cut -c1-64)" = "$LDS" ] || { lg "$T SERVED_DRIVER_HASH"; return 1; }
+    grep -qix "DEVICE=$WMDEV" ~/anxd-work/srv/ackceil/$NIF || { lg "$T NI_DEVICE_IS_NOT_$WMDEV"; return 1; }
+    printf "fetch $A/$LDF TO RAM:anxwifipi.device
" | WS 60 >> $H/$T-fetch.txt 2>&1
+    h=$(sum "Ram%20Disk/anxwifipi.device"); echo "anxwifipi.device $h" >> $H/$T-readback.txt
+    [ "$h" = "$LDS" ] || { lg "$T READBACK_FAIL anxwifipi.device"; return 1; }
+    local DRVID=$LDI   # leg-local: a later leg without DRVF keeps the caller's DRVID
+  fi
   lg "$T readback 3/3, installed driver $( [ "$(sum Workbench/AmiNetXDuo/Devs/Networks/anxwifipi.device)" = "$DRV" ] && echo OK || echo MISMATCH); stack: $(stackid)"
-  printf "Run C:WirelessManager >Work:wt/wm-ack$T.log DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxwifipi.device
Wait 20
C:AddNetInterface RAM:NI/wifipi TIMEOUT=60
Echo \"ANI rc=\$RC\"
C:ShowNetStatus INTERFACE wifipi
RAM:devnodes anxwifipi.device
" | WS 120 > $H/$T-ani.txt 2>&1
+  printf "Run C:WirelessManager >Work:wt/wm-ack$T.log DEVICE=$WMDEV
Wait 20
C:AddNetInterface RAM:NI/wifipi TIMEOUT=60
Echo \"ANI rc=\$RC\"
C:ShowNetStatus INTERFACE wifipi
RAM:devnodes anxwifipi.device
" | WS 120 > $H/$T-ani.txt 2>&1
   local nl; nl=$(tr -d '
��' < $H/$T-ani.txt)
   echo "$nl" | grep -q 'ANI rc=0' && echo "$nl" | grep -q online || { lg "$T BRINGUP_FAIL"; return 1; }
   echo "$nl" | grep -qE '(^|> )nodes 1$' && echo "$nl" | grep -qE '(^|> )node 0 .*'"${DRVID:-1\.0\.0-beta7 \(27\.9\.2026\) AmiNetXDuo 95e3983}" || { lg "$T DRIVER_IDENTITY_FAIL: $(echo "$nl" | grep -m1 -E '(^|> )node')"; return 1; }

@tinic

tinic commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

Runner delta v2, after zz9k's review. The identity gate for driver legs is now a literal comparison of the full resident node id with the leg's DRVID: one resident node, no regex. ackab-115.sh sha256 95e9bf579a72754df651e5d028b78db141ab715c6de83f5fbaed90dcc0d7624b; frozen ackab.sh 3dd831b8… is unchanged.

Leg identities, read from the artifacts:

C (clean base 9112bc1f = beta7 release driver): $VER: anxwifipi.device 1.0.0-beta7 (27.9.2026) AmiNetXDuo 7fd931d any
T (candidate b74230f7):                         $VER: anxwifipi.device 1.0.0-beta7 (28.9.2026) AmiNetXDuo 3dc17aa any
--- ackab-3dd831b8.sh	2026-09-28 05:01:46.752179781 +0000
+++ ackab-115.sh	2026-09-28 07:17:14.829837256 +0000
@@ -51,11 +51,30 @@
   local NIF=$NI_C; [ "$T" = T ] && NIF=$NI_T; local NIH; NIH=$(sha256sum < ~/anxd-work/srv/ackceil/$NIF | cut -c1-64)
   printf "MakeDir RAM:NI
fetch $A/$NIF TO RAM:NI/wifipi
fetch $S/wstat256-v2.bin TO RAM:wstat256
fetch $S/devnodes TO RAM:devnodes
Protect RAM:wstat256 +e
Protect RAM:devnodes +e
" | WS 60 > $H/$T-fetch.txt 2>&1
   for kv in "NI/wifipi $NIH" "wstat256 $WST" "devnodes $DVN"; do set -- $kv; h=$(sum "Ram%20Disk/$1"); echo "$1 $h" >> $H/$T-readback.txt; [ "$h" = "$2" ] || { lg "$T READBACK_FAIL $1"; return 1; }; done
+  # #115 driver A/B, RAM only: DRVF_<C|T> (served name), DRVS_<C|T> (sha256), DRVID_<C|T> (devnodes id) bind the leg to RAM:anxwifipi.device
+  local LDF LDS LDI WMDEV=SYS:AmiNetXDuo/Devs/Networks/anxwifipi.device
+  if [ "$T" = T ]; then LDF=${DRVF_T:-}; LDS=${DRVS_T:-}; LDI=${DRVID_T:-}; else LDF=${DRVF_C:-}; LDS=${DRVS_C:-}; LDI=${DRVID_C:-}; fi
+  if [ -n "$LDF" ]; then
+    [ -n "$LDS" ] && [ -n "$LDI" ] || { lg "$T DRIVER_ENV_INCOMPLETE"; return 1; }
+    WMDEV=RAM:anxwifipi.device
+    [ "$(sha256sum < ~/anxd-work/srv/ackceil/$LDF | cut -c1-64)" = "$LDS" ] || { lg "$T SERVED_DRIVER_HASH"; return 1; }
+    grep -qix "DEVICE=$WMDEV" ~/anxd-work/srv/ackceil/$NIF || { lg "$T NI_DEVICE_IS_NOT_$WMDEV"; return 1; }
+    printf "fetch $A/$LDF TO RAM:anxwifipi.device
" | WS 60 >> $H/$T-fetch.txt 2>&1
+    h=$(sum "Ram%20Disk/anxwifipi.device"); echo "anxwifipi.device $h" >> $H/$T-readback.txt
+    [ "$h" = "$LDS" ] || { lg "$T READBACK_FAIL anxwifipi.device"; return 1; }
+    local DRVID=$LDI   # leg-local: a later leg without DRVF keeps the caller's DRVID
+  fi
   lg "$T readback 3/3, installed driver $( [ "$(sum Workbench/AmiNetXDuo/Devs/Networks/anxwifipi.device)" = "$DRV" ] && echo OK || echo MISMATCH); stack: $(stackid)"
-  printf "Run C:WirelessManager >Work:wt/wm-ack$T.log DEVICE=SYS:AmiNetXDuo/Devs/Networks/anxwifipi.device
Wait 20
C:AddNetInterface RAM:NI/wifipi TIMEOUT=60
Echo \"ANI rc=\$RC\"
C:ShowNetStatus INTERFACE wifipi
RAM:devnodes anxwifipi.device
" | WS 120 > $H/$T-ani.txt 2>&1
+  printf "Run C:WirelessManager >Work:wt/wm-ack$T.log DEVICE=$WMDEV
Wait 20
C:AddNetInterface RAM:NI/wifipi TIMEOUT=60
Echo \"ANI rc=\$RC\"
C:ShowNetStatus INTERFACE wifipi
RAM:devnodes anxwifipi.device
" | WS 120 > $H/$T-ani.txt 2>&1
   local nl; nl=$(tr -d '
��' < $H/$T-ani.txt)
   echo "$nl" | grep -q 'ANI rc=0' && echo "$nl" | grep -q online || { lg "$T BRINGUP_FAIL"; return 1; }
-  echo "$nl" | grep -qE '(^|> )nodes 1$' && echo "$nl" | grep -qE '(^|> )node 0 .*'"${DRVID:-1\.0\.0-beta7 \(27\.9\.2026\) AmiNetXDuo 95e3983}" || { lg "$T DRIVER_IDENTITY_FAIL: $(echo "$nl" | grep -m1 -E '(^|> )node')"; return 1; }
+  if [ -n "$LDF" ]; then
+    # driver legs: ONE resident node, and its id text equals the leg's DRVID literally (no regex)
+    local nid; nid=$(echo "$nl" | grep -m1 -E '(^|> )node 0 ' | sed 's/^.* id //')
+    echo "$nl" | grep -qE '(^|> )nodes 1$' && [ "$nid" = "$LDI" ] || { lg "$T DRIVER_IDENTITY_FAIL: got [$nid] want [$LDI]"; return 1; }
+  else
+    echo "$nl" | grep -qE '(^|> )nodes 1$' && echo "$nl" | grep -qE '(^|> )node 0 .*'"${DRVID:-1\.0\.0-beta7 \(27\.9\.2026\) AmiNetXDuo 95e3983}" || { lg "$T DRIVER_IDENTITY_FAIL: $(echo "$nl" | grep -m1 -E '(^|> )node')"; return 1; }
+  fi
   # the WM log is held open by the running WM (httpd serves 404): association is proven by the lease + peer ping below
   echo "$nl" | grep -qE '^ *address +192\.168\.1\.137 ' || { lg "$T NO_LEASE: wifipi is not 192.168.1.137 ($(echo "$nl" | grep -m1 -E '^ *address '))"; return 1; }
   ssh playhouse3 'ping -c 2 -W 1 192.168.1.137' >/dev/null 2>&1 || { lg "$T PEER_CANNOT_REACH .137"; return 1; }

Moves third_party/wifipi to fix/89-glom-ack-head 29283c4: a glom of 3 or
more frames never starts with two pure ACKs of one TCP flow; such a glom
ends after the pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinic tinic closed this Sep 28, 2026
@tinic
tinic deleted the fix/89-glom-ack-head branch September 30, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant