Many users have a computer with a Wifi connection to the internet and a wired connection to a local network. I'm trying to use IPv4 link-local multicast to discover devices on the wired network but this does not work, apparently because the automatically generated routing tables only transmit the multicast frames via the Wifi interface.
My Java code tries to specify both interfaces:
sock.setNetworkInterface(ifc);
sock.send(new DatagramPacket(...));
but this appears to have no effect.
I can use
sudo route -n change -host 224.0.0.189 -interface en4
to change the routing table, and this works. But it's obviously is not practical for an app.
Any idea how to solve this?
(Ideally, I'd like the multicasts to be transmitted from all interfaces.)
It all works using plain old BSD Sockets code:
static const uint8_t discoveryEchoReq[] = { 0, 0, 0, 2, 0, 0, 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0 };
int main(int argc, char **argv) {
//setup socket addr
struct sockaddr_in mcastSockAddr;
memset((char *)&mcastSockAddr, 0, sizeof(mcastSockAddr));
mcastSockAddr.sin_family = AF_INET;
mcastSockAddr.sin_addr.s_addr = inet_addr("224.0.0.189");
mcastSockAddr.sin_port = htons(48556);
//look for interfaces
struct ifaddrs * interfaces = NULL;
struct ifaddrs * ifc = NULL;
if (getifaddrs(&interfaces) < 0) exit(-1);
ifc = interfaces;
while(ifc != NULL) {
if ( (ifc->ifa_addr->sa_family == AF_INET) && ((ifc->ifa_flags & IFF_MULTICAST) != 0) ) {
int sock = socket(AF_INET, SOCK_DGRAM, 0); //create socket
if (sock < 0) exit(-2);
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr, sizeof(struct in_addr)) < 0) exit(-3);
if (sendto(sock, discoveryEchoReq, sizeof(discoveryEchoReq), 0, (struct sockaddr*)&mcastSockAddr, sizeof(mcastSockAddr)) < 0) exit(-5);
if (close(sock) < 0) exit(-6);
}
ifc = ifc->ifa_next; //follow linked list
}
freeifaddrs(interfaces); //free memory
exit(0);
}
but similar Java code does not work:
for (Enumeration ifcs = NetworkInterface.getNetworkInterfaces() ; ifcs.hasMoreElements() ; ) {
NetworkInterface ifc = (NetworkInterface)ifcs.nextElement();
if (ifc.supportsMulticast()) {
for (Enumeration addrs = ifc.getInetAddresses() ; addrs.hasMoreElements() ; ) {
InetAddress addr = (InetAddress)addrs.nextElement();
if (addr instanceof Inet4Address) {
MulticastSocket sock = new MulticastSocket();
sock.setNetworkInterface(ifc);
sock.send(new DatagramPacket(Discoverer.ECHO_REQUEST, Discoverer.ECHO_REQUEST.length, Daemon.SKT_MCAST));
}
}
}
}
i.e. in the BSD case, the multicast packets are emitted from both network interfaces, but in the Java case they are emitted from only one interface.
Using NIO fixes the problem, so my guess is that there's maybe a bug in the Java runtime?
i.e. this works:
for (Enumeration ifcs = NetworkInterface.getNetworkInterfaces() ; ifcs.hasMoreElements() ; ) {
NetworkInterface ifc = (NetworkInterface)ifcs.nextElement();
if (ifc.supportsMulticast()) {
for (Enumeration addrs = ifc.getInetAddresses() ; addrs.hasMoreElements() ; ) {
InetAddress addr = (InetAddress)addrs.nextElement();
if (addr instanceof Inet4Address) {
DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET).setOption(StandardSocketOptions.IP_MULTICAST_IF, ifc);
DatagramSocket sock = dc.socket();
sock.send(new DatagramPacket(Discoverer.ECHO_REQUEST, Discoverer.ECHO_REQUEST.length, Daemon.SKT_MCAST));
}
}
}
}