3

I want to query a specific server, and get the result at the same way as we get it via getaddrinfo. I want to get a addrinfo struct, so I can have the ip, port and a pointer to the next result.

I'm using the code below, which query the server I want, and gets the results. But each result is at another struct and they don't point one another (not in a list).

This is the code:

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

Is it possible to get the results the way I want? something similar to addrinfo struct?

Edit: I can see that I get three answers ns_msg_count(handle, ns_s_an) = 3 and to access each answer I should call ns_parserr(&handle, ns_s_an, answer_index, &rr) But as I said, I want to get those answers as a list just like I get them by calling getaddrinfo.

fluter
  • 13,238
  • 8
  • 62
  • 100
Witterquick
  • 6,048
  • 3
  • 26
  • 50
  • Have you actually tried anything? From your description it looks as if you know what you're doing, so where exactly is your problem? Look up which of the parameters is the index into the result set (I assume it's the `0`), walk the results, and create a list of `addrinfo`s as you go. – Phillip Apr 06 '17 at 11:26
  • I know I can get in_addr, but how can I get addrinfos from the result? – Witterquick Apr 06 '17 at 12:06
  • Or you mean to just call getaddrinfo for each ip.. – Witterquick Apr 06 '17 at 12:26

1 Answers1

4

getaddrinfo returns more than just ip addresses, it will also resolve service names to port number, and it could support different protocols, majorly tcp and udp. So you will need to resolve service name by calling getservbyname_r and manually construct the result addrinfo for each combination of ip, port and protocols. Here is a simple version which parse ip and port, but it only returns addrinfo for TCP. You could extended it to including UDP easily.

Another feature is that getaddrinfo also supports IPV6, in which case you will need to call res_nquery with T_AAAA instead of T_A to resolve IPV6 addresses.

Here is an example, note it returns a linked list of struct addrinfo just like getaddrinfo does, so the result should be free with freeaddrinfo when you are done.

static int my_getaddrinfo(const char *dns_server,
        const char *node, const char *service,
        const struct addrinfo *hints, struct addrinfo **res) {

    int ret;

    // get dns server sockaddr
    struct addrinfo hint = {0};
    struct addrinfo *ai = NULL;

    hint.ai_family = AF_INET;
    hint.ai_socktype = SOCK_DGRAM;
    hint.ai_protocol = IPPROTO_UDP;
    ret = getaddrinfo(dns_server, "domain", &hint, &ai);
    if (ret != 0) {
        puts("getaddrinfo dns error");
        return 1;
    }

    if (!ai) {
        printf("getaddrinfo returned no result\n");
        return 1;
    }

    freeaddrinfo(ai);

    int port = 0;
    // get service port
    if (service) {
        struct servent srv, *sres;
        char buf[1024];
        ret = getservbyname_r(service, NULL, &srv, buf, sizeof buf, &sres);
        if (ret != 0) {
            printf("getservbyname error\n");
            return 1;
        }
        port = sres->s_port;
    }

    struct __res_state p = {0};
    res_state state = &p;
    unsigned char ans[NS_MAXMSG];
    ns_msg msg;
    ns_rr rr;
    char line[1024];
    int len ;

    ret = res_ninit(state);
    if (ret != 0) {
        printf("res_ninit error\n");
        return 1;
    }

    state->nscount = 1;
    memset(state->nsaddr_list, 0, sizeof state->nsaddr_list);
    memcpy(state->nsaddr_list, ai->ai_addr, sizeof state->nsaddr_list[0]);

    ret = res_nquery(state, node, C_IN, T_A, ans, sizeof ans);
    if (ret < 0) {
        printf("res_nquery error\n");
        return 1;
    }

    len = ret;
    ret = ns_initparse(ans, len, &msg);
    if (ret != 0) {
        printf("ns_initparse error\n");
        return 1;
    }

    len = ns_msg_count(msg, ns_s_an);

    if (len == 0) {
        printf("no address found\n");
        return 0;
    }

    struct addrinfo *head = NULL;
    struct addrinfo *cur = NULL;
    struct addrinfo *prev = NULL;
    struct sockaddr_in *sin;

    for (int i = 0; i < len; i++) {
        ret = ns_parserr(&msg, ns_s_an, i, &rr);
        if (ret != 0) {
            printf("ns_parserr error\n");
        }

        if (ns_rr_rdlen(rr) != NS_INADDRSZ) {
            continue;
        }

        cur = malloc(sizeof *cur + sizeof(struct sockaddr_in));
        memset(cur, 0, sizeof *cur);
        cur->ai_family = AF_INET;
        cur->ai_socktype = SOCK_STREAM;
        cur->ai_protocol = IPPROTO_TCP;
        cur->ai_addrlen = sizeof(struct sockaddr_in);
        cur->ai_addr = (void*)(cur + 1);
        cur->ai_canonname = NULL;
        cur->ai_next = NULL;
        sin = (struct sockaddr_in*)(cur->ai_addr);
        sin->sin_family = cur->ai_family;
        sin->sin_port = port;
        memcpy(&sin->sin_addr, ns_rr_rdata(rr), sizeof sin->sin_addr);
        if (prev)
            prev->ai_next = cur;
        if (head == NULL)
            head = cur;
        prev = cur;
    }
    *res = head;

    return 0;
}

