插座与VPN / UTUN接口也使用的特定以太网接口绑定时如何连接?
我正在尝试编写一个可以使用特定网络接口连接到服务器的函数,以使其始终通过该接口的网关路由。这是在具有一个或多个VPN连接的MACOS系统上。
这是我写过的概念验证测试功能:
void connectionTest(const char *hostname, int portNumber, const char *interface) {
struct hostent *serverHostname = gethostbyname(hostname);
if (serverHostname == NULL) {
printf("error: no such host\n");
return;
}
int socketDesc = socket(AF_INET, SOCK_STREAM, 0);
int interfaceIndex = if_nametoindex(interface);
if (interfaceIndex == 0) {
printf("Error: no such interface\n");
close(socketDesc);
return;
}
// Set the socket to specifically use the specified interface:
setsockopt(socketDesc, IPPROTO_IP, IP_BOUND_IF, &interfaceIndex, sizeof(interfaceIndex));
struct sockaddr_in servAddr;
bzero((char *)&servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
bcopy((char *)serverHostname->h_addr, (char *)&servAddr.sin_addr.s_addr, serverHostname->h_length);
servAddr.sin_port = htons(portNumber);
if (connect(socketDesc, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) {
printf("connect failed, errno: %d", errno);
close(socketDesc);
return;
}
printf("connection succeeded\n");
close(socketDesc);
}
只要接口是VPN创建的utun
接口之一,或者是VPN使用的不是。但是,如果我尝试使用VPN使用的物理界面,则该函数将在ERRNO 51:网络中失败。
对于更具体的示例,请考虑具有以下网络接口的系统:
en0
:以太网连接en1
:Wi-Fi Connectionutun10
:VPN连接1,通过en0
连接 utun11
:VPN连接2,也通过en0
连接,
如果我用类似的功能调用我的功能:
connectionTest("api.ipify.org", 80, "en1");
connectionTest("api.ipify.org", 80, "utun10");
connectionTest("api.ipify.org", 80, "utun11");
...它将成功。但是,这就是产生“网络无法实现的” 错误的原因:
connectionTest("api.ipify.org", 80, "en0");
在en0
的情况下,是否有某种方法可以使功能工作? (最好是不仅仅为此连接更改系统的路由表吗?)
编辑:
看起来系统不知道如何通过en0
在VPN启动时如何路由数据包,除非它具有非非代码-Default路由en0
。
我尝试使用Route
命令检查表中的哪个路由将用于特定接口,并且我得到以下内容:
$ route get -ifscope en0 1.1.1.1
route: writing to routing socket: not in table
仅-IFSCOPE EN0
会产生该错误。但是,路由表表示en0
有默认路由。这是仅连接以太网和VPN时的路由表(因此没有Wi-Fi或第二个VPN):
$ netstat -rn
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
0/1 10.16.0.1 UGSc 165 0 utun10
default 192.168.20.1 UGSc 0 0 en0
10.16/16 10.16.0.8 UGSc 3 0 utun10
10.16.0.8 10.16.0.8 UH 2 0 utun10
127 127.0.0.1 UCS 0 0 lo0
127.0.0.1 127.0.0.1 UH 7 7108160 lo0
128.0/1 10.16.0.1 UGSc 40 0 utun10
169.254 link#8 UCS 1 0 en0 !
192.168.20 link#8 UCS 9 0 en0 !
192.168.20.1/32 link#8 UCS 2 0 en0 !
224.0.0/4 link#22 UmCS 0 0 utun10
224.0.0/4 link#8 UmCSI 1 0 en0 !
224.0.0.251 1:0:5e:0:0:fb UHmLWI 0 0 en0
255.255.255.255/32 link#22 UCS 0 0 utun10
255.255.255.255/32 link#8 UCSI 0 0 en0 !
显然,列出了en0
指向其网关的默认路由,192.168.20.2
。为什么没有路由数据包?如果我为1.1.1.1/32
甚至1/8 创建静态路由。但是,只要
en0
只有默认路由,它就无法正常工作。就像默认路由以某种方式被禁用。
编辑2:
如果我使用以下方式向表添加新路由:
$ route add -ifscope en0 0/0 192.168.20.1
以便路由表现在包括以下条目:
Destination Gateway Flags Refs Use Netif Expire
default 192.168.20.1 UGScI 1 0 en0
与上述所有条目一起,因此现在有二默认条目,然后是连接作品。为什么有必要有特定于接口的默认路由才能工作?
I'm trying to write a function that can connect to a server using a specific network interface so that it's consistently routed through that interface's gateway. This is on a macOS system that has one or more VPN connections.
Here's a proof-of-concept test function I've written:
void connectionTest(const char *hostname, int portNumber, const char *interface) {
struct hostent *serverHostname = gethostbyname(hostname);
if (serverHostname == NULL) {
printf("error: no such host\n");
return;
}
int socketDesc = socket(AF_INET, SOCK_STREAM, 0);
int interfaceIndex = if_nametoindex(interface);
if (interfaceIndex == 0) {
printf("Error: no such interface\n");
close(socketDesc);
return;
}
// Set the socket to specifically use the specified interface:
setsockopt(socketDesc, IPPROTO_IP, IP_BOUND_IF, &interfaceIndex, sizeof(interfaceIndex));
struct sockaddr_in servAddr;
bzero((char *)&servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
bcopy((char *)serverHostname->h_addr, (char *)&servAddr.sin_addr.s_addr, serverHostname->h_length);
servAddr.sin_port = htons(portNumber);
if (connect(socketDesc, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) {
printf("connect failed, errno: %d", errno);
close(socketDesc);
return;
}
printf("connection succeeded\n");
close(socketDesc);
}
This function will successfully connect so long as the interface is one of the utun
interfaces created by the VPNs, or a physical interface that is not used by the VPNs. But if I try to use the physical interface that is used by the VPNs, the function fails with errno 51: Network is unreachable.
For a more specific example, consider a system with the following network interfaces:
en0
: Ethernet connectionen1
: Wi-Fi connectionutun10
: VPN connection 1, connected via en0
utun11
: VPN connection 2, also connected via en0
If I call my function with something like:
connectionTest("api.ipify.org", 80, "en1");
connectionTest("api.ipify.org", 80, "utun10");
connectionTest("api.ipify.org", 80, "utun11");
... it will succeed. However, this is what produces the "network unreachable" error:
connectionTest("api.ipify.org", 80, "en0");
Is there some way to have the function work in the case of en0
? (Preferably without changing the system's routing table just for this one connection?)
Edit:
It looks like the system doesn't know how to route packets through en0
when the VPN is up, unless it has a non-default route for en0
.
I tried using the route
command to check which route in the table would be used for a specific interface, and I get the following:
$ route get -ifscope en0 1.1.1.1
route: writing to routing socket: not in table
Only -ifscope en0
produces that error. However, the route table indicates there is a default route for en0
. Here is the routing table when only ethernet and the VPN are connected (so no Wi-Fi or second VPN):
$ netstat -rn
Routing tables
Internet:
Destination Gateway Flags Refs Use Netif Expire
0/1 10.16.0.1 UGSc 165 0 utun10
default 192.168.20.1 UGSc 0 0 en0
10.16/16 10.16.0.8 UGSc 3 0 utun10
10.16.0.8 10.16.0.8 UH 2 0 utun10
127 127.0.0.1 UCS 0 0 lo0
127.0.0.1 127.0.0.1 UH 7 7108160 lo0
128.0/1 10.16.0.1 UGSc 40 0 utun10
169.254 link#8 UCS 1 0 en0 !
192.168.20 link#8 UCS 9 0 en0 !
192.168.20.1/32 link#8 UCS 2 0 en0 !
224.0.0/4 link#22 UmCS 0 0 utun10
224.0.0/4 link#8 UmCSI 1 0 en0 !
224.0.0.251 1:0:5e:0:0:fb UHmLWI 0 0 en0
255.255.255.255/32 link#22 UCS 0 0 utun10
255.255.255.255/32 link#8 UCSI 0 0 en0 !
There's clearly a default route listed for en0
pointing to its gateway, 192.168.20.1
. Why isn't the packet being routed? If I create a static route for 1.1.1.1/32
or even 1/8
it will work. But so long as en0
only has a default route, it won't work. It's like the default route has been disabled somehow.
Edit 2:
If I add a new route to the table using:
$ route add -ifscope en0 0/0 192.168.20.1
so that the routing table now includes the following entry:
Destination Gateway Flags Refs Use Netif Expire
default 192.168.20.1 UGScI 1 0 en0
alongside all of the above entries, so there are now two default entries, then the connection works. Why is it necessary for there to be an interface-specific default route in order for this to work?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
一旦将路由表添加到问题中,您的问题就变得很明显。
它是确定发送数据包的网关的路由表。路由表将发送主机告诉发送到数据包的网关。它通过将目标地址与路由表中的路由进行比较来做到这一点。使用了最特定的(最长的匹配)路线。默认路由是最不特定的(最短匹配)路由,当路由表中没有更特定的路由时,它被用作最后一个度假胜地的路由。
基于您提供的路由表,任何具有目标地址的数据包,
1.0.0.0
to126.255.255.255
(0.0.0.0.0.0/8
and Code> and Code>127.0.0.0/8
是异常,因为不可用的范围)将匹配0/1
路由表条目而不是默认路由(0/0
),以及带有目标地址的任何数据包,来自128.0.0.0
to223.255.255.255
(224.0.0.0.0/4
是多播和240.0.0.0/4
无法使用)将与128/1
路由表条目匹配,而不是默认路由(0/0
),因为路由1
的长度比0
的默认路由长度更具体。这意味着将发送到这些范围内的地址的任何数据包(组合,所有代表其他网络的地址)将发送到由路由表条目的路由表条目引用的网关(10.16.0.1
) > 0/1 和128/1
路由。要解决问题,您需要删除
0/1
和128/1
路由表条目,然后用一个或多个条目替换为仅限于网络的条目隧道可以到达。因此,将不匹配隧道路由的条目或其他更具体的路由表条目将使用默认路由。Once you added the routing table to your question, your problem became obvious.
It is the routing table that determines to which gateway a packet is sent. The routing table tells the sending host to which gateway the packet is sent. It does that by comparing the destination address to the routes in the routing table. The most-specific (longest match) route is used. A default route is the least-specific (shortest match) route, and it is used as the route of last resort when there are no more-specific routes in the routing table.
Based on the routing table you provided, any packet with a destination address from
1.0.0.0
to126.255.255.255
(0.0.0.0/8
and127.0.0.0/8
are exceptions as unusable ranges) will match the0/1
routing table entry rather than the default route (0/0
), and any packet with a destination address from128.0.0.0
to223.255.255.255
(224.0.0.0/4
is multicast, and240.0.0.0/4
is unusable) will match the128/1
routing table entry rather than the default route (0/0
), because the route length of1
is more specific than the default route length of0
. That means any packets destined to an address in those ranges (combined, all addresses destined for a different network) will be sent to the gateway (10.16.0.1
) referenced by the routing table entries for the0/1
and128/1
routes.To solve your problem, you need to remove the
0/1
and128/1
routing table entries and replace them with one or more entries that are restricted to the networks which the tunnel can reach. With that, the entries not matching the tunnel route(s) or other more specific routing table entries will use the default route.