插座与VPN / UTUN接口也使用的特定以太网接口绑定时如何连接?

发布于 2025-02-10 11:50:39 字数 4293 浏览 1 评论 0原文

我正在尝试编写一个可以使用特定网络接口连接到服务器的函数,以使其始终通过该接口的网关路由。这是在具有一个或多个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 Connection
utun10: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 connection
en1: Wi-Fi connection
utun10: 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

戒ㄋ 2025-02-17 11:50:40

一旦将路由表添加到问题中,您的问题就变得很明显。

它是确定发送数据包的网关的路由表。路由表将发送主机告诉发送到数据包的网关。它通过将目标地址与路由表中的路由进行比较来做到这一点。使用了最特定的(最长的匹配)路线。默认路由是最不特定的(最短匹配)路由,当路由表中没有更特定的路由时,它被用作最后一个度假胜地的路由。

基于您提供的路由表,任何具有目标地址的数据包,1.0.0.0 to 126.255.255.2550.0.0.0.0.0/8 and Code> and Code> 127.0.0.0/8是异常,因为不可用的范围)将匹配0/1路由表条目而不是默认路由(0/0 ),以及带有目标地址的任何数据包,来自128.0.0.0 to 223.255.255.255224.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/1128/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 to 126.255.255.255 (0.0.0.0/8 and 127.0.0.0/8 are exceptions as unusable ranges) will match the 0/1 routing table entry rather than the default route (0/0), and any packet with a destination address from 128.0.0.0 to 223.255.255.255 (224.0.0.0/4 is multicast, and 240.0.0.0/4 is unusable) will match the 128/1 routing table entry rather than the default route (0/0), because the route length of 1 is more specific than the default route length of 0. 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 the 0/1 and 128/1 routes.

To solve your problem, you need to remove the 0/1 and 128/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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文