Skip to content

NetX Duo 6.5.2: fork with upstream v6.5.2.202603_rel, ALPN from the fork - #175

Merged
tinic merged 7 commits into
mainfrom
fix/netxduo-6.5.2
Oct 4, 2026
Merged

tinic merged 7 commits into
mainfrom
fix/netxduo-6.5.2

Conversation

@tinic

@tinic tinic commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Moves AmiNetXDuo onto the NetX Duo fork with upstream v6.5.2.202603_rel merged (tinic/netxduo master 3d916e2a, PR tinic/netxduo#7).

Commits

Commit Content
c5465a8e Atomic: third_party/netxduo → 3d916e2a; AMINETXDUO_NETXDUO_VERSION_PIN 6.5.1 → 6.5.2; src/tls/alpn/ removed with every CMake consumer (src/tls, src/tlslib, tests/x509, tests/fuzz), since ALPN now lives in the fork; host fixtures define _nx_ip_created_count (one NX_IP each, built without nx_ip_create.c); ipv6_frag models a second IP only inside test_pool_reserve(); tcp_sws blocks its waiter on an H_MSS packet.
0ccc18d4, c0d607ce, ff3fd797 tests/syncache aligned to the fork's SYN-cache contract (reviewed earlier as 87305b7 / 0deecf3 / 0e85771; patch-ids identical).
822c0e56 tests/netstack/host/test_tcp_sws_host.c expects the fork's silly-window rules (a820b430 sender, 5615cfd8 receiver): 8 expectations changed, negative controls kept, receiver edge cases driven with exact values.

The pin check reads nx_api.h and stops configure on a mismatch, so the gitlink and the version pin move in one commit.

Gates (playhouse3, ThreadX e24aa9c9)

Gate Result
m68k cross configure + build, default arm rc 0 (1 existing warning, tests/atf/tcp_socket.c:60)
tools/ci.sh host rc 0, 553/553
tests/syncache (14 arms incl. ulong64, detach) 14/14
TLS / x509 / fuzz_tls ctest 20/20

Size effect from 20046b12: NX_SECURE_TLS_SESSION 9876 → 9884 bytes and a 1364-byte CertificateVerify scratch per session, sized through nx_secure_tls_metadata_size_calculate (tls_conn.c:578-587); no parent code change.

The superseded fix/netxduo-pin-bump and two intermediate tips are archived as archive/* tags.

🤖 Generated with Claude Code

tinic and others added 6 commits October 4, 2026 06:08
… the fork's

third_party/netxduo 7bf58f91 -> cbba359a, tinic/netxduo master: the merge
of PR #8 (the CertificateVerify scratch carved after the PRF region) on
top of PR #7 (upstream v6.5.2.202603_rel, 20046b12, into the fork, on top
of PR #6). AMINETXDUO_NETXDUO_VERSION_PIN 6.5.1 -> 6.5.2 in the same commit,
as cmake/AmiNetXDuoVersion.cmake requires: configure stops when the pin
and the submodule's nx_api.h disagree.

The fork builds RFC 7301 ALPN itself (nx_secure/src/nx_secure_tls_alpn.c);
its code is the same as src/tls/alpn/nx_secure_tls_alpn.c, and only the
header comment differs. So, in this commit and not later:

- src/tls/alpn/ is deleted;
- src/tls/CMakeLists.txt no longer appends it to nx_secure (the glob of
  nx_secure/src picks up the fork's file);
- tests/x509/CMakeLists.txt no longer appends it (same glob);
- tests/fuzz/CMakeLists.txt: fuzz_tls_record lists the fork's file
  instead, and fuzz_tls_nx_secure no longer appends it (glob);
- src/tlslib/CMakeLists.txt: test_tls_alpn builds the fork's file;
- src/tlslib/test/test_tls_alpn.c: the header names the new path.

No other place in the tree names src/tls/alpn.

20046b12 makes the CertificateVerify scratch per session: it is carved
from the TLS metadata area, NX_SECURE_TLS_CERTIFICATE_VERIFY_SCRATCH_SIZE
= 1364 bytes on m68k, and NX_SECURE_TLS_SESSION grows from 9876 to 9884
bytes. tls_conn.c sizes each connection's metadata with
nx_secure_tls_metadata_size_calculate(), so nothing here changes for it;
each TLS connection takes about 1.36 KB more.

The fork's fragment budget (08b52c70) makes nx_ipv4_packet_receive.c read
_nx_ip_created_count through NX_IP_FRAGMENT_ADMIT (nx_ip.h). Ten host
fixtures compile that file from a hand-picked source list without
nx_ip_create.c, and stopped linking: the fuzz targets dhcp_lease_regression,
dhcp_lease_option_regression, fuzz_dhcp, fuzz_dns and fuzz_mdns (and, in
the 32-bit build, the TLS fuzz targets that share fuzz_nxstub.c), and
test_ipv6_frag, test_bcast_loopback, test_ipv4_noaddr_account,
test_mcast_share_2sock and test_mcast_share_loopback. Each builds one
NX_IP by hand, so each now defines the count, once per binary, as that one
instance: in tests/fuzz/fuzz_nxstub.c and in the five host harnesses. The
vendored tree and the shipping sources are unchanged.

Two of those fixtures also needed the fork's current model to run:
test_ipv6_frag's test_pool_reserve exercises the pool-wide reserve gate,
which NX_IP_FRAGMENT_ADMIT applies only while another IP instance exists,
so that test alone sets the count to 2 and restores it on the way out;
and test_tcp_sws's blocked sender now waits on an H_MSS packet in
tx_thread_additional_suspend_info, as nx_tcp_socket_send_internal.c
stores it, because transmit_check (a820b430) measures the window
against that packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test-only.  At a5cb72d the tests/syncache arms are written against
netxduo 7bf58f91.  Against the pin bump (e5e89f1b) the two executables did
not link: nx_tcp_syncache.c calls _nx_tcp_packet_send_ack,
_nx_tcp_socket_packet_process and _nx_packet_release since 96502647 and
2d44d563.  With those supplied, 21 cache assertions failed, detach keep
crashed (rc 139) and v6accept failed.  Each failure was run at every
SYN-cache commit after the pin (8a507baf 1b57d8f0 ac17df7f 96502647
2d44d563 8babcaca edf44b07 5523e5bc, then 401cd686 and e5e89f1b) to find
where it starts.  Every one starts at a commit that changed the behaviour
on purpose; none is a NetX defect.

96502647 "the SYN cache answers a SYN only when a socket can take it":
"A SYN that finds no socket parked is now recorded as
NX_TCP_SYNCACHE_DEFERRED and not answered."  The arms that built a
finished handshake with no socket (SYN then ACK, none parked) now get a
deferred SYN, an ACK the cache drops ("Nothing was sent for this SYN"),
and an empty accept queue.  The commit says how one arises now: "Two SYNs
that arrive while one socket is parked are both answered ... so the second
can still finish with no socket."  The arms are rebuilt that way.  A
full cache with no socket parked is reached by filling it while one is
parked, since "Deferred SYNs and finished handshakes together are bounded
by the listen backlog".  15 cache assertions:
  a full backlog waits; past the backlog the queue does not grow; and the
  peer is told; relisten takes one; the queue shortens; and the connection
  reaches the application; and resets every peer waiting on it (unlisten,
  finished handshakes); the cookie SYN-ACK was sent with no socket on the
  port; and it announced a window scale; and the ACK reconstructs that
  scale; one finished handshake is waiting; a forged SYN on the same
  four-tuple does not throw it away; in the accept queue; a relisten takes
  it; with the round trip of the handshake, not of the wait.
The same cause gives detach v6accept ("the handshake is queued for
accept") and detach keep: its queued handshake never existed, so the
survivors check read nx_tcp_syncache_accept_head -> nx_tcp_syncache_interface
through a NULL head in the test.  That NULL read is the rc 139.  It is
a test harness fault, not a production memory access.  The check now
reports a NULL head as a failure instead of reading through it.

Four assertions that passed at these commits passed vacuously.  Each
"a cookie completes" with no socket parked was a deferred SYN whose ACK
the cache drops and reports as consumed.  Each one now also requires
cookies_valid to move.

2d44d563 "a connection the SYN cache finishes waits for accept on the
socket": "Any other socket, at the ACK or on relisten, is now left as
upstream left it" (bound, LISTEN, the peer's port), and
"nx_tcp_server_socket_accept() ... completes the connection instead
(_nx_tcp_syncache_accept)".  4 cache assertions: the connection is
established (twice: the cached and the cookie handshake); and it is the
socket that was parked; and the connection is made.  They now assert the
socket waits for accept, sends nothing, and that _nx_tcp_syncache_accept
connects it with one ACK from iss + 1 acknowledging irs + 1.  A new
block holds data before accept: in-order data is held unacknowledged,
a segment that does not follow it is released, and accept processes
the held data in order.

e5e89f1b "a cached SYN-ACK's sequence number never decodes as a cookie":
the expiry block (an ACK for an entry that has expired) is inverted,
not deleted.  The ACK is now not consumed (the caller resets it), is not
read as a cookie (cookies_valid 0, cookies_invalid 1), makes no
connection, rebuilds nothing onto the parked socket, and the cache sends
nothing for it.  The RST itself, <SEQ=SEG.ACK>, is formed by
_nx_tcp_packet_process, which this rig does not link.  The fork's
netx_3_06, 8_02 and 10_23_01 and ctl_syncookie check that RST.  A fresh
SYN from the same peer is then answered with a new sequence number and
completes; full-table recovery by cookie is checked in the cookie blocks.

8a507baf, 1b57d8f0, ac17df7f, 8babcaca, edf44b07 and 5523e5bc change no
assertion here.

The three functions:
- test_syncache models them and drives each one.  _nx_tcp_packet_send_ack
  records the socket, sequence, acknowledgment and window, and fails on a
  socket with no peer.  _nx_tcp_socket_packet_process takes a held packet
  as an ESTABLISHED socket takes in-order data: it fails on a packet the
  cache does not own, one still marked queued, a socket not established,
  or data out of order.  _nx_packet_release fails on a packet the rig did
  not give out or that was already released.  A model failure fails the arm.
- test_syncache_detach: no arm reaches accept, hold or the accept-queue
  data ACK, so _nx_tcp_socket_packet_process and _nx_tcp_packet_send_ack
  abort the test if called.  Its _nx_packet_release is unchanged.

Boundary vectors in the cookie arm:
- the counter at 0xFFFFFFFF is accepted in its own step and at 0 (and at
  top + 1 in ULONG arithmetic), and refused at 1 (top + 2) and 0xFFFFFFFE;
- the counter at 0xFF is accepted at 0x100 and refused at 0x101;
- the additive carry: at counter 12345 and at 0xFFFFFFFF, a searched
  irs for which h1 + irs overflows 32 bits and (h2 & 0xFFFFFF) + 0x3FF
  crosses 2^24.  The cookie built with data 0x3FF carries out of the
  option field and the check's (rest - h2) borrows.  Its options round
  trip, it is accepted one step on, refused two on and one before, and
  data 0 at the same irs round trips.  The vectors are printed.

64-bit: no target, and no x86_64 host, has a 64-bit ULONG; ThreadX's linux
tx_port.h keeps it 32 bits on x86_64, which is all the fork's 64-bit suite
runs.  test_syncache_ulong64 builds the same test and nx_tcp_syncache.c
with ULONG 64 bits wide (host/ulong64/tx_port.h, with a static assert
on the width), as ctest syncache_cookie_ulong64 and
syncache_cache_ulong64.  The rig's segments carry a 20-byte data offset,
not sizeof(NX_TCP_HEADER), which is wider there.  tools/ci.sh
HOST_TESTS_EXPECTED 551 -> 553 (ctest -N on this tree: 553).

Controls, ThreadX 93387b0a, ctl_native2.res (raw rc of every arm):
- e5e89f1b: all 14 arms rc 0.
- 401cd686 and 6f9097fa (cookie marker reverted / not yet in): cache rc 1
  (32-bit and ulong64), 6 failures, all in the inverted expiry block;
  every other arm rc 0.
- 2d44d563: the same 6, nothing else.
- 96502647 and ac17df7f: test_syncache does not build
  (_nx_tcp_syncache_accept and _hold do not exist before 2d44d563);
  detach keep and v6accept abort (rc 134) on the existing unreached
  _nx_tcp_socket_state_syn_received stub, because before 2d44d563 the
  ACK established the socket at once.
- The unmodified a5cb72d test: cache 21 failures at e5e89f1b, 19 at
  401cd686.  The 2 that differ are the old expiry assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test-only follow-up to 87305b7 (review note).  test_syncache_detach's
_nx_packet_release was still a no-op beside the two fail-fast stubs.
Counted in all ten arms (v4 v4reuse keep gap driver late v6 v6accept
v6delete v6late): 0 calls in each.  The IPv4 send and _nx_ipv6_header_add
stubs keep their packets, and the cache's own releases are in
_nx_tcp_syncache_hold, which no arm reaches.  So a call is now an
unmodelled path and aborts the test, as _nx_tcp_socket_packet_process
and _nx_tcp_packet_send_ack do.

All ten arms rc 0 after the change (netxduo e5e89f1b, ThreadX e24aa9c9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test-only.  Corrects the wording of 87305b7.  Its message ("the caller
resets it"), the assertion label at test_syncache_host.c:932 ("...is not
consumed: it is reset") and the comment above it implied that this native
test forms or checks the RST for an ACK to an expired cached entry.  It
does neither.  The rig does not link _nx_tcp_packet_process.  What the
block proves: _nx_tcp_syncache_ack_received returns NX_FALSE (the ACK is
not consumed) and the cache sends nothing for it.  This native test is
not proof of the RST.  The on-the-wire RST <SEQ=SEG.ACK> is checked by the
fork's netx_3_06, netx_8_02 and netx_10_23_01 and by ctl_syncookie.

The label and the comment change; no assertion, value or code changes.

test_syncache cache rc 0, test_syncache_ulong64 cache rc 0 (ThreadX
e24aa9c9, netxduo e5e89f1b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two fork commits in the 6.5.2 pin changed what this test asserted, and
it failed 8 of its 43 checks (the seq31 and seq32 arms the same):

- a820b430 "tcp: the sender's silly-window test sends a pushed write
  that fits": RFC 1122 4.2.3.4 rule (2) with Nagle off. A pushed write
  that fits the usable window whole (D <= U) goes now.
- 5615cfd8 "tcp: below the SWS floor, the receiver holds its right edge
  instead of advertising zero": NX_TCP_RX_WINDOW_ADVERTISED (nx_tcp.h).
  At or above the floor, min(MSS, buffer / 2), the free space goes out.
  Below it, the edge last sent is held, or the free space if that is
  less.

The 8 expectations, old -> new:

1. b, 100-byte write, 200 usable, 512 in flight: 0 datagrams -> 1.
2. b, the same write: NX_WINDOW_OVERFLOW -> NX_SUCCESS.
3. e, 1200-byte write, 1200 usable, 65535 peak: 0 datagrams -> 1.
4. e, the same write: NX_WINDOW_OVERFLOW -> NX_SUCCESS.
5. k, 68 free, 68 last sent: advertised 0 -> 68.
6. k, 104 free, 104 last sent: 0 -> 104.
7. k, MSS - 1 free and last sent: 0 -> MSS - 1.
8. m, 100 free and last sent, 1000-byte buffer: 0 -> 100.

The names say the new rules: b_sliver_with_flight_holds becomes
b_sliver_with_flight_sends_a_write_that_fits, and
k_a_runt_window_is_advertised_as_zero becomes
k_a_runt_window_already_offered_is_kept. Their messages change to match.

The negative controls stay, and some are added:

- b and e: a write larger than the usable window is still held whole,
  with NX_WINDOW_OVERFLOW and no datagram (300 into 200, 1300 into
  1200), and the persist probe is not armed.
- i keeps the H_MSS waiter: a 200-byte sliver wakes no one, and a full
  segment wakes it once.
- h_advertise_edge() sets the edge last sent apart from the free space,
  and asserts that rx_window_last_sent is the window that went on the
  wire.
- o_below_the_floor_the_edge_holds:
  - 1000 free with 200 last sent holds 200;
  - 100 free with a zero edge stays 0, and so does MSS - 1;
  - MSS free reopens a zero edge to MSS;
  - 100 free with 300 last sent is a genuine shrink, and goes out as 100.
- m: a 1000-byte buffer keeps 100 already offered, holds a zero edge
  at 100 free, and reopens it at its floor, 500.
- l and n are unchanged.

The test now runs 78 checks, all passing, in each of tcp_sws,
tcp_sws_seq31 and tcp_sws_seq32. No shipping or vendored source
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rify scratch

20046b12 makes the CertificateVerify signature buffers per session:
nx_secure_tls_session_create_ext() carves NX_SECURE_TLS_CERTIFICATE_
VERIFY_SCRATCH_SIZE bytes from the crypto metadata area and points
nx_secure_tls_certificate_verify_scratch at it (with its _size), and
_nx_secure_tls_process_certificate_verify() addresses its buffers from
that pointer. test_pss_schemes() zeroes a bare session and calls the
processing directly, so the pointer was NULL: UBSan in the macOS host
tier (run 37198121405, job 111424120344) stopped at
nx_secure_tls_process_certificate_verify.c:169, "applying non-zero
offset 164 to null pointer"; clang 18 on Linux stops at :168.

The test now gives that session the area itself: a ULONG array of
NX_SECURE_TLS_CERTIFICATE_VERIFY_SCRATCH_SIZE bytes in the same scope as
the session, attached as the carve attaches it, after each of the two
memsets that reset the session. The four rejection checks are unchanged,
and so are the sanitizers. No library change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinic
tinic force-pushed the fix/netxduo-6.5.2 branch from 822c0e5 to 837644b Compare October 4, 2026 13:23
The upstream v6.5.2.202603_rel merge (20046b12), pinned in this branch,
moves four measured numbers past their gates. turo decided to raise
them to the values measured in CI run 37205467139: the Linux job
111445651664 (default), cross micro 111445652093 and cross minimal
111445652048.

tools/check-hotpath-budget.sh, the default arm's instruction count:
- __nx_tcp_socket_state_data_check: 483 -> 493

tools/check-ram-size.sh, sizeof(AmiNetStack):
- default: 68608 -> 68836
- minimal: 17408 -> 17928
- micro:   16384 -> 16388

The TLS session's growth (+8 bytes) and the per-session
CertificateVerify scratch are per-connection heap from the TLS metadata
area, not part of sizeof(AmiNetStack). The AmiNetStack growth comes from
the NetX Duo structures and configuration that the merge changed. It is
not attributed further here; tracking its source is deferred.

Only these four numbers change. No other arm, gate, option or source
file changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tinic
tinic merged commit b5b59c4 into main Oct 4, 2026
42 of 43 checks passed
@tinic
tinic deleted the fix/netxduo-6.5.2 branch October 4, 2026 16:22
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