views:

2882

answers:

5

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

A: 

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().

Peter D
Looping through interfaces/addresses is exactly what I'm doing, but the problem is that I can't listen to the broadcast messages. I don't get anything with any of the addresses, only when I create the DatagramSocket with the constructor with the plain port parameter (using "wildcard address").
auramo
A: 

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

Robert S. Barnes
It looks like it might end up in Java 7 some day.
auramo
I just realized that you may have one more option if it's supported in Java. Take a look at my edit.
Robert S. Barnes
Using raw sockets from Java would require native code and JNI or JNA calls. I don't want to go there, because it's quite a hassle. I'd rather fall back to my plan B which is to statically configure the interface to use. It has usability issues, but it beats the native code option.
auramo
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 ).
Robert S. Barnes
I didn't choose broadcast, this is an ancient protocol defined ~20 years ago, and it is in heavy use by our products deployed in the field.
auramo
Ahh, well good luck with that... sorry couldn't be of more help.
Robert S. Barnes
+1  A: 

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.

Kieran Tully
Thanks, I'll check those libraries out and see how portable they are. What I want is to just copy the binaries between win<->unix without worries.
auramo
+2  A: 

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.

auramo
A: 

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 ?

Kieran Tully
No reason whatsoever: you are right that just enumerating the bytes in an array is more elegant.
auramo