プログラムがIPアドレスではなくインターフェイスにバインドされるように強制する

プログラムがIPアドレスではなくインターフェイスにバインドされるように強制する

2つのネットワークインターフェイスと2つの異なるインターネット接続を持つコンピュータがあります。複数のルーティングテーブルがあることを知っています。しかし、非常に単純なシナリオがあります。出てくるSSHアプリケーションは常にwlan0を通過する必要があります。それでは、なぜそんなに複雑なことをするのでしょうか。

カールで初めてテストしてみると完璧に動作します。

curl --interface wlan0 ifconfig.me
185.107.XX.XX

curl --interface eth0 ifconfig.me
62.226.XX.XX

したがって、両方のインターフェイスに特別なルーティングルールを設定しなくても、私が望む方法で正確に動作します。 eth0がデフォルトパスです

ip route
default via 192.168.178.1 dev eth0 proto dhcp src 192.168.178.21 metric 202
default via 172.16.1.1 dev wlan0 proto dhcp src 172.16.1.88 metric 303
172.16.1.0/24 dev wlan0 proto dhcp scope link src 172.16.1.88 metric 303
192.168.178.0/24 dev eth0 proto dhcp scope link src 192.168.178.21 metric 202

それではwgetで同じことをしましょう。 Wgetは--bind-addresssshと同じオプションを持っているので、デバッグに適しています-b

wget -O- --bind-address=192.168.178.21 ifconfig.me 2> /dev/null
62.226.XX.XX

省略すると同じ出力が得られます。--bind-address

wget -O- --bind-address=172.16.1.88 ifconfig.me 2> /dev/null

このコマンドは約9(!)分間中断され、sshと同様に何も出力しません。

私はこの事実を知っていますUnixプログラムを特定のネットワークインタフェースにバインドするワイヤー。ただし、タイトルが「Unixプログラムを特定のネットワークインタフェースにバインドする」にもかかわらず、LD_PRELOADを使用するすべてのソリューションはIPアドレスにバインドされます。 SSHはすでにこの機能をサポートしていますが、ここでは役に立ちません。 Firejailはこの問題を解決することができますが、他のトピックで説明したように、まだWi-Fiで動作しないバグがあります。

それでは、複雑なルーティング、netns、またはiptablesルールなしでアプリケーションが特定のインターフェイスを使用するように実際にどのように強制できますか? LD_PRELOADは非常に有望に見えますが、これまでのところ、このコードはバインディングインターフェイスではなくバインディングIPの変更にのみ焦点を当てています。

ベストアンサー1

(Linuxのみ)を探しているようですSO_BINDTODEVICE。からman 7 socket

 SO_BINDTODEVICE
      Bind this socket to a particular device like “eth0”, as specified in the passed
      interface name.  If the name is an empty string or the option length is zero,
      the socket device binding is removed. The passed option is a variable-length
      null-terminated interface name string with the maximum size of IFNAMSIZ.
      If a socket is bound to an interface, only packets received from that particular
      interface are processed by the socket.  Note that this works only for some socket
      types, particularly AF_INET sockets. It is not supported for packet sockets (use
      normal bind(2) there).

以下は、これを使用するサンプルプログラムです。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <net/if.h>

int main(void)
{
    const int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0) {
        perror("socket");
        return EXIT_FAILURE;
    }

    const struct ifreq ifr = {
        .ifr_name = "enp0s3",
    };

    if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
        perror("setsockopt");
        return EXIT_FAILURE;
    }

    const struct sockaddr_in servaddr = {
        .sin_family      = AF_INET,
        .sin_addr.s_addr = inet_addr("142.250.73.196"),
        .sin_port        = htons(80),
    };

    if (connect(sockfd, (const struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) {
        fprintf(stderr, "Connection to the server failed...\n");
        return EXIT_FAILURE;
    }

    // Make an HTTP request to Google
    dprintf(sockfd, "GET / HTTP/1.1\r\n");
    dprintf(sockfd, "HOST: www.google.com\r\n");
    dprintf(sockfd, "\r\n");

    char buffer[16] = {};
    read(sockfd, buffer, sizeof(buffer) - 1);

    printf("Response: '%s'\n", buffer);

    close(sockfd);
    return EXIT_SUCCESS;
}

SO_BINDTODEVICEこのプログラムは、私のネットワークインターフェース()の1つをバインドするために使用されますenp0s3。その後、Google サーバーのいずれかに接続して簡単な HTTP リクエストを実行し、応答の最初の数バイトを印刷します。

実行例は次のとおりです。

$ ./a.out
Response: 'HTTP/1.1 200 OK'

おすすめ記事