Specify DNS to resolve an address - iOS

In my app, I want to resolve an address with a DNS server address of my choice (the DNS isn't mine, I just want to decide at which DNS the app is going to use).

I have this code (below) but I always gets the error "nodename nor servname provided, or not known".

What am I doing wrong?

Is it even possible?


here is the code I'm using, it's quite short and implemented in C.

dns_server_s is the IP of the DNS server I want to use, and the node is the address I want to resolve via this DNS


int my_getaddrinfo(const char *dns_server_s, const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
    struct in_addr dns_server;
    struct sockaddr_in dns_server_sock;
    int ret = inet_pton(AF_INET, dns_server_s, &dns_server);
    if (ret != 1) {
        return -1;
    }
  
    dns_server_sock.sin_family = AF_INET;
    dns_server_sock.sin_port = htons(53);
    dns_server_sock.sin_addr = dns_server;
  
    if (!(_res.options & RES_INIT)) {
        struct addrinfo hh, *servinfo;
        memset(&hh, 0, sizeof(hh));
        getaddrinfo("google.com", NULL, &hh, &servinfo);
        freeaddrinfo((struct addrinfo*)servinfo);
    }
  
    _res.nscount = 1;
    _res.nsaddr_list[0] = dns_server_sock;
  
    ret = getaddrinfo(node, service, hints, res);
  
    res_init();
  
    return ret;
}

Accepted Reply

Yeah, that code is not going to work. The technique shown there dates from a time when DNS resolution was done by a library within each process (which has never been the case on iOS but was the case on its BSD ancestors). On iOS, DNS resolution is done inside a system daemon (

mDNSResponder
currently) and thus modifying the state within your app won’t work.

IMPORTANT Doing your own DNS resolution is almost always a bad idea. The system DNS resolver has lots of smarts that allow it to work well in a wide variety of configurations. Trying to replicate that in your code is very tricky.

Notwithstanding the above, given that you’re working on a VPN product I can see why you might need to do this. In which case the go-to API is

<dns.h>
. To use this, you must first create a custom
resolv.conf
on disk. In my tests I added the following file to my app bundle.
nameserver 8.8.8.8

You then pass the path of that file to

dns_open
, which creates a resolver based on that configuration. At that point you can call
dns_query
to make DNS queries to the specific server.

Here’s a minimal example:

#include <dns.h>
#include <dns_sd.h>
#include <sys/socket.h>

static NSData * synchronousDNSQuery() {
    NSData *                result;
    NSURL *                confURL;
    dns_handle_t            dns;
    uint8_t                responseBuffer[4096];
    struct sockaddr_storage fromAddr;
    uint32_t                fromAddrLen;
    int32_t                queryResult;

    confURL = [[NSBundle mainBundle] URLForResource:@"resolv" withExtension:@"conf"];
    dns = dns_open(confURL.fileSystemRepresentation);

    fromAddrLen = sizeof(fromAddr);
    queryResult = dns_query(
        dns,
        "example.com.",
        kDNSServiceClass_IN,
        kDNSServiceType_A,
        (char *) responseBuffer,
        sizeof(responseBuffer),
        (struct sockaddr *) &fromAddr,
        &fromAddrLen
    );
    if (queryResult > 0) {
        result = [NSData dataWithBytes:responseBuffer length:(NSUInteger) queryResult];
        NSLog(@"response %@ from %@",
            result,
            [NSData dataWithBytes:&fromAddr length:(NSUInteger) fromAddrLen]
        );
    } else {
        result = nil;
        NSLog(@"error");
    }

    dns_free(dns);

    return result;
}

Note This uses some declarations from

<dns_sd.h>
(like
kDNSServiceClass_IN
) just because their convenient.

This does not give you the equivalent of

getaddrinfo
. To start, you’ll need to parse the resulting response (which you can do using the routines in
<dns_util.h>
). Beyond that, implementing an actual resolver involves a bunch of DNS-specific stuff (like following
CNAME
records).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

What does your code actually look like, since that's not valid C code. Lines 15 and 26 contain parsing errors.

Sry, edited.

Yeah, that code is not going to work. The technique shown there dates from a time when DNS resolution was done by a library within each process (which has never been the case on iOS but was the case on its BSD ancestors). On iOS, DNS resolution is done inside a system daemon (

mDNSResponder
currently) and thus modifying the state within your app won’t work.

IMPORTANT Doing your own DNS resolution is almost always a bad idea. The system DNS resolver has lots of smarts that allow it to work well in a wide variety of configurations. Trying to replicate that in your code is very tricky.

Notwithstanding the above, given that you’re working on a VPN product I can see why you might need to do this. In which case the go-to API is

<dns.h>
. To use this, you must first create a custom
resolv.conf
on disk. In my tests I added the following file to my app bundle.
nameserver 8.8.8.8

You then pass the path of that file to

dns_open
, which creates a resolver based on that configuration. At that point you can call
dns_query
to make DNS queries to the specific server.

Here’s a minimal example:

#include <dns.h>
#include <dns_sd.h>
#include <sys/socket.h>

static NSData * synchronousDNSQuery() {
    NSData *                result;
    NSURL *                confURL;
    dns_handle_t            dns;
    uint8_t                responseBuffer[4096];
    struct sockaddr_storage fromAddr;
    uint32_t                fromAddrLen;
    int32_t                queryResult;

    confURL = [[NSBundle mainBundle] URLForResource:@"resolv" withExtension:@"conf"];
    dns = dns_open(confURL.fileSystemRepresentation);

    fromAddrLen = sizeof(fromAddr);
    queryResult = dns_query(
        dns,
        "example.com.",
        kDNSServiceClass_IN,
        kDNSServiceType_A,
        (char *) responseBuffer,
        sizeof(responseBuffer),
        (struct sockaddr *) &fromAddr,
        &fromAddrLen
    );
    if (queryResult > 0) {
        result = [NSData dataWithBytes:responseBuffer length:(NSUInteger) queryResult];
        NSLog(@"response %@ from %@",
            result,
            [NSData dataWithBytes:&fromAddr length:(NSUInteger) fromAddrLen]
        );
    } else {
        result = nil;
        NSLog(@"error");
    }

    dns_free(dns);

    return result;
}

Note This uses some declarations from

<dns_sd.h>
(like
kDNSServiceClass_IN
) just because their convenient.

This does not give you the equivalent of

getaddrinfo
. To start, you’ll need to parse the resulting response (which you can do using the routines in
<dns_util.h>
). Beyond that, implementing an actual resolver involves a bunch of DNS-specific stuff (like following
CNAME
records).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Great, thank you very much!

In the meantime, I did find a solution, using

__res_state, res_nquery and ns_initparse, but I will replace this with your code.


Thanks again!


Edit:

just to check - this is the solution I used - it's seems to work very well, so will it be reasonable to use it until I replace the code?



int my_getaddrinfo(const char *dns_server_s, const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
  
    int retValue = 1;
    struct __res_state result;
    char ip[16];
    memset(ip, '\0', sizeof(ip));
  
    res_ninit(&result);
    struct in_addr addr;
    inet_aton(dns_server_s, &addr);
  
    result.nsaddr_list[0].sin_addr = addr;
    result.nsaddr_list[0].sin_family = AF_INET;
    result.nsaddr_list[0].sin_port = htons(NS_DEFAULTPORT);
    result.nscount = 1;
  
    u_char answer[NS_PACKETSZ];
    int len = res_nquery(&result, node, 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)));
            getaddrinfo(ip, service, hints, res);
            retValue = 0;
        }
    }
  
    return retValue;
}

Hi,

I have an issue with Mac and IOS DNS resolution.

we have our own app which is running by using our custom VPN.The issue is when we are connecting VPN with Split Tunnel mode not able to access our intranets. Using scutil --dns command we found

  1. DNS servers configured by ISP will be in 1st resolver list (list contains Domains and nameservers).
  2. DNS servers configured by VPN connection will be in 2nd resolver list.

Ex: If we configured xyz.com as domain suffix then DNS request for any abc.xyz.com gets resolved.But we tries to access other internal sites like pqr.com (not configured domain) doesn’t get resolved.


Tried Solutions:

1. SCDynamicStore System Configuration API's but its working only with root priviledges.( Our VPN is working with Plugin which dont have any root access)

2. Set kSCPropNetDNSSearchDomains,kSCPropNetDNSDomainName and kSCPropNetDNSSupplementalMatchDomains. But its setting values only in resolver 2.

3. eskimo's code dns_open, dns_query.But no changes in DNS Server List.


Questions:

1. Programmatically Is there any way to set VPN Configured DNS Server as a 1st resolver?

2. If the above is not possibles is there any right APIs to update the DNS server list to send all DNS traffic to the VPN configured DNS Server? (These APIs need to be used inside a plugin which doesn’t have root privileges).

3. eskimo - does your code will make changes in plugin level or its only from application level?


Any help is much appreciated.

Please help me what may be going wrong?


Thanks,

Priya