Friday, 13 March 2015

Specifying an DNS/NS server for address resolution / getaddrinfo in C

I was recently trying to get my C program to use a custom NS server when resolving an address, within the program. It ended up being a challenge to not have to use to create my own function that would have to deal with the connections directly(connect(), etc.)
The purpose of this was to resolve opendns's server to find out the runner's IP address.


I found that 'libresolv' could be used to overwrite a private/protected struct, "_res".
We must include resolv.h to do this.


Here is an example code as to how to do this:

                inet_pton(AF_INET, "208.67.220.220", &server); 

                serverSock.sin_family = AF_INET;
                serverSock.sin_port = htons(53);
                serverSock.sin_addr = server;

                _res.nscount = 1;
                _res.nsaddr_list[0] = serverSock;


Then, using the code to get resolve something:


        if((getaddrinfo("myip.opendns.com", NULL, &hint, (struct addrinfo**)&result)) == 0) {
                p = result;
                h = (struct sockaddr_in *)p->ai_addr;

                printf("myip.opendns.com resolves, using 208.67.220.220, to: %s\n", inet_ntoa(h->sin_addr));
                freeaddrinfo((struct addrinfo *)result);

                exit(0);
        }



Strangely enough, On the first run of getaddrinfo(), resets _res. So, we have to either do a fake getaddrinfo call, or a loop. [It took me 3 hours to work this out :(] -- This is a bug! bug report

Since a loop could cause problems, we're just going to do a fake getaddrinfo call, which is simple enough:

        struct addrinfo hints, *servinfo;



        memset(&hints, 0, sizeof hints);
        getaddrinfo("google.com", NULL, &hints, &servinfo);
        freeaddrinfo((struct addrinfo*)servinfo);


This must be placed before the overwrite of _res, and will use our regular DNS server.

NOTE: _all_ subsequent DNS resolutions within the program will use whatever we packed _res with.
If you want to reset _res afterwards,we would use res_init(); again.


If we did want to use a loop, it would look something like this:


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

        if((getaddrinfo("myip.opendns.com", NULL, &hint, (struct addrinfo**)&result)) == 0) {
                p = result;
                h = (struct sockaddr_in *)p->ai_addr;

                printf("myip.opendns.com resolves, using 208.67.220.220, to: %s\n", inet_ntoa(h->sin_addr));
                freeaddrinfo((struct addrinfo *)result);

                exit(0);
        }

}





So, combining all of this, we end up with this code:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>


int main(void) {

        struct in_addr server;
        struct addrinfo hint, *result=0, *p=0;
        struct sockaddr_in serverSock, *h=0;

        //For our 'fake' resolution
        struct addrinfo hints, *servinfo;


        if(inet_pton(AF_INET, "208.67.220.220", &server) == 0) {
                exit(1);
        }
        //Have to run getaddrinfo before we do this, atleast once, for some reason.
        memset(&hints, 0, sizeof hints);
        getaddrinfo("google.com", NULL, &hints, &servinfo);
        freeaddrinfo((struct addrinfo*)servinfo);

        serverSock.sin_family = AF_INET;
        serverSock.sin_port = htons(53);
        serverSock.sin_addr = server;

        _res.nscount = 1;
        _res.nsaddr_list[0] = serverSock;

        memset(&hint, 0, sizeof hint);

        int error;
        if((error = getaddrinfo("myip.opendns.com", NULL, &hint, (struct addrinfo**)&result)) == 0) {
                p = result;
                h = (struct sockaddr_in *)p->ai_addr;
                char *heh = inet_ntoa(h->sin_addr);
                printf("Our IP is: %s\n", heh);
                freeaddrinfo((struct addrinfo *)result);
                res_init();
                exit(0);
        } else {
                printf("%s\n", gai_strerror(error));
                exit(1);

        }

}