web-dev-qa-db-ja.com

Linux:UDPリスニングソケットを特定のインターフェイスにバインドしますか(またはデータグラムが送信されたインターフェイスを見つけます)?

作業中のデーモンがあり、UDPブロードキャストパケットをリッスンし、UDPによっても応答します。パケットが届いたら、パケットが届いたIPアドレス(またはNIC)を知りたい[〜#〜] to [〜#〜]そのIPアドレスを送信元として応答できるようにします。 (多くの苦痛を伴う理由で、私たちのシステムの一部のユーザーは、同じマシン上の2つのNICを同じサブネットに接続したいと考えています。私たちは彼らにそうしないように言いますが、彼らは主張します。それがどれほど醜いのかを思い出す必要はありません。 。)

データグラムを調べて、宛先アドレスまたはデータグラムが入ったインターフェースを直接見つける方法はないようです。多くのグーグルに基づいて、データグラムのターゲットを見つける唯一の方法は、インターフェイスごとに1つのリスニングソケットを用意し、そのソケットをそれぞれのインターフェイスにバインドすることであることがわかりました。

まず、私のリスニングソケットは次のように作成されます。

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

ソケットをバインドするために、私が最初に試したのはこれでした。ここで、nicはインターフェイスの名前に対するchar*です。

// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }

これはまったく効果がなく、サイレントに失敗します。 ASCII name(eg eth0)は、この呼び出しに渡す正しいタイプの名前ですか?なぜサイレントに失敗するのですか?man 7 socketによると、「これに注意してください。一部のソケットタイプ、特にAF_INETソケットでのみ機能します。パケットソケットではサポートされていません(通常のbind(8)を使用してください)。 "「パケットソケット」の意味はわかりませんが、これはAF_INETソケットです。

だから私が次に試したのはこれでした( bind vs SO_BINDTODEVICEソケット に基づく):

struct sockaddr_ll sock_address;
memset(&sock_address, 0, sizeof(sock_address));
sock_address.sll_family = PF_PACKET;
sock_address.sll_protocol = htons(ETH_P_ALL);
sock_address.sll_ifindex = if_nametoindex(nic);
rc=bind(s, (struct sockaddr*) &sock_address, sizeof(sock_address));
if (rc < 0) { ... }

それも失敗しますが、今回はエラーCannot assign requested addressが発生します。ファミリをAF_INETに変更しようとしましたが、同じエラーで失敗します。

1つのオプションが残っています。それは、ソケットを特定のIPアドレスにバインドすることです。インターフェイスアドレスを検索して、それらにバインドできます。残念ながら、これは悪いオプションです。DHCPとホットプラグイーサネットケーブルが原因で、アドレスがその場で変更される可能性があるためです。

このオプションは、ブロードキャストやマルチキャストに関しても悪い場合があります。特定のアドレスにバインドすると、(バインドしたアドレス以外のアドレスへの)ブロードキャストを受信できないのではないかと心配しています。私は実際に今晩遅くにこれをテストし、この質問を更新するつもりです。

質問:

  • UDPリスニングソケットを特にインターフェイスにバインドすることは可能ですか?
  • または、代わりに、(ポーリングではなく)変更が発生した瞬間に、インターフェイスのアドレスが変更されたことをプログラムに通知するメカニズムを採用できますか?
  • 特定のインターフェイスにバインドできる別の種類のリスニングソケット(root権限を持っています)はありますか?それ以外はUDPと同じように動作します(つまり、基本的にUDPを自分で実装する必要があるrawソケット以外)?たとえば、AF_PACKETSOCK_DGRAMと一緒に使用できますか?私はすべてのオプションを理解していません。

誰かが私がこの問題を解決するのを手伝ってくれる?ありがとう!

更新:

特定のIPアドレスへのバインドが正しく機能しません。具体的には、ブロードキャストパケットを受信できません。これは、具体的には受信しようとしているものです。

更新:

IP_PKTINFOrecvmsgを使用して、受信中のパケットに関する詳細情報を取得してみました。受信インターフェイス、受信インターフェイスアドレス、送信者のターゲットアドレス、および送信者のアドレスを取得できます。これは、1つのブロードキャストパケットを受信したときに取得するレポートの例です。

Got message from eth0
Peer address 192.168.115.11
Received from interface eth0
Receiving interface address 10.1.2.47
Desination address 10.1.2.47

これについて本当に奇妙なのは、eth0のアドレスが10.1.2.9であり、ech1のアドレスが10.1.2.47であるということです。では、なぜeth0はeth1が受信する必要のあるパケットを受信して​​いるのでしょうか。これは間違いなく問題です。

Net.ipv4.conf.all.arp_filterを有効にしたことに注意してください。ただし、これは送信パケットにのみ適用されると思います。

7
Timothy Miller

私が見つけた解決策は次のとおりです。まず、ARPとRPの設定を変更する必要があります。 /etc/sysctl.confに、以下を追加して再起動します(これを動的に設定するコマンドもあります)。

net.ipv4.conf.default.arp_filter = 1
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.arp_filter = 1
net.ipv4.conf.all.rp_filter = 2

Eth0からの応答がWAN経由でルーティングできるようにするには、arpフィルターが必要でした。 rpフィルターオプションは、着信パケットをNIC着信パケットに厳密に関連付けるために必要でした(それらを任意のNIC)に関連付ける弱いモデルとは対照的です) =サブネットに一致します)EJPからのコメントにより、この重要なステップに進みました。

