How to set the custom DNS with the Network client

We are facing a DNS resolution issue with a specific ISP, where our domain name does not resolve correctly using the system DNS. However, the same domain works as expected when a custom DNS resolver is used.

On Android, this is straightforward to handle by configuring a custom DNS implementation using OkHttp / Retrofit. I am trying to implement a functionally equivalent solution in native iOS (Swift / SwiftUI).

Android Reference (Working Behavior) :

val dns = DnsOverHttps.Builder() .client(OkHttpClient()) .url("https://cloudflare-dns.com/dns-query%22.toHttpUrl()) .bootstrapDnsHosts(InetAddress.getByName("1.1.1.1")) .build() OkHttpClient.Builder() .dns(dns) .build()

Attempted iOS Approach

I attempted the following approach :

  • Resolve the domain to an IP address programmatically (using DNS over HTTPS)
  • Connect directly to the resolved IP address
  • Set the original domain in the Host HTTP header

DNS Resolution via DoH :

func resolveDomain(domain: String) async throws -> String {     guard let url = URL(         string: "https://cloudflare-dns.com/dns-query?name=(domain)&type=A"     ) else {         throw URLError(.badURL)     }

    var request = URLRequest(url: url)     request.setValue("application/dns-json", forHTTPHeaderField: "accept")

    let (data, _) = try await URLSession.shared.data(for: request)     let response = try JSONDecoder().decode(DNSResponse.self, from: data)

    guard let ip = response.Answer?.first?.data else {         throw URLError(.cannotFindHost)     }

    return ip }

API Call Using Resolved IP : 

func callAPIUsingCustomDNS() async throws {     let ip = try await resolveDomain(domain: "example.com")

    guard let url = URL(string: "https://(ip)") else {         throw URLError(.badURL)     }

    let configuration = URLSessionConfiguration.ephemeral     let session = URLSession(         configuration: configuration,         delegate: CustomURLSessionDelegate(originalHost: "example.com"),         delegateQueue: .main     )

    var request = URLRequest(url: url)     request.setValue("example.com", forHTTPHeaderField: "Host")

    let (_, response) = try await session.data(for: request)     print("Success: (response)") }

Problem Encountered

When connecting via the IP address, the TLS handshake fails with the following error: Error Domain=NSURLErrorDomain Code=-1200 "A TLS error caused the secure connection to fail."

This appears to happen because iOS sends the IP address as the Server Name Indication (SNI) during the TLS handshake, while the server’s certificate is issued for the domain name.

Custom URLSessionDelegate Attempt : 

class CustomURLSessionDelegate: NSObject, URLSessionDelegate {

    let originalHost: String

    init(originalHost: String) {         self.originalHost = originalHost     }

    func urlSession(         _ session: URLSession,         didReceive challenge: URLAuthenticationChallenge,         completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void     ) {         guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,               let serverTrust = challenge.protectionSpace.serverTrust else {             completionHandler(.performDefaultHandling, nil)             return         }

        let sslPolicy = SecPolicyCreateSSL(true, originalHost as CFString)         let basicPolicy = SecPolicyCreateBasicX509()         SecTrustSetPolicies(serverTrust, [sslPolicy, basicPolicy] as CFArray)

        var error: CFError?         if SecTrustEvaluateWithError(serverTrust, &error) {             completionHandler(.useCredential, URLCredential(trust: serverTrust))         } else {             completionHandler(.cancelAuthenticationChallenge, nil)         }     } }

However, TLS validation still fails because the SNI remains the IP address, not the domain.

I would appreciate guidance on the supported and App Store–compliant way to handle ISP-specific DNS resolution issues on iOS. If custom DNS or SNI configuration is not supported, what alternative architectural approaches are recommended by Apple?

Answered by DTS Engineer in 868929022
I attempted the following approach

This approach won’t work in general. iOS devices regularly find themselves in situations where the traditional resolve-then-connect approach won’t work. I talk about this in general terms in the Connect by name section of TN3151 Choosing the right networking API

Additionally, this is problematic:

Set the original domain in the Host HTTP header

The Host header is ‘owned’ by URLSession and not something you can reliably set. See here.

Fortunately, there’s an alternative approach that should work, namely to set a per-app DNS resolver. You can do this via Network framework’s privacy context mechanism. See this post for an example.

ps It’s really hard to read your post. To avoid problems like that in the future, have a read of Quinn’s Top Ten DevForums Tips, and specifically the discussion of code blocks in tip 5.

Share and Enjoy

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

I attempted the following approach

This approach won’t work in general. iOS devices regularly find themselves in situations where the traditional resolve-then-connect approach won’t work. I talk about this in general terms in the Connect by name section of TN3151 Choosing the right networking API

Additionally, this is problematic:

Set the original domain in the Host HTTP header

The Host header is ‘owned’ by URLSession and not something you can reliably set. See here.

Fortunately, there’s an alternative approach that should work, namely to set a per-app DNS resolver. You can do this via Network framework’s privacy context mechanism. See this post for an example.

ps It’s really hard to read your post. To avoid problems like that in the future, have a read of Quinn’s Top Ten DevForums Tips, and specifically the discussion of code blocks in tip 5.

Share and Enjoy

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

How to set the custom DNS with the Network client
 
 
Q