This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit aba0afb06b85bdb1981c8df79d5711b513fc6561 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 78f93066a7..4b045fcaea 100644 --- a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java +++ b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java @@ -52,6 +52,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 09d0c42533..f59d31b8c5 100644 --- a/java/org/apache/catalina/tribes/membership/MemberImpl.java +++ b/java/org/apache/catalina/tribes/membership/MemberImpl.java @@ -705,7 +705,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]
