【问题标题】:Java on Linux: Listening to broadcast messages on a bound local addressLinux 上的 Java:在绑定的本地地址上监听广播消息
【发布时间】:2009-05-07 17:21:50
【问题描述】:

我有一个有点奇怪的要求,即能够在 Linux 机器上监听来自 Java 的多个网络接口,并确定其中一个是否接收到某种类型的 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。

编辑: BindException (java.net.BindException: Cannot assign requested address) from call DatagramSocket's constructor with a broadcast-address (eg 126.255.255.255) only come with the latest Ubuntu 9.04 (probable not Ubuntu,但内核版本特定问题)。在 Ubuntu 8.10 以及我正在处理的 Red Hat 版本 (RHEL 4.x) 上都可以。

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

【问题讨论】:

  • 这个bug好像10年没修了!!疯狂的! :D

标签: java linux networking udp broadcast


【解决方案1】:

这最终是一个 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 是如何设法中断收听广播的,但确实如此,并且上述参数修复了它。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 谢谢,我会检查一下这些库,看看它们的可移植性如何。我想要的只是在 winunix 之间复制二进制文件,不用担心。
    【解决方案3】:

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

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

    也许你可以单独绑定到每一个?

    刚刚找到了一些关于 getNetworkInterfaces() 用法的好例子。

    【讨论】:

    • 循环通过接口/地址正是我正在做的,但问题是我无法收听广播消息。我没有得到任何地址,只有当我使用带有普通端口参数的构造函数(使用“通配符地址”)创建 DatagramSocket 时。
    【解决方案4】:

    据我所知,唯一的方法是使用

    IP_RECVDSTADDR

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

    这是我从网上找到的一个 C 示例:

    How to get UDP destination address on incoming packets

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

    编辑:

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

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

    这是一个在 Linux 上使用带有原始套接字的 C UDP 的示例:

    Advanced TCP/IP - THE RAW SOCKET PROGRAM EXAMPLES

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

    编辑2

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

    【讨论】:

    • 看起来它可能有一天会出现在 Java 7 中。
    • 我刚刚意识到,如果 Java 支持它,您可能还有一个选择。看看我的编辑。
    • 使用来自 Java 的原始套接字需要本机代码和 JNI 或 JNA 调用。我不想去那里,因为它很麻烦。我宁愿退回到我的计划 B,即静态配置要使用的接口。它存在可用性问题,但优于原生代码选项。
    • 还有一个想法。您是否有不能使用多播的原因或您选择多播广播的特定原因?据我了解,对于多播,您总是知道接收数据包的接口,因为您在加入多播组时总是绑定到特定的接口(尤其是在 IP4 中,您通过其中一个 IP 地址绑定到接口)。
    • 我没有选择广播,这是一个大约 20 年前定义的古老协议,我们在现场部署的产品大量使用它。
    【解决方案5】:

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

    这很有趣。虽然很好奇你为什么这样做

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

    而不仅仅是

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

    还是接口地址以字符串的形式提供给你?

    【讨论】:

    • 没有任何理由:你是对的,只是枚举数组中的字节更优雅。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 2014-06-10
    • 2012-08-19
    相关资源
    最近更新 更多