-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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