This is an automated email from the ASF dual-hosted git repository.

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

commit c49f50af35420ed729367658e6df7cb54720278e
Author: opencode <[email protected]>
AuthorDate: Sun Oct 11 11:06:03 2026 +0200

    Bound the multicast send paths to a sendable packet size
    
    The size checks that guard the two multicast send paths, the
    application broadcasts of the membership service and the member ping
    payload, capped the packet at the 65535 byte protocol limit of an IP
    packet. That limit includes the IP and UDP headers, so a packet of
    65508 bytes or more passed the checks and then failed in the socket
    send with a message too long error, driving the membership sender into
    its error and recovery loop or surfacing the failure to the
    application as a broadcast exception.
    
    The checks now use a maximum send size that leaves room for the
    headers and for IP options. The receive buffer is unaffected, as a
    receive buffer may be larger than anything the socket will carry.
    
    Add tests for both paths at the new limit, rejecting one byte above it
    while the packet is still small enough for the socket to send, so the
    checks are shown to reject what the socket could not carry.
---
 .../catalina/tribes/membership/McastService.java   |  4 +-
 .../tribes/membership/McastServiceImpl.java        |  7 +++
 .../catalina/tribes/membership/MemberImpl.java     |  2 +-
 .../tribes/membership/TestMcastService.java        | 57 ++++++++++++++++++++++
 .../catalina/tribes/membership/TestMemberImpl.java | 48 ++++++++++++++++++
 5 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/catalina/tribes/membership/McastService.java 
b/java/org/apache/catalina/tribes/membership/McastService.java
index 0672086983..0a05ed2f4d 100644
--- a/java/org/apache/catalina/tribes/membership/McastService.java
+++ b/java/org/apache/catalina/tribes/membership/McastService.java
@@ -466,9 +466,9 @@ public class McastService extends MembershipServiceBase 
implements MessageListen
         }
 
         byte[] data = XByteBuffer.createDataPackage((ChannelData) message);
