【问题标题】:iOS: How to specify DNS to be used to resolve hostname to IP address?iOS:如何指定用于将主机名解析为 IP 地址的 DNS?
【发布时间】:2018-11-15 03:16:13
【问题描述】:

正如标题所说,我有想要使用指定 DNS 服务器解析的主机名(例如 www.example.com)。例如,在一种情况下,我想使用 google 的 IPv4 DNS,而在另一种情况下,我想使用 google 的 IPv6 DNS。

我已经在 iOS 上浏览了类似的东西,发现了类似的问题 (Swift - Get device's IP Address),所以我确信可以做到,但我不清楚如何做?

我该怎么做?

编辑 06/07/2018

@mdeora 建议的解决方案来自http://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/

此解决方案有效,但前提是我使用 IPv4 DNS,例如 google 的“8.8.8.8”。如果我尝试使用 IPv6 DNS 2001:4860:4860::8888,我什么也得不到。

我已经设法改变转换:

void setup_dns_server(res_state res, const char *dns_server)
{
    res_ninit(res);
    struct in_addr addr;

//    int returnValue = inet_aton(dns_server, &addr);
    inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion

    res->nsaddr_list[0].sin_addr = addr;
    res->nsaddr_list[0].sin_family = AF_INET;
    res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
    res->nscount = 1;

};

但还是有这个问题:

void query_ip(res_state res, const char *host, char ip[])
{
    u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ NS_PACKETSZ
    int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));

    ns_msg handle;
    ns_initparse(answer, len, &handle);


    if(ns_msg_count(handle, ns_s_an) > 0) {
        ns_rr rr;
        if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
            strcpy(ip, inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
        }
    }
}

len 得到 -1。从我收集的信息看来,我需要为 IPv6 配置 res_state。

【问题讨论】:

  • 你的问题不是很清楚,能提供一个具体的例子吗?此外,DNS 不会将 URL 解析为 IP 地址,而是将主机名解析为 IP 地址。使用 IPv6 可用递归名称服务器在答案中是否有 AAAA 记录的事实是分开的:您可以通过 IPv4 查询名称服务器并仍然返回 AAAA 记录。传输的内容与用于传输它的传输方式正交。
  • 还有标签iosobjective-cswift在那里做什么?你没有提供任何你希望我们看到和讨论的代码,所以你在这里有点离题。请参阅 WebmastersSuper UserServer Fault
  • 让我澄清一点
  • 这并不能解释为什么你特别想要 1) 首先是谷歌(它们远非唯一的公共 DNS 解析器,2)更重要的是,特别是在 IPv6 传输下?如果您感兴趣的区域发布一些 AAAA 记录,您将获得它们用于查询递归名称服务器的任何 IP 版本。因此,您的问题仍然不清楚,您使用标签的原因也不清楚。否则,对于每个区域的特定 DNS“重定向”,请参阅 dnsmasq

标签: ios objective-c swift dns


【解决方案1】:

这里是我的博文中的代码,上面已经提到过,只是稍微适应了使用 IPv6。

调整 setup_dns_server

首先我们可以从更改 setup_dns_server 开始:

void setup_dns_server(res_state res, const char *dns_server) {
    struct in6_addr addr;

    inet_pton(AF_INET6, dns_server, &addr);

    res->_u._ext.ext->nsaddrs[0].sin6.sin6_addr = addr;
    res->_u._ext.ext->nsaddrs[0].sin6.sin6_family = AF_INET6;
    res->_u._ext.ext->nsaddrs[0].sin6.sin6_port = htons(NS_DEFAULTPORT);
    res->nscount = 1;
}

添加 __res_state_ext

由于缺少 struct __res_state_ext,因此无法编译。不幸的是,此结构位于私有头文件中。

但是这个定义可以从这里得到: https://opensource.apple.com/source/libresolv/libresolv-65/res_private.h.auto.html

struct __res_state_ext {
    union res_sockaddr_union nsaddrs[MAXNS];
    struct sort_list {
        int af;
        union {
            struct in_addr  ina;
            struct in6_addr in6a;
        } addr, mask;
    } sort_list[MAXRESOLVSORT];
    char nsuffix[64];
    char bsuffix[64];
    char nsuffix2[64];
};

可以添加结构,例如在文件的顶部。

适配 resolveHost

此处的更改包括更长的 ip 缓冲区 (INET6_ADDRSTRLEN)。 res_ninit 从 setup_dns_server 移到此方法中,现在与 res_ndestroy 匹配。

+ (NSString *)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer {
    struct __res_state res;
    char ip[INET6_ADDRSTRLEN];
    memset(ip, '\0', sizeof(ip));

    res_ninit(&res);
    setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
    query_ip(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], ip);
    res_ndestroy(&res);

    return [[NSString alloc] initWithCString:ip encoding:NSASCIIStringEncoding];
}

