changeset 77:280a7b75cd39

6447412: Issue with socket.close() for ssl sockets when poweroff on other system Summary: Support SSL sockets SOLINGER Reviewed-by: chegar
author xuelei
date Mon, 17 Mar 2008 03:11:29 -0400
parents 73f50a1c8634
children 1cb78400acce
files src/share/classes/sun/security/ssl/Handshaker.java src/share/classes/sun/security/ssl/OutputRecord.java src/share/classes/sun/security/ssl/SSLSocketImpl.java test/sun/security/ssl/com/sun/net/ssl/internal/ssl/SSLSocketImpl/AsyncSSLSocketClose.java
diffstat 4 files changed, 214 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/ssl/Handshaker.java	Sun Mar 16 23:46:27 2008 -0400
+++ b/src/share/classes/sun/security/ssl/Handshaker.java	Mon Mar 17 03:11:29 2008 -0400
@@ -617,7 +617,8 @@
         r.write(1);     // single byte of data
 
         if (conn != null) {
-            synchronized (conn.writeLock) {
+            conn.writeLock.lock();
+            try {
                 conn.writeRecord(r);
                 conn.changeWriteCiphers();
                 if (debug != null && Debug.isOn("handshake")) {
@@ -625,6 +626,8 @@
                 }
                 mesg.write(output);
                 output.flush();
+            } finally {
+                conn.writeLock.unlock();
             }
         } else {
             synchronized (engine.writeLock) {
--- a/src/share/classes/sun/security/ssl/OutputRecord.java	Sun Mar 16 23:46:27 2008 -0400
+++ b/src/share/classes/sun/security/ssl/OutputRecord.java	Mon Mar 17 03:11:29 2008 -0400
@@ -174,6 +174,18 @@
         return count == headerSize;
     }
 
+    /*
+     * Return true if the record is of a given alert.
+     */
+    boolean isAlert(byte description) {
+        // An alert is defined with a two bytes struct,
+        // {byte level, byte description}, following after the header bytes.
+        if (count > (headerSize + 1) && contentType == ct_alert) {
+            return buf[headerSize + 1] == description;
+        }
+
+        return false;
+    }
 
     /*
      * Compute the MAC and append it to this record.  In case we
--- a/src/share/classes/sun/security/ssl/SSLSocketImpl.java	Sun Mar 16 23:46:27 2008 -0400
+++ b/src/share/classes/sun/security/ssl/SSLSocketImpl.java	Mon Mar 17 03:11:29 2008 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright 1996-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1996-2008 Sun Microsystems, Inc.  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 +33,8 @@
 import java.security.AccessControlContext;
 import java.security.PrivilegedAction;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.crypto.BadPaddingException;
 
@@ -274,7 +276,7 @@
      * from the peer are handled properly.
      */
     private Object              handshakeLock;
-    Object                      writeLock;
+    ReentrantLock               writeLock;
     private Object              readLock;
 
     private InputRecord         inrec;
@@ -314,7 +316,6 @@
     private HashMap<HandshakeCompletedListener, AccessControlContext>
                                                         handshakeListeners;
 
-
     /*
      * Reuse the same internal input/output streams.
      */
@@ -526,7 +527,7 @@
         enabledCipherSuites = CipherSuiteList.getDefault();
         enabledProtocols = ProtocolList.getDefault();
         handshakeLock = new Object();
-        writeLock = new Object();
+        writeLock = new ReentrantLock();
         readLock = new Object();
         inrec = null;
 
@@ -677,16 +678,81 @@
         // implementations are fragile and don't like to see empty
         // records, so this also increases robustness.
         //
-        synchronized (writeLock) {
-            if (!r.isEmpty()) {
-                // r.compress(c);
-                r.addMAC(writeMAC);
-                r.encrypt(writeCipher);
-                r.write(sockOutput);
+        if (!r.isEmpty()) {
+
+            // If the record is a close notify alert, we need to honor
+            // socket option SO_LINGER. Note that we will try to send
+            // the close notify even if the SO_LINGER set to zero.
+            if (r.isAlert(Alerts.alert_close_notify) && getSoLinger() >= 0) {
+
+                // keep and clear the current thread interruption status.
+                boolean interrupted = Thread.interrupted();
+                try {
+                    if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) {
+                        try {
+                            writeRecordInternal(r);
+                        } finally {
+                            writeLock.unlock();
+                        }
+                    } else {
+                        SSLException ssle = new SSLException(
+                                "SO_LINGER timeout," +
+                                " close_notify message cannot be sent.");
+
+
+                        // For layered, non-autoclose sockets, we are not
+                        // able to bring them into a usable state, so we
+                        // treat it as fatal error.
+                        if (self != this && !autoClose) {
+                            // Note that the alert description is
+                            // specified as -1, so no message will be send
+                            // to peer anymore.
+                            fatal((byte)(-1), ssle);
+                        } else if ((debug != null) && Debug.isOn("ssl")) {
+                            System.out.println(threadName() +
+                                ", received Exception: " + ssle);
+                        }
+
+                        // RFC2246 requires that the session becomes
+                        // unresumable if any connection is terminated
+                        // without proper close_notify messages with
+                        // level equal to warning.
+                        //
+                        // RFC4346 no longer requires that a session not be
+                        // resumed if failure to properly close a connection.
+                        //
+                        // We choose to make the session unresumable if
+                        // failed to send the close_notify message.
+                        //
+                        sess.invalidate();
+                    }
+                } catch (InterruptedException ie) {
+                    // keep interrupted status
+                    interrupted = true;
+                }
+
+                // restore the interrupted status
+                if (interrupted) {
+                    Thread.currentThread().interrupt();
+                }
+            } else {
+                writeLock.lock();
+                try {
+                    writeRecordInternal(r);
+                } finally {
+                    writeLock.unlock();
+                }
             }
         }
     }
 
+    private void writeRecordInternal(OutputRecord r) throws IOException {
+        // r.compress(c);
+        r.addMAC(writeMAC);
+        r.encrypt(writeCipher);
+        r.write(sockOutput);
+    }
+
 
     /*
      * Read an application data record.  Alerts and handshake
@@ -1533,7 +1599,11 @@
             if (oldState == cs_HANDSHAKE) {
                 sockInput.skip(sockInput.available());
             }
-            sendAlert(Alerts.alert_fatal, description);
+
+            // If the description equals -1, the alert won't be sent to peer.
+            if (description != -1) {
+                sendAlert(Alerts.alert_fatal, description);
+            }
             if (cause instanceof SSLException) { // only true if != null
                 closeReason = (SSLException)cause;
             } else {
@@ -1614,7 +1684,7 @@
      * Emit alerts.  Caller must have synchronized with "this".
      */
     private void sendAlert(byte level, byte description) {
-        if (connectionState >= cs_CLOSED) {
+        if (connectionState >= cs_SENT_CLOSE) {
             return;
         }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/ssl/com/sun/net/ssl/internal/ssl/SSLSocketImpl/AsyncSSLSocketClose.java	Mon Mar 17 03:11:29 2008 -0400
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6447412
+ * @summary Issue with socket.close() for ssl sockets when poweroff on
+ *          other system
+ */
+
+import javax.net.ssl.*;
+import java.io.*;
+
+public class AsyncSSLSocketClose implements Runnable
+{
+    SSLSocket socket;
+    SSLServerSocket ss;
+
+    // Where do we find the keystores?
+    static String pathToStores = "../../../../../../../etc";
+    static String keyStoreFile = "keystore";
+    static String trustStoreFile = "truststore";
+    static String passwd = "passphrase";
+
+    public static void main(String[] args) {
+        String keyFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + keyStoreFile;
+        String trustFilename =
+            System.getProperty("test.src", "./") + "/" + pathToStores +
+                "/" + trustStoreFile;
+
+        System.setProperty("javax.net.ssl.keyStore", keyFilename);
+        System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+        System.setProperty("javax.net.ssl.trustStore", trustFilename);
+        System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+        new AsyncSSLSocketClose();
+    }
+
+    public AsyncSSLSocketClose() {
+        try {
+            SSLServerSocketFactory sslssf =
+                (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
+            ss = (SSLServerSocket) sslssf.createServerSocket(0);
+
+            SSLSocketFactory sslsf =
+                (SSLSocketFactory)SSLSocketFactory.getDefault();
+            socket = (SSLSocket)sslsf.createSocket("localhost",
+                                                        ss.getLocalPort());
+            SSLSocket serverSoc = (SSLSocket) ss.accept();
+            ss.close();
+
+            (new Thread(this)).start();
+            serverSoc.startHandshake();
+
+            try {
+                Thread.sleep(5000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            socket.setSoLinger(true, 10);
+            System.out.println("Calling Socket.close");
+            socket.close();
+            System.out.println("ssl socket get closed");
+            System.out.flush();
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    // block in write
+    public void run() {
+        try {
+            byte[] ba = new byte[1024];
+            for (int i=0; i<ba.length; i++)
+                ba[i] = 0x7A;
+
+            OutputStream os = socket.getOutputStream();
+            int count = 0;
+            while (true) {
+                count += ba.length;
+                System.out.println(count + " bytes to be written");
+                os.write(ba);
+                System.out.println(count + " bytes written");
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+}
+