Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java on Linux: Listening to broadcast messages on a bound local address

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

like image 329
auramo Avatar asked May 07 '09 17:05

auramo


2 Answers

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.

like image 73
auramo Avatar answered Nov 08 '22 06:11

auramo


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.

like image 21
Kieran Tully Avatar answered Nov 08 '22 05:11

Kieran Tully