Path: blob/master/test/jdk/sun/security/ssl/SSLSessionImpl/NoInvalidateSocketException.java
66645 views
/*1* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425//26// SunJSSE does not support dynamic system properties, no way to re-use27// system properties in samevm/agentvm mode.28//2930/*31* @test32* @bug 827473633* @summary Concurrent read/close of SSLSockets causes SSLSessions to be34* invalidated unnecessarily35* @library /javax/net/ssl/templates36* @run main/othervm NoInvalidateSocketException TLSv1.337* @run main/othervm NoInvalidateSocketException TLSv1.238* @run main/othervm -Djdk.tls.client.enableSessionTicketExtension=false39* NoInvalidateSocketException TLSv1.240*/41424344import java.io.*;45import javax.net.ssl.*;46import java.net.InetAddress;47import java.net.InetSocketAddress;48import java.net.SocketException;49import java.net.SocketTimeoutException;50import java.util.ArrayList;51import java.util.List;52import java.util.Objects;53import java.util.concurrent.TimeUnit;5455public class NoInvalidateSocketException extends SSLSocketTemplate {56private static final int ITERATIONS = 10;5758// This controls how long the main thread waits before closing the socket.59// This may need tweaking for different environments to get the timing60// right.61private static final int CLOSE_DELAY = 10;6263private static SSLContext clientSSLCtx;64private static SSLSocket theSSLSocket;65private static SSLSession theSSLSession;66private static InputStream theInputStream;67private static String theSSLSocketHashCode;68private static SSLSession lastSSLSession;69private static final List<SSLSocket> serverCleanupList = new ArrayList<>();70private static String tlsVersion = null;7172private static int invalidSessCount = 0;73private static volatile boolean readFromSocket = false;74private static volatile boolean finished = false;7576public static void main(String[] args) throws Exception {77if (System.getProperty("javax.net.debug") == null) {78System.setProperty("javax.net.debug", "session");79}8081if (args != null && args.length >= 1) {82tlsVersion = args[0];83}8485new NoInvalidateSocketException(true).run();86if (invalidSessCount > 0) {87throw new RuntimeException("One or more sessions were improperly " +88"invalidated.");89}90}9192public NoInvalidateSocketException(boolean sepSrvThread) {93super(sepSrvThread);94}9596@Override97public boolean isCustomizedClientConnection() {98return true;99}100101@Override102public void runClientApplication(int serverPort) {103Thread.currentThread().setName("Main Client Thread");104105// Create the SSLContext we'll use for client sockets for the106// duration of the test.107try {108clientSSLCtx = createClientSSLContext();109} catch (Exception e) {110throw new RuntimeException("Failed to create client ctx", e);111}112113// Create the reader thread114ReaderThread readerThread = new ReaderThread();115readerThread.setName("Client Reader Thread");116readerThread.start();117118try {119for (int i = 0; i < ITERATIONS; i++) {120openSSLSocket();121doHandshake();122getInputStream();123getAndCompareSession();124125// Perform the Close/Read MT collision126readCloseMultiThreaded();127128// Check to make sure that the initially negotiated session129// remains intact.130isSessionValid();131132lastSSLSession = theSSLSession;133134// Insert a short gap between iterations135Thread.sleep(1000);136System.out.println();137}138} catch (Exception e) {139logToConsole("Unexpected Exception: " + e);140} finally {141// Tell the reader thread to finish142finished = true;143}144}145146private void readCloseMultiThreaded() throws IOException,147InterruptedException {148// Tell the reader thread to start trying to read from this149// socket150readFromSocket = true;151152// Short pause to give the reader thread time to start153// reading.154if (CLOSE_DELAY > 0) {155Thread.sleep(CLOSE_DELAY);156}157158// The problem happens when the reader thread tries to read159// from the socket while this thread is in the close() call160closeSSLSocket();161162// Pause to give the reader thread time to discover that the163// socket is closed and throw a SocketException164Thread.sleep(500);165}166167private class ReaderThread extends Thread {168public void run() {169// This thread runs in a tight loop until170// readFromSocket == true171while (!finished) {172if (readFromSocket) {173int result = 0;174try {175// If the timing is just176// right, this will throw a SocketException177// and the SSLSession will be178// invalidated.179result = readFromSSLSocket();180} catch (Exception e) {181logToConsole("Exception reading from SSLSocket@" +182theSSLSocketHashCode + ": " + e);183e.printStackTrace(System.out);184185// Stop trying to read from186// the socket now187readFromSocket = false;188}189190if (result == -1) {191logToConsole("Reached end of stream reading from " +192"SSLSocket@" + theSSLSocketHashCode);193194// Stop trying to read from195// the socket now196readFromSocket = false;197}198}199}200}201}202203private void openSSLSocket() throws IOException {204theSSLSocket = (SSLSocket)clientSSLCtx.getSocketFactory().205createSocket(serverAddress, serverPort);206if (tlsVersion != null) {207theSSLSocket.setEnabledProtocols(new String[] { tlsVersion });208}209theSSLSocketHashCode = String.format("%08x", theSSLSocket.hashCode());210logToConsole("Opened SSLSocket@" + theSSLSocketHashCode);211}212213private void doHandshake() throws IOException {214logToConsole("Started handshake on SSLSocket@" +215theSSLSocketHashCode);216theSSLSocket.startHandshake();217logToConsole("Finished handshake on SSLSocket@" +218theSSLSocketHashCode);219}220221private void getInputStream() throws IOException {222theInputStream = theSSLSocket.getInputStream();223}224225private void getAndCompareSession() {226theSSLSession = theSSLSocket.getSession();227228// Have we opened a new session or re-used the last one?229if (lastSSLSession == null ||230!theSSLSession.equals(lastSSLSession)) {231logToConsole("*** OPENED NEW SESSION ***: " +232theSSLSession);233} else {234logToConsole("*** RE-USING PREVIOUS SESSION ***: " +235theSSLSession + ")");236}237}238239private void closeSSLSocket() throws IOException {240logToConsole("Closing SSLSocket@" + theSSLSocketHashCode);241theSSLSocket.close();242logToConsole("Closed SSLSocket@" + theSSLSocketHashCode);243}244245private int readFromSSLSocket() throws Exception {246logToConsole("Started reading from SSLSocket@" +247theSSLSocketHashCode);248int result = theInputStream.read();249logToConsole("Finished reading from SSLSocket@" +250theSSLSocketHashCode + ": result = " + result);251return result;252}253254private void isSessionValid() {255// Is the session still valid?256if (theSSLSession.isValid()) {257logToConsole("*** " + theSSLSession + " IS VALID ***");258} else {259logToConsole("*** " + theSSLSession + " IS INVALID ***");260invalidSessCount++;261}262}263264private static void logToConsole(String s) {265System.out.println(System.nanoTime() + ": " +266Thread.currentThread().getName() + ": " + s);267}268269@Override270public void doServerSide() throws Exception {271Thread.currentThread().setName("Server Listener Thread");272SSLContext context = createServerSSLContext();273SSLServerSocketFactory sslssf = context.getServerSocketFactory();274InetAddress serverAddress = this.serverAddress;275SSLServerSocket sslServerSocket = serverAddress == null ?276(SSLServerSocket)sslssf.createServerSocket(serverPort)277: (SSLServerSocket)sslssf.createServerSocket();278if (serverAddress != null) {279sslServerSocket.bind(new InetSocketAddress(serverAddress,280serverPort));281}282configureServerSocket(sslServerSocket);283serverPort = sslServerSocket.getLocalPort();284logToConsole("Listening on " + sslServerSocket.getLocalSocketAddress());285286// Signal the client, the server is ready to accept connection.287serverCondition.countDown();288289// Try to accept a connection in 5 seconds.290// We will do this in a loop until the client flips the291// finished variable to true292SSLSocket sslSocket;293294int timeoutCount = 0;295try {296do {297try {298sslSocket = (SSLSocket) sslServerSocket.accept();299timeoutCount = 0; // Reset the timeout counter;300logToConsole("Accepted connection from " +301sslSocket.getRemoteSocketAddress());302303// Add the socket to the cleanup list so it can get304// closed at the end of the test305serverCleanupList.add(sslSocket);306307boolean clientIsReady =308clientCondition.await(30L, TimeUnit.SECONDS);309if (clientIsReady) {310// Handle the connection in a new thread311ServerHandlerThread sht = null;312try {313sht = new ServerHandlerThread(sslSocket);314sht.start();315} finally {316if (sht != null) {317sht.join();318}319}320}321} catch (SocketTimeoutException ste) {322timeoutCount++;323// If we are finished then we can return, otherwise324// check if we've timed out too many times (an exception325// case). One way or the other we will exit eventually.326if (finished) {327return;328} else if (timeoutCount >= 3) {329logToConsole("Server accept timeout exceeded");330throw ste;331}332}333} while (!finished);334} finally {335sslServerSocket.close();336// run through the server cleanup list and close those sockets337// as well.338for (SSLSocket sock : serverCleanupList) {339try {340if (sock != null) {341sock.close();342}343} catch (IOException ioe) {344// Swallow these close failures as the server itself345// is shutting down anyway.346}347}348}349}350351@Override352public void configureServerSocket(SSLServerSocket socket) {353try {354socket.setReuseAddress(true);355socket.setSoTimeout(5000);356} catch (SocketException se) {357// Rethrow as unchecked to satisfy the override signature358throw new RuntimeException(se);359}360}361362@Override363public void runServerApplication(SSLSocket sslSocket) {364Thread.currentThread().setName("Server Reader Thread");365SSLSocket sock = null;366sock = sslSocket;367try {368BufferedReader is = new BufferedReader(369new InputStreamReader(sock.getInputStream()));370PrintWriter os = new PrintWriter(new BufferedWriter(371new OutputStreamWriter(sock.getOutputStream())));372373// Only handle a single burst of data374char[] buf = new char[1024];375int dataRead = is.read(buf);376logToConsole(String.format("Received: %d bytes of data\n",377dataRead));378379os.println("Received connection from client");380os.flush();381} catch (IOException ioe) {382// Swallow these exceptions for this test383}384}385386private class ServerHandlerThread extends Thread {387SSLSocket sock;388ServerHandlerThread(SSLSocket socket) {389this.sock = Objects.requireNonNull(socket, "Illegal null socket");390}391392@Override393public void run() {394try {395runServerApplication(sock);396} catch (Exception exc) {397// Wrap inside an unchecked exception to satisfy Runnable398throw new RuntimeException(exc);399}400}401402void close() {403try {404sock.close();405} catch (IOException e) {406// swallow this exception407}408}409}410}411412413