8204233: Add configurable option for enhanced socket IOException messages
authormichaelm
Fri, 22 Jun 2018 18:10:20 +0100
changeset 50722 bc104aaf24e9
parent 50721 e541c1b68b89
child 50723 671b02f0e450
8204233: Add configurable option for enhanced socket IOException messages Reviewed-by: alanb, chegar
src/java.base/share/classes/java/net/AbstractPlainSocketImpl.java
src/java.base/share/classes/sun/net/util/SocketExceptions.java
src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java
src/java.base/share/conf/security/java.security
src/java.base/unix/classes/sun/nio/ch/UnixAsynchronousSocketChannelImpl.java
src/java.base/windows/classes/sun/nio/ch/WindowsAsynchronousSocketChannelImpl.java
test/jdk/java/net/Socket/ExceptionText.java
--- 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;
+        }
+    }
+}