iOS 有时以编程方式解析 DNS 返回无效 IP

iOS Resolve DNS programmatically is returning invalid IPs sometimes

嗨,

我正在开发一个 iOS 应用程序,它需要 以编程方式解析 DNS

将其视为解决 iPhone 上所有 dns 查询的代理。我在 iPhone 上收到来自每个应用程序的 DNS 查询并发回相应的 IPList

我试过几种方法,但都有相同的反应。我决定移动的那个在下面给出 resolveHost 函数,写在 objective-c 和 c 我从 swift 代码调用这个方法。

这就是我从 swift 调用的方式,也共享示例 host/url,host 的值可以是任何域 ("google.com, apple.com etc") 或 domain/host 作为在 mkwebview

中打开站点时的跟踪结果

让主机="www.opera.com"

let ipArray = ResolveUtil().resolveHost(host, usingDNSServer: "8.8.8.8") 作为! [字符串]

更具体地说 Facebook 应用不能很好地处理从函数 resolveHost

返回的 IP

运行不正常是指应用程序 未连接到从函数

返回的 IP

有时它 returns 192.16.192.16 作为某些 hosts/domains 的其他 IP 的一部分。这个IP是什么?

- (NSArray*)resolveHost:(NSString *)host usingDNSServer:(NSString *)dnsServer
{

    NSMutableArray* result = [[NSMutableArray alloc] init];
    struct __res_state res;
    setup_dns_server(&res, [dnsServer cStringUsingEncoding:NSASCIIStringEncoding]);
    int count;
    char** ips = query_ips(&res, [host cStringUsingEncoding:NSUTF8StringEncoding], &count);
    for (int i=0; i<count; i++){
        [result addObject:[[NSString alloc] initWithCString:ips[i] encoding:NSASCIIStringEncoding]];
    }

    for (int i=0; i<count; i++){

        free(ips[i]);
    }
    free(ips);
    ips = NULL;

    return result;

}

char ** query_ips(res_state res, const char *host, int* count)
{
    u_char answer[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);

    int messageCount = ns_msg_count(handle, ns_s_an);
    *count = messageCount;
    char **ips = malloc(messageCount * sizeof(char *));

    for (int i=0; i < messageCount; i++) {
        ips[i] = malloc(16 * sizeof(char));
        memset(ips[i], '[=11=]', sizeof(16));
        ns_rr rr;
        if(ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
            strcpy(ips[i], inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
        }
    }
    return ips;
}

其他方法

func resolveIp(_ hostUrl:String) -> [String]{
        var ips:[String] = [String]()
        let host = CFHostCreateWithName(nil,hostUrl as CFString).takeRetainedValue()
        CFHostStartInfoResolution(host, .addresses, nil)
        var success: DarwinBoolean = false
        if let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? {
            for case let theAddress as NSData in addresses {
                var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                if getnameinfo(theAddress.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(theAddress.length),
                               &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 {
                    let numAddress = String(cString: hostname)
                    ips.append(numAddress)
                }
            }
        }
        Logger.info("\(#function) validIPs:\(ips.joined(separator: "-")) url:\(hostUrl)")
        return ips
    }

192.16.192.16

这个IP是什么?完全有效的 IPv4。它解析回 basento.nikhef.nl.

Why is it returned?

我不知道。也许,请参阅 resolv.h:

 * Mac OS supports a DNS query routing API (see <dns.h>) which is used by
 * most system services to access DNS.  The BIND-9 APIs described here are
 * a lower-level that does not do query routing or search amongst multiple
 * resolver clients.  The results of DNS queries from this API may differ
 * significantly from the results of queries sent to the <dns.h> API.  We
 * strongly encourage developers to use higher-level APIs where possible.

By not working well I mean app does not connect to IPs returned from the functions.

这与name/ip地址解析无关。

问题可能出在别处。您的提供商可以阻止它,IP 地址上没有 运行 服务,不允许您访问它,......原因很多。

More specifically Facebook app does not work well with IPs returned from function resolveHost.

这对我来说完全没有意义。你有自己的应用程序,你在其中解析 IP 地址,然后说它不适用于 Facebook。老实说,我不知道你这是什么意思。


我为什么要回答这个问题?那么,您 不应该盲目地从其他 Stack Overflow 问题或任何其他站点复制和粘贴代码 。做了一个研究,它看起来像一些其他答案的复制和粘贴。

为什么?您问题中的代码不处理错误,不遵循文档,...它对您有用真是太幸运了。

如果这是你的问题怎么办?你考虑过这个选项吗?

这是一个示例 Resolver 您可以根据您的条件使用/测试。它可能会也可能不会解决您的问题。

#import <resolv.h>
@import Darwin.POSIX.arpa;

@interface Resolver: NSObject

- (nullable instancetype)initWithDNSServer:(nonnull NSString *)server;
- (nullable NSArray<NSString *> *)resolveHost:(nonnull NSString *)host;

@end

@implementation Resolver {
    struct __res_state *state;
}

- (void)dealloc {
    if (state != NULL) {
        // man 3 resolver:
        //
        // res_ndestroy() should be call to free memory allocated by res_ninit() after last use.
        if ((state->options & RES_INIT) == RES_INIT) {
            res_ndestroy(state);
        }
        free(state);
        state = NULL;
    }
}

- (nullable instancetype)initWithDNSServer:(nonnull NSString *)server {
    if ((self = [super init]) == nil) {
        return nil;
    }

    // man 3 resolver:
    //
    // The memory referred to by statp must be set to all zeros prior
    // to the first call to res_ninit(). res_ndestroy() should be call to free memory
    // allocated by res_ninit() after last use.
    if ((state = calloc(1, sizeof(*state))) == NULL) {
        return nil;
    }

    // 0 success
    if (res_ninit(state) != 0) {
        return nil;
    }

    // Avoid calling inet_aton later with NULL if we can't convert it to ASCII
    if (![server canBeConvertedToEncoding:NSASCIIStringEncoding]) {
        return nil;
    }

    struct in_addr addr;

    // man 3 inet_aton:
    //
    // It returns 1 if the string was successfully interpreted ...
    if (inet_aton([server cStringUsingEncoding:NSASCIIStringEncoding], &addr) != 1) {
        return nil;
    }

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

    return self;
}

- (nullable NSArray<NSString *> *)resolveHost:(nonnull NSString *)host {
    // Avoid calling res_nquery with NULL
    if (![host canBeConvertedToEncoding:NSASCIIStringEncoding]) {
        return nil;
    }

    u_char answer[NS_PACKETSZ];

    int len = res_nquery(state, [host cStringUsingEncoding:NSASCIIStringEncoding],
                         ns_c_in, ns_t_a, answer, sizeof(answer));

    // -1 = error
    if (len == -1) {
        return nil;
    }

    ns_msg handle;

    // 0 success, -1 error
    if (ns_initparse(answer, len, &handle) != 0) {
        return nil;
    }

    u_int16_t count = ns_msg_count(handle, ns_s_an);
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
    for (int i = 0 ; i < count ; i++) {
        ns_rr rr;
        // 0 success, -1 error
        if (ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
            char *address = inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr));

            if (address == NULL) {
                continue;
            }

            NSString *ip = [NSString stringWithCString:address
                                              encoding:NSASCIIStringEncoding];
            [result addObject:ip];
        }
    }

    return result;
}

