# HG changeset patch # User xuelei # Date 1205737889 14400 # Node ID 01ef29ca378f76cbdfc842f2030c854d174bd67e # Parent d70d3cc4dbe3a28cd399684562343bfebb7ecfa5 6447412: Issue with socket.close() for ssl sockets when poweroff on other system Summary: Support SSL sockets SOLINGER Reviewed-by: chegar diff -r d70d3cc4dbe3 -r 01ef29ca378f jdk/src/share/classes/sun/security/ssl/Handshaker.java --- a/jdk/src/share/classes/sun/security/ssl/Handshaker.java Sun Mar 16 23:46:27 2008 -0400 +++ b/jdk/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) { diff -r d70d3cc4dbe3 -r 01ef29ca378f jdk/src/share/classes/sun/security/ssl/OutputRecord.java --- a/jdk/src/share/classes/sun/security/ssl/OutputRecord.java Sun Mar 16 23:46:27 2008 -0400 +++ b/jdk/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 diff -r d70d3cc4dbe3 -r 01ef29ca378f jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java --- a/jdk/src/share/classes/sun/security/ssl/SSLSocketImpl.java Sun Mar 16 23:46:27 2008 -0400 +++ b/jdk/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 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; } diff -r d70d3cc4dbe3 -r 01ef29ca378f jdk/test/sun/security/ssl/com/sun/net/ssl/internal/ssl/SSLSocketImpl/AsyncSSLSocketClose.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/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