如何 select 用于执行主机名查找的接口

How to select the interface used to perform a hostname lookup

我正在使用嵌入在设备 运行 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(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

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

    struct sockaddr_in *addr = (struct sockaddr_in *)((*destInfo)->ai_addr);
    printf("Destination IP: %s\n", 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: %d\n", err);
        exit(EXIT_FAILURE);
    }

    printf("Interface IP: %s\n", inet_ntoa(((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr));
}

int main()
{
    int err;

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

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

    int s = socket(AF_INET, SOCK_STREAM, 0);
    err = bind(s, &ifr.ifr_addr, sizeof(ifr.ifr_addr));
    if (err) {
        fprintf(stderr, "bind error = %d, %d\n", 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 使用选定的接口? 或者,更好的是,有没有办法强制所有连接使用选定的接口而不断开其他连接?

P.S.: 如果您知道如何在更复杂的 SO 中执行此操作,例如 Ubuntu,请分享您的解决方案。

谢谢

恐怕只能通过标准C库来做, 即您需要更改默认网关,并为每个连接设置网关。 请考虑以下伪代码:

  • 系统启动:

    • 通过wifi建立连接
    • 通过3G建立连接
    • 配置wifi接口为默认网关
  • 关于新的连接请求:

    • 进行 DNS 查找(通过默认路由进行)
      • 如果成功
        • 将 IP 地址与文件描述符一起保存
        • 通过作为当前网关的接口配置到此 IP 的路由
        • 打开到 IP 地址的套接字,流量将通过给定的接口
      • 如果没有
        • 将默认网关换成其他接口(3G),再试一次
  • 断开连接时:

    • 通过断开连接的文件描述符查找IP地址
    • 从路由中删除 IP table
    • 转到“根据新连接请求”(这取决于您的应用程序逻辑)

这样,您将能够更改新连接的默认网关,但保留现有连接的默认网关。

路由 table 的改变可以通过 Linux shell 命令如 ip route 等来完成。 它们可以通过 system 从 C 启动,例如system( "ip route show" ); 您还可以编写具有更复杂逻辑的脚本并从您的 C 代码启动它们。

但是,这个解决方案有一个缺陷,通常,如果您当前的界面没有互联网连接, 这意味着所有使用此接口的连接最终可能会失败。