@end

你可以这样使用:

Resolver *resolver = [[Resolver alloc] initWithDNSServer:@"8.8.8.8"];
NSLog(@"%@", [resolver resolveHost:@"www.opera.com"]);

输出:

(
    "13.102.114.111",
    "52.57.141.185",
    "18.196.127.98"
)

我可能没有在我原来的问题中解释问题陈述,但我设法修复了这个错误,所以我想我应该在这里写下我的发现。 我的应用程序用作 dns 代理,因此它的主要职责是解析域和 return IP。

我使用了resolveHost函数来解析IP。此功能具有 zrzka 提到的所有问题,所以如果有人想使用,请考虑他的观点。

我遇到的问题是函数 return 有一些针对特定 hosts/domains 的 IP,这似乎无效,我说无效是因为这些不是可 ping 通的 IP,我从 Wireshark 确认了连接在这些 IP 上不成功,即使 returned IPList 在某个索引处包含有效的 IP,它仍然会导致不必要的延迟,因为首先尝试无效的 IP,因为它们位于列表中的有效 IP 之前。

经过进一步调查,我了解到这些无效 IP 与描述 DNS 记录中的别名的答案类型 CNAME 不符,我不知道我是否仍应将它们称为无效,但忽略它们对我来说已经完成了工作。现在我只接受来自 DNS 响应的 A 类或 AAAA 类答案。我通过以下函数的简单检查实现了这一点。

char ** query_ips(res_state res, const char *host, int* count)
{
    u_char answer[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);

    int messageCount = ns_msg_count(handle, ns_s_an);
    *count = messageCount;
    char **ips = malloc(messageCount * sizeof(char *));

    for (int i=0; i < messageCount; i++) {
        ips[i] = malloc(16 * sizeof(char));
        memset(ips[i], '[=10=]', sizeof(16));
        ns_rr rr;
        if(ns_parserr(&handle, ns_s_an, i, &rr) == 0) {
            if (1 == rr.type || 28 == rr.type) // here is the new check
              strcpy(ips[i], inet_ntoa(*(struct in_addr *)ns_rr_rdata(rr)));
        }
    }
    return ips;
}