We are implementing a Transparent Proxy for HTTPS (via TCP and QUIC).
The following rules are set in startProxy:
settings.includedNetworkRules = [
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "0.0.0.0", port: "443"), prefix: 0, protocol: .TCP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "::", port: "443"), prefix: 0, protocol: .TCP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "0.0.0.0", port: "443"), prefix: 0, protocol: .UDP),
NENetworkRule(destinationNetwork: NWHostEndpoint(hostname: "::", port: "443"), prefix: 0, protocol: .UDP)
]
Handling TCP connections seems to work fine. But opening UDP flows from Chrome (or Brave) always fails with
Error Domain=NEAppProxyFlowErrorDomain Code=2 "The peer closed the flow"
(Doing the same for Firefox works!)
BTW: We first create a remote UDP connection (using the Network framework) and when it is in the ready state, we use connection?.currentPath?.localEndpoint as the localEndpoint parameter in the open method of the flow.
Is it a known issue that QUIC connections from Chrome cannot be handled by a Transparent Proxy Provider?
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.