【问题标题】:iOS: Resolve DNS SRV recordsiOS:解析 DNS SRV 记录
【发布时间】:2021-02-15 22:24:58
【问题描述】:

我是我的 iOS 应用程序,我想解析域的 DNS 服务记录 (SRV)。

我找到了一些允许这样做的库,但响应为空。我尝试过的库之一是DNS。代码看起来像这样:

import UIKit
import DNS

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    let url = "_registration._tcp.gateway.zeeotp.com"
    print("Starting discovery")
    
    do {
    let request = Message(
        type: .query,
        authoritativeAnswer: false, 
        questions: [Question(name: url, type: .service)]
    )
    let requestData = try request.serialize()
    
    // Decoding a message
    let responseData = requestData
    let response = try Message.init(deserialize: responseData)
    print(response)
    } catch {
        print("Unexpected error: \(error).")
    }
  }

}

我得到的输出是:

DNS 请求(id:0,authoritativeAnswer:false,截断:false,recursionDesired:false,recursionAvailable:false,问题:[DNS.Question(名称:“_registration._tcp.gateway.zeeotp.com。”,类型: SRV,唯一:false,internetClass:A)],答案:[],权限:[],附加:[])

在我的研究过程中,我遇到了SRVResolver,但它是在 Objective-C 中的,我不知道如何让它与 Swift 一起工作。

我在 ablove 代码中是否遗漏了一些东西以使其正常工作?

【问题讨论】:

  • 您的使用可能有问题,因为internetClass: A 似乎有问题。如果class 在这里表示class,就像在DNS 术语中一样,它应该是IN
  • 即使我在Question 构造函数中将internetClass 作为IN 传递,它也会被反序列化为A 类。
  • 问题是这个库实际上并不执行 DNS 查询,它只是处理将 dns 查询对象转换为您可以发送到 DNS 服务器的Data 以及接收到的@ 的转换987654333@回到一个对象。您的代码只是在解码它编码的数据 (responseData = requestData),因此您最终没有答案,因为您实际上并没有提出问题。
  • @Paulw11Yes 是有道理的。基于此,我试图找到一种方法来使用这些数据查询公共 DNS 服务,但我能找到的只是使用 CFNetwork 类从主机解析 IP。如果您对如何查询 DNS 有任何想法并能指出我的方向,那就太好了。

标签: ios swift dns


【解决方案1】:

在尝试了各种各样的事情之后,我终于能够完成这项工作。正如 @Paulw11 在 cmets 中提到的,库 DNS 没有发送实际的 DNS 请求,因此我没有得到想要的结果。

选项 1:

我最终决定将 Apple 提供的 Objective-C example 集成到我的 Swift 应用程序中。为此,

  1. 我将SRVResolver.hSRVResolver.m 添加到我的项目中
  2. 创建桥接头文件并添加:
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#include "SRVResolver.h"
  1. 在目标的构建阶段下的Link Binary With Libraries部分添加了libresolv.tbd

现在在我的ViewController.swift 中,我可以按如下方式使用它来获取priorityweighthostname 值:

let url = "_registration._tcp.gateway.zeeotp.com"
print("Starting discovery")
    
let resolver = SRVResolver.init(srvName: url)
assert(resolver != nil)
resolver?.delegate = self
resolver?.start()
while !(resolver!.isFinished) {
    RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
    
if(resolver?.error == nil) {
    for result in resolver?.results ?? [] {
        let dataArray = result as! NSDictionary;
            
        for (key, value) in dataArray { // loop through data items
            print("\(key) -> \(value)")
        }
    }
} else {
    print("Error: \(String(describing: resolver?.error))")
}

选项:2

我还发现了一个外部库NioDNS,它允许我们进行一些 DNS 操作。使用这个库,我也能够检索 SRV 记录。在我的ViewController.swift 中,我添加了以下代码:

import NIO
import DNSClient

let loop: MultiThreadedEventLoopGroup!

do {
    loop = MultiThreadedEventLoopGroup(numberOfThreads: 1)

    let client = try DNSClient.connect(on: loop).wait()
    let records = try client.getSRVRecords(from: url).wait()
    for record in records {
        print(record.resource.weight)
        print(record.resource.priority)
        print(record.resource.domainName.string)
    }
} catch {
    print("Error: \(error)")
}

【讨论】:

    【解决方案2】:

    为此使用第三方库似乎有点过头了。您可以使用 URLSession 实现您想要的结果。此示例使用dns query tools,但还有其他可用的。

    let apiKey = "your api key"
    
    extension Dictionary : URLQueryParameterStringConvertible {
       var queryParameters: String {
          var parts: [String] = []
          for (key, value) in self {
             let part = String(format: "%@=%@",
                               String(describing: key).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!,
                               String(describing: value).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
             parts.append(part as String)
          }
          return parts.joined(separator: "&")
       }
    }
    
    
    extension URL {
       func appendingQueryParameters(_ parametersDictionary : Dictionary<String, String>) -> URL {
          let URLString : String = String(format: "%@?%@", self.absoluteString, parametersDictionary.queryParameters)
          return URL(string: URLString)!
       }
    }
    
    
    protocol URLQueryParameterStringConvertible {
       var queryParameters: String {get}
    }
    
    
    enum RecordTpe: String {
       case A
       case SRV
       case MX
       case AAA
    }
    
    
    let sessionConfig = URLSessionConfiguration.default
    let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
    guard var URL = URL(string: "https://dns.query.tools/v1/dns/query") else {fatalError()}
    
    let URLParams = [
       "host": "_registration._tcp.gateway.zeeotp.com",
       "type": RecordTpe.SRV.rawValue,
    ]
    
    URL = URL.appendingQueryParameters(URLParams)
    var request = URLRequest(url: URL)
    request.httpMethod = "GET"
    
    request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
    let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
       if (error == nil) {
          let json = String(data: data!, encoding: .utf8)
          print(json!)
       }
       else {
          print("URL Session Task Failed: %@", error!.localizedDescription);
       }
    })
    task.resume()
    session.finishTasksAndInvalidate()
    
    

    我认为这些扩展最初来自出色的 Paw 应用,所以请查看:Paw Cloud

    【讨论】:

    • “使用第三方库似乎有点过头了。”但是随后您通过 DOH 使用完整的外部网站来完成这项工作。应用程序不应该有那种依赖关系,它本身 + 操作系统拥有本地执行 DNS 查询所需的一切,不需要依赖第三方,甚至不需要考虑安全性和性能后果......
    • @PatrickMevzek 我同意,这是一个不好的例子,但我不得不举个例子。我应该停止评论添加第三方 deoendency 是矫枉过正。我会删除答案,因为它可能会产生误导。
    • 省略您的答案并没有什么坏处。它确实回答了这个问题,尽管不完全按照 OP 想要的方式,但它提供了另一种观点。应该有其他答案,人们只会投票。