-
-
Save fikeminkel/a9c4bc4d0348527e8df3690e242038d3 to your computer and use it in GitHub Desktop.
import dnssd | |
struct DNSTxtRecord { | |
typealias DNSLookupHandler = ([String: String]?) -> Void | |
static func lookup(_ domainName: String, completionHandler: @escaping DNSLookupHandler) { | |
var mutableCompletionHandler = completionHandler // completionHandler needs to be mutable to be used as inout param | |
let callback: DNSServiceQueryRecordReply = { | |
(sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, context) -> Void in | |
// dereference completionHandler from pointer since we can't directly capture it in a C callback | |
guard let completionHandlerPtr = context?.assumingMemoryBound(to: DNSLookupHandler.self) else { return } | |
let completionHandler = completionHandlerPtr.pointee | |
// map memory at rdata to a UInt8 pointer | |
guard let txtPtr = rdata?.assumingMemoryBound(to: UInt8.self) else { | |
completionHandler(nil) | |
return | |
} | |
// advancing pointer by 1 to skip bad character at beginning of record | |
let txt = String(cString: txtPtr.advanced(by: 1)) | |
// parse name=value txt record into dictionary | |
var record: [String: String] = [:] | |
let recordParts = txt.components(separatedBy: "=") | |
record[recordParts[0]] = recordParts[1] | |
completionHandler(record) | |
} | |
// MemoryLayout<T>.size can give us the necessary size of the struct to allocate | |
let serviceRef: UnsafeMutablePointer<DNSServiceRef?> = UnsafeMutablePointer.allocate(capacity: MemoryLayout<DNSServiceRef>.size) | |
// pass completionHandler as context object to callback so that we have a way to pass the record result back to the caller | |
DNSServiceQueryRecord(serviceRef, 0, 0, domainName, UInt16(kDNSServiceType_TXT), UInt16(kDNSServiceClass_IN), callback, &mutableCompletionHandler); | |
DNSServiceProcessResult(serviceRef.pointee) | |
DNSServiceRefDeallocate(serviceRef.pointee) | |
} | |
} |
Thank you very much! @fikeminkel and @juanheyns both snipped worked for me.
If someone is having an issue like @ethan-gerardot where it gets stuck at DNSServiceProcessResult
, is most likely because the domainName
is invalid, adding a time out like @juanheyns might help.
https://gist.github.com/fikeminkel/a9c4bc4d0348527e8df3690e242038d3#file-dnstxtrecord-swift-L20
Unfortunately this code produces incorrect behaviour on concatenated TXT entries, leaving a garbage byte in place of the concatenation:
// advancing pointer by 1 to skip bad character at beginning of record
let txt = String(cString: txtPtr.advanced(by: 1))
Quoting https://kb.isc.org/docs/aa-00356: per RFC 4408 a TXT or SPF record is allowed to contain multiple strings, which should be concatenated together by the reading application
See apple/swift-async-dns-resolver#43 and apple/swift-async-dns-resolver#44
@mosen or anyone looking for an example parsing SRV records, heres an example I found that worked very well for me: https://github.com/jamf/NoMAD-2/blob/main/NoMAD/SRVLookups/SRVResolver.swift