Linux 上的 Java:监听绑定本地地址上的广播消息

发布于 2024-07-18 18:51:42 字数 1897 浏览 9 评论 0原文

我有一个有点奇怪的要求,即能够在 Linux 机器上从 Java 监听多个网络接口,并确定其中一个接口是否接收某种类型的 UDP 数据包。 我需要的输出数据是相关接口的 IP 地址。 在Java中有什么办法可以做到这一点吗?

侦听通配符地址(new DatagramSocket(port))没有帮助,因为虽然我确实收到了广播数据包,但我无法确定它们所通过的接口的本地 IP 地址。 绑定到某个接口(new DatagramSocket(端口,地址))时监听广播根本不会收到数据包。 这种情况值得一个代码示例来展示我正在尝试做的事情:

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。

编辑:使用广播地址(例如 126.255.255.255)调用 DatagramSocket 的构造函数时出现的 BindException(java.net.BindException:无法分配请求的地址)仅随最新的 Ubuntu 9.04 一起提供(可能不是 Ubuntu,但不过,内核版本特定问题)。 对于 Ubuntu 8.10,以及我正在处理的 Red Hat 版本 (RHEL 4.x),这都有效。

显然,绑定到某个本地 IP 时未收到数据包是正确的行为,尽管在​​ Windows 中这是可行的。 我需要让它在 Linux(RHEL 和 Ubuntu)上运行。 对于低级C代码,有一个解决方法setsockopt(SO_BINDTODEVICE),我在Java-API中找不到它。 并不完全让我充满乐观:-)

I have a somewhat weird requirement to be able to listen to a number of network interfaces from Java on a Linux machine and determine if one of them receives UDP packets of a certain type. The output data which I need is the IP address of the interface in question. Is there any way to do this in Java?

Listening on the wildcard address (new DatagramSocket(port)) doesn't help because while I do get the broadcast packets, I can't determine the local IP address of the interface they came through. Listening to broadcasts while being bound to a certain interface (new DatagramSocket(port, address)) doesn't receive the packets at all. This case deserves a code example which shows what I'm trying to do:

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
  }
}

I also tried to to initialize the socket with the broadcast address constructed based on the beginning of the real IP of the interface and the rest according to the correct netmask:

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);

That just throws a BindException when constructing the DatagramSocket.

EDIT: BindException (java.net.BindException: Cannot assign requested address) from calling DatagramSocket's constructor with a broadcast-address (e.g. 126.255.255.255) only comes with the latest Ubuntu 9.04 (probably not Ubuntu, but kernel-version specific issue though). With Ubuntu 8.10 this worked, as well as with the Red Hat release (RHEL 4.x) I am dealing with.

Apparently not receiving the packets while bound to a certain local IP is the correct behaviour, although in windows this works. I need to get it working on Linux (RHEL and Ubuntu). With low-level C-code there is a workaround setsockopt(SO_BINDTODEVICE) which I can't find in the Java-APIs. This doesn't exactly make me burst with optimism though :-)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(5

诺曦 2024-07-25 18:51:42

这最终是一个 IPV6 Linux 内核问题。 通常我会禁用 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 如何设法中断对广播的侦听,但它确实如此,并且上述参数修复了它。

This was an IPV6 Linux kernel issue in the end. Usually I have disabled IPV6, because it causes all kind of headaches. However in Ubuntu 9.04 it is so hard to disable IPV6 that I gave up, and that bit me.

To listen to broadcast messages from a certain interface, I'll first create the "broadcast version" of the interface's IP address:

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);

Granted, this doesn't really bind me to a certain interface if many interfaces have an IP which starts with the same network part, but for me this solution is sufficient.

Then I create the datagramsocket with that address (and the desired port), and it works. But not without passing the following system properties to the JVM:

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

I have no idea how IPV6 manages to break listening to broadcasts, but it does, and the above parameters fix it.

薄暮涼年 2024-07-25 18:51:42

要重述您的问题,您需要确定在哪个接口上接收广播 UDP 数据包。

  • 如果绑定到通配符地址,您会收到广播,但无法确定数据包是在哪个网络地址上接收的。
  • 如果绑定到特定接口,您就知道正在哪个接口地址上接收,但不再接收广播(至少在 Linux TCP/IP 堆栈上)。

