Как выбрать интерфейс, используемый для выполнения поиска имени хоста

#c #linux #dns #getaddrinfo

#c #linux #dns #getaddrinfo

Вопрос:

Я работаю в приложении, встроенном в устройство под управлением Linux и BusyBox. Аппаратное обеспечение имеет 2 интерфейса связи: Wi-Fi и 3G.

При каждом подключении приложение должно сначала попытаться подключиться с помощью Wi-Fi, и, если это не удается, приложение повторяет попытку с использованием 3G.

Я заставляю соединение использовать выбранный интерфейс, привязывая его следующим образом:

 #include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <net/if.h>

static void resolveDns(const char *hostname, struct addrinfo **destInfo)
{
    int err;
    struct addrinfo hints;

    memset(amp;hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((err = getaddrinfo(hostname, "80", amp;hints, destInfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %sn", gai_strerror(err));
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in *addr = (struct sockaddr_in *)((*destInfo)->ai_addr);
    printf("Destination IP: %sn", inet_ntoa(addr->sin_addr));
}

static void getComInterface(const char *iface, struct ifreq *ifr)
{
    ifr->ifr_addr.sa_family = AF_INET;
    strcpy(ifr->ifr_name, iface);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    int err = ioctl(sock, SIOCGIFADDR, ifr);
    close(sock);

    if (err) {
        fprintf(stderr, "ioctl error: %dn", err);
        exit(EXIT_FAILURE);
    }

    printf("Interface IP: %sn", inet_ntoa(((struct sockaddr_in *) amp;ifr->ifr_addr)->sin_addr));
}

int main()
{
    int err;

    struct ifreq ifr;
    getComInterface("wlan0", amp;ifr);

    struct addrinfo *destInfo;
    resolveDns("www.google.com", amp;destInfo);

    int s = socket(AF_INET, SOCK_STREAM, 0);
    err = bind(s, amp;ifr.ifr_addr, sizeof(ifr.ifr_addr));
    if (err) {
        fprintf(stderr, "bind error = %d, %dn", err, errno);
        exit(EXIT_FAILURE);
    }

    err = connect(s, destInfo->ai_addr, destInfo->ai_addrlen);
    if (err) {
        fprintf(stderr, "connect error = %d, %d n", err, errno);
        exit(EXIT_FAILURE);
    }

    printf("Ok!n");

    freeaddrinfo(destInfo);
    close(s);
    return EXIT_SUCCESS;
}

  

Но это не решает проблему при поиске DNS.

Есть ли способ заставить getaddrinfo использовать выбранный интерфейс? Или, что еще лучше, есть ли способ заставить все соединения использовать выбранный интерфейс без отключения другого?

PS: Если вы знаете, как сделать это в более сложном SO, например, в Ubuntu, пожалуйста, поделитесь своим решением.

Спасибо

Комментарии:

1. Я собираюсь высунуть шею и предположить, что то, что вы хотите, невозможно с функциями разрешения адресов в стандартных библиотеках Linux. Вам придется самостоятельно закодировать операцию DNS и явно привязать интерфейс к сокету в вашем коде. И даже тогда для некоторых вариантов Linux потребуются повышенные привилегии для этого. Однако я готов доказать, что ошибаюсь.

Ответ №1:

Боюсь, что нет способа сделать это только через стандартную библиотеку C, т. Е. Вам нужно изменить шлюз по умолчанию и установить шлюз для каждого соединения. Пожалуйста, рассмотрите приведенный ниже псевдокод:

  • Запуск системы:

    • Установите соединение через wifi
    • Установить соединение через 3G
    • Настройте интерфейс wifi в качестве шлюза по умолчанию
  • При запросе нового подключения:

    • Выполните поиск DNS (выполняется по маршруту по умолчанию)
      • Если это удастся
        • Сохраните IP-адрес вместе с файловым дескриптором
        • Настройте маршрутизацию на этот IP-адрес через интерфейс, который является текущим шлюзом
        • Откройте сокет на IP-адрес, трафик будет проходить через данный интерфейс
      • Если нет
        • Измените шлюз по умолчанию на другой интерфейс (3G) и повторите попытку
  • При отключении:

    • Найдите IP-адрес по файловому дескриптору отключенного соединения
    • Удалите IP-адрес из таблицы маршрутизации
    • Перейдите к «При новом запросе на подключение» (это зависит от логики вашего приложения)

Таким образом, вы сможете изменить шлюз по умолчанию для новых подключений, но сохранить для существующих.

Изменение таблицы маршрутизации может быть выполнено с помощью команд оболочки Linux, таких как ip route и т.д. они могут быть запущены с C через system , например system( "ip route show" ); , вы также можете писать сценарии с более сложной логикой и запускать их из своего кода на C.

Однако у этого решения есть один недостаток, обычно, если ваш текущий интерфейс не имеет подключения к Интернету, это означает, что все соединения, использующие этот интерфейс, могут в конечном итоге завершиться неудачей.

Комментарии:

1. Это кажется хорошей идеей, теперь я должен изучить, как установить правила маршрута. И, вероятно, мне больше не понадобится bind сокет. Анализируя исходный код iproute2, я обнаружил, что могу использовать rtnetlink_socket = socket(AF_NETLINK, socket_type, NETLINK_ROUTE); для изменения таблиц маршрутизации без необходимости вызова system или execv . Я еще не решил, какой из них реализовать.

2. @MatheusRossiSaciotto, к сожалению, привязка к одному «клиентскому» сокету не поможет. Конечно, вы можете реализовать маршрутизацию самостоятельно, но я бы посоветовал использовать существующие инструменты, доступные в busybox, по крайней мере, для начала.