その後、SO_BINDTODEVICEが動作を開始しました。 2つのソケットはそれぞれ独自のNICにバインドされていたため、メッセージの送信元のソケットに基づいて、どちらのNICメッセージが送信されたかを判断できました。

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, IF_NAMESIZE);
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(LISTEN_PORT);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
rc=bind(s, (struct sockaddr *)&si_me, sizeof(si_me))

次に、受信データグラムに、送信元アドレスがNIC元のリクエストの送信元のデータグラム)で応答したかったのです。答えは、そのNICのアドレスを調べて、そのアドレスへの発信ソケット(bindを使用)。

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
get_nic_addr(nics, (struct sockaddr *)&sa)
sa.sin_port = 0;
rc = bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr));
sendto(s, ...);

int get_nic_addr(const char *nic, struct sockaddr *sa)
{
    struct ifreq ifr;
    int fd, r;
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) return -1;
    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, nic, IFNAMSIZ);
    r = ioctl(fd, SIOCGIFADDR, &ifr);
    if (r < 0) { ... }
    close(fd);
    *sa = *(struct sockaddr *)&ifr.ifr_addr;
    return 0;
}

(NICのアドレスを毎回検索するのは無駄に思えるかもしれませんが、アドレスが変更されたときに通知を受けるためのコードがはるかに多く、これらのトランザクションは、バッテリーで動作しないシステムでは数秒に1回しか発生しません。)

5
Timothy Miller

プラットフォームでサポートされている場合は、recvmsg()を使用して、送信者が使用する宛先アドレスをIP_RECVDSTADDRオプションで取得できます。これはかなり複雑で、Unix Network Programming、volume I、3rd edition、#22.2、および manで説明されています。 )ページ

編集をやり直すと、TCP/IPの「ウィークエンドシステムモデル」として知られているものに反対しています。基本的に、パケットが到着すると、システムは正しいポートをリッスンする適切なインターフェイスを介してパケットを配信することを選択できます。これは、TCP/IPRFCのどこかで説明されています。

3
user207421

不正な値をsetsockoptに渡しています。

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));

マニュアルページにはSO_BIND_TO_DEVICE

渡されるオプションは、IFNAMSIZの最大サイズを持つ可変長nullで終了するインターフェース名ストリングです。

strlenには終了nullは含まれません。あなたが試すことができます:

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));

dnsmasqはこれが正しく機能し、使用します

setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)
2
Ben Voigt

あなたは間違った角度から問題に取り組んでいるのではないかと思います。一般的な場合、インターフェイスは同時に複数のIPアドレスを持つことができるため、接続しているインターフェイスを知っていてもIPアドレスはわかりません(一般的な場合)。

代わりに、使用されているインターフェイスについてあまり心配する必要はなく、使用されているIPアドレスに焦点を合わせてください。まず、getifaddrs()を使用してすべてのIPアドレスのリストを取得し、各アドレスに1つのソケットをバインドします。

select()を使用して、すべてのソケットでパケットを一度に待機できます。パケットを受信したソケットを使用して、パケットの宛先アドレスを判別します。また、パケットを受信したソケットを使用して、送信元アドレスを適切に自動的に設定する応答を送信できます。

新しいIPアドレスを確認する必要がある場合もありますが、DHCPによって新しいアドレスが提供されると、ソケットでエラーが発生します。

0
OfNothing

これが古いスレッドであることは知っていますが、ここで探していた答えが見つかりませんでした。

ここで見つけた情報を使用して、別のインターフェイス(ブロードキャスト、IGMPなどを含む)からのパケットがソケットに表示されないように、rawソケットをインターフェイスにバインドしました。

https://cs.wikipedia.org/wiki/Raw_socket

BindRawSocketToInterface()関数の機能は私が必要としていたものでした。

これが他の誰かに役立つことを願っています。乾杯!

0
JimHab