Skip to content

Instantly share code, notes, and snippets.

@fikeminkel
Created April 26, 2017 20:56
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fikeminkel/a9c4bc4d0348527e8df3690e242038d3 to your computer and use it in GitHub Desktop.
Save fikeminkel/a9c4bc4d0348527e8df3690e242038d3 to your computer and use it in GitHub Desktop.
Swift 3.1 DNS TXT record lookup
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)
}
}
@juanheyns
Copy link

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
    }

@eclair4151
Copy link

@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

@rgkobashi
Copy link

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