6

Linux マシン上の Java から多数のネットワーク インターフェイスをリッスンし、そのうちの 1 つが特定のタイプの UDP パケットを受信するかどうかを判断できるという、やや奇妙な要件があります。必要な出力データは、問題のインターフェイスの IP アドレスです。Javaでこれを行う方法はありますか?

ワイルドカード アドレス (new DatagramSocket(port)) をリッスンしても役に立ちません。ブロードキャスト パケットを取得している間は、パケットが通過したインターフェイスのローカル IP アドレスを特定できないためです。特定のインターフェース (new DatagramSocket(port, address)) にバインドされている間にブロードキャストをリッスンすると、パケットがまったく受信されません。このケースは、私がやろうとしていることを示すコード例に値します:

Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
  NetworkInterface ni = (NetworkInterface) interfaces.nextElement();
  Enumeration addresses = ni.getInetAddresses(); 
  while (addresses.hasMoreElements()) { 
    InetAddress address = (InetAddress)addresses.nextElement();
    if (address.isLoopbackAddress() || address instanceof Inet6Address) 
      continue; //Not interested in loopback or ipv6 this time, thanks
    DatagramSocket socket = new DatagramSocket(PORT, address);
     //Try to read the broadcast messages from socket here
  }
}

また、インターフェイスの実際の IP の先頭に基づいて構築されたブロードキャスト アドレスでソケットを初期化し、残りは正しいネットマスクに従って初期化しようとしました。

byte [] mask = { (byte)255, 0, 0, 0 };
byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();
for (int i=0; i < 4; i++) {
  addrBytes[i] |= ((byte)0xFF) ^ mask[i];
}
InetAddress bcastAddr = InetAddress.getByAddress(addrBytes);

これは、DatagramSocket を構築するときに BindException をスローするだけです。

EDIT:ブロードキャストアドレス(例:126.255.255.255)でDatagramSocketのコンストラクターを呼び出すことによるBindException(java.net.BindException:要求されたアドレスを割り当てることができません)は、最新のUbuntu 9.04にのみ付属しています(おそらくUbuntuではなく、カーネルバージョン固有の問題です) . これは Ubuntu 8.10 で機能し、私が扱っている Red Hat リリース (RHEL 4.x) でも機能しました。

Windowsではこれは機能しますが、特定のローカルIPにバインドされている間にパケットを受信しないことは明らかに正しい動作です。Linux (RHEL および Ubuntu) で動作させる必要があります。低レベルの C コードには、Java API では見つからない回避策の setsockopt(SO_BINDTODEVICE) があります。しかし、これは私を楽観的に爆発させるわけではありません:-)

4

5 に答える 5

4

これは、最終的にはIPV6Linuxカーネルの問題でした。通常、私はIPV6を無効にしました。これは、あらゆる種類の頭痛を引き起こすためです。しかし、Ubuntu 9.04では、IPV6を無効にするのは非常に難しいので、私はあきらめました。

特定のインターフェイスからブロードキャストメッセージを聞くために、最初にインターフェイスのIPアドレスの「ブロードキャストバージョン」を作成します。

byte [] mask = { (byte)255, 0, 0, 0 };
byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();
for (int i=0; i < 4; i++) {
  addrBytes[i] |= ((byte)0xFF) ^ mask[i];
}
InetAddress bcastAddr = InetAddress.getByAddress(addrBytes);

確かに、多くのインターフェイスが同じネットワーク部分で始まるIPを持っている場合、これは実際には特定のインターフェイスにバインドされませんが、私にとってはこのソリューションで十分です。

次に、そのアドレス(および目的のポート)を使用してデータグラムソケットを作成すると、機能します。ただし、次のシステムプロパティをJVMに渡す必要があります。

-Djava.net.preferIPv6Addresses=false -Djava.net.preferIPv4Stack=true 

IPV6がどのようにしてブロードキャストのリスニングを中断するのかはわかりませんが、それは可能であり、上記のパラメーターによって修正されます。

于 2009-05-11T12:53:55.150 に答える
1

問題を言い換えると、ブロードキャスト UDP パケットが受信されたインターフェイスを特定する必要があります。

  • ワイルドカード アドレスにバインドすると、ブロードキャストは受信されますが、パケットが受信されたネットワーク アドレスを特定する方法はありません。
  • 特定のインターフェイスにバインドすると、受信しているインターフェイス アドレスがわかりますが、ブロードキャストを受信しなくなります (少なくとも Linux TCP/IP スタックでは)。

他の人が言及したように、RockSawJpcapなどの Java 用のサードパーティの raw ソケット ライブラリがあり、実際のインターフェイスのアドレスを特定するのに役立ちます。

于 2009-05-09T16:35:29.810 に答える
0

私の知る限り、これを行う唯一の方法は、

IP_RECVDSTADDR

ソケットオプション。このオプションは、ワイルドカード アドレスにバインドされたときに、パケットが入ってきたインターフェイスの dst アドレスが利用可能であることを確認することになっています。したがって、ブロードキャストでも動作するはずです。

これは私がインターネットから拾ったCの例です:

着信パケットの UDP 宛先アドレスを取得する方法

私は読んでrecvmsgから、このインターフェースがJavaで利用できるかどうかを調べてみました。

編集:

Java でサポートされている場合、もう 1 つのオプションがあることに気付きました。ソケット オプションが必要な場合もありIP_RECVDSTADDRますが (不明)、recvmsg を使用する代わりに、生のソケットを使用して IP ヘッダーから宛先アドレスを取得できます。

using としてソケットを開くSOCK_RAWと、送信元アドレスと宛先アドレスを含む各メッセージの先頭に完全な IP ヘッダーが表示されます。

Linux の C で raw ソケットで UDP を使用する例を次に示します。

高度な TCP/IP - RAW ソケット プログラムの例

この方法がJavaでも機能しないとしたら、私は驚くでしょう。

編集2

もう1つのアイデア。マルチキャストを使用できない理由、またはマルチキャストではなくブロードキャストを選択した特定の理由はありますか? マルチキャストで理解している限り、マルチキャストグループに参加するときは常に特定のインターフェースにバインドするため、パケットが受信されるインターフェースを常に知ることができます(特に、IPアドレスの1つを介してインターフェースにバインドするIP4の場合)。

于 2009-05-07T18:49:25.480 に答える
0

これが役立つかどうかはわかりませんが、すべてのネットワーク インターフェイスのリストを取得するには次のようにします。

Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();

たぶん、それぞれを個別にバインドできますか?

getNetworkInterfaces() の使用法に関する素晴らしい例をいくつか見つけました。

于 2009-05-07T17:39:27.113 に答える
0

コメントできないため、代わりにこれを回答として追加してください。

それは面白い。なぜあなたがそうするのか興味がありますが

byte[] addrBytes = InetAddress.getByName("126.5.6.7").getAddress();

だけではなく

byte[] addrBytes = {126, 5, 6, 7);

それとも、インターフェースアドレスが String としてあなたに届くのですか?

于 2009-05-12T21:01:24.027 に答える