When I was building my iOS application to automatically discover Apple caching servers on a network, I encountered several critical issues that prevented DNS TXT record lookups from working properly. This post covers the technical problems I faced and how I resolved them to create a working solution that can automatically discover multiple caching servers using just a domain name.
The Problem: DNS Queries That Never Complete
The original implementation appeared correct at first glance - it called DNSServiceQueryRecord to query for TXT records and set up a callback function. However, the callback never fired, and DNS queries would timeout after 10 seconds.
// This approach was fundamentally flawed
let result = DNSServiceQueryRecord(&serviceRef, 0, interfaceIndex, domain,
UInt16(kDNSServiceType_TXT), UInt16(kDNSServiceClass_IN),
callback, context)
DNSServiceProcessResult(serviceRef.pointee) // This blocks and fails
Root Cause Analysis
After extensive research, I identified three critical issues:
1. Incorrect Socket Handling
The biggest problem was calling DNSServiceProcessResult only once. This function needs to be called repeatedly whenever the underlying socket becomes readable, not just once on a background queue.
2. Missing Build Configuration
iOS requires the -lresolv linker flag for DNS queries to function properly. Without this flag, DNS operations fail silently or return immediate errors.
3. Incomplete Callback Implementation
The C callback function wasn't properly implemented to parse TXT record data and handle multiple IP addresses.
The Solution: Asynchronous DNS Handling
Step 1: Add Required Build Settings
First, I configured Xcode to link against the resolver library:
- Select the project in the navigator
- Select the app target
- Click "Build Settings" tab
- Search for "Other Linker Flags"
- Add:
-lresolv
Step 2: Implement Proper Socket Monitoring
Instead of calling DNSServiceProcessResult once, I used DispatchSource to monitor the DNS socket:
let sockfd = DNSServiceRefSockFD(serviceRef)
let source = DispatchSource.makeReadSource(fileDescriptor: sockfd, queue: DispatchQueue.global())
source.setEventHandler {
DNSServiceProcessResult(serviceRef) // Called when socket is readable
}
source.setCancelHandler {
DNSServiceRefDeallocate(serviceRef)
}
source.resume()
Step 3: Implement Callback Function
The callback needed to properly parse TXT record data, which uses length-prefixed strings:
let dnsQueryCallback: DNSServiceQueryRecordReply = { (sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, context) -> Void in
guard errorCode == kDNSServiceErr_NoError else { return }
guard let rdata = rdata?.assumingMemoryBound(to: UInt8.self), rdlen > 0 else { return }
// Parse length-prefixed TXT record strings
var offset = 0
var txtRecords: [String] = []
while offset < rdlen {
let length = Int(rdata[offset])
offset += 1
if length > 0 && offset + length <= rdlen {
let data = Data(bytes: rdata + offset, count: length)
if let record = String(data: data, encoding: .utf8) {
txtRecords.append(record)
}
offset += length
}
}
// Process found IP addresses
for record in txtRecords {
let trimmedRecord = record.trimmingCharacters(in: .whitespacesAndNewlines)
if isValidIPAddress(trimmedRecord) {
globalFoundIPs.append(trimmedRecord)
}
}
}
Step 4: Handle Multiple IP Addresses
The solution automatically processes multiple IP addresses from the TXT record:
// After DNS completes, test each found IP
for ip in globalFoundIPs {
let commonPorts = [49152, 49159, 49200, 49597]
for port in commonPorts {
if try await testConnection(ip: ip, port: port) {
let server = CachingServer(ipAddress: ip, port: port, isActive: true)
servers.append(server)
break // Found working port, move to next IP
}
}
}
DNS Record Configuration
The system queries for TXT records using the naming convention: _internal-cache-servers._tcp.<domain>
The TXT record should contain the IP addresses of caching servers:
- Record Name:
_internal-cache-servers._tcp - Record Type:
TXT - Record Value:
192.168.1.100(or multiple IPs as separate strings)
How It Works End-to-End
- DNS Query: The app queries for
_internal-cache-servers._tcp.<domain>TXT records - Socket Monitoring:
DispatchSourcemonitors the DNS socket for responses - Record Parsing: The callback parses TXT record data to extract IP addresses
- Server Testing: Each IP is tested on common caching server ports (49152, 49159, 49200, 49597)
- Verification: HTTP requests verify servers are actually Apple caching servers by checking response headers
- Results: Discovered servers are presented to the user with connection status