Skip to content

Instantly share code, notes, and snippets.

@robinkunde
Created May 28, 2021 22:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robinkunde/a6132a62bae5af93ecddce9e0976aabe to your computer and use it in GitHub Desktop.
Save robinkunde/a6132a62bae5af93ecddce9e0976aabe to your computer and use it in GitHub Desktop.
Perform DNS lookups using Swift code
//
// dnsLookup.swift
//
// Created by Robin Kunde on 5/28/21.
//
import dnssd
import Foundation
typealias DNSResultHandler = (String, Int) -> Void
func query(domainName: String) -> [String: Int] {
var result: [String: Int] = [:]
var recordHandler: DNSResultHandler = { (hostname, preference) -> Void in
result[hostname] = preference
}
let callback: DNSServiceQueryRecordReply = {
(sdRef, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rawRdataPtr, ttl, context) -> Void in
guard
errorCode == kDNSServiceErr_NoError,
rdlen > 0,
let rdataPtr = rawRdataPtr?.assumingMemoryBound(to: UInt8.self),
let handler = context?.assumingMemoryBound(to: DNSResultHandler.self).pointee
else { return }
let rdata = UnsafeBufferPointer(start: rdataPtr, count: Int(rdlen))
// This is a big, ugly hack (converted from Apple example project at https://developer.apple.com/library/archive/samplecode/SRVResolver/Introduction/Intro.html)
// that allows us to offload DNS record parsing to libresolv (https://opensource.apple.com/source/libresolv/libresolv-38/dns_util.c.auto.html).
// (note about TTL: the value you get is cached so it will probably be the same across several invocations, but it represents the time that was left before this cached value expires *when it was first cached* so it will eventually change)
// You can also parse the raw data by hand. The dns_util.c file linked above has the details, but for MX records it's something like:
// 1. 2 bytes for the record preference
// 2. 1 byte for the length of the next segment of the hostname
// 3. hostname segment bytes
// repeat 2. and 3. until the end
// example for aspmx.l.google.com with priority 1: \u{00}\u{01}\u{05}aspmx\u{01}l\u{06}google\u{03}com
var synthesizedResourceRecordData = Data()
synthesizedResourceRecordData.append(0)
synthesizedResourceRecordData.append(withUnsafeBytes(of: UInt16(kDNSServiceType_MX).bigEndian) { Data($0) })
synthesizedResourceRecordData.append(withUnsafeBytes(of: UInt16(kDNSServiceClass_IN).bigEndian) { Data($0) })
synthesizedResourceRecordData.append(withUnsafeBytes(of: ttl.bigEndian) { Data($0) })
synthesizedResourceRecordData.append(withUnsafeBytes(of: rdlen.bigEndian) { Data($0) })
synthesizedResourceRecordData.append(rdata)
let rrPtr: UnsafeMutablePointer<dns_resource_record_t>? = synthesizedResourceRecordData.withUnsafeBytes {
dns_parse_resource_record($0.baseAddress!.assumingMemoryBound(to: Int8.self), UInt32($0.count))
}
guard
let rr = rrPtr?.pointee,
let hostname = rr.data.MX.pointee.name.map({ String(cString: $0) })
else { return }
let preference = Int(rr.data.MX.pointee.preference)
handler(hostname, preference)
}
let serviceRef: UnsafeMutablePointer<DNSServiceRef?> = UnsafeMutablePointer.allocate(capacity: MemoryLayout<DNSServiceRef>.size)
let resultCode = DNSServiceQueryRecord(
serviceRef,
kDNSServiceFlagsTimeout,
0,
domainName,
UInt16(kDNSServiceType_MX),
UInt16(kDNSServiceClass_IN),
callback,
&recordHandler
)
guard resultCode == kDNSServiceErr_NoError else { return [:] }
defer {
DNSServiceRefDeallocate(serviceRef.pointee)
}
DNSServiceProcessResult(serviceRef.pointee)
return result
}
let res = query(domainName: "recoursive.com")
print(res)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment