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;
}
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"