/* * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, * and the EPL 1.0 (http://h2database.com/html/license.html). * Initial Developer: Sergi Vladykin */ package org.h2.test.unit; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.h2.engine.SysProperties; import org.h2.test.TestBase; import org.h2.util.NetUtils; import org.h2.util.Task; /** * Test the network utilities from {@link NetUtils}. * * @author Sergi Vladykin * @author Tomas Pospichal */ public class TestNetUtils extends TestBase { private static final int WORKER_COUNT = 10; private static final int PORT = 9111; private static final int WAIT_MILLIS = 100; private static final int WAIT_LONGER_MILLIS = 2 * WAIT_MILLIS; private static final String TASK_PREFIX = "ServerSocketThread-"; /** * Run just this test. * * @param a ignored */ public static void main(String... a) throws Exception { TestBase.createCaller().init().test(); } @Override public void test() throws Exception { testAnonymousTlsSession(); testTlsSessionWithServerSideAnonymousDisabled(); testFrequentConnections(true, 100); testFrequentConnections(false, 1000); } /** * With default settings, H2 client SSL socket should be able to connect * to an H2 server SSL socket using an anonymous cipher suite * (no SSL certificate is needed). */ private void testAnonymousTlsSession() throws Exception { assertTrue("Failed assumption: the default value of ENABLE_ANONYMOUS_TLS" + " property should be true", SysProperties.ENABLE_ANONYMOUS_TLS); boolean ssl = true; Task task = null; ServerSocket serverSocket = null; Socket socket = null; try { serverSocket = NetUtils.createServerSocket(PORT, ssl); serverSocket.setSoTimeout(WAIT_LONGER_MILLIS); task = createServerSocketTask(serverSocket); task.execute(TASK_PREFIX + "AnonEnabled"); Thread.sleep(WAIT_MILLIS); socket = NetUtils.createLoopbackSocket(PORT, ssl); assertTrue("loopback anon socket should be connected", socket.isConnected()); SSLSession session = ((SSLSocket) socket).getSession(); assertTrue("TLS session should be valid when anonymous TLS is enabled", session.isValid()); // in case of handshake failure: // the cipher suite is the pre-handshake SSL_NULL_WITH_NULL_NULL assertContains(session.getCipherSuite(), "_anon_"); } finally { closeSilently(socket); closeSilently(serverSocket); if (task != null) { // SSL server socket should succeed using an anonymous cipher // suite, and not throw javax.net.ssl.SSLHandshakeException assertNull(task.getException()); task.join(); } } } /** * TLS connections (without trusted certificates) should fail if the server * does not allow anonymous TLS. * The global property ENABLE_ANONYMOUS_TLS cannot be modified for the test; * instead, the server socket is altered. */ private void testTlsSessionWithServerSideAnonymousDisabled() throws Exception { boolean ssl = true; Task task = null; ServerSocket serverSocket = null; Socket socket = null; try { serverSocket = NetUtils.createServerSocket(PORT, ssl); serverSocket.setSoTimeout(WAIT_LONGER_MILLIS); // emulate the situation ENABLE_ANONYMOUS_TLS=false on server side String[] defaultCipherSuites = SSLContext.getDefault().getServerSocketFactory() .getDefaultCipherSuites(); ((SSLServerSocket) serverSocket).setEnabledCipherSuites(defaultCipherSuites); task = createServerSocketTask(serverSocket); task.execute(TASK_PREFIX + "AnonDisabled"); Thread.sleep(WAIT_MILLIS); socket = NetUtils.createLoopbackSocket(PORT, ssl); assertTrue("loopback socket should be connected", socket.isConnected()); // Java 6 API does not have getHandshakeSession() which could // reveal the actual cipher selected in the attempted handshake SSLSession session = ((SSLSocket) socket).getSession(); assertFalse("TLS session should be invalid when the server" + "disables anonymous TLS", session.isValid()); // the SSL handshake should fail, because non-anon ciphers require // a trusted certificate assertEquals("SSL_NULL_WITH_NULL_NULL", session.getCipherSuite()); } finally { closeSilently(socket); closeSilently(serverSocket); if (task != null) { assertTrue(task.getException() != null); assertEquals(javax.net.ssl.SSLHandshakeException.class.getName(), task.getException().getClass().getName()); assertContains(task.getException().getMessage(), "certificate_unknown"); task.join(); } } } private Task createServerSocketTask(final ServerSocket serverSocket) { Task task = new Task() { @Override public void call() throws Exception { Socket ss = null; try { ss = serverSocket.accept(); ss.getOutputStream().write(123); } finally { closeSilently(ss); } } }; return task; } /** * Close a socket, ignoring errors * * @param socket the socket */ void closeSilently(Socket socket) { try { socket.close(); } catch (Exception e) { // ignore } } /** * Close a server socket, ignoring errors * * @param socket the server socket */ void closeSilently(ServerSocket socket) { try { socket.close(); } catch (Exception e) { // ignore } } private static void testFrequentConnections(boolean ssl, int count) throws Exception { final ServerSocket serverSocket = NetUtils.createServerSocket(PORT, ssl); final AtomicInteger counter = new AtomicInteger(count); Task serverThread = new Task() { @Override public void call() { while (!stop) { try { Socket socket = serverSocket.accept(); // System.out.println("opened " + counter); socket.close(); } catch (Exception e) { // ignore } } // System.out.println("stopped "); } }; serverThread.execute(); try { Set<ConnectWorker> workers = new HashSet<ConnectWorker>(); for (int i = 0; i < WORKER_COUNT; i++) { workers.add(new ConnectWorker(ssl, counter)); } // ensure the server is started Thread.sleep(100); for (ConnectWorker worker : workers) { worker.start(); } for (ConnectWorker worker : workers) { worker.join(); Exception e = worker.getException(); if (e != null) { e.printStackTrace(); } } } finally { try { serverSocket.close(); } catch (Exception e) { // ignore } serverThread.get(); } } /** * A worker thread to test connecting. */ private static class ConnectWorker extends Thread { private final boolean ssl; private final AtomicInteger counter; private Exception exception; ConnectWorker(boolean ssl, AtomicInteger counter) { this.ssl = ssl; this.counter = counter; } @Override public void run() { try { while (!isInterrupted() && counter.decrementAndGet() > 0) { Socket socket = NetUtils.createLoopbackSocket(PORT, ssl); try { socket.close(); } catch (IOException e) { // ignore } } } catch (Exception e) { exception = new Exception("count: " + counter, e); } } public Exception getException() { return exception; } } }