iOS:如何指定 DNS 以将主机名解析为 IP 地址?

6
正如标题所示,我有一个主机名(例如www.example.com),我想使用指定的DNS服务器解析它。例如,在某些情况下,我想使用Google的IPv4 DNS,在另一种情况下,我想使用Google的IPv6 DNS。
我在SO上搜索了iOS上类似的内容,并找到了像这样的问题(Swift-获取设备的IP地址),所以我确定可以做到,但我不清楚如何做?
我该如何做?
编辑06/07/2018
@mdeora建议从http://www.software7.com/blog/programmatically-query-specific-dns-servers-on-ios/查询特定DNS服务器的解决方案。
这个解决方案有效,但只适用于我使用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 记录。传输的内容与用于传输它的传输方式正交。 - Patrick Mevzek
还有,标签iosobjective-cswift是做什么的呢?你没有提供任何代码片段让我们查看和讨论,所以你有点离题了。请参见[Webmasters.se]、[su]或[sf]。 - Patrick Mevzek
让我稍微澄清一下。 - MegaManX
这并没有解释为什么你特别想要 1) 首先使用Google(它们远非唯一的公共DNS解析器)和2) 更重要的是,特别在IPv6传输下使用?如果您感兴趣的区域发布了一些“AAAA”记录,则无论您使用哪个IP版本查询递归名称服务器,您都将得到它们。因此,您的问题仍然不清楚,您使用标签的原因也不清楚。否则,针对特定DNS“重新定向”的每个区域,请查看 dnsmasq - Patrick Mevzek
3个回答

4

以下是我的博客文章中提到的代码,稍作修改以使用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

由于缺少结构体__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地址

如果你只想使用IPv6地址作为你的DNS服务器,上述更改已经足够。因此,在query_ip中,如果你仍然想获取IPv4地址,则不需要进行任何更改。

如果你想从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资源记录(四元组-A记录),因为在DNS中,这指定了IPv6地址和主机名之间的映射关系。对于许多主机而言,没有这样的四元组-A记录,这意味着您只能通过IPv4访问它们。
调用:
您可以像这样调用它:
NSString *resolved = [ResolveUtil resolveHost:@"www.google.com" usingDNSServer:@"2001:4860:4860::8888"];
NSLog(@"%@", resolved);

结果将如下所示:

测试输出

免责声明

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


1
您可以使用以下 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()未定义。 - MegaManX
看起来对于iOS来说并不是很直接,尽管我已经更新了iOS的答案。 - mdeora
1
让它适用于IPv6应该很简单,请告诉我您是否遇到了任何特定的问题? - mdeora
是的,我无法修改此解决方案以使用IPv6谷歌DNS(2001:4860:4860::8888)。让我困扰的是这一行int len = res_nquery(res, host, ns_c_in, ns_t_a, answer, sizeof(answer));在这里,我总是得到-1,对于len。我将在我的问题中更新详细信息。 - MegaManX
inet_pton(AF_INET6, dns_server, &addr); 这段代码返回什么?如果返回值不是1,那就需要进行检查。 - mdeora
显示剩余6条评论

1
以下是设置 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 的扩展,但我不知道如何正确配置它。 - MegaManX
你使用的是哪个iOS SDK? - mdeora
从以下苹果文档中可以看出,似乎需要用in_addr6替换in_addr:https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html - mdeora
iOS11 SDK... 无法将in6_addr分配给in_addr。 - MegaManX
1
嘿,你可能想在我拿到这段代码之前看一下这个 - https://github.com/Bouke/DNS/tree/feature/resolver/Sources 有人正在尝试用Swift实现它。 - mdeora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接