Finish the IPv6 surface (UDP, accept peer, asyncnet, AAAA, dual-stack)
- Type: feature — Track B (
lib/rtl). - Status: working
- Parent: [[feature-networking]]. Split out so the remaining IPv6 work is visible and rankable on its own instead of living inside a strategy umbrella.
Already landed (2026-07-20)
- PAL:
TPalIn6Addr,PalBindIpv6,PalConnectIpv6,PalIn6Loopback,PalIn6Any; realsockaddr_in6in the posix backend; ESP backend refuses honestly withPAL_ERR_UNSUPPORTED. net.pas:TNetAddresscarriesFamily/V6/ScopeId;NetAddress6,NetLoopback6,NetAny6,NetIsV6,NetTcpAccept6; listen/connect branch on family.- Gated:
test/lib_ipv6.pas(PAL) andtest/lib_net6.pas(net.pas, plus proof the v4 path is unchanged).
So TCP client and server speak IPv6 today.
Remaining
PalAcceptIpv6.NetTcpAccept6currently sets the peer'sFamilybut not its address, because the PAL's accept fills an IPv4sockaddr. Reporting a zeroed v6 peer as if it were real would be worse than reporting nothing, which is why it is left empty — but it needs finishing before anything logs or authorises on a peer address.- UDP over v6 —
NetUdpBind/NetUdpSendTo/NetUdpRecvFromare still IPv4-only, and the PAL needsPalSendToIpv6/PalRecvFromIpv6. asyncnet— the coroutine/epoll reactor is IPv4-only; the same family branch asnet.pasapplies.- AAAA lookups in the resolver, and a happy-eyeballs-ish ordering decision
when a host has both A and AAAA. That ordering is a real design choice, not
just plumbing — file it as a Track U
decide-if it is not obvious when reached. - Dual-stack listeners —
IPV6_V6ONLYis untouched, so a::listener's behaviour for v4-mapped clients is currently whatever the host default is. That should be an explicit, documented choice rather than inherited. scopeIdis plumbed through but untested — only a link-local (fe80::/10) connection exercises it, which needs a real interface index.
Acceptance
- A v6 peer address comes back from accept.
- UDP round trip over
::1, gated. asyncnetaccepts and connects over v6.- A host with both A and AAAA resolves and connects by a documented rule.
IPV6_V6ONLYset deliberately, with the choice written down.
Log
- 2026-07-20 — Filed from the Track B sweep, splitting the concrete remainder out of [[feature-networking]].
2026-07-31 — four of the six items DONE; the other two are a Track U call
Landed
PalAcceptIpv6. Posix backend fills a realsockaddr_in6and aParseSockAddrIpv6reads back the address, the wire-order port AND the scope id — a link-local peer is unanswerable without the last one.NetTcpAccept6now returns a real peer instead of a family with an empty address. The ESP backend refuses withPAL_ERR_UNSUPPORTEDand zeroes the out-parameters, which is the same honesty rule bind/connect already follow there.- UDP over v6.
PalSendToIpv6/PalRecvFromIpv6in the PAL, andNetUdpBind/NetUdpSendTo/NetUdpRecvFrombranch on family exactly as the TCP path does.NetUdpBindwas creating anAF_INETsocket regardless of the address it was handed; it now usesaddr.Family. On receive it is the CALLER'ssrcthat says which family to read back, and that is documented at the declaration. asyncnet.TcpListen6/TcpConnect6/TcpConnectAddr6. Only the four socket-CREATING calls needed pairs —TcpAccept,TcpRecv,TcpSendandTcpClosetake an fd whose family was decided when it was made, so the reactor half needed no change at all. The unit header now says which is which.
Gated
test/lib_net6.pasextended: the accepted peer must actually BE::1with a nonzero ephemeral port (not merely carryAF_INET6), a v6 UDP round trip checks payload and sender address and sender port, and the v4 UDP path is asserted unchanged beside it.test/lib_asyncnet6.pas(new): a server and a client coroutine on one thread, both parked on the epoll reactor, over::1.- Both SKIP cleanly on a host without
AF_INET6, likelib_ipv6already did.
Not done, and deliberately not guessed
Items 4 (A vs AAAA ordering) and 5 (IPV6_V6ONLY) are the two this
ticket itself calls design choices rather than plumbing — item 4 says in so many
words to file a Track U decide- if the answer is not obvious when reached. It
was not: V6ONLY=1 is predictable and V6ONLY=0 is what most of the world defaults
to, and AAAA-first costs a full timeout on a host with broken v6 routing. Filed
as [[decide-ipv6-dualstack-and-aaaa-ordering]] with the trade-offs and a
recommendation.
Item 6 (scopeId) is now plumbed all the way through accept and UDP, but still only exercised as a zero — proving it needs a real link-local peer on a real interface index, which this box cannot arrange. The value is carried and returned correctly; that it is carried is all this ticket can honestly claim.
- 2026-07-31 — resolved, commit da971d179.