Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If getaddrinfo fails once, it fails forever (even after network is ready)

I am writing a C application which is run as a systemd service on boot (distro: Arch Linux) and which shall connect to a server. Because the application is run on boot it eventually happens that the network connection is not yet established. This naturally leads to a failure of the first function which requires one, which in my case is getaddrinfo.

So I thought that I would just write a loop which repeatedly calls getaddrinfo until it suceeds once the network is ready. Unfortunately I found that getaddrinfo keeps failing with name or service not known even after the connection was established.

I am able to ping the server by its hostname but getaddrinfo still won't do it. If I stop the application and run it again, everything works fine. If the network connection is already established before the first call, getaddrinfo works fine too.

Apparently, if getaddrinfo failed once because the network was not ready, it will fail forever. It seems not to be aware of the now existing connection. When using the deprecated gethostbyname, the behaviour is the same.

What is the reason for this behaviour? Is there a way to force getaddrinfo to refresh internal variables (if such exist) or similar which might explain why the function still believes that there is no connection? Is there another function which I should call previously in order to check whether the network is ready?

I would like to avoid a delay which waits for some time, expecting the network to be connected afterwards. I would also prefer to check for a connection from within my application and not have a bash script first check for it and then start the application.

like image 828
kassiopeia Avatar asked May 20 '15 20:05

kassiopeia


1 Answers

You can understand the answer by compiling the following test program, and following the instructions below:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    while (1)
    {
        struct addrinfo *res;
        int rc=getaddrinfo(argv[1], "http", NULL, &res);

        printf("getaddrinfo returned %d\n", rc);

        if (rc == 0)
            freeaddrinfo(res);

        sleep(1);
    }
}

Before you run this test program:

  1. Connect to the network.
  2. Rename, temporarily /etc/resolv.conf to /etc/resolv.conf.save.
  3. Start this test program, using a good hostname.
  4. Soon after the test program starts, and starts printing error codes, rename /etc/resolv.conf.save to /etc/resolv.conf.
  5. Observe that the test program is still reporting DNS resolution failures.
  6. If you CTRL-C and restart it, though, the test program will now report valid DNS resolution.

When you disconnect and reconnect from the network, your network stack rewrites and updates /etc/resolv.conf accordingly. This configuration file is needed by the DNS resolver in the C library. The C library reads the DNS configuration from /etc/resolv.conf the first time, and caches it. It doesn't check, with every lookup, if the contents of /etc/resolv.conf have changed.

Finally:

  1. Your homework assignment is to add a call to res_init(), defined in resolv.h, to this test program, read the corresponding man page, and see what happens. That's your answer.
like image 183
Sam Varshavchik Avatar answered Nov 13 '22 16:11

Sam Varshavchik