如何 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 代码启动它们。
但是,这个解决方案有一个缺陷,通常,如果您当前的界面没有互联网连接,
这意味着所有使用此接口的连接最终可能会失败。
我正在使用嵌入在设备 运行 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),再试一次
- 如果成功
- 进行 DNS 查找(通过默认路由进行)
断开连接时:
- 通过断开连接的文件描述符查找IP地址
- 从路由中删除 IP table
- 转到“根据新连接请求”(这取决于您的应用程序逻辑)
这样,您将能够更改新连接的默认网关,但保留现有连接的默认网关。
路由 table 的改变可以通过 Linux shell 命令如 ip route
等来完成。
它们可以通过 system
从 C 启动,例如system( "ip route show" );
您还可以编写具有更复杂逻辑的脚本并从您的 C 代码启动它们。
但是,这个解决方案有一个缺陷,通常,如果您当前的界面没有互联网连接, 这意味着所有使用此接口的连接最终可能会失败。