Created
April 26, 2017 20:56
-
-
Save fikeminkel/a9c4bc4d0348527e8df3690e242038d3 to your computer and use it in GitHub Desktop.
Swift 3.1 DNS TXT record lookup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
Adapted the above version to the following which works for me. Had to add some error handling and a timeout, see below: kDNSServiceErr_NoError
, kDNSServiceFlagsTimeout
import Foundation
import dnssd
// ...
typealias DNSLookupHandler = ([String: String]?) -> Void
func query(domainName: String) -> [String: String]? {
var result: [String: String] = [:]
var recordHandler: DNSRecordHandler = {
(record) -> Void in
if (record != nil) {
for (k, v) in record! {
result.updateValue(v, forKey: k)
}
}
}
let callback: DNSServiceQueryRecordReply = {
(sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, context) -> Void in
guard let handlerPtr = context?.assumingMemoryBound(to: DNSLookupHandler.self) else {
return
}
let handler = handlerPtr.pointee
if (errorCode != kDNSServiceErr_NoError) {
return
}
guard let txtPtr = rdata?.assumingMemoryBound(to: UInt8.self) else {
return
}
let txt = String(cString: txtPtr.advanced(by: 1))
var record: [String: String] = [:]
let parts = txt.components(separatedBy: "=")
record[parts[0]] = parts[1]
handler(record)
}
let serviceRef: UnsafeMutablePointer<DNSServiceRef?> = UnsafeMutablePointer.allocate(capacity: MemoryLayout<DNSServiceRef>.size)
let code = DNSServiceQueryRecord(serviceRef, kDNSServiceFlagsTimeout, 0, domainName, UInt16(kDNSServiceType_TXT), UInt16(kDNSServiceClass_IN), callback, &recordHandler)
if (code != kDNSServiceErr_NoError) {
return nil
}
DNSServiceProcessResult(serviceRef.pointee)
DNSServiceRefDeallocate(serviceRef.pointee)
return result
}
@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
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this, but there's an issue some people may have. This code:
The pointer needs to be advanced, but the number to skip depends on the type of record. For example when looking up MX records, the first two bytes are a 16-bit integer indicating record preference. So for MX the pointer needs to advance by 2. Without that, you can end up getting empty strings, since advancing by 1 can still mean that
txtPtr
starts with a null. Other record types may need other changes. See RFC 1035 for details.