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?