Reputation: 13357
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 :-)
Upvotes: 6
Views: 9130
Reputation: 450
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 ?
Upvotes: 0
Reputation: 13357
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.
Upvotes: 4
Reputation: 450
To restate your problem, you need to determine which interface broadcast UDP packets were received on.
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.
Upvotes: 1
Reputation: 40548
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 ).
Upvotes: 0
Reputation: 4931
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().
Upvotes: 0