-
-
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