正如其他人提到的,有用于 Java 的第三方原始套接字库,例如 RockSawJpcap,这可以帮助您确定实际接口的地址。

To restate your problem, you need to determine which interface broadcast UDP packets were received on.

  • If you bind to the wildcard address you receive the broadcasts, but there is no way to determine which network address the packet was received on.
  • If you bind to a specific interface you know which interface address you are receiving on, but no longer receive the broadcasts (at least on the Linux TCP/IP stack.).

As others have mentioned, there are third party raw sockets libraries for Java such as RockSaw or Jpcap, which may help you determine the address of the actual interface.

倦话 2024-07-25 18:51:42

不确定这是否有帮助,但我知道要获取所有网络接口的列表:

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

也许您可以独立绑定到每个网络接口?

刚刚发现了一些关于 getNetworkInterfaces() 用法的很好的例子。

Not sure if this helps but I know that to get a list of all the network interfaces:

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

Maybe you can bind to each one independently?

Just found some great examples on the usage of getNetworkInterfaces().

通知家属抬走 2024-07-25 18:51:42

据我所知,执行此操作的唯一方法是使用

IP_RECVDSTADDR

套接字选项。 此选项旨在确保数据包传入的接口的目标地址在绑定到通配符地址时可用。 所以我认为它也应该适用于广播。

这是我从互联网上获取的一个 C 示例:

如何获取传入数据包的 UDP 目标地址

我会阅读 recvmsg 然后尝试找出该接口在 Java 中是否可用。

编辑:

我刚刚意识到,如果 Java 支持的话,您可能还有一个选择。 您可能仍然需要 IP_RECVDSTADDR 套接字选项(不确定),但您可以使用原始套接字并从 IP 标头获取目标地址,而不是使用 recvmsg。

使用 SOCK_RAW 打开套接字,您将在每条消息的开头获得完整的 IP 标头,包括源地址和目标地址。

下面是在 Linux 上使用 C 语言的 UDP 和原始套接字的示例:

高级 TCP/IP - 原始套接字程序示例

如果此方法在 Java 中也不起作用,我会感到惊讶。

编辑2

还有一个想法。 是否存在无法使用多播的原因或您选择广播而不是多播的特定原因? 据我了解,对于多播,您始终知道在哪个接口上接收数据包,因为在加入多播组时您总是绑定到特定接口(特别是对于 IP4,您通过其 IP 地址之一绑定到接口)。

To the best of my knowledge the only way to do this would be with the

IP_RECVDSTADDR

socket option. This option is supposed to make sure that the dst address of the interface the packet came in on is available when bound to the wildcard address. So it should work with broadcast too I assume.

Here is a C example I picked up off the internet:

How to get UDP destination address on incoming packets

I would read up on recvmsg and then try and find out if this interface is available in Java.

Edit:

I just realized that you may have one more option if it's supported in Java. You may still need the IP_RECVDSTADDR socket option ( not sure ), but instead of using recvmsg you could use a raw socket and get the destination address from the IP header.

Open your socket as using SOCK_RAW and you'll get the full IP header at the beginning of each message including source and destination addresses.

Here's an example of using UDP with a raw socket in C on Linux:

Advanced TCP/IP - THE RAW SOCKET PROGRAM EXAMPLES

I'd be surprised if this method doesn't work in Java also.

Edit2

One more idea. Is there a reason you can't use Multicast or a specific reason you chose Broadcast over Multicast? As far as I understand with Multicast you'd always know which Interface the packets are received on since you always bind to a specific interface when joining a Multicast group ( especially with IP4 where you bind to the interface via one of it's IP addresses ).

折戟 2024-07-25 18:51:42

无法发表评论,因此将其添加为答案。

那很有意思。 虽然我很好奇为什么你这样做

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

而不仅仅是

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

接口地址作为 String 到达你?

Can't comment, so adding this as an answer instead.

That's interesting. Though am curious why you do

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

rather than just

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

or is it that the interface addresses get to you as String ?

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