I ran more tests and, I think, I can now explain the Chrome QUIC failures. change_fdguard_np is not the problem: a UDP socket guarded with GUARD_CLOSE | GUARD_DUP (applied before connect(), exactly like Chromium) proxies flawlessly through my NETransparentProxyProvider (both datagram directions).
The actual cause: on a flow-diverted UDP socket,
setsockopt(..., IPPROTO_IP, IP_DONTFRAG, ...)
and
setsockopt(..., IPPROTO_IP, IP_RECVTOS, ...)
fail with ECONNRESET (errno 54).
Without the proxy, they succeed. Chromium applies exactly these options immediately after connect() (QuicSessionPool::FinishConnectAndConfigureSocket, net/quic/quic_session_pool.cc) and aborts QUIC session creation on any failure, closing the socket within microseconds of the flow being created. The provider's subsequent flow.open() therefore always fails with
NEAppProxyFlowErrorDomain Code=2 "The peer closed the flow".
Repro without Chrome: create a UDP socket, connect() to any destination covered by the proxy's rules, then call setsockopt(..., IPPROTO_IP, IP_DONTFRAG, ...): It returns ECONNRESET whenever the transparent proxy is active.
I will create a new bug report.