SOCKS5 proxies (from PAC or ProxyConfiguration) are never applied to QUIC, and NWConnection silently connects directly

On macOS 27.0.1 (26A434), a SOCKS5 proxy is applied to TCP connections but never to QUIC. This happens whether the proxy comes from a PAC file in the system proxy settings or from an explicit ProxyConfiguration. The system SOCKS5 client only ever sends CONNECT; it never sends UDP ASSOCIATE (RFC 1928, CMD 0x03), so it has no way to carry UDP. URLSession handles this by staying on TCP through the proxy. A QUIC NWConnection instead connects directly to the origin, as if no proxy were configured.

Setup. The system proxy settings use a PAC URL (ProxyAutoConfigURLString in scutil --proxy; in my test it was installed through NEProxySettings.proxyAutoConfigurationURL). For the test host, a public site that serves HTTP/3, the PAC returns SOCKS5 127.0.0.1:<port>. For the explicit cases I used a local SOCKS5 server that logs every command it receives.

With the system PAC, default parameters, no explicit proxy:

  1. URLSession, assumesHTTP3Capable = true: goes through the proxy over TCP (transaction metrics: h2, isProxyConnection == true, remote address 127.0.0.1:<port>). HTTP/3 isn't used, which is consistent: the proxy can't carry it.
  2. NWConnection, TLS: currentPath.remoteEndpoint is 127.0.0.1:<port>. The PAC is honored.
  3. NWConnection, QUIC (ALPN h3): .ready in about 170 ms, and currentPath.remoteEndpoint is the origin's own address. The PAC is ignored.

With an explicit proxy, set via NWParameters.PrivacyContext.proxyConfigurations:

  1. QUIC with a working SOCKS5 server: .ready, and the server receives nothing — no CONNECT, no UDP ASSOCIATE.
  2. QUIC with the proxy pointing at a port nothing listens on: still .ready, still connected to the origin's own address.
  3. TLS with that same dead proxy: stays in .waiting with ECONNREFUSED. The proxy is honored for TCP.

At no point did the SOCKS5 server receive a UDP ASSOCIATE request.

Cases 3–5 are the concerning part. An app that configures a proxy, or a user or administrator who installs a PAC, reasonably expects traffic to go through the proxy or fail. Instead, QUIC traffic goes straight to the origin, which then sees the device's own address, and nothing in the API signals that the proxy was skipped.

Minimal snippet:

import Network

let host: NWEndpoint.Host = "YOUR_HTTP3_HOST"   // any public host that serves HTTP/3
let parameters = NWParameters(quic: NWProtocolQUIC.Options(alpn: ["h3"]))

// Remove these four lines to test with the system PAC instead.
let privacy = NWParameters.PrivacyContext(description: "socks5-quic")
privacy.proxyConfigurations = [
    ProxyConfiguration(socksv5Proxy: .hostPort(host: "127.0.0.1", port: 1099)) // nothing listens on 1099
]
parameters.setPrivacyContext(privacy)

let connection = NWConnection(host: host, port: 443, using: parameters)
connection.stateUpdateHandler = { state in
    if case .ready = state {
        // Prints the origin's own address. With NWParameters.tls instead of QUIC,
        // the same proxy configuration leaves the connection in .waiting (ECONNREFUSED).
        print(connection.currentPath?.remoteEndpoint as Any)
    }
}
connection.start(queue: .main)

I filed this as FB24760235 on 09/13/2026 and haven't had a response, so I'd like to ask here:

  1. Is it intended that a QUIC NWConnection ignores both the system PAC and proxyConfigurations? If so, how can an app find out that a connection went direct? And shouldn't a connection that can't honor the configured proxy fail rather than bypass it?
  2. Is UDP ASSOCIATE support planned for the system SOCKS5 client, so that QUIC and other UDP traffic can follow a SOCKS5 proxy the way TCP does?

Thanks!

Answered by DTS Engineer in 908525022
I filed this as FB24760235

Thanks. That’s the right path for this issue.

Is UDP ASSOCIATE support planned for the system SOCKS5 client … ?

I can’t talk about The Future™ — see tip 3 in Quinn’s Top Ten DevForums Tips — but the fact that your bug hasn’t been bounced back is a promising sign (-:

how can an app find out that a connection went direct?

This should be reflected in the usedProxy property of the connection establishment report.

shouldn't a connection that can't honor the configured proxy fail rather than bypass it?

Not in general. It’s traditional for proxies to be deployed in environments where the network itself blocks the connection, and thus this isn’t a concern. But…

Did you try setting the allowFailover property to false?

I think it defaults to false, in which case this will have no effect. And if that’s the case then, yeah, I’d consider this behaviour to be a bug.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I filed this as FB24760235

Thanks. That’s the right path for this issue.

Is UDP ASSOCIATE support planned for the system SOCKS5 client … ?

I can’t talk about The Future™ — see tip 3 in Quinn’s Top Ten DevForums Tips — but the fact that your bug hasn’t been bounced back is a promising sign (-:

how can an app find out that a connection went direct?

This should be reflected in the usedProxy property of the connection establishment report.

shouldn't a connection that can't honor the configured proxy fail rather than bypass it?

Not in general. It’s traditional for proxies to be deployed in environments where the network itself blocks the connection, and thus this isn’t a concern. But…

Did you try setting the allowFailover property to false?

I think it defaults to false, in which case this will have no effect. And if that’s the case then, yeah, I’d consider this behaviour to be a bug.

Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

SOCKS5 proxies (from PAC or ProxyConfiguration) are never applied to QUIC, and NWConnection silently connects directly
 
 
Q