diff --git a/.jcheck/conf b/.jcheck/conf index c39a4fe49e6..e8b8223875e 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,7 +1,7 @@ [general] project=jdk-updates jbs=JDK -version=17.0.12 +version=17.0.13 [checks] error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 37266259ef2..9b91536f591 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -28,12 +28,12 @@ DEFAULT_VERSION_FEATURE=17 DEFAULT_VERSION_INTERIM=0 -DEFAULT_VERSION_UPDATE=12 +DEFAULT_VERSION_UPDATE=13 DEFAULT_VERSION_PATCH=0 DEFAULT_VERSION_EXTRA1=0 DEFAULT_VERSION_EXTRA2=0 DEFAULT_VERSION_EXTRA3=0 -DEFAULT_VERSION_DATE=2024-07-16 +DEFAULT_VERSION_DATE=2024-10-15 DEFAULT_VERSION_CLASSFILE_MAJOR=61 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 diff --git a/src/hotspot/share/gc/shared/genArguments.cpp b/src/hotspot/share/gc/shared/genArguments.cpp index 805f3535762..6bba0f2cf69 100644 --- a/src/hotspot/share/gc/shared/genArguments.cpp +++ b/src/hotspot/share/gc/shared/genArguments.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -273,6 +273,9 @@ void GenArguments::initialize_size_info() { // and maximum heap size since no explicit flags exist // for setting the old generation maximum. MaxOldSize = MAX2(MaxHeapSize - max_young_size, GenAlignment); + MinOldSize = MIN3(MaxOldSize, + InitialHeapSize - initial_young_size, + MinHeapSize - MinNewSize); size_t initial_old_size = OldSize; @@ -284,9 +287,8 @@ void GenArguments::initialize_size_info() { // with the overall heap size). In either case make // the minimum, maximum and initial sizes consistent // with the young sizes and the overall heap sizes. - MinOldSize = GenAlignment; initial_old_size = clamp(InitialHeapSize - initial_young_size, MinOldSize, MaxOldSize); - // MaxOldSize has already been made consistent above. + // MaxOldSize and MinOldSize have already been made consistent above. } else { // OldSize has been explicitly set on the command line. Use it // for the initial size but make sure the minimum allow a young @@ -301,9 +303,10 @@ void GenArguments::initialize_size_info() { ", -XX:OldSize flag is being ignored", MaxHeapSize); initial_old_size = MaxOldSize; + } else if (initial_old_size < MinOldSize) { + log_warning(gc, ergo)("Inconsistency between initial old size and minimum old size"); + MinOldSize = initial_old_size; } - - MinOldSize = MIN2(initial_old_size, MinHeapSize - MinNewSize); } // The initial generation sizes should match the initial heap size, diff --git a/src/java.base/share/classes/java/lang/ProcessBuilder.java b/src/java.base/share/classes/java/lang/ProcessBuilder.java index 1bbbec2a847..396b87f4c54 100644 --- a/src/java.base/share/classes/java/lang/ProcessBuilder.java +++ b/src/java.base/share/classes/java/lang/ProcessBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1296,6 +1296,10 @@ public static List startPipeline(List builders) throws redirects[1] = new RedirectPipeImpl(); // placeholder for new output } processes.add(builder.start(redirects)); + if (prevOutput instanceof RedirectPipeImpl redir) { + // Wrap the fd so it can be closed + new Process.PipeInputStream(redir.getFd()).close(); + } prevOutput = redirects[1]; } } catch (Exception ex) { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java index 3f3e4ff339a..aefae6507dc 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java @@ -53,17 +53,27 @@ import java.util.function.IntBinaryOperator; /** - * Implements SSL using two SubscriberWrappers. + * Implements SSL using two {@link SubscriberWrapper}s. * - *

Constructor takes two Flow.Subscribers: one that receives the network - * data (after it has been encrypted by SSLFlowDelegate) data, and one that - * receives the application data (before it has been encrypted by SSLFlowDelegate). + *

Constructor takes two {@linkplain Flow.Subscriber subscribers} - {@code downReader} + * and {@code downWriter}. {@code downReader} receives the application data (after it has + * been decrypted by SSLFlowDelegate). {@code downWriter} receives the network data (after it has + * been encrypted by SSLFlowDelegate). * - *

Methods upstreamReader() and upstreamWriter() return the corresponding - * Flow.Subscribers containing Flows for the encrypted/decrypted upstream data. - * See diagram below. + *

Method {@link #upstreamWriter()} returns a {@linkplain Subscriber subscriber} which should + * be subscribed with a {@linkplain Flow.Publisher publisher} which publishes application data + * that can then be encrypted into network data by this SSLFlowDelegate and handed off to the + * {@code downWriter}. * - *

How Flow.Subscribers are used in this class, and where they come from: + *

Method {@link #upstreamReader()} returns a {@link Subscriber subscriber} which should be + * subscribed with a {@linkplain Flow.Publisher publisher} which publishes encrypted network data + * that can then be decrypted into application data by this SSLFlowDelegate and handed off to the + * {@code downReader}. + * + *

Errors are reported to the {@code downReader} subscriber. + * + *

The diagram below illustrates how the Flow.Subscribers are used in this class, and where + * they come from: *

  * {@code
  *
@@ -72,17 +82,21 @@
  * --------->  data flow direction
  *
  *
- *                         +------------------+
- *        upstreamWriter   |                  | downWriter
- *        ---------------> |                  | ------------>
- *  obtained from this     |                  | supplied to constructor
- *                         | SSLFlowDelegate  |
- *        downReader       |                  | upstreamReader
- *        <--------------- |                  | <--------------
- * supplied to constructor |                  | obtained from this
- *                         +------------------+
- *
- * Errors are reported to the downReader Flow.Subscriber
+ *                  |                                   ^
+ *  upstreamWriter  |                                   | downReader
+ *  obtained from   |                                   | supplied to
+ * upstreamWriter() |                                   | constructor
+ *                  v                                   |
+ *      +-----------------------------------------------------------+
+ *      *                                            decrypts       *
+ *      *                       SSLFlowDelegate                     *
+ *      *        encrypts                                           *
+ *      +-----------------------------------------------------------+
+ *                  |                                   ^
+ *    downWriter    |                                   | upstreamReader
+ *    supplied to   |                                   | obtained from
+ *    constructor   |                                   | upstreamReader()
+ *                  v                                   |
  *
  * }
  * 
@@ -467,7 +481,7 @@ else if (this.completing) { } } // request more data and return. - requestMore(); + requestMoreDataIfNeeded(); return; } if (complete && result.status() == Status.CLOSED) { diff --git a/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp b/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp index 9a3cbaaaefb..2a8aaf8ad8d 100644 --- a/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp +++ b/src/jdk.jpackage/windows/native/applauncher/WinLauncher.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -134,6 +134,82 @@ tstring getJvmLibPath(const Jvm& jvm) { } +class RunExecutorWithMsgLoop { +public: + static DWORD apply(const Executor& exec) { + RunExecutorWithMsgLoop instance(exec); + + UniqueHandle threadHandle = UniqueHandle(CreateThread(NULL, 0, worker, + static_cast(&instance), 0, NULL)); + if (threadHandle.get() == NULL) { + JP_THROW(SysError("CreateThread() failed", CreateThread)); + } + + MSG msg; + BOOL bRet; + while((bRet = GetMessage(&msg, instance.hwnd, 0, 0 )) != 0) { + if (bRet == -1) { + JP_THROW(SysError("GetMessage() failed", GetMessage)); + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + // Wait for worker thread to terminate to guarantee it will not linger + // around after the thread running a message loop terminates. + const DWORD res = ::WaitForSingleObject(threadHandle.get(), INFINITE); + if (WAIT_FAILED == res) { + JP_THROW(SysError("WaitForSingleObject() failed", + WaitForSingleObject)); + } + + LOG_TRACE(tstrings::any() + << "Executor worker thread terminated. Exit code=" + << instance.exitCode); + return instance.exitCode; + } + +private: + RunExecutorWithMsgLoop(const Executor& v): exec(v) { + exitCode = 1; + + // Message-only window. + hwnd = CreateWindowEx(0, _T("STATIC"), _T(""), 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL); + if (!hwnd) { + JP_THROW(SysError("CreateWindowEx() failed", CreateWindowEx)); + } + } + + static DWORD WINAPI worker(LPVOID param) { + static_cast(param)->run(); + return 0; + } + + void run() { + JP_TRY; + exitCode = static_cast(exec.execAndWaitForExit()); + JP_CATCH_ALL; + + JP_TRY; + if (!PostMessage(hwnd, WM_QUIT, 0, 0)) { + JP_THROW(SysError("PostMessage(WM_QUIT) failed", PostMessage)); + } + return; + JP_CATCH_ALL; + + // All went wrong, PostMessage() failed. Just terminate with error code. + exit(1); + } + +private: + const Executor& exec; + DWORD exitCode; + HWND hwnd; +}; + + void launchApp() { // [RT-31061] otherwise UI can be left in back of other windows. ::AllowSetForegroundWindow(ASFW_ANY); @@ -165,7 +241,7 @@ void launchApp() { } JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { }; jobInfo.BasicLimitInformation.LimitFlags = - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; if (!SetInformationJobObject(jobHandle.get(), JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo))) { JP_THROW(SysError(tstrings::any() << @@ -180,7 +256,7 @@ void launchApp() { exec.arg(arg); }); - DWORD exitCode = static_cast(exec.execAndWaitForExit()); + DWORD exitCode = RunExecutorWithMsgLoop::apply(exec); exit(exitCode); return; diff --git a/test/hotspot/jtreg/gtest/AsyncLogGtest.java b/test/hotspot/jtreg/gtest/AsyncLogGtest.java index f85f6d5c7e2..302730b1515 100644 --- a/test/hotspot/jtreg/gtest/AsyncLogGtest.java +++ b/test/hotspot/jtreg/gtest/AsyncLogGtest.java @@ -1,5 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +34,7 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * java.xml + * @requires vm.flagless * @run main/native GTestWrapper --gtest_filter=AsyncLogTest* -Xlog:async * @run main/native GTestWrapper --gtest_filter=Log*Test* -Xlog:async */ diff --git a/test/hotspot/jtreg/gtest/NMTGtests.java b/test/hotspot/jtreg/gtest/NMTGtests.java index 57666680b6b..6516fde8da5 100644 --- a/test/hotspot/jtreg/gtest/NMTGtests.java +++ b/test/hotspot/jtreg/gtest/NMTGtests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -32,6 +32,7 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * java.xml + * @requires vm.flagless * @run main/native GTestWrapper --gtest_filter=NMT*:os* -XX:NativeMemoryTracking=off */ @@ -40,6 +41,7 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * java.xml + * @requires vm.flagless * @run main/native GTestWrapper --gtest_filter=NMT*:os* -XX:NativeMemoryTracking=summary */ @@ -48,5 +50,6 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * java.xml + * @requires vm.flagless * @run main/native GTestWrapper --gtest_filter=NMT*:os* -XX:NativeMemoryTracking=detail */ diff --git a/test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java b/test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java index 259ed5b5146..82ee888f653 100644 --- a/test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java +++ b/test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java @@ -28,5 +28,6 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * java.xml + * @requires vm.flagless * @run main/native GTestWrapper --gtest_filter=os.trim* -Xlog:trimnative -XX:TrimNativeHeapInterval=100 */ diff --git a/test/jdk/java/lang/ProcessBuilder/PipelineLeaksFD.java b/test/jdk/java/lang/ProcessBuilder/PipelineLeaksFD.java new file mode 100644 index 00000000000..4f572610b9d --- /dev/null +++ b/test/jdk/java/lang/ProcessBuilder/PipelineLeaksFD.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/* + * @test + * @bug 8289643 + * @requires (os.family == "linux" & !vm.musl) + * @summary file descriptor leak with ProcessBuilder.startPipeline + * @run testng/othervm PipelineLeaksFD + */ + +@Test +public class PipelineLeaksFD { + @DataProvider + public Object[][] builders() { + return new Object[][]{ + {List.of(new ProcessBuilder("cat"))}, + {List.of(new ProcessBuilder("cat"), + new ProcessBuilder("cat"))}, + {List.of(new ProcessBuilder("cat"), + new ProcessBuilder("cat"), + new ProcessBuilder("cat"), + new ProcessBuilder("cat"), + new ProcessBuilder("cat"))}, + }; + } + + @Test(dataProvider = "builders") + void checkForLeaks(List builders) throws IOException { + + Set pipesBefore = myPipes(); + if (pipesBefore.size() < 3) { + System.out.println(pipesBefore); + Assert.fail("There should be at least 3 pipes before, (0, 1, 2)"); + } + + // Redirect all of the error streams to stdout (except the last) + // so those file descriptors are not left open + for (int i = 0; i < builders.size() - 1; i++) { + builders.get(i).redirectErrorStream(true); + } + + List processes = ProcessBuilder.startPipeline(builders); + + // Write something through the pipeline + try (OutputStream out = processes.get(0).getOutputStream()) { + out.write('a'); + } + + Process last = processes.get(processes.size() - 1); + try (InputStream inputStream = last.getInputStream(); + InputStream errorStream = last.getErrorStream()) { + byte[] bytes = inputStream.readAllBytes(); + Assert.assertEquals(bytes.length, 1, "stdout bytes read"); + byte[] errBytes = errorStream.readAllBytes(); + Assert.assertEquals(errBytes.length, 0, "stderr bytes read"); + } + + processes.forEach(p -> waitForQuiet(p)); + + Set pipesAfter = myPipes(); + if (!pipesBefore.equals(pipesAfter)) { + Set missing = new HashSet<>(pipesBefore); + missing.removeAll(pipesAfter); + printPipes(missing, "Missing from pipesAfter"); + Set extra = new HashSet<>(pipesAfter); + extra.removeAll(pipesBefore); + printPipes(extra, "Extra pipes in pipesAfter"); + Assert.fail("More or fewer pipes than expected"); + } + } + + static void printPipes(Set pipes, String label) { + System.out.printf("%s: [%d]%n", label, pipes.size()); + pipes.forEach(r -> System.out.printf("%-20s: %s%n", r.fd(), r.link())); + } + + static void waitForQuiet(Process p) { + try { + int st = p.waitFor(); + if (st != 0) { + System.out.println("non-zero exit status: " + p); + } + } catch (InterruptedException ie) { + } + } + + /** + * Collect a Set of pairs of /proc fd paths and the symbol links that are pipes. + * @return A set of PipeRecords, possibly empty + */ + static Set myPipes() { + Path path = Path.of("/proc/" + ProcessHandle.current().pid() + "/fd"); + Set pipes = new HashSet<>(); + File[] files = path.toFile().listFiles(f -> Files.isSymbolicLink(f.toPath())); + if (files != null) { + for (File file : files) { + try { + Path link = Files.readSymbolicLink(file.toPath()); + if (link.toString().startsWith("pipe:")) { + pipes.add(new PipeRecord(file.toPath(), link)); + } + } catch (IOException ioe) { + } + } + } + return pipes; + } + + record PipeRecord(Path fd, Path link) { }; +} diff --git a/test/jdk/java/net/httpclient/whitebox/SSLFlowDelegateTestDriver.java b/test/jdk/java/net/httpclient/whitebox/SSLFlowDelegateTestDriver.java new file mode 100644 index 00000000000..083a291751f --- /dev/null +++ b/test/jdk/java/net/httpclient/whitebox/SSLFlowDelegateTestDriver.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8308144 + * @summary tests that the SSLFlowDelegate doesn't accumulate application data when the + * downReader doesn't request any + * @modules java.net.http/jdk.internal.net.http + * @run testng/othervm -Djdk.internal.httpclient.debug=true + * -Djavax.net.debug=ssl:handshake + * java.net.http/jdk.internal.net.http.SSLFlowDelegateTest + */ diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLFlowDelegateTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLFlowDelegateTest.java new file mode 100644 index 00000000000..45af27a96a8 --- /dev/null +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLFlowDelegateTest.java @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Flow; +import java.util.concurrent.Flow.Subscriber; +import java.util.concurrent.SubmissionPublisher; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; + +import jdk.internal.net.http.common.Logger; +import jdk.internal.net.http.common.SSLFlowDelegate; +import jdk.internal.net.http.common.SubscriberWrapper; +import jdk.internal.net.http.common.Utils; +import org.testng.Assert; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +// jtreg test definition for this test resides in SSLFlowDelegateTestDriver.java +public class SSLFlowDelegateTest { + private static final String ALPN = "foobar"; + private static final String debugTag = SSLFlowDelegateTest.class.getSimpleName(); + private static final Random random = new Random(); + private static final byte DATA_BYTE = (byte) random.nextInt(); + + private ExecutorService executor; + private SSLContext sslContext; + private SSLParameters sslParams; + private SSLServerSocket sslServerSocket; + private SSLEngine clientEngine; + private CompletableFuture testCompletion; + + @BeforeTest + public void beforeTest() throws Exception { + this.executor = Executors.newCachedThreadPool(); + this.sslContext = new jdk.internal.net.http.SimpleSSLContext().get(); + this.testCompletion = new CompletableFuture<>(); + + final SSLParameters sp = new SSLParameters(); + sp.setApplicationProtocols(new String[]{ALPN}); + this.sslParams = sp; + + this.sslServerSocket = startServer(this.sslContext); + println(debugTag, "Server started at " + this.sslServerSocket.getInetAddress() + ":" + + this.sslServerSocket.getLocalPort()); + + this.clientEngine = createClientEngine(this.sslContext); + } + + @AfterTest + public void afterTest() throws Exception { + if (this.sslServerSocket != null) { + println(debugTag, "Closing server socket " + this.sslServerSocket); + this.sslServerSocket.close(); + } + if (this.executor != null) { + println(debugTag, "Shutting down the executor " + this.executor); + this.executor.shutdownNow(); + } + } + + private static void println(final String debugTag, final String msg) { + println(debugTag, msg, null); + } + + private static void println(final String debugTag, final String msg, final Throwable t) { + final Logger logger = Utils.getDebugLogger(() -> debugTag); + logger.log(msg); + if (t != null) { + t.printStackTrace(); + } + } + + private SSLServerSocket createSSLServerSocket( + final SSLContext ctx, final InetSocketAddress bindAddr) throws IOException { + final SSLServerSocketFactory fac = ctx.getServerSocketFactory(); + final SSLServerSocket sslServerSocket = (SSLServerSocket) fac.createServerSocket(); + sslServerSocket.setReuseAddress(false); + sslServerSocket.setSSLParameters(this.sslParams); + sslServerSocket.bind(bindAddr); + return sslServerSocket; + } + + private SSLServerSocket startServer(final SSLContext ctx) throws Exception { + final SSLServerSocket sslServerSocket = createSSLServerSocket(ctx, + new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + final Runnable serverResponsePusher = new ServerResponsePusher(sslServerSocket, + this.testCompletion); + final Thread serverThread = new Thread(serverResponsePusher, "serverResponsePusher"); + // start the thread which will accept() a socket connection and send data over it + serverThread.start(); + return sslServerSocket; + } + + private SSLEngine createClientEngine(final SSLContext ctx) { + final SSLEngine clientEngine = ctx.createSSLEngine(); + clientEngine.setSSLParameters(this.sslParams); + clientEngine.setUseClientMode(true); + return clientEngine; + } + + /** + * Constructs a {@code SSLFlowDelegate} with a {@code downReader} which only requests one + * item and then never requests any more. After that one item has been received by the + * {@code downReader}, this test feeds the + * {@linkplain SSLFlowDelegate#upstreamReader() upstreamReader} with (network SSL) data in + * such a manner that while + * {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer) unwrapping} it in the internal implementation + * of {@code SSLFlowDelegate}, it will often trigger {@code BUFFER_UNDERFLOW} state. + * This test then verifies that the {@code SSLFlowDelegate} when it reaches such a state will + * not keep asking for more (network SSL) data and decrypting it to application data and + * accumulating it (since the {@code downReader} won't be using any of this accumulated data). + */ + @Test + public void testUnsolicitedBytes() throws Exception { + // initiate a connection to the server + try (final Socket socket = new Socket(sslServerSocket.getInetAddress(), + sslServerSocket.getLocalPort())) { + println(debugTag, "connected to server, local socket: " + socket); + // this future is completed when the AppResponseReceiver subscriber receives + // the sole item that is requests for (through just one subscription.request(1)) + final CompletableFuture soleExpectedAppData = new CompletableFuture<>(); + // the "testCompletion" CompletableFuture represents that future that's used + // in various places in this test. If the "testCompletion" completes before + // the "soleExpectedAppData" completes (typically due to some exception), + // then we complete the "soleExpectedAppData" too. + this.testCompletion.whenComplete((r, t) -> { + if (soleExpectedAppData.isDone()) { + return; + } + if (t == null) { + soleExpectedAppData.complete(-1L); // -1 indicates no item received + return; + } + soleExpectedAppData.completeExceptionally(t); + }); + // the "downReader" Subscriber which is passed to the constructor of SSLFlowDelegate. + // This subscriber receives the (decrypted) application data. This subscriber requests + // only one item (no restriction on how many bytes are received in this one item). + final AppResponseReceiver appResponseReceiver = new AppResponseReceiver( + this.testCompletion, soleExpectedAppData); + // the "downWriter" Subscriber which is passed to the constructor of the + // SSLFlowDelegate. + // This subscriber receives the (encrypted) network data and just writes it out to the + // connected socket's OutputStream. Makes no restrictions on how much items (and thus + // bytes) it receives and just keeps writing as and when it receives the data. + final SocketWriter networkDataWriter = new SocketWriter(socket, this.testCompletion); + // construct the SSLFlowDelegate + final SSLFlowDelegate sslFlowDelegate = new SSLFlowDelegate(clientEngine, executor, + appResponseReceiver, networkDataWriter); + // the SocketReader runs in a thread and it keeps reading data from the connected + // socket's InputStream. This data keeps coming from the ServerResponsePusher which too + // is running in a thread of its own and is writing it out over the connected socket's + // OutputStream. The SocketReader and ServerResponsePusher are convenience constructs + // which use the simple APIs (InputStream/OutputStream) provided by SSLServerSocket + // and (SSL)Socket to generate SSL network data. This generated data is then fed to + // the relevant subscribers of SSLFlowDelegate. The SocketReader and the + // ServerResponsePusher play no other role than just generating this SSL network data. + final SocketReader socketReader = new SocketReader(socket, executor, + sslFlowDelegate.upstreamReader(), this.testCompletion); + // start reading from the socket in separate thread + new Thread(socketReader, "socketReader").start(); + + // we use this publisher only to trigger the SSL handshake and the publisher itself + // doesn't publish any data i.e. there is no application client "request" data needed + // in this test + final Flow.Publisher> publisher = new ZeroDataPublisher<>(); + println(debugTag, "Subscribing the upstreamWriter() to trigger SSL handshake"); + // now connect all the pieces. + // this call to subscribe the upstreamWriter() triggers the SSL handshake (doesn't + // matter if our zero app data publisher publishes no app data; SSL handshake + // doesn't require app data). see SSLFlowDelegate$Writer.onSubscribe() where + // it triggers the SSL handshake when this subscription happens + publisher.subscribe(sslFlowDelegate.upstreamWriter()); + println(debugTag, "Waiting for handshake to complete"); + final String negotiatedALPN = sslFlowDelegate.alpn().join(); + println(debugTag, "handshake completed, with negotiated ALPN: " + negotiatedALPN); + Assert.assertEquals(negotiatedALPN, ALPN, "unexpected ALPN negotiated"); + try { + // now wait for the initial (and the only) chunk of application data to be + // received by the AppResponseReceiver + println(debugTag, "waiting for the sole expected chunk of application data to" + + " become available to " + appResponseReceiver); + final long numAppDataBytesReceived = soleExpectedAppData.join(); + println(debugTag, "Received " + numAppDataBytesReceived + " app data bytes," + + " no more app data expected"); + // at this point, we have received the only expected item in the downReader + // i.e. the AppResponseReceiver. We no longer expect the SSLFlowDelegate to be + // accumulating any decrypted application data (because the downReader hasn't + // requested any). + // We will now let the SocketReader and the ServerResponsePusher threads to keep + // generating and feeding the SSL network data to the SSLFlowDelegate subscribers, + // until they are "done" (either normally or exceptionally). Those threads decide + // when to stop generating the SSL network data. + this.testCompletion.join(); + } catch (CompletionException ce) { + // fail with a Assert.fail instead of throwing an exception, thus providing a + // better failure report + failTest(ce); + } + println(debugTag, "now checking if any unsolicited bytes accumulated"); + // SSL network data generation has completed, now check if the SSLFlowDelegate + // decrypted and accumulated any application data when it shouldn't have. + assertNoUnsolicitedBytes(sslFlowDelegate); + println(debugTag, "testing completed successfully, no unsolicited bytes accumulated"); + } + } + + private void failTest(final CompletionException ce) { + final Throwable cause = ce.getCause(); + Assert.fail(cause.getMessage() == null ? "test failed" : cause.getMessage(), cause); + } + + // uses reflection to get hold of the SSLFlowDelegate.reader.outputQ member field, + // which is a ConcurrentLinkedQueue holding the decrypted application data that + // is supposed to be sent to the downReader subscriber of the SSLFlowDelegate. + // Asserts that this outputQ has 0 bytes of data accumulated + private void assertNoUnsolicitedBytes(final SSLFlowDelegate sslFlowDelegate) throws Exception { + final Field readerField = SSLFlowDelegate.class.getDeclaredField("reader"); + readerField.setAccessible(true); + + final Field readerOutputQField = SubscriberWrapper.class.getDeclaredField("outputQ"); + readerOutputQField.setAccessible(true); + + final Object reader = readerField.get(sslFlowDelegate); + final ConcurrentLinkedQueue> outputQ = + ConcurrentLinkedQueue.class.cast(readerOutputQField.get(reader)); + long numUnsolicitated = 0; + List accumulations; + while ((accumulations = outputQ.poll()) != null) { + println(debugTag, "found some items in outputQ"); + for (final ByteBuffer buf : accumulations) { + if (!buf.hasRemaining()) { + continue; + } + try { + numUnsolicitated = Math.addExact(numUnsolicitated, buf.remaining()); + } catch (ArithmeticException ame) { + numUnsolicitated = Long.MAX_VALUE; + break; + } + } + println(debugTag, "num unsolicited bytes so far = " + numUnsolicitated); + } + Assert.assertEquals(numUnsolicitated, 0, + "SSLFlowDelegate has accumulated " + numUnsolicitated + " unsolicited bytes"); + } + + // A publisher which accepts only one subscriber and doesn't ever publish any data + private static final class ZeroDataPublisher implements Flow.Publisher { + private final AtomicBoolean hasSubscriber = new AtomicBoolean(); + + @Override + public void subscribe(final Subscriber subscriber) { + if (!hasSubscriber.compareAndSet(false, true)) { + // we allow only one subscriber + throw new IllegalStateException("Cannot subscribe more than once"); + } + subscriber.onSubscribe(new Flow.Subscription() { + @Override + public void request(long n) { + // no-op, we don't publish any data + } + + @Override + public void cancel() { + // no-op + } + }); + } + } + + // a Subscriber which subscribers for encrypted SSL network data that it will then + // write to a connected (SSL) Socket's OutputStream + private static final class SocketWriter implements Subscriber> { + private static final String debugTag = SocketWriter.class.getSimpleName(); + + private final Socket socket; + private final CompletableFuture completion; + private volatile Flow.Subscription subscription; + private final AtomicLong numBytesWritten = new AtomicLong(); + + private SocketWriter(final Socket socket, final CompletableFuture completion) { + this.socket = socket; + this.completion = completion; + } + + @Override + public void onSubscribe(final Flow.Subscription subscription) { + this.subscription = subscription; + println(debugTag, "onSubscribe invoked, requesting for data to write to socket"); + subscription.request(1); + } + + @Override + public void onNext(final List bufs) { + try { + final OutputStream os = + new BufferedOutputStream(this.socket.getOutputStream()); + + // these buffers contain encrypted SSL network data that we receive + // from the SSLFlowDelegate. We just write them out to the + // Socket's OutputStream. + for (final ByteBuffer buf : bufs) { + int len = buf.remaining(); + int written = writeToStream(os, buf); + assert len == written; + this.numBytesWritten.addAndGet(len); + assert !buf.hasRemaining() + : "buffer has " + buf.remaining() + " bytes left"; + this.subscription.request(1); // willing to write out more data when available + } + } catch (Throwable e) { + println(debugTag, "failed: " + e, e); + completion.completeExceptionally(e); + } + } + + @Override + public void onError(final Throwable throwable) { + println(debugTag, "error: " + throwable, throwable); + completion.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + println(debugTag, "onComplete(), total bytes written: " + this.numBytesWritten.get()); + } + + private int writeToStream(final OutputStream os, final ByteBuffer buf) throws IOException { + final byte[] b = buf.array(); + final int offset = buf.arrayOffset() + buf.position(); + final int n = buf.limit() - buf.position(); + os.write(b, offset, n); + buf.position(buf.limit()); + os.flush(); + return n; + } + } + + // a background task that keeps reading encrypted SSL network data from a connected + // (SSL) Socket and publishes this data to the SSLFlowDelegate's upstreamReader() subscriber. + // Very importantly, irrespective of how many bytes of data this SocketReader reads + // of the Socket's InputStream in one read() operation, it publishes this data to the + // upstreamReader() subscriber in very small chunks, so that when the upstreamReader() + // subscriber receives it and starts unwrapping that SSL network data, it often + // encounters a BUFFER_UNDERFLOW state. + private static final class SocketReader implements Runnable { + private static final String debugTag = SocketReader.class.getSimpleName(); + + // the size of data that will be published to the upstreamReader() subscriber. + // small enough; no other meaning to this value + private static final int VERY_SMALL_DATA_SIZE = 123; + + private final Socket socket; + private final SubmissionPublisher> publisher; + private final CompletableFuture completion; + + private SocketReader(final Socket socket, final Executor executor, + final Subscriber> incomingNetworkDataSubscriber, + final CompletableFuture completion) { + this.socket = socket; + this.completion = completion; + this.publisher = new SubmissionPublisher<>(executor, Flow.defaultBufferSize(), + (s, t) -> completion.completeExceptionally(t)); + this.publisher.subscribe(incomingNetworkDataSubscriber); + } + + @Override + public void run() { + try { + // reads off the SSLSocket the data from the "server" + final InputStream is = socket.getInputStream(); + long numBytesRead = 0; + long numBytesPublished = 0; + while (true) { + final byte[] buf = new byte[10240]; // this size doesn't matter + final int n = is.read(buf); + if (n == -1) { + println(debugTag, "got EOF, now closing resources; total read " + + numBytesRead + " bytes, total published " + numBytesPublished + + " bytes"); + closeAndComplete(is); + return; + } + println(debugTag, "read " + n + " bytes from socket"); + numBytesRead = Math.addExact(numBytesRead, n); + int remaining = n; + int index = 0; + while (remaining > 0) { + final int chunkSize = Math.min(remaining, VERY_SMALL_DATA_SIZE); + final byte[] chunk = Arrays.copyOfRange(buf, index, index + chunkSize); + index += chunkSize; + remaining -= chunkSize; + final int lagOrDrops = publisher.offer( + List.of(ByteBuffer.wrap(chunk)), 2, TimeUnit.SECONDS, null); + if (lagOrDrops < 0) { + println(debugTag, "dropped a chunk, re-offering"); + // dropped, we now re-attempt once more and if that too is dropped, + // we stop + final int newLagOrDrops = publisher.offer( + List.of(ByteBuffer.wrap(chunk)), 2, TimeUnit.SECONDS, null); + if (newLagOrDrops < 0) { + println(debugTag, "dropped the re-offered chunk; closing resources," + + " total bytes read: " + numBytesRead + + " total bytes published: " + numBytesPublished); + closeAndComplete(is); + return; + } + } + numBytesPublished += chunkSize; + println(debugTag, "published " + numBytesPublished + " bytes of total " + + numBytesRead + " bytes read"); + } + } + } catch (Throwable e) { + println(debugTag, "failed: " + e, e); + completion.completeExceptionally(e); + } + } + + private void closeAndComplete(final InputStream is) { + publisher.close(); + completion.complete(null); + Utils.close(is); + } + } + + // a background task which accepts one socket connection on a SSLServerSocket and keeps + // writing (application) data to the OutputStream of that socket. + private static final class ServerResponsePusher implements Runnable { + private static final String debugTag = ServerResponsePusher.class.getSimpleName(); + private final SSLServerSocket sslServerSocket; + private final CompletableFuture completion; + + private ServerResponsePusher(final SSLServerSocket sslServerSocket, + final CompletableFuture completion) { + this.sslServerSocket = sslServerSocket; + this.completion = completion; + } + + @Override + public void run() { + try { + // accept a connection + try (final Socket socket = this.sslServerSocket.accept()) { + println(debugTag, "Accepted connection from " + socket); + try (final OutputStream os = socket.getOutputStream()) { + final byte[] resp = new byte[10240]; // this size doesn't matter + Arrays.fill(resp, DATA_BYTE); + long numWritten = 0; + // reasonable number of times to generate enough network data + final int numTimes = 50; + for (int i = 0; i < numTimes; i++) { + println(debugTag, "writing " + resp.length + " bytes, " + + numWritten + " written so far"); + os.write(resp); + numWritten += resp.length; + os.flush(); + } + println(debugTag, "stopped writing, total bytes written: " + numWritten); + } + } + } catch (Throwable e) { + println(debugTag, "error: " + e, e); + this.completion.completeExceptionally(e); + } + } + } + + // the "downReader" Subscriber which is passed to the constructor of SSLFlowDelegate. + // This subscriber receives the (decrypted) application data. This subscriber requests + // only one item (no restriction on how many bytes are received in this one item). + private static final class AppResponseReceiver implements Subscriber> { + private static final String debugTag = AppResponseReceiver.class.getSimpleName(); + + private final byte[] expectedData = new byte[1024]; // no significance of the size + + private final AtomicLong numBytesReceived; + private volatile Flow.Subscription subscription; + private final CompletableFuture completion; + private final CompletableFuture soleExpectedAppData; + private boolean receivedOneItem; + + private AppResponseReceiver(final CompletableFuture completion, + final CompletableFuture soleExpectedAppData) { + this.numBytesReceived = new AtomicLong(0); + this.soleExpectedAppData = soleExpectedAppData; + this.completion = completion; + Arrays.fill(expectedData, DATA_BYTE); + } + + @Override + public void onSubscribe(Flow.Subscription subscription) { + println(debugTag, "onSubscribe invoked"); + this.subscription = subscription; + subscription.request(1); // the sole item request this subscriber will make + } + + @Override + public void onNext(final List buffers) { + if (receivedOneItem) { + // don't throw an exception since that will go against the Subscriber's + // specification, instead complete the future exceptionally + completion.completeExceptionally(new AssertionError("onNext() called more than" + + " once, even though no request was made")); + return; + } + receivedOneItem = true; + // these buffers contain (decrypted) application data that the SSLFlowDelegate has + // forwarded to this subscriber + for (final ByteBuffer buf : buffers) { + final int numBytes = buf.remaining(); + // verify the content of the received application data + while (buf.hasRemaining()) { + final int size = Math.min(buf.remaining(), expectedData.length); + final byte[] actual = new byte[size]; + buf.get(actual); + // this is just convenience/performance optimization - instead of checking + // one byte at a time, we compare multiple bytes + final int index = Arrays.mismatch(expectedData, 0, size, actual, 0, size); + if (index != -1) { + final String errMsg = "Unexpected byte received: " + actual[index]; + println(debugTag, "Cancelling subscription due to error: " + errMsg); + subscription.cancel(); + completion.completeExceptionally(new AssertionError(errMsg)); + return; + } + } + numBytesReceived.addAndGet(numBytes); + } + println(debugTag, "Received " + numBytesReceived.get() + " bytes," + + " will not request any more data"); + soleExpectedAppData.complete(numBytesReceived.get()); + } + + @Override + public void onError(final Throwable throwable) { + completion.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + final long n = numBytesReceived.get(); + println(debugTag, "Completed: received " + n + " bytes"); + } + } +} diff --git a/test/jdk/java/nio/channels/DatagramChannel/SendReceiveMaxSize.java b/test/jdk/java/nio/channels/DatagramChannel/SendReceiveMaxSize.java index 6f20df76deb..83854fe4a42 100644 --- a/test/jdk/java/nio/channels/DatagramChannel/SendReceiveMaxSize.java +++ b/test/jdk/java/nio/channels/DatagramChannel/SendReceiveMaxSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,6 +57,7 @@ import static java.net.StandardProtocolFamily.INET; import static java.net.StandardProtocolFamily.INET6; import static java.net.StandardSocketOptions.SO_SNDBUF; +import static java.net.StandardSocketOptions.SO_RCVBUF; import static jdk.test.lib.net.IPSupport.hasIPv4; import static jdk.test.lib.net.IPSupport.hasIPv6; import static jdk.test.lib.net.IPSupport.preferIPv4Stack; @@ -65,8 +66,6 @@ import static org.testng.Assert.assertTrue; public class SendReceiveMaxSize { - private final static int IPV4_SNDBUF = 65507; - private final static int IPV6_SNDBUF = 65527; private final static Class IOE = IOException.class; private final static Random random = RandomFactory.getRandom(); @@ -91,12 +90,12 @@ public Object[][] invariants() throws IOException { .orElse((Inet4Address) InetAddress.getByName("127.0.0.1")); testcases.add(new Object[]{ supplier(() -> DatagramChannel.open()), - IPV4_SNDBUF, + IPSupport.getMaxUDPSendBufSizeIPv4(), IPv4Addr }); testcases.add(new Object[]{ supplier(() -> DatagramChannel.open(INET)), - IPV4_SNDBUF, + IPSupport.getMaxUDPSendBufSizeIPv4(), IPv4Addr }); } @@ -107,12 +106,12 @@ public Object[][] invariants() throws IOException { .orElse((Inet6Address) InetAddress.getByName("::1")); testcases.add(new Object[]{ supplier(() -> DatagramChannel.open()), - IPV6_SNDBUF, + IPSupport.getMaxUDPSendBufSizeIPv6(), IPv6Addr }); testcases.add(new Object[]{ supplier(() -> DatagramChannel.open(INET6)), - IPV6_SNDBUF, + IPSupport.getMaxUDPSendBufSizeIPv6(), IPv6Addr }); } @@ -134,6 +133,10 @@ public void testSendReceiveMaxSize(DatagramChannelSupplier supplier, int capacit throws IOException { try (var receiver = DatagramChannel.open()) { receiver.bind(new InetSocketAddress(host, 0)); + assertTrue(receiver.getOption(SO_RCVBUF) >= capacity, + receiver.getOption(SO_RCVBUF) + + " for UDP receive buffer too small to hold capacity " + + capacity); var port = receiver.socket().getLocalPort(); var addr = new InetSocketAddress(host, port); diff --git a/test/lib/jdk/test/lib/net/IPSupport.java b/test/lib/jdk/test/lib/net/IPSupport.java index 05f3966dd5e..c9873fa644a 100644 --- a/test/lib/jdk/test/lib/net/IPSupport.java +++ b/test/lib/jdk/test/lib/net/IPSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,8 @@ package jdk.test.lib.net; +import jdk.test.lib.Platform; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -47,6 +49,9 @@ public class IPSupport { private static final boolean hasIPv6; private static final boolean preferIPv4Stack; private static final boolean preferIPv6Addresses; + private static final int IPV4_SNDBUF = 65507; + private static final int IPV6_SNDBUF = 65527; + private static final int IPV6_SNDBUF_AIX = 65487; static { try { @@ -121,7 +126,6 @@ public static final boolean preferIPv6Addresses() { return preferIPv6Addresses; } - /** * Whether or not the current networking configuration is valid or not. * @@ -164,4 +168,21 @@ public static void printPlatformSupport(PrintStream out) { out.println("preferIPv6Addresses: " + preferIPv6Addresses()); } + /** + * Return current platform's maximum size for IPv4 UDP send buffer + */ + public static final int getMaxUDPSendBufSizeIPv4() { + return IPV4_SNDBUF; + } + + /** + * Return current platform's maximum size for IPv6 UDP send buffer + */ + public static final int getMaxUDPSendBufSizeIPv6() { + if (Platform.isAix()) { + return IPV6_SNDBUF_AIX; + } else { + return IPV6_SNDBUF; + } + } }