summaryrefslogtreecommitdiff
path: root/DomainDig/DNSLookupService.swift
blob: 85a5275003b5a60eeb10ae9bda08416e8b8381b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import Foundation

struct DNSLookupService {
    private static let baseURL = "https://cloudflare-dns.com/dns-query"

    static func lookup(domain: String, recordType: DNSRecordType) async throws -> [DNSRecord] {
        var components = URLComponents(string: baseURL)!
        components.queryItems = [
            URLQueryItem(name: "name", value: domain),
            URLQueryItem(name: "type", value: String(recordType.queryType))
        ]

        var request = URLRequest(url: components.url!)
        request.setValue("application/dns-json", forHTTPHeaderField: "Accept")

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }

        let dnsResponse = try JSONDecoder().decode(CloudflareDNSResponse.self, from: data)

        guard let answers = dnsResponse.Answer else {
            return []
        }

        // Filter answers to only include the requested type
        return answers
            .filter { $0.type == recordType.queryType }
            .map { answer in
                let value = answer.data.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
                return DNSRecord(value: value, ttl: answer.TTL)
            }
    }

    /// Record types that support wildcard queries.
    private nonisolated(unsafe) static let wildcardTypes: Set<DNSRecordType> = [.A, .AAAA, .MX, .TXT]

    static func lookupAll(domain: String) async -> [DNSSection] {
        // Each task returns (recordType, apex records, wildcard records).
        typealias Result = (type: DNSRecordType, records: [DNSRecord], wildcard: [DNSRecord], error: String?)

        return await withTaskGroup(of: Result.self, returning: [DNSSection].self) { group in
            for recordType in DNSRecordType.allCases {
                group.addTask {
                    var apexRecords: [DNSRecord] = []
                    var wildcardRecords: [DNSRecord] = []
                    var lookupError: String?

                    // Apex query
                    do {
                        apexRecords = try await lookup(domain: domain, recordType: recordType)
                    } catch {
                        lookupError = error.localizedDescription
                    }

                    // Wildcard query (only for applicable types, and only if apex didn't fail)
                    if wildcardTypes.contains(recordType) && lookupError == nil {
                        do {
                            wildcardRecords = try await lookup(domain: "*.\(domain)", recordType: recordType)
                        } catch {
                            // Wildcard failure is non-fatal; just leave empty
                        }
                    }

                    return (recordType, apexRecords, wildcardRecords, lookupError)
                }
            }

            var sections: [DNSSection] = []
            for await result in group {
                sections.append(DNSSection(
                    recordType: result.type,
                    records: result.records,
                    wildcardRecords: result.wildcard,
                    error: result.error
                ))
            }

            // Sort to maintain consistent order
            let order = DNSRecordType.allCases
            return sections.sorted { a, b in
                (order.firstIndex(of: a.recordType) ?? 0) < (order.firstIndex(of: b.recordType) ?? 0)
            }
        }
    }
}