int main(int argc, char *argv[])
{
    const char *node = "bing.com";
    struct addrinfo *res = NULL;
    if (argc == 2)
        node = argv[1];
    int ret = my_getaddrinfo("8.8.8.8", node, "http", NULL, &res);
    if (ret != 0) {
        puts("getaddrinfo error");
        return 1;
    }

    // do stuff with res
    struct addrinfo *rp;
    struct sockaddr_in *sin;
    char p[1024];
    for (rp = res; rp != NULL; rp = rp->ai_next) {
        sin = (struct sockaddr_in*)rp->ai_addr;
        const char *s = inet_ntop(rp->ai_family,
                &sin->sin_addr, p, sizeof p);
        printf("Got %s: %d\n", s, ntohs(sin->sin_port));
    }
    freeaddrinfo(res);
    return 0;
}

example:

$ ./a.out bing.com
Got 204.79.197.200: 80
Got 13.107.21.200: 80
$ ./a.out google.com
Got 172.217.24.14: 80
fluter
  • 13,238
  • 8
  • 62
  • 100
  • Looks great, but 1:u r using memcpy(state->nsaddr_list, ai->ai_addr, sizeof state->nsaddr_list[0]); after free(ai) 2: Im getting an error for getservbyname_r (implicit declaration of function getservbyname_r is invalid in c99), any other way I can get the port? – Witterquick Apr 08 '17 at 10:42
  • @Roee84 you could use 'getservbyname', if _r is not available, but it will be non-reentrent, I used the reentrent version. – fluter Apr 08 '17 at 10:45
  • @Roee84 Did you try include `#include `? that's where `getservbyname_r` was defined, it is actually a posix standard function, so include the right header should do it, it is not a C standard function so it has nothing to do with which version of C you are using. – fluter Apr 08 '17 at 11:29
  • Tried it without success, and also tried struct servent* se = getservbyname(node, NULL); port = se->s_port; but the port is NULL. But it's less important. Anyway, your answer is great (and accepted as the correct one), but final question: is it wrong to use my original code (at the question), and then for every answer - call to getaddrinfo(ip, service, hints, res), and then put the result in a linked list (like you did..) ? – Witterquick Apr 09 '17 at 13:35
  • @Roee84 yes you could do that, but each result needs to be freed separately, so you will need to do your own book keeping on which node should call freeaddrinfo. – fluter Apr 09 '17 at 14:16
  • Why? I'll change it so each result ai_next would point to the next one (or NULL at the end), so at the end I'll just need to call freeaddrinfo for the "first" result. Sounds right? – Witterquick Apr 09 '17 at 14:18
  • Thanks! Great answer – Witterquick Apr 09 '17 at 15:15