-        if (data.length > McastServiceImpl.MAX_PACKET_SIZE) {
+        if (data.length > McastServiceImpl.MAX_SEND_SIZE) {
             throw new 
ChannelException(sm.getString("mcastService.exceed.maxPacketSize", 
Integer.toString(data.length),
-                    Integer.toString(McastServiceImpl.MAX_PACKET_SIZE)));
+                    Integer.toString(McastServiceImpl.MAX_SEND_SIZE)));
         }
         DatagramPacket packet = new DatagramPacket(data, 0, data.length);
         try {
diff --git a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java 
b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
index 24b2b99a19..60935bb95f 100644
--- a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
+++ b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
@@ -53,6 +53,13 @@ public class McastServiceImpl extends MembershipProviderBase 
{
      */
     protected static final int MAX_PACKET_SIZE = 65535;
 
+    /**
+     * The maximum payload size of a packet that can be sent. This is lower 
than the maximum
+     * packet size because the protocol limit for a UDP datagram includes the 
IP and UDP headers,
+     * and the IP header may be extended by options.
+     */
+    protected static final int MAX_SEND_SIZE = MAX_PACKET_SIZE - 48;
+
     /**
      * The string manager for this class.
      */
diff --git a/java/org/apache/catalina/tribes/membership/MemberImpl.java 
b/java/org/apache/catalina/tribes/membership/MemberImpl.java
index ded0596239..edac5f8dab 100644
--- a/java/org/apache/catalina/tribes/membership/MemberImpl.java
+++ b/java/org/apache/catalina/tribes/membership/MemberImpl.java
@@ -688,7 +688,7 @@ public class MemberImpl implements Member, 
java.io.Externalizable {
         if (newPayloadLength > oldPayloadLength) {
             // It is possible that the max packet size will be exceeded
             if ((newPayloadLength - oldPayloadLength +
-                    getData(false, false).length) > 
McastServiceImpl.MAX_PACKET_SIZE) {
+                    getData(false, false).length) > 
McastServiceImpl.MAX_SEND_SIZE) {
                 throw new 
IllegalArgumentException(sm.getString("memberImpl.large.payload"));
             }
         }
diff --git a/test/org/apache/catalina/tribes/membership/TestMcastService.java 
b/test/org/apache/catalina/tribes/membership/TestMcastService.java
index 431e6fae9a..77e43af28c 100644
--- a/test/org/apache/catalina/tribes/membership/TestMcastService.java
+++ b/test/org/apache/catalina/tribes/membership/TestMcastService.java
@@ -23,7 +23,10 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import org.apache.catalina.tribes.Channel;
+import org.apache.catalina.tribes.ChannelException;
 import org.apache.catalina.tribes.group.GroupChannel;
+import org.apache.catalina.tribes.io.ChannelData;
+import org.apache.catalina.tribes.io.XByteBuffer;
 
 public class TestMcastService {
 
@@ -119,4 +122,58 @@ public class TestMcastService {
         Assert.assertEquals(0, service.getSoTimeout());
         Assert.assertEquals(0, service.getTtl());
     }
+
+    /*
+     * The 65535 byte protocol limit of an IP packet includes the IP and UDP 
headers, so a
+     * broadcast that fits within it can still exceed what the socket is able 
to send. Such a
+     * broadcast must be rejected by the size check rather than fail later in 
the send.
+     */
+    @Test(timeout = 60000)
+    public void testBroadcastRejectsPacketsTheSocketCannotCarry() throws 
Exception {
+        int base = 40000 + (int) Math.floorMod(System.nanoTime(), 10000);
+        Properties props = new Properties();
+        props.setProperty("mcastAddress", "228.0.0.4");
+        props.setProperty("mcastPort", String.valueOf(base));
+        props.setProperty("tcpListenHost", "127.0.0.1");
+        props.setProperty("tcpListenPort", String.valueOf(base + 1));
+        props.setProperty("tcpSecurePort", "0");
+        props.setProperty("udpListenPort", "0");
+        props.setProperty("mcastFrequency", "100");
+        props.setProperty("memberDropTime", "3000");
+        props.setProperty("recoveryEnabled", "false");
+        props.setProperty("recoveryCounter", "10");
+        props.setProperty("recoverySleepTime", "3000");
+
+        GroupChannel channel = new GroupChannel();
+        channel.setName("TestMcastService-" + System.nanoTime());
+
+        McastService service = new McastService();
+        service.setChannel(channel);
+        service.setProperties(props);
+        service.start(Channel.MBR_TX_SEQ);
+        try {
+            // Build a packet just above the sendable size but still within 
the 65535 byte packet
+            // limit the old check used, and small enough for this platform to 
actually send
+            ChannelData probe = new ChannelData();
+            probe.setMessage(new XByteBuffer(new byte[0], false));
+            int overhead = XByteBuffer.createDataPackage(probe).length;
+            Assert.assertTrue(overhead > 0 && overhead < 128);
+            byte[] messageBytes = new byte[McastServiceImpl.MAX_SEND_SIZE + 8 
- overhead];
+            Assert.assertEquals(McastServiceImpl.MAX_SEND_SIZE + 8,
+                    XByteBuffer.createDataPackage(new 
ChannelData(probe.getUniqueId(),
+                            new XByteBuffer(messageBytes, false), 0)).length);
+
+            ChannelData message = new ChannelData();
+            message.setMessage(new XByteBuffer(messageBytes, false));
+            message.setTimestamp(System.currentTimeMillis());
+            try {
+                service.broadcast(message);
+                Assert.fail("A broadcast above the sendable size must be 
rejected");
+            } catch (ChannelException expected) {
+                // The size check must reject it
+            }
+        } finally {
+            service.stop(Channel.MBR_TX_SEQ);
+        }
+    }
 }
diff --git a/test/org/apache/catalina/tribes/membership/TestMemberImpl.java 
b/test/org/apache/catalina/tribes/membership/TestMemberImpl.java
new file mode 100644
index 0000000000..b13e8e9030
--- /dev/null
+++ b/test/org/apache/catalina/tribes/membership/TestMemberImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.tribes.membership;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.tribes.util.UUIDGenerator;
+
+public class TestMemberImpl {
+
+    /*
+     * The 65535 byte protocol limit of an IP packet includes the IP and UDP 
headers, so a member
+     * ping that fits within it can still be too large for the multicast 
socket to send. The payload
+     * size check must bound the ping at the size that can actually be sent, 
not the packet limit.
+     */
+    @Test
+    public void testPayloadIsBoundedAtTheSendableSize() throws Exception {
+        MemberImpl member = new MemberImpl("localhost", 4000, 100);
+        member.setUniqueId(UUIDGenerator.randomUUID(false));
+        int base = member.getData(false, false).length;
+
+        // A payload that fills the ping exactly up to the sendable size is 
accepted
+        member.setPayload(new byte[McastServiceImpl.MAX_SEND_SIZE - base]);
+
+        // One byte more passes the 65535 byte packet limit but cannot be 
carried by the socket
+        try {
+            member.setPayload(new byte[McastServiceImpl.MAX_SEND_SIZE - base + 
1]);
+            Assert.fail("A payload that takes the ping above the sendable size 
must be rejected");
+        } catch (IllegalArgumentException expected) {
+            // The size check must reject it
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to