diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index 1496be5f5c6..48cc1a7bb02 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -68,13 +68,14 @@ NET_ThrowByNameWithLastError(JNIEnv *env, const char *name, void NET_ThrowNew(JNIEnv *env, int errorNumber, char *msg) { char fullMsg[512]; - if (!msg) { - msg = "no further information"; - } switch(errorNumber) { case EBADF: - jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg); - JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg); + if (msg == NULL) { + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg); + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg); + } break; default: errno = errorNumber; diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index bac3d1438ab..5abdd8d4c2e 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -139,13 +139,6 @@ NET_ThrowNew(JNIEnv *env, int errorNum, char *msg) return; } - /* - * Default message text if not provided - */ - if (!msg) { - msg = "no further information"; - } - /* * Check table for known winsock errors */ @@ -163,13 +156,22 @@ NET_ThrowNew(JNIEnv *env, int errorNum, char *msg) */ if (i < table_size) { excP = (char *)winsock_errors[i].exc; - jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", - (char *)winsock_errors[i].errString, msg); + if (msg == NULL) { + jio_snprintf(fullMsg, sizeof(fullMsg), "%s", + (char *)winsock_errors[i].errString); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", + (char *)winsock_errors[i].errString, msg); + } } else { - jio_snprintf(fullMsg, sizeof(fullMsg), - "Unrecognized Windows Sockets error: %d: %s", - errorNum, msg); - + if (msg == NULL) { + jio_snprintf(fullMsg, sizeof(fullMsg), + "Unrecognized Windows Sockets error: %d", errorNum); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), + "Unrecognized Windows Sockets error: %d: %s", + errorNum, msg); + } } /* diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index 814f502c48a..adfd67b5017 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -733,7 +733,7 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv* env, jclass this, jobject fdo, jlong tim NET_ThrowNew(env, lastError, "getsockopt"); } } else if (optError != NO_ERROR) { - NET_ThrowNew(env, optError, "getsockopt"); + NET_ThrowNew(env, optError, NULL); } return JNI_FALSE; } diff --git a/test/jdk/java/nio/channels/Selector/ConnectionRefusedMessage.java b/test/jdk/java/nio/channels/Selector/ConnectionRefusedMessage.java new file mode 100644 index 00000000000..5e7b6395a66 --- /dev/null +++ b/test/jdk/java/nio/channels/Selector/ConnectionRefusedMessage.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2026, 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 java.io.IOException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/* + * @test + * @bug 8376290 + * @summary Verify that when a SocketChannel is registered with a Selector + * with an interest in CONNECT operation, then SocketChannel.finishConnect() + * throws the correct exception message, if the connect() fails + * @run junit ${test.main.class} + */ +class ConnectionRefusedMessage { + + /* + * On a non-blocking SocketChannel, registered with a Selector, this test method + * attempts a SocketChannel.connect() against an address that is expected to return + * Connection refused. The test then calls SocketChannel.finishConnect() when the + * Selector makes available the ready key for this connect operation and expects + * that finishConnect() throws a ConnectException with the expected exception message. + */ + @Test + void testFinishConnect() throws Exception { + // find a suitable address against which the connect() attempt + // will result in a Connection refused exception + final InetSocketAddress destAddr = findSuitableRefusedAddress(); + // skip the test if we couldn't find a port which would raise a connection refused error + assumeTrue(destAddr != null, + "couldn't find a suitable port which will generate a connection refused error"); + try (Selector selector = Selector.open(); + SocketChannel sc = SocketChannel.open()) { + + // non-blocking + sc.configureBlocking(false); + sc.register(selector, SelectionKey.OP_CONNECT); + + System.err.println("establishing connection to " + destAddr); + boolean connected = sc.connect(destAddr); + // this test checks the exception message of a ConnectException, so it's + // OK to skip the test if something unexpectedly accepted the connection + assumeFalse(connected, "unexpectedly connected to " + destAddr); + // wait for ready ops + int numReady = selector.select(Duration.ofMinutes(10).toMillis()); + System.err.println("Num ready keys = " + numReady); + for (SelectionKey readyKey : selector.selectedKeys()) { + System.err.println("ready key: " + readyKey); + assertTrue(readyKey.isConnectable(), "unexpected key, readyOps = " + + readyKey.readyOps()); + readyKey.cancel(); + try { + boolean success = sc.finishConnect(); + // this test checks the exception message of a ConnectException, so it's + // OK to skip the test if something unexpectedly accepted the connection + assumeFalse(success, "unexpectedly connected to " + destAddr); + // this test doesn't expect finishConnect() to return normally + // with a return value of false + fail("ConnectException was not thrown"); + } catch (ConnectException ce) { + System.err.println("got (expected) ConnectException - " + ce); + // verify exception message + if (!"Connection refused".equals(ce.getMessage())) { + // propagate the original exception + fail("unexpected exception message: " + ce.getMessage(), ce); + } + } + } + } + } + + // Try to find a suitable port to provoke a "Connection Refused" error. + private static InetSocketAddress findSuitableRefusedAddress() throws IOException { + final InetAddress loopbackAddr = InetAddress.getLoopbackAddress(); + // Ports 47, 51, 61 are in the IANA reserved port list, and + // are currently unassigned to any specific service. + // We use them here on the assumption that there won't be + // any service listening on them. + InetSocketAddress destAddr = new InetSocketAddress(loopbackAddr, 47); + try (SocketChannel sc1 = SocketChannel.open(destAddr)) { + // we managed to connect (unexpectedly), let's try the next reserved port + destAddr = new InetSocketAddress(loopbackAddr, 51); + try (SocketChannel sc2 = SocketChannel.open(destAddr)) { + } + // we managed to connect (unexpectedly again), let's try the next reserved port + // as a last attempt + destAddr = new InetSocketAddress(loopbackAddr, 61); + try (SocketChannel sc3 = SocketChannel.open(destAddr)) { + } + return null; + } catch (ConnectException x) { + } + // the address which will generate a connection refused, when a connection is attempted + return destAddr; + } +}