(tomcat) branch 9.0.x updated: Skip tribes membership tests when IP multicast is unavailable (#1052)

[email protected]
Newsgroups gmane.comp.jakarta.tomcat.devel
Message-ID <178768321787.3656410.6632721059636650841@gitbox3-he-fi.apache.org>
This is an automated email from the ASF dual-hosted git repository.

csutherl pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
     new c5c67261f3 Skip tribes membership tests when IP multicast is unavailable (#1052)
c5c67261f3 is described below

commit c5c67261f3e9a77091341368c478957c74da55a4
Author: Coty Sutherland <[email protected]>
AuthorDate: Tue Aug 25 14:38:19 2026 -0400

    Skip tribes membership tests when IP multicast is unavailable (#1052)
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 test/org/apache/catalina/tribes/TesterUtil.java    | 64 ++++++++++++++++++++++
 .../group/TestGroupChannelMemberArrival.java       |  3 +
 .../interceptors/TestDomainFilterInterceptor.java  |  4 ++
 .../interceptors/TestNonBlockingCoordinator.java   |  7 +++
 .../group/interceptors/TestOrderInterceptor.java   |  7 +++
 .../group/interceptors/TestTcpFailureDetector.java |  3 +
 6 files changed, 88 insertions(+)

diff --git a/test/org/apache/catalina/tribes/TesterUtil.java b/test/org/apache/catalina/tribes/TesterUtil.java
index 54a8db3957..78458cbd74 100644
--- a/test/org/apache/catalina/tribes/TesterUtil.java
+++ b/test/org/apache/catalina/tribes/TesterUtil.java
@@ -16,6 +16,15 @@
  */
 package org.apache.catalina.tribes;
 
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.StandardSocketOptions;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+
 import org.apache.catalina.tribes.group.interceptors.DomainFilterInterceptor;
 import org.apache.catalina.tribes.util.UUIDGenerator;
 
@@ -29,6 +38,61 @@ public class TesterUtil {
     }
 
 
+    /*
+     * Determines whether IP multicast is actually usable in the current
+     * environment. The tribes membership tests rely on the default
+     * {@link org.apache.catalina.tribes.membership.McastService} which
+     * discovers members via IP multicast. Some environments (containers,
+     * minimal CI images, hosts with multicast blocked by a firewall or with no
+     * multicast-capable network interface) cannot deliver multicast traffic. In
+     * those environments the affected tests would fail on a membership timeout
+     * through no fault of the code under test, so they should be skipped instead.
+     *
+     * This performs a functional check - it sends a multicast datagram to the
+     * default group over loopback and confirms it is received - rather than
+     * simply inspecting interface flags, so that it also detects environments
+     * where multicast sockets can be created but traffic is silently dropped.
+     * Any failure is treated as "multicast not available" so a hostile
+     * environment results in a skipped test rather than a spurious failure.
+     */
+    public static boolean isMulticastAvailable() {
+        InetAddress group;
+        try {
+            // Same default group as McastService
+            group = InetAddress.getByName("228.0.0.4");
+        } catch (UnknownHostException e) {
+            return false;
+        }
+
+        // Bind to an ephemeral port (not the McastService default) so this probe
+        // never clashes with a running membership service on the same host.
+        try (MulticastSocket socket = new MulticastSocket(0)) {
+            // Loopback must be enabled for the probe to receive its own packet.
+            socket.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, Boolean.TRUE);
+            socket.setSoTimeout(1000);
+
+            int port = socket.getLocalPort();
+            InetSocketAddress groupAddress = new InetSocketAddress(group, port);
+            // null network interface -> let the OS pick the default, mirroring
+            // McastServiceImpl when no bind address is configured.
+            socket.joinGroup(groupAddress, null);
+            try {
+                byte[] probe = "tomcat-tribes-multicast-probe".getBytes(StandardCharsets.UTF_8);
+                socket.send(new DatagramPacket(probe, probe.length, group, port));
+
+                DatagramPacket received = new DatagramPacket(new byte[probe.length], probe.length);
+                // Throws SocketTimeoutException if the packet is not delivered.
+                socket.receive(received);
+                return received.getLength() == probe.length;
+            } finally {
+                socket.leaveGroup(groupAddress, null);
+            }
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+
     /*
      * Configures a set of channels to use a random domain. Use to ensure that
      * multiple instance of the test suite do not interfere when running on the
diff --git a/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java b/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
index 6163361579..58f9af09be 100644
--- a/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
+++ b/test/org/apache/catalina/tribes/group/TestGroupChannelMemberArrival.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -37,6 +38,8 @@ public class TestGroupChannelMemberArrival {
 
     @Before
     public void setUp() throws Exception {
+        Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+                TesterUtil.isMulticastAvailable());
         for (int i = 0; i < channels.length; i++) {
             channels[i] = new GroupChannel();
             ((ReceiverBase) channels[i].getChannelReceiver()).setHost("localhost");
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java b/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
index 075ce5452b..4def88c6ec 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestDomainFilterInterceptor.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -27,6 +28,7 @@ import org.apache.catalina.tribes.Channel;
 import org.apache.catalina.tribes.ManagedChannel;
 import org.apache.catalina.tribes.Member;
 import org.apache.catalina.tribes.MembershipListener;
+import org.apache.catalina.tribes.TesterUtil;
 import org.apache.catalina.tribes.group.GroupChannel;
 import org.apache.catalina.tribes.util.UUIDGenerator;
 
@@ -37,6 +39,8 @@ public class TestDomainFilterInterceptor {
 
     @Before
     public void setUp() throws Exception {
+        Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+                TesterUtil.isMulticastAvailable());
         for (int i = 0; i < channels.length; i++) {
             channels[i] = new GroupChannel();
             channels[i].getMembershipService().setPayload( ("Channel-" + (i + 1)).getBytes("ASCII"));
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java b/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
index 1ad80db300..3c61c0952c 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestNonBlockingCoordinator.java
@@ -21,6 +21,7 @@ import java.util.logging.LogManager;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -43,6 +44,8 @@ public class TestNonBlockingCoordinator {
 
     @Before
     public void setUp() throws Exception {
+        Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+                TesterUtil.isMulticastAvailable());
         LogManager.getLogManager().getLogger(
                 "org.apache.catalina.tribes.group.interceptors.TestNonBlockingCoordinator").setLevel(Level.ALL);
         try {
@@ -177,6 +180,10 @@ public class TestNonBlockingCoordinator {
     @After
     public void tearDown() throws Exception {
         log.info("tearDown");
+        if (channels == null) {
+            // setUp was skipped (e.g. multicast unavailable)
+            return;
+        }
         for ( int i=0; i<CHANNEL_COUNT; i++ ) {
             channels[i].stop(Channel.DEFAULT);
         }
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java b/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
index 66d74b1ce0..35398458a2 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestOrderInterceptor.java
@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -48,6 +49,8 @@ public class TestOrderInterceptor {
 
     @Before
     public void setUp() throws Exception {
+        Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+                TesterUtil.isMulticastAvailable());
         System.out.println("Setup");
         channels = new GroupChannel[channelCount];
         orderitcs = new OrderInterceptor[channelCount];
@@ -152,6 +155,10 @@ public class TestOrderInterceptor {
     @After
     public void tearDown() throws Exception {
         System.out.println("tearDown");
+        if (channels == null) {
+            // setUp was skipped (e.g. multicast unavailable)
+            return;
+        }
         for ( int i=0; i<channelCount; i++ ) {
             channels[i].stop(Channel.DEFAULT);
         }
diff --git a/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java b/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
index 7c8a87c699..fdcd7d6345 100644
--- a/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
+++ b/test/org/apache/catalina/tribes/group/interceptors/TestTcpFailureDetector.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -43,6 +44,8 @@ public class TestTcpFailureDetector {
 
     @Before
     public void setUp() throws Exception {
+        Assume.assumeTrue("Skipping test - IP multicast is not available in this environment",
+                TesterUtil.isMulticastAvailable());
         channel1 = new GroupChannel();
         channel2 = new GroupChannel();
         ((ReceiverBase) channel1.getChannelReceiver()).setHost("localhost");
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.