Если getaddrinfo дает сбой один раз, он завершается с ошибкой навсегда (даже после того, как сеть будет готова)

Я пишу приложение C, которое запускается как служба systemd при загрузке (дистрибутив : Arch Linux) и который должен подключаться к серверу. Поскольку приложение запускается при загрузке, в конечном итоге сетевое соединение еще не установлено. Это, естественно, приводит к отказу первой функции, для которой она требуется, в моем случае это getaddrinfo.

Поэтому я подумал, что просто напишу цикл, который многократно вызывает getaddrinfo до тех пор, пока он не завершится успешно, как только сеть будет готова. К сожалению, я обнаружил, что getaddrinfo продолжает сбой с name or service not known даже после того, как соединение было установлено.

Я могу пропинговать сервер по его имени хоста, но getaddrinfo по-прежнему этого не делает. Если я останавливаю приложение и запускаю его снова, все работает нормально. Если сетевое соединение уже установлено до первого звонка, getaddrinfo тоже работает нормально.

Судя по всему, если getaddrinfo один раз не удалось, потому что сеть не была готова, то она будет давать сбой навсегда. Кажется, он не знает о существующей связи. При использовании устаревшего gethostbyname поведение такое же.

В чем причина такого поведения? Есть ли способ заставить getaddrinfo обновить внутренние переменные (если таковые существуют) или что-то подобное, что могло бы объяснить, почему функция все еще считает, что связи нет? Есть ли другая функция, которую я должен вызвать ранее, чтобы проверить, готова ли сеть?

Я хотел бы избежать задержки, которая ждет некоторое время, ожидая, что сеть будет подключена после этого. Я также предпочел бы проверять наличие соединения из своего приложения, а не сначала проверять его с помощью сценария bash, а затем запускать приложение.


person kassiopeia    schedule 20.05.2015    source источник


Ответы (1)


Вы можете понять ответ, скомпилировав следующую тестовую программу и следуя приведенным ниже инструкциям:

#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);
    }
}

Перед запуском этой тестовой программы:

  1. Подключиться к сети.
  2. Переименуйте временно /etc/resolv.conf в /etc/resolv.conf.save.
  3. Запустите эту тестовую программу, используя хорошее имя хоста.
  4. Вскоре после того, как тестовая программа запустится и начнет печатать коды ошибок, переименуйте /etc/resolv.conf.save в /etc/resolv.conf.
  5. Обратите внимание, что тестовая программа по-прежнему сообщает об ошибках разрешения DNS.
  6. Однако, если вы нажмете CTRL-C и перезапустите его, тестовая программа теперь сообщит о действительном разрешении DNS.

Когда вы отключаетесь от сети и снова подключаетесь к ней, ваш сетевой стек перезаписывает и обновляет /etc/resolv.conf соответственно. Этот файл конфигурации необходим преобразователю DNS в библиотеке C. Библиотека C считывает конфигурацию DNS из /etc/resolv.conf в первый раз и кэширует ее. Он не проверяет при каждом поиске, изменилось ли содержимое /etc/resolv.conf.

Ну наконец то:

  1. Ваше домашнее задание — добавить вызов res_init(), определенный в resolv.h, в эту тестовую программу, прочитать соответствующую справочную страницу и посмотреть, что произойдет. Это твой ответ.
person Sam Varshavchik    schedule 20.05.2015
comment
Под этим я имел в виду res_init() внутри цикла. - person Sam Varshavchik; 21.05.2015
comment
Это действительно иногда работает (но довольно редко). Я предполагаю, что это работает, если сетевое соединение уже установлено, но resolv.conf еще не было обновлено при запуске приложения. После обновления resolv.conf вызов res_init в цикле загружает новую конфигурацию, а затем getaddrinfo завершается успешно. Но если сетевое соединение не готово при запуске приложения, оно все равно продолжает давать сбой, - person kassiopeia; 21.05.2015
comment
Моя пошаговая демонстрация включает в себя запуск приложения, когда разрешение DNS вообще не настроено, что может быть в случае отсутствия сетевых подключений. Вы пытались запустить демонстрационный код вызовом rs_init() без подключения к сети, а затем подключиться к сети? Этот небольшой демонстрационный код довольно легко протестировать. Не нужно ничего предполагать. - person Sam Varshavchik; 22.05.2015
comment
Когда я впервые использовал вашу тестовую программу, я только переименовал resolv.conf, но не пытался отключить и снова подключить сеть. Теперь я попробовал это, и это работает. В своем приложении я сразу же попытался использовать res_ninit, так как справочные страницы сказали мне, что другая функция устарела. После возврата к res_init это тоже работает нормально. Вероятно, я что-то упустил из виду при инициализации структуры __res_state. Я еще раз пройдусь по этому вопросу и посмотрю, как заставить res_ninit вести себя так же, как res_init. - person kassiopeia; 22.05.2015