8204233: Add configurable option for enhanced socket IOException messages
Reviewed-by: alanb, chegar
--- a/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java Fri Jun 22 18:19:26 2018 +0200
+++ b/src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java Fri Jun 22 18:10:20 2018 +0100
@@ -37,6 +37,7 @@
import sun.net.ConnectionResetException;
import sun.net.NetHooks;
import sun.net.ResourceManager;
+import sun.net.util.SocketExceptions;
/**
* Default Socket Implementation. This implementation does
@@ -415,7 +416,7 @@
}
} catch (IOException e) {
close();
- throw e;
+ throw SocketExceptions.of(e, new InetSocketAddress(address, port));
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/net/util/SocketExceptions.java Fri Jun 22 18:10:20 2018 +0100
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.net.util;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.InetSocketAddress;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.Security;
+
+public final class SocketExceptions {
+ private SocketExceptions() {}
+
+ /**
+ * Security or system property which specifies categories of
+ * (potentially sensitive) information that may be included
+ * in exception text. This class only defines one category:
+ * "hostInfo" which represents the hostname and port number
+ * of the remote peer relating to a socket exception.
+ * The property value is a comma separated list of
+ * case insignificant category names.
+ */
+ private static final String enhancedTextPropname = "jdk.net.includeInExceptions";
+
+ private static final boolean enhancedExceptionText = initTextProp();
+
+ private static boolean initTextProp() {
+ return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ String val = System.getProperty(enhancedTextPropname);
+ if (val == null) {
+ val = Security.getProperty(enhancedTextPropname);
+ if (val == null)
+ return false;
+ }
+ String[] tokens = val.split(",");
+ for (String token : tokens) {
+ if (token.equalsIgnoreCase("hostinfo"))
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+
+ /**
+ * Utility which takes an exception and returns either the same exception
+ * or a new exception of the same type with the same stack trace
+ * and detail message enhanced with addressing information from the
+ * given InetSocketAddress.
+ *
+ * If the system/security property "jdk.net.enhanceExceptionText" is not
+ * set or is false, then the original exception is returned.
+ *
+ * Only specific IOException subtypes are supported.
+ */
+ public static IOException of(IOException e, InetSocketAddress address) {
+ if (!enhancedExceptionText || address == null)
+ return e;
+ int port = address.getPort();
+ String host = address.getHostString();
+ StringBuilder sb = new StringBuilder();
+ sb.append(e.getMessage());
+ sb.append(": ");
+ sb.append(host);
+ sb.append(':');
+ sb.append(Integer.toString(port));
+ String enhancedMsg = sb.toString();
+ return create(e, enhancedMsg);
+ }
+
+ // return a new instance of the same type with the given detail
+ // msg, or if the type doesn't support detail msgs, return given
+ // instance.
+
+ private static IOException create(IOException e, String msg) {
+ return AccessController.doPrivileged(new PrivilegedAction<IOException>() {
+ public IOException run() {
+ try {
+ Class<?> clazz = e.getClass();
+ Constructor<?> ctor = clazz.getConstructor(String.class);
+ IOException e1 = (IOException)(ctor.newInstance(msg));
+ e1.setStackTrace(e.getStackTrace());
+ return e1;
+ } catch (Exception e0) {
+ // Some eg AsynchronousCloseException have no detail msg
+ return e;
+ }
+ }
+ });
+ }
+}
--- a/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java Fri Jun 22 18:19:26 2018 +0200
+++ b/src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java Fri Jun 22 18:10:20 2018 +0100
@@ -54,6 +54,7 @@
import sun.net.NetHooks;
import sun.net.ext.ExtendedSocketOptions;
+import sun.net.util.SocketExceptions;
import static sun.net.ext.ExtendedSocketOptions.SOCK_STREAM;
/**
@@ -706,7 +707,7 @@
} catch (IOException ioe) {
// connect failed, close the channel
close();
- throw ioe;
+ throw SocketExceptions.of(ioe, isa);
}
}
@@ -792,7 +793,7 @@
} catch (IOException ioe) {
// connect failed, close the channel
close();
- throw ioe;
+ throw SocketExceptions.of(ioe, remoteAddress);
}
}
--- a/src/java.base/share/conf/security/java.security Fri Jun 22 18:19:26 2018 +0200
+++ b/src/java.base/share/conf/security/java.security Fri Jun 22 18:10:20 2018 +0100
@@ -993,10 +993,10 @@
#
# An IOR type check filter, if configured, is used by an ORB during
# an ORB::string_to_object invocation to check the veracity of the type encoded
-# in the ior string.
+# in the ior string.
#
# The filter pattern consists of a semi-colon separated list of class names.
-# The configured list contains the binary class names of the IDL interface types
+# The configured list contains the binary class names of the IDL interface types
# corresponding to the IDL stub class to be instantiated.
# As such, a filter specifies a list of IDL stub classes that will be
# allowed by an ORB when an ORB::string_to_object is invoked.
@@ -1025,3 +1025,16 @@
# and javax.crypto.spec.SecretKeySpec and rejects all the others.
jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\
java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!*
+
+#
+# Enhanced exception message text
+#
+# By default, socket exception messages do not include potentially sensitive
+# information such as hostnames or port numbers. This property may be set to one
+# or more values, separated by commas, and with no white-space. Each value
+# represents a category of enhanced information. Currently, the only category defined
+# is "hostInfo" which enables more detailed information in the IOExceptions
+# thrown by java.net.Socket and also the socket types in the java.nio.channels package.
+# The setting in this file can be overridden by a system property of the same name
+# and with the same syntax and possible values.
+#jdk.net.includeInExceptions=hostInfo
--- a/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java Fri Jun 22 18:19:26 2018 +0200
+++ b/src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java Fri Jun 22 18:10:20 2018 +0100
@@ -32,6 +32,7 @@
import java.io.IOException;
import java.io.FileDescriptor;
import sun.net.NetHooks;
+import sun.net.util.SocketExceptions;
import sun.security.action.GetPropertyAction;
/**
@@ -258,6 +259,10 @@
end();
}
if (e != null) {
+ if (e instanceof IOException) {
+ var isa = (InetSocketAddress)pendingRemote;
+ e = SocketExceptions.of((IOException)e, isa);
+ }
// close channel if connection cannot be established
try {
close();
@@ -350,6 +355,9 @@
// close channel if connect fails
if (e != null) {
+ if (e instanceof IOException) {
+ e = SocketExceptions.of((IOException)e, isa);
+ }
try {
close();
} catch (Throwable suppressed) {
--- a/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java Fri Jun 22 18:19:26 2018 +0200
+++ b/src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java Fri Jun 22 18:10:20 2018 +0100
@@ -35,6 +35,7 @@
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import jdk.internal.misc.Unsafe;
+import sun.net.util.SocketExceptions;
/**
* Windows implementation of AsynchronousSocketChannel using overlapped I/O.
@@ -253,7 +254,8 @@
if (exc != null) {
closeChannel();
- result.setFailure(toIOException(exc));
+ exc = SocketExceptions.of(toIOException(exc), remote);
+ result.setFailure(exc);
}
Invoker.invoke(result);
}
@@ -278,7 +280,9 @@
// can't close channel while in begin/end block
if (exc != null) {
closeChannel();
- result.setFailure(toIOException(exc));
+ IOException ee = toIOException(exc);
+ ee = SocketExceptions.of(ee, remote);
+ result.setFailure(ee);
}
if (canInvokeDirect) {
@@ -293,11 +297,13 @@
*/
@Override
public void failed(int error, IOException x) {
+ x = SocketExceptions.of(x, remote);
if (isOpen()) {
closeChannel();
result.setFailure(x);
} else {
- result.setFailure(new AsynchronousCloseException());
+ x = SocketExceptions.of(new AsynchronousCloseException(), remote);
+ result.setFailure(x);
}
Invoker.invoke(result);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/Socket/ExceptionText.java Fri Jun 22 18:10:20 2018 +0100
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018, 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
+ * @library /test/lib
+ * @build jdk.test.lib.Utils
+ * @bug 8204233
+ * @summary Add configurable option for enhanced socket IOException messages
+ * @run main/othervm ExceptionText
+ * @run main/othervm -Djdk.net.includeInExceptions= ExceptionText
+ * @run main/othervm -Djdk.net.includeInExceptions=hostInfo ExceptionText
+ * @run main/othervm -Djdk.net.includeInExceptions=somethingElse ExceptionText
+ */
+
+import java.net.*;
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.*;
+import java.util.concurrent.ExecutionException;
+import jdk.test.lib.Utils;
+
+public class ExceptionText {
+
+ enum TestTarget {SOCKET, CHANNEL, ASYNC_CHANNEL};
+
+ static boolean propEnabled() {
+ String val = System.getProperty("jdk.net.includeInExceptions");
+ if ("hostinfo".equalsIgnoreCase(val))
+ return true;
+ return false;
+ }
+
+ public static void main(String args[]) throws Exception {
+ boolean prop = propEnabled();
+ test(prop);
+ }
+
+ static final InetSocketAddress dest = Utils.refusingEndpoint();
+ static final String PORT = ":" + Integer.toString(dest.getPort());
+ static final String HOST = dest.getHostString();
+
+ static void test(boolean withProperty) {
+ // Socket
+ IOException e = getException(TestTarget.SOCKET);
+ checkResult(e, withProperty);
+ // SocketChannel
+ e = getException(TestTarget.CHANNEL);
+ checkResult(e, withProperty);
+ // AsyncSocketChannel
+ e = getException(TestTarget.ASYNC_CHANNEL);
+ checkResult(e, withProperty);
+ }
+
+ static void checkResult(IOException e, boolean withProperty) {
+ String msg = e.getMessage();
+ if (!withProperty) {
+ if (msg.contains(HOST) || msg.contains(PORT)) {
+ System.err.println("msg = " + msg);
+ throw new RuntimeException("Test failed: exception contains address info");
+ }
+ } else {
+ if (!msg.contains(HOST) || !msg.contains(PORT)) {
+ if (e instanceof ClosedChannelException)
+ return; // has no detail msg
+ System.err.println("msg = " + msg);
+ throw new RuntimeException("Test failed: exception does not contain address info");
+ }
+ }
+ }
+
+ static IOException getException(TestTarget target) {
+ try {
+ if (target == TestTarget.SOCKET) {
+ Socket s = new Socket();
+ s.connect(dest);
+ } else if (target == TestTarget.CHANNEL) {
+ SocketChannel c = SocketChannel.open(dest);
+ } else if (target == TestTarget.ASYNC_CHANNEL) {
+ AsynchronousSocketChannel c = AsynchronousSocketChannel.open();
+ try {
+ c.connect(dest).get();
+ } catch (InterruptedException | ExecutionException ee) {
+ if (ee.getCause() instanceof IOException)
+ throw (IOException)ee.getCause();
+ throw new RuntimeException(ee.getCause());
+ }
+ }
+ return null;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return e;
+ }
+ }
+}