v0.3.5
Second stability round from the 2026-08 audit: every remaining P1 finding is fixed. All changes verified per-commit on Python 3.9 with the full test suite (4157 tests) and smoke-tested end-to-end against a physical iOS device over the pymobiledevice3 userspace tunnel (zero stranded tasks, latency unchanged).
Highlights
connect()failures always surface. A soft ICMP unreachable during the handshake used to wake the blockedconnect()with an error value it never mapped — the caller returned as if connected to a session still mid-handshake. Per the stack's RFC 5927 hint-not-proof stance, the soft error is now recorded without waking the caller (a spoofed ICMP can't kill a pending connect) and is reported — asOSError(EHOSTUNREACH/ENETUNREACH)— if the R2 budget gives up, exactly Linux'ssk_err_softbehavior. The R2 give-up also now releases aconnect()parked through a crossed-SYN simultaneous open (SYN_RCVD), which previously hung forever.- Listener teardown resets its backlog. Closing/aborting a listening socket now aborts every established-but-unaccepted child (RST to the peer, socket deregistered, port released — Linux
inet_csk_listen_stopparity) and wakes a blockedaccept()withEBADF/ECONNABORTEDinstead of stranding it forever. close()works on a session-less TCP socket. A bound-but-never-connected socket previously could not be closed at all (assertraised) and leaked its port permanently.- Datapath loops survive their first bug. One exception in a subsystem iteration (e.g. NUD maintenance), a periodic-housekeeping pass, or a frame's TX assembly used to kill the loop silently — neighbor resolution frozen, temp-address sweeps stopped, or egress wedged behind a poison frame — while the stack looked alive. All three now log through the stdlib logger and keep running.
recvmsg(MSG_ERRQUEUE)can no longer crash after an ICMP error burst: the bounded error queue's semaphore now stays in sync with the deque instead of accumulating phantom permits that ended inIndexError.