Skip to content

Multicast behavior on different platforms

Ramiz Dündar edited this page Mar 22, 2022 · 3 revisions

This is a result table of MulticastSocket behavior investigation.

The first column in the table says if the address used was a loopback one. Next 3 columns shows parameters:

  • was loopbackMode requested?
  • was setInterface() called?
  • was a host used in MultiSocket.bind()?

The last 4 columns show when the multicast socket was able to receive a datagram sent from the same host.

address setLoopbackMode to true setInterface called bind to explicit InetAddress Solaris Mac OS Linux Windows
non‑loopback FALSE FALSE FALSE FALSE FALSE FALSE FALSE
non‑loopback FALSE FALSE TRUE FALSE FALSE FALSE FALSE
non‑loopback FALSE TRUE FALSE FALSE TRUE FALSE FALSE
non‑loopback FALSE TRUE TRUE FALSE FALSE FALSE FALSE
non‑loopback TRUE FALSE FALSE TRUE TRUE TRUE TRUE
non‑loopback TRUE FALSE TRUE FALSE FALSE FALSE TRUE
non‑loopback TRUE TRUE FALSE TRUE TRUE TRUE TRUE
non‑loopback TRUE TRUE TRUE FALSE FALSE FALSE FALSE
loopback FALSE FALSE FALSE FALSE FALSE FALSE FALSE
loopback FALSE FALSE TRUE FALSE FALSE FALSE FALSE
loopback FALSE TRUE FALSE FALSE FALSE FALSE FALSE
loopback FALSE TRUE TRUE FALSE FALSE FALSE FALSE
loopback TRUE FALSE FALSE TRUE TRUE TRUE TRUE
loopback TRUE FALSE TRUE FALSE FALSE FALSE FALSE
loopback TRUE TRUE FALSE FALSE TRUE FALSE TRUE
loopback TRUE TRUE TRUE FALSE FALSE FALSE TRUE

So, in tests, Solaris and Linux behaved in the same way. Mac OS and Windows required different parameters to be configured. Don't forget to use -Djava.net.preferIPv4Stack=true vm option with Mac OS.

I used just 1 machine for each tested OS, so there may be other environmental effects I didn't notice.

Sources for the testing:
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class App {

    private static final int DATAGRAM_BUFFER_SIZE = 64 * 1024;
    private static final int PORT = 54327;
    private static final InetAddress GROUP_ADDRESS;

    static {
        try {
            GROUP_ADDRESS = InetAddress.getByName("224.2.2.3");
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    final InetAddress bindAddress;
    final boolean isLoopbackMode;
    final boolean isSetInterface;
    final boolean isSetBindAddress;

    final String uuid;

    private volatile boolean received;

    public App(InetAddress bindAddress, boolean isLoopbackMode, boolean isSetInterface, boolean isSetBindAddress) {
        this.bindAddress = bindAddress;
        this.isLoopbackMode = isLoopbackMode;
        this.isSetInterface = isSetInterface;
        this.isSetBindAddress = isSetBindAddress;
        this.uuid = UUID.randomUUID().toString();
    }

    public static void main(String[] args) throws Exception {
        // if (args == null || args.length != 4) {
        // System.err.println("Unexpected number of arguments");
        // System.err.println("Usage:");
        // System.err.println("\tjava -jar app.jar [bindAddress] [loopbackModeEnabled] [setInterface] [setBindAddress]");
        // System.exit(2);
        // }
        // InetAddress bindAddress = InetAddress.getByName(args[0]);
        // boolean isLoopbackMode = Boolean.parseBoolean(args[1]);
        // boolean isSetInterface = Boolean.parseBoolean(args[2]);
        // boolean isSetBindAddress = Boolean.parseBoolean(args[3]);
        // App app = new App(bindAddress, isLoopbackMode, isSetInterface, isSetBindAddress);
        for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces())) {
            boolean skip = ni.isVirtual() || !ni.isUp();
            if (skip) continue;
            for (InetAddress ia : Collections.list(ni.getInetAddresses())) {
                if (ia instanceof Inet4Address) {
                    for (boolean loopbackMode : Arrays.asList(false, true)) {
                        for (boolean setInterface : Arrays.asList(false, true)) {
                            for (boolean setBindAddr : Arrays.asList(false, true)) {
                                System.out.println("========================================");
                                App app = new App(ia, loopbackMode, setInterface, setBindAddr);
                                Thread sender = app.sender();
                                System.out.println(app.received()
                                        + "\t" + app.bindAddress.getHostAddress()
                                        + "\t" + app.isLoopbackMode
                                        + "\t" + app.isSetInterface
                                        + "\t" + app.isSetBindAddress
                                        + "\t" + app.uuid
                                );
                                Thread.sleep(2000);
                                sender.interrupt();
                                Thread.sleep(2000);
                                System.out.println("========================================");
                            }
                        }
                    }
                }
            }
        }
    }

    private MulticastSocket configureSocket() throws Exception {
        MulticastSocket multicastSocket = new MulticastSocket(null);
        multicastSocket.setReuseAddress(true);
        multicastSocket.bind(isSetBindAddress ? new InetSocketAddress(bindAddress, PORT) : new InetSocketAddress(PORT));

        multicastSocket.setLoopbackMode(!isLoopbackMode);
        if (isSetInterface) {
            multicastSocket.setInterface(bindAddress);
        }
        multicastSocket.joinGroup(GROUP_ADDRESS);
        return multicastSocket;
    }

    public Thread sender() {
        Thread sender = new Thread(() -> {
            try (MulticastSocket multicastSocket = configureSocket()) {
                byte[] packetData = uuid.getBytes();
                DatagramPacket packet = new DatagramPacket(packetData, packetData.length, GROUP_ADDRESS, PORT);
                while (!received) {
//                    System.out.println("Sending");
                    multicastSocket.send(packet);
                    TimeUnit.SECONDS.sleep(1);
                }
            } catch (InterruptedException ie) {
                System.out.println("Failed sending: " + uuid);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        sender.start();
        return sender;
    }

    public boolean received() {
        try (MulticastSocket multicastSocket = configureSocket()) {
            DatagramPacket datagramPacketReceive = new DatagramPacket(new byte[DATAGRAM_BUFFER_SIZE], DATAGRAM_BUFFER_SIZE);
            multicastSocket.setSoTimeout(1000);
            LocalDateTime end = LocalDateTime.now().plusSeconds(5);
            while (LocalDateTime.now().isBefore(end)) {
                try {
                    multicastSocket.receive(datagramPacketReceive);
                    String receivedStr = new String(datagramPacketReceive.getData(), datagramPacketReceive.getOffset(),
                            datagramPacketReceive.getLength());
                    System.out.println("Received: '" + receivedStr + "' from " + datagramPacketReceive.getSocketAddress() + "("
                            + (uuid.equals(receivedStr) ? "self" : "other") + ")");
                    if (!received) {
                        received = uuid.equals(receivedStr);
                    }
                    if (received) {
                        return true;
                    }
                } catch (SocketTimeoutException ignored) {
                }
            }
            multicastSocket.leaveGroup(GROUP_ADDRESS);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

This page was originally a GH issue comment: https://github.com/hazelcast/hazelcast/pull/19251#issuecomment-891375270