1

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.

NickHowes
  • 133
  • 2
  • 14
  • 1
    *(Ideally, I'd like the multicasts to be transmitted from all interfaces.)* I don't believe that's possible. You always send a multicast datagram per NIC, since the IP layer will set the source address on the datagram differently for each NIC. There is only one field in the IP header for the source address so if you're having multiple NICs, is going to be one multicast datagram per NIC/network. – Daniel Feb 10 '22 at 17:05

1 Answers1

2

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));
            }
        }
    }
}
NickHowes
  • 133
  • 2
  • 14