Created
May 28, 2021 22:15
-
-
Save robinkunde/a6132a62bae5af93ecddce9e0976aabe to your computer and use it in GitHub Desktop.
Perform DNS lookups using Swift code
This file contains hidden or 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
// | |
// 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