检索 IPv6 地址

如果您只想为您的 DNS 服务器使用 IPv6 地址,上述更改已经足够了。因此,如果您仍想检索 IPv4 地址,则在 query_ip 中无需进行任何更改。

如果您还想从 DNS 服务器检索 IPv6 地址,您可以这样做:

void query_ip(res_state res, const char *host, char ip[]) {
    u_char answer[NS_PACKETSZ];
    int len = res_nquery(res, host, ns_c_in, ns_t_aaaa, answer, sizeof(answer));

    ns_msg handle;
    ns_initparse(answer, len, &handle);


    if(ns_msg_count(handle, ns_s_an) > 0) {
        ns_rr rr;
        if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
            inet_ntop(AF_INET6, ns_rr_rdata(rr), ip, INET6_ADDRSTRLEN);
        }
    }
}

请注意:我们这里使用 ns_t_aaaa 来获取 AAAA 资源记录(quad-A 记录),因为在 DNS 中这指定了 IPv6 地址和主机名之间的映射。对于许多主机来说,没有这样的四 A 记录,这意味着您可以通过 IPv4 访问它们。

致电

你可以称之为例如像这样:

NSString *resolved = [ResolveUtil resolveHost:@"www.google.com" usingDNSServer:@"2001:4860:4860::8888"];
NSLog(@"%@", resolved);

结果将如下所示:

免责声明

这些只是简单的示例调用,演示了函数的基本用法。没有错误处理。

【讨论】:

    【解决方案2】:

    您可以使用以下 swift 代码执行此操作 -

    import Foundation
    let task = Process()
    task.launchPath = "/usr/bin/env"
    task.arguments = ["dig", "@8.8.8.8", "google.com"]
    
    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()
    
    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
    
    print(output!)
    

    在上面的代码中,通过替换 8.8.8.8 来使用您选择的 DNS 服务器

    对于 Objective-C iOS,请参考以下链接 - https://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/

    【讨论】:

    • 这是用于 OSX 的,我需要它用于 iOS。 iOS 上未定义 Process()
    • 看起来对于 iOS 来说并不直接,尽管我已经更新了 iOS 的答案。
    • 使其适用于 IPV6 应该很简单,如果您遇到任何具体问题,请告诉我?
    • 也刚遇到这个-developer.apple.com/documentation/networkextension/… 有设置DNS服务器的功能,也有ipv6投诉
    • 是的,我无法修改此解决方案以使用 2001:4860:4860::8888 的 IPv6 google DNS。给我带来麻烦的是这一行 int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));在这里我总是得到 -1,对于 len。我会用详细信息更新我的问题
    【解决方案3】:

    下面是修改后的dns设置代码-

    void setup_dns_server(res_state res, const char *dns_server)
        {
            res_ninit(res);
            struct in_addr6 addr;
    
        //    int returnValue = inet_aton(dns_server, &addr);
            inet_pton(AF_INET6, dns_server, &addr); // for IPv6 conversion
    
            res->nsaddr_list[0].sin_addr = addr;
            res->nsaddr_list[0].sin_family = AF_INET6;
            res->nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
            res->nscount = 1;
    
        };
    

    还有查询代码——

    void query_ip(res_state res, const char *host, char ip[])
    {
        u_char answer[NS_PACKETSZ];//NS_IN6ADDRSZ NS_PACKETSZ
        int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));
    
        ns_msg handle;
        ns_initparse(answer, len, &handle);
    
    
        if(ns_msg_count(handle, ns_s_an) > 0) {
            ns_rr rr;
            if(ns_parserr(&handle, ns_s_an, 0, &rr) == 0) {
                strcpy(ip, inet_ntoa(*(struct in_addr6 *)ns_rr_rdata(rr)));
            }
        }
    }
    

    PS - 我无法对其进行测试,但它应该适用于 ipv6 dns。

    【讨论】:

    • res->nsaddr_list[0].sin_addr = addr;我自己也试过了,但它不会编译。在 res_state 的文档中,我看到 IPv6 有一些扩展,但我不知道如何正确配置它
    • 你使用的是什么 ios SDK?
    • 以下苹果文档中的 in_addr 似乎要替换为 in_addr6。 developer.apple.com/library/archive/documentation/…
    • iOS11 SDK...不能将in6_addr分配给in_addr
    • 嘿,你可能想看看这个,直到我得到这个代码 - github.com/Bouke/DNS/tree/feature/resolver/Sources 有人试图在 Swift 中实现它。
    猜你喜欢
    • 2021-10-08
    • 2019-09-29
    • 2011-03-12
    • 2012-08-04
    • 1970-01-01
    • 2011-05-07
    • 2013-03-25
    • 1970-01-01
    • 2017-06-20
    相关资源
    最近更新 更多